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