feat: execute short-circuit boolean expressions
This commit is contained in:
+19
-4
@@ -185,7 +185,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
.if: return try parse_if(state)
|
||||
.while: return try parse_while(state, allow_else)
|
||||
.return: return try parse_return(state, allow_else)
|
||||
.ident, .int, .float, .string, .true, .false, .minus, .open_paren: {
|
||||
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren: {
|
||||
expression :: try parse_expr(state)
|
||||
return try ast.add_expression_stmt(&state.builder, expression)
|
||||
}
|
||||
@@ -418,10 +418,11 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
||||
state.next_token += 1
|
||||
operand :: try parse_expr_bp(state, right_bp)
|
||||
operand_span :: ast.builder_span(&state.builder, operand)
|
||||
operator ast.UnaryOperator :: if (token.kind == .bang) .logical_not else .negate
|
||||
yield try ast.add_unary(
|
||||
&state.builder,
|
||||
ast.Span{ start = token.span.start, end = operand_span.end },
|
||||
.negate,
|
||||
operator,
|
||||
operand,
|
||||
)
|
||||
} else {
|
||||
@@ -525,7 +526,7 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
else
|
||||
try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
||||
|
||||
if token.kind != .comma and !is_expression_boundary(token.kind) {
|
||||
if !is_expression_recovery_boundary(token.kind) {
|
||||
state.next_token += 1
|
||||
}
|
||||
|
||||
@@ -542,6 +543,16 @@ is_expression_boundary proc(kind lexer.TokenKind) bool {
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
is_expression_recovery_boundary proc(kind lexer.TokenKind) bool {
|
||||
return (
|
||||
kind == .comma
|
||||
or kind == .and
|
||||
or kind == .or
|
||||
or is_expression_boundary(kind)
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
recover_decl proc(
|
||||
state @mut State,
|
||||
@@ -625,7 +636,7 @@ consume_either proc(state @mut State, kinds []lexer.TokenKind) lexer.Token ! Syn
|
||||
@hide:file
|
||||
prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
|
||||
return match kind {
|
||||
.minus: BindingPowerScalar(50)
|
||||
.minus, .bang: BindingPowerScalar(50)
|
||||
else: null
|
||||
}
|
||||
}
|
||||
@@ -633,6 +644,8 @@ prefix_binding_power proc(kind lexer.TokenKind) ?BindingPowerScalar {
|
||||
@hide:file
|
||||
infix_binding_power proc(kind lexer.TokenKind) ?BindingPower {
|
||||
return match kind {
|
||||
.or: BindingPower{ left = BindingPowerScalar(2), right = BindingPowerScalar(3) }
|
||||
.and: BindingPower{ left = BindingPowerScalar(4), right = BindingPowerScalar(5) }
|
||||
.equal_equal, .not_equal: BindingPower{ left = BindingPowerScalar(10), right = BindingPowerScalar(11) }
|
||||
.less, .less_equal, .greater, .greater_equal: BindingPower{ left = BindingPowerScalar(20), right = BindingPowerScalar(21) }
|
||||
.plus, .minus: BindingPower{ left = BindingPowerScalar(30), right = BindingPowerScalar(31) }
|
||||
@@ -644,6 +657,8 @@ infix_binding_power proc(kind lexer.TokenKind) ?BindingPower {
|
||||
@hide:file
|
||||
binary_operator proc(kind lexer.TokenKind) ast.BinaryOperator {
|
||||
return match kind {
|
||||
.and: .logical_and
|
||||
.or: .logical_or
|
||||
.plus: .add
|
||||
.minus: .subtract
|
||||
.star: .multiply
|
||||
|
||||
@@ -739,3 +739,163 @@ recovers_nested_braces_in_malformed_loop_body_and_assignment test {
|
||||
tail :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
||||
try testing.expect_equal("tail", ast.source_text(&tree, tail.name))
|
||||
}
|
||||
|
||||
parses_logical_precedence_and_associativity 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 :: "value :: !probe() == a + b * c < d and e and f or g or h\n"
|
||||
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(0, diagnostics.count(&diagnostic_store))
|
||||
value :: ast.statement_decl(&tree, ast.package_decl(&tree, 0))
|
||||
outer_or :: ast.binary_expr(&tree, value.initializer)
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_or, outer_or.operator)
|
||||
try testing.expect_equal("h", ast.source_text(&tree, ast.node_span(&tree, outer_or.right)))
|
||||
inner_or :: ast.binary_expr(&tree, outer_or.left)
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_or, inner_or.operator)
|
||||
outer_and :: ast.binary_expr(&tree, inner_or.left)
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_and, outer_and.operator)
|
||||
try testing.expect_equal("f", ast.source_text(&tree, ast.node_span(&tree, outer_and.right)))
|
||||
inner_and :: ast.binary_expr(&tree, outer_and.left)
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_and, inner_and.operator)
|
||||
equality :: ast.binary_expr(&tree, inner_and.left)
|
||||
try testing.expect_equal(ast.BinaryOperator.equal, equality.operator)
|
||||
logical_not :: ast.unary_expr(&tree, equality.left)
|
||||
try testing.expect_equal(ast.UnaryOperator.logical_not, logical_not.operator)
|
||||
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, logical_not.operand))
|
||||
try testing.expect_equal("!probe()", ast.source_text(&tree, logical_not.span))
|
||||
comparison :: ast.binary_expr(&tree, equality.right)
|
||||
try testing.expect_equal(ast.BinaryOperator.less, comparison.operator)
|
||||
addition :: ast.binary_expr(&tree, comparison.left)
|
||||
try testing.expect_equal(ast.BinaryOperator.add, addition.operator)
|
||||
multiply :: ast.binary_expr(&tree, addition.right)
|
||||
try testing.expect_equal(ast.BinaryOperator.multiply, multiply.operator)
|
||||
}
|
||||
|
||||
parses_right_associative_mixed_prefix_operators 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 :: "value :: !-!probe()\nother :: -!false\n"
|
||||
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(0, diagnostics.count(&diagnostic_store))
|
||||
value :: ast.statement_decl(&tree, ast.package_decl(&tree, 0))
|
||||
first :: ast.unary_expr(&tree, value.initializer)
|
||||
second :: ast.unary_expr(&tree, first.operand)
|
||||
third :: ast.unary_expr(&tree, second.operand)
|
||||
try testing.expect_equal(ast.UnaryOperator.logical_not, first.operator)
|
||||
try testing.expect_equal(ast.UnaryOperator.negate, second.operator)
|
||||
try testing.expect_equal(ast.UnaryOperator.logical_not, third.operator)
|
||||
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, third.operand))
|
||||
try testing.expect_equal("!-!probe()", ast.source_text(&tree, first.span))
|
||||
try testing.expect_equal("-!probe()", ast.source_text(&tree, second.span))
|
||||
other :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
||||
negate :: ast.unary_expr(&tree, other.initializer)
|
||||
try testing.expect_equal(ast.UnaryOperator.negate, negate.operator)
|
||||
try testing.expect_equal(ast.UnaryOperator.logical_not, ast.unary_expr(&tree, negate.operand).operator)
|
||||
}
|
||||
|
||||
parses_logical_expressions_in_all_expression_positions 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 ::
|
||||
`global :: false and absent
|
||||
`main proc() bool {
|
||||
` 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))
|
||||
`}
|
||||
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(0, diagnostics.count(&diagnostic_store))
|
||||
global :: ast.statement_decl(&tree, ast.package_decl(&tree, 0))
|
||||
global_and :: ast.binary_expr(&tree, global.initializer)
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_and, global_and.operator)
|
||||
try testing.expect_equal("absent", ast.source_text(&tree, ast.node_span(&tree, global_and.right)))
|
||||
function :: ast.function_decl(&tree, ast.package_decl(&tree, 1))
|
||||
try testing.expect_equal(6, ast.block_statement_count(&tree, function.body))
|
||||
local :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0))
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_or, ast.binary_expr(&tree, local.initializer).operator)
|
||||
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, function.body, 1))
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_and, ast.binary_expr(&tree, assignment.value).operator)
|
||||
expression :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 2))
|
||||
try testing.expect_equal(ast.UnaryOperator.logical_not, ast.unary_expr(&tree, expression).operator)
|
||||
conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, function.body, 3))
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_or, ast.binary_expr(&tree, conditional.condition).operator)
|
||||
loop :: ast.while_stmt(&tree, ast.block_statement(&tree, function.body, 4))
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_and, ast.binary_expr(&tree, loop.condition).operator)
|
||||
returned :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 5)).value?
|
||||
try testing.expect_equal(2, ast.call_arg_count(&tree, returned))
|
||||
first_argument :: ast.call_arg(&tree, returned, 0)
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_or, ast.binary_expr(&tree, first_argument).operator)
|
||||
nested :: ast.call_arg(&tree, returned, 1)
|
||||
nested_argument :: ast.call_arg(&tree, nested, 0)
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_and, ast.binary_expr(&tree, nested_argument).operator)
|
||||
}
|
||||
|
||||
recovers_missing_logical_operands_and_preserves_enclosing_nodes 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() bool {
|
||||
` first :: true and
|
||||
` second :: true and or false
|
||||
` third :: choose(!, false or)
|
||||
` if ! { return false }
|
||||
` return true or }
|
||||
`after :: true
|
||||
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(6, diagnostics.count(&diagnostic_store))
|
||||
try testing.expect_equal(2, ast.package_decl_count(&tree))
|
||||
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||
try testing.expect_equal(5, ast.block_statement_count(&tree, function.body))
|
||||
first :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0))
|
||||
first_and :: ast.binary_expr(&tree, first.initializer)
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, first_and.right))
|
||||
second :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1))
|
||||
second_or :: ast.binary_expr(&tree, second.initializer)
|
||||
try testing.expect_equal(ast.BinaryOperator.logical_or, second_or.operator)
|
||||
second_and :: ast.binary_expr(&tree, second_or.left)
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, second_and.right))
|
||||
try testing.expect_equal(ast.ExprKind.literal_bool, ast.expr_kind(&tree, second_or.right))
|
||||
third :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 2))
|
||||
try testing.expect_equal(2, ast.call_arg_count(&tree, third.initializer))
|
||||
missing_prefix :: ast.unary_expr(&tree, ast.call_arg(&tree, third.initializer, 0))
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing_prefix.operand))
|
||||
missing_rhs :: ast.binary_expr(&tree, ast.call_arg(&tree, third.initializer, 1))
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing_rhs.right))
|
||||
conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, function.body, 3))
|
||||
try testing.expect_equal(1, ast.block_statement_count(&tree, conditional.then_block))
|
||||
returned :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 4)).value?
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, ast.binary_expr(&tree, returned).right))
|
||||
after :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
||||
try testing.expect_equal("after", ast.source_text(&tree, after.name))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user