Skip to main content

lex_syntax/
parser.rs

1//! Recursive-descent parser for Lex. Pratt-style precedence climbing for
2//! binary operators; everything else is straightforward LL(1)-with-lookahead.
3
4use crate::syntax::*;
5use crate::token::{Token, TokenKind};
6
7pub fn parse(tokens: Vec<Token>) -> Result<Program, ParseError> {
8    let mut p = Parser::new(tokens);
9    let program = p.parse_program()?;
10    p.skip_newlines();
11    if !p.at_eof() {
12        return Err(p.error("unexpected token after program"));
13    }
14    Ok(program)
15}
16
17#[derive(Debug, thiserror::Error)]
18#[error("parse error at byte {pos}: {msg}")]
19pub struct ParseError {
20    pub pos: usize,
21    pub msg: String,
22}
23
24struct Parser {
25    tokens: Vec<Token>,
26    idx: usize,
27    /// Recursion depth across `parse_expr`. Capped at `MAX_DEPTH`
28    /// to defend against adversarial input like a long sequence of
29    /// `[[[{{{...` that would otherwise blow the stack. Found by
30    /// the libFuzzer parser target — see `fuzz/fuzz_targets/parser.rs`.
31    depth: u32,
32}
33
34/// Maximum nesting depth the parser will accept before refusing
35/// with a parse error. Real Lex code rarely exceeds 30; 96 leaves
36/// generous headroom for legitimate generated code.
37///
38/// Each `parse_expr` level produces ~4-5 stack frames through the
39/// `parse_binary_expr → parse_unary_expr → parse_postfix →
40/// parse_primary → ...` chain, so this caps the actual frame
41/// count around 400-500 — well below even a 2 MiB test stack.
42const MAX_DEPTH: u32 = 96;
43
44impl Parser {
45    fn new(tokens: Vec<Token>) -> Self {
46        Self { tokens, idx: 0, depth: 0 }
47    }
48
49    fn at_eof(&self) -> bool {
50        self.idx >= self.tokens.len()
51    }
52
53    fn peek(&self) -> Option<&TokenKind> {
54        self.tokens.get(self.idx).map(|t| &t.kind)
55    }
56
57    fn bump(&mut self) -> Option<Token> {
58        let t = self.tokens.get(self.idx).cloned();
59        if t.is_some() {
60            self.idx += 1;
61        }
62        t
63    }
64
65    fn current_pos(&self) -> usize {
66        self.tokens
67            .get(self.idx)
68            .map(|t| t.span.start)
69            .unwrap_or_else(|| self.tokens.last().map(|t| t.span.end).unwrap_or(0))
70    }
71
72    fn error(&self, msg: impl Into<String>) -> ParseError {
73        ParseError { pos: self.current_pos(), msg: msg.into() }
74    }
75
76    fn skip_newlines(&mut self) {
77        while matches!(self.peek(), Some(TokenKind::Newline) | Some(TokenKind::Semi)) {
78            self.idx += 1;
79        }
80    }
81
82    fn expect(&mut self, expected: &TokenKind, ctx: &str) -> Result<Token, ParseError> {
83        self.skip_newlines();
84        match self.peek() {
85            Some(k) if std::mem::discriminant(k) == std::mem::discriminant(expected) => {
86                Ok(self.bump().unwrap())
87            }
88            Some(other) => Err(self.error(format!(
89                "expected {expected:?} {ctx}, got {other:?}"
90            ))),
91            None => Err(self.error(format!("expected {expected:?} {ctx}, got EOF"))),
92        }
93    }
94
95    fn eat(&mut self, k: &TokenKind) -> bool {
96        self.skip_newlines();
97        if let Some(cur) = self.peek() {
98            if std::mem::discriminant(cur) == std::mem::discriminant(k) {
99                self.bump();
100                return true;
101            }
102        }
103        false
104    }
105
106    fn expect_ident(&mut self, ctx: &str) -> Result<String, ParseError> {
107        self.skip_newlines();
108        match self.peek() {
109            Some(TokenKind::Ident(_)) => match self.bump().unwrap().kind {
110                TokenKind::Ident(name) => Ok(name),
111                _ => unreachable!(),
112            },
113            other => Err(self.error(format!("expected identifier {ctx}, got {other:?}"))),
114        }
115    }
116
117    // --- top level ---
118
119    fn parse_program(&mut self) -> Result<Program, ParseError> {
120        let mut items = Vec::new();
121        loop {
122            self.skip_newlines();
123            if self.at_eof() {
124                break;
125            }
126            items.push(self.parse_item()?);
127        }
128        Ok(Program { items })
129    }
130
131    fn parse_item(&mut self) -> Result<Item, ParseError> {
132        match self.peek() {
133            Some(TokenKind::Import) => self.parse_import().map(Item::Import),
134            Some(TokenKind::Type) => self.parse_type_decl().map(Item::TypeDecl),
135            Some(TokenKind::Fn) => self.parse_fn_decl().map(Item::FnDecl),
136            other => Err(self.error(format!(
137                "expected `import`, `type`, or `fn` at top level, got {other:?}"
138            ))),
139        }
140    }
141
142    fn parse_import(&mut self) -> Result<Import, ParseError> {
143        self.expect(&TokenKind::Import, "in import")?;
144        let reference = match self.bump().map(|t| t.kind) {
145            Some(TokenKind::Str(s)) => s,
146            other => return Err(self.error(format!("expected string after `import`, got {other:?}"))),
147        };
148        self.expect(&TokenKind::As, "in import")?;
149        let alias = self.expect_ident("for import alias")?;
150        Ok(Import { reference, alias })
151    }
152
153    fn parse_type_decl(&mut self) -> Result<TypeDecl, ParseError> {
154        self.expect(&TokenKind::Type, "in type decl")?;
155        let name = self.expect_ident("for type name")?;
156        let params = if self.eat(&TokenKind::LBracket) {
157            let ps = self.parse_ident_list()?;
158            self.expect(&TokenKind::RBracket, "after type params")?;
159            ps
160        } else {
161            Vec::new()
162        };
163        self.expect(&TokenKind::Eq, "in type decl")?;
164        let definition = self.parse_type_decl_rhs()?;
165        Ok(TypeDecl { name, params, definition })
166    }
167
168    fn parse_ident_list(&mut self) -> Result<Vec<String>, ParseError> {
169        let mut out = Vec::new();
170        out.push(self.expect_ident("in identifier list")?);
171        while self.eat(&TokenKind::Comma) {
172            if matches!(self.peek_skip_newlines(), Some(TokenKind::RBracket)) { break; }
173            out.push(self.expect_ident("in identifier list")?);
174        }
175        Ok(out)
176    }
177
178    /// `type Foo = Variant1 | Variant2(Payload)` is a union; otherwise a plain type expression.
179    fn parse_type_decl_rhs(&mut self) -> Result<TypeExpr, ParseError> {
180        let first = self.parse_type_expr()?;
181        // Detect union: PascalCase ident (or named type w/ optional payload) followed by `|`.
182        if matches!(self.peek_skip_newlines(), Some(TokenKind::Bar)) {
183            let mut variants = Vec::new();
184            variants.push(type_to_variant(first)?);
185            while self.eat(&TokenKind::Bar) {
186                let next = self.parse_type_expr()?;
187                variants.push(type_to_variant(next)?);
188            }
189            Ok(TypeExpr::Union(variants))
190        } else {
191            Ok(first)
192        }
193    }
194
195    fn peek_skip_newlines(&mut self) -> Option<TokenKind> {
196        let saved = self.idx;
197        self.skip_newlines();
198        let out = self.peek().cloned();
199        self.idx = saved;
200        out
201    }
202
203    fn parse_type_expr(&mut self) -> Result<TypeExpr, ParseError> {
204        self.skip_newlines();
205        match self.peek() {
206            Some(TokenKind::LBrace) => self.parse_record_type(),
207            Some(TokenKind::LParen) => self.parse_paren_type_or_function(),
208            Some(TokenKind::Ident(_)) => {
209                let mut name = self.expect_ident("in type expr")?;
210                // Module-qualified type: `m.Type` or `m.n.Type`. We accept
211                // dotted names here and let the loader rewrite them to the
212                // file-local mangled form. After the loader pass, all type
213                // names referenced by the type checker are single segments.
214                while matches!(self.peek(), Some(TokenKind::Dot)) {
215                    self.bump();
216                    let next = self.expect_ident("after `.` in qualified type")?;
217                    name.push('.');
218                    name.push_str(&next);
219                }
220                let args = if matches!(self.peek(), Some(TokenKind::LBracket)) {
221                    self.bump();
222                    let mut args = Vec::new();
223                    args.push(self.parse_type_expr()?);
224                    while self.eat(&TokenKind::Comma) {
225                        if matches!(self.peek_skip_newlines(), Some(TokenKind::RBracket)) { break; }
226                        args.push(self.parse_type_expr()?);
227                    }
228                    self.expect(&TokenKind::RBracket, "after type args")?;
229                    args
230                } else if matches!(self.peek(), Some(TokenKind::LParen)) {
231                    // Constructor type with payload: `Name(T)` or `Name(T1, T2)`.
232                    self.bump();
233                    let mut args = Vec::new();
234                    if !matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) {
235                        args.push(self.parse_type_expr()?);
236                        while self.eat(&TokenKind::Comma) {
237                            if matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) { break; }
238                            args.push(self.parse_type_expr()?);
239                        }
240                    }
241                    self.expect(&TokenKind::RParen, "after constructor payload")?;
242                    args
243                } else {
244                    Vec::new()
245                };
246                Ok(TypeExpr::Named { name, args })
247            }
248            other => Err(self.error(format!("expected type expression, got {other:?}"))),
249        }
250    }
251
252    fn parse_record_type(&mut self) -> Result<TypeExpr, ParseError> {
253        self.expect(&TokenKind::LBrace, "in record type")?;
254        let mut fields = Vec::new();
255        if !matches!(self.peek_skip_newlines(), Some(TokenKind::RBrace)) {
256            loop {
257                self.skip_newlines();
258                let name = self.expect_ident("in record field")?;
259                self.expect(&TokenKind::ColonColon, "after record field name")?;
260                let ty = self.parse_type_expr()?;
261                fields.push(TypeField { name, ty });
262                self.skip_newlines();
263                if !self.eat(&TokenKind::Comma) { break; }
264                if matches!(self.peek_skip_newlines(), Some(TokenKind::RBrace)) { break; }
265            }
266        }
267        self.expect(&TokenKind::RBrace, "in record type")?;
268        Ok(TypeExpr::Record(fields))
269    }
270
271    fn parse_paren_type_or_function(&mut self) -> Result<TypeExpr, ParseError> {
272        self.expect(&TokenKind::LParen, "in type")?;
273        let mut args = Vec::new();
274        if !matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) {
275            args.push(self.parse_type_expr()?);
276            while self.eat(&TokenKind::Comma) {
277                if matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) { break; }
278                args.push(self.parse_type_expr()?);
279            }
280        }
281        self.expect(&TokenKind::RParen, "in type")?;
282        // Function type if followed by `->`.
283        if matches!(self.peek_skip_newlines(), Some(TokenKind::Arrow)) {
284            self.skip_newlines();
285            self.bump();
286            let effects = self.parse_effects()?;
287            let ret = self.parse_type_expr()?;
288            Ok(TypeExpr::Function {
289                params: args,
290                effects,
291                ret: Box::new(ret),
292            })
293        } else if args.len() == 1 {
294            // Parenthesized type expression.
295            Ok(args.into_iter().next().unwrap())
296        } else {
297            Ok(TypeExpr::Tuple(args))
298        }
299    }
300
301    fn parse_fn_decl(&mut self) -> Result<FnDecl, ParseError> {
302        self.expect(&TokenKind::Fn, "in fn decl")?;
303        let name = self.expect_ident("for function name")?;
304        let type_params = if self.eat(&TokenKind::LBracket) {
305            let ps = self.parse_ident_list()?;
306            self.expect(&TokenKind::RBracket, "after type params")?;
307            ps
308        } else {
309            Vec::new()
310        };
311        self.expect(&TokenKind::LParen, "before params")?;
312        let mut params = Vec::new();
313        if !matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) {
314            params.push(self.parse_param()?);
315            while self.eat(&TokenKind::Comma) {
316                if matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) { break; }
317                params.push(self.parse_param()?);
318            }
319        }
320        self.expect(&TokenKind::RParen, "after params")?;
321        self.expect(&TokenKind::Arrow, "before return type")?;
322        let effects = self.parse_effects()?;
323        let return_type = self.parse_type_expr()?;
324        let body = self.parse_block()?;
325        Ok(FnDecl { name, type_params, params, effects, return_type, body })
326    }
327
328    fn parse_param(&mut self) -> Result<Param, ParseError> {
329        let name = self.expect_ident("for parameter name")?;
330        self.expect(&TokenKind::ColonColon, "after parameter name")?;
331        let ty = self.parse_type_expr()?;
332        Ok(Param { name, ty })
333    }
334
335    fn parse_effects(&mut self) -> Result<Vec<Effect>, ParseError> {
336        if !self.eat(&TokenKind::LBracket) {
337            return Ok(Vec::new());
338        }
339        let mut out = Vec::new();
340        if !matches!(self.peek_skip_newlines(), Some(TokenKind::RBracket)) {
341            out.push(self.parse_effect()?);
342            while self.eat(&TokenKind::Comma) {
343                if matches!(self.peek_skip_newlines(), Some(TokenKind::RBracket)) { break; }
344                out.push(self.parse_effect()?);
345            }
346        }
347        self.expect(&TokenKind::RBracket, "after effects")?;
348        Ok(out)
349    }
350
351    fn parse_effect(&mut self) -> Result<Effect, ParseError> {
352        let name = self.expect_ident("for effect name")?;
353        let arg = if self.eat(&TokenKind::LParen) {
354            let arg = match self.bump().map(|t| t.kind) {
355                Some(TokenKind::Str(s)) => EffectArg::Str(s),
356                Some(TokenKind::Int(n)) => EffectArg::Int(n),
357                Some(TokenKind::Ident(s)) => EffectArg::Ident(s),
358                other => return Err(self.error(format!("invalid effect arg: {other:?}"))),
359            };
360            self.expect(&TokenKind::RParen, "after effect arg")?;
361            Some(arg)
362        } else {
363            None
364        };
365        Ok(Effect { name, arg })
366    }
367
368    // --- blocks and statements ---
369
370    fn parse_block(&mut self) -> Result<Block, ParseError> {
371        self.expect(&TokenKind::LBrace, "before block")?;
372        let mut statements = Vec::new();
373        let result;
374        loop {
375            self.skip_newlines();
376            if matches!(self.peek(), Some(TokenKind::RBrace)) {
377                // Empty block: synthesize Unit literal.
378                result = Box::new(Expr::Lit(Literal::Unit));
379                break;
380            }
381            // Try parsing a let; otherwise an expression.
382            if matches!(self.peek(), Some(TokenKind::Let)) {
383                let stmt = self.parse_let_statement()?;
384                statements.push(stmt);
385                self.skip_newlines();
386                continue;
387            }
388            let expr = self.parse_expr()?;
389            self.skip_newlines();
390            // If the next token is `}`, this expression is the block's result.
391            if matches!(self.peek(), Some(TokenKind::RBrace)) {
392                result = Box::new(expr);
393                break;
394            }
395            statements.push(Statement::Expr(expr));
396        }
397        self.expect(&TokenKind::RBrace, "to close block")?;
398        Ok(Block { statements, result })
399    }
400
401    fn parse_let_statement(&mut self) -> Result<Statement, ParseError> {
402        self.expect(&TokenKind::Let, "in let")?;
403        let name = self.expect_ident("after `let`")?;
404        let ty = if self.eat(&TokenKind::ColonColon) {
405            Some(self.parse_type_expr()?)
406        } else {
407            None
408        };
409        self.expect(&TokenKind::ColonEq, "in let")?;
410        let value = self.parse_expr()?;
411        Ok(Statement::Let { name, ty, value })
412    }
413
414    // --- expressions ---
415
416    fn parse_expr(&mut self) -> Result<Expr, ParseError> {
417        // Recursion gate: every nested expression — match arms,
418        // tuple/list/record/block elements, function args, etc. —
419        // enters here, so this is the right place to bound depth.
420        // Decrement happens whether the inner call succeeds or fails.
421        if self.depth >= MAX_DEPTH {
422            return Err(ParseError {
423                pos: self.current_pos(),
424                msg: format!(
425                    "expression nests too deeply (max {MAX_DEPTH}); \
426                     malformed or hand-crafted input?"),
427            });
428        }
429        self.depth += 1;
430        let r = self.parse_expr_inner();
431        self.depth -= 1;
432        r
433    }
434
435    fn parse_expr_inner(&mut self) -> Result<Expr, ParseError> {
436        // Pipes are left-associative and bind less tightly than binary ops.
437        let mut left = self.parse_binary_expr(0)?;
438        while matches!(self.peek_skip_newlines(), Some(TokenKind::Pipe)) {
439            self.skip_newlines();
440            self.bump();
441            let right = self.parse_binary_expr(0)?;
442            left = Expr::Pipe { left: Box::new(left), right: Box::new(right) };
443        }
444        Ok(left)
445    }
446
447    fn parse_binary_expr(&mut self, min_prec: u8) -> Result<Expr, ParseError> {
448        let mut lhs = self.parse_unary()?;
449        loop {
450            let op = match self.peek_binop() {
451                Some(op) if op.precedence() >= min_prec => op,
452                _ => break,
453            };
454            self.skip_newlines();
455            self.bump();
456            let rhs = self.parse_binary_expr(op.precedence() + 1)?;
457            lhs = Expr::BinOp { op, lhs: Box::new(lhs), rhs: Box::new(rhs) };
458        }
459        Ok(lhs)
460    }
461
462    fn peek_binop(&mut self) -> Option<BinOp> {
463        match self.peek_skip_newlines()? {
464            TokenKind::Plus => Some(BinOp::Add),
465            TokenKind::Minus => Some(BinOp::Sub),
466            TokenKind::Star => Some(BinOp::Mul),
467            TokenKind::Slash => Some(BinOp::Div),
468            TokenKind::Percent => Some(BinOp::Mod),
469            TokenKind::EqEq => Some(BinOp::Eq),
470            TokenKind::BangEq => Some(BinOp::Neq),
471            TokenKind::Lt => Some(BinOp::Lt),
472            TokenKind::LtEq => Some(BinOp::Lte),
473            TokenKind::Gt => Some(BinOp::Gt),
474            TokenKind::GtEq => Some(BinOp::Gte),
475            TokenKind::And => Some(BinOp::And),
476            TokenKind::Or => Some(BinOp::Or),
477            _ => None,
478        }
479    }
480
481    fn parse_unary(&mut self) -> Result<Expr, ParseError> {
482        self.skip_newlines();
483        match self.peek() {
484            Some(TokenKind::Not) => {
485                self.bump();
486                let inner = self.parse_unary()?;
487                Ok(Expr::UnaryOp { op: UnaryOp::Not, expr: Box::new(inner) })
488            }
489            Some(TokenKind::Minus) => {
490                self.bump();
491                let inner = self.parse_unary()?;
492                Ok(Expr::UnaryOp { op: UnaryOp::Neg, expr: Box::new(inner) })
493            }
494            _ => self.parse_postfix(),
495        }
496    }
497
498    fn parse_postfix(&mut self) -> Result<Expr, ParseError> {
499        let mut expr = self.parse_primary()?;
500        loop {
501            // Postfix operations don't cross newlines (they bind tightly).
502            match self.peek() {
503                Some(TokenKind::Dot) => {
504                    self.bump();
505                    let field = self.expect_ident("after `.`")?;
506                    expr = Expr::Field { value: Box::new(expr), field };
507                }
508                Some(TokenKind::LParen) => {
509                    self.bump();
510                    let mut args = Vec::new();
511                    if !matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) {
512                        args.push(self.parse_expr()?);
513                        while self.eat(&TokenKind::Comma) {
514                            if matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) { break; }
515                            args.push(self.parse_expr()?);
516                        }
517                    }
518                    self.expect(&TokenKind::RParen, "in call")?;
519                    expr = Expr::Call { callee: Box::new(expr), args };
520                }
521                Some(TokenKind::Question) => {
522                    self.bump();
523                    expr = Expr::Try(Box::new(expr));
524                }
525                _ => break,
526            }
527        }
528        Ok(expr)
529    }
530
531    fn parse_primary(&mut self) -> Result<Expr, ParseError> {
532        self.skip_newlines();
533        match self.peek() {
534            Some(TokenKind::Int(_)) => match self.bump().unwrap().kind {
535                TokenKind::Int(n) => Ok(Expr::Lit(Literal::Int(n))),
536                _ => unreachable!(),
537            },
538            Some(TokenKind::Float(_)) => match self.bump().unwrap().kind {
539                TokenKind::Float(n) => Ok(Expr::Lit(Literal::Float(n))),
540                _ => unreachable!(),
541            },
542            Some(TokenKind::Str(_)) => match self.bump().unwrap().kind {
543                TokenKind::Str(s) => Ok(Expr::Lit(Literal::Str(s))),
544                _ => unreachable!(),
545            },
546            Some(TokenKind::Bytes(_)) => match self.bump().unwrap().kind {
547                TokenKind::Bytes(b) => Ok(Expr::Lit(Literal::Bytes(b))),
548                _ => unreachable!(),
549            },
550            Some(TokenKind::True) => { self.bump(); Ok(Expr::Lit(Literal::Bool(true))) }
551            Some(TokenKind::False) => { self.bump(); Ok(Expr::Lit(Literal::Bool(false))) }
552            Some(TokenKind::If) => self.parse_if(),
553            Some(TokenKind::Match) => self.parse_match(),
554            Some(TokenKind::Fn) => self.parse_lambda(),
555            Some(TokenKind::LBrace) => self.parse_brace_expr(),
556            Some(TokenKind::LBracket) => self.parse_list_literal(),
557            Some(TokenKind::LParen) => self.parse_paren_or_tuple(),
558            Some(TokenKind::Ident(_)) => self.parse_ident_or_record(),
559            other => Err(self.error(format!("expected expression, got {other:?}"))),
560        }
561    }
562
563    /// Disambiguate `{` between record literal and block.
564    /// Lookahead: `{ Ident :` is a record literal; `{ }` is also a record
565    /// (empty block has no use). Anything else is a block.
566    fn parse_brace_expr(&mut self) -> Result<Expr, ParseError> {
567        // Save position; peek 2-3 tokens past `{` (skipping newlines).
568        let saved = self.idx;
569        self.bump(); // `{`
570        // Skip newlines.
571        while matches!(self.peek(), Some(TokenKind::Newline) | Some(TokenKind::Semi)) {
572            self.idx += 1;
573        }
574        let is_record = matches!(self.peek(), Some(TokenKind::RBrace))
575            || (matches!(self.peek(), Some(TokenKind::Ident(_)))
576                && matches!(self.tokens.get(self.idx + 1).map(|t| &t.kind), Some(TokenKind::Colon) | Some(TokenKind::Comma) | Some(TokenKind::RBrace)));
577        self.idx = saved;
578        if is_record {
579            self.parse_record_literal()
580        } else {
581            Ok(Expr::Block(self.parse_block()?))
582        }
583    }
584
585    fn parse_record_literal(&mut self) -> Result<Expr, ParseError> {
586        self.expect(&TokenKind::LBrace, "in record literal")?;
587        let mut fields = Vec::new();
588        if !matches!(self.peek_skip_newlines(), Some(TokenKind::RBrace)) {
589            loop {
590                self.skip_newlines();
591                let name = self.expect_ident("in record literal")?;
592                let value = if self.eat(&TokenKind::Colon) {
593                    self.parse_expr()?
594                } else {
595                    // shorthand: `{ name }` => `{ name: name }`
596                    Expr::Var(name.clone())
597                };
598                fields.push(RecordLitField { name, value });
599                self.skip_newlines();
600                if !self.eat(&TokenKind::Comma) { break; }
601                if matches!(self.peek_skip_newlines(), Some(TokenKind::RBrace)) { break; }
602            }
603        }
604        self.expect(&TokenKind::RBrace, "after record literal")?;
605        Ok(Expr::RecordLit(fields))
606    }
607
608    fn parse_if(&mut self) -> Result<Expr, ParseError> {
609        self.expect(&TokenKind::If, "in if")?;
610        let cond = self.parse_expr()?;
611        let then_block = self.parse_block()?;
612        self.expect(&TokenKind::Else, "expected `else`")?;
613        let else_block = self.parse_block()?;
614        Ok(Expr::If { cond: Box::new(cond), then_block, else_block })
615    }
616
617    fn parse_match(&mut self) -> Result<Expr, ParseError> {
618        self.expect(&TokenKind::Match, "in match")?;
619        let scrutinee = self.parse_expr()?;
620        self.expect(&TokenKind::LBrace, "before match arms")?;
621        let mut arms = Vec::new();
622        loop {
623            self.skip_newlines();
624            if matches!(self.peek(), Some(TokenKind::RBrace)) { break; }
625            let pattern = self.parse_pattern()?;
626            self.expect(&TokenKind::FatArrow, "in match arm")?;
627            let body = self.parse_expr()?;
628            arms.push(Arm { pattern, body });
629            self.skip_newlines();
630            if !self.eat(&TokenKind::Comma) { break; }
631        }
632        self.expect(&TokenKind::RBrace, "after match arms")?;
633        Ok(Expr::Match { scrutinee: Box::new(scrutinee), arms })
634    }
635
636    fn parse_lambda(&mut self) -> Result<Expr, ParseError> {
637        self.expect(&TokenKind::Fn, "in lambda")?;
638        self.expect(&TokenKind::LParen, "before lambda params")?;
639        let mut params = Vec::new();
640        if !matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) {
641            params.push(self.parse_param()?);
642            while self.eat(&TokenKind::Comma) {
643                if matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) { break; }
644                params.push(self.parse_param()?);
645            }
646        }
647        self.expect(&TokenKind::RParen, "after lambda params")?;
648        self.expect(&TokenKind::Arrow, "before lambda return type")?;
649        let effects = self.parse_effects()?;
650        let return_type = self.parse_type_expr()?;
651        let body = self.parse_block()?;
652        Ok(Expr::Lambda(Box::new(Lambda { params, effects, return_type, body })))
653    }
654
655    fn parse_list_literal(&mut self) -> Result<Expr, ParseError> {
656        self.expect(&TokenKind::LBracket, "before list literal")?;
657        let mut items = Vec::new();
658        if !matches!(self.peek_skip_newlines(), Some(TokenKind::RBracket)) {
659            items.push(self.parse_expr()?);
660            while self.eat(&TokenKind::Comma) {
661                if matches!(self.peek_skip_newlines(), Some(TokenKind::RBracket)) { break; }
662                items.push(self.parse_expr()?);
663            }
664        }
665        self.expect(&TokenKind::RBracket, "after list literal")?;
666        Ok(Expr::ListLit(items))
667    }
668
669    fn parse_paren_or_tuple(&mut self) -> Result<Expr, ParseError> {
670        self.expect(&TokenKind::LParen, "")?;
671        if matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) {
672            self.bump();
673            return Ok(Expr::Lit(Literal::Unit));
674        }
675        let first = self.parse_expr()?;
676        if self.eat(&TokenKind::Comma) {
677            let mut items = vec![first];
678            if !matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) {
679                items.push(self.parse_expr()?);
680                while self.eat(&TokenKind::Comma) {
681                    if matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) { break; }
682                    items.push(self.parse_expr()?);
683                }
684            }
685            self.expect(&TokenKind::RParen, "after tuple")?;
686            Ok(Expr::TupleLit(items))
687        } else {
688            self.expect(&TokenKind::RParen, "after parenthesized expression")?;
689            Ok(first)
690        }
691    }
692
693    fn parse_ident_or_record(&mut self) -> Result<Expr, ParseError> {
694        // Ident is parsed as a Var; later postfix (`(`, `.`, `?`) attach.
695        let name = self.expect_ident("")?;
696        Ok(Expr::Var(name))
697    }
698
699    // --- patterns ---
700
701    fn parse_pattern(&mut self) -> Result<Pattern, ParseError> {
702        self.skip_newlines();
703        match self.peek() {
704            Some(TokenKind::Underscore) => { self.bump(); Ok(Pattern::Wild) }
705            Some(TokenKind::Int(_)) => match self.bump().unwrap().kind {
706                TokenKind::Int(n) => Ok(Pattern::Lit(Literal::Int(n))),
707                _ => unreachable!(),
708            },
709            Some(TokenKind::Float(_)) => match self.bump().unwrap().kind {
710                TokenKind::Float(n) => Ok(Pattern::Lit(Literal::Float(n))),
711                _ => unreachable!(),
712            },
713            Some(TokenKind::Str(_)) => match self.bump().unwrap().kind {
714                TokenKind::Str(s) => Ok(Pattern::Lit(Literal::Str(s))),
715                _ => unreachable!(),
716            },
717            Some(TokenKind::True) => { self.bump(); Ok(Pattern::Lit(Literal::Bool(true))) }
718            Some(TokenKind::False) => { self.bump(); Ok(Pattern::Lit(Literal::Bool(false))) }
719            Some(TokenKind::LBrace) => self.parse_record_pattern(),
720            Some(TokenKind::LParen) => self.parse_tuple_pattern(),
721            Some(TokenKind::Ident(_)) => {
722                let name = self.expect_ident("")?;
723                if matches!(self.peek(), Some(TokenKind::LParen)) {
724                    self.bump();
725                    let mut args = Vec::new();
726                    if !matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) {
727                        args.push(self.parse_pattern()?);
728                        while self.eat(&TokenKind::Comma) {
729                            if matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) { break; }
730                            args.push(self.parse_pattern()?);
731                        }
732                    }
733                    self.expect(&TokenKind::RParen, "after constructor pattern")?;
734                    Ok(Pattern::Constructor { name, args })
735                } else {
736                    Ok(Pattern::Var(name))
737                }
738            }
739            other => Err(self.error(format!("expected pattern, got {other:?}"))),
740        }
741    }
742
743    fn parse_record_pattern(&mut self) -> Result<Pattern, ParseError> {
744        self.expect(&TokenKind::LBrace, "")?;
745        let mut fields = Vec::new();
746        let rest = false;
747        if !matches!(self.peek_skip_newlines(), Some(TokenKind::RBrace)) {
748            loop {
749                self.skip_newlines();
750                let name = self.expect_ident("in record pattern")?;
751                let pattern = if self.eat(&TokenKind::Colon) {
752                    Some(self.parse_pattern()?)
753                } else {
754                    None
755                };
756                fields.push(RecordPatField { name, pattern });
757                self.skip_newlines();
758                if !self.eat(&TokenKind::Comma) { break; }
759                if matches!(self.peek_skip_newlines(), Some(TokenKind::RBrace)) { break; }
760            }
761        }
762        self.expect(&TokenKind::RBrace, "after record pattern")?;
763        Ok(Pattern::Record { fields, rest })
764    }
765
766    fn parse_tuple_pattern(&mut self) -> Result<Pattern, ParseError> {
767        self.expect(&TokenKind::LParen, "")?;
768        let mut items = Vec::new();
769        if !matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) {
770            items.push(self.parse_pattern()?);
771            while self.eat(&TokenKind::Comma) {
772                if matches!(self.peek_skip_newlines(), Some(TokenKind::RParen)) { break; }
773                items.push(self.parse_pattern()?);
774            }
775        }
776        self.expect(&TokenKind::RParen, "after tuple pattern")?;
777        if items.len() == 1 {
778            Ok(items.into_iter().next().unwrap())
779        } else {
780            Ok(Pattern::Tuple(items))
781        }
782    }
783}
784
785/// In a union RHS, every leaf must be a `Named` type expression — that is, a
786/// PascalCase ident with optional payload via `Variant(payload_type)`.
787fn type_to_variant(t: TypeExpr) -> Result<UnionVariant, ParseError> {
788    match t {
789        TypeExpr::Named { name, args } => {
790            let payload = match args.len() {
791                0 => None,
792                1 => Some(args.into_iter().next().unwrap()),
793                _ => Some(TypeExpr::Tuple(args)),
794            };
795            Ok(UnionVariant { name, payload })
796        }
797        // `Foo({ field :: T })` parses as Named with one arg = Record. handled above.
798        _ => Err(ParseError {
799            pos: 0,
800            msg: "union variant must be a constructor name".into(),
801        }),
802    }
803}