feat: execute local assignments and while loops
This commit is contained in:
+90
-34
@@ -168,6 +168,9 @@ parse_block proc(state @mut State) ast.NodeId ! ParseError {
|
||||
|
||||
@hide:file
|
||||
parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
if (next_is(state, .ident) and next_n_is(state, 1, .equal)) {
|
||||
return try parse_assignment(state, allow_else)
|
||||
}
|
||||
if (
|
||||
next_is(state, .ident)
|
||||
and (
|
||||
@@ -180,6 +183,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
}
|
||||
match current(state).kind {
|
||||
.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: {
|
||||
expression :: try parse_expr(state)
|
||||
@@ -214,9 +218,73 @@ parse_return proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_assignment proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
name :: try consume(state, .ident)
|
||||
target :: try ast.add_identifier(&state.builder, name.span)
|
||||
_ = try consume(state, .equal)
|
||||
value :: try parse_expr(state)
|
||||
if (
|
||||
!next_is(state, .newline)
|
||||
and !next_is(state, .close_curly)
|
||||
and !next_is(state, .eof)
|
||||
and !(allow_else and next_is(state, .else))
|
||||
) {
|
||||
return .unexpected_token
|
||||
}
|
||||
end :: state.tokens[state.next_token - 1].span.end
|
||||
return try ast.add_assignment_stmt(
|
||||
&state.builder,
|
||||
ast.Span{ start = name.span.start, end = end },
|
||||
target,
|
||||
value,
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_if proc(state @mut State) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .if)
|
||||
condition :: try parse_guard(state)
|
||||
braced :: next_is(state, .open_curly)
|
||||
then_block :: try parse_arm(state, true)
|
||||
# only a braced arm permits its else on a later line.
|
||||
after_then :: state.next_token
|
||||
if (braced) skip_newlines(state)
|
||||
alternative ?ast.NodeId := null
|
||||
if next_is(state, .else) {
|
||||
state.next_token += 1
|
||||
alternative = try parse_arm(state, true)
|
||||
} else {
|
||||
state.next_token = after_then
|
||||
}
|
||||
end :: if (alternative) |block|
|
||||
ast.builder_span(&state.builder, block).end
|
||||
else
|
||||
ast.builder_span(&state.builder, then_block).end
|
||||
return try ast.add_if_stmt(
|
||||
&state.builder,
|
||||
ast.Span{ start = keyword.span.start, end = end },
|
||||
condition,
|
||||
then_block,
|
||||
alternative,
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_while proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .while)
|
||||
condition :: try parse_guard(state)
|
||||
body :: try parse_arm(state, allow_else)
|
||||
return try ast.add_while_stmt(
|
||||
&state.builder,
|
||||
ast.Span{ start = keyword.span.start, end = ast.builder_span(&state.builder, body).end },
|
||||
condition,
|
||||
body,
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_guard proc(state @mut State) ast.NodeId ! ParseError {
|
||||
condition_start :: state.next_token
|
||||
condition := parse_expr(state) catch |err| {
|
||||
match err {
|
||||
@@ -227,12 +295,14 @@ parse_if proc(state @mut State) ast.NodeId ! ParseError {
|
||||
yield try ast.add_invalid_expr(&state.builder, current(state).span, diagnostic)
|
||||
}
|
||||
if (ast.builder_expr_kind(&state.builder, condition) == .invalid) {
|
||||
# discard a malformed guard's tail, but leave its arm and enclosing block intact.
|
||||
# discard a malformed guard's tail, but leave its body and enclosing block intact.
|
||||
while (
|
||||
!next_is(state, .open_curly) and !next_is(state, .close_curly)
|
||||
and !next_is(state, .newline) and !next_is(state, .eof)
|
||||
and !next_is(state, .else) and !next_is(state, .return) and !next_is(state, .if)
|
||||
and !next_is(state, .else) and !next_is(state, .return)
|
||||
and !next_is(state, .if) and !next_is(state, .while)
|
||||
) {
|
||||
if (next_is(state, .ident) and next_n_is(state, 1, .equal)) break
|
||||
state.next_token += 1
|
||||
}
|
||||
}
|
||||
@@ -249,29 +319,7 @@ parse_if proc(state @mut State) ast.NodeId ! ParseError {
|
||||
diagnostic,
|
||||
)
|
||||
}
|
||||
braced :: next_is(state, .open_curly)
|
||||
then_block :: try parse_arm(state)
|
||||
# only a braced arm permits its else on a later line.
|
||||
after_then :: state.next_token
|
||||
if (braced) skip_newlines(state)
|
||||
alternative ?ast.NodeId := null
|
||||
if next_is(state, .else) {
|
||||
state.next_token += 1
|
||||
alternative = try parse_arm(state)
|
||||
} else {
|
||||
state.next_token = after_then
|
||||
}
|
||||
end :: if (alternative) |block|
|
||||
ast.builder_span(&state.builder, block).end
|
||||
else
|
||||
ast.builder_span(&state.builder, then_block).end
|
||||
return try ast.add_if_stmt(
|
||||
&state.builder,
|
||||
ast.Span{ start = keyword.span.start, end = end },
|
||||
condition,
|
||||
then_block,
|
||||
alternative,
|
||||
)
|
||||
return condition
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -290,10 +338,10 @@ parenthesized_guard proc(state @State, start usize) bool {
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_arm proc(state @mut State) ast.NodeId ! ParseError {
|
||||
parse_arm proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
if next_is(state, .open_curly) return try parse_block(state)
|
||||
start :: state.next_token
|
||||
statement :: parse_statement(state, true) catch |err| {
|
||||
statement :: parse_statement(state, allow_else) catch |err| {
|
||||
match err {
|
||||
.unexpected_token: {}
|
||||
else: return err
|
||||
@@ -314,10 +362,14 @@ parse_arm proc(state @mut State) ast.NodeId ! ParseError {
|
||||
synchronize_arm proc(state @mut State, start usize) ast.Span {
|
||||
first :: state.tokens[start].span
|
||||
end := first.end
|
||||
while (
|
||||
!next_is(state, .eof) and !next_is(state, .newline)
|
||||
and !next_is(state, .close_curly) and !next_is(state, .else)
|
||||
) {
|
||||
depth usize := 0
|
||||
while !next_is(state, .eof) {
|
||||
if (
|
||||
depth == 0
|
||||
and (next_is(state, .newline) or next_is(state, .close_curly) or next_is(state, .else))
|
||||
) break
|
||||
if next_is(state, .open_curly) depth += 1
|
||||
if (next_is(state, .close_curly) and depth > 0) depth -= 1
|
||||
end = current(state).span.end
|
||||
state.next_token += 1
|
||||
}
|
||||
@@ -485,7 +537,7 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
@hide:file
|
||||
is_expression_boundary proc(kind lexer.TokenKind) bool {
|
||||
return match kind {
|
||||
.close_paren, .newline, .close_curly, .open_curly, .else, .return, .if, .eof: true
|
||||
.close_paren, .newline, .close_curly, .open_curly, .else, .return, .if, .while, .eof: true
|
||||
else: false
|
||||
}
|
||||
}
|
||||
@@ -514,8 +566,12 @@ recover_statement proc(
|
||||
synchronize proc(state @mut State, start usize, stop_at_curly bool) ast.Span {
|
||||
first :: state.tokens[start].span
|
||||
end := first.end
|
||||
while !next_is(state, .eof) and !next_is(state, .newline) {
|
||||
if (stop_at_curly and next_is(state, .close_curly)) break
|
||||
depth usize := 0
|
||||
while !next_is(state, .eof) {
|
||||
if (depth == 0 and next_is(state, .newline)) break
|
||||
if (stop_at_curly and depth == 0 and next_is(state, .close_curly)) break
|
||||
if (stop_at_curly and next_is(state, .open_curly)) depth += 1
|
||||
if (stop_at_curly and next_is(state, .close_curly) and depth > 0) depth -= 1
|
||||
end = current(state).span.end
|
||||
state.next_token += 1
|
||||
}
|
||||
|
||||
@@ -510,3 +510,232 @@ recovers_invalid_guard_syntax_and_missing_arm_at_enclosing_brace test {
|
||||
try testing.expect_equal("}", ast.source_text(&tree, arm_error.span))
|
||||
try testing.expect_equal(3, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
|
||||
parses_iterative_factorial_assignments_and_loop 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 ::
|
||||
`factorial proc(n int) int {
|
||||
` remaining := n
|
||||
` product := 1
|
||||
` while remaining > 1 {
|
||||
` product = product * remaining
|
||||
` remaining = remaining - 1
|
||||
` }
|
||||
` return product
|
||||
`}
|
||||
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))
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
|
||||
try testing.expect_equal(4, ast.block_statement_count(&tree, body))
|
||||
loop_id :: ast.block_statement(&tree, body, 2)
|
||||
try testing.expect_equal(ast.StmtKind.while_stmt, ast.stmt_kind(&tree, loop_id))
|
||||
loop :: ast.while_stmt(&tree, loop_id)
|
||||
try testing.expect_equal(ast.BinaryOperator.greater, ast.binary_expr(&tree, loop.condition).operator)
|
||||
try testing.expect_equal(2, ast.block_statement_count(&tree, loop.body))
|
||||
update_id :: ast.block_statement(&tree, loop.body, 0)
|
||||
try testing.expect_equal(ast.StmtKind.assignment_stmt, ast.stmt_kind(&tree, update_id))
|
||||
update :: ast.assignment_stmt(&tree, update_id)
|
||||
try testing.expect_equal(ast.ExprKind.identifier, ast.expr_kind(&tree, update.target))
|
||||
try testing.expect_equal("product", ast.source_text(&tree, ast.node_span(&tree, update.target)))
|
||||
try testing.expect_equal(ast.BinaryOperator.multiply, ast.binary_expr(&tree, update.value).operator)
|
||||
decrement :: ast.assignment_stmt(&tree, ast.block_statement(&tree, loop.body, 1))
|
||||
try testing.expect_equal(ast.BinaryOperator.subtract, ast.binary_expr(&tree, decrement.value).operator)
|
||||
}
|
||||
|
||||
parses_assignment_precedence_grouped_span_and_rhs_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 + 2 * 3)
|
||||
` if (true) value = 4 else value = 5
|
||||
` while ready() value = value - 1
|
||||
` value = 6 }
|
||||
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))
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
|
||||
try testing.expect_equal(4, ast.block_statement_count(&tree, body))
|
||||
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 0))
|
||||
try testing.expect_equal("value = (1 + 2 * 3)", ast.source_text(&tree, assignment.span))
|
||||
add :: ast.binary_expr(&tree, assignment.value)
|
||||
try testing.expect_equal(ast.BinaryOperator.add, add.operator)
|
||||
try testing.expect_equal(ast.BinaryOperator.multiply, ast.binary_expr(&tree, add.right).operator)
|
||||
conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 1))
|
||||
first :: ast.assignment_stmt(&tree, ast.block_statement(&tree, conditional.then_block, 0))
|
||||
second :: ast.assignment_stmt(&tree, ast.block_statement(&tree, conditional.else_block?, 0))
|
||||
try testing.expect_equal("4", ast.source_text(&tree, ast.node_span(&tree, first.value)))
|
||||
try testing.expect_equal("5", ast.source_text(&tree, ast.node_span(&tree, second.value)))
|
||||
loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 2))
|
||||
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, loop.condition))
|
||||
try testing.expect_equal(1, ast.block_statement_count(&tree, loop.body))
|
||||
try testing.expect_equal(ast.StmtKind.assignment_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, loop.body, 0)))
|
||||
last :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 3))
|
||||
try testing.expect_equal("6", ast.source_text(&tree, ast.node_span(&tree, last.value)))
|
||||
}
|
||||
|
||||
parses_nested_loops_and_preserves_nearest_if_else 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 {
|
||||
` if (true) while (false) if (true) x = 1 else x = 2 else x = 3
|
||||
` while (true) while (false) x = 4
|
||||
` while (true) {
|
||||
` while false { x = 5 }
|
||||
` }
|
||||
`}
|
||||
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))
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
|
||||
try testing.expect_equal(3, ast.block_statement_count(&tree, body))
|
||||
outer :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 0))
|
||||
loop :: ast.while_stmt(&tree, ast.block_statement(&tree, outer.then_block, 0))
|
||||
inner :: ast.if_stmt(&tree, ast.block_statement(&tree, loop.body, 0))
|
||||
inner_else :: ast.assignment_stmt(&tree, ast.block_statement(&tree, inner.else_block?, 0))
|
||||
outer_else :: ast.assignment_stmt(&tree, ast.block_statement(&tree, outer.else_block?, 0))
|
||||
try testing.expect_equal("2", ast.source_text(&tree, ast.node_span(&tree, inner_else.value)))
|
||||
try testing.expect_equal("3", ast.source_text(&tree, ast.node_span(&tree, outer_else.value)))
|
||||
braceless :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 1))
|
||||
nested :: ast.while_stmt(&tree, ast.block_statement(&tree, braceless.body, 0))
|
||||
try testing.expect_equal(1, ast.block_statement_count(&tree, nested.body))
|
||||
braced :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 2))
|
||||
child :: ast.while_stmt(&tree, ast.block_statement(&tree, braced.body, 0))
|
||||
try testing.expect_equal(ast.ExprKind.literal_bool, ast.expr_kind(&tree, child.condition))
|
||||
try testing.expect_equal(1, ast.block_statement_count(&tree, child.body))
|
||||
}
|
||||
|
||||
recovers_loop_guards_bodies_and_assignments_without_losing_braces 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 {
|
||||
` while { x = 1 }
|
||||
` while true x = 2
|
||||
` while broken( { x = 3 }
|
||||
` x =
|
||||
` x = 1 junk
|
||||
` if (true) x = else x = 4
|
||||
` kept :: 7
|
||||
` while (false) }
|
||||
`tail :: 8
|
||||
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(diagnostics.count(&diagnostic_store) > 0)
|
||||
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(8, ast.block_statement_count(&tree, body))
|
||||
for 0..3 |index| {
|
||||
loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, usize(index)))
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, loop.condition))
|
||||
try testing.expect_equal(1, ast.block_statement_count(&tree, loop.body))
|
||||
try testing.expect_equal(ast.StmtKind.assignment_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, loop.body, 0)))
|
||||
}
|
||||
missing :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 3))
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing.value))
|
||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 4)))
|
||||
conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 5))
|
||||
missing_arm :: ast.assignment_stmt(&tree, ast.block_statement(&tree, conditional.then_block, 0))
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing_arm.value))
|
||||
try testing.expect(conditional.else_block != null)
|
||||
kept :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 6))
|
||||
try testing.expect_equal("kept", ast.source_text(&tree, kept.name))
|
||||
last :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 7))
|
||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, last.body, 0)))
|
||||
tail :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
||||
try testing.expect_equal("tail", ast.source_text(&tree, tail.name))
|
||||
}
|
||||
|
||||
rejects_top_level_assignment_loop_else_and_assignment_expressions 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 ::
|
||||
`x = 1
|
||||
`main proc() void {
|
||||
` while (true) x = 2 else x = 3
|
||||
` x = y = 4
|
||||
` kept :: 5
|
||||
`}
|
||||
`tail :: 6
|
||||
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(diagnostics.count(&diagnostic_store) > 0)
|
||||
try testing.expect_equal(3, ast.package_decl_count(&tree))
|
||||
try testing.expect_equal(ast.DeclKind.invalid, ast.decl_kind(&tree, ast.package_decl(&tree, 0)))
|
||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 1)).body
|
||||
try testing.expect_equal(4, ast.block_statement_count(&tree, body))
|
||||
loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 0))
|
||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, loop.body, 0)))
|
||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 1)))
|
||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 2)))
|
||||
kept :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 3))
|
||||
try testing.expect_equal("kept", ast.source_text(&tree, kept.name))
|
||||
}
|
||||
|
||||
recovers_nested_braces_in_malformed_loop_body_and_assignment 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 {
|
||||
` while (true) @ {
|
||||
` bad()
|
||||
` }
|
||||
` x = {
|
||||
` bad()
|
||||
` }
|
||||
` kept :: 7
|
||||
`}
|
||||
`tail :: 8
|
||||
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(diagnostics.count(&diagnostic_store) > 0)
|
||||
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(3, ast.block_statement_count(&tree, body))
|
||||
loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 0))
|
||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, loop.body, 0)))
|
||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 1)))
|
||||
kept :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 2))
|
||||
try testing.expect_equal("kept", ast.source_text(&tree, kept.name))
|
||||
tail :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
||||
try testing.expect_equal("tail", ast.source_text(&tree, tail.name))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user