fix: allow multiline parenthesized expressions in every context
This commit is contained in:
@@ -27,6 +27,7 @@ State :: struct {
|
||||
tokens []lexer.Token
|
||||
allocator mem.Allocator
|
||||
next_token usize
|
||||
paren_depth usize = 0
|
||||
}
|
||||
|
||||
#! borrows `tokens` and `source` while parsing; the returned Tree borrows `source` until ast.deinit.
|
||||
@@ -413,6 +414,7 @@ parse_expr proc(state @mut State) ast.NodeId ! ParseError {
|
||||
|
||||
@hide:file
|
||||
parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! ParseError {
|
||||
skip_operand_newlines(state)
|
||||
token := current(state)
|
||||
left := if (prefix_binding_power(token.kind)) |right_bp| {
|
||||
state.next_token += 1
|
||||
@@ -430,6 +432,7 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
||||
}
|
||||
|
||||
while true {
|
||||
skip_continuation_newlines(state)
|
||||
token = current(state)
|
||||
if (token.kind == .open_paren) {
|
||||
left = try parse_call(state, left)
|
||||
@@ -458,6 +461,9 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
||||
@hide:file
|
||||
parse_call proc(state @mut State, callee ast.NodeId) ast.NodeId ! ParseError {
|
||||
_ = try consume(state, .open_paren)
|
||||
state.paren_depth += 1
|
||||
defer state.paren_depth -= 1
|
||||
skip_operand_newlines(state)
|
||||
arguments arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||
defer arraylist.deinit(&arguments)
|
||||
|
||||
@@ -466,6 +472,7 @@ parse_call proc(state @mut State, callee ast.NodeId) ast.NodeId ! ParseError {
|
||||
try arraylist.append(&arguments, argument)
|
||||
if (!next_is(state, .comma)) break
|
||||
state.next_token += 1
|
||||
skip_operand_newlines(state)
|
||||
}
|
||||
close :: try consume(state, .close_paren)
|
||||
return try ast.add_call(
|
||||
@@ -485,6 +492,8 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
match token.kind {
|
||||
.open_paren: {
|
||||
state.next_token += 1
|
||||
state.paren_depth += 1
|
||||
defer state.paren_depth -= 1
|
||||
expression :: try parse_expr(state)
|
||||
if next_is(state, .close_paren) {
|
||||
state.next_token += 1
|
||||
@@ -553,6 +562,38 @@ is_expression_recovery_boundary proc(kind lexer.TokenKind) bool {
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
skip_operand_newlines proc(state @mut State) void {
|
||||
if (state.paren_depth == 0 or !next_is(state, .newline)) return
|
||||
start :: state.next_token
|
||||
skip_newlines(state)
|
||||
kind :: current(state).kind
|
||||
if (kind != .close_paren and is_expression_boundary(kind)) state.next_token = start
|
||||
# a missing operand must not consume the declaration or assignment on the next line.
|
||||
if (
|
||||
next_is(state, .ident)
|
||||
and (
|
||||
next_n_is(state, 1, .double_colon)
|
||||
or next_n_is(state, 1, .colon_equal)
|
||||
or next_n_is(state, 1, .equal)
|
||||
or next_n_is(state, 1, .ident)
|
||||
or next_n_is(state, 1, .proc)
|
||||
)
|
||||
) state.next_token = start
|
||||
}
|
||||
|
||||
@hide:file
|
||||
skip_continuation_newlines proc(state @mut State) void {
|
||||
if (state.paren_depth == 0 or !next_is(state, .newline)) return
|
||||
start :: state.next_token
|
||||
skip_newlines(state)
|
||||
kind :: current(state).kind
|
||||
if (
|
||||
kind != .close_paren and kind != .comma and kind != .open_paren
|
||||
and infix_binding_power(kind) == null
|
||||
) state.next_token = start
|
||||
}
|
||||
|
||||
@hide:file
|
||||
recover_decl proc(
|
||||
state @mut State,
|
||||
|
||||
@@ -813,14 +813,42 @@ parses_logical_expressions_in_all_expression_positions test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`global :: false and absent
|
||||
`global :: (
|
||||
` false
|
||||
` and absent
|
||||
`)
|
||||
`main proc() bool {
|
||||
` value := true or absent
|
||||
` value = !value and ready()
|
||||
` value := (
|
||||
` true
|
||||
` or absent
|
||||
` )
|
||||
` value = (
|
||||
` !
|
||||
` value
|
||||
` and ready(
|
||||
` )
|
||||
` )
|
||||
` !value
|
||||
` if !value or ready() { value = false }
|
||||
` while value and !done() { value = false }
|
||||
` return choose(value or false, nested(!value and true))
|
||||
` if (
|
||||
` !value
|
||||
` or ready()
|
||||
` ) { value = false }
|
||||
` while (
|
||||
` value
|
||||
` and !done()
|
||||
` ) { value = false }
|
||||
` return (
|
||||
` choose(
|
||||
` (
|
||||
` value
|
||||
` or false
|
||||
` ),
|
||||
` nested(
|
||||
` !value
|
||||
` and true
|
||||
` ),
|
||||
` )
|
||||
` )
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
@@ -854,6 +882,40 @@ parses_logical_expressions_in_all_expression_positions test {
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_and, ast.binary_expr(&tree, nested_argument).operator)
|
||||
}
|
||||
|
||||
multiline_group_errors_preserve_statement_boundaries test {
|
||||
strpool.strings = strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strpool.strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`main proc() void {
|
||||
` value := (
|
||||
` 1 +
|
||||
` next int :: 2
|
||||
` value = 3
|
||||
` return (
|
||||
` true
|
||||
`}
|
||||
`tail :: 9
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||
defer ast.deinit(&tree)
|
||||
|
||||
try testing.expect_equal(3, diagnostics.count(&diagnostic_store))
|
||||
try testing.expect_equal(2, ast.package_decl_count(&tree))
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
|
||||
try testing.expect_equal(4, ast.block_statement_count(&tree, body))
|
||||
next :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 1))
|
||||
try testing.expect_equal("next", ast.source_text(&tree, next.name))
|
||||
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 2))
|
||||
try testing.expect_equal("3", ast.source_text(&tree, ast.node_span(&tree, assignment.value)))
|
||||
try testing.expect_equal(ast.StmtKind.return_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 3)))
|
||||
tail :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
||||
try testing.expect_equal("tail", ast.source_text(&tree, tail.name))
|
||||
}
|
||||
|
||||
recovers_missing_logical_operands_and_preserves_enclosing_nodes test {
|
||||
strpool.strings = strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strpool.strings)
|
||||
|
||||
Reference in New Issue
Block a user