braceless one-statement for-loops
This commit is contained in:
+1
-1
@@ -87,7 +87,7 @@ fields. `_` is not a keyword member name.
|
|||||||
- checked integer `+ - *`, unary `-`, float-only `/`, IEEE float arithmetic, comparisons, `!`, `and`, and `or`
|
- checked integer `+ - *`, unary `-`, float-only `/`, IEEE float arithmetic, comparisons, `!`, `and`, and `or`
|
||||||
- assignments and compound assignments `+= -= *= /=` with single evaluation of complex lvalues; `/=` is float-only
|
- assignments and compound assignments `+= -= *= /=` with single evaluation of complex lvalues; `/=` is float-only
|
||||||
- field access through struct values and pointers, index/slice bounds contextually coerced to `usize`, and unsigned narrower index support
|
- field access through struct values and pointers, index/slice bounds contextually coerced to `usize`, and unsigned narrower index support
|
||||||
- boolean `if` / `else if` / `else`, braceless single-statement branches, and optional parenthesized conditions
|
- boolean `if` / `else if` / `else` and `for` loops with braceless single-statement bodies when the preceding expression is parenthesized or a function call
|
||||||
- `while` loops with optional post-iteration update clauses
|
- `while` loops with optional post-iteration update clauses
|
||||||
- `for` loops over ranges, arrays, slices, and pointers-to-arrays with copy captures, pointer captures `|@item|`, and optional `usize` index captures
|
- `for` loops over ranges, arrays, slices, and pointers-to-arrays with copy captures, pointer captures `|@item|`, and optional `usize` index captures
|
||||||
- `break`, `continue`, labeled `break :label`, labeled `continue :label`, and labeled plain blocks; `break :label` can cross nested scopes to exit a labeled block
|
- `break`, `continue`, labeled `break :label`, labeled `continue :label`, and labeled plain blocks; `break :label` can cross nested scopes to exit a labeled block
|
||||||
|
|||||||
@@ -252,15 +252,15 @@
|
|||||||
c :: b + 3 # c is constrained to `i32`
|
c :: b + 3 # c is constrained to `i32`
|
||||||
```
|
```
|
||||||
|
|
||||||
16. for if statements, allow `if (cond) one-line statement` or `if some_func(some_arg) one-line statement` (instead of forcing either `if (cond) { block }` or `if cond { block }`) (implemented)
|
16. allow brace-less single-statement `if` and `for` bodies (implemented)
|
||||||
- if statements without a bracketed body must wrap the condition in parentheses UNLESS it's a function call
|
- brace-less bodies must wrap the condition or iterable in parentheses UNLESS it's a function call
|
||||||
- brace-less single-statement bodies apply to the then-body, the `else`-body, and the
|
- brace-less single-statement bodies apply to the then-body, the `else`-body, and the
|
||||||
unwrap/guard forms (`if v |x| stmt`); each branch is independent, so braced and
|
unwrap/guard forms (`if (v) |x| stmt`); each branch is independent, so braced and
|
||||||
brace-less branches mix freely
|
brace-less branches mix freely
|
||||||
- the parenthesize-or-call rule constrains only the then-branch condition; `else` and the
|
- the parenthesize-or-call rule constrains `if` then-branch conditions (including unwraps)
|
||||||
unwrap `|...|` already delimit, so they need no parentheses
|
and `for` iterables; `else` bodies have no preceding expression to constrain
|
||||||
- the brace-less statement may sit on the line after the condition
|
- the brace-less statement may sit on the following line
|
||||||
- parser-only change (`parse_branch_body` in `compiler/parser/parser.odin`): a brace-less
|
- parser-only change (`parse_control_body` in `compiler/parser/parser.odin`): a brace-less
|
||||||
body is just a 1-element statement slice, so the checker and codegen are unchanged
|
body is just a 1-element statement slice, so the checker and codegen are unchanged
|
||||||
|
|
||||||
17. multi-line strings (implemented; see below)
|
17. multi-line strings (implemented; see below)
|
||||||
|
|||||||
+19
-13
@@ -1993,20 +1993,18 @@ parse_block :: proc(parser: ^Parser) -> []ast.Stmt_Id {
|
|||||||
return body[:]
|
return body[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse_branch_body parses an `if` then/else body: a braced block (possibly on
|
// parse_control_body parses a braced block (possibly on a following line) or a
|
||||||
// a following line) or a single brace-less statement (milestone 16). For the
|
// single brace-less statement. A non-empty diagnostic enforces the shared
|
||||||
// then-branch (`validate_condition`), a brace-less body requires the condition
|
// parenthesized-or-call rule for the preceding condition or iterable.
|
||||||
// to be parenthesized unless it is a function call; the `else` keyword and the
|
parse_control_body :: proc(parser: ^Parser, header: ast.Expr_Id, diagnostic: string) -> []ast.Stmt_Id {
|
||||||
// unwrap `|...|` already delimit, so those positions skip the check.
|
|
||||||
parse_branch_body :: proc(parser: ^Parser, validate_condition: bool, condition: ast.Expr_Id, has_captures: bool) -> []ast.Stmt_Id {
|
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
if current(parser).kind == .Left_Brace {
|
if current(parser).kind == .Left_Brace {
|
||||||
return parse_block(parser)
|
return parse_block(parser)
|
||||||
}
|
}
|
||||||
if validate_condition && !has_captures && condition != ast.INVALID_EXPR && int(condition) < len(parser.module.exprs) {
|
if len(diagnostic) > 0 && header != ast.INVALID_EXPR && int(header) < len(parser.module.exprs) {
|
||||||
expr := parser.module.exprs[condition]
|
expr := parser.module.exprs[header]
|
||||||
if !expr.parenthesized && expr.kind != .Call {
|
if !expr.parenthesized && expr.kind != .Call {
|
||||||
source.add(parser.diagnostics, expr.span, "a brace-less 'if' body requires the condition to be parenthesized unless it is a function call")
|
source.add(parser.diagnostics, expr.span, diagnostic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
single := make([]ast.Stmt_Id, 1, parser.module.allocator)
|
single := make([]ast.Stmt_Id, 1, parser.module.allocator)
|
||||||
@@ -2056,7 +2054,11 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
source.add(parser.diagnostics, current(parser).span, "expected '|' to close unwrap captures")
|
source.add(parser.diagnostics, current(parser).span, "expected '|' to close unwrap captures")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
then_body := parse_branch_body(parser, true, condition, len(captures) > 0)
|
then_body := parse_control_body(
|
||||||
|
parser,
|
||||||
|
condition,
|
||||||
|
"a brace-less 'if' body requires the condition to be parenthesized unless it is a function call",
|
||||||
|
)
|
||||||
else_body: []ast.Stmt_Id = nil
|
else_body: []ast.Stmt_Id = nil
|
||||||
saved_cursor := parser.cursor
|
saved_cursor := parser.cursor
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
@@ -2069,7 +2071,7 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
single[0] = nested
|
single[0] = nested
|
||||||
else_body = single
|
else_body = single
|
||||||
} else {
|
} else {
|
||||||
else_body = parse_branch_body(parser, false, ast.INVALID_EXPR, false)
|
else_body = parse_control_body(parser, ast.INVALID_EXPR, "")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
parser.cursor = saved_cursor
|
parser.cursor = saved_cursor
|
||||||
@@ -2089,7 +2091,7 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse_arm_body parses a match arm's body after the `:`: a braced block (whose
|
// parse_arm_body parses a match arm's body after the `:`: a braced block (whose
|
||||||
// inner statements are returned unwrapped, like `parse_branch_body`) or a single
|
// inner statements are returned unwrapped, like `parse_control_body`) or a single
|
||||||
// brace-less statement. For value-match a brace-less body is a single expression
|
// brace-less statement. For value-match a brace-less body is a single expression
|
||||||
// that the checker yields implicitly.
|
// that the checker yields implicitly.
|
||||||
parse_arm_body :: proc(parser: ^Parser) -> []ast.Stmt_Id {
|
parse_arm_body :: proc(parser: ^Parser) -> []ast.Stmt_Id {
|
||||||
@@ -2306,7 +2308,11 @@ parse_for :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
}
|
}
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
label := parse_optional_loop_label(parser)
|
label := parse_optional_loop_label(parser)
|
||||||
body := parse_block(parser)
|
body := parse_control_body(
|
||||||
|
parser,
|
||||||
|
iterable,
|
||||||
|
"a brace-less 'for' body requires the iterable to be parenthesized unless it is a function call",
|
||||||
|
)
|
||||||
|
|
||||||
id := ast.stmt_id(len(parser.module.statements))
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
append(&parser.module.statements, ast.Stmt{
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
|||||||
+80
-1
@@ -8798,7 +8798,7 @@ main func() void {
|
|||||||
if (x == 2) x = 3 else x = 4
|
if (x == 2) x = 3 else x = 4
|
||||||
if (x > 0) { x = 10 } else x = 11
|
if (x > 0) { x = 10 } else x = 11
|
||||||
v ?i32 = 5
|
v ?i32 = 5
|
||||||
if v |u| _ = u
|
if (v) |u| _ = u
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
source_file := source.Source{path="test.bro", text=text}
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
@@ -8859,6 +8859,29 @@ parser_diagnoses_braceless_if_without_parens_or_call :: proc(t: ^testing.T) {
|
|||||||
testing.expect(t, strings.contains(diagnostics.items[0].message, "parenthesized"))
|
testing.expect(t, strings.contains(diagnostics.items[0].message, "parenthesized"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
parser_diagnoses_braceless_for_and_unwrap_without_parens_or_call :: proc(t: ^testing.T) {
|
||||||
|
text := `main func() void {
|
||||||
|
for tokens.items |item| _ = item
|
||||||
|
value ?i32 = 1
|
||||||
|
if value |present| _ = present
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 2)
|
||||||
|
testing.expect(t, strings.contains(diagnostics.items[0].message, "brace-less 'for'"))
|
||||||
|
testing.expect(t, strings.contains(diagnostics.items[1].message, "brace-less 'if'"))
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
braceless_if_compiles_and_runs :: proc(t: ^testing.T) {
|
braceless_if_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
directory := "/tmp/brolang-test-braceless-if"
|
directory := "/tmp/brolang-test-braceless-if"
|
||||||
@@ -9345,6 +9368,62 @@ for_loop_tokens_and_parser_capture_range_shape :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, module.exprs[third.expr].integer, u64(1))
|
testing.expect_value(t, module.exprs[third.expr].integer, u64(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
parser_accepts_braceless_for_bodies :: proc(t: ^testing.T) {
|
||||||
|
text := `make_items func() [2]i32 { return [1, 2] }
|
||||||
|
main func() void {
|
||||||
|
for (tokens.items) |item| _ = item
|
||||||
|
for make_items() |item|
|
||||||
|
_ = item
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
main := module.functions[1]
|
||||||
|
testing.expect_value(t, len(main.body), 2)
|
||||||
|
parenthesized := module.statements[main.body[0]]
|
||||||
|
call := module.statements[main.body[1]]
|
||||||
|
testing.expect_value(t, len(parenthesized.body), 1)
|
||||||
|
testing.expect(t, module.exprs[parenthesized.expr].parenthesized)
|
||||||
|
testing.expect_value(t, len(call.body), 1)
|
||||||
|
testing.expect_value(t, module.exprs[call.expr].kind, ast.Expr_Kind.Call)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
braceless_for_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-braceless-for"
|
||||||
|
main_path := "/tmp/brolang-test-braceless-for/main.bro"
|
||||||
|
output := "/tmp/brolang-test-braceless-for-output"
|
||||||
|
text := `make_items func() [2]i32 { return [20, 2] }
|
||||||
|
main func() i32 {
|
||||||
|
total i32 = 0
|
||||||
|
items :: [10, 11]
|
||||||
|
for (items) |item| total += item
|
||||||
|
for make_items() |item|
|
||||||
|
total += item
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
`
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||||
|
status := compiler_core.compile_package(directory, output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 43)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
range_bound_parenthesization_is_enforced :: proc(t: ^testing.T) {
|
range_bound_parenthesization_is_enforced :: proc(t: ^testing.T) {
|
||||||
text := `main func() void {
|
text := `main func() void {
|
||||||
|
|||||||
Reference in New Issue
Block a user