1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 17:06:04 +00:00

Language: match statement

match state {
        Title => { if button.start { state = Playing } }
        Playing => { /* ... */ }
        GameOver => { if button.a { state = Title } }
        _ => {}
    }

- Lexer: \`match\` keyword and \`=>\` (FatArrow) token
- Parser: \`parse_match\` after the existing loop constructs. Each
  arm is \`pattern => { body }\`, with \`_\` as the catch-all. The
  match scrutinee is parsed with struct-literal restriction enabled
  so the following \`{\` is unambiguously the match body, not a
  struct literal.
- The parser desugars match directly into an if/else-if chain so
  the analyzer, IR lowering, and codegen don't need new AST variants
  — each arm becomes \`scrutinee == pattern\` as the condition, and
  the default arm (if any) becomes the final \`else\` block.

Tests cover parse + full pipeline integration for state-style
dispatch using an enum.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
Claude 2026-04-12 17:21:00 +00:00
parent c8ae433a7c
commit f0264b124a
No known key found for this signature in database
7 changed files with 180 additions and 0 deletions

View file

@ -850,6 +850,7 @@ impl Parser {
TokenKind::KwIf => self.parse_if(),
TokenKind::KwWhile => self.parse_while(),
TokenKind::KwFor => self.parse_for(),
TokenKind::KwMatch => self.parse_match(),
TokenKind::KwLoop => self.parse_loop(),
TokenKind::KwBreak => {
let span = self.current_span();
@ -998,6 +999,83 @@ impl Parser {
Ok(Statement::Loop(body, start))
}
/// Parse `match expr { pat => { body }, pat => { body }, _ => { body } }`.
/// Desugars to a chain of `if expr == pat { body } else if ...`
/// at parse time — no dedicated AST variant needed.
fn parse_match(&mut self) -> Result<Statement, Diagnostic> {
let start = self.current_span();
self.expect(&TokenKind::KwMatch)?;
let saved = self.restrict_struct_literals;
self.restrict_struct_literals = true;
let scrutinee = self.parse_expr()?;
self.restrict_struct_literals = saved;
self.expect(&TokenKind::LBrace)?;
let mut arms: Vec<(Expr, Block)> = Vec::new();
let mut default: Option<Block> = None;
while *self.peek() != TokenKind::RBrace && *self.peek() != TokenKind::Eof {
// A default arm is `_ => { ... }`.
if let TokenKind::Ident(name) = self.peek().clone() {
if name == "_" {
self.advance();
self.expect(&TokenKind::FatArrow)?;
let body = self.parse_block()?;
default = Some(body);
if *self.peek() == TokenKind::Comma {
self.advance();
}
continue;
}
}
let pat_span = self.current_span();
let pat = self.parse_expr()?;
self.expect(&TokenKind::FatArrow)?;
let body = self.parse_block()?;
// Build `scrutinee == pat` as the branch condition.
let cond = Expr::BinaryOp(
Box::new(scrutinee.clone()),
BinOp::Eq,
Box::new(pat),
pat_span,
);
arms.push((cond, body));
if *self.peek() == TokenKind::Comma {
self.advance();
}
}
self.expect(&TokenKind::RBrace)?;
if arms.is_empty() {
// `match x { _ => body }` or empty match — emit the
// default block directly, or an empty no-op.
if let Some(body) = default {
return Ok(Statement::If(
Expr::BoolLiteral(true, start),
body,
Vec::new(),
None,
start,
));
}
return Ok(Statement::If(
Expr::BoolLiteral(false, start),
Block {
statements: Vec::new(),
span: start,
},
Vec::new(),
None,
start,
));
}
// Build an if/else-if chain. The first arm becomes the
// `then` block; subsequent arms become `else if` entries;
// the default arm (if any) becomes the final `else`.
let (first_cond, first_body) = arms.remove(0);
Ok(Statement::If(first_cond, first_body, arms, default, start))
}
fn parse_for(&mut self) -> Result<Statement, Diagnostic> {
let start = self.current_span();
self.expect(&TokenKind::KwFor)?;