feat: execute fixed arrays and indexed access

This commit is contained in:
2026-10-10 17:21:43 +02:00
parent cf39481bfc
commit c6a56b7e79
23 changed files with 3474 additions and 256 deletions
+214 -54
View File
@@ -103,7 +103,7 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
while true {
token :: try consume(state, .ident)
try arraylist.append(&names, token.span)
if next_is(state, .ident) break
if (next_is(state, .ident) or next_is(state, .open_bracket)) break
_ = try consume(state, .comma)
}
@@ -169,13 +169,11 @@ 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 (
next_n_is(state, 1, .ident)
or (next_n_is(state, 1, .open_bracket) and array_starts_type(state))
or next_n_is(state, 1, .double_colon)
or next_n_is(state, 1, .colon_equal)
)
@@ -188,8 +186,9 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
.for: return try parse_for(state, allow_else)
.return: return try parse_return(state, allow_else)
.break, .continue: return try parse_loop_control(state, allow_else)
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren: {
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket: {
expression :: try parse_expr(state)
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
return try ast.add_expression_stmt(&state.builder, expression)
}
else: return .unexpected_token
@@ -239,9 +238,7 @@ parse_loop_control proc(state @mut State, allow_else bool) ast.NodeId ! ParseErr
}
@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)
parse_assignment proc(state @mut State, target ast.NodeId, allow_else bool) ast.NodeId ! ParseError {
_ = try consume(state, .equal)
value :: try parse_expr(state)
if (
@@ -255,7 +252,7 @@ parse_assignment proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
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 },
ast.Span{ start = ast.builder_span(&state.builder, target).start, end = end },
target,
value,
)
@@ -454,8 +451,8 @@ parse_arm proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
.unexpected_token: {}
else: return err
}
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
span :: synchronize_arm(state, start)
diagnostic :: try unexpected_diagnostic(state)
span :: synchronize(state, start, true, true, false)
yield try ast.add_invalid_stmt(&state.builder, span, diagnostic)
}
statements [1]ast.NodeId :: [statement]
@@ -466,28 +463,11 @@ parse_arm proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
)
}
@hide:file
synchronize_arm proc(state @mut State, start usize) ast.Span {
first :: state.tokens[start].span
end := first.end
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
}
return ast.Span{ start = first.start, end = end }
}
@hide:file
parse_statement_decl proc(state @mut State) ast.NodeId ! ParseError {
name :: try consume(state, .ident)
type_id ?ast.NodeId :: if next_is(state, .ident)
type_id ?ast.NodeId :: if (next_is(state, .ident) or next_is(state, .open_bracket))
try parse_type(state)
else
null
@@ -508,9 +488,29 @@ parse_statement_decl proc(state @mut State) ast.NodeId ! ParseError {
@hide:file
parse_type proc(state @mut State) ast.NodeId ! ParseError {
if (!next_is(state, .ident)) return .unexpected_token
token :: current(state)
state.next_token += 1
if next_is(state, .open_bracket) {
open :: try consume(state, .open_bracket)
count ?ast.NodeId := null
if (
next_is(state, .ident)
and current(state).span.end == current(state).span.start + 1
and state.builder.source[usize(current(state).span.start)] == '_'
) {
state.next_token += 1
} else {
count = try parse_expr(state)
}
_ = try consume(state, .close_bracket)
mutable :: next_is(state, .mut)
if (mutable) state.next_token += 1
element :: try parse_type(state)
return try ast.add_array_type(
&state.builder,
ast.Span{ start = open.span.start, end = ast.builder_span(&state.builder, element).end },
count, element, mutable,
)
}
token :: try consume(state, .ident)
return try ast.add_identifier(&state.builder, token.span)
}
@@ -544,7 +544,12 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
skip_continuation_newlines(state)
token = current(state)
if (token.kind == .open_paren) {
left = try parse_call(state, left)
left = try parse_call(state, left, state.tokens[expression_start].span.start)
left_grouped = false
continue
}
if (token.kind == .open_bracket) {
left = try parse_index(state, left, state.tokens[expression_start].span.start)
left_grouped = false
continue
}
@@ -603,7 +608,7 @@ validate_range_bound proc(
}
@hide:file
parse_call proc(state @mut State, callee ast.NodeId) ast.NodeId ! ParseError {
parse_call proc(state @mut State, callee ast.NodeId, start u32) ast.NodeId ! ParseError {
_ = try consume(state, .open_paren)
state.paren_depth += 1
defer state.paren_depth -= 1
@@ -622,7 +627,7 @@ parse_call proc(state @mut State, callee ast.NodeId) ast.NodeId ! ParseError {
return try ast.add_call(
&state.builder,
ast.Span{
start = ast.builder_span(&state.builder, callee).start,
start = start,
end = close.span.end,
},
callee,
@@ -630,10 +635,108 @@ parse_call proc(state @mut State, callee ast.NodeId) ast.NodeId ! ParseError {
)
}
@hide:file
parse_index proc(state @mut State, base ast.NodeId, start u32) ast.NodeId ! ParseError {
_ = try consume(state, .open_bracket)
index :: try parse_expr(state)
if !next_is(state, .close_bracket) {
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
return try ast.add_invalid_expr(
&state.builder,
ast.Span{
start = start,
end = ast.builder_span(&state.builder, index).end,
},
diagnostic,
)
}
close :: try consume(state, .close_bracket)
return try ast.add_index(
&state.builder,
ast.Span{ start = start, end = close.span.end },
base, index,
)
}
@hide:file
array_starts_type proc(state @State) bool {
depth usize := 0
for (state.next_token)..(state.tokens.len) |i| {
kind :: state.tokens[i].kind
if (kind == .open_bracket) depth += 1
if (kind == .close_bracket) {
depth -= 1
if (depth == 0) {
if (i + 1 >= state.tokens.len) return false
next :: state.tokens[i + 1].kind
return next == .ident or next == .mut or next == .open_bracket
}
}
if (kind == .open_curly or kind == .close_curly or kind == .eof) return false
}
return false
}
@hide:file
parse_array_literal proc(state @mut State) ast.NodeId ! ParseError {
open :: try consume(state, .open_bracket)
elements arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
defer arraylist.deinit(&elements)
after_open :: state.next_token
skip_newlines(state)
if named_binding_starts(state) {
state.next_token = after_open
return try invalid_array_literal(state, open.span.start, open.span.end)
}
while !next_is(state, .close_bracket) {
element :: try parse_expr(state)
try arraylist.append(&elements, element)
after_element :: state.next_token
skip_newlines(state)
if next_is(state, .close_bracket) break
if !next_is(state, .comma) {
state.next_token = after_element
return try invalid_array_literal(state, open.span.start, ast.builder_span(&state.builder, element).end)
}
state.next_token += 1
after_comma :: state.next_token
skip_newlines(state)
if named_binding_starts(state) {
state.next_token = after_comma
return try invalid_array_literal(state, open.span.start, ast.builder_span(&state.builder, element).end)
}
}
close :: try consume(state, .close_bracket)
return try ast.add_array_literal(
&state.builder, ast.Span{ start = open.span.start, end = close.span.end }, elements.items,
)
}
@hide:file
invalid_array_literal proc(state @mut State, start, end u32) ast.NodeId ! ParseError {
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
return try ast.add_invalid_expr(&state.builder, ast.Span{ start = start, end = end }, diagnostic)
}
@hide:file
named_binding_starts proc(state @State) bool {
return (
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, .ident) or next_n_is(state, 1, .proc)
)
)
}
@hide:file
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
token :: current(state)
match token.kind {
.open_bracket: {
if array_starts_type(state) return try parse_type(state)
return try parse_array_literal(state)
}
.open_paren: {
state.next_token += 1
state.paren_depth += 1
@@ -709,6 +812,7 @@ is_call_boundary proc(kind lexer.TokenKind) bool {
is_expression_recovery_boundary proc(kind lexer.TokenKind) bool {
return (
kind == .comma
or kind == .close_bracket
or kind == .and
or kind == .or
or kind == .pipe
@@ -725,14 +829,8 @@ skip_operand_newlines proc(state @mut State) void {
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)
)
named_binding_starts(state)
or (next_is(state, .ident) and next_n_is(state, 1, .equal))
) state.next_token = start
}
@@ -744,6 +842,8 @@ skip_continuation_newlines proc(state @mut State) void {
kind :: current(state).kind
if (
kind != .close_paren and kind != .comma and kind != .open_paren
and kind != .close_bracket and kind != .open_bracket
and kind != .range and kind != .range_inclusive
and infix_binding_power(kind) == null
) state.next_token = start
}
@@ -753,8 +853,8 @@ recover_decl proc(
state @mut State,
start usize,
) ast.NodeId ! (mem.AllocError | diagnostics.CapacityError) {
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
span :: synchronize(state, start, false)
diagnostic :: try unexpected_diagnostic(state)
span :: synchronize(state, start, false, false, true)
return try ast.add_invalid_decl(&state.builder, span, diagnostic)
}
@@ -763,31 +863,91 @@ recover_statement proc(
state @mut State,
start usize,
) ast.NodeId ! (mem.AllocError | diagnostics.CapacityError) {
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
span :: synchronize(state, start, true)
diagnostic :: try unexpected_diagnostic(state)
span :: synchronize(state, start, true, false, true)
return try ast.add_invalid_stmt(&state.builder, span, diagnostic)
}
@hide:file
synchronize proc(state @mut State, start usize, stop_at_curly bool) ast.Span {
synchronize proc(
state @mut State, start usize, stop_at_curly, allow_else, consume_newline bool,
) ast.Span {
first :: state.tokens[start].span
end := first.end
depth usize := 0
curly usize := 0
paren usize := 0
square usize := 0
for (start..state.next_token) |i| {
kind :: state.tokens[i].kind
if (kind == .open_paren) paren += 1
if (kind == .close_paren and paren > 0) paren -= 1
if (kind == .open_bracket) square += 1
if (kind == .close_bracket and square > 0) square -= 1
}
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
if (curly == 0) {
if (stop_at_curly and next_is(state, .close_curly)) break
if (allow_else and next_is(state, .else)) break
if (
next_is(state, .newline)
and !recovery_closes_delimiters(state, paren, square)
) break
}
kind :: current(state).kind
if (kind == .open_curly) curly += 1
if (kind == .close_curly and curly > 0) curly -= 1
if (kind == .open_paren) paren += 1
if (kind == .close_paren and paren > 0) paren -= 1
if (kind == .open_bracket) square += 1
if (kind == .close_bracket and square > 0) square -= 1
end = current(state).span.end
state.next_token += 1
}
if next_is(state, .newline) {
if (consume_newline and next_is(state, .newline)) {
end = current(state).span.end
state.next_token += 1
}
return ast.Span{ start = first.start, end = end }
}
@hide:file
recovery_closes_delimiters proc(state @State, paren_depth, square_depth usize) bool {
if (paren_depth == 0 and square_depth == 0) return false
paren := paren_depth
square := square_depth
for (state.next_token)..(state.tokens.len) |i| {
kind :: state.tokens[i].kind
match kind {
.open_curly, .close_curly, .eof, .if, .else, .while, .for,
.return, .break, .continue: return false
.ident: {
if (i + 1 < state.tokens.len) {
next :: state.tokens[i + 1].kind
if (next == .double_colon or next == .colon_equal or next == .ident or next == .proc) {
return false
}
}
}
else: {}
}
if (kind == .open_paren) paren += 1
if (kind == .close_paren and paren > 0) paren -= 1
if (kind == .open_bracket) square += 1
if (kind == .close_bracket and square > 0) square -= 1
if (paren == 0 and square == 0) return true
}
return false
}
@hide:file
unexpected_diagnostic proc(state @mut State) diagnostics.DiagnosticId ! (mem.AllocError | diagnostics.CapacityError) {
token :: current(state)
if (token.kind == .invalid and token.data.diagnostic != diagnostics.no_diagnostic) {
return token.data.diagnostic
}
return try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
}
@hide:file
skip_newlines proc(state @mut State) void {
while next_is(state, .newline) state.next_token += 1
+198
View File
@@ -1200,3 +1200,201 @@ range_loop_preserves_nearest_else_and_canonical_body test {
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, call_loop.iterable))
try testing.expect_equal(1, ast.block_statement_count(&tree, call_loop.body))
}
parses_recursive_array_annotations_literals_and_grouped_parameters 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 ::
`matrix [2]mut [width]mut i64 := [
` [1, 2,],
` [3, 4],
`]
`inferred [_]f64 :: [1.0,]
`empty :: []
`width usize :: 2
`choose proc(a, b [width]mut [2]i64, index usize) [_]f64 {
` return []
`}
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))
try testing.expect_equal(5, ast.package_decl_count(&tree))
declaration :: ast.statement_decl(&tree, ast.package_decl(&tree, 0))
outer :: ast.array_type(&tree, declaration.type?)
try testing.expect(outer.mutable)
try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, outer.count?))
inner :: ast.array_type(&tree, outer.element)
try testing.expect(inner.mutable)
try testing.expect_equal(ast.ExprKind.identifier, ast.expr_kind(&tree, inner.count?))
try testing.expect_equal("i64", ast.source_text(&tree, ast.node_span(&tree, inner.element)))
try testing.expect_equal(2, ast.array_element_count(&tree, declaration.initializer))
for (0..ast.array_element_count(&tree, declaration.initializer)) |i| {
try testing.expect_equal(2, ast.array_element_count(
&tree, ast.array_element(&tree, declaration.initializer, i),
))
}
inferred :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
try testing.expect_equal(null, ast.array_type(&tree, inferred.type?).count)
try testing.expect_equal(1, ast.array_element_count(&tree, inferred.initializer))
empty :: ast.statement_decl(&tree, ast.package_decl(&tree, 2))
try testing.expect_equal(0, ast.array_element_count(&tree, empty.initializer))
function :: ast.package_decl(&tree, 4)
try testing.expect_equal(3, ast.function_param_count(&tree, function))
first :: ast.param(&tree, ast.function_param(&tree, function, 0))
second :: ast.param(&tree, ast.function_param(&tree, function, 1))
try testing.expect_equal(first.type, second.type)
try testing.expect_equal(ast.ExprKind.array_type, ast.expr_kind(&tree, first.type))
try testing.expect_equal(null, ast.array_type(&tree, ast.function_decl(&tree, function).return_type).count)
}
parses_postfix_indices_and_general_assignment_targets 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 :: -factory()[position()][0] + ([1, 2])[0]
`main proc() void {
` (matrix[1])[0] = rhs()[0]
` (matrix) = [[1]]
` 1 = 2
`}
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))
expression :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
add :: ast.binary_expr(&tree, expression)
negative :: ast.unary_expr(&tree, add.left)
outer :: ast.index_expr(&tree, negative.operand)
inner :: ast.index_expr(&tree, outer.base)
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, inner.base))
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, inner.index))
try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, outer.index))
literal_index :: ast.index_expr(&tree, add.right)
try testing.expect_equal(ast.ExprKind.array_literal, ast.expr_kind(&tree, literal_index.base))
body :: ast.function_decl(&tree, ast.package_decl(&tree, 1)).body
try testing.expect_equal(3, ast.block_statement_count(&tree, body))
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 0))
target :: ast.index_expr(&tree, assignment.target)
try testing.expect_equal(ast.ExprKind.index, ast.expr_kind(&tree, target.base))
try testing.expect_equal(ast.ExprKind.index, ast.expr_kind(&tree, assignment.value))
grouped :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 1))
try testing.expect_equal(ast.ExprKind.identifier, ast.expr_kind(&tree, grouped.target))
invalid_target :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 2))
try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, invalid_target.target))
}
mirrors_array_type_and_leading_declaration_ambiguities 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 ::
`ambiguous :: [1, 2][0]
`shape :: [1]mut [2]i64
`main proc() void {
` matrix[1][0] = 9
` (matrix[1])[0] = 9
` result :: matrix[1][0]
`}
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(2, diagnostics.count(&diagnostic_store))
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)))
shape :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)).initializer
try testing.expect_equal(ast.ExprKind.array_type, ast.expr_kind(&tree, shape))
try testing.expect_equal(ast.ExprKind.array_type, ast.expr_kind(&tree, ast.array_type(&tree, shape).element))
body :: ast.function_decl(&tree, ast.package_decl(&tree, 2)).body
try testing.expect_equal(3, ast.block_statement_count(&tree, body))
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 0)))
try testing.expect_equal(ast.StmtKind.assignment_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 1)))
result :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 2)).initializer
try testing.expect_equal(ast.ExprKind.index, ast.expr_kind(&tree, result))
}
preserves_array_error_ids_control_bodies_and_following_siblings 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 {
` bad [$]i64 :: [$, 2]
` while (items[0) { break }
` for items[0 |i| { continue }
` unfinished :: [1,
` kept :: []
`}
`tail :: 4
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &diagnostic_store)
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
defer ast.deinit(&tree)
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(5, ast.block_statement_count(&tree, body))
bad :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 0))
count :: ast.array_type(&tree, bad.type?).count?
element :: ast.array_element(&tree, bad.initializer, 0)
try testing.expect_equal(diagnostics.DiagnosticId(0), ast.invalid(&tree, count).diagnostic)
try testing.expect_equal(diagnostics.DiagnosticId(1), ast.invalid(&tree, element).diagnostic)
while_loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 1))
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, while_loop.condition))
try testing.expect_equal(ast.StmtKind.break_stmt, ast.stmt_kind(
&tree, ast.block_statement(&tree, while_loop.body, 0),
))
for_loop :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 2))
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, for_loop.iterable))
try testing.expect_equal(ast.StmtKind.continue_stmt, ast.stmt_kind(
&tree, ast.block_statement(&tree, for_loop.body, 0),
))
unfinished :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 3))
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, unfinished.initializer))
kept :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 4))
try testing.expect_equal("kept", ast.source_text(&tree, kept.name))
try testing.expect_equal(0, ast.array_element_count(&tree, kept.initializer))
}
recovers_nested_square_and_parenthesis_delimiters_without_consuming_siblings 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 {
` bad [call(1,
` 2)]i64 = [3]
` kept :: []
` missing [2
` sibling :: [4]
`}
`tail :: 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(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(4, ast.block_statement_count(&tree, body))
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 0)))
kept :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 1))
sibling :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 3))
try testing.expect_equal(0, ast.array_element_count(&tree, kept.initializer))
try testing.expect_equal(1, ast.array_element_count(&tree, sibling.initializer))
}