feat: support yielding value blocks
This commit is contained in:
+68
-3
@@ -321,6 +321,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
||||
.while: return try parse_while(state, allow_else)
|
||||
.for, .inline: return try parse_for(state, allow_else)
|
||||
.return: return try parse_return(state, allow_else)
|
||||
.yield: return try parse_yield(state, allow_else)
|
||||
.break, .continue: return try parse_loop_control(state, allow_else)
|
||||
.defer, .errdefer: return try parse_cleanup(state, allow_else)
|
||||
.open_curly: return try ast.add_block_stmt(state.builder, try parse_block(state))
|
||||
@@ -385,6 +386,31 @@ parse_return proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_yield proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
keyword :: try consume(state, .yield)
|
||||
value :: if (
|
||||
next_is(state, .newline) or next_is(state, .close_curly) or next_is(state, .eof)
|
||||
or (state.match_arm_body and next_is(state, .comma))
|
||||
or (allow_else and next_is(state, .else))
|
||||
) {
|
||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, keyword.span)
|
||||
yield try ast.add_invalid_expr(state.builder, keyword.span, diagnostic)
|
||||
} else {
|
||||
yield 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))
|
||||
and !(state.match_arm_body and next_is(state, .comma))
|
||||
) return .unexpected_token
|
||||
return try ast.add_yield_stmt(state.builder, ast.Span{
|
||||
start = keyword.span.start,
|
||||
end = state.tokens[state.next_token - 1].span.end,
|
||||
source = keyword.span.source,
|
||||
}, value)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_loop_control proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||
keyword :: current(state)
|
||||
@@ -925,7 +951,7 @@ starts_type proc(kind lexer.TokenKind) bool {
|
||||
@hide:file
|
||||
member_name proc(kind lexer.TokenKind) bool {
|
||||
return match kind {
|
||||
.ident, .if, .else, .for, .inline, .while, .proc, .return, .break, .continue,
|
||||
.ident, .if, .else, .for, .inline, .while, .proc, .return, .yield, .break, .continue,
|
||||
.true, .false, .and, .or, .mut, .struct, .c_struct, .opaque, .enum, .union, .distinct,
|
||||
.alias, .match, .import, .test, .c_func, .null, .undefined, .try, .catch, .defer, .errdefer: true
|
||||
else: false
|
||||
@@ -1158,7 +1184,7 @@ parse_match_arm proc(state @mut State) ast.NodeId ! ParseError {
|
||||
state.match_arm_body = true
|
||||
defer state.match_arm_body = previous
|
||||
statement_body :: match current(state).kind {
|
||||
.open_curly, .return, .break, .continue, .if, .while, .for, .inline, .defer, .errdefer: true
|
||||
.open_curly, .return, .yield, .break, .continue, .if, .while, .for, .inline, .defer, .errdefer: true
|
||||
else: false
|
||||
}
|
||||
body ast.NodeId := undefined
|
||||
@@ -1701,6 +1727,41 @@ named_binding_starts proc(state @State) bool {
|
||||
)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
brace_starts_block proc(state @State) bool {
|
||||
first := state.next_token + 1
|
||||
while (first < state.tokens.len and state.tokens[first].kind == .newline) first += 1
|
||||
# empty records and braces without statement markers retain record syntax.
|
||||
if (first >= state.tokens.len or state.tokens[first].kind == .close_curly) return false
|
||||
depth := usize(0)
|
||||
for (state.next_token)..(state.tokens.len) |index| {
|
||||
kind :: state.tokens[index].kind
|
||||
if (kind == .eof) return false
|
||||
if (depth > 0
|
||||
and (state.tokens[first].kind == .if or state.tokens[first].kind == .open_curly)
|
||||
and (kind == .yield or kind == .return or kind == .defer or kind == .errdefer)
|
||||
and state.tokens[index - 1].kind != .dot
|
||||
and !(index + 1 < state.tokens.len and state.tokens[index + 1].kind == .equal)) return true
|
||||
if (depth == 1) {
|
||||
if (kind == .double_colon or kind == .colon_equal) return true
|
||||
previous :: if (index > first) state.tokens[index - 1].kind else lexer.TokenKind.open_curly
|
||||
if (previous != .dot and !(index + 1 < state.tokens.len and state.tokens[index + 1].kind == .equal)) {
|
||||
match kind {
|
||||
.yield, .return, .break, .continue, .while, .for, .inline, .defer, .errdefer: return true
|
||||
else: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (kind == .open_curly or kind == .open_paren or kind == .open_bracket) depth += 1
|
||||
if (kind == .close_curly or kind == .close_paren or kind == .close_bracket) {
|
||||
if (depth == 0) return false
|
||||
depth -= 1
|
||||
if (depth == 0) return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
token :: current(state)
|
||||
@@ -1736,6 +1797,10 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||
}, member_id)
|
||||
}
|
||||
.open_curly: {
|
||||
if (state.allow_record_literals and brace_starts_block(state)) {
|
||||
body :: try parse_block(state)
|
||||
return try ast.add_block_expr(state.builder, ast.builder_span(state.builder, body), body)
|
||||
}
|
||||
if (state.allow_record_literals) return try parse_record_literal(state, null, token.span.start)
|
||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
||||
return try ast.add_invalid_expr(state.builder, token.span, diagnostic)
|
||||
@@ -1808,7 +1873,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, .while, .for, .inline, .defer, .errdefer, .eof: true
|
||||
.close_paren, .newline, .close_curly, .open_curly, .else, .return, .yield, .if, .while, .for, .inline, .defer, .errdefer, .eof: true
|
||||
else: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2040,3 +2040,205 @@ keeps_match_default_arm_after_newline_single_statement_if test {
|
||||
try testing.expect(branch.else_block == null)
|
||||
try testing.expect(ast.match_arm_view(&tree, ast.match_arm(&tree, expression, 1)).catch_all)
|
||||
}
|
||||
|
||||
parses_value_blocks_and_clones_yield_children test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`run proc(flag bool) int {
|
||||
` result :: {
|
||||
` local :: 3
|
||||
` { if (flag) yield local }
|
||||
` yield { yield 4 }
|
||||
` }
|
||||
` return if (flag) { yield result } else { yield 5 }
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||
builder := ast.init_builder(mem.c_allocator, source)
|
||||
defer ast.deinit_builder(&builder)
|
||||
root :: try parse_source(&builder, scan_state.tokens.items, &diagnostic_store)
|
||||
original :: ast.view(&builder, root)
|
||||
mapping :: try mem.alloc(ast.NodeId, mem.c_allocator, ast.node_count(&original))
|
||||
defer mem.free(mem.c_allocator, mapping)
|
||||
mem.set(mapping, maxval!(ast.NodeId))
|
||||
cloned :: try ast.clone_subtree(&builder, root, mapping)
|
||||
tree :: ast.view(&builder, root)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
roots [2]ast.NodeId :: [root, cloned]
|
||||
for (roots) |package| {
|
||||
function :: ast.function_decl(&tree, ast.child(&tree, package, 0))
|
||||
value :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0)).initializer
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, value))
|
||||
try testing.expect_equal(ast.NodeClass.expression, ast.node_class(&tree, value))
|
||||
block :: ast.block_expr(&tree, value)
|
||||
try testing.expect_equal(3, ast.block_statement_count(&tree, block))
|
||||
ordinary :: ast.statement_block(&tree, ast.block_statement(&tree, block, 1))
|
||||
branch :: ast.if_stmt(&tree, ast.block_statement(&tree, ordinary, 0))
|
||||
first :: ast.block_statement(&tree, branch.then_block, 0)
|
||||
try testing.expect_equal(ast.StmtKind.yield_stmt, ast.stmt_kind(&tree, first))
|
||||
try testing.expect_equal(ast.NodeClass.statement, ast.node_class(&tree, first))
|
||||
try testing.expect_equal("yield local", ast.source_text(&tree, ast.yield_stmt(&tree, first).span))
|
||||
last :: ast.yield_stmt(&tree, ast.block_statement(&tree, block, 2))
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, last.value?))
|
||||
nested :: ast.block_statement(&tree, ast.block_expr(&tree, last.value?), 0)
|
||||
try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, ast.yield_stmt(&tree, nested).value?))
|
||||
conditional :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 1)).value?
|
||||
for (0..ast.match_arm_count(&tree, conditional)) |index| {
|
||||
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, conditional, index))
|
||||
try testing.expect(arm.expression)
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, arm.body))
|
||||
}
|
||||
}
|
||||
first_function :: ast.function_decl(&tree, ast.child(&tree, root, 0))
|
||||
first_value :: ast.statement_decl(&tree, ast.block_statement(&tree, first_function.body, 0)).initializer
|
||||
copied_value :: mapping[usize(first_value)]
|
||||
try testing.expect(copied_value != first_value)
|
||||
try testing.expect_equal(mapping[usize(ast.block_expr(&tree, first_value))], ast.block_expr(&tree, copied_value))
|
||||
}
|
||||
|
||||
parses_value_blocks_starting_with_named_assignments test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`main proc() i16 {
|
||||
` value ?i16 :: 42
|
||||
` return if value |payload| { payload = 7
|
||||
` yield payload
|
||||
` } else 0
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &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))
|
||||
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||
conditional :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 1)).value?
|
||||
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, conditional, 0))
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, arm.body))
|
||||
block :: ast.block_expr(&tree, arm.body)
|
||||
try testing.expect_equal(2, ast.block_statement_count(&tree, block))
|
||||
first :: ast.block_statement(&tree, block, 0)
|
||||
try testing.expect_equal(ast.StmtKind.assignment_stmt, ast.stmt_kind(&tree, first))
|
||||
assignment :: ast.assignment_stmt(&tree, first)
|
||||
try testing.expect_equal("payload", ast.source_text(&tree, ast.node_span(&tree, assignment.target)))
|
||||
try testing.expect_equal("7", ast.source_text(&tree, ast.node_span(&tree, assignment.value)))
|
||||
last :: ast.block_statement(&tree, block, 1)
|
||||
try testing.expect_equal(ast.StmtKind.yield_stmt, ast.stmt_kind(&tree, last))
|
||||
try testing.expect_equal("payload", ast.source_text(&tree, ast.node_span(&tree, ast.yield_stmt(&tree, last).value?)))
|
||||
}
|
||||
|
||||
preserves_record_literals_alongside_value_blocks test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`empty :: {}
|
||||
`single :: {1}
|
||||
`pair :: {1, 2}
|
||||
`keyed :: { value = { yield 3 } }
|
||||
`keyword :: { yield = 4 }
|
||||
`choose :: if (true) { value = 7 } else { value = 8 }
|
||||
`conditional :: {if (true) 1 else 2}
|
||||
`block :: { if (true) yield 9 }
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &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))
|
||||
try testing.expect_equal(8, ast.package_decl_count(&tree))
|
||||
for (usize(0)..usize(5)) |index| {
|
||||
value :: ast.statement_decl(&tree, ast.package_decl(&tree, index)).initializer
|
||||
try testing.expect_equal(ast.ExprKind.record_literal, ast.expr_kind(&tree, value))
|
||||
}
|
||||
choose :: ast.statement_decl(&tree, ast.package_decl(&tree, 5)).initializer
|
||||
for (usize(0)..usize(2)) |index| {
|
||||
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, choose, index))
|
||||
try testing.expect_equal(ast.ExprKind.record_literal, ast.expr_kind(&tree, arm.body))
|
||||
}
|
||||
conditional :: ast.statement_decl(&tree, ast.package_decl(&tree, 6)).initializer
|
||||
try testing.expect_equal(ast.ExprKind.record_literal, ast.expr_kind(&tree, conditional))
|
||||
block :: ast.statement_decl(&tree, ast.package_decl(&tree, 7)).initializer
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, block))
|
||||
}
|
||||
|
||||
requires_yield_values_and_recovers_following_statements test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`run proc() int {
|
||||
` value :: {
|
||||
` yield
|
||||
` after :: 2
|
||||
` if (true) yield else yield 3
|
||||
` yield 4 5
|
||||
` yield after
|
||||
` }
|
||||
` return value
|
||||
`}
|
||||
`tail :: 7
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &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))
|
||||
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||
value :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0)).initializer
|
||||
block :: ast.block_expr(&tree, value)
|
||||
try testing.expect_equal(5, ast.block_statement_count(&tree, block))
|
||||
missing :: ast.yield_stmt(&tree, ast.block_statement(&tree, block, 0))
|
||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing.value?))
|
||||
try testing.expect_equal(ast.StmtKind.declaration, ast.stmt_kind(&tree, ast.block_statement(&tree, block, 1)))
|
||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, block, 3)))
|
||||
last :: ast.yield_stmt(&tree, ast.block_statement(&tree, block, 4))
|
||||
try testing.expect_equal("after", ast.source_text(&tree, ast.node_span(&tree, last.value?)))
|
||||
}
|
||||
|
||||
keeps_yielding_match_and_catch_bodies_as_raw_blocks test {
|
||||
strings := strpool.init(mem.c_allocator)
|
||||
defer strpool.deinit(&strings)
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`run proc() int {
|
||||
` selected :: match 1 { 1: { yield 2 }, else: yield 3 }
|
||||
` recovered :: selected catch |err| { yield err }
|
||||
` return use({ yield recovered }, $({ yield 4 }))
|
||||
`}
|
||||
scan_state := lexer.init(mem.c_allocator)
|
||||
defer lexer.deinit(&scan_state)
|
||||
try lexer.scan(&scan_state, source, &strings, &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))
|
||||
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||
selected :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0)).initializer
|
||||
for (usize(0)..usize(2)) |index| {
|
||||
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, selected, index))
|
||||
try testing.expect(!arm.expression)
|
||||
try testing.expect_equal(ast.NodeClass.block, ast.node_class(&tree, arm.body))
|
||||
try testing.expect_equal(ast.StmtKind.yield_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, arm.body, 0)))
|
||||
}
|
||||
recovered :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1)).initializer
|
||||
handler :: ast.catch_expr(&tree, recovered)
|
||||
try testing.expect(handler.handler_is_block)
|
||||
try testing.expect_equal(ast.NodeClass.block, ast.node_class(&tree, handler.handler))
|
||||
try testing.expect_equal(ast.StmtKind.yield_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, handler.handler, 0)))
|
||||
call :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 2)).value?
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, ast.call_arg(&tree, call, 0)))
|
||||
constant :: ast.call_arg(&tree, call, 1)
|
||||
try testing.expect_equal(ast.ExprKind.comptime_expr, ast.expr_kind(&tree, constant))
|
||||
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, ast.comptime_operand(&tree, constant)))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user