Skip to main content

cjc_parser/
lib.rs

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