feat: support enums, distinct types, unions and matching

This commit is contained in:
2026-10-25 23:16:09 +01:00
parent 280059d83d
commit 9966a1f523
23 changed files with 3049 additions and 176 deletions
+289 -13
View File
@@ -30,6 +30,7 @@ State :: struct {
next_token usize
paren_depth usize = 0
allow_record_literals bool = true
match_arm_body bool = false
}
#! borrows `tokens` and `source` while parsing; the returned Tree borrows `source` until ast.deinit.
@@ -156,7 +157,7 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
while true {
token :: try consume(state, .ident)
try arraylist.append(&names, token)
if (next_is(state, .ident) or next_is(state, .open_bracket) or next_is(state, .struct)) break
if (starts_type(current(state).kind)) break
_ = try consume(state, .comma)
}
@@ -237,6 +238,8 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
and (
next_n_is(state, 1, .ident)
or next_n_is(state, 1, .struct)
or next_n_is(state, 1, .enum) or next_n_is(state, 1, .union)
or next_n_is(state, 1, .distinct)
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)
@@ -250,7 +253,8 @@ 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, .open_bracket, .open_curly, .struct: {
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .open_curly, .struct,
.enum, .union, .distinct, .alias, .dot, .match: {
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)
@@ -264,6 +268,7 @@ parse_return proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
keyword :: try consume(state, .return)
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))
) {
return try ast.add_return_stmt(state.builder, keyword.span, null)
@@ -272,6 +277,7 @@ parse_return proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
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
}
@@ -297,6 +303,7 @@ parse_loop_control proc(state @mut State, allow_else bool) ast.NodeId ! ParseErr
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
if (keyword.kind == .break)
@@ -314,6 +321,7 @@ parse_assignment proc(state @mut State, target ast.NodeId, allow_else bool) ast.
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
}
@@ -569,7 +577,7 @@ parse_arm proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
@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) or next_is(state, .open_bracket) or next_is(state, .struct))
type_id ?ast.NodeId :: if (starts_type(current(state).kind))
try parse_type(state)
else
null
@@ -622,11 +630,14 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
)
}
if next_is(state, .struct) return try parse_record_type(state)
if next_is(state, .enum) return try parse_enum_type(state)
if next_is(state, .union) return try parse_union_type(state)
if (next_is(state, .distinct) or next_is(state, .alias)) return try parse_type_wrapper(state)
token :: try consume(state, .ident)
type := try ast.add_identifier(state.builder, token.span, token.data.str_id)
while next_is(state, .dot) {
state.next_token += 1
member :: try consume(state, .ident)
member :: try consume_member(state, false)
member_id :: try ast.add_identifier(state.builder, member.span, member.data.str_id)
type = try ast.add_selection(state.builder, ast.Span{
start = token.span.start,
@@ -637,6 +648,261 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
return type
}
@hide:file
starts_type proc(kind lexer.TokenKind) bool {
return (
kind == .ident or kind == .open_bracket or kind == .struct
or kind == .enum or kind == .union or kind == .distinct or kind == .alias
)
}
@hide:file
member_name proc(kind lexer.TokenKind) bool {
return match kind {
.ident, .if, .else, .for, .while, .proc, .return, .break, .continue,
.true, .false, .and, .or, .mut, .struct, .enum, .union, .distinct,
.alias, .match, .import, .test: true
else: false
}
}
@hide:file
consume_member proc(state @mut State, numeric bool) lexer.Token ! SyntaxError {
token :: current(state)
if (!member_name(token.kind) and !(numeric and token.kind == .int)) return .unexpected_token
state.next_token += 1
return token
}
@hide:file
parse_type_wrapper proc(state @mut State) ast.NodeId ! ParseError {
keyword :: current(state)
state.next_token += 1
operand :: try parse_type(state)
return try ast.add_type_operand(state.builder, ast.Span{
start = keyword.span.start,
end = ast.builder_span(state.builder, operand).end,
source = keyword.span.source,
}, operand, keyword.kind == .alias)
}
@hide:file
parse_enum_type proc(state @mut State) ast.NodeId ! ParseError {
keyword :: try consume(state, .enum)
backing ?ast.NodeId := null
if next_is(state, .open_paren) {
state.next_token += 1
backing = try parse_type(state)
_ = try consume(state, .close_paren)
}
skip_newlines(state)
_ = try consume(state, .open_curly)
members arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
defer arraylist.deinit(&members)
skip_newlines(state)
while (!next_is(state, .close_curly) and !next_is(state, .eof)) {
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, .proc))
) return try invalid_record(state, keyword.span.start, false)
start :: state.next_token
member :: parse_enum_member(state) catch |err| {
match err {
.unexpected_token: {}
else: return err
}
diagnostic :: try unexpected_diagnostic(state)
span :: recover_record_field(state, start)
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
yield try ast.add_field(state.builder, span, null, null, invalid)
}
try arraylist.append(&members, member)
if next_is(state, .close_curly) break
if next_is(state, .comma) state.next_token += 1
else if (!next_is(state, .newline)) return try invalid_record(state, keyword.span.start, true)
skip_newlines(state)
}
if (!next_is(state, .close_curly)) return try invalid_record(state, keyword.span.start, false)
close :: try consume(state, .close_curly)
return try ast.add_enum_type(state.builder, ast.Span{
start = keyword.span.start, end = close.span.end, source = keyword.span.source,
}, backing, members.items)
}
@hide:file
parse_enum_member proc(state @mut State) ast.NodeId ! ParseError {
token :: try consume_member(state, false)
name :: try ast.add_identifier(state.builder, token.span, token.data.str_id)
value ?ast.NodeId := null
if next_is(state, .equal) {
state.next_token += 1
value = try parse_expr(state)
}
end :: if (value) |id| ast.builder_span(state.builder, id).end else token.span.end
return try ast.add_field(state.builder, ast.Span{
start = token.span.start,
end = end,
source = token.span.source,
}, name, null, value)
}
@hide:file
parse_union_type proc(state @mut State) ast.NodeId ! ParseError {
keyword :: try consume(state, .union)
tag ?ast.NodeId := null
auto_tag := false
if next_is(state, .open_paren) {
state.next_token += 1
if next_is(state, .enum) {
state.next_token += 1
auto_tag = true
} else {
tag = try parse_type(state)
}
_ = try consume(state, .close_paren)
}
skip_newlines(state)
_ = try consume(state, .open_curly)
fields arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
defer arraylist.deinit(&fields)
skip_newlines(state)
while (!next_is(state, .close_curly) and !next_is(state, .eof)) {
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, .proc))
) return try invalid_record(state, keyword.span.start, false)
start :: state.next_token
field :: parse_union_field(state, auto_tag or tag != null) catch |err| {
match err {
.unexpected_token: {}
else: return err
}
diagnostic :: try unexpected_diagnostic(state)
span :: recover_record_field(state, start)
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
yield try ast.add_field(state.builder, span, null, invalid, null)
}
try arraylist.append(&fields, field)
if next_is(state, .close_curly) break
if next_is(state, .comma) state.next_token += 1
else if (!next_is(state, .newline)) return try invalid_record(state, keyword.span.start, true)
skip_newlines(state)
}
if (!next_is(state, .close_curly)) return try invalid_record(state, keyword.span.start, false)
close :: try consume(state, .close_curly)
return try ast.add_union_type(state.builder, ast.Span{
start = keyword.span.start, end = close.span.end, source = keyword.span.source,
}, tag, auto_tag, fields.items)
}
@hide:file
parse_union_field proc(state @mut State, tagged bool) ast.NodeId ! ParseError {
token :: if (tagged) try consume_member(state, false) else try consume(state, .ident)
name :: try ast.add_identifier(state.builder, token.span, token.data.str_id)
type :: try parse_type(state)
return try ast.add_field(state.builder, ast.Span{
start = token.span.start, end = ast.builder_span(state.builder, type).end, source = token.span.source,
}, name, type, null)
}
@hide:file
parse_match proc(state @mut State) ast.NodeId ! ParseError {
keyword :: try consume(state, .match)
skip_newlines(state)
subject :: try parse_guard_expr(state)
skip_newlines(state)
_ = try consume(state, .open_curly)
previous_depth :: state.paren_depth
state.paren_depth = 0
defer state.paren_depth = previous_depth
arms arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
defer arraylist.deinit(&arms)
skip_newlines(state)
while (!next_is(state, .close_curly) and !next_is(state, .eof)) {
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, .proc))
) return try invalid_record(state, keyword.span.start, false)
start :: state.next_token
arm :: parse_match_arm(state) catch |err| {
match err {
.unexpected_token: {}
else: return err
}
diagnostic :: try unexpected_diagnostic(state)
span :: recover_record_field(state, start)
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
yield try ast.add_match_arm(state.builder, span, &[], null, invalid, true, false)
}
try arraylist.append(&arms, arm)
if next_is(state, .close_curly) break
if next_is(state, .comma) state.next_token += 1
else if (!next_is(state, .newline)) return try invalid_record(state, keyword.span.start, true)
skip_newlines(state)
}
if (!next_is(state, .close_curly)) return try invalid_record(state, keyword.span.start, false)
close :: try consume(state, .close_curly)
return try ast.add_match(state.builder, ast.Span{
start = keyword.span.start, end = close.span.end, source = keyword.span.source,
}, subject, arms.items)
}
@hide:file
parse_match_arm proc(state @mut State) ast.NodeId ! ParseError {
start :: current(state).span
patterns arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
defer arraylist.deinit(&patterns)
catch_all :: next_is(state, .else)
capture ?ast.NodeId := null
if (catch_all) state.next_token += 1
else {
while true {
try arraylist.append(&patterns, try parse_guard_expr(state))
if (!next_is(state, .comma)) break
state.next_token += 1
skip_newlines(state)
}
if next_is(state, .pipe) {
state.next_token += 1
# pointer payload captures are outside the current runtime representation.
if (next_is(state, .at) or next_is(state, .star)) return .unexpected_token
token :: try consume(state, .ident)
if (
token.span.end != token.span.start + 1
or state.builder.source[usize(token.span.start)] != '_'
) capture = try ast.add_identifier(state.builder, token.span, token.data.str_id)
_ = try consume(state, .pipe)
}
}
_ = try consume(state, .colon)
skip_newlines(state)
previous :: state.match_arm_body
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: true
else: false
}
if (statement_body or named_binding_starts(state)) {
body :: try parse_arm(state, false)
return try ast.add_match_arm(state.builder, ast.Span{
start = start.start, end = ast.builder_span(state.builder, body).end, source = start.source,
}, patterns.items, capture, body, false, catch_all)
}
value :: try parse_expr(state)
if next_is(state, .equal) {
statement :: try parse_assignment(state, value, false)
statements [1]ast.NodeId :: [statement]
body :: try ast.add_block(state.builder, ast.builder_span(state.builder, statement), statements[..])
return try ast.add_match_arm(state.builder, ast.Span{
start = start.start, end = ast.builder_span(state.builder, body).end, source = start.source,
}, patterns.items, capture, body, false, catch_all)
}
return try ast.add_match_arm(state.builder, ast.Span{
start = start.start, end = ast.builder_span(state.builder, value).end, source = start.source,
}, patterns.items, capture, value, true, catch_all)
}
@hide:file
parse_record_type proc(state @mut State) ast.NodeId ! ParseError {
keyword :: try consume(state, .struct)
@@ -711,15 +977,16 @@ parse_record_field proc(state @mut State, declaration bool) ast.NodeId ! ParseEr
name ?ast.NodeId := null
type ?ast.NodeId := null
value ?ast.NodeId := null
named :: next_is(state, .ident) and (
named :: member_name(current(state).kind) and (
(!declaration and next_n_is(state, 1, .equal))
or (declaration and (
next_n_is(state, 1, .ident) or next_n_is(state, 1, .open_bracket)
or next_n_is(state, 1, .struct)
or next_n_is(state, 1, .struct) or next_n_is(state, 1, .enum)
or next_n_is(state, 1, .union) or next_n_is(state, 1, .distinct)
))
)
if (named) {
token :: try consume(state, .ident)
token :: if (declaration) try consume(state, .ident) else try consume_member(state, false)
name = try ast.add_identifier(state.builder, token.span, token.data.str_id)
}
if (declaration) {
@@ -836,7 +1103,7 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
}
if (token.kind == .dot) {
state.next_token += 1
member :: try consume_either(state, &[.ident, .int])
member :: try consume_member(state, true)
member_id :: if (member.kind == .int)
try ast.add_literal(state.builder, .int, member.span)
else
@@ -999,7 +1266,7 @@ array_starts_type proc(state @State) bool {
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 or next == .struct
return starts_type(next) or next == .mut
}
}
if (kind == .open_curly or kind == .close_curly or kind == .eof) return false
@@ -1073,6 +1340,18 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
token :: current(state)
match token.kind {
.struct: return try parse_record_type(state)
.enum: return try parse_enum_type(state)
.union: return try parse_union_type(state)
.distinct, .alias: return try parse_type_wrapper(state)
.match: return try parse_match(state)
.dot: {
state.next_token += 1
member :: try consume_member(state, false)
member_id :: try ast.add_identifier(state.builder, member.span, member.data.str_id)
return try ast.add_enum_literal(state.builder, ast.Span{
start = token.span.start, end = member.span.end, source = token.span.source,
}, member_id)
}
.open_curly: {
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)
@@ -1126,10 +1405,7 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
return try ast.add_identifier(state.builder, token.span, token.data.str_id)
}
else: {
diagnostic :: if (token.data.diagnostic != diagnostics.no_diagnostic)
token.data.diagnostic
else
try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
diagnostic :: try unexpected_diagnostic(state)
if !is_expression_recovery_boundary(token.kind) {
state.next_token += 1
+124 -2
View File
@@ -940,7 +940,6 @@ recovers_missing_logical_operands_and_preserves_enclosing_nodes test {
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))
@@ -952,6 +951,11 @@ recovers_missing_logical_operands_and_preserves_enclosing_nodes test {
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))
missing :: ast.invalid(&tree, second_and.right)
diagnostic :: diagnostics.get(&diagnostic_store, missing.diagnostic)?
try testing.expect_equal(diagnostics.Code.unexpected_token, diagnostic.code)
try testing.expect_equal(missing.span.start, diagnostic.span.start)
try testing.expect_equal(missing.span.end, diagnostic.span.end)
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))
@@ -1036,13 +1040,17 @@ rejects_control_tails_and_preserves_following_declarations test {
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(9, diagnostics.count(&diagnostic_store))
try testing.expect_equal(6, ast.package_decl_count(&tree))
for (usize(0)..usize(2)) |index| try testing.expect_equal(ast.DeclKind.invalid, ast.decl_kind(&tree, ast.package_decl(&tree, index)))
for (usize(2)..usize(4)) |index| {
declaration :: ast.package_decl(&tree, index)
try testing.expect_equal(ast.DeclKind.statement, ast.decl_kind(&tree, declaration))
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, ast.statement_decl(&tree, declaration).initializer))
invalid :: ast.invalid(&tree, ast.statement_decl(&tree, declaration).initializer)
diagnostic :: diagnostics.get(&diagnostic_store, invalid.diagnostic)?
try testing.expect_equal(diagnostics.Code.unexpected_token, diagnostic.code)
try testing.expect_equal(invalid.span.start, diagnostic.span.start)
try testing.expect_equal(invalid.span.end, diagnostic.span.end)
}
body :: ast.function_decl(&tree, ast.package_decl(&tree, 4)).body
try testing.expect_equal(2, ast.block_statement_count(&tree, body))
@@ -1588,3 +1596,117 @@ rolls_back_shared_nodes_extra_spans_and_diagnostics_on_allocation_failure test {
}
try testing.expect(failures > 0)
}
parses_enum_union_wrappers_and_keyword_variants test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`Tag :: enum(i32) { if = -2, else = 3 # preserve the member boundary
` return
`}
`Payload :: union(Tag) { if i32, else void, return struct { value i32 } }
`Auto :: union(enum) { value i32, empty void }
`Raw :: union { integer i32, real f64 }
`Id :: distinct i32
`Other :: alias Id
`value Payload :: Payload{ if = 4 }
`context Payload :: .return{value = 5}
`empty Payload :: .else
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(9, ast.package_decl_count(&tree))
enumeration :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
try testing.expect(ast.enum_type(&tree, enumeration).backing != null)
try testing.expect_equal(3, ast.enum_member_count(&tree, enumeration))
member :: ast.field(&tree, ast.enum_member(&tree, enumeration, 0))
try testing.expect_equal("if", strpool.get_str(&strings, ast.name_id(&tree, member.name?))?)
try testing.expect_equal(ast.ExprKind.unary, ast.expr_kind(&tree, member.value?))
tagged :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)).initializer
automatic :: ast.statement_decl(&tree, ast.package_decl(&tree, 2)).initializer
raw :: ast.statement_decl(&tree, ast.package_decl(&tree, 3)).initializer
try testing.expect(ast.union_type(&tree, tagged).tag != null)
try testing.expect(ast.union_type(&tree, automatic).auto_tag)
try testing.expect(!ast.union_type(&tree, raw).auto_tag)
try testing.expect_equal(2, ast.record_field_count(&tree, raw))
distinct_type :: ast.statement_decl(&tree, ast.package_decl(&tree, 4)).initializer
alias_value :: ast.statement_decl(&tree, ast.package_decl(&tree, 5)).initializer
try testing.expect_equal(ast.ExprKind.distinct_type, ast.expr_kind(&tree, distinct_type))
try testing.expect_equal(ast.ExprKind.alias_expr, ast.expr_kind(&tree, alias_value))
contextual :: ast.statement_decl(&tree, ast.package_decl(&tree, 7)).initializer
variant :: ast.record_literal(&tree, contextual).type?
try testing.expect_equal(ast.ExprKind.enum_literal, ast.expr_kind(&tree, variant))
try testing.expect_equal("return", strpool.get_str(&strings, ast.name_id(&tree, ast.enum_literal(&tree, variant)))?)
}
parses_match_patterns_payload_captures_and_statement_arms test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`choose proc(value int) int {
` return match value {
` 0, 1..=3: 10,
` 4: 20 # preserve the arm boundary
` else: 30
` }
`}
`use proc(value int) void {
` match value {
` .if, .return |payload|: { local :: payload }
` .else: return,
` else: {}
` }
`}
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))
choose :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
value_match :: ast.return_stmt(&tree, ast.block_statement(&tree, choose.body, 0)).value?
try testing.expect_equal(3, ast.match_arm_count(&tree, value_match))
first :: ast.match_arm(&tree, value_match, 0)
try testing.expect_equal(2, ast.match_pattern_count(&tree, first))
try testing.expect_equal(ast.ExprKind.range, ast.expr_kind(&tree, ast.match_pattern(&tree, first, 1)))
try testing.expect(ast.match_arm_view(&tree, first).expression)
use :: ast.function_decl(&tree, ast.package_decl(&tree, 1))
statement_match :: ast.statement_expr(&tree, ast.block_statement(&tree, use.body, 0))
captured :: ast.match_arm_view(&tree, ast.match_arm(&tree, statement_match, 0))
try testing.expect(captured.capture != null)
try testing.expect(!captured.expression)
try testing.expect(ast.match_arm_view(&tree, ast.match_arm(&tree, statement_match, 2)).catch_all)
}
rejects_pointer_match_captures_and_preserves_siblings test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`broken :: enum { first
`after :: 1
`use proc(value int) void {
` match value {
` .item |@payload|: payload
` else: {}
` }
`}
`last :: 2
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(diagnostics.count(&diagnostic_store) > 0)
try testing.expect_equal(4, ast.package_decl_count(&tree))
try testing.expect_equal("after", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 1)).name))
try testing.expect_equal("last", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 3)).name))
}