Skip to main content

cjc_parser/
lib.rs

1//! Pratt parser for CJC source code.
2//!
3//! Convenience entry point: `let (program, diags) = cjc_parser::parse_source(src);`
4//!
5//! Implements a Pratt (top-down operator precedence) parser that produces
6//! an AST from the token stream generated by `cjc-lexer`.
7
8use cjc_ast::{
9    self, BinOp, Block, CallArg, ClassDecl, ConstDecl, Decl, DeclKind, ElseBranch, EnumDecl,
10    Expr, ExprKind, FieldDecl, FieldInit, FnDecl, FnSig, ForIter, ForStmt, Ident, IfStmt,
11    ImplDecl, ImportDecl, LetStmt, MatchArm, Param, Pattern, PatternField, PatternKind, Program,
12    RecordDecl, ShapeDim, Stmt, StmtKind, StructDecl, TraitDecl, TypeArg, TypeExpr, TypeExprKind,
13    TypeParam, UnaryOp, VariantDecl, Visibility, WhileStmt,
14};
15use cjc_diag::{Diagnostic, DiagnosticBag};
16use cjc_lexer::{Token, TokenKind};
17
18// ── Span conversion ────────────────────────────────────────────────────
19
20/// Convert a `cjc_diag::Span` to a `cjc_ast::Span`.
21fn to_ast_span(s: cjc_diag::Span) -> cjc_ast::Span {
22    cjc_ast::Span::new(s.start, s.end)
23}
24
25/// Convert a `cjc_ast::Span` to a `cjc_diag::Span`.
26fn to_diag_span(s: cjc_ast::Span) -> cjc_diag::Span {
27    cjc_diag::Span::new(s.start, s.end)
28}
29
30/// Merge two `cjc_ast::Span` values.
31fn merge_spans(a: cjc_ast::Span, b: cjc_ast::Span) -> cjc_ast::Span {
32    cjc_ast::Span::new(a.start.min(b.start), a.end.max(b.end))
33}
34
35// ── Precedence levels for Pratt parsing ────────────────────────────────
36
37/// Binding power values used in the Pratt expression parser.
38/// Each level returns `(left_bp, right_bp)`.  For left-associative
39/// operators `right_bp = left_bp + 1`; for right-associative operators
40/// `right_bp = left_bp`.
41mod prec {
42    /// Assignment `=` `+=` `-=` etc. — right-associative, lowest precedence.
43    pub const ASSIGN: u8 = 2;
44    /// Pipe `|>` — left-associative.
45    pub const PIPE: u8 = 4;
46    /// Logical or `||`.
47    pub const OR: u8 = 6;
48    /// Logical and `&&`.
49    pub const AND: u8 = 8;
50    /// Bitwise or `|`.
51    pub const BIT_OR: u8 = 9;
52    /// Bitwise xor `^`.
53    pub const BIT_XOR: u8 = 10;
54    /// Bitwise and `&`.
55    pub const BIT_AND: u8 = 11;
56    /// Equality `==` `!=`.
57    pub const EQ: u8 = 12;
58    /// Comparison `<` `>` `<=` `>=`.
59    pub const CMP: u8 = 14;
60    /// Shift `<<` `>>`.
61    pub const SHIFT: u8 = 16;
62    /// Addition `+` `-`.
63    pub const ADD: u8 = 18;
64    /// Multiplication `*` `/` `%`.
65    pub const MUL: u8 = 20;
66    /// Exponentiation `**` — right-associative.
67    pub const POW: u8 = 22;
68    /// Unary prefix `-` `!` `~`.
69    pub const UNARY: u8 = 24;
70    /// Postfix `.` `[` `(`.
71    pub const POSTFIX: u8 = 26;
72}
73
74// ── Parser ─────────────────────────────────────────────────────────────
75
76pub struct Parser {
77    tokens: Vec<Token>,
78    pos: usize,
79    pub diagnostics: DiagnosticBag,
80    /// When `false`, `Ident {` is NOT parsed as a struct literal.
81    /// Used to resolve the ambiguity between struct literals and block
82    /// bodies in `if`/`while` conditions.
83    allow_struct_lit: bool,
84    /// When `false`, `|` is NOT consumed as a union-type operator in type
85    /// expressions.  Used inside lambda parameter lists where `|` delimits
86    /// the parameter list, not a type union.
87    allow_pipe_in_type: bool,
88    /// Nesting depth of while/for loops. Used to validate break/continue.
89    loop_depth: usize,
90}
91
92/// Result type used internally — `Err(())` means a diagnostic was already
93/// emitted and the caller should attempt recovery.
94type PResult<T> = Result<T, ()>;
95
96impl Parser {
97    // ── Construction ───────────────────────────────────────────────
98
99    pub fn new(tokens: Vec<Token>) -> Self {
100        Self {
101            tokens,
102            pos: 0,
103            diagnostics: DiagnosticBag::new(),
104            allow_struct_lit: true,
105            allow_pipe_in_type: true,
106            loop_depth: 0,
107        }
108    }
109
110    // ── Token stream helpers ───────────────────────────────────────
111
112    fn peek(&self) -> &Token {
113        self.tokens.get(self.pos).unwrap_or_else(|| self.tokens.last().unwrap())
114    }
115
116    fn peek_kind(&self) -> TokenKind {
117        self.peek().kind
118    }
119
120    fn at(&self, kind: TokenKind) -> bool {
121        self.peek_kind() == kind
122    }
123
124    fn at_eof(&self) -> bool {
125        self.at(TokenKind::Eof)
126    }
127
128    fn advance(&mut self) -> &Token {
129        let tok = &self.tokens[self.pos];
130        if self.pos + 1 < self.tokens.len() {
131            self.pos += 1;
132        }
133        tok
134    }
135
136    fn expect(&mut self, kind: TokenKind) -> PResult<Token> {
137        if self.at(kind) {
138            Ok(self.advance().clone())
139        } else {
140            let tok = self.peek().clone();
141            self.error_expected(kind.describe(), &tok);
142            Err(())
143        }
144    }
145
146    fn eat(&mut self, kind: TokenKind) -> Option<Token> {
147        if self.at(kind) {
148            Some(self.advance().clone())
149        } else {
150            None
151        }
152    }
153
154    fn current_span(&self) -> cjc_diag::Span {
155        self.peek().span
156    }
157
158    fn previous_span(&self) -> cjc_diag::Span {
159        if self.pos > 0 {
160            self.tokens[self.pos - 1].span
161        } else {
162            self.current_span()
163        }
164    }
165
166    // ── Diagnostics ────────────────────────────────────────────────
167
168    fn error(&mut self, message: impl Into<String>, span: cjc_diag::Span) {
169        self.diagnostics
170            .emit(Diagnostic::error("E1000", message, span));
171    }
172
173    fn error_expected(&mut self, expected: &str, found: &Token) {
174        let msg = format!(
175            "expected {}, found {}",
176            expected,
177            found.kind.describe()
178        );
179        self.diagnostics.emit(
180            Diagnostic::error("E1001", msg, found.span)
181                .with_label(found.span, format!("expected {} here", expected)),
182        );
183    }
184
185    fn error_with_hint(
186        &mut self,
187        message: impl Into<String>,
188        span: cjc_diag::Span,
189        hint: impl Into<String>,
190    ) {
191        self.diagnostics.emit(
192            Diagnostic::error("E1002", message, span).with_hint(hint),
193        );
194    }
195
196    // ── Error recovery ─────────────────────────────────────────────
197
198    /// Skip tokens until we reach a synchronization point: a semicolon,
199    /// a closing brace, or a declaration-starting keyword.
200    fn synchronize(&mut self) {
201        loop {
202            match self.peek_kind() {
203                TokenKind::Eof => return,
204                TokenKind::Semicolon => {
205                    self.advance();
206                    return;
207                }
208                TokenKind::RBrace => return,
209                TokenKind::Struct
210                | TokenKind::Class
211                | TokenKind::Record
212                | TokenKind::Fn
213                | TokenKind::Trait
214                | TokenKind::Impl
215                | TokenKind::Let
216                | TokenKind::Import
217                | TokenKind::Mod
218                | TokenKind::NoGc
219                | TokenKind::If
220                | TokenKind::While
221                | TokenKind::For
222                | TokenKind::Return => return,
223                _ => {
224                    self.advance();
225                }
226            }
227        }
228    }
229
230    // ── Top-level entry point ──────────────────────────────────────
231
232    pub fn parse_program(mut self) -> (Program, DiagnosticBag) {
233        let mut declarations = Vec::new();
234        while !self.at_eof() {
235            let before = self.pos;
236            match self.parse_decl() {
237                Ok(decl) => declarations.push(decl),
238                Err(()) => {
239                    self.synchronize();
240                    // Guarantee forward progress: if neither parse_decl nor
241                    // synchronize advanced the position (e.g. a stray `}`
242                    // that synchronize stops at without consuming), skip
243                    // the offending token so we never loop forever.
244                    if self.pos == before && !self.at_eof() {
245                        self.advance();
246                    }
247                }
248            }
249        }
250        (Program { declarations }, self.diagnostics)
251    }
252
253    // ── Declarations ───────────────────────────────────────────────
254
255    fn parse_decl(&mut self) -> PResult<Decl> {
256        // Handle optional `pub` visibility prefix
257        if self.peek_kind() == TokenKind::Pub {
258            return self.parse_pub_decl();
259        }
260        self.parse_decl_with_vis(Visibility::Private)
261    }
262
263    /// Parse a declaration preceded by `pub`.
264    fn parse_pub_decl(&mut self) -> PResult<Decl> {
265        self.advance(); // consume `pub`
266        self.parse_decl_with_vis(Visibility::Public)
267    }
268
269    fn parse_decl_with_vis(&mut self, vis: Visibility) -> PResult<Decl> {
270        match self.peek_kind() {
271            TokenKind::Struct => self.parse_struct_decl_with_vis(vis),
272            TokenKind::Class => self.parse_class_decl_with_vis(vis),
273            TokenKind::Record => self.parse_record_decl_with_vis(vis),
274            TokenKind::Enum => self.parse_enum_decl(),
275            TokenKind::At => {
276                let decorators = self.parse_decorator_list()?;
277                if self.peek_kind() == TokenKind::NoGc && self.peek_ahead(1) == TokenKind::Fn {
278                    self.advance(); // nogc
279                    self.parse_fn_decl_with_vis(true, decorators, vis)
280                } else {
281                    self.parse_fn_decl_with_vis(false, decorators, vis)
282                }
283            }
284            TokenKind::Fn => self.parse_fn_decl_with_vis(false, vec![], vis),
285            TokenKind::NoGc if self.peek_ahead(1) == TokenKind::Fn => self.parse_nogc_fn_decl_with_vis(vis),
286            TokenKind::Trait => self.parse_trait_decl(),
287            TokenKind::Impl => self.parse_impl_decl(),
288            TokenKind::Import => self.parse_import_decl(),
289            TokenKind::Mod => self.parse_mod_decl(),
290            TokenKind::Let => self.parse_let_decl(),
291            TokenKind::Const => self.parse_const_decl(),
292            // Top-level statements: if, while, nogc block, expression statements
293            TokenKind::If => {
294                let start_span = to_ast_span(self.current_span());
295                let if_stmt = self.parse_if_stmt()?;
296                let end_span = if_stmt.then_block.span;
297                let span = merge_spans(start_span, end_span);
298                Ok(Decl {
299                    kind: DeclKind::Stmt(Stmt {
300                        kind: StmtKind::If(if_stmt),
301                        span,
302                    }),
303                    span,
304                })
305            }
306            TokenKind::While => {
307                let while_stmt = self.parse_while_stmt()?;
308                let span = while_stmt.body.span;
309                Ok(Decl {
310                    kind: DeclKind::Stmt(Stmt {
311                        kind: StmtKind::While(while_stmt),
312                        span,
313                    }),
314                    span,
315                })
316            }
317            TokenKind::For => {
318                let start_span = to_ast_span(self.current_span());
319                let for_stmt = self.parse_for_stmt()?;
320                let span = merge_spans(start_span, for_stmt.body.span);
321                Ok(Decl {
322                    kind: DeclKind::Stmt(Stmt {
323                        kind: StmtKind::For(for_stmt),
324                        span,
325                    }),
326                    span,
327                })
328            }
329            TokenKind::NoGc if self.peek_ahead(1) == TokenKind::LBrace => {
330                let nogc_start = self.advance().span;
331                let block = self.parse_block()?;
332                let span = merge_spans(to_ast_span(nogc_start), block.span);
333                Ok(Decl {
334                    kind: DeclKind::Stmt(Stmt {
335                        kind: StmtKind::NoGcBlock(block),
336                        span,
337                    }),
338                    span,
339                })
340            }
341            _ => {
342                // Try parsing as a top-level expression statement (e.g., `print("hello");`)
343                let expr = self.parse_expr()?;
344                let span = expr.span;
345                self.expect(TokenKind::Semicolon)?;
346                Ok(Decl {
347                    kind: DeclKind::Stmt(Stmt {
348                        kind: StmtKind::Expr(expr),
349                        span,
350                    }),
351                    span,
352                })
353            }
354        }
355    }
356
357    fn peek_ahead(&self, offset: usize) -> TokenKind {
358        self.tokens
359            .get(self.pos + offset)
360            .map(|t| t.kind)
361            .unwrap_or(TokenKind::Eof)
362    }
363
364    // ── struct ─────────────────────────────────────────────────────
365
366    fn parse_struct_decl_with_vis(&mut self, vis: Visibility) -> PResult<Decl> {
367        let start = self.expect(TokenKind::Struct)?.span;
368        let name = self.parse_ident()?;
369        let type_params = self.parse_optional_type_params()?;
370        self.expect(TokenKind::LBrace)?;
371        let fields = self.parse_field_list()?;
372        let end = self.expect(TokenKind::RBrace)?.span;
373        Ok(Decl {
374            kind: DeclKind::Struct(StructDecl {
375                name,
376                type_params,
377                fields,
378                vis,
379            }),
380            span: to_ast_span(start.merge(end)),
381        })
382    }
383
384    // ── class ──────────────────────────────────────────────────────
385
386    fn parse_class_decl_with_vis(&mut self, vis: Visibility) -> PResult<Decl> {
387        let start = self.expect(TokenKind::Class)?.span;
388        let name = self.parse_ident()?;
389        let type_params = self.parse_optional_type_params()?;
390        self.expect(TokenKind::LBrace)?;
391        let fields = self.parse_field_list()?;
392        let end = self.expect(TokenKind::RBrace)?.span;
393        Ok(Decl {
394            kind: DeclKind::Class(ClassDecl {
395                name,
396                type_params,
397                fields,
398                vis,
399            }),
400            span: to_ast_span(start.merge(end)),
401        })
402    }
403
404    // ── record ─────────────────────────────────────────────────────
405
406    fn parse_record_decl_with_vis(&mut self, vis: Visibility) -> PResult<Decl> {
407        let start = self.expect(TokenKind::Record)?.span;
408        let name = self.parse_ident()?;
409        let type_params = self.parse_optional_type_params()?;
410        self.expect(TokenKind::LBrace)?;
411        let fields = self.parse_field_list()?;
412        let end = self.expect(TokenKind::RBrace)?.span;
413        Ok(Decl {
414            kind: DeclKind::Record(RecordDecl {
415                name,
416                type_params,
417                fields,
418                vis,
419            }),
420            span: to_ast_span(start.merge(end)),
421        })
422    }
423
424    // ── enum ───────────────────────────────────────────────────────
425
426    fn parse_enum_decl(&mut self) -> PResult<Decl> {
427        let start = self.expect(TokenKind::Enum)?.span;
428        let name = self.parse_ident()?;
429        let type_params = self.parse_optional_type_params()?;
430        self.expect(TokenKind::LBrace)?;
431        let mut variants = Vec::new();
432        while !self.at(TokenKind::RBrace) && !self.at_eof() {
433            let variant = self.parse_variant_decl()?;
434            variants.push(variant);
435            // Allow optional trailing comma
436            self.eat(TokenKind::Comma);
437        }
438        let end = self.expect(TokenKind::RBrace)?.span;
439        Ok(Decl {
440            kind: DeclKind::Enum(EnumDecl {
441                name,
442                type_params,
443                variants,
444            }),
445            span: to_ast_span(start.merge(end)),
446        })
447    }
448
449    fn parse_variant_decl(&mut self) -> PResult<VariantDecl> {
450        let name = self.parse_ident()?;
451        let start_span = name.span;
452        let mut fields = Vec::new();
453        let end_span;
454        if self.eat(TokenKind::LParen).is_some() {
455            // Tuple-like variant: `Variant(T1, T2, ...)`
456            if !self.at(TokenKind::RParen) {
457                loop {
458                    fields.push(self.parse_type_expr()?);
459                    if self.eat(TokenKind::Comma).is_none() {
460                        break;
461                    }
462                    if self.at(TokenKind::RParen) {
463                        break;
464                    }
465                }
466            }
467            let rparen = self.expect(TokenKind::RParen)?;
468            end_span = to_ast_span(rparen.span);
469        } else {
470            // Unit variant: `Variant`
471            end_span = start_span;
472        }
473        Ok(VariantDecl {
474            name,
475            fields,
476            span: merge_spans(start_span, end_span),
477        })
478    }
479
480    // ── fields (shared between struct/class) ───────────────────────
481
482    fn parse_field_list(&mut self) -> PResult<Vec<FieldDecl>> {
483        let mut fields = Vec::new();
484        while !self.at(TokenKind::RBrace) && !self.at_eof() {
485            let field = self.parse_field_decl()?;
486            fields.push(field);
487            // Allow optional trailing comma.
488            self.eat(TokenKind::Comma);
489        }
490        Ok(fields)
491    }
492
493    fn parse_field_decl(&mut self) -> PResult<FieldDecl> {
494        // Check for optional `pub` visibility on field
495        let vis = if self.eat(TokenKind::Pub).is_some() {
496            Visibility::Public
497        } else {
498            Visibility::Private
499        };
500        let name = self.parse_ident()?;
501        self.expect(TokenKind::Colon)?;
502        let ty = self.parse_type_expr()?;
503        let default = if self.eat(TokenKind::Eq).is_some() {
504            Some(self.parse_expr()?)
505        } else {
506            None
507        };
508        let span = merge_spans(name.span, if let Some(ref d) = default { d.span } else { ty.span });
509        Ok(FieldDecl {
510            name,
511            ty,
512            default,
513            vis,
514            span,
515        })
516    }
517
518    // ── fn ─────────────────────────────────────────────────────────
519
520    fn parse_fn_decl_with_vis(&mut self, is_nogc: bool, decorators: Vec<cjc_ast::Decorator>, vis: Visibility) -> PResult<Decl> {
521        let start = self.expect(TokenKind::Fn)?.span;
522        let name = self.parse_ident()?;
523        let type_params = self.parse_optional_type_params()?;
524        self.expect(TokenKind::LParen)?;
525        let params = self.parse_param_list()?;
526        self.expect(TokenKind::RParen)?;
527        let return_type = if self.eat(TokenKind::Arrow).is_some() {
528            Some(self.parse_type_expr()?)
529        } else {
530            None
531        };
532        let effect_annotation = self.parse_effect_annotation()?;
533        let body = self.parse_block()?;
534        let span = merge_spans(to_ast_span(start), body.span);
535        Ok(Decl {
536            kind: DeclKind::Fn(FnDecl {
537                name,
538                type_params,
539                params,
540                return_type,
541                body,
542                is_nogc,
543                effect_annotation,
544                decorators,
545                vis,
546            }),
547            span,
548        })
549    }
550
551    fn parse_nogc_fn_decl_with_vis(&mut self, vis: Visibility) -> PResult<Decl> {
552        self.advance(); // nogc
553        self.parse_fn_decl_with_vis(true, vec![], vis)
554    }
555
556    /// Parse a list of decorators: `@name` or `@name(arg1, arg2)`, one per line.
557    fn parse_decorator_list(&mut self) -> PResult<Vec<cjc_ast::Decorator>> {
558        let mut decorators = Vec::new();
559        while self.at(TokenKind::At) {
560            let at_span = self.expect(TokenKind::At)?.span;
561            let name = self.parse_ident()?;
562            let args = if self.eat(TokenKind::LParen).is_some() {
563                let mut args = Vec::new();
564                while !self.at(TokenKind::RParen) && !self.at(TokenKind::Eof) {
565                    args.push(self.parse_expr()?);
566                    if !self.at(TokenKind::RParen) {
567                        self.expect(TokenKind::Comma)?;
568                    }
569                }
570                self.expect(TokenKind::RParen)?;
571                args
572            } else {
573                vec![]
574            };
575            let span = merge_spans(to_ast_span(at_span), name.span);
576            decorators.push(cjc_ast::Decorator { name, args, span });
577        }
578        Ok(decorators)
579    }
580
581    /// Parse optional effect annotation: `/ pure`, `/ io`, `/ pure + alloc`, etc.
582    ///
583    /// Syntax: `/ effect_name (+ effect_name)*`
584    /// Valid effect names: pure, io, alloc, gc, nondet, mutates, arena_ok, captures
585    fn parse_effect_annotation(&mut self) -> PResult<Option<Vec<String>>> {
586        if self.eat(TokenKind::Slash).is_none() {
587            return Ok(None);
588        }
589        let mut effects = Vec::new();
590        // Parse first effect name (required after `/`)
591        let ident = self.parse_ident()?;
592        effects.push(ident.name);
593        // Parse additional effects separated by `+`
594        while self.eat(TokenKind::Plus).is_some() {
595            let ident = self.parse_ident()?;
596            effects.push(ident.name);
597        }
598        Ok(Some(effects))
599    }
600
601    fn parse_param_list(&mut self) -> PResult<Vec<Param>> {
602        let mut params = Vec::new();
603        if self.at(TokenKind::RParen) {
604            return Ok(params);
605        }
606        loop {
607            let param = self.parse_param()?;
608            let is_variadic = param.is_variadic;
609            params.push(param);
610            if is_variadic {
611                // Variadic must be the last parameter — no more params after it.
612                break;
613            }
614            if self.eat(TokenKind::Comma).is_none() {
615                break;
616            }
617            // Allow trailing comma before `)`.
618            if self.at(TokenKind::RParen) {
619                break;
620            }
621        }
622        Ok(params)
623    }
624
625    fn parse_param(&mut self) -> PResult<Param> {
626        // Variadic prefix: `...name: Type`
627        let is_variadic = self.eat(TokenKind::DotDotDot).is_some();
628        let name = self.parse_ident()?;
629        self.expect(TokenKind::Colon)?;
630        let ty = self.parse_type_expr()?;
631        // Optional default value: `param: Type = expr`
632        let default = if self.eat(TokenKind::Eq).is_some() {
633            if is_variadic {
634                self.error("variadic parameters cannot have default values", self.current_span());
635                return Err(());
636            }
637            Some(self.parse_expr()?)
638        } else {
639            None
640        };
641        let end_span = default.as_ref().map(|d| d.span).unwrap_or(ty.span);
642        let span = merge_spans(name.span, end_span);
643        Ok(Param { name, ty, default, is_variadic, span })
644    }
645
646    // ── trait ──────────────────────────────────────────────────────
647
648    fn parse_trait_decl(&mut self) -> PResult<Decl> {
649        let start = self.expect(TokenKind::Trait)?.span;
650        let name = self.parse_ident()?;
651        let type_params = self.parse_optional_type_params()?;
652        let super_traits = if self.eat(TokenKind::Colon).is_some() {
653            self.parse_trait_bound_list()?
654        } else {
655            Vec::new()
656        };
657        self.expect(TokenKind::LBrace)?;
658        let mut methods = Vec::new();
659        while !self.at(TokenKind::RBrace) && !self.at_eof() {
660            let sig = self.parse_fn_sig()?;
661            self.expect(TokenKind::Semicolon)?;
662            methods.push(sig);
663        }
664        let end = self.expect(TokenKind::RBrace)?.span;
665        Ok(Decl {
666            kind: DeclKind::Trait(TraitDecl {
667                name,
668                type_params,
669                super_traits,
670                methods,
671            }),
672            span: to_ast_span(start.merge(end)),
673        })
674    }
675
676    fn parse_fn_sig(&mut self) -> PResult<FnSig> {
677        let start = self.expect(TokenKind::Fn)?.span;
678        let name = self.parse_ident()?;
679        let type_params = self.parse_optional_type_params()?;
680        self.expect(TokenKind::LParen)?;
681        let params = self.parse_param_list()?;
682        let end_paren = self.expect(TokenKind::RParen)?.span;
683        let return_type = if self.eat(TokenKind::Arrow).is_some() {
684            Some(self.parse_type_expr()?)
685        } else {
686            None
687        };
688        let end = return_type
689            .as_ref()
690            .map(|t| to_diag_span(t.span))
691            .unwrap_or(end_paren);
692        Ok(FnSig {
693            name,
694            type_params,
695            params,
696            return_type,
697            span: to_ast_span(start.merge(end)),
698        })
699    }
700
701    fn parse_trait_bound_list(&mut self) -> PResult<Vec<TypeExpr>> {
702        let mut bounds = Vec::new();
703        bounds.push(self.parse_type_expr()?);
704        while self.eat(TokenKind::Plus).is_some() {
705            bounds.push(self.parse_type_expr()?);
706        }
707        Ok(bounds)
708    }
709
710    // ── impl ───────────────────────────────────────────────────────
711
712    fn parse_impl_decl(&mut self) -> PResult<Decl> {
713        let start = self.expect(TokenKind::Impl)?.span;
714        let type_params = self.parse_optional_type_params()?;
715        let first_type = self.parse_type_expr()?;
716
717        // P2-1: Support two syntaxes:
718        //   (1) `impl Type : Trait { ... }`  — original CJC syntax
719        //   (2) `impl Trait for Type { ... }` — Rust-style syntax
720        let (target, trait_ref) = if self.eat(TokenKind::For).is_some() {
721            // Syntax (2): first_type is the trait, next is the concrete type
722            let concrete = self.parse_type_expr()?;
723            (concrete, Some(first_type))
724        } else if self.eat(TokenKind::Colon).is_some() {
725            // Syntax (1): first_type is the target, next is the trait
726            (first_type, Some(self.parse_type_expr()?))
727        } else {
728            // Bare impl (no trait reference)
729            (first_type, None)
730        };
731        self.expect(TokenKind::LBrace)?;
732        let mut methods = Vec::new();
733        while !self.at(TokenKind::RBrace) && !self.at_eof() {
734            let is_nogc = if self.at(TokenKind::NoGc) && self.peek_ahead(1) == TokenKind::Fn {
735                self.advance();
736                true
737            } else {
738                false
739            };
740            let fn_start = self.expect(TokenKind::Fn)?.span;
741            let name = self.parse_ident()?;
742            let fn_type_params = self.parse_optional_type_params()?;
743            self.expect(TokenKind::LParen)?;
744            let params = self.parse_param_list()?;
745            self.expect(TokenKind::RParen)?;
746            let return_type = if self.eat(TokenKind::Arrow).is_some() {
747                Some(self.parse_type_expr()?)
748            } else {
749                None
750            };
751            let effect_annotation = self.parse_effect_annotation()?;
752            let body = self.parse_block()?;
753            let fn_span = merge_spans(to_ast_span(fn_start), body.span);
754            methods.push(FnDecl {
755                name,
756                type_params: fn_type_params,
757                params,
758                return_type,
759                body,
760                is_nogc,
761                effect_annotation,
762                decorators: vec![],
763                vis: Visibility::Private,
764            });
765            let _ = fn_span; // span used for method
766        }
767        let end = self.expect(TokenKind::RBrace)?.span;
768        Ok(Decl {
769            kind: DeclKind::Impl(ImplDecl {
770                type_params,
771                target,
772                trait_ref,
773                methods,
774                span: to_ast_span(start.merge(end)),
775            }),
776            span: to_ast_span(start.merge(end)),
777        })
778    }
779
780    // ── import ─────────────────────────────────────────────────────
781
782    fn parse_import_decl(&mut self) -> PResult<Decl> {
783        let start = self.expect(TokenKind::Import)?.span;
784        let mut path = Vec::new();
785        path.push(self.parse_ident()?);
786        while self.eat(TokenKind::Dot).is_some() {
787            path.push(self.parse_ident()?);
788        }
789        let alias = if self.eat(TokenKind::As).is_some() {
790            Some(self.parse_ident()?)
791        } else {
792            None
793        };
794        let end_span = alias
795            .as_ref()
796            .map(|a| to_diag_span(a.span))
797            .or_else(|| path.last().map(|p| to_diag_span(p.span)))
798            .unwrap_or(start);
799        Ok(Decl {
800            kind: DeclKind::Import(ImportDecl { path, alias }),
801            span: to_ast_span(start.merge(end_span)),
802        })
803    }
804
805    /// Parse `mod name;` — syntactic sugar for `import name`.
806    fn parse_mod_decl(&mut self) -> PResult<Decl> {
807        let start = self.expect(TokenKind::Mod)?.span;
808        let name = self.parse_ident()?;
809        let end_span = to_diag_span(name.span);
810        // `mod X;` desugars to `import X`
811        Ok(Decl {
812            kind: DeclKind::Import(ImportDecl {
813                path: vec![name],
814                alias: None,
815            }),
816            span: to_ast_span(start.merge(end_span)),
817        })
818    }
819
820    // ── let (top-level or statement) ───────────────────────────────
821
822    fn parse_let_decl(&mut self) -> PResult<Decl> {
823        let start = self.current_span();
824        let let_stmt = self.parse_let_stmt()?;
825        self.expect(TokenKind::Semicolon)?;
826        let end = self.previous_span();
827        Ok(Decl {
828            kind: DeclKind::Let(let_stmt),
829            span: to_ast_span(start.merge(end)),
830        })
831    }
832
833    /// P2-3: Parse a compile-time constant declaration: `const NAME: Type = expr;`
834    fn parse_const_decl(&mut self) -> PResult<Decl> {
835        let start = self.expect(TokenKind::Const)?.span;
836        let name = self.parse_ident()?;
837        self.expect(TokenKind::Colon)?;
838        let ty = self.parse_type_expr()?;
839        self.expect(TokenKind::Eq)?;
840        let value = self.parse_expr()?;
841        let end = self.expect(TokenKind::Semicolon)?.span;
842        let span = to_ast_span(start.merge(end));
843        Ok(Decl {
844            kind: DeclKind::Const(ConstDecl {
845                name,
846                ty,
847                value: Box::new(value),
848                span,
849            }),
850            span,
851        })
852    }
853
854    /// P2-5: Parse the raw content of a format string `f"..."` into segments.
855    ///
856    /// A segment is `(literal_text, Option<interpolated_expr>)`.
857    /// The token text already has `{expr}` blocks verbatim (including the braces).
858    /// We split on those blocks, re-lex and re-parse each expression fragment.
859    fn parse_fstring_segments(
860        &self,
861        raw: &str,
862        _span: cjc_ast::Span,
863    ) -> PResult<Vec<(String, Option<Box<Expr>>)>> {
864        let mut segments: Vec<(String, Option<Box<Expr>>)> = Vec::new();
865        let bytes = raw.as_bytes();
866        let mut i = 0;
867        let mut literal = String::new();
868
869        while i < bytes.len() {
870            if bytes[i] == b'{' {
871                // Find the matching closing `}` (respecting nesting)
872                let mut depth = 1usize;
873                let hole_start = i + 1;
874                let mut j = hole_start;
875                while j < bytes.len() && depth > 0 {
876                    if bytes[j] == b'{' {
877                        depth += 1;
878                    } else if bytes[j] == b'}' {
879                        depth -= 1;
880                    }
881                    j += 1;
882                }
883                let hole_end = j - 1; // index of the closing `}`
884                let expr_src = &raw[hole_start..hole_end];
885
886                // Re-lex and re-parse the inner expression
887                let lexer = cjc_lexer::Lexer::new(expr_src);
888                let (tokens, _lex_diags) = lexer.tokenize();
889                let mut sub_parser = Parser::new(tokens);
890                let interp_expr = sub_parser.parse_expr().map_err(|_| ())?;
891                segments.push((literal.clone(), Some(Box::new(interp_expr))));
892                literal.clear();
893                i = j; // skip past the `}`
894            } else {
895                literal.push(bytes[i] as char);
896                i += 1;
897            }
898        }
899
900        // Trailing literal (possibly empty)
901        segments.push((literal, None));
902        Ok(segments)
903    }
904
905    fn parse_let_stmt(&mut self) -> PResult<LetStmt> {
906        self.expect(TokenKind::Let)?;
907        let mutable = self.eat(TokenKind::Mut).is_some();
908        let name = self.parse_ident()?;
909        let ty = if self.eat(TokenKind::Colon).is_some() {
910            Some(self.parse_type_expr()?)
911        } else {
912            None
913        };
914        self.expect(TokenKind::Eq)?;
915        let init = self.parse_expr()?;
916        Ok(LetStmt {
917            name,
918            mutable,
919            ty,
920            init: Box::new(init),
921        })
922    }
923
924    // ── Type expressions ───────────────────────────────────────────
925
926    fn parse_type_expr(&mut self) -> PResult<TypeExpr> {
927        let base = match self.peek_kind() {
928            TokenKind::Ident => self.parse_named_type()?,
929            TokenKind::LParen => self.parse_tuple_type()?,
930            TokenKind::LBracket => self.parse_array_or_shape_type()?,
931            TokenKind::Fn => self.parse_fn_type()?,
932            _ => {
933                let tok = self.peek().clone();
934                self.error(
935                    format!("expected type, found {}", tok.kind.describe()),
936                    tok.span,
937                );
938                return Err(());
939            }
940        };
941
942        // Desugar `T | null` to `Option<T>`
943        // Skip this when inside lambda param lists where `|` closes the params.
944        if self.allow_pipe_in_type && self.at(TokenKind::Pipe) {
945            let pipe_span = self.advance().span;
946            if self.at(TokenKind::Null) {
947                let null_tok = self.advance();
948                let base_span = base.span;
949                let end_span = to_ast_span(null_tok.span);
950                let option_name = cjc_ast::Ident::new("Option", base_span);
951                return Ok(TypeExpr {
952                    kind: TypeExprKind::Named {
953                        name: option_name,
954                        args: vec![TypeArg::Type(base)],
955                    },
956                    span: merge_spans(base_span, end_span),
957                });
958            } else {
959                let tok = self.peek().clone();
960                self.error(
961                    format!(
962                        "expected `null` after `|` in type expression (full union types are not yet supported), found {}",
963                        tok.kind.describe()
964                    ),
965                    pipe_span,
966                );
967                return Err(());
968            }
969        }
970
971        Ok(base)
972    }
973
974    fn parse_named_type(&mut self) -> PResult<TypeExpr> {
975        let name = self.parse_ident()?;
976        let start_span = name.span;
977        let args = if self.at(TokenKind::Lt) {
978            self.parse_type_arg_list()?
979        } else {
980            Vec::new()
981        };
982        let end_span = if args.is_empty() {
983            start_span
984        } else {
985            // The `>` was consumed; use previous token span.
986            to_ast_span(self.previous_span())
987        };
988        Ok(TypeExpr {
989            kind: TypeExprKind::Named { name, args },
990            span: merge_spans(start_span, end_span),
991        })
992    }
993
994    fn parse_type_arg_list(&mut self) -> PResult<Vec<TypeArg>> {
995        self.expect(TokenKind::Lt)?;
996        let mut args = Vec::new();
997        if !self.at(TokenKind::Gt) {
998            loop {
999                let arg = self.parse_type_arg()?;
1000                args.push(arg);
1001                if self.eat(TokenKind::Comma).is_none() {
1002                    break;
1003                }
1004                if self.at(TokenKind::Gt) {
1005                    break;
1006                }
1007            }
1008        }
1009        self.expect(TokenKind::Gt)?;
1010        Ok(args)
1011    }
1012
1013    fn parse_type_arg(&mut self) -> PResult<TypeArg> {
1014        // Heuristic: if next is `[` it could be a shape, otherwise try type.
1015        // Integer literals become Expr type args.
1016        match self.peek_kind() {
1017            TokenKind::LBracket => {
1018                let dims = self.parse_shape_dims()?;
1019                Ok(TypeArg::Shape(dims))
1020            }
1021            TokenKind::IntLit => {
1022                let expr = self.parse_expr()?;
1023                Ok(TypeArg::Expr(expr))
1024            }
1025            _ => {
1026                let ty = self.parse_type_expr()?;
1027                Ok(TypeArg::Type(ty))
1028            }
1029        }
1030    }
1031
1032    fn parse_shape_dims(&mut self) -> PResult<Vec<ShapeDim>> {
1033        self.expect(TokenKind::LBracket)?;
1034        let mut dims = Vec::new();
1035        if !self.at(TokenKind::RBracket) {
1036            loop {
1037                let dim = match self.peek_kind() {
1038                    TokenKind::IntLit => {
1039                        let tok = self.advance().clone();
1040                        ShapeDim::Lit(tok.int_value())
1041                    }
1042                    TokenKind::Ident => {
1043                        let ident = self.parse_ident()?;
1044                        ShapeDim::Name(ident)
1045                    }
1046                    _ => {
1047                        let tok = self.peek().clone();
1048                        self.error(
1049                            format!(
1050                                "expected shape dimension (integer or name), found {}",
1051                                tok.kind.describe()
1052                            ),
1053                            tok.span,
1054                        );
1055                        return Err(());
1056                    }
1057                };
1058                dims.push(dim);
1059                if self.eat(TokenKind::Comma).is_none() {
1060                    break;
1061                }
1062            }
1063        }
1064        self.expect(TokenKind::RBracket)?;
1065        Ok(dims)
1066    }
1067
1068    fn parse_tuple_type(&mut self) -> PResult<TypeExpr> {
1069        let start = self.expect(TokenKind::LParen)?.span;
1070        let mut elems = Vec::new();
1071        if !self.at(TokenKind::RParen) {
1072            loop {
1073                elems.push(self.parse_type_expr()?);
1074                if self.eat(TokenKind::Comma).is_none() {
1075                    break;
1076                }
1077                if self.at(TokenKind::RParen) {
1078                    break;
1079                }
1080            }
1081        }
1082        let end = self.expect(TokenKind::RParen)?.span;
1083        Ok(TypeExpr {
1084            kind: TypeExprKind::Tuple(elems),
1085            span: to_ast_span(start.merge(end)),
1086        })
1087    }
1088
1089    fn parse_array_or_shape_type(&mut self) -> PResult<TypeExpr> {
1090        // `[T; N]` — array type, or `[M, N]` — shape literal.
1091        // Peek ahead to decide: if we see `Ident ;` or `Ident <...> ;` it
1092        // is an array type.  Otherwise treat as shape.
1093        let start = self.expect(TokenKind::LBracket)?.span;
1094
1095        // Try to detect array type: first element is a type followed by `;`.
1096        // We save position and attempt to parse type then check for `;`.
1097        let saved_pos = self.pos;
1098        let saved_diag_len = self.diagnostics.diagnostics.len();
1099
1100        if let Ok(elem_ty) = self.parse_type_expr() {
1101            if self.eat(TokenKind::Semicolon).is_some() {
1102                // This is an array type `[T; N]`.
1103                let size = self.parse_expr()?;
1104                let end = self.expect(TokenKind::RBracket)?.span;
1105                return Ok(TypeExpr {
1106                    kind: TypeExprKind::Array {
1107                        elem: Box::new(elem_ty),
1108                        size: Box::new(size),
1109                    },
1110                    span: to_ast_span(start.merge(end)),
1111                });
1112            }
1113        }
1114
1115        // Backtrack — it is a shape literal.
1116        self.pos = saved_pos;
1117        self.diagnostics.diagnostics.truncate(saved_diag_len);
1118
1119        let mut dims = Vec::new();
1120        if !self.at(TokenKind::RBracket) {
1121            loop {
1122                let dim = match self.peek_kind() {
1123                    TokenKind::IntLit => {
1124                        let tok = self.advance().clone();
1125                        ShapeDim::Lit(tok.int_value())
1126                    }
1127                    TokenKind::Ident => {
1128                        let ident = self.parse_ident()?;
1129                        ShapeDim::Name(ident)
1130                    }
1131                    _ => {
1132                        let tok = self.peek().clone();
1133                        self.error(
1134                            format!(
1135                                "expected shape dimension, found {}",
1136                                tok.kind.describe()
1137                            ),
1138                            tok.span,
1139                        );
1140                        return Err(());
1141                    }
1142                };
1143                dims.push(dim);
1144                if self.eat(TokenKind::Comma).is_none() {
1145                    break;
1146                }
1147            }
1148        }
1149        let end = self.expect(TokenKind::RBracket)?.span;
1150        Ok(TypeExpr {
1151            kind: TypeExprKind::ShapeLit(dims),
1152            span: to_ast_span(start.merge(end)),
1153        })
1154    }
1155
1156    fn parse_fn_type(&mut self) -> PResult<TypeExpr> {
1157        let start = self.expect(TokenKind::Fn)?.span;
1158        self.expect(TokenKind::LParen)?;
1159        let mut params = Vec::new();
1160        if !self.at(TokenKind::RParen) {
1161            loop {
1162                params.push(self.parse_type_expr()?);
1163                if self.eat(TokenKind::Comma).is_none() {
1164                    break;
1165                }
1166                if self.at(TokenKind::RParen) {
1167                    break;
1168                }
1169            }
1170        }
1171        self.expect(TokenKind::RParen)?;
1172        self.expect(TokenKind::Arrow)?;
1173        let ret = self.parse_type_expr()?;
1174        let end_span = ret.span;
1175        Ok(TypeExpr {
1176            kind: TypeExprKind::Fn {
1177                params,
1178                ret: Box::new(ret),
1179            },
1180            span: merge_spans(to_ast_span(start), end_span),
1181        })
1182    }
1183
1184    // ── Type parameters ────────────────────────────────────────────
1185
1186    fn parse_optional_type_params(&mut self) -> PResult<Vec<TypeParam>> {
1187        if !self.at(TokenKind::Lt) {
1188            return Ok(Vec::new());
1189        }
1190        self.expect(TokenKind::Lt)?;
1191        let mut params = Vec::new();
1192        if !self.at(TokenKind::Gt) {
1193            loop {
1194                let param = self.parse_type_param()?;
1195                params.push(param);
1196                if self.eat(TokenKind::Comma).is_none() {
1197                    break;
1198                }
1199                if self.at(TokenKind::Gt) {
1200                    break;
1201                }
1202            }
1203        }
1204        self.expect(TokenKind::Gt)?;
1205        Ok(params)
1206    }
1207
1208    fn parse_type_param(&mut self) -> PResult<TypeParam> {
1209        let name = self.parse_ident()?;
1210        let start_span = name.span;
1211        let bounds = if self.eat(TokenKind::Colon).is_some() {
1212            self.parse_trait_bound_list()?
1213        } else {
1214            Vec::new()
1215        };
1216        let end_span = bounds
1217            .last()
1218            .map(|b| b.span)
1219            .unwrap_or(start_span);
1220        Ok(TypeParam {
1221            name,
1222            bounds,
1223            span: merge_spans(start_span, end_span),
1224        })
1225    }
1226
1227    // ── Blocks ─────────────────────────────────────────────────────
1228
1229    fn parse_block(&mut self) -> PResult<Block> {
1230        let start = self.expect(TokenKind::LBrace)?.span;
1231        let mut stmts = Vec::new();
1232        let mut tail_expr: Option<Box<Expr>> = None;
1233
1234        while !self.at(TokenKind::RBrace) && !self.at_eof() {
1235            // Try to parse a statement.  If the statement is an expression
1236            // without a trailing semicolon *and* it is the last thing before
1237            // `}`, treat it as the block's tail expression.
1238            match self.peek_kind() {
1239                TokenKind::Let => {
1240                    let let_stmt = self.parse_let_stmt()?;
1241                    self.expect(TokenKind::Semicolon)?;
1242                    let span = merge_spans(
1243                        let_stmt.name.span,
1244                        let_stmt.init.span,
1245                    );
1246                    stmts.push(Stmt {
1247                        kind: StmtKind::Let(let_stmt),
1248                        span,
1249                    });
1250                }
1251                TokenKind::Return => {
1252                    let ret_start = self.advance().span;
1253                    let value = if !self.at(TokenKind::Semicolon)
1254                        && !self.at(TokenKind::RBrace)
1255                        && !self.at_eof()
1256                    {
1257                        Some(self.parse_expr()?)
1258                    } else {
1259                        None
1260                    };
1261                    let end = self.expect(TokenKind::Semicolon)?.span;
1262                    stmts.push(Stmt {
1263                        kind: StmtKind::Return(value),
1264                        span: to_ast_span(ret_start.merge(end)),
1265                    });
1266                }
1267                TokenKind::Break => {
1268                    let brk_start = self.advance().span;
1269                    if self.loop_depth == 0 {
1270                        self.diagnostics.emit(Diagnostic::error(
1271                            "E0400",
1272                            "`break` outside of loop",
1273                            brk_start,
1274                        ));
1275                    }
1276                    let end = self.expect(TokenKind::Semicolon)?.span;
1277                    stmts.push(Stmt {
1278                        kind: StmtKind::Break,
1279                        span: to_ast_span(brk_start.merge(end)),
1280                    });
1281                }
1282                TokenKind::Continue => {
1283                    let cont_start = self.advance().span;
1284                    if self.loop_depth == 0 {
1285                        self.diagnostics.emit(Diagnostic::error(
1286                            "E0401",
1287                            "`continue` outside of loop",
1288                            cont_start,
1289                        ));
1290                    }
1291                    let end = self.expect(TokenKind::Semicolon)?.span;
1292                    stmts.push(Stmt {
1293                        kind: StmtKind::Continue,
1294                        span: to_ast_span(cont_start.merge(end)),
1295                    });
1296                }
1297                TokenKind::If => {
1298                    let if_start_span = to_ast_span(self.current_span());
1299                    let if_stmt = self.parse_if_stmt()?;
1300                    let if_end_span = if_stmt.then_block.span;
1301                    stmts.push(Stmt {
1302                        kind: StmtKind::If(if_stmt),
1303                        span: merge_spans(if_start_span, if_end_span),
1304                    });
1305                }
1306                TokenKind::While => {
1307                    let while_stmt = self.parse_while_stmt()?;
1308                    let span = while_stmt.body.span;
1309                    stmts.push(Stmt {
1310                        kind: StmtKind::While(while_stmt),
1311                        span,
1312                    });
1313                }
1314                TokenKind::For => {
1315                    let for_start_span = to_ast_span(self.current_span());
1316                    let for_stmt = self.parse_for_stmt()?;
1317                    let span = merge_spans(for_start_span, for_stmt.body.span);
1318                    stmts.push(Stmt {
1319                        kind: StmtKind::For(for_stmt),
1320                        span,
1321                    });
1322                }
1323                TokenKind::NoGc if self.peek_ahead(1) == TokenKind::LBrace => {
1324                    let nogc_start = self.advance().span;
1325                    let block = self.parse_block()?;
1326                    let span = merge_spans(to_ast_span(nogc_start), block.span);
1327                    stmts.push(Stmt {
1328                        kind: StmtKind::NoGcBlock(block),
1329                        span,
1330                    });
1331                }
1332                _ => {
1333                    // Expression statement or tail expression.
1334                    let expr = self.parse_expr()?;
1335                    if self.eat(TokenKind::Semicolon).is_some() {
1336                        let span = expr.span;
1337                        stmts.push(Stmt {
1338                            kind: StmtKind::Expr(expr),
1339                            span,
1340                        });
1341                    } else if self.at(TokenKind::RBrace) {
1342                        // Tail expression.
1343                        tail_expr = Some(Box::new(expr));
1344                    } else {
1345                        // Missing semicolon — emit error, treat as statement.
1346                        let span = expr.span;
1347                        self.error_with_hint(
1348                            "expected `;` after expression statement",
1349                            to_diag_span(span),
1350                            "add a `;` here",
1351                        );
1352                        stmts.push(Stmt {
1353                            kind: StmtKind::Expr(expr),
1354                            span,
1355                        });
1356                    }
1357                }
1358            }
1359        }
1360        let end = self.expect(TokenKind::RBrace)?.span;
1361        Ok(Block {
1362            stmts,
1363            expr: tail_expr,
1364            span: to_ast_span(start.merge(end)),
1365        })
1366    }
1367
1368    // ── Statements ─────────────────────────────────────────────────
1369
1370    fn parse_if_stmt(&mut self) -> PResult<IfStmt> {
1371        self.expect(TokenKind::If)?;
1372        // Disable struct literals in the condition so that `if x { ... }`
1373        // does not try to parse `x { ... }` as a struct literal.
1374        let prev = self.allow_struct_lit;
1375        self.allow_struct_lit = false;
1376        let condition = self.parse_expr();
1377        self.allow_struct_lit = prev;
1378        let condition = condition?;
1379        let then_block = self.parse_block()?;
1380        let else_branch = if self.eat(TokenKind::Else).is_some() {
1381            if self.at(TokenKind::If) {
1382                Some(ElseBranch::ElseIf(Box::new(self.parse_if_stmt()?)))
1383            } else {
1384                Some(ElseBranch::Else(self.parse_block()?))
1385            }
1386        } else {
1387            None
1388        };
1389        Ok(IfStmt {
1390            condition,
1391            then_block,
1392            else_branch,
1393        })
1394    }
1395
1396    fn parse_while_stmt(&mut self) -> PResult<WhileStmt> {
1397        self.expect(TokenKind::While)?;
1398        // Disable struct literals in the condition so that `while x { ... }`
1399        // does not try to parse `x { ... }` as a struct literal.
1400        let prev = self.allow_struct_lit;
1401        self.allow_struct_lit = false;
1402        let condition = self.parse_expr();
1403        self.allow_struct_lit = prev;
1404        let condition = condition?;
1405        self.loop_depth += 1;
1406        let body = self.parse_block()?;
1407        self.loop_depth -= 1;
1408        Ok(WhileStmt { condition, body })
1409    }
1410
1411    fn parse_for_stmt(&mut self) -> PResult<ForStmt> {
1412        self.expect(TokenKind::For)?;
1413        let ident = self.parse_ident()?;
1414        self.expect(TokenKind::In)?;
1415
1416        // Parse the iterator expression. We need to detect the range form
1417        // `start..end`. Strategy: parse a primary expression, then check
1418        // if `..` follows. If so, parse the end expression. Otherwise,
1419        // treat the whole thing as an expression iterator.
1420        //
1421        // Disable struct literals to avoid ambiguity with the block body.
1422        let prev = self.allow_struct_lit;
1423        self.allow_struct_lit = false;
1424        let start_expr = self.parse_expr_bp(prec::CMP + 1)?;
1425        let iter = if self.eat(TokenKind::DotDot).is_some() {
1426            let end_expr = self.parse_expr_bp(prec::CMP + 1)?;
1427            ForIter::Range {
1428                start: Box::new(start_expr),
1429                end: Box::new(end_expr),
1430            }
1431        } else {
1432            ForIter::Expr(Box::new(start_expr))
1433        };
1434        self.allow_struct_lit = prev;
1435
1436        self.loop_depth += 1;
1437        let body = self.parse_block()?;
1438        self.loop_depth -= 1;
1439        Ok(ForStmt { ident, iter, body })
1440    }
1441
1442    // ── Expressions (Pratt parser) ─────────────────────────────────
1443
1444    fn parse_expr(&mut self) -> PResult<Expr> {
1445        self.parse_expr_bp(0)
1446    }
1447
1448    /// Pratt parser: parse an expression with minimum binding power `min_bp`.
1449    fn parse_expr_bp(&mut self, min_bp: u8) -> PResult<Expr> {
1450        // ── Prefix / atom ──────────────────────────────────────────
1451        let mut lhs = self.parse_prefix()?;
1452
1453        // ── Infix / postfix loop ───────────────────────────────────
1454        loop {
1455            let (op_info, is_postfix) = match self.peek_kind() {
1456                // Postfix operators
1457                TokenKind::Dot => (Some((prec::POSTFIX, prec::POSTFIX + 1)), true),
1458                TokenKind::LParen => (Some((prec::POSTFIX, prec::POSTFIX + 1)), true),
1459                TokenKind::LBracket => (Some((prec::POSTFIX, prec::POSTFIX + 1)), true),
1460                TokenKind::Question => (Some((prec::POSTFIX, prec::POSTFIX + 1)), true),
1461
1462                // Struct literal: `Ident { ... }` is only valid when lhs is
1463                // a bare identifier.  We need to be careful not to confuse
1464                // it with a block expression in statement position.  The
1465                // caller (parse_block) handles this ambiguity by checking
1466                // for a trailing semicolon.
1467                TokenKind::LBrace => {
1468                    // Only treat `{ ... }` as a struct literal when the lhs
1469                    // is a simple identifier AND struct literals are allowed
1470                    // in the current context (they are disallowed in
1471                    // `if`/`while` conditions to avoid ambiguity with the
1472                    // block body).
1473                    if self.allow_struct_lit && matches!(lhs.kind, ExprKind::Ident(_)) {
1474                        (Some((prec::POSTFIX, prec::POSTFIX + 1)), true)
1475                    } else {
1476                        break;
1477                    }
1478                }
1479
1480                // Assignment (right-associative)
1481                TokenKind::Eq => {
1482                    let (l_bp, r_bp) = (prec::ASSIGN, prec::ASSIGN);
1483                    (Some((l_bp, r_bp)), false)
1484                }
1485
1486                // Compound assignment (right-associative)
1487                TokenKind::PlusEq | TokenKind::MinusEq | TokenKind::StarEq
1488                | TokenKind::SlashEq | TokenKind::PercentEq | TokenKind::StarStarEq
1489                | TokenKind::AmpEq | TokenKind::PipeEq | TokenKind::CaretEq
1490                | TokenKind::LtLtEq | TokenKind::GtGtEq => {
1491                    (Some((prec::ASSIGN, prec::ASSIGN)), false)
1492                }
1493
1494                // Pipe
1495                TokenKind::PipeGt => {
1496                    let (l_bp, r_bp) = (prec::PIPE, prec::PIPE + 1);
1497                    (Some((l_bp, r_bp)), false)
1498                }
1499
1500                // Binary operators
1501                TokenKind::PipePipe => (Some((prec::OR, prec::OR + 1)), false),
1502                TokenKind::AmpAmp => (Some((prec::AND, prec::AND + 1)), false),
1503                // Bitwise operators
1504                TokenKind::Pipe => (Some((prec::BIT_OR, prec::BIT_OR + 1)), false),
1505                TokenKind::Caret => (Some((prec::BIT_XOR, prec::BIT_XOR + 1)), false),
1506                TokenKind::Amp => (Some((prec::BIT_AND, prec::BIT_AND + 1)), false),
1507                TokenKind::EqEq | TokenKind::BangEq => (Some((prec::EQ, prec::EQ + 1)), false),
1508                TokenKind::TildeEq | TokenKind::BangTilde => (Some((prec::EQ, prec::EQ + 1)), false),
1509                TokenKind::Lt | TokenKind::Gt | TokenKind::LtEq | TokenKind::GtEq => {
1510                    (Some((prec::CMP, prec::CMP + 1)), false)
1511                }
1512                // Shift operators
1513                TokenKind::LtLt | TokenKind::GtGt => (Some((prec::SHIFT, prec::SHIFT + 1)), false),
1514                TokenKind::Plus | TokenKind::Minus => (Some((prec::ADD, prec::ADD + 1)), false),
1515                TokenKind::Star | TokenKind::Slash | TokenKind::Percent => {
1516                    (Some((prec::MUL, prec::MUL + 1)), false)
1517                }
1518                // Power (right-associative)
1519                TokenKind::StarStar => (Some((prec::POW, prec::POW)), false),
1520
1521                _ => break,
1522            };
1523
1524            let (l_bp, r_bp) = match op_info {
1525                Some(bp) => bp,
1526                None => break,
1527            };
1528
1529            if l_bp < min_bp {
1530                break;
1531            }
1532
1533            if is_postfix {
1534                lhs = self.parse_postfix(lhs)?;
1535            } else {
1536                lhs = self.parse_infix(lhs, r_bp)?;
1537            }
1538        }
1539
1540        Ok(lhs)
1541    }
1542
1543    /// Parse a prefix expression or an atomic expression.
1544    fn parse_prefix(&mut self) -> PResult<Expr> {
1545        match self.peek_kind() {
1546            // Unary minus
1547            TokenKind::Minus => {
1548                let op_tok = self.advance().clone();
1549                let operand = self.parse_expr_bp(prec::UNARY)?;
1550                let span = merge_spans(to_ast_span(op_tok.span), operand.span);
1551                Ok(Expr {
1552                    kind: ExprKind::Unary {
1553                        op: UnaryOp::Neg,
1554                        operand: Box::new(operand),
1555                    },
1556                    span,
1557                })
1558            }
1559            // Unary not
1560            TokenKind::Bang => {
1561                let op_tok = self.advance().clone();
1562                let operand = self.parse_expr_bp(prec::UNARY)?;
1563                let span = merge_spans(to_ast_span(op_tok.span), operand.span);
1564                Ok(Expr {
1565                    kind: ExprKind::Unary {
1566                        op: UnaryOp::Not,
1567                        operand: Box::new(operand),
1568                    },
1569                    span,
1570                })
1571            }
1572            // Bitwise NOT
1573            TokenKind::Tilde => {
1574                let op_tok = self.advance().clone();
1575                let operand = self.parse_expr_bp(prec::UNARY)?;
1576                let span = merge_spans(to_ast_span(op_tok.span), operand.span);
1577                Ok(Expr {
1578                    kind: ExprKind::Unary {
1579                        op: UnaryOp::BitNot,
1580                        operand: Box::new(operand),
1581                    },
1582                    span,
1583                })
1584            }
1585            // If expression: `if cond { a } else { b }` used in expression context
1586            TokenKind::If => {
1587                let start_span = to_ast_span(self.current_span());
1588                let if_stmt = self.parse_if_stmt()?;
1589                let end_span = if_stmt.then_block.span;
1590                let span = merge_spans(start_span, end_span);
1591                Ok(Expr {
1592                    kind: ExprKind::IfExpr {
1593                        condition: Box::new(if_stmt.condition),
1594                        then_block: if_stmt.then_block,
1595                        else_branch: if_stmt.else_branch,
1596                    },
1597                    span,
1598                })
1599            }
1600            _ => self.parse_atom(),
1601        }
1602    }
1603
1604    /// Parse an atomic (primary) expression.
1605    fn parse_atom(&mut self) -> PResult<Expr> {
1606        match self.peek_kind() {
1607            TokenKind::IntLit => {
1608                let tok = self.advance().clone();
1609                Ok(Expr {
1610                    kind: ExprKind::IntLit(tok.int_value()),
1611                    span: to_ast_span(tok.span),
1612                })
1613            }
1614            TokenKind::FloatLit => {
1615                let tok = self.advance().clone();
1616                Ok(Expr {
1617                    kind: ExprKind::FloatLit(tok.float_value()),
1618                    span: to_ast_span(tok.span),
1619                })
1620            }
1621            TokenKind::StringLit => {
1622                let tok = self.advance().clone();
1623                Ok(Expr {
1624                    kind: ExprKind::StringLit(tok.text.clone()),
1625                    span: to_ast_span(tok.span),
1626                })
1627            }
1628            TokenKind::ByteStringLit => {
1629                let tok = self.advance().clone();
1630                let bytes: Vec<u8> = tok.text.chars().map(|c| c as u8).collect();
1631                Ok(Expr {
1632                    kind: ExprKind::ByteStringLit(bytes),
1633                    span: to_ast_span(tok.span),
1634                })
1635            }
1636            TokenKind::ByteCharLit => {
1637                let tok = self.advance().clone();
1638                let byte_val: u8 = tok.text.parse().unwrap_or(0);
1639                Ok(Expr {
1640                    kind: ExprKind::ByteCharLit(byte_val),
1641                    span: to_ast_span(tok.span),
1642                })
1643            }
1644            TokenKind::RawStringLit => {
1645                let tok = self.advance().clone();
1646                Ok(Expr {
1647                    kind: ExprKind::RawStringLit(tok.text.clone()),
1648                    span: to_ast_span(tok.span),
1649                })
1650            }
1651            TokenKind::RawByteStringLit => {
1652                let tok = self.advance().clone();
1653                let bytes: Vec<u8> = tok.text.bytes().collect();
1654                Ok(Expr {
1655                    kind: ExprKind::RawByteStringLit(bytes),
1656                    span: to_ast_span(tok.span),
1657                })
1658            }
1659            TokenKind::FStringLit => {
1660                let tok = self.advance().clone();
1661                let span = to_ast_span(tok.span);
1662                // Parse the raw token text into segments.
1663                // Format: alternating literal text and `{expr}` holes.
1664                let segments = self.parse_fstring_segments(&tok.text, span)?;
1665                Ok(Expr {
1666                    kind: ExprKind::FStringLit(segments),
1667                    span,
1668                })
1669            }
1670            TokenKind::RegexLit => {
1671                let tok = self.advance().clone();
1672                // Token text format: "pattern\0flags" or just "pattern" (no NUL if no flags)
1673                let (pattern, flags) = if let Some(idx) = tok.text.find('\0') {
1674                    (tok.text[..idx].to_string(), tok.text[idx + 1..].to_string())
1675                } else {
1676                    (tok.text.clone(), String::new())
1677                };
1678                Ok(Expr {
1679                    kind: ExprKind::RegexLit { pattern, flags },
1680                    span: to_ast_span(tok.span),
1681                })
1682            }
1683            TokenKind::True => {
1684                let tok = self.advance().clone();
1685                Ok(Expr {
1686                    kind: ExprKind::BoolLit(true),
1687                    span: to_ast_span(tok.span),
1688                })
1689            }
1690            TokenKind::False => {
1691                let tok = self.advance().clone();
1692                Ok(Expr {
1693                    kind: ExprKind::BoolLit(false),
1694                    span: to_ast_span(tok.span),
1695                })
1696            }
1697            TokenKind::Ident => {
1698                let ident = self.parse_ident()?;
1699                Ok(Expr {
1700                    kind: ExprKind::Ident(ident.clone()),
1701                    span: ident.span,
1702                })
1703            }
1704            TokenKind::Col => self.parse_col_expr(),
1705            TokenKind::LParen => self.parse_paren_expr(),
1706            TokenKind::LBracket => self.parse_array_lit(),
1707            TokenKind::LBracketPipe => self.parse_tensor_lit(),
1708            TokenKind::Pipe => self.parse_lambda(),
1709            TokenKind::PipePipe => self.parse_lambda_no_params(),
1710            TokenKind::Match => self.parse_match_expr(),
1711            TokenKind::LBrace => {
1712                let block = self.parse_block()?;
1713                let span = block.span;
1714                Ok(Expr {
1715                    kind: ExprKind::Block(block),
1716                    span,
1717                })
1718            }
1719            _ => {
1720                let tok = self.peek().clone();
1721                self.error(
1722                    format!("expected expression, found {}", tok.kind.describe()),
1723                    tok.span,
1724                );
1725                Err(())
1726            }
1727        }
1728    }
1729
1730    fn parse_col_expr(&mut self) -> PResult<Expr> {
1731        let start = self.expect(TokenKind::Col)?.span;
1732        self.expect(TokenKind::LParen)?;
1733        let name_tok = self.expect(TokenKind::StringLit)?;
1734        let end = self.expect(TokenKind::RParen)?.span;
1735        Ok(Expr {
1736            kind: ExprKind::Col(name_tok.text.clone()),
1737            span: to_ast_span(start.merge(end)),
1738        })
1739    }
1740
1741    fn parse_paren_expr(&mut self) -> PResult<Expr> {
1742        let start = self.expect(TokenKind::LParen)?.span;
1743        // Empty parens: ()
1744        if self.at(TokenKind::RParen) {
1745            let end = self.advance().span;
1746            return Ok(Expr {
1747                kind: ExprKind::TupleLit(vec![]),
1748                span: to_ast_span(start.merge(end)),
1749            });
1750        }
1751        let first = self.parse_expr()?;
1752        // If followed by comma, this is a tuple literal
1753        if self.at(TokenKind::Comma) {
1754            let mut elems = vec![first];
1755            while self.eat(TokenKind::Comma).is_some() {
1756                if self.at(TokenKind::RParen) {
1757                    break; // trailing comma
1758                }
1759                elems.push(self.parse_expr()?);
1760            }
1761            let end = self.expect(TokenKind::RParen)?.span;
1762            return Ok(Expr {
1763                kind: ExprKind::TupleLit(elems),
1764                span: to_ast_span(start.merge(end)),
1765            });
1766        }
1767        let end = self.expect(TokenKind::RParen)?.span;
1768        // Single expression in parens — just grouping.
1769        Ok(Expr {
1770            kind: first.kind,
1771            span: to_ast_span(start.merge(end)),
1772        })
1773    }
1774
1775    fn parse_array_lit(&mut self) -> PResult<Expr> {
1776        let start = self.expect(TokenKind::LBracket)?.span;
1777        let mut elems = Vec::new();
1778        if !self.at(TokenKind::RBracket) {
1779            loop {
1780                elems.push(self.parse_expr()?);
1781                if self.eat(TokenKind::Comma).is_none() {
1782                    break;
1783                }
1784                if self.at(TokenKind::RBracket) {
1785                    break;
1786                }
1787            }
1788        }
1789        let end = self.expect(TokenKind::RBracket)?.span;
1790        Ok(Expr {
1791            kind: ExprKind::ArrayLit(elems),
1792            span: to_ast_span(start.merge(end)),
1793        })
1794    }
1795
1796    /// Parse a tensor literal: `[| 1.0, 2.0; 3.0, 4.0 |]`
1797    ///
1798    /// Grammar: `[|` row (`;` row)* `|]`
1799    /// where row = expr (`,` expr)*
1800    ///
1801    /// A 1-D tensor is a single row: `[| 1.0, 2.0, 3.0 |]`
1802    /// A 2-D tensor uses `;` as row separator: `[| 1, 2; 3, 4 |]` (2×2)
1803    fn parse_tensor_lit(&mut self) -> PResult<Expr> {
1804        let start = self.expect(TokenKind::LBracketPipe)?.span;
1805        let mut rows: Vec<Vec<Expr>> = Vec::new();
1806
1807        if !self.at(TokenKind::PipeRBracket) {
1808            loop {
1809                // Parse one row: comma-separated expressions
1810                let mut row = Vec::new();
1811                loop {
1812                    row.push(self.parse_expr()?);
1813                    if self.eat(TokenKind::Comma).is_none() {
1814                        break;
1815                    }
1816                    // Trailing comma before `;` or `|]`
1817                    if self.at(TokenKind::Semicolon) || self.at(TokenKind::PipeRBracket) {
1818                        break;
1819                    }
1820                }
1821                rows.push(row);
1822                // `;` separates rows
1823                if self.eat(TokenKind::Semicolon).is_none() {
1824                    break;
1825                }
1826                // Trailing `;` before `|]`
1827                if self.at(TokenKind::PipeRBracket) {
1828                    break;
1829                }
1830            }
1831        }
1832
1833        let end = self.expect(TokenKind::PipeRBracket)?.span;
1834        Ok(Expr {
1835            kind: ExprKind::TensorLit { rows },
1836            span: to_ast_span(start.merge(end)),
1837        })
1838    }
1839
1840    /// Parse a zero-parameter lambda: `|| body`.
1841    /// The lexer greedily tokenizes `||` as `PipePipe`, so we handle it here.
1842    fn parse_lambda_no_params(&mut self) -> PResult<Expr> {
1843        let start = to_ast_span(self.expect(TokenKind::PipePipe)?.span);
1844        let body = self.parse_expr()?;
1845        let span = merge_spans(start, body.span);
1846        Ok(Expr {
1847            kind: ExprKind::Lambda {
1848                params: vec![],
1849                body: Box::new(body),
1850            },
1851            span,
1852        })
1853    }
1854
1855    /// Parse a lambda expression: `|params| body` or `|params| { block }`.
1856    fn parse_lambda(&mut self) -> PResult<Expr> {
1857        let start = to_ast_span(self.expect(TokenKind::Pipe)?.span);
1858
1859        // Disable pipe-in-type so that `|x: i64|` doesn't try to parse
1860        // the closing `|` as a union type operator.
1861        let prev_pipe = self.allow_pipe_in_type;
1862        self.allow_pipe_in_type = false;
1863
1864        // Parse parameter list (comma-separated, like fn params but delimited by `|`)
1865        let mut params = Vec::new();
1866        if !self.at(TokenKind::Pipe) {
1867            loop {
1868                let param = self.parse_param()?;
1869                params.push(param);
1870                if self.eat(TokenKind::Comma).is_none() {
1871                    break;
1872                }
1873                // Allow trailing comma before closing `|`
1874                if self.at(TokenKind::Pipe) {
1875                    break;
1876                }
1877            }
1878        }
1879
1880        self.allow_pipe_in_type = prev_pipe;
1881        self.expect(TokenKind::Pipe)?;
1882
1883        // Parse the body expression
1884        let body = self.parse_expr()?;
1885        let span = merge_spans(start, body.span);
1886
1887        Ok(Expr {
1888            kind: ExprKind::Lambda {
1889                params,
1890                body: Box::new(body),
1891            },
1892            span,
1893        })
1894    }
1895
1896    /// Parse a match expression: `match expr { pat => body, ... }`
1897    fn parse_match_expr(&mut self) -> PResult<Expr> {
1898        let start = self.expect(TokenKind::Match)?.span;
1899        // Parse the scrutinee (disable struct lit to avoid `match x { ... }`
1900        // being parsed as `match (x { struct lit }) { ... }`)
1901        let old_allow = self.allow_struct_lit;
1902        self.allow_struct_lit = false;
1903        let scrutinee = self.parse_expr()?;
1904        self.allow_struct_lit = old_allow;
1905
1906        self.expect(TokenKind::LBrace)?;
1907        let mut arms = Vec::new();
1908        while !self.at(TokenKind::RBrace) && !self.at_eof() {
1909            let arm = self.parse_match_arm()?;
1910            arms.push(arm);
1911            // Arms are separated by commas (optional trailing comma)
1912            if self.eat(TokenKind::Comma).is_none() {
1913                break;
1914            }
1915        }
1916        let end = self.expect(TokenKind::RBrace)?.span;
1917        Ok(Expr {
1918            kind: ExprKind::Match {
1919                scrutinee: Box::new(scrutinee),
1920                arms,
1921            },
1922            span: to_ast_span(start.merge(end)),
1923        })
1924    }
1925
1926    /// Parse a single match arm: `pattern => body`
1927    fn parse_match_arm(&mut self) -> PResult<MatchArm> {
1928        let pattern = self.parse_pattern()?;
1929        self.expect(TokenKind::FatArrow)?;
1930        let body = self.parse_expr()?;
1931        let span = merge_spans(pattern.span, body.span);
1932        Ok(MatchArm {
1933            pattern,
1934            body,
1935            span,
1936        })
1937    }
1938
1939    /// Parse a pattern.
1940    fn parse_pattern(&mut self) -> PResult<Pattern> {
1941        match self.peek_kind() {
1942            // Wildcard `_`
1943            TokenKind::Underscore => {
1944                let tok = self.advance().clone();
1945                Ok(Pattern {
1946                    kind: PatternKind::Wildcard,
1947                    span: to_ast_span(tok.span),
1948                })
1949            }
1950            // Bool literals
1951            TokenKind::True => {
1952                let tok = self.advance().clone();
1953                Ok(Pattern {
1954                    kind: PatternKind::LitBool(true),
1955                    span: to_ast_span(tok.span),
1956                })
1957            }
1958            TokenKind::False => {
1959                let tok = self.advance().clone();
1960                Ok(Pattern {
1961                    kind: PatternKind::LitBool(false),
1962                    span: to_ast_span(tok.span),
1963                })
1964            }
1965            // String literal
1966            TokenKind::StringLit => {
1967                let tok = self.advance().clone();
1968                Ok(Pattern {
1969                    kind: PatternKind::LitString(tok.text.clone()),
1970                    span: to_ast_span(tok.span),
1971                })
1972            }
1973            // Integer literal (possibly negative)
1974            TokenKind::IntLit => {
1975                let tok = self.advance().clone();
1976                Ok(Pattern {
1977                    kind: PatternKind::LitInt(tok.int_value()),
1978                    span: to_ast_span(tok.span),
1979                })
1980            }
1981            // Float literal
1982            TokenKind::FloatLit => {
1983                let tok = self.advance().clone();
1984                Ok(Pattern {
1985                    kind: PatternKind::LitFloat(tok.float_value()),
1986                    span: to_ast_span(tok.span),
1987                })
1988            }
1989            // Negative literal: `-42` or `-3.14`
1990            TokenKind::Minus => {
1991                let start = self.advance().span;
1992                match self.peek_kind() {
1993                    TokenKind::IntLit => {
1994                        let tok = self.advance().clone();
1995                        Ok(Pattern {
1996                            kind: PatternKind::LitInt(-tok.int_value()),
1997                            span: to_ast_span(start.merge(tok.span)),
1998                        })
1999                    }
2000                    TokenKind::FloatLit => {
2001                        let tok = self.advance().clone();
2002                        Ok(Pattern {
2003                            kind: PatternKind::LitFloat(-tok.float_value()),
2004                            span: to_ast_span(start.merge(tok.span)),
2005                        })
2006                    }
2007                    _ => {
2008                        let tok = self.peek().clone();
2009                        self.error(
2010                            format!(
2011                                "expected numeric literal after `-` in pattern, found {}",
2012                                tok.kind.describe()
2013                            ),
2014                            tok.span,
2015                        );
2016                        Err(())
2017                    }
2018                }
2019            }
2020            // Tuple pattern: `(a, b, c)`
2021            TokenKind::LParen => {
2022                let start = self.advance().span;
2023                let mut pats = Vec::new();
2024                if !self.at(TokenKind::RParen) {
2025                    loop {
2026                        pats.push(self.parse_pattern()?);
2027                        if self.eat(TokenKind::Comma).is_none() {
2028                            break;
2029                        }
2030                        if self.at(TokenKind::RParen) {
2031                            break;
2032                        }
2033                    }
2034                }
2035                let end = self.expect(TokenKind::RParen)?.span;
2036                Ok(Pattern {
2037                    kind: PatternKind::Tuple(pats),
2038                    span: to_ast_span(start.merge(end)),
2039                })
2040            }
2041            // Identifier: could be a binding, struct destructuring, or variant pattern
2042            TokenKind::Ident => {
2043                let ident = self.parse_ident()?;
2044                if self.at(TokenKind::LBrace) {
2045                    // Struct destructuring: `Name { field, field: pat, ... }`
2046                    self.advance(); // `{`
2047                    let mut fields = Vec::new();
2048                    while !self.at(TokenKind::RBrace) && !self.at_eof() {
2049                        let field = self.parse_pattern_field()?;
2050                        fields.push(field);
2051                        if self.eat(TokenKind::Comma).is_none() {
2052                            break;
2053                        }
2054                    }
2055                    let end = self.expect(TokenKind::RBrace)?.span;
2056                    Ok(Pattern {
2057                        kind: PatternKind::Struct {
2058                            name: ident.clone(),
2059                            fields,
2060                        },
2061                        span: merge_spans(ident.span, to_ast_span(end)),
2062                    })
2063                } else if self.at(TokenKind::LParen) {
2064                    // Variant pattern: `Some(x)`, `Ok(val)`, `Err(e)`
2065                    self.advance(); // `(`
2066                    let mut sub_pats = Vec::new();
2067                    if !self.at(TokenKind::RParen) {
2068                        loop {
2069                            sub_pats.push(self.parse_pattern()?);
2070                            if self.eat(TokenKind::Comma).is_none() {
2071                                break;
2072                            }
2073                            if self.at(TokenKind::RParen) {
2074                                break;
2075                            }
2076                        }
2077                    }
2078                    let end = self.expect(TokenKind::RParen)?.span;
2079                    Ok(Pattern {
2080                        kind: PatternKind::Variant {
2081                            enum_name: None,
2082                            variant: ident.clone(),
2083                            fields: sub_pats,
2084                        },
2085                        span: merge_spans(ident.span, to_ast_span(end)),
2086                    })
2087                } else {
2088                    // Plain binding pattern
2089                    Ok(Pattern {
2090                        kind: PatternKind::Binding(ident.clone()),
2091                        span: ident.span,
2092                    })
2093                }
2094            }
2095            _ => {
2096                let tok = self.peek().clone();
2097                self.error(
2098                    format!("expected pattern, found {}", tok.kind.describe()),
2099                    tok.span,
2100                );
2101                Err(())
2102            }
2103        }
2104    }
2105
2106    /// Parse a single field in a struct pattern: `name` or `name: pattern`
2107    fn parse_pattern_field(&mut self) -> PResult<PatternField> {
2108        let name = self.parse_ident()?;
2109        let start = name.span;
2110        if self.eat(TokenKind::Colon).is_some() {
2111            let pattern = self.parse_pattern()?;
2112            let span = merge_spans(start, pattern.span);
2113            Ok(PatternField {
2114                name,
2115                pattern: Some(pattern),
2116                span,
2117            })
2118        } else {
2119            // Shorthand: `x` means `x: x`
2120            Ok(PatternField {
2121                name: name.clone(),
2122                pattern: None,
2123                span: start,
2124            })
2125        }
2126    }
2127
2128    /// Parse a postfix operation applied to `lhs`.
2129    fn parse_postfix(&mut self, lhs: Expr) -> PResult<Expr> {
2130        match self.peek_kind() {
2131            TokenKind::Dot => {
2132                self.advance(); // `.`
2133                let name = self.parse_ident()?;
2134                let span = merge_spans(lhs.span, name.span);
2135                Ok(Expr {
2136                    kind: ExprKind::Field {
2137                        object: Box::new(lhs),
2138                        name,
2139                    },
2140                    span,
2141                })
2142            }
2143            TokenKind::LParen => {
2144                self.advance(); // `(`
2145                let args = self.parse_call_args()?;
2146                let end = self.expect(TokenKind::RParen)?.span;
2147                let span = merge_spans(lhs.span, to_ast_span(end));
2148                Ok(Expr {
2149                    kind: ExprKind::Call {
2150                        callee: Box::new(lhs),
2151                        args,
2152                    },
2153                    span,
2154                })
2155            }
2156            TokenKind::LBracket => {
2157                self.advance(); // `[`
2158                let mut indices = Vec::new();
2159                if !self.at(TokenKind::RBracket) {
2160                    loop {
2161                        indices.push(self.parse_expr()?);
2162                        if self.eat(TokenKind::Comma).is_none() {
2163                            break;
2164                        }
2165                        if self.at(TokenKind::RBracket) {
2166                            break;
2167                        }
2168                    }
2169                }
2170                let end = self.expect(TokenKind::RBracket)?.span;
2171                let span = merge_spans(lhs.span, to_ast_span(end));
2172                if indices.len() == 1 {
2173                    Ok(Expr {
2174                        kind: ExprKind::Index {
2175                            object: Box::new(lhs),
2176                            index: Box::new(indices.into_iter().next().unwrap()),
2177                        },
2178                        span,
2179                    })
2180                } else {
2181                    Ok(Expr {
2182                        kind: ExprKind::MultiIndex {
2183                            object: Box::new(lhs),
2184                            indices,
2185                        },
2186                        span,
2187                    })
2188                }
2189            }
2190            TokenKind::LBrace => {
2191                // Struct literal: `Name { f: v, ... }`
2192                // Only reached when lhs is ExprKind::Ident.
2193                let name = match lhs.kind {
2194                    ExprKind::Ident(ref id) => id.clone(),
2195                    _ => unreachable!("struct literal postfix should only apply to identifiers"),
2196                };
2197                self.advance(); // `{`
2198                let mut fields = Vec::new();
2199                while !self.at(TokenKind::RBrace) && !self.at_eof() {
2200                    let field_name = self.parse_ident()?;
2201                    self.expect(TokenKind::Colon)?;
2202                    let value = self.parse_expr()?;
2203                    let field_span = merge_spans(field_name.span, value.span);
2204                    fields.push(FieldInit {
2205                        name: field_name,
2206                        value,
2207                        span: field_span,
2208                    });
2209                    if self.eat(TokenKind::Comma).is_none() {
2210                        break;
2211                    }
2212                }
2213                let end = self.expect(TokenKind::RBrace)?.span;
2214                let span = merge_spans(lhs.span, to_ast_span(end));
2215                Ok(Expr {
2216                    kind: ExprKind::StructLit { name, fields },
2217                    span,
2218                })
2219            }
2220            TokenKind::Question => {
2221                let tok = self.advance().clone();
2222                let span = merge_spans(lhs.span, to_ast_span(tok.span));
2223                Ok(Expr {
2224                    kind: ExprKind::Try(Box::new(lhs)),
2225                    span,
2226                })
2227            }
2228            _ => unreachable!("parse_postfix called with non-postfix token"),
2229        }
2230    }
2231
2232    fn parse_call_args(&mut self) -> PResult<Vec<CallArg>> {
2233        let mut args = Vec::new();
2234        if self.at(TokenKind::RParen) {
2235            return Ok(args);
2236        }
2237        loop {
2238            let arg = self.parse_call_arg()?;
2239            args.push(arg);
2240            if self.eat(TokenKind::Comma).is_none() {
2241                break;
2242            }
2243            if self.at(TokenKind::RParen) {
2244                break;
2245            }
2246        }
2247        Ok(args)
2248    }
2249
2250    fn parse_call_arg(&mut self) -> PResult<CallArg> {
2251        // Check for named argument: `name: expr`.
2252        // We look ahead: if Ident followed by Colon, it is a named arg.
2253        let start_span = to_ast_span(self.current_span());
2254        if self.at(TokenKind::Ident) && self.peek_ahead(1) == TokenKind::Colon {
2255            let name = self.parse_ident()?;
2256            self.advance(); // colon
2257            let value = self.parse_expr()?;
2258            let span = merge_spans(name.span, value.span);
2259            return Ok(CallArg {
2260                name: Some(name),
2261                value,
2262                span,
2263            });
2264        }
2265        let value = self.parse_expr()?;
2266        let span = merge_spans(start_span, value.span);
2267        Ok(CallArg {
2268            name: None,
2269            value,
2270            span,
2271        })
2272    }
2273
2274    /// Parse an infix operator and its right-hand operand.
2275    fn parse_infix(&mut self, lhs: Expr, r_bp: u8) -> PResult<Expr> {
2276        let op_tok = self.advance().clone();
2277
2278        match op_tok.kind {
2279            TokenKind::Eq => {
2280                let rhs = self.parse_expr_bp(r_bp)?;
2281                let span = merge_spans(lhs.span, rhs.span);
2282                Ok(Expr {
2283                    kind: ExprKind::Assign {
2284                        target: Box::new(lhs),
2285                        value: Box::new(rhs),
2286                    },
2287                    span,
2288                })
2289            }
2290            // Compound assignment: +=, -=, *=, /=, %=, **=, &=, |=, ^=, <<=, >>=
2291            TokenKind::PlusEq | TokenKind::MinusEq | TokenKind::StarEq
2292            | TokenKind::SlashEq | TokenKind::PercentEq | TokenKind::StarStarEq
2293            | TokenKind::AmpEq | TokenKind::PipeEq | TokenKind::CaretEq
2294            | TokenKind::LtLtEq | TokenKind::GtGtEq => {
2295                let op = compound_assign_to_binop(op_tok.kind);
2296                let rhs = self.parse_expr_bp(r_bp)?;
2297                let span = merge_spans(lhs.span, rhs.span);
2298                Ok(Expr {
2299                    kind: ExprKind::CompoundAssign {
2300                        op,
2301                        target: Box::new(lhs),
2302                        value: Box::new(rhs),
2303                    },
2304                    span,
2305                })
2306            }
2307            TokenKind::PipeGt => {
2308                let rhs = self.parse_expr_bp(r_bp)?;
2309                let span = merge_spans(lhs.span, rhs.span);
2310                Ok(Expr {
2311                    kind: ExprKind::Pipe {
2312                        left: Box::new(lhs),
2313                        right: Box::new(rhs),
2314                    },
2315                    span,
2316                })
2317            }
2318            _ => {
2319                let op = token_to_binop(op_tok.kind);
2320                let rhs = self.parse_expr_bp(r_bp)?;
2321                let span = merge_spans(lhs.span, rhs.span);
2322                Ok(Expr {
2323                    kind: ExprKind::Binary {
2324                        op,
2325                        left: Box::new(lhs),
2326                        right: Box::new(rhs),
2327                    },
2328                    span,
2329                })
2330            }
2331        }
2332    }
2333
2334    // ── Identifier helper ──────────────────────────────────────────
2335
2336    fn parse_ident(&mut self) -> PResult<Ident> {
2337        let tok = self.expect(TokenKind::Ident)?;
2338        Ok(Ident {
2339            name: tok.text.clone(),
2340            span: to_ast_span(tok.span),
2341        })
2342    }
2343}
2344
2345// ── Token → BinOp mapping ──────────────────────────────────────────────
2346
2347fn token_to_binop(kind: TokenKind) -> BinOp {
2348    match kind {
2349        TokenKind::Plus => BinOp::Add,
2350        TokenKind::Minus => BinOp::Sub,
2351        TokenKind::Star => BinOp::Mul,
2352        TokenKind::Slash => BinOp::Div,
2353        TokenKind::Percent => BinOp::Mod,
2354        TokenKind::StarStar => BinOp::Pow,
2355        TokenKind::EqEq => BinOp::Eq,
2356        TokenKind::BangEq => BinOp::Ne,
2357        TokenKind::Lt => BinOp::Lt,
2358        TokenKind::Gt => BinOp::Gt,
2359        TokenKind::LtEq => BinOp::Le,
2360        TokenKind::GtEq => BinOp::Ge,
2361        TokenKind::AmpAmp => BinOp::And,
2362        TokenKind::PipePipe => BinOp::Or,
2363        TokenKind::TildeEq => BinOp::Match,
2364        TokenKind::BangTilde => BinOp::NotMatch,
2365        // Bitwise
2366        TokenKind::Amp => BinOp::BitAnd,
2367        TokenKind::Pipe => BinOp::BitOr,
2368        TokenKind::Caret => BinOp::BitXor,
2369        TokenKind::LtLt => BinOp::Shl,
2370        TokenKind::GtGt => BinOp::Shr,
2371        _ => unreachable!("token_to_binop called with non-operator token {:?}", kind),
2372    }
2373}
2374
2375fn compound_assign_to_binop(kind: TokenKind) -> BinOp {
2376    match kind {
2377        TokenKind::PlusEq => BinOp::Add,
2378        TokenKind::MinusEq => BinOp::Sub,
2379        TokenKind::StarEq => BinOp::Mul,
2380        TokenKind::SlashEq => BinOp::Div,
2381        TokenKind::PercentEq => BinOp::Mod,
2382        TokenKind::StarStarEq => BinOp::Pow,
2383        TokenKind::AmpEq => BinOp::BitAnd,
2384        TokenKind::PipeEq => BinOp::BitOr,
2385        TokenKind::CaretEq => BinOp::BitXor,
2386        TokenKind::LtLtEq => BinOp::Shl,
2387        TokenKind::GtGtEq => BinOp::Shr,
2388        _ => unreachable!("compound_assign_to_binop called with {:?}", kind),
2389    }
2390}
2391
2392// ── Convenience: parse from source ─────────────────────────────────────
2393
2394/// Lex and parse a source string, returning the AST and combined diagnostics.
2395pub fn parse_source(source: &str) -> (Program, DiagnosticBag) {
2396    let lexer = cjc_lexer::Lexer::new(source);
2397    let (tokens, mut lex_diags) = lexer.tokenize();
2398    let parser = Parser::new(tokens);
2399    let (program, parse_diags) = parser.parse_program();
2400    // Merge diagnostics.
2401    for d in parse_diags.diagnostics {
2402        lex_diags.emit(d);
2403    }
2404    (program, lex_diags)
2405}
2406
2407// ── Tests ──────────────────────────────────────────────────────────────
2408
2409#[cfg(test)]
2410mod tests {
2411    use super::*;
2412
2413    /// Helper: lex + parse, assert no errors, return program.
2414    fn parse_ok(source: &str) -> Program {
2415        let (program, diags) = parse_source(source);
2416        if diags.has_errors() {
2417            let rendered = diags.render_all(source, "<test>");
2418            panic!("unexpected parse errors:\n{}", rendered);
2419        }
2420        program
2421    }
2422
2423    /// Helper: lex + parse, assert at least one error.
2424    fn parse_err(source: &str) -> DiagnosticBag {
2425        let (_, diags) = parse_source(source);
2426        assert!(
2427            diags.has_errors(),
2428            "expected parse error but got none for: {}",
2429            source
2430        );
2431        diags
2432    }
2433
2434    // ── Struct parsing ─────────────────────────────────────────────
2435
2436    #[test]
2437    fn test_parse_struct_simple() {
2438        let prog = parse_ok("struct Point { x: f64, y: f64 }");
2439        assert_eq!(prog.declarations.len(), 1);
2440        match &prog.declarations[0].kind {
2441            DeclKind::Struct(s) => {
2442                assert_eq!(s.name.name, "Point");
2443                assert_eq!(s.fields.len(), 2);
2444                assert_eq!(s.fields[0].name.name, "x");
2445                assert_eq!(s.fields[1].name.name, "y");
2446            }
2447            _ => panic!("expected struct"),
2448        }
2449    }
2450
2451    #[test]
2452    fn test_parse_struct_generic() {
2453        let prog = parse_ok("struct Pair<T: Clone, U> { first: T, second: U }");
2454        match &prog.declarations[0].kind {
2455            DeclKind::Struct(s) => {
2456                assert_eq!(s.type_params.len(), 2);
2457                assert_eq!(s.type_params[0].name.name, "T");
2458                assert_eq!(s.type_params[0].bounds.len(), 1);
2459                assert_eq!(s.type_params[1].name.name, "U");
2460                assert!(s.type_params[1].bounds.is_empty());
2461            }
2462            _ => panic!("expected struct"),
2463        }
2464    }
2465
2466    // ── Class parsing ──────────────────────────────────────────────
2467
2468    #[test]
2469    fn test_parse_class() {
2470        let prog = parse_ok("class Node<T> { value: T, next: Node<T> }");
2471        match &prog.declarations[0].kind {
2472            DeclKind::Class(c) => {
2473                assert_eq!(c.name.name, "Node");
2474                assert_eq!(c.type_params.len(), 1);
2475                assert_eq!(c.fields.len(), 2);
2476            }
2477            _ => panic!("expected class"),
2478        }
2479    }
2480
2481    // ── Function parsing ───────────────────────────────────────────
2482
2483    #[test]
2484    fn test_parse_fn_simple() {
2485        let prog = parse_ok("fn add(a: i64, b: i64) -> i64 { a + b }");
2486        match &prog.declarations[0].kind {
2487            DeclKind::Fn(f) => {
2488                assert_eq!(f.name.name, "add");
2489                assert_eq!(f.params.len(), 2);
2490                assert!(f.return_type.is_some());
2491                assert!(!f.is_nogc);
2492                // The body should have a tail expression.
2493                assert!(f.body.expr.is_some());
2494            }
2495            _ => panic!("expected fn"),
2496        }
2497    }
2498
2499    #[test]
2500    fn test_parse_fn_nogc() {
2501        let prog = parse_ok("nogc fn fast(x: f64) -> f64 { x }");
2502        match &prog.declarations[0].kind {
2503            DeclKind::Fn(f) => {
2504                assert!(f.is_nogc);
2505                assert_eq!(f.name.name, "fast");
2506            }
2507            _ => panic!("expected fn"),
2508        }
2509    }
2510
2511    #[test]
2512    fn test_parse_fn_no_return_type() {
2513        let prog = parse_ok("fn greet(name: String) { name }");
2514        match &prog.declarations[0].kind {
2515            DeclKind::Fn(f) => {
2516                assert!(f.return_type.is_none());
2517            }
2518            _ => panic!("expected fn"),
2519        }
2520    }
2521
2522    // ── Trait parsing ──────────────────────────────────────────────
2523
2524    #[test]
2525    fn test_parse_trait() {
2526        let prog = parse_ok(
2527            "trait Numeric: Add + Mul { fn zero() -> Self; fn one() -> Self; }",
2528        );
2529        match &prog.declarations[0].kind {
2530            DeclKind::Trait(t) => {
2531                assert_eq!(t.name.name, "Numeric");
2532                assert_eq!(t.super_traits.len(), 2);
2533                assert_eq!(t.methods.len(), 2);
2534                assert_eq!(t.methods[0].name.name, "zero");
2535            }
2536            _ => panic!("expected trait"),
2537        }
2538    }
2539
2540    // ── Impl parsing ──────────────────────────────────────────────
2541
2542    #[test]
2543    fn test_parse_impl() {
2544        let prog = parse_ok(
2545            "impl<T> Vec<T> : Iterable { fn len(self: Vec<T>) -> i64 { 0 } }",
2546        );
2547        match &prog.declarations[0].kind {
2548            DeclKind::Impl(i) => {
2549                assert_eq!(i.type_params.len(), 1);
2550                assert!(i.trait_ref.is_some());
2551                assert_eq!(i.methods.len(), 1);
2552            }
2553            _ => panic!("expected impl"),
2554        }
2555    }
2556
2557    // ── Import parsing ─────────────────────────────────────────────
2558
2559    #[test]
2560    fn test_parse_import() {
2561        let prog = parse_ok("import std.io.File as F");
2562        match &prog.declarations[0].kind {
2563            DeclKind::Import(i) => {
2564                assert_eq!(i.path.len(), 3);
2565                assert_eq!(i.path[0].name, "std");
2566                assert_eq!(i.path[1].name, "io");
2567                assert_eq!(i.path[2].name, "File");
2568                assert_eq!(i.alias.as_ref().unwrap().name, "F");
2569            }
2570            _ => panic!("expected import"),
2571        }
2572    }
2573
2574    #[test]
2575    fn test_parse_import_no_alias() {
2576        let prog = parse_ok("import math.linalg");
2577        match &prog.declarations[0].kind {
2578            DeclKind::Import(i) => {
2579                assert_eq!(i.path.len(), 2);
2580                assert!(i.alias.is_none());
2581            }
2582            _ => panic!("expected import"),
2583        }
2584    }
2585
2586    // ── Let statement ──────────────────────────────────────────────
2587
2588    #[test]
2589    fn test_parse_let() {
2590        let prog = parse_ok("let x: i64 = 42;");
2591        match &prog.declarations[0].kind {
2592            DeclKind::Let(l) => {
2593                assert_eq!(l.name.name, "x");
2594                assert!(!l.mutable);
2595                assert!(l.ty.is_some());
2596            }
2597            _ => panic!("expected let"),
2598        }
2599    }
2600
2601    #[test]
2602    fn test_parse_let_mut() {
2603        let prog = parse_ok("let mut count = 0;");
2604        match &prog.declarations[0].kind {
2605            DeclKind::Let(l) => {
2606                assert!(l.mutable);
2607                assert!(l.ty.is_none());
2608            }
2609            _ => panic!("expected let"),
2610        }
2611    }
2612
2613    // ── Expression parsing ─────────────────────────────────────────
2614
2615    #[test]
2616    fn test_parse_binary_precedence() {
2617        // `1 + 2 * 3` should parse as `1 + (2 * 3)`.
2618        let prog = parse_ok("fn main() { 1 + 2 * 3 }");
2619        match &prog.declarations[0].kind {
2620            DeclKind::Fn(f) => {
2621                let tail = f.body.expr.as_ref().unwrap();
2622                match &tail.kind {
2623                    ExprKind::Binary { op, left, right } => {
2624                        assert_eq!(*op, BinOp::Add);
2625                        // left should be 1.
2626                        assert!(matches!(left.kind, ExprKind::IntLit(1)));
2627                        // right should be 2 * 3.
2628                        match &right.kind {
2629                            ExprKind::Binary { op, .. } => assert_eq!(*op, BinOp::Mul),
2630                            _ => panic!("expected binary mul"),
2631                        }
2632                    }
2633                    _ => panic!("expected binary add"),
2634                }
2635            }
2636            _ => panic!("expected fn"),
2637        }
2638    }
2639
2640    #[test]
2641    fn test_parse_unary() {
2642        let prog = parse_ok("fn f() { -x }");
2643        match &prog.declarations[0].kind {
2644            DeclKind::Fn(f) => {
2645                let tail = f.body.expr.as_ref().unwrap();
2646                match &tail.kind {
2647                    ExprKind::Unary { op, .. } => assert_eq!(*op, UnaryOp::Neg),
2648                    _ => panic!("expected unary"),
2649                }
2650            }
2651            _ => panic!("expected fn"),
2652        }
2653    }
2654
2655    #[test]
2656    fn test_parse_call_with_named_args() {
2657        let prog = parse_ok("fn f() { create(width: 10, height: 20) }");
2658        match &prog.declarations[0].kind {
2659            DeclKind::Fn(f) => {
2660                let tail = f.body.expr.as_ref().unwrap();
2661                match &tail.kind {
2662                    ExprKind::Call { args, .. } => {
2663                        assert_eq!(args.len(), 2);
2664                        assert_eq!(args[0].name.as_ref().unwrap().name, "width");
2665                        assert_eq!(args[1].name.as_ref().unwrap().name, "height");
2666                    }
2667                    _ => panic!("expected call"),
2668                }
2669            }
2670            _ => panic!("expected fn"),
2671        }
2672    }
2673
2674    #[test]
2675    fn test_parse_field_access_and_method_call() {
2676        let prog = parse_ok("fn f() { obj.field.method(x) }");
2677        match &prog.declarations[0].kind {
2678            DeclKind::Fn(f) => {
2679                let tail = f.body.expr.as_ref().unwrap();
2680                // Should be: Call { callee: Field { object: Field { ... }, name: method }, args: [x] }
2681                match &tail.kind {
2682                    ExprKind::Call { callee, args } => {
2683                        assert_eq!(args.len(), 1);
2684                        match &callee.kind {
2685                            ExprKind::Field { name, .. } => {
2686                                assert_eq!(name.name, "method");
2687                            }
2688                            _ => panic!("expected field access"),
2689                        }
2690                    }
2691                    _ => panic!("expected call"),
2692                }
2693            }
2694            _ => panic!("expected fn"),
2695        }
2696    }
2697
2698    #[test]
2699    fn test_parse_index_and_multi_index() {
2700        let prog = parse_ok("fn f() { a[0]; b[1, 2] }");
2701        match &prog.declarations[0].kind {
2702            DeclKind::Fn(f) => {
2703                // First statement: a[0] — single index.
2704                match &f.body.stmts[0].kind {
2705                    StmtKind::Expr(e) => match &e.kind {
2706                        ExprKind::Index { .. } => {}
2707                        _ => panic!("expected index"),
2708                    },
2709                    _ => panic!("expected expr stmt"),
2710                }
2711                // Tail expression: b[1, 2] — multi-index.
2712                let tail = f.body.expr.as_ref().unwrap();
2713                match &tail.kind {
2714                    ExprKind::MultiIndex { indices, .. } => {
2715                        assert_eq!(indices.len(), 2);
2716                    }
2717                    _ => panic!("expected multi-index"),
2718                }
2719            }
2720            _ => panic!("expected fn"),
2721        }
2722    }
2723
2724    #[test]
2725    fn test_parse_pipe() {
2726        let prog = parse_ok("fn f() { data |> filter(x) |> map(y) }");
2727        match &prog.declarations[0].kind {
2728            DeclKind::Fn(f) => {
2729                let tail = f.body.expr.as_ref().unwrap();
2730                // Should be left-associative: (data |> filter(x)) |> map(y)
2731                match &tail.kind {
2732                    ExprKind::Pipe { right, .. } => {
2733                        match &right.kind {
2734                            ExprKind::Call { callee, .. } => match &callee.kind {
2735                                ExprKind::Ident(id) => assert_eq!(id.name, "map"),
2736                                _ => panic!("expected ident"),
2737                            },
2738                            _ => panic!("expected call"),
2739                        }
2740                    }
2741                    _ => panic!("expected pipe"),
2742                }
2743            }
2744            _ => panic!("expected fn"),
2745        }
2746    }
2747
2748    #[test]
2749    fn test_parse_assignment() {
2750        let prog = parse_ok("fn f() { x = 10; }");
2751        match &prog.declarations[0].kind {
2752            DeclKind::Fn(f) => match &f.body.stmts[0].kind {
2753                StmtKind::Expr(e) => match &e.kind {
2754                    ExprKind::Assign { .. } => {}
2755                    _ => panic!("expected assign"),
2756                },
2757                _ => panic!("expected expr stmt"),
2758            },
2759            _ => panic!("expected fn"),
2760        }
2761    }
2762
2763    #[test]
2764    fn test_parse_struct_literal() {
2765        let prog = parse_ok("fn f() { Point { x: 1, y: 2 } }");
2766        match &prog.declarations[0].kind {
2767            DeclKind::Fn(f) => {
2768                let tail = f.body.expr.as_ref().unwrap();
2769                match &tail.kind {
2770                    ExprKind::StructLit { name, fields } => {
2771                        assert_eq!(name.name, "Point");
2772                        assert_eq!(fields.len(), 2);
2773                    }
2774                    _ => panic!("expected struct lit"),
2775                }
2776            }
2777            _ => panic!("expected fn"),
2778        }
2779    }
2780
2781    #[test]
2782    fn test_parse_array_literal() {
2783        let prog = parse_ok("fn f() { [1, 2, 3] }");
2784        match &prog.declarations[0].kind {
2785            DeclKind::Fn(f) => {
2786                let tail = f.body.expr.as_ref().unwrap();
2787                match &tail.kind {
2788                    ExprKind::ArrayLit(elems) => assert_eq!(elems.len(), 3),
2789                    _ => panic!("expected array lit"),
2790                }
2791            }
2792            _ => panic!("expected fn"),
2793        }
2794    }
2795
2796    #[test]
2797    fn test_parse_col() {
2798        let prog = parse_ok(r#"fn f() { col("price") }"#);
2799        match &prog.declarations[0].kind {
2800            DeclKind::Fn(f) => {
2801                let tail = f.body.expr.as_ref().unwrap();
2802                match &tail.kind {
2803                    ExprKind::Col(name) => assert_eq!(name, "price"),
2804                    _ => panic!("expected col"),
2805                }
2806            }
2807            _ => panic!("expected fn"),
2808        }
2809    }
2810
2811    // ── Control flow ───────────────────────────────────────────────
2812
2813    #[test]
2814    fn test_parse_if_else_if_else() {
2815        let prog = parse_ok(
2816            "fn f() { if x { 1; } else if y { 2; } else { 3; } }",
2817        );
2818        match &prog.declarations[0].kind {
2819            DeclKind::Fn(f) => {
2820                assert_eq!(f.body.stmts.len(), 1);
2821                match &f.body.stmts[0].kind {
2822                    StmtKind::If(if_stmt) => {
2823                        assert!(if_stmt.else_branch.is_some());
2824                        match if_stmt.else_branch.as_ref().unwrap() {
2825                            ElseBranch::ElseIf(elif) => {
2826                                assert!(elif.else_branch.is_some());
2827                                match elif.else_branch.as_ref().unwrap() {
2828                                    ElseBranch::Else(_) => {}
2829                                    _ => panic!("expected else block"),
2830                                }
2831                            }
2832                            _ => panic!("expected else-if"),
2833                        }
2834                    }
2835                    _ => panic!("expected if"),
2836                }
2837            }
2838            _ => panic!("expected fn"),
2839        }
2840    }
2841
2842    #[test]
2843    fn test_parse_while() {
2844        let prog = parse_ok("fn f() { while x > 0 { x = x - 1; } }");
2845        match &prog.declarations[0].kind {
2846            DeclKind::Fn(f) => match &f.body.stmts[0].kind {
2847                StmtKind::While(w) => {
2848                    assert!(!w.body.stmts.is_empty());
2849                }
2850                _ => panic!("expected while"),
2851            },
2852            _ => panic!("expected fn"),
2853        }
2854    }
2855
2856    #[test]
2857    fn test_parse_return() {
2858        let prog = parse_ok("fn f() { return 42; }");
2859        match &prog.declarations[0].kind {
2860            DeclKind::Fn(f) => match &f.body.stmts[0].kind {
2861                StmtKind::Return(Some(e)) => {
2862                    assert!(matches!(e.kind, ExprKind::IntLit(42)));
2863                }
2864                _ => panic!("expected return"),
2865            },
2866            _ => panic!("expected fn"),
2867        }
2868    }
2869
2870    #[test]
2871    fn test_parse_nogc_block() {
2872        let prog = parse_ok("fn f() { nogc { x + y; } }");
2873        match &prog.declarations[0].kind {
2874            DeclKind::Fn(f) => match &f.body.stmts[0].kind {
2875                StmtKind::NoGcBlock(block) => {
2876                    assert_eq!(block.stmts.len(), 1);
2877                }
2878                _ => panic!("expected nogc block"),
2879            },
2880            _ => panic!("expected fn"),
2881        }
2882    }
2883
2884    // ── Error recovery ─────────────────────────────────────────────
2885
2886    #[test]
2887    fn test_error_recovery_missing_semicolon() {
2888        // Missing semicolon after let — parser should recover and parse
2889        // the next declaration.
2890        let (prog, diags) = parse_source("let x = 1\nfn f() { 0 }");
2891        assert!(diags.has_errors());
2892        // Should still have parsed the fn.
2893        assert!(prog.declarations.iter().any(|d| matches!(&d.kind, DeclKind::Fn(_))));
2894    }
2895
2896    #[test]
2897    fn test_error_recovery_unexpected_token() {
2898        let diags = parse_err("@@@ fn f() { 0 }");
2899        assert!(diags.has_errors());
2900    }
2901
2902    #[test]
2903    fn test_error_expected_expression() {
2904        let diags = parse_err("fn f() { let x = ; }");
2905        assert!(diags.has_errors());
2906    }
2907
2908    // ── Complex integration test ───────────────────────────────────
2909
2910    #[test]
2911    fn test_parse_full_program() {
2912        let source = r#"
2913            import std.math as m
2914
2915            struct Vec2 {
2916                x: f64,
2917                y: f64
2918            }
2919
2920            fn dot(a: Vec2, b: Vec2) -> f64 {
2921                a.x * b.x + a.y * b.y
2922            }
2923
2924            trait Shape {
2925                fn area(self: Self) -> f64;
2926            }
2927
2928            impl Vec2 : Shape {
2929                fn area(self: Vec2) -> f64 {
2930                    self.x * self.y
2931                }
2932            }
2933
2934            let result: f64 = dot(Vec2 { x: 1.0, y: 2.0 }, Vec2 { x: 3.0, y: 4.0 });
2935        "#;
2936        let prog = parse_ok(source);
2937        assert_eq!(prog.declarations.len(), 6);
2938    }
2939
2940    #[test]
2941    fn test_parse_pipe_chain() {
2942        let source = r#"
2943            fn pipeline(data: DataFrame) -> DataFrame {
2944                data
2945                    |> filter(col("age") > 18)
2946                    |> group_by(col("city"))
2947            }
2948        "#;
2949        // Should parse without errors. The pipe chain produces a tail
2950        // expression.
2951        let prog = parse_ok(source);
2952        match &prog.declarations[0].kind {
2953            DeclKind::Fn(f) => {
2954                assert!(f.body.expr.is_some());
2955            }
2956            _ => panic!("expected fn"),
2957        }
2958    }
2959
2960    // ── Boolean and logical operators ──────────────────────────────
2961
2962    #[test]
2963    fn test_parse_logical_operators() {
2964        let prog = parse_ok("fn f() { a && b || c }");
2965        match &prog.declarations[0].kind {
2966            DeclKind::Fn(f) => {
2967                let tail = f.body.expr.as_ref().unwrap();
2968                // `&&` binds tighter than `||`, so: (a && b) || c
2969                match &tail.kind {
2970                    ExprKind::Binary { op, .. } => assert_eq!(*op, BinOp::Or),
2971                    _ => panic!("expected binary or"),
2972                }
2973            }
2974            _ => panic!("expected fn"),
2975        }
2976    }
2977
2978    #[test]
2979    fn test_parse_comparison_chain() {
2980        let prog = parse_ok("fn f() { x == 1 && y != 2 }");
2981        match &prog.declarations[0].kind {
2982            DeclKind::Fn(f) => {
2983                let tail = f.body.expr.as_ref().unwrap();
2984                match &tail.kind {
2985                    ExprKind::Binary { op, .. } => assert_eq!(*op, BinOp::And),
2986                    _ => panic!("expected and"),
2987                }
2988            }
2989            _ => panic!("expected fn"),
2990        }
2991    }
2992}