match statements

This commit is contained in:
2026-06-28 23:44:01 +02:00
parent 981ccb047a
commit 462632554c
12 changed files with 1057 additions and 6 deletions
+109 -1
View File
@@ -1128,6 +1128,7 @@ parse_value_control_flow :: proc(parser: ^Parser) -> (ast.Stmt_Id, bool) {
case .Keyword_If: return parse_if(parser), true
case .Keyword_For: return parse_for(parser), true
case .Keyword_While: return parse_while(parser), true
case .Keyword_Match: return parse_match(parser), true
}
return ast.INVALID_STMT, false
}
@@ -1157,6 +1158,9 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
if current(parser).kind == .Keyword_Yield {
return parse_yield(parser)
}
if current(parser).kind == .Keyword_Match {
return parse_match(parser)
}
// A leading `{` opens a bare block scope (struct literals are postfix only).
if current(parser).kind == .Left_Brace {
return parse_block_statement(parser)
@@ -1526,6 +1530,110 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
return id
}
// parse_arm_body parses a match arm's body after the `:`: a braced block (whose
// inner statements are returned unwrapped, like `parse_branch_body`) or a single
// brace-less statement. For value-match a brace-less body is a single expression
// that the checker yields implicitly.
parse_arm_body :: proc(parser: ^Parser) -> []ast.Stmt_Id {
skip_newlines(parser)
if current(parser).kind == .Left_Brace {
return parse_block(parser)
}
single := make([]ast.Stmt_Id, 1, parser.module.allocator)
single[0] = parse_statement(parser)
return single
}
// parse_match_arm parses one `<pattern> [|capture|]: <body>` arm (or `else: <body>`).
// The pattern is `INVALID_EXPR` for `else`; `captures` holds the optional 0-or-1
// payload capture name (tagged-union variants only).
parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
start := current(parser).span
pattern := ast.INVALID_EXPR
captures: [dynamic]symbol.Id
captures.allocator = parser.module.allocator
if _, is_else := allow(parser, .Keyword_Else); !is_else {
saved := parser.no_struct_literal
parser.no_struct_literal = true
pattern = parse_expression(parser)
parser.no_struct_literal = saved
if _, ok := allow(parser, .Pipe); ok {
name_tok := current(parser)
if name_tok.kind == .Identifier || name_tok.kind == .Underscore {
advance(parser)
append(&captures, name_tok.symbol)
} else {
source.add(parser.diagnostics, current(parser).span, "expected a capture name after '|'")
}
if _, close_ok := allow(parser, .Pipe); !close_ok {
source.add(parser.diagnostics, current(parser).span, "expected '|' to close the match capture")
}
}
}
if _, ok := allow(parser, .Colon); !ok {
source.add(parser.diagnostics, current(parser).span, "expected ':' after a match pattern")
}
body := parse_arm_body(parser)
id := ast.stmt_id(len(parser.module.statements))
append(&parser.module.statements, ast.Stmt{
kind=.Match_Arm,
span=span_from(start, previous(parser).span),
expr=pattern,
captures=captures[:],
body=body,
target=ast.INVALID_EXPR,
update=ast.INVALID_STMT,
diagnostic=source.INVALID_DIAGNOSTIC,
})
return id
}
// parse_match parses `match <subject> { <arm>* }`. Arms are newline-separated; each
// is a `Match_Arm` statement stored in the `Match`'s `body`. Usable as a statement
// and (via `parse_value_control_flow`) as a value source on a declaration/assignment.
parse_match :: proc(parser: ^Parser) -> ast.Stmt_Id {
start := advance(parser) // consume 'match'
skip_newlines(parser)
saved := parser.no_struct_literal
parser.no_struct_literal = true
subject := parse_expression(parser)
parser.no_struct_literal = saved
arms: [dynamic]ast.Stmt_Id
arms.allocator = parser.module.allocator
skip_newlines(parser)
if _, ok := allow(parser, .Left_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '{' to open match arms")
}
skip_newlines(parser)
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
append(&arms, parse_match_arm(parser))
if diagnostic := finish_statement(parser, true); diagnostic != source.INVALID_DIAGNOSTIC {
arm_id := ast.stmt_id(len(parser.module.statements))
append(&parser.module.statements, ast.Stmt{
kind=.Invalid,
span=current(parser).span,
expr=ast.INVALID_EXPR,
diagnostic=diagnostic,
})
append(&arms, arm_id)
}
}
if _, ok := allow(parser, .Right_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '}' to close match arms")
}
id := ast.stmt_id(len(parser.module.statements))
append(&parser.module.statements, ast.Stmt{
kind=.Match,
span=span_from(start.span, previous(parser).span),
expr=subject,
body=arms[:],
target=ast.INVALID_EXPR,
update=ast.INVALID_STMT,
diagnostic=source.INVALID_DIAGNOSTIC,
})
return id
}
parse_while_update :: proc(parser: ^Parser) -> ast.Stmt_Id {
parenthesized := false
if _, ok := allow(parser, .Left_Paren); ok {
@@ -1559,7 +1667,7 @@ parse_while_update :: proc(parser: ^Parser) -> ast.Stmt_Id {
statement := &parser.module.statements[update]
switch statement.kind {
case .Assignment, .Expression:
case .Invalid, .Declaration, .Return, .If, .While, .For, .Break, .Continue, .Block, .Defer, .Yield:
case .Invalid, .Declaration, .Return, .If, .While, .For, .Break, .Continue, .Block, .Defer, .Yield, .Match, .Match_Arm:
diagnostic := source.add(
parser.diagnostics,
statement.span,