Skip to main content

lumen_compiler/compiler/
parser.rs

1//! Recursive descent parser with Pratt expression parsing for Lumen.
2
3use crate::compiler::ast::*;
4use crate::compiler::tokens::{Span, Token, TokenKind};
5use thiserror::Error;
6
7#[derive(Debug, Error, Clone)]
8pub enum ParseError {
9    #[error("unexpected token {found} at line {line}, col {col}; expected {expected}")]
10    Unexpected {
11        found: String,
12        expected: String,
13        line: usize,
14        col: usize,
15    },
16    #[error("unexpected end of input")]
17    UnexpectedEof,
18    #[error("unclosed '{bracket}' opened at line {open_line}, col {open_col}")]
19    UnclosedBracket {
20        bracket: char,
21        open_line: usize,
22        open_col: usize,
23        current_line: usize,
24        current_col: usize,
25    },
26    #[error("expected 'end' to close '{construct}' at line {open_line}, col {open_col}")]
27    MissingEnd {
28        construct: String,
29        open_line: usize,
30        open_col: usize,
31        current_line: usize,
32        current_col: usize,
33    },
34    #[error("expected type after ':' at line {line}, col {col}")]
35    MissingType { line: usize, col: usize },
36    #[error("incomplete expression at line {line}, col {col}")]
37    IncompleteExpression {
38        line: usize,
39        col: usize,
40        context: String,
41    },
42    #[error("malformed {construct} at line {line}, col {col}: {reason}")]
43    MalformedConstruct {
44        construct: String,
45        reason: String,
46        line: usize,
47        col: usize,
48    },
49}
50
51pub struct Parser {
52    tokens: Vec<Token>,
53    pos: usize,
54    bracket_depth: usize,
55    errors: Vec<ParseError>,
56}
57
58impl Parser {
59    pub fn new(tokens: Vec<Token>) -> Self {
60        Self {
61            tokens,
62            pos: 0,
63            bracket_depth: 0,
64            errors: Vec::new(),
65        }
66    }
67
68    /// Record a parse error and continue parsing
69    fn record_error(&mut self, error: ParseError) {
70        self.errors.push(error);
71    }
72
73    pub fn errors(&self) -> &[ParseError] {
74        &self.errors
75    }
76
77    /// Synchronize parser state by skipping tokens until we reach a declaration boundary
78    /// This function includes infinite loop protection by tracking position advancement
79    fn synchronize(&mut self) {
80        // Skip tokens until we reach a synchronization point:
81        // - A new declaration keyword (cell, record, enum, type, grant, import, etc.)
82        // - End of file
83        let _start_pos = self.pos;
84        let mut last_pos = self.pos;
85        let mut iterations = 0;
86        const MAX_ITERATIONS: usize = 10000; // Safety limit
87
88        while !self.at_end() && iterations < MAX_ITERATIONS {
89            // Safety check: ensure we're making progress
90            if self.pos == last_pos && iterations > 0 {
91                // Position hasn't advanced, force progress to avoid infinite loop
92                self.advance();
93            }
94            last_pos = self.pos;
95            iterations += 1;
96
97            match self.peek_kind() {
98                TokenKind::Cell
99                | TokenKind::Record
100                | TokenKind::Enum
101                | TokenKind::Type
102                | TokenKind::Grant
103                | TokenKind::Import
104                | TokenKind::Use
105                | TokenKind::Pub
106                | TokenKind::Async
107                | TokenKind::At
108                | TokenKind::Schema
109                | TokenKind::Trait
110                | TokenKind::Impl
111                | TokenKind::Const
112                | TokenKind::Macro
113                | TokenKind::Extern
114                | TokenKind::Comptime
115                | TokenKind::Eof => break,
116                TokenKind::Ident(name) => {
117                    if matches!(
118                        name.as_str(),
119                        "agent"
120                            | "effect"
121                            | "bind"
122                            | "handler"
123                            | "pipeline"
124                            | "orchestration"
125                            | "machine"
126                            | "memory"
127                            | "guardrail"
128                            | "eval"
129                            | "pattern"
130                    ) {
131                        break;
132                    }
133                    self.advance();
134                }
135                _ => {
136                    self.advance();
137                }
138            }
139        }
140    }
141    /// Synchronize within a statement block to the next valid statement boundary
142    /// Improved with better keyword detection and infinite loop protection
143    fn synchronize_stmt(&mut self) {
144        let mut last_pos = self.pos;
145        let mut iterations = 0;
146        const MAX_ITERATIONS: usize = 10000;
147
148        while !self.at_end() && iterations < MAX_ITERATIONS {
149            // Safety check: ensure we're making progress
150            if self.pos == last_pos && iterations > 0 {
151                self.advance();
152            }
153            last_pos = self.pos;
154            iterations += 1;
155
156            match self.peek_kind() {
157                TokenKind::Newline => {
158                    self.advance();
159                    // After newline, check if next token is a statement keyword
160                    if self.is_stmt_keyword() {
161                        break;
162                    }
163                }
164                TokenKind::End | TokenKind::Else | TokenKind::Dedent | TokenKind::Eof => break,
165                // Statement keywords - new statement starts here
166                TokenKind::Let
167                | TokenKind::If
168                | TokenKind::For
169                | TokenKind::While
170                | TokenKind::Loop
171                | TokenKind::Match
172                | TokenKind::Return
173                | TokenKind::Halt
174                | TokenKind::Break
175                | TokenKind::Continue
176                | TokenKind::Emit => break,
177                // Top-level declaration keywords
178                TokenKind::Cell
179                | TokenKind::Record
180                | TokenKind::Enum
181                | TokenKind::Type
182                | TokenKind::Grant
183                | TokenKind::Import
184                | TokenKind::Use
185                | TokenKind::Pub
186                | TokenKind::Schema
187                | TokenKind::Fn
188                | TokenKind::Mod
189                | TokenKind::Trait
190                | TokenKind::Impl => break,
191                _ => {
192                    self.advance();
193                }
194            }
195        }
196    }
197
198    /// Check if current token is a statement keyword
199    fn is_stmt_keyword(&self) -> bool {
200        matches!(
201            self.peek_kind(),
202            TokenKind::Let
203                | TokenKind::If
204                | TokenKind::For
205                | TokenKind::While
206                | TokenKind::Loop
207                | TokenKind::Match
208                | TokenKind::Return
209                | TokenKind::Halt
210                | TokenKind::Break
211                | TokenKind::Continue
212                | TokenKind::Emit
213        )
214    }
215
216    fn current(&self) -> &Token {
217        self.tokens
218            .get(self.pos)
219            .unwrap_or_else(|| self.tokens.last().unwrap())
220    }
221
222    fn peek_kind(&self) -> &TokenKind {
223        &self.current().kind
224    }
225
226    fn advance(&mut self) -> &Token {
227        let tok = &self.tokens[self.pos.min(self.tokens.len() - 1)];
228        if self.pos < self.tokens.len() {
229            self.pos += 1;
230        }
231        tok
232    }
233
234    fn expect(&mut self, kind: &TokenKind) -> Result<Token, ParseError> {
235        let tok = self.current().clone();
236        if std::mem::discriminant(&tok.kind) == std::mem::discriminant(kind) {
237            self.advance();
238            Ok(tok)
239        } else {
240            Err(ParseError::Unexpected {
241                found: format!("{}", tok.kind),
242                expected: format!("{}", kind),
243                line: tok.span.line,
244                col: tok.span.col,
245            })
246        }
247    }
248
249    fn skip_newlines(&mut self) {
250        if self.bracket_depth > 0 {
251            self.skip_whitespace_tokens();
252        } else {
253            while matches!(self.peek_kind(), TokenKind::Newline) {
254                self.advance();
255            }
256        }
257    }
258
259    /// Skip newlines, indents, and dedents — used inside bracketed contexts
260    fn skip_whitespace_tokens(&mut self) {
261        while matches!(
262            self.peek_kind(),
263            TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent
264        ) {
265            self.advance();
266        }
267    }
268
269    fn at_end(&self) -> bool {
270        matches!(self.peek_kind(), TokenKind::Eof)
271    }
272
273    fn peek_n_kind(&self, n: usize) -> Option<&TokenKind> {
274        self.tokens.get(self.pos + n).map(|t| &t.kind)
275    }
276
277    /// Push an opening bracket onto the stack for error tracking
278    fn looks_like_named_field(&self) -> bool {
279        matches!(self.peek_kind(), TokenKind::Ident(_))
280            && matches!(self.peek_n_kind(1), Some(TokenKind::Colon))
281    }
282
283    fn token_can_be_named_arg_key(&self) -> bool {
284        matches!(
285            self.peek_kind(),
286            TokenKind::Ident(_)
287                | TokenKind::Role
288                | TokenKind::Schema
289                | TokenKind::Tool
290                | TokenKind::Type
291                | TokenKind::From
292                | TokenKind::With
293                | TokenKind::Result
294                | TokenKind::List
295                | TokenKind::Map
296                | TokenKind::Set
297                | TokenKind::Tuple
298        )
299    }
300
301    fn is_identifier_like(kind: &TokenKind) -> bool {
302        matches!(
303            kind,
304            TokenKind::Ident(_)
305                | TokenKind::SelfKw
306                | TokenKind::Result
307                | TokenKind::Cell
308                | TokenKind::String_
309                | TokenKind::Int_
310                | TokenKind::Float_
311                | TokenKind::Bool
312                | TokenKind::Bytes
313                | TokenKind::Json
314                | TokenKind::Type
315                | TokenKind::List
316                | TokenKind::Map
317                | TokenKind::Set
318                | TokenKind::Tuple
319                | TokenKind::Schema
320                | TokenKind::Ok_
321                | TokenKind::Err_
322                | TokenKind::Tool
323                | TokenKind::Role
324                | TokenKind::Union
325                | TokenKind::From
326                | TokenKind::With
327                | TokenKind::Where
328                | TokenKind::When
329                | TokenKind::Try
330                | TokenKind::Step
331                | TokenKind::Comptime
332                | TokenKind::Macro
333                | TokenKind::Extern
334                | TokenKind::Async
335                | TokenKind::Loop
336                | TokenKind::If
337                | TokenKind::Match
338                | TokenKind::End
339        )
340    }
341
342    fn consumes_section_colon_block(&self) -> bool {
343        let mut i = self.pos;
344        if !matches!(
345            self.tokens.get(i).map(|t| &t.kind),
346            Some(TokenKind::Ident(_))
347                | Some(TokenKind::Role)
348                | Some(TokenKind::Tool)
349                | Some(TokenKind::With)
350                | Some(TokenKind::From)
351                | Some(TokenKind::Type)
352                | Some(TokenKind::Result)
353                | Some(TokenKind::List)
354                | Some(TokenKind::Map)
355                | Some(TokenKind::Set)
356                | Some(TokenKind::Tuple)
357        ) {
358            return false;
359        }
360        i += 1;
361
362        if matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::LParen)) {
363            let mut depth = 0usize;
364            while let Some(kind) = self.tokens.get(i).map(|t| &t.kind) {
365                match kind {
366                    TokenKind::LParen => depth += 1,
367                    TokenKind::RParen => {
368                        depth -= 1;
369                        if depth == 0 {
370                            i += 1;
371                            break;
372                        }
373                    }
374                    TokenKind::Eof => return false,
375                    _ => {}
376                }
377                i += 1;
378            }
379        }
380
381        if !matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Colon)) {
382            return false;
383        }
384        i += 1;
385        while matches!(
386            self.tokens.get(i).map(|t| &t.kind),
387            Some(TokenKind::Newline)
388        ) {
389            i += 1;
390        }
391        matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Indent))
392    }
393
394    // ── Top-level parsing ──
395
396    pub fn parse_program(&mut self, directives: Vec<Directive>) -> Result<Program, ParseError> {
397        let span_start = self.current().span;
398        let mut items = Vec::new();
399        let mut top_level_stmts = Vec::new();
400        self.skip_newlines();
401        while !self.at_end() {
402            self.skip_newlines();
403            if self.at_end() {
404                break;
405            }
406            if matches!(self.peek_kind(), TokenKind::Indent | TokenKind::Dedent) {
407                self.advance();
408                continue;
409            }
410            if matches!(self.peek_kind(), TokenKind::End) {
411                self.advance();
412                continue;
413            }
414            if self.is_top_level_stmt_start() {
415                match self.parse_stmt() {
416                    Ok(stmt) => top_level_stmts.push(stmt),
417                    Err(err) => {
418                        self.record_error(err);
419                        self.synchronize();
420                    }
421                }
422            } else {
423                match self.parse_item() {
424                    Ok(item) => items.push(item),
425                    Err(err) => {
426                        self.record_error(err);
427                        self.synchronize();
428                    }
429                }
430            }
431            self.skip_newlines();
432        }
433        if !top_level_stmts.is_empty() {
434            let has_main = items
435                .iter()
436                .any(|item| matches!(item, Item::Cell(c) if c.name == "main"));
437            let synthetic_name = if has_main {
438                "__script_main".to_string()
439            } else {
440                "main".to_string()
441            };
442            let end_span = top_level_stmts
443                .last()
444                .map(|s| s.span())
445                .unwrap_or(span_start);
446            items.push(Item::Cell(CellDef {
447                name: synthetic_name,
448                generic_params: vec![],
449                params: vec![],
450                return_type: None,
451                effects: vec![],
452                body: top_level_stmts,
453                is_pub: false,
454                is_async: false,
455                where_clauses: vec![],
456                span: span_start.merge(end_span),
457            }));
458        }
459        let span = if items.is_empty() {
460            span_start
461        } else {
462            span_start.merge(items.last().unwrap().span())
463        };
464        Ok(Program {
465            directives,
466            items,
467            span,
468        })
469    }
470
471    fn is_top_level_stmt_start(&self) -> bool {
472        match self.peek_kind() {
473            TokenKind::Let
474            | TokenKind::If
475            | TokenKind::For
476            | TokenKind::Match
477            | TokenKind::Return
478            | TokenKind::Halt
479            | TokenKind::While
480            | TokenKind::Loop
481            | TokenKind::Break
482            | TokenKind::Continue
483            | TokenKind::Emit
484            | TokenKind::Role
485            | TokenKind::LParen
486            | TokenKind::LBracket
487            | TokenKind::LBrace
488            | TokenKind::IntLit(_)
489            | TokenKind::FloatLit(_)
490            | TokenKind::StringLit(_)
491            | TokenKind::StringInterpLit(_)
492            | TokenKind::RawStringLit(_)
493            | TokenKind::BytesLit(_)
494            | TokenKind::BoolLit(_)
495            | TokenKind::Null
496            | TokenKind::NullLit
497            | TokenKind::Minus
498            | TokenKind::Not
499            | TokenKind::Tilde
500            | TokenKind::Fn
501            | TokenKind::With
502            | TokenKind::SelfKw => true,
503            TokenKind::Ident(name) => !matches!(
504                name.as_str(),
505                "agent"
506                    | "effect"
507                    | "bind"
508                    | "handler"
509                    | "pipeline"
510                    | "orchestration"
511                    | "machine"
512                    | "memory"
513                    | "guardrail"
514                    | "eval"
515                    | "pattern"
516            ),
517            _ => false,
518        }
519    }
520
521    fn parse_item(&mut self) -> Result<Item, ParseError> {
522        // Handle `pub` modifier
523        let is_pub = matches!(self.peek_kind(), TokenKind::Pub);
524        if is_pub {
525            self.advance();
526            self.skip_newlines();
527        }
528
529        // Handle `async` modifier for cells
530        let is_async = matches!(self.peek_kind(), TokenKind::Async);
531        if is_async {
532            self.advance();
533            self.skip_newlines();
534        }
535
536        match self.peek_kind() {
537            TokenKind::Record => {
538                let mut r = self.parse_record()?;
539                r.is_pub = is_pub;
540                Ok(Item::Record(r))
541            }
542            TokenKind::Enum => {
543                let mut e = self.parse_enum()?;
544                e.is_pub = is_pub;
545                Ok(Item::Enum(e))
546            }
547            TokenKind::Cell => {
548                let mut c = self.parse_cell(true)?;
549                c.is_pub = is_pub;
550                c.is_async = is_async;
551                Ok(Item::Cell(c))
552            }
553            TokenKind::At => Ok(Item::Addon(self.parse_attribute_decl()?)),
554            TokenKind::Use => Ok(Item::UseTool(self.parse_use_tool()?)),
555            TokenKind::Grant => Ok(Item::Grant(self.parse_grant()?)),
556            TokenKind::Type => Ok(Item::TypeAlias(self.parse_type_alias(is_pub)?)),
557            TokenKind::Trait => Ok(Item::Trait(self.parse_trait_def(is_pub)?)),
558            TokenKind::Impl => Ok(Item::Impl(self.parse_impl_def()?)),
559            TokenKind::Import => Ok(Item::Import(self.parse_import(is_pub)?)),
560            TokenKind::Const => Ok(Item::ConstDecl(self.parse_const_decl()?)),
561            TokenKind::Macro => Ok(Item::MacroDecl(self.parse_macro_decl()?)),
562            TokenKind::Schema => {
563                let start = self.current().span;
564                self.advance();
565                self.consume_block_until_end();
566                Ok(Item::Addon(AddonDecl {
567                    kind: "schema".into(),
568                    name: None,
569                    span: start.merge(self.current().span),
570                }))
571            }
572            TokenKind::Extern | TokenKind::Comptime => {
573                let start = self.current().span;
574                let kind = format!("{}", self.peek_kind());
575                self.advance();
576                self.consume_rest_of_line();
577                Ok(Item::Addon(AddonDecl {
578                    kind,
579                    name: None,
580                    span: start.merge(self.current().span),
581                }))
582            }
583            TokenKind::Ident(name) => match name.as_str() {
584                "agent" => Ok(Item::Agent(self.parse_agent_decl()?)),
585                "effect" => Ok(Item::Effect(self.parse_effect_decl()?)),
586                "handler" => Ok(Item::Handler(self.parse_handler_decl()?)),
587                "bind" => {
588                    if matches!(self.peek_n_kind(1), Some(TokenKind::Ident(s)) if s == "effect") {
589                        Ok(Item::EffectBind(self.parse_effect_bind_decl()?))
590                    } else {
591                        Ok(Item::Addon(self.parse_addon_decl()?))
592                    }
593                }
594                "pipeline" | "orchestration" | "machine" | "memory" | "guardrail" | "eval"
595                | "pattern" => Ok(Item::Process(self.parse_process_decl()?)),
596                _ => {
597                    eprintln!("DEBUG: Ident fallback for {}", name);
598                    let tok = self.current().clone();
599                    Err(ParseError::Unexpected {
600                        found: name.clone(),
601                        expected: "top-level declaration".into(),
602                        line: tok.span.line,
603                        col: tok.span.col,
604                    })
605                }
606            },
607            _ => {
608                let kind = format!("{}", self.peek_kind());
609                eprintln!("DEBUG: General fallback for {}", kind);
610                let tok = self.current().clone();
611                Err(ParseError::Unexpected {
612                    found: kind,
613                    expected: "top-level declaration".into(),
614                    line: tok.span.line,
615                    col: tok.span.col,
616                })
617            }
618        }
619    }
620
621    // ── Record ──
622
623    fn parse_record(&mut self) -> Result<RecordDef, ParseError> {
624        let start = self.expect(&TokenKind::Record)?.span;
625        let name = self.expect_ident()?;
626        let generic_params = self.parse_optional_generic_params()?;
627        self.skip_newlines();
628        let mut fields = Vec::new();
629        // Fields can be indent-based or just listed until 'end'
630        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
631        if has_indent {
632            self.advance();
633        }
634        self.skip_newlines();
635        while !matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
636            self.skip_newlines();
637            if matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
638                break;
639            }
640            if matches!(self.peek_kind(), TokenKind::Indent | TokenKind::Dedent) {
641                self.advance();
642                continue;
643            }
644            if matches!(self.peek_kind(), TokenKind::At) {
645                let _ = self.parse_attribute_decl()?;
646            } else if self.looks_like_named_field() {
647                fields.push(self.parse_field()?);
648            } else if matches!(self.peek_kind(), TokenKind::Ident(s) if s == "migrate") {
649                self.advance();
650                self.consume_block_until_end();
651            } else {
652                self.consume_rest_of_line();
653            }
654            self.skip_newlines();
655        }
656        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
657            self.advance();
658        }
659        while matches!(
660            self.peek_kind(),
661            TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent
662        ) {
663            self.advance();
664        }
665        let end_span = self.expect(&TokenKind::End)?.span;
666        Ok(RecordDef {
667            name,
668            generic_params,
669            fields,
670            is_pub: false,
671            span: start.merge(end_span),
672        })
673    }
674
675    fn parse_field(&mut self) -> Result<FieldDef, ParseError> {
676        let start = self.current().span;
677        let name = self.expect_ident()?;
678        self.expect(&TokenKind::Colon)?;
679        let ty = self.parse_type()?;
680        let default_value = if matches!(self.peek_kind(), TokenKind::Assign) {
681            self.advance();
682            Some(self.parse_expr(0)?)
683        } else {
684            None
685        };
686        let constraint = if matches!(self.peek_kind(), TokenKind::Where) {
687            self.advance();
688            Some(self.parse_expr(0)?)
689        } else {
690            None
691        };
692        let span = start.merge(
693            constraint
694                .as_ref()
695                .map(|c| c.span())
696                .or(default_value.as_ref().map(|d| d.span()))
697                .unwrap_or(ty.span()),
698        );
699        Ok(FieldDef {
700            name,
701            ty,
702            default_value,
703            constraint,
704            span,
705        })
706    }
707
708    // ── Enum ──
709
710    fn parse_enum(&mut self) -> Result<EnumDef, ParseError> {
711        let start = self.expect(&TokenKind::Enum)?.span;
712        let name = self.expect_ident()?;
713        let generic_params = self.parse_optional_generic_params()?;
714        self.skip_newlines();
715        let mut variants = Vec::new();
716        let mut methods = Vec::new();
717        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
718        if has_indent {
719            self.advance();
720        }
721        self.skip_newlines();
722        while !matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
723            self.skip_newlines();
724            if matches!(self.peek_kind(), TokenKind::Dedent) {
725                let mut i = self.pos;
726                while matches!(
727                    self.tokens.get(i).map(|t| &t.kind),
728                    Some(TokenKind::Dedent | TokenKind::Newline)
729                ) {
730                    i += 1;
731                }
732                let mut j = i;
733                let mut saw_arrow = false;
734                while let Some(tok) = self.tokens.get(j) {
735                    match tok.kind {
736                        TokenKind::Arrow => {
737                            saw_arrow = true;
738                            break;
739                        }
740                        TokenKind::Newline
741                        | TokenKind::Eof
742                        | TokenKind::End
743                        | TokenKind::Dedent => break,
744                        _ => j += 1,
745                    }
746                }
747                if saw_arrow {
748                    while matches!(self.peek_kind(), TokenKind::Dedent | TokenKind::Newline) {
749                        self.advance();
750                    }
751                    continue;
752                }
753                break;
754            }
755            if matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
756                break;
757            }
758            if matches!(self.peek_kind(), TokenKind::Cell) {
759                methods.push(self.parse_cell(true)?);
760                self.skip_newlines();
761                continue;
762            }
763            let vs = self.current().span;
764            let vname = self.expect_ident()?;
765            let payload = if matches!(self.peek_kind(), TokenKind::LParen) {
766                self.advance();
767                if matches!(self.peek_kind(), TokenKind::RParen) {
768                    self.advance();
769                    None
770                } else {
771                    let start_span = self.current().span;
772                    let mut types = Vec::new();
773
774                    loop {
775                        // Check for named param pattern: name: Type
776                        // The name can be an Ident or a keyword (e.g. `result`, `ok`, `err`)
777                        // so we check if the next token is a colon regardless of current token kind.
778                        let is_named_field = self
779                            .tokens
780                            .get(self.pos + 1)
781                            .is_some_and(|tok| matches!(tok.kind, TokenKind::Colon));
782
783                        if is_named_field {
784                            // Named param: skip name and colon, parse type
785                            self.advance();
786                            self.advance();
787                            match self.parse_type() {
788                                Ok(ty) => types.push(ty),
789                                Err(_) => {
790                                    self.consume_variant_arg_tokens();
791                                }
792                            }
793                        } else {
794                            match self.parse_type() {
795                                Ok(ty) => types.push(ty),
796                                Err(_) => {
797                                    types.push(TypeExpr::Named("Any".into(), start_span));
798                                }
799                            }
800                        }
801
802                        if matches!(self.peek_kind(), TokenKind::Comma) {
803                            self.advance();
804                        } else {
805                            break;
806                        }
807                    }
808
809                    if matches!(self.peek_kind(), TokenKind::RParen) {
810                        self.advance();
811                    }
812
813                    if types.is_empty() {
814                        None
815                    } else if types.len() == 1 {
816                        Some(types.remove(0))
817                    } else {
818                        Some(TypeExpr::Tuple(types, start_span))
819                    }
820                }
821            } else {
822                None
823            };
824            variants.push(EnumVariant {
825                name: vname,
826                payload,
827                span: vs,
828            });
829            self.skip_newlines();
830        }
831        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
832            self.advance();
833        }
834        self.skip_newlines();
835        let end_span = self.expect(&TokenKind::End)?.span;
836        Ok(EnumDef {
837            name,
838            generic_params,
839            variants,
840            methods,
841            is_pub: false,
842            span: start.merge(end_span),
843        })
844    }
845
846    // ── Cell ──
847
848    fn parse_cell(&mut self, require_body: bool) -> Result<CellDef, ParseError> {
849        let start = self.expect(&TokenKind::Cell)?.span;
850        let name = self.expect_ident()?;
851        let generic_params = self.parse_optional_generic_params()?;
852        self.expect(&TokenKind::LParen)?;
853        self.bracket_depth += 1;
854        let mut params = Vec::new();
855        self.skip_whitespace_tokens();
856        while !matches!(self.peek_kind(), TokenKind::RParen) {
857            if !params.is_empty() {
858                self.expect(&TokenKind::Comma)?;
859                self.skip_whitespace_tokens();
860            }
861            let variadic = if matches!(self.peek_kind(), TokenKind::DotDot | TokenKind::DotDotDot) {
862                self.advance();
863                true
864            } else {
865                false
866            };
867            let ps = self.current().span;
868            let pname = self.expect_ident()?;
869            let pty = if matches!(self.peek_kind(), TokenKind::Colon) {
870                self.advance();
871                self.parse_type()?
872            } else {
873                TypeExpr::Named("Any".into(), ps)
874            };
875            let default_value = if matches!(self.peek_kind(), TokenKind::Assign) {
876                self.advance();
877                Some(self.parse_expr(0)?)
878            } else {
879                None
880            };
881            params.push(Param {
882                name: pname,
883                ty: pty,
884                default_value,
885                variadic,
886                span: ps,
887            });
888            self.skip_whitespace_tokens();
889        }
890        self.bracket_depth -= 1;
891        self.expect(&TokenKind::RParen)?;
892        let ret = if matches!(self.peek_kind(), TokenKind::Arrow) {
893            self.advance();
894            Some(self.parse_type()?)
895        } else {
896            None
897        };
898        let effects = self.parse_optional_effect_row()?;
899
900        if matches!(self.peek_kind(), TokenKind::Assign) {
901            self.advance();
902            let expr = self.parse_expr(0)?;
903            let span = start.merge(expr.span());
904            return Ok(CellDef {
905                name,
906                generic_params,
907                params,
908                return_type: ret,
909                effects,
910                body: vec![Stmt::Return(ReturnStmt { value: expr, span })],
911                is_pub: false,
912                is_async: false,
913                where_clauses: vec![],
914                span,
915            });
916        }
917
918        // Prototype/signature form (used in effect declarations and trait-like stubs):
919        // cell f(x: Int) -> Int / {http}
920        if !require_body
921            && matches!(
922                self.peek_kind(),
923                TokenKind::Newline | TokenKind::Eof | TokenKind::Dedent
924            )
925        {
926            let mut look = self.pos;
927            while matches!(
928                self.tokens.get(look).map(|t| &t.kind),
929                Some(TokenKind::Newline)
930            ) {
931                look += 1;
932            }
933            if !matches!(
934                self.tokens.get(look).map(|t| &t.kind),
935                Some(TokenKind::Indent)
936            ) {
937                let end_span = self.current().span;
938                return Ok(CellDef {
939                    name,
940                    generic_params,
941                    params,
942                    return_type: ret,
943                    effects,
944                    body: vec![],
945                    is_pub: false,
946                    is_async: false,
947                    where_clauses: vec![],
948                    span: start.merge(end_span),
949                });
950            }
951        }
952
953        self.skip_newlines();
954        let body = self.parse_block()?;
955        let end_span = self.expect(&TokenKind::End)?.span;
956        Ok(CellDef {
957            name,
958            generic_params,
959            params,
960            return_type: ret,
961            effects,
962            body,
963            is_pub: false,
964            is_async: false,
965            where_clauses: vec![],
966            span: start.merge(end_span),
967        })
968    }
969
970    fn parse_block(&mut self) -> Result<Vec<Stmt>, ParseError> {
971        let mut stmts = Vec::new();
972        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
973        if has_indent {
974            self.advance();
975        }
976        self.skip_newlines();
977        while !matches!(
978            self.peek_kind(),
979            TokenKind::End
980                | TokenKind::Eof
981                | TokenKind::Else
982                | TokenKind::Cell
983                | TokenKind::Record
984                | TokenKind::Enum
985                | TokenKind::Type
986                | TokenKind::Grant
987                | TokenKind::Import
988                | TokenKind::Use
989                | TokenKind::Pub
990                | TokenKind::Schema
991                | TokenKind::Fn
992                | TokenKind::Mod
993                | TokenKind::Trait
994                | TokenKind::Impl
995        ) {
996            self.skip_newlines();
997            if matches!(
998                self.peek_kind(),
999                TokenKind::End
1000                    | TokenKind::Eof
1001                    | TokenKind::Else
1002                    | TokenKind::Cell
1003                    | TokenKind::Record
1004                    | TokenKind::Enum
1005                    | TokenKind::Type
1006                    | TokenKind::Grant
1007                    | TokenKind::Import
1008                    | TokenKind::Use
1009                    | TokenKind::Pub
1010                    | TokenKind::Schema
1011                    | TokenKind::Fn
1012                    | TokenKind::Mod
1013                    | TokenKind::Trait
1014                    | TokenKind::Impl
1015            ) {
1016                break;
1017            }
1018            if matches!(self.peek_kind(), TokenKind::Dedent) {
1019                let mut i = self.pos;
1020                while matches!(
1021                    self.tokens.get(i).map(|t| &t.kind),
1022                    Some(TokenKind::Dedent | TokenKind::Newline)
1023                ) {
1024                    i += 1;
1025                }
1026                if matches!(
1027                    self.tokens.get(i).map(|t| &t.kind),
1028                    Some(
1029                        TokenKind::End
1030                            | TokenKind::Else
1031                            | TokenKind::Eof
1032                            | TokenKind::Cell
1033                            | TokenKind::Record
1034                            | TokenKind::Enum
1035                            | TokenKind::Type
1036                            | TokenKind::Grant
1037                            | TokenKind::Import
1038                            | TokenKind::Use
1039                            | TokenKind::Pub
1040                            | TokenKind::Schema
1041                            | TokenKind::Fn
1042                            | TokenKind::Mod
1043                            | TokenKind::Trait
1044                            | TokenKind::Impl
1045                    )
1046                ) {
1047                    break;
1048                }
1049                self.advance();
1050                continue;
1051            }
1052            match self.parse_stmt() {
1053                Ok(stmt) => stmts.push(stmt),
1054                Err(err) => {
1055                    self.record_error(err);
1056                    self.synchronize_stmt();
1057                }
1058            }
1059            self.skip_newlines();
1060        }
1061        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
1062            self.advance();
1063        }
1064        while matches!(self.peek_kind(), TokenKind::Dedent) {
1065            let mut i = self.pos + 1;
1066            while matches!(
1067                self.tokens.get(i).map(|t| &t.kind),
1068                Some(TokenKind::Newline | TokenKind::Dedent)
1069            ) {
1070                i += 1;
1071            }
1072            if matches!(
1073                self.tokens.get(i).map(|t| &t.kind),
1074                Some(
1075                    TokenKind::End
1076                        | TokenKind::Else
1077                        | TokenKind::Eof
1078                        | TokenKind::Cell
1079                        | TokenKind::Record
1080                        | TokenKind::Enum
1081                        | TokenKind::Type
1082                        | TokenKind::Grant
1083                        | TokenKind::Import
1084                        | TokenKind::Use
1085                        | TokenKind::Pub
1086                        | TokenKind::Schema
1087                        | TokenKind::Fn
1088                        | TokenKind::Mod
1089                        | TokenKind::Trait
1090                        | TokenKind::Impl
1091                )
1092            ) {
1093                self.advance();
1094            } else {
1095                break;
1096            }
1097        }
1098        self.skip_newlines();
1099        Ok(stmts)
1100    }
1101
1102    fn parse_block_strict_dedent(&mut self) -> Result<Vec<Stmt>, ParseError> {
1103        let mut stmts = Vec::new();
1104        if matches!(self.peek_kind(), TokenKind::Indent) {
1105            self.advance();
1106        }
1107        self.skip_newlines();
1108        while !matches!(self.peek_kind(), TokenKind::Dedent | TokenKind::Eof) {
1109            self.skip_newlines();
1110            if matches!(self.peek_kind(), TokenKind::Dedent | TokenKind::Eof) {
1111                break;
1112            }
1113            stmts.push(self.parse_stmt()?);
1114            self.skip_newlines();
1115        }
1116        if matches!(self.peek_kind(), TokenKind::Dedent) {
1117            self.advance();
1118        }
1119        Ok(stmts)
1120    }
1121
1122    // ── Statements ──
1123
1124    fn parse_stmt(&mut self) -> Result<Stmt, ParseError> {
1125        match self.peek_kind() {
1126            TokenKind::Let => self.parse_let(),
1127            TokenKind::If => self.parse_if(),
1128            TokenKind::For => self.parse_for(),
1129            TokenKind::Match => self.parse_match(),
1130            TokenKind::Return => self.parse_return(),
1131            TokenKind::Halt => self.parse_halt(),
1132            TokenKind::While => self.parse_while(),
1133            TokenKind::Loop => self.parse_loop(),
1134            TokenKind::Break => self.parse_break(),
1135            TokenKind::Continue => self.parse_continue(),
1136            TokenKind::Emit => self.parse_emit(),
1137            TokenKind::Defer => self.parse_defer(),
1138            TokenKind::Where => {
1139                let s = self.current().span;
1140                self.advance();
1141                self.consume_rest_of_line();
1142                Ok(Stmt::Emit(EmitStmt {
1143                    value: Expr::StringLit("where".into(), s),
1144                    span: s,
1145                }))
1146            }
1147            TokenKind::At => {
1148                let save = self.pos;
1149                self.advance();
1150                if matches!(self.peek_kind(), TokenKind::Ident(_)) {
1151                    self.advance();
1152                }
1153                if matches!(
1154                    self.peek_kind(),
1155                    TokenKind::For | TokenKind::While | TokenKind::Loop
1156                ) {
1157                    return self.parse_stmt();
1158                }
1159                self.pos = save;
1160                let start = self.current().span;
1161                let decl = self.parse_attribute_decl()?;
1162                let label = decl.name.unwrap_or_else(|| "attribute".to_string());
1163                let value = Expr::StringLit(label, start);
1164                let span = start.merge(value.span());
1165                Ok(Stmt::Emit(EmitStmt { value, span }))
1166            }
1167            TokenKind::With => self.parse_addon_stmt(),
1168            TokenKind::Ident(_)
1169            | TokenKind::SelfKw
1170            | TokenKind::Result
1171            | TokenKind::Cell
1172            | TokenKind::String_
1173            | TokenKind::Int_
1174            | TokenKind::Float_
1175            | TokenKind::Bool
1176            | TokenKind::Bytes
1177            | TokenKind::Json
1178            | TokenKind::Type
1179            | TokenKind::List
1180            | TokenKind::Map
1181            | TokenKind::Set
1182            | TokenKind::Tuple
1183            | TokenKind::Schema
1184            | TokenKind::Ok_
1185            | TokenKind::Err_
1186            | TokenKind::Tool
1187            | TokenKind::Union
1188            | TokenKind::From
1189            | TokenKind::When
1190            | TokenKind::Try
1191            | TokenKind::Step
1192            | TokenKind::Comptime
1193            | TokenKind::Macro
1194            | TokenKind::Extern
1195            | TokenKind::Async
1196            | TokenKind::End => {
1197                if self.is_addon_stmt_keyword() {
1198                    return self.parse_addon_stmt();
1199                }
1200                // Check for compound assignment: ident += expr
1201                if self.is_compound_assignment() {
1202                    self.parse_compound_assign()
1203                } else if self.is_assignment() {
1204                    self.parse_assign()
1205                } else {
1206                    self.parse_expr_stmt()
1207                }
1208            }
1209            TokenKind::Role => {
1210                let expr = self.parse_role_block_stmt()?;
1211                Ok(Stmt::Expr(ExprStmt {
1212                    expr: expr.clone(),
1213                    span: expr.span(),
1214                }))
1215            }
1216            _ => self.parse_expr_stmt(),
1217        }
1218    }
1219
1220    fn is_addon_stmt_keyword(&self) -> bool {
1221        matches!(
1222            self.peek_kind(),
1223            TokenKind::Ident(name)
1224                if matches!(name.as_str(), "approve" | "checkpoint" | "escalate" | "observe" | "with" | "with_tool")
1225        )
1226    }
1227
1228    fn parse_addon_stmt(&mut self) -> Result<Stmt, ParseError> {
1229        let start = self.current().span;
1230        let kind = match self.peek_kind().clone() {
1231            TokenKind::Ident(_) => self.expect_ident()?,
1232            TokenKind::With => {
1233                self.advance();
1234                "with".to_string()
1235            }
1236            _ => {
1237                let tok = self.current().clone();
1238                return Err(ParseError::Unexpected {
1239                    found: format!("{}", tok.kind),
1240                    expected: "addon statement".into(),
1241                    line: tok.span.line,
1242                    col: tok.span.col,
1243                });
1244            }
1245        };
1246        self.consume_rest_of_line();
1247
1248        // Addendum block statements carry DSL metadata. For now we preserve payload
1249        // for compilation and keep optional `in` bodies executable.
1250        if matches!(self.peek_kind(), TokenKind::Newline) {
1251            self.skip_newlines();
1252            if matches!(self.peek_kind(), TokenKind::Indent) {
1253                self.consume_indented_payload();
1254                self.skip_newlines();
1255                if matches!(self.peek_kind(), TokenKind::End) {
1256                    self.advance();
1257                }
1258            } else if matches!(self.peek_kind(), TokenKind::In) {
1259                self.consume_block_until_end();
1260            }
1261        }
1262
1263        let mut in_block_end = None;
1264        let mut in_block = None;
1265        self.skip_newlines();
1266        if matches!(self.peek_kind(), TokenKind::In) {
1267            self.advance();
1268            self.skip_newlines();
1269            let body = self.parse_block()?;
1270            let end = self.expect(&TokenKind::End)?.span;
1271            in_block_end = Some(end);
1272            in_block = Some(body);
1273        }
1274
1275        if let Some(body) = in_block {
1276            let end = in_block_end.unwrap_or(start);
1277            return Ok(Stmt::If(IfStmt {
1278                condition: Expr::BoolLit(true, start),
1279                then_body: body,
1280                else_body: None,
1281                span: start.merge(end),
1282            }));
1283        }
1284
1285        let value = Expr::StringLit(kind, start);
1286        let span = start.merge(value.span());
1287        Ok(Stmt::Emit(EmitStmt { value, span }))
1288    }
1289
1290    fn parse_let(&mut self) -> Result<Stmt, ParseError> {
1291        let start = self.expect(&TokenKind::Let)?.span;
1292        let mutable = if matches!(self.peek_kind(), TokenKind::Mut) {
1293            self.advance();
1294            true
1295        } else {
1296            false
1297        };
1298        let (name, pattern) = if matches!(self.peek_kind(), TokenKind::LParen | TokenKind::LBracket)
1299        {
1300            // Destructuring pattern: let (a, b) = ... or let [a, b] = ...
1301            let pat = self.parse_pattern()?;
1302            let pat_name = match &pat {
1303                Pattern::TupleDestructure { elements, .. } => {
1304                    // Use first element name as the binding name for backwards compat
1305                    elements
1306                        .first()
1307                        .and_then(|p| match p {
1308                            Pattern::Ident(n, _) => Some(n.clone()),
1309                            _ => None,
1310                        })
1311                        .unwrap_or_else(|| "__tuple".to_string())
1312                }
1313                Pattern::ListDestructure { elements, .. } => elements
1314                    .first()
1315                    .and_then(|p| match p {
1316                        Pattern::Ident(n, _) => Some(n.clone()),
1317                        _ => None,
1318                    })
1319                    .unwrap_or_else(|| "__pattern".to_string()),
1320                _ => "__pattern".to_string(),
1321            };
1322            (pat_name, Some(pat))
1323        } else if matches!(self.peek_kind(), TokenKind::LBrace) {
1324            // Record destructuring: let { a, b } = ...
1325            let brace_span = self.advance().span; // consume {
1326            let mut fields = Vec::new();
1327            while !matches!(self.peek_kind(), TokenKind::RBrace | TokenKind::Eof) {
1328                if matches!(self.peek_kind(), TokenKind::Comma) {
1329                    self.advance();
1330                    continue;
1331                }
1332                let field_name = self.expect_ident()?;
1333                fields.push((field_name, None));
1334            }
1335            if matches!(self.peek_kind(), TokenKind::RBrace) {
1336                self.advance();
1337            }
1338            let first_name = fields
1339                .first()
1340                .map(|(n, _)| n.clone())
1341                .unwrap_or_else(|| "__pattern".to_string());
1342            let pat = Pattern::RecordDestructure {
1343                type_name: String::new(), // anonymous destructure
1344                fields,
1345                open: false,
1346                span: brace_span,
1347            };
1348            (first_name, Some(pat))
1349        } else {
1350            let first = self.expect_ident()?;
1351            if matches!(self.peek_kind(), TokenKind::LParen) {
1352                // Variant/record destructuring: let Name(x) = ... or let ok(v) = ...
1353                // Back up pos to include the ident we already consumed
1354                self.pos -= 1;
1355                let pat = self.parse_pattern()?;
1356                let pat_name = match &pat {
1357                    Pattern::Variant(_, Some(binding), _) => Self::pattern_binding_name(binding)
1358                        .unwrap_or_else(|| "__pattern".to_string()),
1359                    Pattern::RecordDestructure { fields, .. } => fields
1360                        .first()
1361                        .map(|(n, _)| n.clone())
1362                        .unwrap_or_else(|| "__pattern".to_string()),
1363                    _ => first.clone(),
1364                };
1365                (pat_name, Some(pat))
1366            } else {
1367                (first, None)
1368            }
1369        };
1370        let ty = if matches!(self.peek_kind(), TokenKind::Colon) {
1371            self.advance();
1372            Some(self.parse_type()?)
1373        } else {
1374            None
1375        };
1376        self.expect(&TokenKind::Assign)?;
1377        let value = self.parse_expr(0)?;
1378        let span = start.merge(value.span());
1379        Ok(Stmt::Let(LetStmt {
1380            name,
1381            mutable,
1382            pattern,
1383            ty,
1384            value,
1385            span,
1386        }))
1387    }
1388
1389    fn parse_if(&mut self) -> Result<Stmt, ParseError> {
1390        let start = self.expect(&TokenKind::If)?.span;
1391
1392        // Detect `if let <pattern> = <expr>` and desugar to match
1393        if matches!(self.peek_kind(), TokenKind::Let) {
1394            self.advance();
1395            let pattern = self.parse_pattern()?;
1396            let subject = if matches!(self.peek_kind(), TokenKind::Assign) {
1397                self.advance();
1398                self.parse_expr(0)?
1399            } else {
1400                // Malformed: treat as matching against true
1401                Expr::BoolLit(true, start)
1402            };
1403
1404            // Parse the then body (block-style only for if-let)
1405            self.skip_newlines();
1406            let then_body = self.parse_block()?;
1407
1408            // Parse optional else
1409            let else_body = if matches!(self.peek_kind(), TokenKind::Else) {
1410                self.advance();
1411                self.skip_newlines();
1412                if matches!(self.peek_kind(), TokenKind::If) {
1413                    let elif = self.parse_if()?;
1414                    Some(vec![elif])
1415                } else {
1416                    Some(self.parse_block()?)
1417                }
1418            } else {
1419                None
1420            };
1421
1422            let end_span = if matches!(self.peek_kind(), TokenKind::End) {
1423                self.expect(&TokenKind::End)?.span
1424            } else if let Some(ref eb) = else_body {
1425                eb.last().map(|s| s.span()).unwrap_or(start)
1426            } else {
1427                start
1428            };
1429
1430            // Build match arms: pattern => then_body, _ => else_body
1431            let mut arms = vec![MatchArm {
1432                pattern,
1433                body: then_body,
1434                span: start,
1435            }];
1436            if let Some(eb) = else_body {
1437                arms.push(MatchArm {
1438                    pattern: Pattern::Wildcard(start),
1439                    body: eb,
1440                    span: start,
1441                });
1442            }
1443
1444            return Ok(Stmt::Match(MatchStmt {
1445                subject,
1446                arms,
1447                span: start.merge(end_span),
1448            }));
1449        }
1450
1451        let cond = self.parse_expr(0)?;
1452        if matches!(self.peek_kind(), TokenKind::Then) {
1453            self.advance();
1454            let then_expr = self.parse_expr(0)?;
1455            let then_stmt = Stmt::Expr(ExprStmt {
1456                expr: then_expr.clone(),
1457                span: then_expr.span(),
1458            });
1459            let else_body = if matches!(self.peek_kind(), TokenKind::Else) {
1460                self.advance();
1461                if matches!(self.peek_kind(), TokenKind::If) {
1462                    let elif = self.parse_if()?;
1463                    Some(vec![elif])
1464                } else {
1465                    let expr = self.parse_expr(0)?;
1466                    Some(vec![Stmt::Expr(ExprStmt {
1467                        span: expr.span(),
1468                        expr,
1469                    })])
1470                }
1471            } else {
1472                None
1473            };
1474            let end_span = else_body
1475                .as_ref()
1476                .and_then(|b| b.last().map(|s| s.span()))
1477                .unwrap_or(then_stmt.span());
1478            return Ok(Stmt::If(IfStmt {
1479                condition: cond,
1480                then_body: vec![then_stmt],
1481                else_body,
1482                span: start.merge(end_span),
1483            }));
1484        }
1485        self.skip_newlines();
1486        let then_body = self.parse_block()?;
1487        let else_body = if matches!(self.peek_kind(), TokenKind::Else) {
1488            self.advance();
1489            self.skip_newlines();
1490            if matches!(self.peek_kind(), TokenKind::If) {
1491                // else if
1492                let elif = self.parse_if()?;
1493                Some(vec![elif])
1494            } else {
1495                Some(self.parse_block()?)
1496            }
1497        } else {
1498            None
1499        };
1500        let end_span = if matches!(self.peek_kind(), TokenKind::End) {
1501            self.expect(&TokenKind::End)?.span
1502        } else if let Some(ref eb) = else_body {
1503            eb.last().map(|s| s.span()).unwrap_or(start)
1504        } else {
1505            start
1506        };
1507        Ok(Stmt::If(IfStmt {
1508            condition: cond,
1509            then_body,
1510            else_body,
1511            span: start.merge(end_span),
1512        }))
1513    }
1514
1515    fn parse_for(&mut self) -> Result<Stmt, ParseError> {
1516        let start = self.expect(&TokenKind::For)?.span;
1517        let label = if matches!(self.peek_kind(), TokenKind::At) {
1518            self.advance();
1519            Some(self.expect_ident()?)
1520        } else {
1521            None
1522        };
1523        let (var, pattern) = if matches!(self.peek_kind(), TokenKind::LParen) {
1524            // Tuple destructuring: for (k, v) in ...
1525            let pat = self.parse_pattern()?;
1526            let first_name = match &pat {
1527                Pattern::TupleDestructure { elements, .. } => elements
1528                    .first()
1529                    .and_then(|p| match p {
1530                        Pattern::Ident(n, _) => Some(n.clone()),
1531                        _ => None,
1532                    })
1533                    .unwrap_or_else(|| "__tuple".to_string()),
1534                Pattern::Ident(n, _) => n.clone(),
1535                _ => "__pattern".to_string(),
1536            };
1537            (first_name, Some(pat))
1538        } else {
1539            (self.expect_ident()?, None)
1540        };
1541        self.expect(&TokenKind::In)?;
1542        let iter = self.parse_expr(0)?;
1543        let filter = if matches!(self.peek_kind(), TokenKind::If) {
1544            self.advance();
1545            Some(self.parse_expr(0)?)
1546        } else {
1547            None
1548        };
1549        self.skip_newlines();
1550        let body = self.parse_block()?;
1551        let end_span = self.expect(&TokenKind::End)?.span;
1552        Ok(Stmt::For(ForStmt {
1553            label,
1554            var,
1555            pattern,
1556            iter,
1557            filter,
1558            body,
1559            span: start.merge(end_span),
1560        }))
1561    }
1562
1563    fn parse_match(&mut self) -> Result<Stmt, ParseError> {
1564        let start = self.expect(&TokenKind::Match)?.span;
1565        let subject = self.parse_expr(0)?;
1566        self.skip_newlines();
1567        let mut arms = Vec::new();
1568        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
1569        if has_indent {
1570            self.advance();
1571        }
1572        self.skip_newlines();
1573        while !matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
1574            self.skip_newlines();
1575            if matches!(self.peek_kind(), TokenKind::Dedent) {
1576                self.advance();
1577                continue;
1578            }
1579            if matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
1580                break;
1581            }
1582            let arm_start = self.current().span;
1583            let first_pattern = self.parse_pattern()?;
1584            let mut pattern = if matches!(self.peek_kind(), TokenKind::Pipe) {
1585                let mut patterns = vec![first_pattern];
1586                while matches!(self.peek_kind(), TokenKind::Pipe) {
1587                    self.advance();
1588                    patterns.push(self.parse_pattern()?);
1589                }
1590                Pattern::Or {
1591                    patterns,
1592                    span: arm_start,
1593                }
1594            } else {
1595                first_pattern
1596            };
1597            if matches!(self.peek_kind(), TokenKind::If) {
1598                self.advance();
1599                let guard = self.parse_expr(0)?;
1600                pattern = Pattern::Guard {
1601                    inner: Box::new(pattern),
1602                    condition: Box::new(guard),
1603                    span: arm_start,
1604                };
1605            }
1606            self.expect(&TokenKind::Arrow)?;
1607            // Check for block body (indent after arrow) or single-line
1608            let body = if matches!(self.peek_kind(), TokenKind::Newline) {
1609                // Multi-line arm body: newline followed by indent
1610                self.skip_newlines();
1611                if matches!(self.peek_kind(), TokenKind::Indent) {
1612                    self.parse_block_strict_dedent()?
1613                } else {
1614                    // Just whitespace, parse single statement
1615                    vec![self.parse_stmt()?]
1616                }
1617            } else {
1618                // Single-line arm: parse one statement
1619                vec![self.parse_stmt()?]
1620            };
1621            let arm_span = arm_start.merge(body.last().map(|s| s.span()).unwrap_or(arm_start));
1622            arms.push(MatchArm {
1623                pattern,
1624                body,
1625                span: arm_span,
1626            });
1627            self.skip_newlines();
1628        }
1629        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
1630            self.advance();
1631        }
1632        self.skip_newlines();
1633        let end_span = self.expect(&TokenKind::End)?.span;
1634        Ok(Stmt::Match(MatchStmt {
1635            subject,
1636            arms,
1637            span: start.merge(end_span),
1638        }))
1639    }
1640
1641    fn parse_pattern(&mut self) -> Result<Pattern, ParseError> {
1642        if matches!(self.peek_kind(), TokenKind::LParen) {
1643            let s = self.advance().span;
1644            if self.paren_contains_top_level_arrow() {
1645                let mut depth = 1usize;
1646                while !self.at_end() {
1647                    match self.peek_kind() {
1648                        TokenKind::LParen => {
1649                            depth += 1;
1650                            self.advance();
1651                        }
1652                        TokenKind::RParen => {
1653                            depth -= 1;
1654                            self.advance();
1655                            if depth == 0 {
1656                                break;
1657                            }
1658                        }
1659                        _ => {
1660                            self.advance();
1661                        }
1662                    }
1663                }
1664                return Ok(Pattern::Wildcard(s));
1665            }
1666            if matches!(self.peek_kind(), TokenKind::RParen) {
1667                self.advance();
1668                return Ok(Pattern::TupleDestructure {
1669                    elements: vec![],
1670                    span: s,
1671                });
1672            }
1673            let first = self.parse_pattern()?;
1674            if matches!(self.peek_kind(), TokenKind::Comma) {
1675                let mut elements = vec![first];
1676                while matches!(self.peek_kind(), TokenKind::Comma) {
1677                    self.advance();
1678                    if matches!(self.peek_kind(), TokenKind::RParen | TokenKind::Eof) {
1679                        break;
1680                    }
1681                    elements.push(self.parse_pattern()?);
1682                }
1683                self.expect(&TokenKind::RParen)?;
1684                return Ok(Pattern::TupleDestructure { elements, span: s });
1685            }
1686            self.expect(&TokenKind::RParen)?;
1687            return Ok(first);
1688        }
1689        if matches!(self.peek_kind(), TokenKind::LBracket) {
1690            let s = self.advance().span;
1691            let mut elements = Vec::new();
1692            let mut rest = None;
1693            while !matches!(self.peek_kind(), TokenKind::RBracket | TokenKind::Eof) {
1694                if matches!(self.peek_kind(), TokenKind::Comma) {
1695                    self.advance();
1696                    continue;
1697                }
1698                if matches!(self.peek_kind(), TokenKind::DotDot | TokenKind::DotDotDot) {
1699                    self.advance();
1700                    if matches!(self.peek_kind(), TokenKind::Ident(_)) {
1701                        rest = Some(self.expect_ident()?);
1702                    }
1703                    while !matches!(self.peek_kind(), TokenKind::RBracket | TokenKind::Eof) {
1704                        if matches!(self.peek_kind(), TokenKind::Comma) {
1705                            self.advance();
1706                            break;
1707                        }
1708                        self.advance();
1709                    }
1710                    continue;
1711                }
1712                elements.push(self.parse_pattern()?);
1713            }
1714            self.expect(&TokenKind::RBracket)?;
1715            return Ok(Pattern::ListDestructure {
1716                elements,
1717                rest,
1718                span: s,
1719            });
1720        }
1721
1722        match self.peek_kind().clone() {
1723            TokenKind::IntLit(n) => {
1724                let s = self.advance().span;
1725                Ok(Pattern::Literal(Expr::IntLit(n, s)))
1726            }
1727            TokenKind::FloatLit(n) => {
1728                let s = self.advance().span;
1729                Ok(Pattern::Literal(Expr::FloatLit(n, s)))
1730            }
1731            TokenKind::StringLit(ref sv) => {
1732                let sv = sv.clone();
1733                let s = self.advance().span;
1734                Ok(Pattern::Literal(Expr::StringLit(sv, s)))
1735            }
1736            TokenKind::BoolLit(b) => {
1737                let s = self.advance().span;
1738                Ok(Pattern::Literal(Expr::BoolLit(b, s)))
1739            }
1740            TokenKind::Ident(ref name) if name == "_" => {
1741                let s = self.advance().span;
1742                Ok(Pattern::Wildcard(s))
1743            }
1744            TokenKind::Ok_ | TokenKind::Err_ => {
1745                let vname = format!("{}", self.peek_kind());
1746                let s = self.advance().span;
1747                if matches!(self.peek_kind(), TokenKind::LParen) {
1748                    self.advance();
1749                    let inner = if matches!(self.peek_kind(), TokenKind::RParen) {
1750                        None
1751                    } else {
1752                        Some(Box::new(self.parse_pattern()?))
1753                    };
1754                    self.expect(&TokenKind::RParen)?;
1755                    Ok(Pattern::Variant(vname, inner, s))
1756                } else {
1757                    Ok(Pattern::Variant(vname, None, s))
1758                }
1759            }
1760            TokenKind::Ident(ref name) => {
1761                let mut name = name.clone();
1762                let s = self.advance().span;
1763                while matches!(self.peek_kind(), TokenKind::Dot) {
1764                    self.advance();
1765                    name.push('.');
1766                    name.push_str(&self.expect_ident()?);
1767                }
1768                if matches!(self.peek_kind(), TokenKind::Colon) {
1769                    self.advance();
1770                    let ty = self.parse_type()?;
1771                    return Ok(Pattern::TypeCheck {
1772                        name,
1773                        type_expr: Box::new(ty),
1774                        span: s,
1775                    });
1776                }
1777                if matches!(self.peek_kind(), TokenKind::LParen) {
1778                    if self.paren_looks_like_record_destructure() {
1779                        self.advance(); // (
1780                        let mut fields = Vec::new();
1781                        let mut open = false;
1782                        while !matches!(self.peek_kind(), TokenKind::RParen | TokenKind::Eof) {
1783                            if matches!(self.peek_kind(), TokenKind::Comma) {
1784                                self.advance();
1785                                continue;
1786                            }
1787                            if matches!(self.peek_kind(), TokenKind::DotDot | TokenKind::DotDotDot)
1788                            {
1789                                open = true;
1790                                self.advance();
1791                                if matches!(self.peek_kind(), TokenKind::Ident(_)) {
1792                                    self.advance();
1793                                }
1794                                continue;
1795                            }
1796                            let field_name = self.expect_ident()?;
1797                            let field_pat = if matches!(self.peek_kind(), TokenKind::Colon) {
1798                                self.advance();
1799                                if matches!(self.peek_kind(), TokenKind::Comma | TokenKind::RParen)
1800                                {
1801                                    None
1802                                } else {
1803                                    Some(self.parse_pattern()?)
1804                                }
1805                            } else {
1806                                None
1807                            };
1808                            fields.push((field_name, field_pat));
1809                            if matches!(self.peek_kind(), TokenKind::Comma) {
1810                                self.advance();
1811                            }
1812                        }
1813                        self.expect(&TokenKind::RParen)?;
1814                        Ok(Pattern::RecordDestructure {
1815                            type_name: name,
1816                            fields,
1817                            open,
1818                            span: s,
1819                        })
1820                    } else {
1821                        self.advance();
1822                        let binding = self.parse_variant_binding_candidate()?;
1823                        self.expect(&TokenKind::RParen)?;
1824                        Ok(Pattern::Variant(name, binding, s))
1825                    }
1826                } else {
1827                    Ok(Pattern::Ident(name, s))
1828                }
1829            }
1830            _ => {
1831                let tok = self.current().clone();
1832                Err(ParseError::Unexpected {
1833                    found: format!("{}", tok.kind),
1834                    expected: "pattern".into(),
1835                    line: tok.span.line,
1836                    col: tok.span.col,
1837                })
1838            }
1839        }
1840    }
1841
1842    fn parse_return(&mut self) -> Result<Stmt, ParseError> {
1843        let start = self.expect(&TokenKind::Return)?.span;
1844        let value = self.parse_expr(0)?;
1845        Ok(Stmt::Return(ReturnStmt {
1846            value: value.clone(),
1847            span: start.merge(value.span()),
1848        }))
1849    }
1850
1851    fn parse_halt(&mut self) -> Result<Stmt, ParseError> {
1852        let start = self.expect(&TokenKind::Halt)?.span;
1853        self.expect(&TokenKind::LParen)?;
1854        let msg = self.parse_expr(0)?;
1855        self.expect(&TokenKind::RParen)?;
1856        Ok(Stmt::Halt(HaltStmt {
1857            message: msg.clone(),
1858            span: start.merge(msg.span()),
1859        }))
1860    }
1861
1862    fn parse_while(&mut self) -> Result<Stmt, ParseError> {
1863        let start = self.expect(&TokenKind::While)?.span;
1864        let label = if matches!(self.peek_kind(), TokenKind::At) {
1865            self.advance();
1866            Some(self.expect_ident()?)
1867        } else {
1868            None
1869        };
1870
1871        // Detect `while let <pattern> = <expr>` and desugar to loop + match
1872        if matches!(self.peek_kind(), TokenKind::Let) {
1873            self.advance();
1874            let pattern = self.parse_pattern()?;
1875            let subject = if matches!(self.peek_kind(), TokenKind::Assign) {
1876                self.advance();
1877                self.parse_expr(0)?
1878            } else {
1879                Expr::BoolLit(true, start)
1880            };
1881            self.skip_newlines();
1882            let body = self.parse_block()?;
1883            let end_span = self.expect(&TokenKind::End)?.span;
1884
1885            // Desugar to:
1886            //   loop
1887            //     match <subject>
1888            //       <pattern> => <body>
1889            //       _ => break
1890            //     end
1891            //   end
1892            let match_stmt = Stmt::Match(MatchStmt {
1893                subject,
1894                arms: vec![
1895                    MatchArm {
1896                        pattern,
1897                        body,
1898                        span: start,
1899                    },
1900                    MatchArm {
1901                        pattern: Pattern::Wildcard(start),
1902                        body: vec![Stmt::Break(BreakStmt {
1903                            label: None,
1904                            value: None,
1905                            span: start,
1906                        })],
1907                        span: start,
1908                    },
1909                ],
1910                span: start,
1911            });
1912
1913            return Ok(Stmt::Loop(LoopStmt {
1914                label,
1915                body: vec![match_stmt],
1916                span: start.merge(end_span),
1917            }));
1918        }
1919
1920        let cond = self.parse_expr(0)?;
1921        self.skip_newlines();
1922        let body = self.parse_block()?;
1923        let end_span = self.expect(&TokenKind::End)?.span;
1924        Ok(Stmt::While(WhileStmt {
1925            label,
1926            condition: cond,
1927            body,
1928            span: start.merge(end_span),
1929        }))
1930    }
1931
1932    fn parse_loop(&mut self) -> Result<Stmt, ParseError> {
1933        let start = self.expect(&TokenKind::Loop)?.span;
1934        let label = if matches!(self.peek_kind(), TokenKind::At) {
1935            self.advance();
1936            Some(self.expect_ident()?)
1937        } else {
1938            None
1939        };
1940        self.skip_newlines();
1941        let body = self.parse_block()?;
1942        let end_span = self.expect(&TokenKind::End)?.span;
1943        Ok(Stmt::Loop(LoopStmt {
1944            label,
1945            body,
1946            span: start.merge(end_span),
1947        }))
1948    }
1949
1950    fn parse_defer(&mut self) -> Result<Stmt, ParseError> {
1951        let start = self.expect(&TokenKind::Defer)?.span;
1952        self.skip_newlines();
1953        let body = self.parse_block()?;
1954        let end_span = self.expect(&TokenKind::End)?.span;
1955        Ok(Stmt::Defer(DeferStmt {
1956            body,
1957            span: start.merge(end_span),
1958        }))
1959    }
1960
1961    fn parse_break(&mut self) -> Result<Stmt, ParseError> {
1962        let start = self.expect(&TokenKind::Break)?.span;
1963        let label = if matches!(self.peek_kind(), TokenKind::At) {
1964            self.advance();
1965            Some(self.expect_ident()?)
1966        } else {
1967            None
1968        };
1969        let value = if label.is_none()
1970            && !matches!(
1971                self.peek_kind(),
1972                TokenKind::Newline | TokenKind::Eof | TokenKind::End | TokenKind::Dedent
1973            ) {
1974            Some(self.parse_expr(0)?)
1975        } else {
1976            None
1977        };
1978        let span = value
1979            .as_ref()
1980            .map(|v| start.merge(v.span()))
1981            .unwrap_or(start);
1982        Ok(Stmt::Break(BreakStmt { label, value, span }))
1983    }
1984
1985    fn parse_continue(&mut self) -> Result<Stmt, ParseError> {
1986        let start = self.expect(&TokenKind::Continue)?.span;
1987        let label = if matches!(self.peek_kind(), TokenKind::At) {
1988            self.advance();
1989            Some(self.expect_ident()?)
1990        } else {
1991            None
1992        };
1993        Ok(Stmt::Continue(ContinueStmt { label, span: start }))
1994    }
1995
1996    fn parse_emit(&mut self) -> Result<Stmt, ParseError> {
1997        let start = self.expect(&TokenKind::Emit)?.span;
1998        let value = self.parse_expr(0)?;
1999        let span = start.merge(value.span());
2000        Ok(Stmt::Emit(EmitStmt { value, span }))
2001    }
2002
2003    fn is_compound_assignment(&self) -> bool {
2004        let mut i = self.pos;
2005        if !matches!(self.tokens.get(i).map(|t| &t.kind), Some(k) if Self::is_identifier_like(k)) {
2006            return false;
2007        }
2008        i += 1;
2009        while matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Dot))
2010            && matches!(
2011                self.tokens.get(i + 1).map(|t| &t.kind),
2012                Some(TokenKind::Ident(_))
2013            )
2014        {
2015            i += 2;
2016        }
2017        matches!(
2018            self.tokens.get(i).map(|t| &t.kind),
2019            Some(
2020                TokenKind::PlusAssign
2021                    | TokenKind::MinusAssign
2022                    | TokenKind::StarAssign
2023                    | TokenKind::SlashAssign
2024                    | TokenKind::FloorDivAssign
2025                    | TokenKind::PercentAssign
2026                    | TokenKind::StarStarAssign
2027                    | TokenKind::AmpAssign
2028                    | TokenKind::PipeAssign
2029                    | TokenKind::CaretAssign
2030            )
2031        )
2032    }
2033
2034    fn parse_compound_assign(&mut self) -> Result<Stmt, ParseError> {
2035        let start = self.tokens[self.pos].span;
2036        let name = self.parse_assignment_target()?;
2037        let op = match self.peek_kind() {
2038            TokenKind::PlusAssign => {
2039                self.advance();
2040                CompoundOp::AddAssign
2041            }
2042            TokenKind::MinusAssign => {
2043                self.advance();
2044                CompoundOp::SubAssign
2045            }
2046            TokenKind::StarAssign => {
2047                self.advance();
2048                CompoundOp::MulAssign
2049            }
2050            TokenKind::SlashAssign => {
2051                self.advance();
2052                CompoundOp::DivAssign
2053            }
2054            TokenKind::FloorDivAssign => {
2055                self.advance();
2056                CompoundOp::FloorDivAssign
2057            }
2058            TokenKind::PercentAssign => {
2059                self.advance();
2060                CompoundOp::ModAssign
2061            }
2062            TokenKind::StarStarAssign => {
2063                self.advance();
2064                CompoundOp::PowAssign
2065            }
2066            TokenKind::AmpAssign => {
2067                self.advance();
2068                CompoundOp::BitAndAssign
2069            }
2070            TokenKind::PipeAssign => {
2071                self.advance();
2072                CompoundOp::BitOrAssign
2073            }
2074            TokenKind::CaretAssign => {
2075                self.advance();
2076                CompoundOp::BitXorAssign
2077            }
2078            _ => unreachable!(),
2079        };
2080        let value = self.parse_expr(0)?;
2081        let span = start.merge(value.span());
2082        Ok(Stmt::CompoundAssign(CompoundAssignStmt {
2083            target: name,
2084            op,
2085            value,
2086            span,
2087        }))
2088    }
2089
2090    // ── New item parsers ──
2091
2092    fn parse_type_alias(&mut self, is_pub: bool) -> Result<TypeAliasDef, ParseError> {
2093        let start = self.expect(&TokenKind::Type)?.span;
2094        let name = self.expect_ident()?;
2095        let generic_params = self.parse_optional_generic_params()?;
2096        self.expect(&TokenKind::Assign)?;
2097        let type_expr = self.parse_type()?;
2098        if matches!(self.peek_kind(), TokenKind::Where) {
2099            self.advance();
2100            let _ = self.parse_expr(0)?;
2101        }
2102        let span = start.merge(type_expr.span());
2103        Ok(TypeAliasDef {
2104            name,
2105            generic_params,
2106            type_expr,
2107            is_pub,
2108            span,
2109        })
2110    }
2111
2112    fn parse_trait_def(&mut self, is_pub: bool) -> Result<TraitDef, ParseError> {
2113        let start = self.expect(&TokenKind::Trait)?.span;
2114        let name = self.expect_ident()?;
2115        let parent_traits = if matches!(self.peek_kind(), TokenKind::Colon) {
2116            self.advance();
2117            let mut traits = vec![self.expect_ident()?];
2118            while matches!(self.peek_kind(), TokenKind::Comma | TokenKind::Plus) {
2119                self.advance();
2120                traits.push(self.expect_ident()?);
2121            }
2122            traits
2123        } else {
2124            vec![]
2125        };
2126        self.skip_newlines();
2127        let mut methods = Vec::new();
2128        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
2129        if has_indent {
2130            self.advance();
2131        }
2132        self.skip_newlines();
2133        while !matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
2134            self.skip_newlines();
2135            if matches!(self.peek_kind(), TokenKind::Dedent) {
2136                self.advance();
2137                continue;
2138            }
2139            if matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
2140                break;
2141            }
2142            methods.push(self.parse_cell(false)?);
2143            self.skip_newlines();
2144        }
2145        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
2146            self.advance();
2147        }
2148        self.skip_newlines();
2149        let end_span = self.expect(&TokenKind::End)?.span;
2150        Ok(TraitDef {
2151            name,
2152            parent_traits,
2153            methods,
2154            is_pub,
2155            span: start.merge(end_span),
2156        })
2157    }
2158
2159    fn parse_impl_def(&mut self) -> Result<ImplDef, ParseError> {
2160        let start = self.expect(&TokenKind::Impl)?.span;
2161        let generic_params = self.parse_optional_generic_params()?;
2162        let trait_name = self.expect_ident()?;
2163        self.expect(&TokenKind::For)?;
2164        let mut target_type = self.parse_dotted_ident()?;
2165        if matches!(self.peek_kind(), TokenKind::LBracket) {
2166            let mut depth = 0usize;
2167            let mut suffix = String::new();
2168            while !self.at_end() {
2169                match self.peek_kind() {
2170                    TokenKind::LBracket => {
2171                        depth += 1;
2172                        suffix.push('[');
2173                        self.advance();
2174                    }
2175                    TokenKind::RBracket => {
2176                        suffix.push(']');
2177                        self.advance();
2178                        depth -= 1;
2179                        if depth == 0 {
2180                            break;
2181                        }
2182                    }
2183                    _ => {
2184                        suffix.push_str(&format!("{}", self.current().kind));
2185                        self.advance();
2186                    }
2187                }
2188            }
2189            target_type.push_str(&suffix);
2190        }
2191        self.skip_newlines();
2192        let mut cells = Vec::new();
2193        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
2194        if has_indent {
2195            self.advance();
2196        }
2197        self.skip_newlines();
2198        while !matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
2199            self.skip_newlines();
2200            if matches!(self.peek_kind(), TokenKind::Dedent) {
2201                self.advance();
2202                continue;
2203            }
2204            if matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
2205                break;
2206            }
2207            cells.push(self.parse_cell(true)?);
2208            self.skip_newlines();
2209        }
2210        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
2211            self.advance();
2212        }
2213        self.skip_newlines();
2214        let end_span = self.expect(&TokenKind::End)?.span;
2215        Ok(ImplDef {
2216            trait_name,
2217            generic_params,
2218            target_type,
2219            cells,
2220            span: start.merge(end_span),
2221        })
2222    }
2223
2224    fn parse_import(&mut self, is_pub: bool) -> Result<ImportDecl, ParseError> {
2225        let start = self.expect(&TokenKind::Import)?.span;
2226        let mut path = vec![self.expect_ident()?];
2227        while matches!(self.peek_kind(), TokenKind::Dot) {
2228            self.advance();
2229            path.push(self.expect_ident()?);
2230        }
2231        self.expect(&TokenKind::Colon)?;
2232        let names = if matches!(self.peek_kind(), TokenKind::Star) {
2233            self.advance();
2234            ImportList::Wildcard
2235        } else {
2236            let mut names = Vec::new();
2237            loop {
2238                let ns = self.current().span;
2239                let n = self.expect_ident()?;
2240                let alias = if matches!(self.peek_kind(), TokenKind::As) {
2241                    self.advance();
2242                    Some(self.expect_ident()?)
2243                } else {
2244                    None
2245                };
2246                names.push(ImportName {
2247                    name: n,
2248                    alias,
2249                    span: ns,
2250                });
2251                if !matches!(self.peek_kind(), TokenKind::Comma) {
2252                    break;
2253                }
2254                self.advance();
2255            }
2256            ImportList::Names(names)
2257        };
2258        let span = start.merge(self.current().span);
2259        Ok(ImportDecl {
2260            path,
2261            names,
2262            is_pub,
2263            span,
2264        })
2265    }
2266
2267    fn parse_const_decl(&mut self) -> Result<ConstDeclDef, ParseError> {
2268        let start = self.expect(&TokenKind::Const)?.span;
2269        let name = self.expect_ident()?;
2270        let type_ann = if matches!(self.peek_kind(), TokenKind::Colon) {
2271            self.advance();
2272            Some(self.parse_type()?)
2273        } else {
2274            None
2275        };
2276        self.expect(&TokenKind::Assign)?;
2277        let value = self.parse_expr(0)?;
2278        let span = start.merge(value.span());
2279        Ok(ConstDeclDef {
2280            name,
2281            type_ann,
2282            value,
2283            span,
2284        })
2285    }
2286
2287    fn parse_macro_decl(&mut self) -> Result<MacroDeclDef, ParseError> {
2288        let start = self.expect(&TokenKind::Macro)?.span;
2289        let mut name = if matches!(self.peek_kind(), TokenKind::Ident(_)) {
2290            self.expect_ident()?
2291        } else {
2292            "__macro".to_string()
2293        };
2294        if matches!(self.peek_kind(), TokenKind::Bang) {
2295            self.advance();
2296            name.push('!');
2297        }
2298
2299        let mut params = Vec::new();
2300        if matches!(self.peek_kind(), TokenKind::LParen) {
2301            self.advance();
2302            while !matches!(self.peek_kind(), TokenKind::RParen | TokenKind::Eof) {
2303                if matches!(self.peek_kind(), TokenKind::Comma) {
2304                    self.advance();
2305                    continue;
2306                }
2307                if Self::is_identifier_like(self.peek_kind()) {
2308                    params.push(self.expect_ident()?);
2309                } else {
2310                    self.advance();
2311                }
2312            }
2313            if matches!(self.peek_kind(), TokenKind::RParen) {
2314                self.advance();
2315            }
2316        }
2317
2318        self.consume_block_until_end();
2319        let span = start.merge(self.current().span);
2320        Ok(MacroDeclDef {
2321            name,
2322            params,
2323            body: vec![],
2324            span,
2325        })
2326    }
2327
2328    fn parse_optional_generic_params(&mut self) -> Result<Vec<GenericParam>, ParseError> {
2329        if !matches!(self.peek_kind(), TokenKind::LBracket) {
2330            return Ok(vec![]);
2331        }
2332        self.advance();
2333        let mut params = Vec::new();
2334        while !matches!(self.peek_kind(), TokenKind::RBracket) {
2335            if !params.is_empty() {
2336                self.expect(&TokenKind::Comma)?;
2337            }
2338            let ps = self.current().span;
2339            let name = self.expect_ident()?;
2340            let bounds = if matches!(self.peek_kind(), TokenKind::Colon) {
2341                self.advance();
2342                let mut b = vec![self.expect_ident()?];
2343                while matches!(self.peek_kind(), TokenKind::Plus) {
2344                    self.advance();
2345                    b.push(self.expect_ident()?);
2346                }
2347                b
2348            } else {
2349                vec![]
2350            };
2351            params.push(GenericParam {
2352                name,
2353                bounds,
2354                span: ps,
2355            });
2356        }
2357        self.expect(&TokenKind::RBracket)?;
2358        Ok(params)
2359    }
2360
2361    fn parse_optional_effect_row(&mut self) -> Result<Vec<String>, ParseError> {
2362        if !matches!(self.peek_kind(), TokenKind::Slash) {
2363            return Ok(vec![]);
2364        }
2365        self.advance();
2366        self.skip_newlines();
2367        if matches!(self.peek_kind(), TokenKind::LBrace) {
2368            self.advance();
2369            let mut effects = Vec::new();
2370            while !matches!(self.peek_kind(), TokenKind::RBrace | TokenKind::Eof) {
2371                if matches!(self.peek_kind(), TokenKind::Comma) {
2372                    self.advance();
2373                    continue;
2374                }
2375                effects.push(self.parse_effect_name()?);
2376                if matches!(self.peek_kind(), TokenKind::Comma) {
2377                    self.advance();
2378                }
2379            }
2380            self.expect(&TokenKind::RBrace)?;
2381            Ok(effects)
2382        } else if matches!(
2383            self.peek_kind(),
2384            TokenKind::Ident(_)
2385                | TokenKind::Emit
2386                | TokenKind::Role
2387                | TokenKind::Use
2388                | TokenKind::Tool
2389                | TokenKind::Grant
2390                | TokenKind::Await
2391                | TokenKind::Async
2392                | TokenKind::Parallel
2393        ) {
2394            Ok(vec![self.parse_effect_name()?])
2395        } else {
2396            Ok(vec![])
2397        }
2398    }
2399
2400    fn parse_effect_name(&mut self) -> Result<String, ParseError> {
2401        match self.peek_kind().clone() {
2402            TokenKind::Ident(_) => self.expect_ident(),
2403            TokenKind::Emit => {
2404                self.advance();
2405                Ok("emit".into())
2406            }
2407            TokenKind::Role => {
2408                self.advance();
2409                Ok("role".into())
2410            }
2411            TokenKind::Use => {
2412                self.advance();
2413                Ok("use".into())
2414            }
2415            TokenKind::Tool => {
2416                self.advance();
2417                Ok("tool".into())
2418            }
2419            TokenKind::Grant => {
2420                self.advance();
2421                Ok("grant".into())
2422            }
2423            TokenKind::Await => {
2424                self.advance();
2425                Ok("await".into())
2426            }
2427            TokenKind::Async => {
2428                self.advance();
2429                Ok("async".into())
2430            }
2431            TokenKind::Parallel => {
2432                self.advance();
2433                Ok("parallel".into())
2434            }
2435            _ => {
2436                let tok = self.current().clone();
2437                Err(ParseError::Unexpected {
2438                    found: format!("{}", tok.kind),
2439                    expected: "effect name".into(),
2440                    line: tok.span.line,
2441                    col: tok.span.col,
2442                })
2443            }
2444        }
2445    }
2446
2447    fn parse_attribute_decl(&mut self) -> Result<AddonDecl, ParseError> {
2448        let start = self.expect(&TokenKind::At)?.span;
2449        let name = match self.peek_kind() {
2450            TokenKind::Ident(_) => Some(self.expect_ident()?),
2451            _ => None,
2452        };
2453
2454        if matches!(self.peek_kind(), TokenKind::LParen) {
2455            self.consume_parenthesized();
2456        }
2457
2458        if matches!(self.peek_kind(), TokenKind::Newline) {
2459            // Block attributes such as `@location_policy` can carry an indented
2460            // payload and a trailing `end`.
2461            self.skip_newlines();
2462            if matches!(self.peek_kind(), TokenKind::Indent) {
2463                self.consume_indented_payload();
2464                self.skip_newlines();
2465                if matches!(self.peek_kind(), TokenKind::End) {
2466                    self.advance();
2467                }
2468            }
2469        } else {
2470            self.consume_rest_of_line();
2471        }
2472
2473        let end = self.current().span;
2474        Ok(AddonDecl {
2475            kind: "attribute".into(),
2476            name,
2477            span: start.merge(end),
2478        })
2479    }
2480
2481    fn parse_addon_decl(&mut self) -> Result<AddonDecl, ParseError> {
2482        let start = self.current().span;
2483        let kind = self.expect_ident()?;
2484        let name = self.parse_optional_decl_name()?;
2485
2486        let is_line_decl = matches!(kind.as_str(), "bind" | "pattern");
2487        if is_line_decl {
2488            self.consume_rest_of_line();
2489        } else {
2490            self.consume_block_until_end();
2491        }
2492
2493        let end = self.current().span;
2494        Ok(AddonDecl {
2495            kind,
2496            name,
2497            span: start.merge(end),
2498        })
2499    }
2500
2501    fn parse_agent_decl(&mut self) -> Result<AgentDecl, ParseError> {
2502        let start = self.current().span;
2503        let _kw = self.expect_ident()?;
2504        let name = self
2505            .parse_optional_decl_name()?
2506            .unwrap_or_else(|| "Agent".to_string());
2507
2508        let mut cells = Vec::new();
2509        let mut grants = Vec::new();
2510
2511        self.skip_newlines();
2512        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
2513        if has_indent {
2514            self.advance();
2515        }
2516        self.skip_newlines();
2517
2518        while !matches!(
2519            self.peek_kind(),
2520            TokenKind::End | TokenKind::Dedent | TokenKind::Eof
2521        ) {
2522            self.skip_newlines();
2523            if matches!(
2524                self.peek_kind(),
2525                TokenKind::End | TokenKind::Dedent | TokenKind::Eof
2526            ) {
2527                break;
2528            }
2529
2530            let is_async = matches!(self.peek_kind(), TokenKind::Async);
2531            if is_async {
2532                self.advance();
2533                self.skip_newlines();
2534            }
2535
2536            match self.peek_kind() {
2537                TokenKind::Cell => {
2538                    let mut cell = self.parse_cell(true)?;
2539                    cell.is_async = is_async;
2540                    if cell.params.first().map(|p| p.name.as_str()) != Some("self") {
2541                        let self_span = cell.span;
2542                        cell.params.insert(
2543                            0,
2544                            Param {
2545                                name: "self".into(),
2546                                ty: TypeExpr::Named("Json".into(), self_span),
2547                                default_value: None,
2548                                variadic: false,
2549                                span: self_span,
2550                            },
2551                        );
2552                    }
2553                    cells.push(cell);
2554                }
2555                TokenKind::Grant => grants.push(self.parse_grant()?),
2556                TokenKind::At => {
2557                    let _ = self.parse_attribute_decl()?;
2558                }
2559                TokenKind::Role | TokenKind::Tool => {
2560                    self.advance();
2561                    self.consume_section_or_line_after_name();
2562                }
2563                TokenKind::Impl => {
2564                    self.advance();
2565                    self.consume_block_until_end();
2566                }
2567                TokenKind::Ident(_) => {
2568                    self.consume_named_section_or_line();
2569                }
2570                _ => {
2571                    self.consume_rest_of_line();
2572                }
2573            }
2574            self.skip_newlines();
2575        }
2576
2577        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
2578            self.advance();
2579        }
2580        while matches!(
2581            self.peek_kind(),
2582            TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent
2583        ) {
2584            self.advance();
2585        }
2586        let end_span = if matches!(self.peek_kind(), TokenKind::End) {
2587            self.advance().span
2588        } else {
2589            self.current().span
2590        };
2591        Ok(AgentDecl {
2592            name,
2593            cells,
2594            grants,
2595            span: start.merge(end_span),
2596        })
2597    }
2598
2599    fn parse_effect_decl(&mut self) -> Result<EffectDecl, ParseError> {
2600        let start = self.current().span;
2601        let kw = self.expect_ident()?;
2602        if kw != "effect" {
2603            let tok = self.current().clone();
2604            return Err(ParseError::Unexpected {
2605                found: kw,
2606                expected: "effect".into(),
2607                line: tok.span.line,
2608                col: tok.span.col,
2609            });
2610        }
2611        let name = self
2612            .parse_optional_decl_name()?
2613            .unwrap_or_else(|| "Effect".to_string());
2614        self.skip_newlines();
2615        let mut operations = Vec::new();
2616        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
2617        if has_indent {
2618            self.advance();
2619        }
2620        self.skip_newlines();
2621        while !matches!(
2622            self.peek_kind(),
2623            TokenKind::End | TokenKind::Dedent | TokenKind::Eof
2624        ) {
2625            self.skip_newlines();
2626            if matches!(
2627                self.peek_kind(),
2628                TokenKind::End | TokenKind::Dedent | TokenKind::Eof
2629            ) {
2630                break;
2631            }
2632            if matches!(self.peek_kind(), TokenKind::Cell) {
2633                operations.push(self.parse_cell(false)?);
2634            } else {
2635                self.consume_rest_of_line();
2636            }
2637            self.skip_newlines();
2638        }
2639        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
2640            self.advance();
2641        }
2642        while matches!(
2643            self.peek_kind(),
2644            TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent
2645        ) {
2646            self.advance();
2647        }
2648        let end_span = if matches!(self.peek_kind(), TokenKind::End) {
2649            self.advance().span
2650        } else {
2651            self.current().span
2652        };
2653        Ok(EffectDecl {
2654            name,
2655            operations,
2656            span: start.merge(end_span),
2657        })
2658    }
2659
2660    fn parse_process_decl(&mut self) -> Result<ProcessDecl, ParseError> {
2661        let start = self.current().span;
2662        let kind = self.expect_ident()?;
2663        let name = self
2664            .parse_optional_decl_name()?
2665            .unwrap_or_else(|| "Process".to_string());
2666        let mut cells = Vec::new();
2667        let mut grants = Vec::new();
2668        let mut pipeline_stages = Vec::new();
2669        let mut machine_initial = None;
2670        let mut machine_states = Vec::new();
2671
2672        self.skip_newlines();
2673        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
2674        if has_indent {
2675            self.advance();
2676        }
2677        self.skip_newlines();
2678
2679        while !matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
2680            self.skip_newlines();
2681            if matches!(self.peek_kind(), TokenKind::Dedent) {
2682                self.advance();
2683                continue;
2684            }
2685            if matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
2686                break;
2687            }
2688
2689            let is_async = matches!(self.peek_kind(), TokenKind::Async);
2690            if is_async {
2691                self.advance();
2692                self.skip_newlines();
2693            }
2694
2695            match self.peek_kind() {
2696                TokenKind::Cell => {
2697                    let mut cell = self.parse_cell(true)?;
2698                    cell.is_async = is_async;
2699                    if cell.params.first().map(|p| p.name.as_str()) != Some("self") {
2700                        let self_span = cell.span;
2701                        cell.params.insert(
2702                            0,
2703                            Param {
2704                                name: "self".into(),
2705                                ty: TypeExpr::Named("Json".into(), self_span),
2706                                default_value: None,
2707                                variadic: false,
2708                                span: self_span,
2709                            },
2710                        );
2711                    }
2712                    cells.push(cell);
2713                }
2714                TokenKind::Grant => grants.push(self.parse_grant()?),
2715                TokenKind::Ident(name) if kind == "pipeline" && name == "stages" => {
2716                    pipeline_stages = self.parse_pipeline_stages_decl()?;
2717                }
2718                TokenKind::Ident(name) if kind == "machine" && name == "initial" => {
2719                    machine_initial = Some(self.parse_machine_initial_decl()?);
2720                }
2721                TokenKind::Ident(name) if kind == "machine" && name == "state" => {
2722                    machine_states.push(self.parse_machine_state_decl()?);
2723                }
2724                TokenKind::At => {
2725                    let _ = self.parse_attribute_decl()?;
2726                }
2727                TokenKind::Role | TokenKind::Tool => {
2728                    self.advance();
2729                    self.consume_section_or_line_after_name();
2730                }
2731                TokenKind::Record | TokenKind::Enum | TokenKind::Trait | TokenKind::Impl => {
2732                    self.advance();
2733                    self.consume_block_until_end();
2734                }
2735                TokenKind::Ident(name)
2736                    if matches!(
2737                        name.as_str(),
2738                        "state"
2739                            | "on_enter"
2740                            | "on_event"
2741                            | "on_error"
2742                            | "on_input"
2743                            | "on_output"
2744                            | "on_violation"
2745                            | "on_timeout"
2746                            | "migrate"
2747                    ) =>
2748                {
2749                    self.advance();
2750                    self.consume_block_until_end();
2751                }
2752                TokenKind::Ident(_) => {
2753                    self.consume_named_section_or_line();
2754                }
2755                _ => {
2756                    self.consume_rest_of_line();
2757                }
2758            }
2759            self.skip_newlines();
2760        }
2761
2762        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
2763            self.advance();
2764        }
2765        while matches!(
2766            self.peek_kind(),
2767            TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent
2768        ) {
2769            self.advance();
2770        }
2771        let end_span = if matches!(self.peek_kind(), TokenKind::End) {
2772            self.advance().span
2773        } else {
2774            self.current().span
2775        };
2776
2777        Ok(ProcessDecl {
2778            kind,
2779            name,
2780            cells,
2781            grants,
2782            pipeline_stages,
2783            machine_initial,
2784            machine_states,
2785            span: start.merge(end_span),
2786        })
2787    }
2788
2789    fn parse_pipeline_stages_decl(&mut self) -> Result<Vec<String>, ParseError> {
2790        let kw = self.expect_ident()?;
2791        if kw != "stages" {
2792            let tok = self.current().clone();
2793            return Err(ParseError::Unexpected {
2794                found: kw,
2795                expected: "stages".into(),
2796                line: tok.span.line,
2797                col: tok.span.col,
2798            });
2799        }
2800        if matches!(self.peek_kind(), TokenKind::Colon) {
2801            self.advance();
2802        }
2803        self.skip_newlines();
2804        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
2805        if has_indent {
2806            self.advance();
2807        }
2808        self.skip_newlines();
2809        let mut stages = Vec::new();
2810        while !matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
2811            self.skip_newlines();
2812            if matches!(self.peek_kind(), TokenKind::Dedent) {
2813                self.advance();
2814                continue;
2815            }
2816            if matches!(self.peek_kind(), TokenKind::Indent) {
2817                self.advance();
2818                continue;
2819            }
2820            if matches!(self.peek_kind(), TokenKind::Arrow) {
2821                self.advance();
2822                self.skip_whitespace_tokens();
2823            }
2824            if matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
2825                break;
2826            }
2827            stages.push(self.parse_dotted_ident()?);
2828            self.consume_rest_of_line();
2829            self.skip_newlines();
2830        }
2831        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
2832            self.advance();
2833        }
2834        if matches!(self.peek_kind(), TokenKind::End) {
2835            self.advance();
2836        }
2837        Ok(stages)
2838    }
2839
2840    fn parse_machine_initial_decl(&mut self) -> Result<String, ParseError> {
2841        let kw = self.expect_ident()?;
2842        if kw != "initial" {
2843            let tok = self.current().clone();
2844            return Err(ParseError::Unexpected {
2845                found: kw,
2846                expected: "initial".into(),
2847                line: tok.span.line,
2848                col: tok.span.col,
2849            });
2850        }
2851        if matches!(self.peek_kind(), TokenKind::Colon) {
2852            self.advance();
2853        }
2854        let state = self.expect_ident()?;
2855        self.consume_rest_of_line();
2856        Ok(state)
2857    }
2858
2859    fn parse_machine_state_decl(&mut self) -> Result<MachineStateDecl, ParseError> {
2860        let start = self.current().span;
2861        let kw = self.expect_ident()?;
2862        if kw != "state" {
2863            let tok = self.current().clone();
2864            return Err(ParseError::Unexpected {
2865                found: kw,
2866                expected: "state".into(),
2867                line: tok.span.line,
2868                col: tok.span.col,
2869            });
2870        }
2871        let name = self.expect_ident()?;
2872        let params = if matches!(self.peek_kind(), TokenKind::LParen) {
2873            self.parse_machine_state_params()?
2874        } else {
2875            vec![]
2876        };
2877        let mut terminal = false;
2878        let mut guard = None;
2879        let mut transition_to = None;
2880        let mut transition_args = Vec::new();
2881
2882        self.skip_newlines();
2883        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
2884        if has_indent {
2885            self.advance();
2886        }
2887        self.skip_newlines();
2888
2889        while !matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
2890            self.skip_newlines();
2891            if matches!(self.peek_kind(), TokenKind::Dedent) {
2892                self.advance();
2893                continue;
2894            }
2895            match self.peek_kind() {
2896                TokenKind::Ident(id) if id == "terminal" => {
2897                    self.advance();
2898                    if matches!(self.peek_kind(), TokenKind::Colon) {
2899                        self.advance();
2900                    }
2901                    terminal = match self.peek_kind().clone() {
2902                        TokenKind::BoolLit(v) => {
2903                            self.advance();
2904                            v
2905                        }
2906                        TokenKind::Ident(text) if text == "true" => {
2907                            self.advance();
2908                            true
2909                        }
2910                        TokenKind::Ident(text) if text == "false" => {
2911                            self.advance();
2912                            false
2913                        }
2914                        _ => false,
2915                    };
2916                    self.consume_rest_of_line();
2917                }
2918                TokenKind::Ident(id) if id == "guard" => {
2919                    self.advance();
2920                    if matches!(self.peek_kind(), TokenKind::Colon) {
2921                        self.advance();
2922                    }
2923                    guard = Some(self.parse_expr(0)?);
2924                    self.consume_rest_of_line();
2925                }
2926                TokenKind::Ident(id) if id == "transition" => {
2927                    self.advance();
2928                    let (target, args) = self.parse_machine_transition_decl()?;
2929                    transition_to = Some(target);
2930                    transition_args = args;
2931                    self.consume_rest_of_line();
2932                }
2933                TokenKind::Ident(id)
2934                    if matches!(id.as_str(), "on_enter" | "on_event" | "on_timeout") =>
2935                {
2936                    self.advance();
2937                    self.consume_rest_of_line();
2938                    self.skip_newlines();
2939                    let nested_indent = matches!(self.peek_kind(), TokenKind::Indent);
2940                    if nested_indent {
2941                        self.advance();
2942                    }
2943                    self.skip_newlines();
2944                    while !matches!(self.peek_kind(), TokenKind::End | TokenKind::Eof) {
2945                        self.skip_newlines();
2946                        if matches!(self.peek_kind(), TokenKind::Dedent) {
2947                            self.advance();
2948                            continue;
2949                        }
2950                        if let TokenKind::Ident(id2) = self.peek_kind() {
2951                            if id2 == "transition" {
2952                                self.advance();
2953                                let (target, args) = self.parse_machine_transition_decl()?;
2954                                transition_to = Some(target);
2955                                transition_args = args;
2956                                self.consume_rest_of_line();
2957                                continue;
2958                            }
2959                        }
2960                        self.consume_rest_of_line();
2961                    }
2962                    if nested_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
2963                        self.advance();
2964                    }
2965                    if matches!(self.peek_kind(), TokenKind::End) {
2966                        self.advance();
2967                    }
2968                }
2969                _ => {
2970                    self.consume_rest_of_line();
2971                }
2972            }
2973            self.skip_newlines();
2974        }
2975
2976        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
2977            self.advance();
2978        }
2979        let end_span = if matches!(self.peek_kind(), TokenKind::End) {
2980            self.advance().span
2981        } else {
2982            self.current().span
2983        };
2984
2985        Ok(MachineStateDecl {
2986            name,
2987            params,
2988            terminal,
2989            guard,
2990            transition_to,
2991            transition_args,
2992            span: start.merge(end_span),
2993        })
2994    }
2995
2996    fn parse_machine_state_params(&mut self) -> Result<Vec<Param>, ParseError> {
2997        let mut params = Vec::new();
2998        self.expect(&TokenKind::LParen)?;
2999        self.bracket_depth += 1;
3000        self.skip_whitespace_tokens();
3001        while !matches!(self.peek_kind(), TokenKind::RParen | TokenKind::Eof) {
3002            if !params.is_empty() {
3003                self.expect(&TokenKind::Comma)?;
3004                self.skip_whitespace_tokens();
3005            }
3006            let ps = self.current().span;
3007            let pname = self.expect_ident()?;
3008            let pty = if matches!(self.peek_kind(), TokenKind::Colon) {
3009                self.advance();
3010                self.parse_type()?
3011            } else {
3012                TypeExpr::Named("Any".into(), ps)
3013            };
3014            params.push(Param {
3015                name: pname,
3016                ty: pty,
3017                default_value: None,
3018                variadic: false,
3019                span: ps,
3020            });
3021            self.skip_whitespace_tokens();
3022        }
3023        self.bracket_depth -= 1;
3024        self.expect(&TokenKind::RParen)?;
3025        Ok(params)
3026    }
3027
3028    fn parse_machine_transition_decl(&mut self) -> Result<(String, Vec<Expr>), ParseError> {
3029        let target = self.expect_ident()?;
3030        let mut args = Vec::new();
3031        if matches!(self.peek_kind(), TokenKind::LParen) {
3032            self.expect(&TokenKind::LParen)?;
3033            self.bracket_depth += 1;
3034            self.skip_whitespace_tokens();
3035            while !matches!(self.peek_kind(), TokenKind::RParen | TokenKind::Eof) {
3036                if !args.is_empty() {
3037                    self.expect(&TokenKind::Comma)?;
3038                    self.skip_whitespace_tokens();
3039                }
3040                args.push(self.parse_expr(0)?);
3041                self.skip_whitespace_tokens();
3042            }
3043            self.bracket_depth -= 1;
3044            self.expect(&TokenKind::RParen)?;
3045        }
3046        Ok((target, args))
3047    }
3048
3049    fn parse_effect_bind_decl(&mut self) -> Result<EffectBindDecl, ParseError> {
3050        let start = self.current().span;
3051        let bind_kw = self.expect_ident()?;
3052        if bind_kw != "bind" {
3053            let tok = self.current().clone();
3054            return Err(ParseError::Unexpected {
3055                found: bind_kw,
3056                expected: "bind".into(),
3057                line: tok.span.line,
3058                col: tok.span.col,
3059            });
3060        }
3061        let effect_kw = self.expect_ident()?;
3062        if effect_kw != "effect" {
3063            let tok = self.current().clone();
3064            return Err(ParseError::Unexpected {
3065                found: effect_kw,
3066                expected: "effect".into(),
3067                line: tok.span.line,
3068                col: tok.span.col,
3069            });
3070        }
3071        let effect_path = self.parse_dotted_ident()?;
3072        let to_kw = self.expect_ident()?;
3073        if to_kw != "to" {
3074            let tok = self.current().clone();
3075            return Err(ParseError::Unexpected {
3076                found: to_kw,
3077                expected: "to".into(),
3078                line: tok.span.line,
3079                col: tok.span.col,
3080            });
3081        }
3082        let tool_alias = self.parse_dotted_ident()?;
3083        self.consume_rest_of_line();
3084        Ok(EffectBindDecl {
3085            effect_path,
3086            tool_alias,
3087            span: start.merge(self.current().span),
3088        })
3089    }
3090
3091    fn parse_handler_decl(&mut self) -> Result<HandlerDecl, ParseError> {
3092        let start = self.current().span;
3093        let kw = self.expect_ident()?;
3094        if kw != "handler" {
3095            let tok = self.current().clone();
3096            return Err(ParseError::Unexpected {
3097                found: kw,
3098                expected: "handler".into(),
3099                line: tok.span.line,
3100                col: tok.span.col,
3101            });
3102        }
3103        let name = self
3104            .parse_optional_decl_name()?
3105            .unwrap_or_else(|| "Handler".to_string());
3106        self.skip_newlines();
3107        let mut handles = Vec::new();
3108        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
3109        if has_indent {
3110            self.advance();
3111        }
3112        self.skip_newlines();
3113        while !matches!(
3114            self.peek_kind(),
3115            TokenKind::End | TokenKind::Dedent | TokenKind::Eof
3116        ) {
3117            self.skip_newlines();
3118            if matches!(
3119                self.peek_kind(),
3120                TokenKind::End | TokenKind::Dedent | TokenKind::Eof
3121            ) {
3122                break;
3123            }
3124            if matches!(self.peek_kind(), TokenKind::Ident(s) if s == "handle") {
3125                handles.push(self.parse_handle_cell()?);
3126            } else if matches!(self.peek_kind(), TokenKind::Cell) {
3127                handles.push(self.parse_cell(true)?);
3128            } else {
3129                self.consume_rest_of_line();
3130            }
3131            self.skip_newlines();
3132        }
3133        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
3134            self.advance();
3135        }
3136        while matches!(
3137            self.peek_kind(),
3138            TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent
3139        ) {
3140            self.advance();
3141        }
3142        let end_span = if matches!(self.peek_kind(), TokenKind::End) {
3143            self.advance().span
3144        } else {
3145            self.current().span
3146        };
3147        Ok(HandlerDecl {
3148            name,
3149            handles,
3150            span: start.merge(end_span),
3151        })
3152    }
3153
3154    fn parse_handle_cell(&mut self) -> Result<CellDef, ParseError> {
3155        let start = self.current().span;
3156        let kw = self.expect_ident()?;
3157        if kw != "handle" {
3158            let tok = self.current().clone();
3159            return Err(ParseError::Unexpected {
3160                found: kw,
3161                expected: "handle".into(),
3162                line: tok.span.line,
3163                col: tok.span.col,
3164            });
3165        }
3166        let name = self.parse_dotted_ident()?;
3167        self.expect(&TokenKind::LParen)?;
3168        self.bracket_depth += 1;
3169        let mut params = Vec::new();
3170        self.skip_whitespace_tokens();
3171        while !matches!(self.peek_kind(), TokenKind::RParen) {
3172            if !params.is_empty() {
3173                self.expect(&TokenKind::Comma)?;
3174                self.skip_whitespace_tokens();
3175            }
3176            let variadic = if matches!(self.peek_kind(), TokenKind::DotDot | TokenKind::DotDotDot) {
3177                self.advance();
3178                true
3179            } else {
3180                false
3181            };
3182            let ps = self.current().span;
3183            let pname = self.expect_ident()?;
3184            let pty = if matches!(self.peek_kind(), TokenKind::Colon) {
3185                self.advance();
3186                self.parse_type()?
3187            } else {
3188                TypeExpr::Named("Any".into(), ps)
3189            };
3190            let default_value = if matches!(self.peek_kind(), TokenKind::Assign) {
3191                self.advance();
3192                Some(self.parse_expr(0)?)
3193            } else {
3194                None
3195            };
3196            params.push(Param {
3197                name: pname,
3198                ty: pty,
3199                default_value,
3200                variadic,
3201                span: ps,
3202            });
3203            self.skip_whitespace_tokens();
3204        }
3205        self.bracket_depth -= 1;
3206        self.expect(&TokenKind::RParen)?;
3207        let ret = if matches!(self.peek_kind(), TokenKind::Arrow) {
3208            self.advance();
3209            Some(self.parse_type()?)
3210        } else {
3211            None
3212        };
3213        let effects = self.parse_optional_effect_row()?;
3214
3215        if matches!(self.peek_kind(), TokenKind::Assign) {
3216            self.advance();
3217            let expr = self.parse_expr(0)?;
3218            let span = start.merge(expr.span());
3219            return Ok(CellDef {
3220                name,
3221                generic_params: vec![],
3222                params,
3223                return_type: ret,
3224                effects,
3225                body: vec![Stmt::Return(ReturnStmt { value: expr, span })],
3226                is_pub: false,
3227                is_async: false,
3228                where_clauses: vec![],
3229                span,
3230            });
3231        }
3232
3233        if matches!(
3234            self.peek_kind(),
3235            TokenKind::Newline | TokenKind::Eof | TokenKind::Dedent
3236        ) {
3237            let mut look = self.pos;
3238            while matches!(
3239                self.tokens.get(look).map(|t| &t.kind),
3240                Some(TokenKind::Newline)
3241            ) {
3242                look += 1;
3243            }
3244            if !matches!(
3245                self.tokens.get(look).map(|t| &t.kind),
3246                Some(TokenKind::Indent)
3247            ) {
3248                let end_span = self.current().span;
3249                return Ok(CellDef {
3250                    name,
3251                    generic_params: vec![],
3252                    params,
3253                    return_type: ret,
3254                    effects,
3255                    body: vec![],
3256                    is_pub: false,
3257                    is_async: false,
3258                    where_clauses: vec![],
3259                    span: start.merge(end_span),
3260                });
3261            }
3262        }
3263
3264        self.skip_newlines();
3265        let body = self.parse_block()?;
3266        let end_span = self.expect(&TokenKind::End)?.span;
3267        Ok(CellDef {
3268            name,
3269            generic_params: vec![],
3270            params,
3271            return_type: ret,
3272            effects,
3273            body,
3274            is_pub: false,
3275            is_async: false,
3276            where_clauses: vec![],
3277            span: start.merge(end_span),
3278        })
3279    }
3280
3281    fn consume_parenthesized(&mut self) {
3282        if !matches!(self.peek_kind(), TokenKind::LParen) {
3283            return;
3284        }
3285        let mut depth = 0usize;
3286        while !self.at_end() {
3287            match self.peek_kind() {
3288                TokenKind::LParen => {
3289                    depth += 1;
3290                    self.advance();
3291                }
3292                TokenKind::RParen => {
3293                    self.advance();
3294                    if depth == 0 {
3295                        break;
3296                    }
3297                    depth -= 1;
3298                    if depth == 0 {
3299                        break;
3300                    }
3301                }
3302                _ => {
3303                    self.advance();
3304                }
3305            }
3306            if depth == 0 && !matches!(self.peek_kind(), TokenKind::RParen) {
3307                break;
3308            }
3309        }
3310    }
3311
3312    fn consume_rest_of_line(&mut self) {
3313        while !matches!(self.peek_kind(), TokenKind::Newline | TokenKind::Eof) {
3314            self.advance();
3315        }
3316    }
3317
3318    fn consume_indented_payload(&mut self) {
3319        if !matches!(self.peek_kind(), TokenKind::Indent) {
3320            return;
3321        }
3322        self.advance();
3323        let mut depth = 0usize;
3324        while !self.at_end() {
3325            match self.peek_kind() {
3326                TokenKind::Indent => {
3327                    depth += 1;
3328                    self.advance();
3329                }
3330                TokenKind::Dedent => {
3331                    if depth == 0 {
3332                        self.advance();
3333                        break;
3334                    }
3335                    depth -= 1;
3336                    self.advance();
3337                }
3338                _ => {
3339                    self.advance();
3340                }
3341            }
3342        }
3343    }
3344
3345    fn consume_block_until_end(&mut self) {
3346        let mut depth = 0usize;
3347        while !self.at_end() {
3348            match self.peek_kind() {
3349                TokenKind::End => {
3350                    if depth == 0 {
3351                        self.advance();
3352                        break;
3353                    }
3354                    depth -= 1;
3355                    self.advance();
3356                }
3357                TokenKind::Record
3358                | TokenKind::Enum
3359                | TokenKind::Cell
3360                | TokenKind::Trait
3361                | TokenKind::Impl
3362                | TokenKind::If
3363                | TokenKind::For
3364                | TokenKind::Match
3365                | TokenKind::While
3366                | TokenKind::Loop => {
3367                    depth += 1;
3368                    self.advance();
3369                }
3370                TokenKind::Role | TokenKind::Tool if self.consumes_section_colon_block() => {
3371                    depth += 1;
3372                    self.advance();
3373                }
3374                TokenKind::Ident(name)
3375                    if matches!(
3376                        name.as_str(),
3377                        "effect"
3378                            | "handler"
3379                            | "agent"
3380                            | "pipeline"
3381                            | "orchestration"
3382                            | "machine"
3383                            | "memory"
3384                            | "guardrail"
3385                            | "eval"
3386                            | "handle"
3387                            | "state"
3388                            | "on_enter"
3389                            | "on_event"
3390                            | "on_error"
3391                            | "on_input"
3392                            | "on_output"
3393                            | "on_violation"
3394                            | "on_timeout"
3395                            | "migrate"
3396                            | "approve"
3397                            | "checkpoint"
3398                            | "escalate"
3399                            | "observe"
3400                            | "with"
3401                            | "stages"
3402                            | "thresholds"
3403                    ) =>
3404                {
3405                    depth += 1;
3406                    self.advance();
3407                }
3408                TokenKind::Ident(_) if self.consumes_section_colon_block() => {
3409                    depth += 1;
3410                    self.advance();
3411                }
3412                _ => {
3413                    self.advance();
3414                }
3415            }
3416        }
3417    }
3418
3419    fn consume_named_section_or_line(&mut self) {
3420        self.advance(); // section name
3421        self.consume_section_or_line_after_name();
3422    }
3423
3424    fn consume_section_or_line_after_name(&mut self) {
3425        if matches!(self.peek_kind(), TokenKind::Colon) {
3426            self.advance();
3427            if matches!(self.peek_kind(), TokenKind::Newline) {
3428                self.skip_newlines();
3429                if matches!(self.peek_kind(), TokenKind::Indent) {
3430                    self.consume_indented_payload();
3431                    self.skip_newlines();
3432                    if matches!(self.peek_kind(), TokenKind::End) {
3433                        self.advance();
3434                    }
3435                    return;
3436                }
3437            }
3438            if matches!(
3439                self.peek_kind(),
3440                TokenKind::LBracket | TokenKind::LBrace | TokenKind::LParen
3441            ) {
3442                self.consume_balanced_group();
3443                self.consume_rest_of_line();
3444                return;
3445            }
3446            self.consume_rest_of_line();
3447            return;
3448        }
3449        self.consume_rest_of_line();
3450        if matches!(self.peek_kind(), TokenKind::Newline) {
3451            self.skip_newlines();
3452            if matches!(self.peek_kind(), TokenKind::Indent) {
3453                self.consume_indented_payload();
3454                self.skip_newlines();
3455                if matches!(self.peek_kind(), TokenKind::End) {
3456                    self.advance();
3457                }
3458            }
3459        }
3460    }
3461
3462    fn consume_balanced_group(&mut self) {
3463        let (open, close) = match self.peek_kind() {
3464            TokenKind::LBracket => (TokenKind::LBracket, TokenKind::RBracket),
3465            TokenKind::LBrace => (TokenKind::LBrace, TokenKind::RBrace),
3466            TokenKind::LParen => (TokenKind::LParen, TokenKind::RParen),
3467            _ => return,
3468        };
3469        let mut depth = 0usize;
3470        while !self.at_end() {
3471            if std::mem::discriminant(self.peek_kind()) == std::mem::discriminant(&open) {
3472                depth += 1;
3473                self.advance();
3474                continue;
3475            }
3476            if std::mem::discriminant(self.peek_kind()) == std::mem::discriminant(&close) {
3477                self.advance();
3478                depth -= 1;
3479                if depth == 0 {
3480                    break;
3481                }
3482                continue;
3483            }
3484            self.advance();
3485        }
3486    }
3487
3488    fn pattern_binding_name(pattern: &Pattern) -> Option<String> {
3489        match pattern {
3490            Pattern::Ident(name, _) => Some(name.clone()),
3491            Pattern::TypeCheck { name, .. } => Some(name.clone()),
3492            Pattern::Variant(_, Some(inner), _) => Self::pattern_binding_name(inner),
3493            Pattern::TupleDestructure { elements, .. }
3494            | Pattern::ListDestructure { elements, .. } => {
3495                elements.iter().find_map(Self::pattern_binding_name)
3496            }
3497            Pattern::RecordDestructure { fields, .. } => fields.iter().find_map(|(name, pat)| {
3498                pat.as_ref()
3499                    .and_then(Self::pattern_binding_name)
3500                    .or_else(|| Some(name.clone()))
3501            }),
3502            Pattern::Guard { inner, .. } => Self::pattern_binding_name(inner),
3503            Pattern::Or { patterns, .. } => patterns.iter().find_map(Self::pattern_binding_name),
3504            _ => None,
3505        }
3506    }
3507
3508    fn parse_variant_binding_candidate(&mut self) -> Result<Option<Box<Pattern>>, ParseError> {
3509        let mut binding = None;
3510        if !matches!(self.peek_kind(), TokenKind::RParen) {
3511            let save = self.pos;
3512            match self.parse_pattern() {
3513                Ok(pattern) => binding = Some(Box::new(pattern)),
3514                Err(_) => {
3515                    self.pos = save;
3516                    self.consume_variant_arg_tokens();
3517                }
3518            }
3519            while !matches!(self.peek_kind(), TokenKind::RParen | TokenKind::Eof) {
3520                if matches!(self.peek_kind(), TokenKind::Comma) {
3521                    self.advance();
3522                    if matches!(self.peek_kind(), TokenKind::RParen) {
3523                        break;
3524                    }
3525                }
3526                self.consume_variant_arg_tokens();
3527            }
3528        }
3529        Ok(binding)
3530    }
3531
3532    fn paren_looks_like_record_destructure(&self) -> bool {
3533        if !matches!(self.peek_kind(), TokenKind::LParen) {
3534            return false;
3535        }
3536        let mut i = self.pos + 1;
3537        let mut depth = 0usize;
3538        while let Some(tok) = self.tokens.get(i) {
3539            match tok.kind {
3540                TokenKind::LParen => depth += 1,
3541                TokenKind::RParen => {
3542                    if depth == 0 {
3543                        break;
3544                    }
3545                    depth -= 1;
3546                }
3547                TokenKind::Colon | TokenKind::DotDot | TokenKind::DotDotDot if depth == 0 => {
3548                    return true;
3549                }
3550                _ => {}
3551            }
3552            i += 1;
3553        }
3554        false
3555    }
3556
3557    fn paren_contains_top_level_arrow(&self) -> bool {
3558        let mut i = self.pos;
3559        // Caller has already consumed the opening '(' for this group.
3560        let mut depth = 1usize;
3561        while let Some(tok) = self.tokens.get(i) {
3562            match tok.kind {
3563                TokenKind::LParen => depth += 1,
3564                TokenKind::RParen => {
3565                    depth -= 1;
3566                    if depth == 0 {
3567                        break;
3568                    }
3569                }
3570                TokenKind::Arrow if depth == 1 => return true,
3571                _ => {}
3572            }
3573            i += 1;
3574        }
3575        false
3576    }
3577
3578    fn consume_variant_arg_tokens(&mut self) {
3579        let mut depth = 0usize;
3580        loop {
3581            match self.peek_kind() {
3582                TokenKind::LParen | TokenKind::LBracket | TokenKind::LBrace => {
3583                    depth += 1;
3584                    self.advance();
3585                }
3586                TokenKind::RParen | TokenKind::RBracket | TokenKind::RBrace => {
3587                    if depth == 0 {
3588                        break;
3589                    }
3590                    depth -= 1;
3591                    self.advance();
3592                }
3593                TokenKind::Comma if depth == 0 => break,
3594                TokenKind::Eof => break,
3595                _ => {
3596                    self.advance();
3597                }
3598            }
3599        }
3600    }
3601
3602    fn parse_optional_decl_name(&mut self) -> Result<Option<String>, ParseError> {
3603        if matches!(self.peek_kind(), TokenKind::Ident(_)) {
3604            return Ok(Some(self.expect_ident()?));
3605        }
3606        if matches!(self.peek_kind(), TokenKind::Lt) {
3607            // Template notation in specification snippets, e.g. `agent <Name>`.
3608            self.advance(); // <
3609            let mut buf = String::new();
3610            while !matches!(self.peek_kind(), TokenKind::Gt | TokenKind::Eof) {
3611                buf.push_str(&format!("{}", self.current().kind));
3612                self.advance();
3613            }
3614            if matches!(self.peek_kind(), TokenKind::Gt) {
3615                self.advance();
3616            }
3617            if !buf.trim().is_empty() {
3618                return Ok(Some(buf.trim().to_string()));
3619            }
3620        }
3621        Ok(None)
3622    }
3623
3624    fn parse_assignment_target(&mut self) -> Result<String, ParseError> {
3625        let mut parts = Vec::new();
3626        match self.peek_kind().clone() {
3627            TokenKind::SelfKw => {
3628                self.advance();
3629                parts.push("self".to_string());
3630            }
3631            _ if Self::is_identifier_like(self.peek_kind()) => {
3632                parts.push(self.expect_ident()?);
3633            }
3634            _ => {
3635                let tok = self.current().clone();
3636                return Err(ParseError::Unexpected {
3637                    found: format!("{}", tok.kind),
3638                    expected: "assignment target".into(),
3639                    line: tok.span.line,
3640                    col: tok.span.col,
3641                });
3642            }
3643        }
3644
3645        while matches!(self.peek_kind(), TokenKind::Dot) {
3646            self.advance();
3647            parts.push(self.expect_ident()?);
3648        }
3649
3650        Ok(parts.join("."))
3651    }
3652
3653    fn parse_expr_stmt(&mut self) -> Result<Stmt, ParseError> {
3654        let expr = self.parse_expr(0)?;
3655        let mut span = expr.span();
3656        if matches!(self.peek_kind(), TokenKind::In) {
3657            self.advance();
3658            self.skip_newlines();
3659            if matches!(self.peek_kind(), TokenKind::Indent) {
3660                self.consume_indented_payload();
3661                self.skip_newlines();
3662                if matches!(self.peek_kind(), TokenKind::End) {
3663                    span = span.merge(self.advance().span);
3664                }
3665            } else {
3666                self.consume_block_until_end();
3667                span = span.merge(self.current().span);
3668            }
3669        }
3670        Ok(Stmt::Expr(ExprStmt { expr, span }))
3671    }
3672
3673    /// Check if the current position is an assignment (ident followed by =)
3674    fn is_assignment(&self) -> bool {
3675        let mut i = self.pos;
3676        if !matches!(self.tokens.get(i).map(|t| &t.kind), Some(k) if Self::is_identifier_like(k)) {
3677            return false;
3678        }
3679        i += 1;
3680        while matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Dot))
3681            && matches!(
3682                self.tokens.get(i + 1).map(|t| &t.kind),
3683                Some(TokenKind::Ident(_))
3684            )
3685        {
3686            i += 2;
3687        }
3688        matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Assign))
3689    }
3690
3691    /// Parse an assignment statement: ident = expr
3692    fn parse_assign(&mut self) -> Result<Stmt, ParseError> {
3693        let start = self.tokens[self.pos].span;
3694        let name = self.parse_assignment_target()?;
3695        self.expect(&TokenKind::Assign)?;
3696        let value = self.parse_expr(0)?;
3697        let span = start.merge(value.span());
3698        Ok(Stmt::Assign(AssignStmt {
3699            target: name,
3700            value,
3701            span,
3702        }))
3703    }
3704
3705    // ── Use Tool / Grant ──
3706
3707    fn parse_use_tool(&mut self) -> Result<UseToolDecl, ParseError> {
3708        let start = self.expect(&TokenKind::Use)?.span;
3709        self.expect(&TokenKind::Tool)?;
3710        // Could be: `use tool llm.chat as Chat` or `use tool mcp "url" as Name`
3711        let mcp_url = if matches!(self.peek_kind(), TokenKind::Ident(ref s) if s == "mcp") {
3712            self.advance();
3713            let url = self.expect_string()?;
3714            Some(url)
3715        } else {
3716            None
3717        };
3718        let tool_path = if mcp_url.is_none() {
3719            let mut path = self.parse_dotted_ident()?;
3720            if matches!(self.peek_kind(), TokenKind::At) {
3721                self.advance();
3722                let mut ver = String::new();
3723                while !matches!(
3724                    self.peek_kind(),
3725                    TokenKind::As | TokenKind::Newline | TokenKind::Eof
3726                ) {
3727                    ver.push_str(&format!("{}", self.current().kind));
3728                    self.advance();
3729                }
3730                if !ver.is_empty() {
3731                    path = format!("{path}@{ver}");
3732                }
3733            }
3734            path
3735        } else {
3736            String::new()
3737        };
3738        self.expect(&TokenKind::As)?;
3739        let alias = if matches!(self.peek_kind(), TokenKind::Star) {
3740            self.advance();
3741            "__all__".to_string()
3742        } else {
3743            self.expect_ident()?
3744        };
3745        Ok(UseToolDecl {
3746            tool_path,
3747            alias,
3748            mcp_url,
3749            span: start,
3750        })
3751    }
3752
3753    fn parse_grant(&mut self) -> Result<GrantDecl, ParseError> {
3754        let start = self.expect(&TokenKind::Grant)?.span;
3755        let alias = self.parse_dotted_ident()?;
3756        let mut constraints = Vec::new();
3757        self.skip_newlines();
3758        // Parse constraints: key value pairs on same line or indented
3759        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
3760        if has_indent {
3761            self.advance();
3762            self.skip_newlines();
3763        }
3764        while matches!(self.peek_kind(), TokenKind::Ident(_)) {
3765            let cs = self.current().span;
3766            let key = self.expect_ident()?;
3767            let value = self.parse_expr(0)?;
3768            constraints.push(GrantConstraint {
3769                key,
3770                value,
3771                span: cs,
3772            });
3773            self.consume_rest_of_line();
3774            self.skip_newlines();
3775            if !has_indent {
3776                break;
3777            } // single-line grants
3778        }
3779        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
3780            self.advance();
3781        }
3782        Ok(GrantDecl {
3783            tool_alias: alias,
3784            constraints,
3785            span: start,
3786        })
3787    }
3788
3789    // ── Types ──
3790
3791    fn parse_type(&mut self) -> Result<TypeExpr, ParseError> {
3792        let base = self.parse_base_type()?;
3793        // Check for union: T | U
3794        if matches!(self.peek_kind(), TokenKind::Pipe | TokenKind::Ampersand) {
3795            let mut types = vec![base];
3796            while matches!(self.peek_kind(), TokenKind::Pipe | TokenKind::Ampersand) {
3797                self.advance();
3798                types.push(self.parse_base_type()?);
3799            }
3800            let span = types
3801                .first()
3802                .unwrap()
3803                .span()
3804                .merge(types.last().unwrap().span());
3805            Ok(TypeExpr::Union(types, span))
3806        } else {
3807            Ok(base)
3808        }
3809    }
3810
3811    fn parse_base_type(&mut self) -> Result<TypeExpr, ParseError> {
3812        let base = match self.peek_kind().clone() {
3813            TokenKind::Null => {
3814                let s = self.advance().span;
3815                Ok(TypeExpr::Null(s))
3816            }
3817            TokenKind::List => {
3818                let s = self.advance().span;
3819                self.expect(&TokenKind::LBracket)?;
3820                let inner = self.parse_type()?;
3821                self.expect(&TokenKind::RBracket)?;
3822                Ok(TypeExpr::List(Box::new(inner), s))
3823            }
3824            TokenKind::Map => {
3825                let s = self.advance().span;
3826                self.expect(&TokenKind::LBracket)?;
3827                let k = self.parse_type()?;
3828                self.expect(&TokenKind::Comma)?;
3829                let v = self.parse_type()?;
3830                self.expect(&TokenKind::RBracket)?;
3831                Ok(TypeExpr::Map(Box::new(k), Box::new(v), s))
3832            }
3833            TokenKind::Result => {
3834                let s = self.advance().span;
3835                self.expect(&TokenKind::LBracket)?;
3836                let ok = self.parse_type()?;
3837                self.expect(&TokenKind::Comma)?;
3838                let err = self.parse_type()?;
3839                self.expect(&TokenKind::RBracket)?;
3840                Ok(TypeExpr::Result(Box::new(ok), Box::new(err), s))
3841            }
3842            TokenKind::Set => {
3843                let s = self.advance().span;
3844                self.expect(&TokenKind::LBracket)?;
3845                let inner = self.parse_type()?;
3846                self.expect(&TokenKind::RBracket)?;
3847                Ok(TypeExpr::Set(Box::new(inner), s))
3848            }
3849            TokenKind::Tuple => {
3850                let s = self.advance().span;
3851                self.expect(&TokenKind::LBracket)?;
3852                let mut types = vec![self.parse_type()?];
3853                while matches!(self.peek_kind(), TokenKind::Comma) {
3854                    self.advance();
3855                    types.push(self.parse_type()?);
3856                }
3857                self.expect(&TokenKind::RBracket)?;
3858                Ok(TypeExpr::Tuple(types, s))
3859            }
3860            TokenKind::Fn => {
3861                let s = self.advance().span;
3862                self.expect(&TokenKind::LParen)?;
3863                let mut params = Vec::new();
3864                if !matches!(self.peek_kind(), TokenKind::RParen) {
3865                    params.push(self.parse_type()?);
3866                    while matches!(self.peek_kind(), TokenKind::Comma) {
3867                        self.advance();
3868                        params.push(self.parse_type()?);
3869                    }
3870                }
3871                self.expect(&TokenKind::RParen)?;
3872                self.expect(&TokenKind::Arrow)?;
3873                let ret = self.parse_type()?;
3874                let effects = self.parse_optional_effect_row()?;
3875                Ok(TypeExpr::Fn(params, Box::new(ret), effects, s))
3876            }
3877            TokenKind::LParen => {
3878                // Tuple type: (A, B, C)
3879                let s = self.advance().span;
3880                let mut types = vec![self.parse_type()?];
3881                while matches!(self.peek_kind(), TokenKind::Comma) {
3882                    self.advance();
3883                    types.push(self.parse_type()?);
3884                }
3885                self.expect(&TokenKind::RParen)?;
3886                Ok(TypeExpr::Tuple(types, s))
3887            }
3888            TokenKind::Ident(_) => {
3889                let name = self.expect_ident()?;
3890                let span = self.current().span;
3891                // Check for generic: Name[T, U]
3892                if matches!(self.peek_kind(), TokenKind::LBracket) {
3893                    self.advance(); // consume [
3894                    let mut args = vec![self.parse_type()?];
3895                    while matches!(self.peek_kind(), TokenKind::Comma) {
3896                        self.advance();
3897                        args.push(self.parse_type()?);
3898                    }
3899                    self.expect(&TokenKind::RBracket)?;
3900                    Ok(TypeExpr::Generic(name, args, span))
3901                } else {
3902                    Ok(TypeExpr::Named(name, span))
3903                }
3904            }
3905            // Type keywords used as type names
3906            TokenKind::String_ => {
3907                let s = self.advance().span;
3908                Ok(TypeExpr::Named("String".to_string(), s))
3909            }
3910            TokenKind::Int_ => {
3911                let s = self.advance().span;
3912                Ok(TypeExpr::Named("Int".to_string(), s))
3913            }
3914            TokenKind::Float_ => {
3915                let s = self.advance().span;
3916                Ok(TypeExpr::Named("Float".to_string(), s))
3917            }
3918            TokenKind::Bool => {
3919                let s = self.advance().span;
3920                Ok(TypeExpr::Named("Bool".to_string(), s))
3921            }
3922            TokenKind::Bytes => {
3923                let s = self.advance().span;
3924                Ok(TypeExpr::Named("Bytes".to_string(), s))
3925            }
3926            TokenKind::Json => {
3927                let s = self.advance().span;
3928                Ok(TypeExpr::Named("Json".to_string(), s))
3929            }
3930            TokenKind::Type => {
3931                let s = self.advance().span;
3932                if matches!(self.peek_kind(), TokenKind::LBracket) {
3933                    self.advance();
3934                    let mut args = vec![self.parse_type()?];
3935                    while matches!(self.peek_kind(), TokenKind::Comma) {
3936                        self.advance();
3937                        args.push(self.parse_type()?);
3938                    }
3939                    self.expect(&TokenKind::RBracket)?;
3940                    Ok(TypeExpr::Generic("type".into(), args, s))
3941                } else {
3942                    Ok(TypeExpr::Named("type".to_string(), s))
3943                }
3944            }
3945            TokenKind::Comptime => {
3946                let s = self.advance().span;
3947                if matches!(self.peek_kind(), TokenKind::LBrace) {
3948                    self.consume_balanced_group();
3949                }
3950                Ok(TypeExpr::Named("Any".to_string(), s))
3951            }
3952            _ => {
3953                let tok = self.current().clone();
3954                Err(ParseError::Unexpected {
3955                    found: format!("{}", tok.kind),
3956                    expected: "type".into(),
3957                    line: tok.span.line,
3958                    col: tok.span.col,
3959                })
3960            }
3961        }?;
3962
3963        // T? sugar: desugars to T | Null
3964        if matches!(self.peek_kind(), TokenKind::Question) {
3965            let q_span = self.advance().span;
3966            let span = base.span().merge(q_span);
3967            Ok(TypeExpr::Union(vec![base, TypeExpr::Null(q_span)], span))
3968        } else {
3969            Ok(base)
3970        }
3971    }
3972
3973    // ── Expressions (Pratt parser) ──
3974
3975    fn parse_expr(&mut self, min_bp: u8) -> Result<Expr, ParseError> {
3976        let mut lhs = self.parse_prefix()?;
3977        let mut pending_continuation_dedents: usize = 0;
3978        loop {
3979            while pending_continuation_dedents > 0
3980                && matches!(self.peek_kind(), TokenKind::Newline | TokenKind::Dedent)
3981            {
3982                if matches!(self.peek_kind(), TokenKind::Dedent) {
3983                    pending_continuation_dedents -= 1;
3984                }
3985                self.advance();
3986            }
3987            if matches!(
3988                self.peek_kind(),
3989                TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent
3990            ) {
3991                let ws_start = self.pos;
3992                let mut i = self.pos;
3993                while matches!(
3994                    self.tokens.get(i).map(|t| &t.kind),
3995                    Some(TokenKind::Newline | TokenKind::Indent | TokenKind::Dedent)
3996                ) {
3997                    i += 1;
3998                }
3999                if matches!(
4000                    self.tokens.get(i).map(|t| &t.kind),
4001                    Some(
4002                        TokenKind::PipeForward
4003                            | TokenKind::TildeArrow
4004                            | TokenKind::Compose
4005                            | TokenKind::RightShift
4006                            | TokenKind::LeftShift
4007                            | TokenKind::Dot
4008                            | TokenKind::QuestionQuestion
4009                            | TokenKind::Plus
4010                            | TokenKind::Minus
4011                            | TokenKind::Star
4012                            | TokenKind::Slash
4013                            | TokenKind::FloorDiv
4014                            | TokenKind::Percent
4015                            | TokenKind::Eq
4016                            | TokenKind::NotEq
4017                            | TokenKind::Lt
4018                            | TokenKind::LtEq
4019                            | TokenKind::Gt
4020                            | TokenKind::GtEq
4021                            | TokenKind::And
4022                            | TokenKind::Or
4023                            | TokenKind::In
4024                    )
4025                ) {
4026                    while self.pos < i {
4027                        self.advance();
4028                    }
4029                    for tok in &self.tokens[ws_start..i] {
4030                        match tok.kind {
4031                            TokenKind::Indent => pending_continuation_dedents += 1,
4032                            TokenKind::Dedent => {
4033                                pending_continuation_dedents =
4034                                    pending_continuation_dedents.saturating_sub(1);
4035                            }
4036                            _ => {}
4037                        }
4038                    }
4039                }
4040            }
4041            let kind = self.peek_kind();
4042            let (op, bp) = match kind {
4043                TokenKind::Plus => (BinOp::Add, (22, 23)),
4044                TokenKind::Minus => (BinOp::Sub, (22, 23)),
4045                TokenKind::Star => (BinOp::Mul, (24, 25)),
4046                TokenKind::Slash => (BinOp::Div, (24, 25)),
4047                TokenKind::FloorDiv => (BinOp::FloorDiv, (24, 25)),
4048                TokenKind::Percent => (BinOp::Mod, (24, 25)),
4049                TokenKind::StarStar => (BinOp::Pow, (27, 26)), // right-assoc
4050                TokenKind::Eq => (BinOp::Eq, (14, 15)),
4051                TokenKind::NotEq => (BinOp::NotEq, (14, 15)),
4052                TokenKind::Lt => (BinOp::Lt, (14, 15)),
4053                TokenKind::LtEq => (BinOp::LtEq, (14, 15)),
4054                TokenKind::Gt => (BinOp::Gt, (14, 15)),
4055                TokenKind::GtEq => (BinOp::GtEq, (14, 15)),
4056                TokenKind::In => {
4057                    if matches!(
4058                        self.peek_n_kind(1),
4059                        Some(TokenKind::Newline | TokenKind::Eof | TokenKind::Dedent)
4060                    ) {
4061                        break;
4062                    }
4063                    (BinOp::In, (14, 15))
4064                }
4065                // Type test: expr is TypeName
4066                TokenKind::Is => {
4067                    if min_bp > 14 {
4068                        break;
4069                    }
4070                    self.advance();
4071                    let type_name = self.expect_type_name_for_is()?;
4072                    let span = lhs.span().merge(self.current().span);
4073                    lhs = Expr::IsType {
4074                        expr: Box::new(lhs),
4075                        type_name,
4076                        span,
4077                    };
4078                    continue;
4079                }
4080                // Type cast: expr as Type
4081                TokenKind::As => {
4082                    if min_bp > 14 {
4083                        break;
4084                    }
4085                    self.advance();
4086                    let target_type = self.expect_type_name_for_is()?;
4087                    let span = lhs.span().merge(self.current().span);
4088                    lhs = Expr::TypeCast {
4089                        expr: Box::new(lhs),
4090                        target_type,
4091                        span,
4092                    };
4093                    continue;
4094                }
4095                TokenKind::And => (BinOp::And, (12, 13)),
4096                TokenKind::Or => (BinOp::Or, (10, 11)),
4097                TokenKind::PlusPlus => (BinOp::Concat, (18, 19)),
4098                // Pipe |> and compose >> / step: produce Expr::Pipe
4099                TokenKind::PipeForward | TokenKind::Compose | TokenKind::Step => {
4100                    // Note: Compose (>>) is legacy; new code should use |> for pipes
4101                    // and >> now lexes as RightShift for bitwise shift
4102                    if min_bp > 16 {
4103                        break;
4104                    }
4105                    self.advance();
4106                    let rhs = self.parse_expr(17)?;
4107                    let span = lhs.span().merge(rhs.span());
4108                    lhs = Expr::Pipe {
4109                        left: Box::new(lhs),
4110                        right: Box::new(rhs),
4111                        span,
4112                    };
4113                    continue;
4114                }
4115                // Illuminate ~>: produce Expr::Illuminate
4116                TokenKind::TildeArrow => {
4117                    if min_bp > 16 {
4118                        break;
4119                    }
4120                    self.advance();
4121                    let rhs = self.parse_expr(17)?;
4122                    let span = lhs.span().merge(rhs.span());
4123                    lhs = Expr::Illuminate {
4124                        input: Box::new(lhs),
4125                        transform: Box::new(rhs),
4126                        span,
4127                    };
4128                    continue;
4129                }
4130                TokenKind::Ampersand => (BinOp::BitAnd, (14, 15)),
4131                TokenKind::Caret => (BinOp::BitXor, (14, 15)),
4132                TokenKind::LeftShift => (BinOp::Shl, (14, 15)),
4133                TokenKind::RightShift => (BinOp::Shr, (14, 15)),
4134                TokenKind::PlusAssign => (BinOp::Add, (2, 3)),
4135                TokenKind::MinusAssign => (BinOp::Sub, (2, 3)),
4136                TokenKind::StarAssign => (BinOp::Mul, (2, 3)),
4137                TokenKind::SlashAssign => (BinOp::Div, (2, 3)),
4138                TokenKind::FloorDivAssign => (BinOp::FloorDiv, (2, 3)),
4139                TokenKind::PercentAssign => (BinOp::Mod, (2, 3)),
4140                TokenKind::StarStarAssign => (BinOp::Pow, (2, 3)),
4141                TokenKind::AmpAssign => (BinOp::BitAnd, (2, 3)),
4142                TokenKind::PipeAssign => (BinOp::BitOr, (2, 3)),
4143                TokenKind::CaretAssign => (BinOp::BitXor, (2, 3)),
4144                // Null coalescing
4145                TokenKind::QuestionQuestion => {
4146                    if min_bp > 8 {
4147                        break;
4148                    }
4149                    self.advance();
4150                    let rhs = self.parse_expr(9)?;
4151                    let span = lhs.span().merge(rhs.span());
4152                    lhs = Expr::NullCoalesce(Box::new(lhs), Box::new(rhs), span);
4153                    continue;
4154                }
4155                // Range operators
4156                TokenKind::DotDot => {
4157                    if min_bp > 20 {
4158                        break;
4159                    }
4160                    self.advance();
4161                    let rhs = if matches!(
4162                        self.peek_kind(),
4163                        TokenKind::Newline
4164                            | TokenKind::Eof
4165                            | TokenKind::RBracket
4166                            | TokenKind::RParen
4167                            | TokenKind::Comma
4168                    ) {
4169                        None
4170                    } else {
4171                        Some(Box::new(self.parse_expr(21)?))
4172                    };
4173                    let span = lhs
4174                        .span()
4175                        .merge(rhs.as_ref().map(|r| r.span()).unwrap_or(lhs.span()));
4176                    lhs = Expr::RangeExpr {
4177                        start: Some(Box::new(lhs)),
4178                        end: rhs,
4179                        inclusive: false,
4180                        step: None,
4181                        span,
4182                    };
4183                    continue;
4184                }
4185                TokenKind::DotDotEq => {
4186                    if min_bp > 20 {
4187                        break;
4188                    }
4189                    self.advance();
4190                    let rhs = self.parse_expr(21)?;
4191                    let span = lhs.span().merge(rhs.span());
4192                    lhs = Expr::RangeExpr {
4193                        start: Some(Box::new(lhs)),
4194                        end: Some(Box::new(rhs)),
4195                        inclusive: true,
4196                        step: None,
4197                        span,
4198                    };
4199                    continue;
4200                }
4201                // Postfix: dot, index, call, ?, !, ?.
4202                TokenKind::Dot => {
4203                    if min_bp > 32 {
4204                        break;
4205                    }
4206                    self.advance();
4207                    let field = match self.peek_kind().clone() {
4208                        TokenKind::IntLit(n) => {
4209                            self.advance();
4210                            n.to_string()
4211                        }
4212                        _ => self.expect_ident()?,
4213                    };
4214                    let span = lhs.span().merge(self.current().span);
4215                    lhs = Expr::DotAccess(Box::new(lhs), field, span);
4216                    continue;
4217                }
4218                TokenKind::QuestionDot => {
4219                    if min_bp > 32 {
4220                        break;
4221                    }
4222                    self.advance();
4223                    let field = self.expect_ident()?;
4224                    let span = lhs.span().merge(self.current().span);
4225                    lhs = Expr::NullSafeAccess(Box::new(lhs), field, span);
4226                    continue;
4227                }
4228                TokenKind::QuestionBracket => {
4229                    if min_bp > 32 {
4230                        break;
4231                    }
4232                    self.advance(); // consume ?[
4233                    let idx = self.parse_expr(0)?;
4234                    self.expect(&TokenKind::RBracket)?;
4235                    let span = lhs.span().merge(self.current().span);
4236                    lhs = Expr::NullSafeIndex(Box::new(lhs), Box::new(idx), span);
4237                    continue;
4238                }
4239                TokenKind::LBracket => {
4240                    if min_bp > 32 {
4241                        break;
4242                    }
4243                    self.advance();
4244                    if matches!(self.peek_kind(), TokenKind::RBracket) {
4245                        self.expect(&TokenKind::RBracket)?;
4246                        let span = lhs.span().merge(self.current().span);
4247                        lhs =
4248                            Expr::IndexAccess(Box::new(lhs), Box::new(Expr::IntLit(0, span)), span);
4249                        continue;
4250                    }
4251                    let idx = self.parse_expr(0)?;
4252                    if matches!(self.peek_kind(), TokenKind::Comma) {
4253                        let mut args = vec![CallArg::Positional(idx)];
4254                        while matches!(self.peek_kind(), TokenKind::Comma) {
4255                            self.advance();
4256                            if matches!(self.peek_kind(), TokenKind::RBracket) {
4257                                break;
4258                            }
4259                            args.push(CallArg::Positional(self.parse_expr(0)?));
4260                        }
4261                        self.expect(&TokenKind::RBracket)?;
4262                        let span = lhs.span().merge(self.current().span);
4263                        lhs = Expr::Call(Box::new(lhs), args, span);
4264                        continue;
4265                    }
4266                    self.expect(&TokenKind::RBracket)?;
4267                    let span = lhs.span().merge(self.current().span);
4268                    lhs = Expr::IndexAccess(Box::new(lhs), Box::new(idx), span);
4269                    continue;
4270                }
4271                TokenKind::LParen => {
4272                    if min_bp > 32 {
4273                        break;
4274                    }
4275                    lhs = self.parse_call(lhs)?;
4276                    continue;
4277                }
4278                TokenKind::Question => {
4279                    if min_bp > 32 {
4280                        break;
4281                    }
4282                    let span = lhs.span().merge(self.advance().span);
4283                    lhs = Expr::TryExpr(Box::new(lhs), span);
4284                    continue;
4285                }
4286                TokenKind::Bang => {
4287                    if min_bp > 32 {
4288                        break;
4289                    }
4290                    let span = lhs.span().merge(self.advance().span);
4291                    lhs = Expr::NullAssert(Box::new(lhs), span);
4292                    continue;
4293                }
4294                TokenKind::Expect => {
4295                    if min_bp > 1 {
4296                        break;
4297                    }
4298                    self.advance();
4299                    self.expect(&TokenKind::Schema)?;
4300                    let schema_name = self.expect_ident()?;
4301                    let span = lhs.span().merge(self.current().span);
4302                    lhs = Expr::ExpectSchema(Box::new(lhs), schema_name, span);
4303                    continue;
4304                }
4305                TokenKind::At => {
4306                    if min_bp > 32 {
4307                        break;
4308                    }
4309                    self.advance();
4310                    let _ = self.expect_ident();
4311                    if matches!(self.peek_kind(), TokenKind::LParen) {
4312                        self.consume_parenthesized();
4313                    }
4314                    continue;
4315                }
4316                _ => break,
4317            };
4318            let (l_bp, r_bp) = bp;
4319            if l_bp < min_bp {
4320                break;
4321            }
4322            self.advance();
4323            let rhs = self.parse_expr(r_bp)?;
4324            let span = lhs.span().merge(rhs.span());
4325            lhs = Expr::BinOp(Box::new(lhs), op, Box::new(rhs), span);
4326        }
4327        Ok(lhs)
4328    }
4329
4330    fn parse_prefix(&mut self) -> Result<Expr, ParseError> {
4331        match self.peek_kind().clone() {
4332            TokenKind::IntLit(n) => {
4333                let s = self.advance().span;
4334                Ok(Expr::IntLit(n, s))
4335            }
4336            TokenKind::FloatLit(n) => {
4337                let s = self.advance().span;
4338                Ok(Expr::FloatLit(n, s))
4339            }
4340            TokenKind::StringLit(ref sv) => {
4341                let sv = sv.clone();
4342                let s = self.advance().span;
4343                Ok(Expr::StringLit(sv, s))
4344            }
4345            TokenKind::Symbol('\'') => {
4346                let s = self.advance().span;
4347                let mut buf = String::new();
4348                while !matches!(self.peek_kind(), TokenKind::Symbol('\'') | TokenKind::Eof) {
4349                    if !buf.is_empty() {
4350                        buf.push(' ');
4351                    }
4352                    buf.push_str(&format!("{}", self.current().kind));
4353                    self.advance();
4354                }
4355                let end = if matches!(self.peek_kind(), TokenKind::Symbol('\'')) {
4356                    self.advance().span
4357                } else {
4358                    s
4359                };
4360                Ok(Expr::StringLit(buf, s.merge(end)))
4361            }
4362            TokenKind::RawStringLit(ref sv) => {
4363                let sv = sv.clone();
4364                let s = self.advance().span;
4365                Ok(Expr::RawStringLit(sv, s))
4366            }
4367            TokenKind::BytesLit(ref bv) => {
4368                let bv = bv.clone();
4369                let s = self.advance().span;
4370                Ok(Expr::BytesLit(bv, s))
4371            }
4372            TokenKind::NullLit => {
4373                let s = self.advance().span;
4374                Ok(Expr::NullLit(s))
4375            }
4376            TokenKind::StringInterpLit(ref segments) => {
4377                let segments = segments.clone();
4378                let span = self.advance().span;
4379                let mut ast_segments = Vec::new();
4380                for (is_expr, text) in segments {
4381                    if is_expr {
4382                        // Parse the expression string
4383                        // We need a fresh lexer/parser for the snippet
4384                        // Use base offsets from the current span?
4385                        // For simplicity in v1, we won't perfectly map the span inside the string,
4386                        // but we could if we tracked offsets in StringInterpLit.
4387                        // The Lexer change I made doesn't track offsets per segment yet, just strings.
4388                        // So correct source mapping is a TODO for v2.
4389                        let mut lexer =
4390                            crate::compiler::lexer::Lexer::new(&text, span.line, span.col);
4391                        let tokens = lexer.tokenize().map_err(|e| ParseError::Unexpected {
4392                            found: format!("lexer error: {}", e),
4393                            expected: "expression".into(),
4394                            line: span.line,
4395                            col: span.col,
4396                        })?;
4397                        let mut parser = Parser::new(tokens);
4398                        let expr = parser.parse_expr(0)?;
4399                        ast_segments.push(StringSegment::Interpolation(Box::new(expr)));
4400                    } else {
4401                        ast_segments.push(StringSegment::Literal(text));
4402                    }
4403                }
4404                Ok(Expr::StringInterp(ast_segments, span))
4405            }
4406            TokenKind::BoolLit(b) => {
4407                let s = self.advance().span;
4408                Ok(Expr::BoolLit(b, s))
4409            }
4410            TokenKind::Null => {
4411                let s = self.advance().span;
4412                Ok(Expr::NullLit(s))
4413            }
4414            TokenKind::Minus => {
4415                let s = self.advance().span;
4416                let expr = self.parse_expr(28)?; // high bp for unary
4417                let span = s.merge(expr.span());
4418                Ok(Expr::UnaryOp(UnaryOp::Neg, Box::new(expr), span))
4419            }
4420            TokenKind::Not => {
4421                let s = self.advance().span;
4422                let expr = self.parse_expr(28)?;
4423                let span = s.merge(expr.span());
4424                Ok(Expr::UnaryOp(UnaryOp::Not, Box::new(expr), span))
4425            }
4426            TokenKind::Tilde => {
4427                let s = self.advance().span;
4428                let expr = self.parse_expr(28)?;
4429                let span = s.merge(expr.span());
4430                Ok(Expr::UnaryOp(UnaryOp::BitNot, Box::new(expr), span))
4431            }
4432            TokenKind::DotDotDot => {
4433                let s = self.advance().span;
4434                if matches!(
4435                    self.peek_kind(),
4436                    TokenKind::RBracket | TokenKind::RParen | TokenKind::RBrace | TokenKind::Comma
4437                ) {
4438                    return Ok(Expr::Ident("...".into(), s));
4439                }
4440                let expr = self.parse_expr(0)?;
4441                let span = s.merge(expr.span());
4442                Ok(Expr::SpreadExpr(Box::new(expr), span))
4443            }
4444            TokenKind::DotDot => {
4445                let s = self.advance().span;
4446                if matches!(
4447                    self.peek_kind(),
4448                    TokenKind::RBracket | TokenKind::RParen | TokenKind::RBrace | TokenKind::Comma
4449                ) {
4450                    return Ok(Expr::Ident("..".into(), s));
4451                }
4452                let expr = self.parse_expr(28)?;
4453                let span = s.merge(expr.span());
4454                Ok(Expr::SpreadExpr(Box::new(expr), span))
4455            }
4456            TokenKind::Await => {
4457                let s = self.advance().span;
4458                let expr = self.parse_expr(0)?;
4459                let expr = self.rewrite_await_orchestration(expr)?;
4460                let span = s.merge(expr.span());
4461                Ok(Expr::AwaitExpr(Box::new(expr), span))
4462            }
4463            TokenKind::Fn => self.parse_lambda(),
4464            TokenKind::Parallel => {
4465                let s = self.advance().span;
4466                Ok(Expr::Ident("parallel".into(), s))
4467            }
4468            TokenKind::Match => {
4469                // Reuse the statement-level match parser, then extract the AST
4470                let stmt = self.parse_match()?;
4471                if let Stmt::Match(ms) = stmt {
4472                    let span = ms.span;
4473                    Ok(Expr::MatchExpr {
4474                        subject: Box::new(ms.subject),
4475                        arms: ms.arms,
4476                        span,
4477                    })
4478                } else {
4479                    let span = stmt.span();
4480                    Ok(Expr::BlockExpr(vec![stmt], span))
4481                }
4482            }
4483            TokenKind::If => {
4484                // Reuse the statement-level if parser, wrap result as block expr
4485                let stmt = self.parse_if()?;
4486                let span = stmt.span();
4487                Ok(Expr::BlockExpr(vec![stmt], span))
4488            }
4489            TokenKind::When => {
4490                let s = self.advance().span;
4491                self.skip_newlines();
4492                self.consume_block_until_end();
4493                Ok(Expr::Ident("when_expr".into(), s))
4494            }
4495            TokenKind::Loop => {
4496                // Reuse statement-level loop parser, wrap as block expr
4497                let stmt = self.parse_loop()?;
4498                let span = stmt.span();
4499                Ok(Expr::BlockExpr(vec![stmt], span))
4500            }
4501            TokenKind::Let => {
4502                // Reuse statement-level let parser, wrap as block expr
4503                let stmt = self.parse_let()?;
4504                let span = stmt.span();
4505                Ok(Expr::BlockExpr(vec![stmt], span))
4506            }
4507            TokenKind::Try => {
4508                let s = self.advance().span;
4509                let inner = self.parse_expr(0)?;
4510                let span = s.merge(inner.span());
4511                Ok(Expr::TryExpr(Box::new(inner), span))
4512            }
4513            TokenKind::Async => {
4514                let s = self.advance().span;
4515                // async block: `async ... end` or `async <expr>`
4516                if matches!(self.peek_kind(), TokenKind::Newline | TokenKind::Indent) {
4517                    self.skip_newlines();
4518                    let block = self.parse_block()?;
4519                    let end_span = if matches!(self.peek_kind(), TokenKind::End) {
4520                        self.advance().span
4521                    } else {
4522                        s
4523                    };
4524                    Ok(Expr::BlockExpr(block, s.merge(end_span)))
4525                } else if !matches!(
4526                    self.peek_kind(),
4527                    TokenKind::RParen
4528                        | TokenKind::RBracket
4529                        | TokenKind::RBrace
4530                        | TokenKind::Comma
4531                        | TokenKind::Eof
4532                ) {
4533                    let inner = self.parse_expr(0)?;
4534                    let span = s.merge(inner.span());
4535                    Ok(Expr::AwaitExpr(Box::new(inner), span))
4536                } else {
4537                    Ok(Expr::BlockExpr(vec![], s))
4538                }
4539            }
4540            TokenKind::Comptime => {
4541                let s = self.advance().span;
4542                if matches!(self.peek_kind(), TokenKind::LBrace) {
4543                    self.consume_balanced_group();
4544                } else {
4545                    self.consume_rest_of_line();
4546                }
4547                Ok(Expr::Ident("comptime".into(), s))
4548            }
4549            TokenKind::Use => {
4550                let s = self.advance().span;
4551                Ok(Expr::Ident("use".into(), s))
4552            }
4553            TokenKind::Halt => {
4554                let s = self.advance().span;
4555                Ok(Expr::Ident("halt".into(), s))
4556            }
4557            TokenKind::Cell => {
4558                let s = self.advance().span;
4559                Ok(Expr::Ident("cell".into(), s))
4560            }
4561            TokenKind::End => {
4562                let s = self.advance().span;
4563                Ok(Expr::Ident("end".into(), s))
4564            }
4565            TokenKind::Dot => {
4566                let s = self.advance().span;
4567                let name = self.expect_ident()?;
4568                let mut expr = Expr::Ident(format!(".{name}"), s);
4569                if matches!(self.peek_kind(), TokenKind::LParen) {
4570                    expr = self.parse_call(expr)?;
4571                }
4572                Ok(expr)
4573            }
4574            TokenKind::Set => {
4575                let s = self.advance().span;
4576                self.expect(&TokenKind::LBracket)?;
4577                self.bracket_depth += 1;
4578                self.skip_whitespace_tokens();
4579                if matches!(self.peek_kind(), TokenKind::RBracket) {
4580                    self.bracket_depth -= 1;
4581                    let end = self.expect(&TokenKind::RBracket)?.span;
4582                    return Ok(Expr::SetLit(vec![], s.merge(end)));
4583                }
4584                let first = self.parse_expr(0)?;
4585                self.skip_whitespace_tokens();
4586                if matches!(self.peek_kind(), TokenKind::For) {
4587                    self.advance();
4588                    let var = self.expect_ident()?;
4589                    self.expect(&TokenKind::In)?;
4590                    let iter = self.parse_expr(0)?;
4591                    while matches!(self.peek_kind(), TokenKind::For) {
4592                        self.advance();
4593                        let _ = self.expect_ident()?;
4594                        self.expect(&TokenKind::In)?;
4595                        let _ = self.parse_expr(0)?;
4596                    }
4597                    let condition = if matches!(self.peek_kind(), TokenKind::If) {
4598                        self.advance();
4599                        Some(Box::new(self.parse_expr(0)?))
4600                    } else {
4601                        None
4602                    };
4603                    self.skip_whitespace_tokens();
4604                    self.bracket_depth -= 1;
4605                    let end = self.expect(&TokenKind::RBrace)?.span;
4606                    return Ok(Expr::Comprehension {
4607                        body: Box::new(first),
4608                        var,
4609                        iter: Box::new(iter),
4610                        condition,
4611                        kind: ComprehensionKind::Set,
4612                        span: s.merge(end),
4613                    });
4614                }
4615                let mut elems = vec![first];
4616                while matches!(self.peek_kind(), TokenKind::Comma) {
4617                    self.advance();
4618                    self.skip_whitespace_tokens();
4619                    if matches!(self.peek_kind(), TokenKind::RBrace) {
4620                        break;
4621                    }
4622                    elems.push(self.parse_expr(0)?);
4623                    self.skip_whitespace_tokens();
4624                }
4625                self.bracket_depth -= 1;
4626                let end = self.expect(&TokenKind::RBrace)?.span;
4627                Ok(Expr::SetLit(elems, s.merge(end)))
4628            }
4629            TokenKind::LParen => {
4630                let s = self.advance().span;
4631                // Could be grouping, tuple, or empty tuple
4632                if matches!(self.peek_kind(), TokenKind::RParen) {
4633                    // Empty tuple
4634                    let end = self.advance().span;
4635                    return Ok(Expr::TupleLit(vec![], s.merge(end)));
4636                }
4637                let first = self.parse_expr(0)?;
4638                if matches!(self.peek_kind(), TokenKind::Comma) {
4639                    // Tuple
4640                    let mut elems = vec![first];
4641                    while matches!(self.peek_kind(), TokenKind::Comma) {
4642                        self.advance();
4643                        if matches!(self.peek_kind(), TokenKind::RParen) {
4644                            break;
4645                        }
4646                        elems.push(self.parse_expr(0)?);
4647                    }
4648                    let end = self.expect(&TokenKind::RParen)?.span;
4649                    Ok(Expr::TupleLit(elems, s.merge(end)))
4650                } else {
4651                    // Grouping
4652                    self.expect(&TokenKind::RParen)?;
4653                    Ok(first)
4654                }
4655            }
4656            TokenKind::LBracket => self.parse_list_or_comprehension(),
4657            TokenKind::LBrace => self.parse_map_lit(),
4658            TokenKind::Role => self.parse_role_block_expr(),
4659            TokenKind::Ok_ => {
4660                let s = self.advance().span;
4661                Ok(Expr::Ident("ok".into(), s))
4662            }
4663            TokenKind::Err_ => {
4664                let s = self.advance().span;
4665                Ok(Expr::Ident("err".into(), s))
4666            }
4667            TokenKind::SelfKw => {
4668                let s = self.advance().span;
4669                Ok(Expr::Ident("self".into(), s))
4670            }
4671            TokenKind::Ident(_) => {
4672                let start = self.current().span;
4673                let name = self.expect_ident()?;
4674                if name == "agent" {
4675                    return self.parse_expr(28);
4676                }
4677                if name == "confirm" {
4678                    let arg = self.parse_expr(28)?;
4679                    let span = start.merge(arg.span());
4680                    return Ok(Expr::Call(
4681                        Box::new(Expr::Ident(name, start)),
4682                        vec![CallArg::Positional(arg)],
4683                        span,
4684                    ));
4685                }
4686                let span = self.current().span;
4687                Ok(Expr::Ident(name, span))
4688            }
4689            // Type keywords used as function names in expression position
4690            TokenKind::String_ => {
4691                let s = self.advance().span;
4692                Ok(Expr::Ident("string".into(), s))
4693            }
4694            TokenKind::Int_ => {
4695                let s = self.advance().span;
4696                Ok(Expr::Ident("int".into(), s))
4697            }
4698            TokenKind::Float_ => {
4699                let s = self.advance().span;
4700                Ok(Expr::Ident("float".into(), s))
4701            }
4702            TokenKind::Bool => {
4703                let s = self.advance().span;
4704                Ok(Expr::Ident("bool".into(), s))
4705            }
4706            TokenKind::Bytes => {
4707                let s = self.advance().span;
4708                Ok(Expr::Ident("bytes".into(), s))
4709            }
4710            TokenKind::Json => {
4711                let s = self.advance().span;
4712                Ok(Expr::Ident("json".into(), s))
4713            }
4714            TokenKind::List => {
4715                let s = self.advance().span;
4716                Ok(Expr::Ident("list".into(), s))
4717            }
4718            TokenKind::Map => {
4719                let s = self.advance().span;
4720                Ok(Expr::Ident("map".into(), s))
4721            }
4722            TokenKind::Type => {
4723                let s = self.advance().span;
4724                Ok(Expr::Ident("type".into(), s))
4725            }
4726            TokenKind::Result => {
4727                let s = self.advance().span;
4728                Ok(Expr::Ident("result".into(), s))
4729            }
4730            TokenKind::Tool => {
4731                let s = self.advance().span;
4732                Ok(Expr::Ident("tool".into(), s))
4733            }
4734            TokenKind::Schema => {
4735                let s = self.advance().span;
4736                Ok(Expr::Ident("schema".into(), s))
4737            }
4738            _ => {
4739                let tok = self.current().clone();
4740                Err(ParseError::Unexpected {
4741                    found: format!("{}", tok.kind),
4742                    expected: "expression".into(),
4743                    line: tok.span.line,
4744                    col: tok.span.col,
4745                })
4746            }
4747        }
4748    }
4749
4750    fn parse_lambda(&mut self) -> Result<Expr, ParseError> {
4751        let start = self.expect(&TokenKind::Fn)?.span;
4752        self.expect(&TokenKind::LParen)?;
4753        let mut params = Vec::new();
4754        while !matches!(self.peek_kind(), TokenKind::RParen) {
4755            if !params.is_empty() {
4756                self.expect(&TokenKind::Comma)?;
4757            }
4758            let ps = self.current().span;
4759            let pname = if matches!(self.peek_kind(), TokenKind::LParen) {
4760                self.consume_balanced_group();
4761                format!("__arg{}", params.len())
4762            } else {
4763                self.expect_ident()?
4764            };
4765            let pty = if matches!(self.peek_kind(), TokenKind::Colon) {
4766                self.advance();
4767                self.parse_type()?
4768            } else {
4769                TypeExpr::Named("Any".into(), ps)
4770            };
4771            params.push(Param {
4772                name: pname,
4773                ty: pty,
4774                default_value: None,
4775                variadic: false,
4776                span: ps,
4777            });
4778        }
4779        self.expect(&TokenKind::RParen)?;
4780        let return_type = if matches!(self.peek_kind(), TokenKind::Arrow) {
4781            self.advance();
4782            Some(Box::new(self.parse_type()?))
4783        } else {
4784            None
4785        };
4786        let body = if matches!(self.peek_kind(), TokenKind::FatArrow) {
4787            self.advance();
4788            self.skip_whitespace_tokens();
4789            let expr = self.parse_expr(0)?;
4790            LambdaBody::Expr(Box::new(expr))
4791        } else {
4792            self.skip_newlines();
4793            let stmts = self.parse_block()?;
4794            self.expect(&TokenKind::End)?;
4795            LambdaBody::Block(stmts)
4796        };
4797        let end_span = match &body {
4798            LambdaBody::Expr(e) => e.span(),
4799            LambdaBody::Block(stmts) => stmts.last().map(|s| s.span()).unwrap_or(start),
4800        };
4801        Ok(Expr::Lambda {
4802            params,
4803            return_type,
4804            body,
4805            span: start.merge(end_span),
4806        })
4807    }
4808
4809    fn parse_list_or_comprehension(&mut self) -> Result<Expr, ParseError> {
4810        let start = self.expect(&TokenKind::LBracket)?.span;
4811        self.bracket_depth += 1;
4812        self.skip_whitespace_tokens();
4813        if matches!(self.peek_kind(), TokenKind::RBracket) {
4814            self.bracket_depth -= 1;
4815            let end = self.expect(&TokenKind::RBracket)?.span;
4816            return Ok(Expr::ListLit(vec![], start.merge(end)));
4817        }
4818        // Parse first element
4819        let first = self.parse_expr(0)?;
4820        self.skip_whitespace_tokens();
4821        // Check for comprehension: [expr for var in iter]
4822        if matches!(self.peek_kind(), TokenKind::For) {
4823            self.advance();
4824            let var = if matches!(self.peek_kind(), TokenKind::LParen) {
4825                self.advance();
4826                let first = self.expect_ident()?;
4827                while !matches!(self.peek_kind(), TokenKind::RParen | TokenKind::Eof) {
4828                    self.advance();
4829                }
4830                if matches!(self.peek_kind(), TokenKind::RParen) {
4831                    self.advance();
4832                }
4833                first
4834            } else {
4835                self.expect_ident()?
4836            };
4837            self.expect(&TokenKind::In)?;
4838            let iter = self.parse_expr(0)?;
4839            self.skip_whitespace_tokens();
4840            while matches!(self.peek_kind(), TokenKind::For) {
4841                self.advance();
4842                if matches!(self.peek_kind(), TokenKind::LParen) {
4843                    self.advance();
4844                    while !matches!(self.peek_kind(), TokenKind::RParen | TokenKind::Eof) {
4845                        self.advance();
4846                    }
4847                    if matches!(self.peek_kind(), TokenKind::RParen) {
4848                        self.advance();
4849                    }
4850                } else {
4851                    let _ = self.expect_ident()?;
4852                }
4853                self.expect(&TokenKind::In)?;
4854                let _ = self.parse_expr(0)?;
4855                self.skip_whitespace_tokens();
4856            }
4857            let condition = if matches!(self.peek_kind(), TokenKind::If) {
4858                self.advance();
4859                Some(Box::new(self.parse_expr(0)?))
4860            } else {
4861                None
4862            };
4863            self.skip_whitespace_tokens();
4864            self.bracket_depth -= 1;
4865            let end = self.expect(&TokenKind::RBracket)?.span;
4866            return Ok(Expr::Comprehension {
4867                body: Box::new(first),
4868                var,
4869                iter: Box::new(iter),
4870                condition,
4871                kind: ComprehensionKind::List,
4872                span: start.merge(end),
4873            });
4874        }
4875        // Regular list
4876        let mut elems = vec![first];
4877        while matches!(self.peek_kind(), TokenKind::Comma) {
4878            self.advance();
4879            self.skip_whitespace_tokens();
4880            if matches!(self.peek_kind(), TokenKind::RBracket) {
4881                break;
4882            }
4883            elems.push(self.parse_expr(0)?);
4884            self.skip_whitespace_tokens();
4885        }
4886        self.bracket_depth -= 1;
4887        let end = self.expect(&TokenKind::RBracket)?.span;
4888        Ok(Expr::ListLit(elems, start.merge(end)))
4889    }
4890
4891    fn parse_call(&mut self, callee: Expr) -> Result<Expr, ParseError> {
4892        let start = callee.span();
4893        self.expect(&TokenKind::LParen)?;
4894        self.bracket_depth += 1;
4895        let mut args = Vec::new();
4896        while !matches!(self.peek_kind(), TokenKind::RParen) {
4897            self.skip_whitespace_tokens();
4898            if matches!(self.peek_kind(), TokenKind::RParen) {
4899                break;
4900            }
4901            if !args.is_empty() {
4902                self.expect(&TokenKind::Comma)?;
4903                self.skip_whitespace_tokens();
4904            }
4905            if matches!(self.peek_kind(), TokenKind::Schema) {
4906                let ks = self.advance().span;
4907                self.skip_whitespace_tokens();
4908                if matches!(self.peek_kind(), TokenKind::Colon) {
4909                    self.advance();
4910                    self.skip_whitespace_tokens();
4911                }
4912                let val = self.parse_expr(28)?;
4913                let span = ks.merge(val.span());
4914                args.push(CallArg::Named("schema".into(), val, span));
4915                self.skip_whitespace_tokens();
4916                continue;
4917            }
4918            // Check for role blocks inline
4919            if matches!(self.peek_kind(), TokenKind::Role)
4920                && !matches!(self.peek_n_kind(1), Some(TokenKind::Colon))
4921            {
4922                self.advance();
4923                let role_span = self.current().span;
4924                let role_name = self.expect_ident()?;
4925                self.expect(&TokenKind::Colon)?;
4926
4927                if matches!(self.peek_kind(), TokenKind::Newline) {
4928                    while matches!(self.peek_kind(), TokenKind::Newline) {
4929                        self.advance();
4930                    }
4931                }
4932                let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
4933                let role_terminators: &[TokenKind] = if has_indent {
4934                    &[TokenKind::RParen]
4935                } else {
4936                    &[TokenKind::Comma, TokenKind::RParen]
4937                };
4938                let content_expr = self.parse_role_content(role_terminators, has_indent)?;
4939                if has_indent && matches!(self.peek_kind(), TokenKind::End) {
4940                    self.advance();
4941                }
4942
4943                let span = role_span.merge(content_expr.span());
4944                args.push(CallArg::Role(role_name, content_expr, span));
4945                continue;
4946            }
4947            if self.token_can_be_named_arg_key() {
4948                // Check if named arg: name: expr
4949                let save = self.pos;
4950                let name_clone = self.expect_ident()?;
4951                if matches!(self.peek_kind(), TokenKind::Colon) {
4952                    self.advance();
4953                    self.skip_whitespace_tokens();
4954                    let val = self.parse_expr(0)?;
4955                    let span = val.span();
4956                    args.push(CallArg::Named(name_clone, val, span));
4957                } else {
4958                    // Property shorthand: Point(x, y) => Point(x: x, y: y)
4959                    // Only when callee is an uppercase ident (record constructor)
4960                    // and the argument is a bare identifier (followed by , or ))
4961                    let is_record_ctor =
4962                        matches!(&callee, Expr::Ident(n, _) if n.starts_with(char::is_uppercase));
4963                    if is_record_ctor
4964                        && matches!(
4965                            self.peek_kind(),
4966                            TokenKind::Comma | TokenKind::RParen | TokenKind::Newline
4967                        )
4968                    {
4969                        let span = self.tokens[save].span;
4970                        args.push(CallArg::Named(
4971                            name_clone.clone(),
4972                            Expr::Ident(name_clone, span),
4973                            span,
4974                        ));
4975                    } else {
4976                        self.pos = save;
4977                        let expr = self.parse_expr(0)?;
4978                        args.push(CallArg::Positional(expr));
4979                    }
4980                }
4981            } else {
4982                let expr = self.parse_expr(0)?;
4983                args.push(CallArg::Positional(expr));
4984            }
4985            self.skip_whitespace_tokens();
4986        }
4987        self.bracket_depth -= 1;
4988        let end = self.expect(&TokenKind::RParen)?.span;
4989
4990        Ok(Expr::Call(Box::new(callee), args, start.merge(end)))
4991    }
4992
4993    fn parse_map_lit(&mut self) -> Result<Expr, ParseError> {
4994        let start = self.expect(&TokenKind::LBrace)?.span;
4995        self.bracket_depth += 1;
4996        self.skip_whitespace_tokens();
4997
4998        // Empty braces -> empty map
4999        if matches!(self.peek_kind(), TokenKind::RBrace) {
5000            self.bracket_depth -= 1;
5001            let end = self.expect(&TokenKind::RBrace)?.span;
5002            return Ok(Expr::MapLit(vec![], start.merge(end)));
5003        }
5004
5005        // Check for spread at the start
5006        if matches!(self.peek_kind(), TokenKind::DotDot | TokenKind::DotDotDot) {
5007            let spread_span = self.advance().span;
5008            let spread_expr = self.parse_expr(0)?;
5009            let mut pairs = vec![(Expr::StringLit("__spread".into(), spread_span), spread_expr)];
5010            self.skip_whitespace_tokens();
5011
5012            while matches!(self.peek_kind(), TokenKind::Comma) {
5013                self.advance();
5014                self.skip_whitespace_tokens();
5015                if matches!(self.peek_kind(), TokenKind::RBrace) {
5016                    break;
5017                }
5018                if matches!(self.peek_kind(), TokenKind::DotDot | TokenKind::DotDotDot) {
5019                    let spread_span = self.advance().span;
5020                    let spread_expr = self.parse_expr(0)?;
5021                    pairs.push((Expr::StringLit("__spread".into(), spread_span), spread_expr));
5022                } else {
5023                    let key = if let TokenKind::Ident(name) = self.peek_kind().clone() {
5024                        if matches!(
5025                            self.tokens.get(self.pos + 1).map(|t| &t.kind),
5026                            Some(TokenKind::Colon)
5027                        ) {
5028                            let span = self.current().span;
5029                            self.advance();
5030                            Expr::StringLit(name, span)
5031                        } else {
5032                            self.parse_expr(0)?
5033                        }
5034                    } else {
5035                        self.parse_expr(0)?
5036                    };
5037                    self.expect(&TokenKind::Colon)?;
5038                    self.skip_whitespace_tokens();
5039                    let val = self.parse_expr(0)?;
5040                    pairs.push((key, val));
5041                }
5042                self.skip_whitespace_tokens();
5043            }
5044
5045            self.bracket_depth -= 1;
5046            let end = self.expect(&TokenKind::RBrace)?.span;
5047            return Ok(Expr::MapLit(pairs, start.merge(end)));
5048        }
5049
5050        // Parse first expression to determine if this is a set or map
5051        let first = self.parse_expr(0)?;
5052        self.skip_whitespace_tokens();
5053
5054        // Check what follows the first expression
5055        match self.peek_kind() {
5056            TokenKind::For => {
5057                // Set comprehension: {expr for var in iter}
5058                self.advance();
5059                let var = self.expect_ident()?;
5060                self.expect(&TokenKind::In)?;
5061                let iter = self.parse_expr(0)?;
5062                while matches!(self.peek_kind(), TokenKind::For) {
5063                    self.advance();
5064                    let _ = self.expect_ident()?;
5065                    self.expect(&TokenKind::In)?;
5066                    let _ = self.parse_expr(0)?;
5067                }
5068                let condition = if matches!(self.peek_kind(), TokenKind::If) {
5069                    self.advance();
5070                    Some(Box::new(self.parse_expr(0)?))
5071                } else {
5072                    None
5073                };
5074                self.skip_whitespace_tokens();
5075                self.bracket_depth -= 1;
5076                let end = self.expect(&TokenKind::RBrace)?.span;
5077
5078                // Determine comprehension kind based on body type
5079                // If body is a tuple, it's a map comprehension
5080                let kind = if matches!(first, Expr::TupleLit(..)) {
5081                    ComprehensionKind::Map
5082                } else {
5083                    ComprehensionKind::Set
5084                };
5085
5086                Ok(Expr::Comprehension {
5087                    body: Box::new(first),
5088                    var,
5089                    iter: Box::new(iter),
5090                    condition,
5091                    kind,
5092                    span: start.merge(end),
5093                })
5094            }
5095            TokenKind::Colon | TokenKind::Assign => {
5096                // Map literal: {key: value, ...}
5097                self.advance();
5098                self.skip_whitespace_tokens();
5099                let val = self.parse_expr(0)?;
5100                self.skip_whitespace_tokens();
5101
5102                let mut pairs = vec![(first, val)];
5103                while matches!(self.peek_kind(), TokenKind::Comma) {
5104                    self.advance();
5105                    self.skip_whitespace_tokens();
5106                    if matches!(self.peek_kind(), TokenKind::RBrace) {
5107                        break;
5108                    }
5109                    if matches!(self.peek_kind(), TokenKind::DotDot | TokenKind::DotDotDot) {
5110                        let spread_span = self.advance().span;
5111                        let spread_expr = self.parse_expr(0)?;
5112                        pairs.push((Expr::StringLit("__spread".into(), spread_span), spread_expr));
5113                        self.skip_whitespace_tokens();
5114                        continue;
5115                    }
5116                    let key = if let TokenKind::Ident(name) = self.peek_kind().clone() {
5117                        if matches!(
5118                            self.tokens.get(self.pos + 1).map(|t| &t.kind),
5119                            Some(TokenKind::Colon)
5120                        ) {
5121                            let span = self.current().span;
5122                            self.advance();
5123                            Expr::StringLit(name, span)
5124                        } else {
5125                            self.parse_expr(0)?
5126                        }
5127                    } else {
5128                        self.parse_expr(0)?
5129                    };
5130                    self.expect(&TokenKind::Colon)?;
5131                    self.skip_whitespace_tokens();
5132                    let val = self.parse_expr(0)?;
5133                    pairs.push((key, val));
5134                    self.skip_whitespace_tokens();
5135                }
5136                self.bracket_depth -= 1;
5137                let end = self.expect(&TokenKind::RBrace)?.span;
5138                Ok(Expr::MapLit(pairs, start.merge(end)))
5139            }
5140            TokenKind::Comma | TokenKind::RBrace => {
5141                // Set literal: {val1, val2, ...}
5142                let mut elems = vec![first];
5143                while matches!(self.peek_kind(), TokenKind::Comma) {
5144                    self.advance();
5145                    self.skip_whitespace_tokens();
5146                    if matches!(self.peek_kind(), TokenKind::RBrace) {
5147                        break;
5148                    }
5149                    elems.push(self.parse_expr(0)?);
5150                    self.skip_whitespace_tokens();
5151                }
5152                self.bracket_depth -= 1;
5153                let end = self.expect(&TokenKind::RBrace)?.span;
5154                Ok(Expr::SetLit(elems, start.merge(end)))
5155            }
5156            _ => {
5157                let tok = self.current().clone();
5158                Err(ParseError::Unexpected {
5159                    found: format!("{}", tok.kind),
5160                    expected: "',', ':', or 'for'".into(),
5161                    line: tok.span.line,
5162                    col: tok.span.col,
5163                })
5164            }
5165        }
5166    }
5167
5168    fn parse_role_block_expr(&mut self) -> Result<Expr, ParseError> {
5169        // Expression context (default): stops at RParen to support call args
5170        self.parse_role_block_general(&[
5171            TokenKind::End,
5172            TokenKind::Role,
5173            TokenKind::Eof,
5174            TokenKind::RParen,
5175        ])
5176    }
5177
5178    /// Parse a role block in statement context (allows RParen)
5179    fn parse_role_block_stmt(&mut self) -> Result<Expr, ParseError> {
5180        self.parse_role_block_general(&[TokenKind::End, TokenKind::Role, TokenKind::Eof])
5181    }
5182
5183    fn parse_role_block_general(&mut self, terminators: &[TokenKind]) -> Result<Expr, ParseError> {
5184        let start = self.expect(&TokenKind::Role)?.span;
5185        let name = self.expect_ident()?;
5186        self.expect(&TokenKind::Colon)?;
5187
5188        // Check if it was an indented block
5189        // We need to know if we started with indentation.
5190        // But parse_role_content consumed the indent/dedent if present.
5191        // Actually parse_role_content determines this.
5192        // We should move the `Expect End` logic into parse_role_content or check here?
5193        // parse_role_content: returns Expr.
5194        // If we inspect parse_role_content more closely, it handles indent/dedent.
5195        // If it was inline, it stopped at Newline.
5196        // Does inline require `end`? No.
5197        // Does block require `end`? Yes.
5198        // How do we know which one it was?
5199        // peek Indent *before* calling parse_role_content?
5200        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
5201        let content_expr = self.parse_role_content(terminators, has_indent)?;
5202
5203        let end_span = if has_indent || matches!(self.peek_kind(), TokenKind::End) {
5204            self.expect(&TokenKind::End)?.span
5205        } else {
5206            // Inline role ends at newline (which is peeked but not consumed by parse_role_content?)
5207            // parse_role_content breaks on Newline. So Newline is next.
5208            // We don't need to consume it necessarily, skip_newlines in parse_block will handle it.
5209            content_expr.span()
5210        };
5211
5212        Ok(Expr::RoleBlock(
5213            name,
5214            Box::new(content_expr),
5215            start.merge(end_span),
5216        ))
5217    }
5218
5219    /// Parse role block content with interpolation support.
5220    /// Stops at TokenKind::End, TokenKind::Role (peeked), or EOF/Dedent.
5221    /// Does NOT consume 'end' or 'role', but consumes the content.
5222    fn parse_role_content(
5223        &mut self,
5224        terminators: &[TokenKind],
5225        has_indent: bool,
5226    ) -> Result<Expr, ParseError> {
5227        let start = self.current().span;
5228        let mut segments = Vec::new();
5229        let mut text_buf = String::new();
5230        if has_indent {
5231            self.advance();
5232        }
5233        self.skip_newlines();
5234
5235        loop {
5236            let peek = self.peek_kind();
5237            if terminators.contains(peek) {
5238                break;
5239            }
5240            if matches!(peek, TokenKind::Newline) && !has_indent {
5241                break;
5242            }
5243            if matches!(peek, TokenKind::Dedent) && has_indent {
5244                break;
5245            }
5246
5247            match peek {
5248                TokenKind::LBrace => {
5249                    // Try interpolation first; if parsing fails, keep `{...}` as literal text.
5250                    let saved_pos = self.pos;
5251                    let saved_bracket_depth = self.bracket_depth;
5252                    self.advance(); // consume `{`
5253                    let parsed_interp = (|| {
5254                        let expr = self.parse_expr(0).ok()?;
5255                        self.expect(&TokenKind::RBrace).ok()?;
5256                        Some(expr)
5257                    })();
5258
5259                    if let Some(expr) = parsed_interp {
5260                        if !text_buf.is_empty() {
5261                            segments.push(StringSegment::Literal(text_buf.clone()));
5262                            text_buf.clear();
5263                        }
5264                        segments.push(StringSegment::Interpolation(Box::new(expr)));
5265                    } else {
5266                        self.pos = saved_pos;
5267                        self.bracket_depth = saved_bracket_depth;
5268                        text_buf.push('{');
5269                        self.advance();
5270                    }
5271                }
5272                TokenKind::Newline => {
5273                    // Inside indented block, preserve newline
5274                    text_buf.push('\n');
5275                    self.advance();
5276                }
5277                TokenKind::Indent | TokenKind::Dedent => {
5278                    // Should imply structural change, but if we are inside role block content...
5279                    // Indent here means nested indentation?
5280                    // We just consume it as whitespace/structure?
5281                    // Or maybe we should skip it?
5282                    self.advance();
5283                }
5284                _ => {
5285                    // Accumulate text
5286                    let tok = self.advance();
5287                    let text = format!("{}", tok.kind);
5288                    if !text_buf.is_empty() && !text_buf.ends_with('\n') {
5289                        text_buf.push(' ');
5290                    }
5291                    text_buf.push_str(&text);
5292                }
5293            }
5294        }
5295
5296        if !text_buf.is_empty() {
5297            segments.push(StringSegment::Literal(text_buf));
5298        }
5299
5300        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
5301            self.advance();
5302        }
5303
5304        // If we stopped at End, consume it?
5305        // parse_role_block_expr expects to consume 'End'.
5306        // parse_call expects to NOT consume 'Role' (next block).
5307        // So this helper should just parse content.
5308
5309        let span = if segments.is_empty() {
5310            start
5311        } else {
5312            start.merge(self.current().span)
5313        };
5314
5315        if segments.len() == 1 {
5316            if let StringSegment::Literal(ref s) = segments[0] {
5317                return Ok(Expr::StringLit(s.clone(), span));
5318            }
5319        }
5320
5321        Ok(Expr::StringInterp(segments, span))
5322    }
5323
5324    // ── Helpers ──
5325
5326    fn expect_ident(&mut self) -> Result<String, ParseError> {
5327        let tok = self.current().clone();
5328        match &tok.kind {
5329            TokenKind::Ident(name) => {
5330                let n = name.clone();
5331                self.advance();
5332                Ok(n)
5333            }
5334            TokenKind::Result => {
5335                self.advance();
5336                Ok("result".into())
5337            }
5338            TokenKind::Ok_ => {
5339                self.advance();
5340                Ok("ok".into())
5341            }
5342            TokenKind::Err_ => {
5343                self.advance();
5344                Ok("err".into())
5345            }
5346            TokenKind::List => {
5347                self.advance();
5348                Ok("list".into())
5349            }
5350            TokenKind::Map => {
5351                self.advance();
5352                Ok("map".into())
5353            }
5354            TokenKind::Json => {
5355                self.advance();
5356                Ok("json".into())
5357            }
5358            TokenKind::Bytes => {
5359                self.advance();
5360                Ok("bytes".into())
5361            }
5362            TokenKind::Bool => {
5363                self.advance();
5364                Ok("bool".into())
5365            }
5366            TokenKind::Int_ => {
5367                self.advance();
5368                Ok("int".into())
5369            }
5370            TokenKind::Float_ => {
5371                self.advance();
5372                Ok("float".into())
5373            }
5374            TokenKind::Set => {
5375                self.advance();
5376                Ok("set".into())
5377            }
5378            TokenKind::Tuple => {
5379                self.advance();
5380                Ok("tuple".into())
5381            }
5382            TokenKind::Type => {
5383                self.advance();
5384                Ok("type".into())
5385            }
5386            TokenKind::String_ => {
5387                self.advance();
5388                Ok("string".into())
5389            }
5390            TokenKind::Cell => {
5391                self.advance();
5392                Ok("cell".into())
5393            }
5394            TokenKind::Record => {
5395                self.advance();
5396                Ok("record".into())
5397            }
5398            TokenKind::SelfKw => {
5399                self.advance();
5400                Ok("self".into())
5401            }
5402            TokenKind::Schema => {
5403                self.advance();
5404                Ok("schema".into())
5405            }
5406            TokenKind::Try => {
5407                self.advance();
5408                Ok("try".into())
5409            }
5410            TokenKind::When => {
5411                self.advance();
5412                Ok("when".into())
5413            }
5414            TokenKind::Step => {
5415                self.advance();
5416                Ok("step".into())
5417            }
5418            TokenKind::Comptime => {
5419                self.advance();
5420                Ok("comptime".into())
5421            }
5422            TokenKind::Macro => {
5423                self.advance();
5424                Ok("macro".into())
5425            }
5426            TokenKind::Extern => {
5427                self.advance();
5428                Ok("extern".into())
5429            }
5430            TokenKind::Union => {
5431                self.advance();
5432                Ok("union".into())
5433            }
5434            TokenKind::If => {
5435                self.advance();
5436                Ok("if".into())
5437            }
5438            TokenKind::Match => {
5439                self.advance();
5440                Ok("match".into())
5441            }
5442            TokenKind::Loop => {
5443                self.advance();
5444                Ok("loop".into())
5445            }
5446            TokenKind::Async => {
5447                self.advance();
5448                Ok("async".into())
5449            }
5450            TokenKind::With => {
5451                self.advance();
5452                Ok("with".into())
5453            }
5454            TokenKind::Tool => {
5455                self.advance();
5456                Ok("tool".into())
5457            }
5458            TokenKind::Role => {
5459                self.advance();
5460                Ok("role".into())
5461            }
5462            TokenKind::Parallel => {
5463                self.advance();
5464                Ok("parallel".into())
5465            }
5466            TokenKind::From => {
5467                self.advance();
5468                Ok("from".into())
5469            }
5470            TokenKind::Where => {
5471                self.advance();
5472                Ok("where".into())
5473            }
5474            _ => Err(ParseError::Unexpected {
5475                found: format!("{}", tok.kind),
5476                expected: "identifier".into(),
5477                line: tok.span.line,
5478                col: tok.span.col,
5479            }),
5480        }
5481    }
5482
5483    /// Parse a type name after `is` or `as` keyword.
5484    /// Accepts identifiers and type keywords (Int, String, Bool, Float, List, Map, Null, etc.)
5485    fn expect_type_name_for_is(&mut self) -> Result<String, ParseError> {
5486        let tok = self.current().clone();
5487        match &tok.kind {
5488            TokenKind::Ident(name) => {
5489                let n = name.clone();
5490                self.advance();
5491                Ok(n)
5492            }
5493            TokenKind::Int_ => {
5494                self.advance();
5495                Ok("Int".into())
5496            }
5497            TokenKind::Float_ => {
5498                self.advance();
5499                Ok("Float".into())
5500            }
5501            TokenKind::String_ => {
5502                self.advance();
5503                Ok("String".into())
5504            }
5505            TokenKind::Bool => {
5506                self.advance();
5507                Ok("Bool".into())
5508            }
5509            TokenKind::Null => {
5510                self.advance();
5511                Ok("Null".into())
5512            }
5513            TokenKind::List => {
5514                self.advance();
5515                Ok("List".into())
5516            }
5517            TokenKind::Map => {
5518                self.advance();
5519                Ok("Map".into())
5520            }
5521            TokenKind::Set => {
5522                self.advance();
5523                Ok("Set".into())
5524            }
5525            TokenKind::Tuple => {
5526                self.advance();
5527                Ok("Tuple".into())
5528            }
5529            TokenKind::Bytes => {
5530                self.advance();
5531                Ok("Bytes".into())
5532            }
5533            TokenKind::Json => {
5534                self.advance();
5535                Ok("Json".into())
5536            }
5537            TokenKind::Result => {
5538                self.advance();
5539                Ok("Result".into())
5540            }
5541            _ => Err(ParseError::Unexpected {
5542                found: format!("{}", tok.kind),
5543                expected: "type name".into(),
5544                line: tok.span.line,
5545                col: tok.span.col,
5546            }),
5547        }
5548    }
5549
5550    fn expect_string(&mut self) -> Result<String, ParseError> {
5551        let tok = self.current().clone();
5552        match &tok.kind {
5553            TokenKind::StringLit(s) => {
5554                let s = s.clone();
5555                self.advance();
5556                Ok(s)
5557            }
5558            _ => Err(ParseError::Unexpected {
5559                found: format!("{}", tok.kind),
5560                expected: "string literal".into(),
5561                line: tok.span.line,
5562                col: tok.span.col,
5563            }),
5564        }
5565    }
5566
5567    fn rewrite_await_orchestration(&mut self, expr: Expr) -> Result<Expr, ParseError> {
5568        if let Some((name, base_args, span)) = self.orchestration_call_parts(&expr) {
5569            if name == "parallel" && matches!(self.peek_kind(), TokenKind::For) {
5570                return self.rewrite_await_parallel_for(base_args, span);
5571            }
5572            if self.await_block_follows() {
5573                return self.rewrite_await_orchestration_block(name, base_args, span);
5574            }
5575        }
5576        Ok(expr)
5577    }
5578
5579    fn rewrite_await_parallel_for(
5580        &mut self,
5581        mut base_args: Vec<CallArg>,
5582        call_span: Span,
5583    ) -> Result<Expr, ParseError> {
5584        self.expect(&TokenKind::For)?;
5585        let var = self.expect_ident()?;
5586        self.expect(&TokenKind::In)?;
5587        let iter = self.parse_expr(0)?;
5588        self.skip_newlines();
5589
5590        if matches!(self.peek_kind(), TokenKind::Indent) {
5591            self.advance();
5592            self.skip_newlines();
5593        }
5594        let body_expr = self.parse_expr(0)?;
5595        self.skip_newlines();
5596        if matches!(self.peek_kind(), TokenKind::Dedent) {
5597            self.advance();
5598        }
5599        let end_span = self.expect(&TokenKind::End)?.span;
5600
5601        let body_span = body_expr.span();
5602        let lambda = Expr::Lambda {
5603            params: vec![Param {
5604                name: var.clone(),
5605                ty: TypeExpr::Named("Any".into(), body_span),
5606                default_value: None,
5607                variadic: false,
5608                span: body_span,
5609            }],
5610            return_type: None,
5611            body: LambdaBody::Expr(Box::new(body_expr)),
5612            span: body_span,
5613        };
5614        let spawn_body = Expr::Call(
5615            Box::new(Expr::Ident("spawn".into(), body_span)),
5616            vec![
5617                CallArg::Positional(lambda),
5618                CallArg::Positional(Expr::Ident(var.clone(), body_span)),
5619            ],
5620            body_span,
5621        );
5622        let comp = Expr::Comprehension {
5623            body: Box::new(spawn_body),
5624            var,
5625            iter: Box::new(iter),
5626            condition: None,
5627            kind: ComprehensionKind::List,
5628            span: call_span.merge(end_span),
5629        };
5630        base_args.push(CallArg::Positional(comp));
5631        Ok(Expr::Call(
5632            Box::new(Expr::Ident("parallel".into(), call_span)),
5633            base_args,
5634            call_span.merge(end_span),
5635        ))
5636    }
5637
5638    fn rewrite_await_orchestration_block(
5639        &mut self,
5640        name: String,
5641        mut base_args: Vec<CallArg>,
5642        call_span: Span,
5643    ) -> Result<Expr, ParseError> {
5644        self.skip_newlines();
5645        let has_indent = matches!(self.peek_kind(), TokenKind::Indent);
5646        if has_indent {
5647            self.advance();
5648        }
5649        self.skip_newlines();
5650
5651        while !matches!(
5652            self.peek_kind(),
5653            TokenKind::End | TokenKind::Dedent | TokenKind::Eof
5654        ) {
5655            let branch_expr = self.parse_expr(0)?;
5656            base_args.push(CallArg::Positional(self.make_spawn_lambda(branch_expr)));
5657            self.skip_newlines();
5658        }
5659        if has_indent && matches!(self.peek_kind(), TokenKind::Dedent) {
5660            self.advance();
5661        }
5662        let end_span = self.expect(&TokenKind::End)?.span;
5663        Ok(Expr::Call(
5664            Box::new(Expr::Ident(name, call_span)),
5665            base_args,
5666            call_span.merge(end_span),
5667        ))
5668    }
5669
5670    fn make_spawn_lambda(&self, body_expr: Expr) -> Expr {
5671        let body_span = body_expr.span();
5672        let lambda = Expr::Lambda {
5673            params: vec![],
5674            return_type: None,
5675            body: LambdaBody::Expr(Box::new(body_expr)),
5676            span: body_span,
5677        };
5678        Expr::Call(
5679            Box::new(Expr::Ident("spawn".into(), body_span)),
5680            vec![CallArg::Positional(lambda)],
5681            body_span,
5682        )
5683    }
5684
5685    fn orchestration_call_parts(&self, expr: &Expr) -> Option<(String, Vec<CallArg>, Span)> {
5686        match expr {
5687            Expr::Ident(name, span)
5688                if matches!(name.as_str(), "parallel" | "race" | "vote" | "select") =>
5689            {
5690                Some((name.clone(), vec![], *span))
5691            }
5692            Expr::Call(callee, args, span) => {
5693                if let Expr::Ident(name, _) = callee.as_ref() {
5694                    if matches!(name.as_str(), "parallel" | "race" | "vote" | "select") {
5695                        return Some((name.clone(), args.clone(), *span));
5696                    }
5697                }
5698                None
5699            }
5700            _ => None,
5701        }
5702    }
5703
5704    fn await_block_follows(&self) -> bool {
5705        let mut i = self.pos;
5706        if !matches!(
5707            self.tokens.get(i).map(|t| &t.kind),
5708            Some(TokenKind::Newline)
5709        ) {
5710            return false;
5711        }
5712        while matches!(
5713            self.tokens.get(i).map(|t| &t.kind),
5714            Some(TokenKind::Newline)
5715        ) {
5716            i += 1;
5717        }
5718        matches!(self.tokens.get(i).map(|t| &t.kind), Some(TokenKind::Indent))
5719    }
5720
5721    fn parse_dotted_ident(&mut self) -> Result<String, ParseError> {
5722        let mut parts = vec![self.expect_ident()?];
5723        while matches!(self.peek_kind(), TokenKind::Dot) {
5724            self.advance();
5725            parts.push(self.expect_ident()?);
5726        }
5727        Ok(parts.join("."))
5728    }
5729
5730    /// Parse a program with error recovery enabled.
5731    /// Returns the program AST (possibly partial) and a vector of all parse errors encountered.
5732    /// If no errors occurred, the vector will be empty.
5733    pub fn parse_program_with_recovery(
5734        &mut self,
5735        directives: Vec<Directive>,
5736    ) -> (Program, Vec<ParseError>) {
5737        let result = self.parse_program(directives);
5738        let program = match result {
5739            Ok(program) => program,
5740            Err(err) => {
5741                // Preserve fatal parser failure so callers still emit diagnostics.
5742                self.record_error(err);
5743                Program {
5744                    directives: vec![],
5745                    items: vec![],
5746                    span: Span {
5747                        start: 0,
5748                        end: 0,
5749                        line: 1,
5750                        col: 1,
5751                    },
5752                }
5753            }
5754        };
5755        let errors = std::mem::take(&mut self.errors);
5756        (program, errors)
5757    }
5758}
5759
5760/// Parse tokens with error recovery.
5761/// Returns the program AST (possibly partial) and a vector of all parse errors encountered.
5762pub fn parse_with_recovery(
5763    tokens: Vec<Token>,
5764    directives: Vec<Directive>,
5765) -> (Program, Vec<ParseError>) {
5766    let mut parser = Parser::new(tokens);
5767    parser.parse_program_with_recovery(directives)
5768}
5769
5770#[cfg(test)]
5771mod tests {
5772    use super::*;
5773    use crate::compiler::lexer::Lexer;
5774
5775    fn parse_src(src: &str) -> Result<Program, ParseError> {
5776        let mut lexer = Lexer::new(src, 1, 0);
5777        let tokens = lexer.tokenize().unwrap();
5778        let mut parser = Parser::new(tokens);
5779        parser.parse_program(vec![])
5780    }
5781
5782    #[test]
5783    fn test_parse_record() {
5784        let prog = parse_src("record Foo\n  x: Int\n  y: String\nend").unwrap();
5785        assert_eq!(prog.items.len(), 1);
5786        if let Item::Record(r) = &prog.items[0] {
5787            assert_eq!(r.name, "Foo");
5788            assert_eq!(r.fields.len(), 2);
5789        } else {
5790            panic!("expected record");
5791        }
5792    }
5793
5794    #[test]
5795    fn test_parse_cell() {
5796        let prog = parse_src("cell add(a: Int, b: Int) -> Int\n  return a + b\nend").unwrap();
5797        assert_eq!(prog.items.len(), 1);
5798        if let Item::Cell(c) = &prog.items[0] {
5799            assert_eq!(c.name, "add");
5800            assert_eq!(c.params.len(), 2);
5801        } else {
5802            panic!("expected cell");
5803        }
5804    }
5805
5806    #[test]
5807    fn test_parse_cell_effect_row_preserved_after_return_type() {
5808        let prog = parse_src("cell main() -> Int / {http, emit}\n  return 1\nend").unwrap();
5809        assert_eq!(prog.items.len(), 1);
5810        if let Item::Cell(c) = &prog.items[0] {
5811            assert_eq!(c.effects, vec!["http".to_string(), "emit".to_string()]);
5812        } else {
5813            panic!("expected cell");
5814        }
5815    }
5816
5817    #[test]
5818    fn test_parse_enum() {
5819        let prog = parse_src("enum Color\n  Red\n  Green\n  Blue\nend").unwrap();
5820        if let Item::Enum(e) = &prog.items[0] {
5821            assert_eq!(e.name, "Color");
5822            assert_eq!(e.variants.len(), 3);
5823        } else {
5824            panic!("expected enum");
5825        }
5826    }
5827
5828    #[test]
5829    fn test_parse_match() {
5830        let src = "cell test(x: Int) -> String\n  match x\n    1 -> return \"one\"\n    _ -> return \"other\"\n  end\nend";
5831        let prog = parse_src(src).unwrap();
5832        assert_eq!(prog.items.len(), 1);
5833    }
5834
5835    #[test]
5836    fn test_parse_addon_stmt_in_block_preserved() {
5837        let src = r#"cell main() -> Int
5838  observe "batch"
5839    metrics:
5840      counter items_processed
5841    end
5842  in
5843    return 1
5844  end
5845end"#;
5846        let prog = parse_src(src).unwrap();
5847        if let Item::Cell(c) = &prog.items[0] {
5848            assert_eq!(c.body.len(), 1);
5849            match &c.body[0] {
5850                Stmt::If(ifs) => {
5851                    assert_eq!(ifs.then_body.len(), 1);
5852                    assert!(matches!(ifs.then_body[0], Stmt::Return(_)));
5853                }
5854                _ => panic!("expected addon in-body to lower to executable block"),
5855            }
5856        } else {
5857            panic!("expected cell");
5858        }
5859    }
5860
5861    #[test]
5862    fn test_parse_machine_state_block() {
5863        let src = r#"machine TicketFlow
5864  initial: Start
5865  state Start(ticket: Int)
5866    guard: ticket > 0
5867    on_enter() / {trace}
5868      transition Done(ticket)
5869    end
5870  end
5871  state Done(value: Int)
5872    terminal: true
5873  end
5874end"#;
5875        let prog = parse_src(src).unwrap();
5876        assert_eq!(prog.items.len(), 1);
5877        if let Item::Process(p) = &prog.items[0] {
5878            assert_eq!(p.kind, "machine");
5879            assert_eq!(p.name, "TicketFlow");
5880            assert_eq!(p.machine_initial.as_deref(), Some("Start"));
5881            assert_eq!(p.machine_states.len(), 2);
5882            let start_state = p
5883                .machine_states
5884                .iter()
5885                .find(|s| s.name == "Start")
5886                .expect("Start state should be parsed");
5887            assert_eq!(start_state.params.len(), 1);
5888            assert_eq!(start_state.params[0].name, "ticket");
5889            assert!(start_state.guard.is_some());
5890            assert_eq!(start_state.transition_to.as_deref(), Some("Done"));
5891            assert_eq!(start_state.transition_args.len(), 1);
5892            let done_state = p
5893                .machine_states
5894                .iter()
5895                .find(|s| s.name == "Done")
5896                .expect("Done state should be parsed");
5897            assert_eq!(done_state.params.len(), 1);
5898            assert_eq!(done_state.params[0].name, "value");
5899            assert!(done_state.terminal);
5900        } else {
5901            panic!("expected process");
5902        }
5903    }
5904
5905    #[test]
5906    fn test_parse_match_arm_line_continuation_with_null_coalesce() {
5907        let src = r#"cell main() -> String
5908  loop
5909    let result = foo()
5910    match result
5911      Response.Handoff(target, reason) ->
5912        let current_agent = self.agents.find(fn(a) => a.name == target)
5913          ?? halt("Unknown agent: {target}")
5914      Response.Escalate(reason) ->
5915        return "ok"
5916    end
5917  end
5918end"#;
5919        let prog = parse_src(src).unwrap();
5920        assert_eq!(prog.items.len(), 1);
5921    }
5922
5923    #[test]
5924    fn test_parse_machine_states_with_empty_bodies() {
5925        let src = r#"machine DistributedOrder
5926  @replicated(factor: 3)
5927
5928  state Pending
5929    # ...
5930  end
5931
5932  state Processing
5933    @location("warehouse-{region}")
5934    # ...
5935  end
5936
5937  state Shipped
5938    # ...
5939  end
5940end"#;
5941        let prog = parse_src(src).unwrap();
5942        assert_eq!(prog.items.len(), 1);
5943        assert!(matches!(prog.items[0], Item::Process(_)));
5944    }
5945
5946    #[test]
5947    fn test_parse_pipeline_stages_chain() {
5948        let src = r#"pipeline InvoicePipeline
5949  stages:
5950    Extractor.extract
5951      -> Validator.validate
5952      -> Writer.store
5953  end
5954end"#;
5955        let prog = parse_src(src).unwrap();
5956        let Item::Process(p) = &prog.items[0] else {
5957            panic!("expected process");
5958        };
5959        assert_eq!(p.kind, "pipeline");
5960        assert_eq!(
5961            p.pipeline_stages,
5962            vec![
5963                "Extractor.extract".to_string(),
5964                "Validator.validate".to_string(),
5965                "Writer.store".to_string()
5966            ]
5967        );
5968    }
5969
5970    #[test]
5971    fn test_parse_await_parallel_for_desugars_to_spawn_comprehension() {
5972        let src = r#"cell main() -> Int / {async}
5973  let values = await parallel for i in 0..3
5974    i * 2
5975  end
5976  return length(values)
5977end"#;
5978        let prog = parse_src(src).unwrap();
5979        let Item::Cell(cell) = &prog.items[0] else {
5980            panic!("expected cell");
5981        };
5982        let Stmt::Let(let_stmt) = &cell.body[0] else {
5983            panic!("expected let");
5984        };
5985        let Expr::AwaitExpr(inner, _) = &let_stmt.value else {
5986            panic!("expected await expression");
5987        };
5988        let Expr::Call(callee, args, _) = inner.as_ref() else {
5989            panic!("expected orchestration call");
5990        };
5991        assert!(matches!(callee.as_ref(), Expr::Ident(name, _) if name == "parallel"));
5992        assert_eq!(args.len(), 1);
5993        let CallArg::Positional(Expr::Comprehension { body, var, .. }) = &args[0] else {
5994            panic!("expected list comprehension argument");
5995        };
5996        assert_eq!(var, "i");
5997        assert!(matches!(body.as_ref(), Expr::Call(_, _, _)));
5998    }
5999
6000    #[test]
6001    fn test_parse_await_race_block_desugars_to_spawn_calls() {
6002        let src = r#"cell main() -> Int / {async}
6003  let fastest = await race
6004    10
6005    20
6006  end
6007  return fastest
6008end"#;
6009        let prog = parse_src(src).unwrap();
6010        let Item::Cell(cell) = &prog.items[0] else {
6011            panic!("expected cell");
6012        };
6013        let Stmt::Let(let_stmt) = &cell.body[0] else {
6014            panic!("expected let");
6015        };
6016        let Expr::AwaitExpr(inner, _) = &let_stmt.value else {
6017            panic!("expected await expression");
6018        };
6019        let Expr::Call(callee, args, _) = inner.as_ref() else {
6020            panic!("expected orchestration call");
6021        };
6022        assert!(matches!(callee.as_ref(), Expr::Ident(name, _) if name == "race"));
6023        assert_eq!(args.len(), 2);
6024        for arg in args {
6025            assert!(matches!(arg, CallArg::Positional(Expr::Call(_, _, _))));
6026        }
6027    }
6028
6029    #[test]
6030    fn test_if_let_desugars_to_match() {
6031        let src = r#"cell test(x: Int) -> Int
6032  if let ok(val) = get_result()
6033    return val
6034  else
6035    return 0
6036  end
6037end"#;
6038        let prog = parse_src(src).unwrap();
6039        let Item::Cell(c) = &prog.items[0] else {
6040            panic!("expected cell");
6041        };
6042        // if let should desugar to a match statement
6043        let Stmt::Match(ms) = &c.body[0] else {
6044            panic!("expected match from if-let desugar, got {:?}", c.body[0]);
6045        };
6046        assert_eq!(ms.arms.len(), 2);
6047        // First arm should be the pattern (ok(val))
6048        assert!(
6049            matches!(&ms.arms[0].pattern, Pattern::Variant(name, Some(binding), _)
6050            if name == "ok" && matches!(binding.as_ref(), Pattern::Ident(bind_name, _) if bind_name == "val"))
6051        );
6052        // Second arm should be the wildcard (else branch)
6053        assert!(matches!(&ms.arms[1].pattern, Pattern::Wildcard(_)));
6054    }
6055
6056    #[test]
6057    fn test_if_let_no_else() {
6058        let src = r#"cell test(x: Int) -> Int
6059  if let ok(val) = get_result()
6060    print(val)
6061  end
6062  return 0
6063end"#;
6064        let prog = parse_src(src).unwrap();
6065        let Item::Cell(c) = &prog.items[0] else {
6066            panic!("expected cell");
6067        };
6068        let Stmt::Match(ms) = &c.body[0] else {
6069            panic!("expected match from if-let desugar");
6070        };
6071        // No else branch means only one match arm
6072        assert_eq!(ms.arms.len(), 1);
6073        assert!(matches!(&ms.arms[0].pattern, Pattern::Variant(name, _, _)
6074            if name == "ok"));
6075    }
6076
6077    #[test]
6078    fn test_while_let_desugars_to_loop_match() {
6079        let src = r#"cell test(items: list[Int]) -> Int
6080  while let ok(item) = next(items)
6081    print(item)
6082  end
6083  return 0
6084end"#;
6085        let prog = parse_src(src).unwrap();
6086        let Item::Cell(c) = &prog.items[0] else {
6087            panic!("expected cell");
6088        };
6089        // while let should desugar to a loop
6090        let Stmt::Loop(ls) = &c.body[0] else {
6091            panic!("expected loop from while-let desugar, got {:?}", c.body[0]);
6092        };
6093        // Loop body should contain a match
6094        assert_eq!(ls.body.len(), 1);
6095        let Stmt::Match(ms) = &ls.body[0] else {
6096            panic!("expected match inside loop");
6097        };
6098        assert_eq!(ms.arms.len(), 2);
6099        // First arm: the pattern
6100        assert!(matches!(&ms.arms[0].pattern, Pattern::Variant(name, _, _)
6101            if name == "ok"));
6102        // Second arm: wildcard with break
6103        assert!(matches!(&ms.arms[1].pattern, Pattern::Wildcard(_)));
6104        assert!(matches!(&ms.arms[1].body[0], Stmt::Break(_)));
6105    }
6106
6107    #[test]
6108    fn test_expr_position_match_produces_match_expr() {
6109        let src = r#"cell test(x: Int) -> String
6110  let y = match x
6111    1 -> "one"
6112    _ -> "other"
6113  end
6114  return y
6115end"#;
6116        let prog = parse_src(src).unwrap();
6117        let Item::Cell(c) = &prog.items[0] else {
6118            panic!("expected cell");
6119        };
6120        let Stmt::Let(ls) = &c.body[0] else {
6121            panic!("expected let");
6122        };
6123        // The value should be a MatchExpr, not an Ident placeholder
6124        assert!(
6125            matches!(&ls.value, Expr::MatchExpr { arms, .. } if arms.len() == 2),
6126            "expected MatchExpr with 2 arms, got {:?}",
6127            ls.value
6128        );
6129    }
6130
6131    #[test]
6132    fn test_expr_position_if_produces_block_expr() {
6133        let src = r#"cell test(x: Int) -> Int
6134  let y = if x > 0
6135    return 1
6136  else
6137    return 0
6138  end
6139  return y
6140end"#;
6141        let prog = parse_src(src).unwrap();
6142        let Item::Cell(c) = &prog.items[0] else {
6143            panic!("expected cell");
6144        };
6145        let Stmt::Let(ls) = &c.body[0] else {
6146            panic!("expected let");
6147        };
6148        // The value should be a BlockExpr containing an If statement
6149        assert!(
6150            matches!(&ls.value, Expr::BlockExpr(stmts, _) if matches!(&stmts[0], Stmt::If(_))),
6151            "expected BlockExpr with If, got {:?}",
6152            ls.value
6153        );
6154    }
6155
6156    #[test]
6157    fn test_expr_position_loop_produces_block_expr() {
6158        let src = r#"cell test() -> Int
6159  let y = loop
6160    break 42
6161  end
6162  return y
6163end"#;
6164        let prog = parse_src(src).unwrap();
6165        let Item::Cell(c) = &prog.items[0] else {
6166            panic!("expected cell");
6167        };
6168        let Stmt::Let(ls) = &c.body[0] else {
6169            panic!("expected let");
6170        };
6171        assert!(
6172            matches!(&ls.value, Expr::BlockExpr(stmts, _) if matches!(&stmts[0], Stmt::Loop(_))),
6173            "expected BlockExpr with Loop, got {:?}",
6174            ls.value
6175        );
6176    }
6177
6178    #[test]
6179    fn test_for_loop_tuple_destructuring() {
6180        let src = r#"cell test(m: map[String, Int]) -> Int
6181  for (k, v) in m
6182    print(k)
6183  end
6184  return 0
6185end"#;
6186        let prog = parse_src(src).unwrap();
6187        let Item::Cell(c) = &prog.items[0] else {
6188            panic!("expected cell");
6189        };
6190        let Stmt::For(fs) = &c.body[0] else {
6191            panic!("expected for, got {:?}", c.body[0]);
6192        };
6193        // var should be the first name for backwards compat
6194        assert_eq!(fs.var, "k");
6195        // pattern should capture both names
6196        let Some(Pattern::TupleDestructure { elements, .. }) = &fs.pattern else {
6197            panic!("expected tuple destructure pattern, got {:?}", fs.pattern);
6198        };
6199        assert_eq!(elements.len(), 2);
6200        assert!(matches!(&elements[0], Pattern::Ident(n, _) if n == "k"));
6201        assert!(matches!(&elements[1], Pattern::Ident(n, _) if n == "v"));
6202    }
6203
6204    #[test]
6205    fn test_let_variant_destructuring() {
6206        let src = r#"cell test() -> Int
6207  let ok(val) = get_result()
6208  return val
6209end"#;
6210        let prog = parse_src(src).unwrap();
6211        let Item::Cell(c) = &prog.items[0] else {
6212            panic!("expected cell");
6213        };
6214        let Stmt::Let(ls) = &c.body[0] else {
6215            panic!("expected let");
6216        };
6217        assert_eq!(ls.name, "val");
6218        let Some(Pattern::Variant(name, binding, _)) = &ls.pattern else {
6219            panic!("expected variant pattern, got {:?}", ls.pattern);
6220        };
6221        assert_eq!(name, "ok");
6222        assert!(matches!(
6223            binding.as_deref(),
6224            Some(Pattern::Ident(bind_name, _)) if bind_name == "val"
6225        ));
6226    }
6227
6228    #[test]
6229    fn test_let_brace_destructuring() {
6230        let src = r#"cell test() -> Int
6231  let { x, y } = get_point()
6232  return x
6233end"#;
6234        let prog = parse_src(src).unwrap();
6235        let Item::Cell(c) = &prog.items[0] else {
6236            panic!("expected cell");
6237        };
6238        let Stmt::Let(ls) = &c.body[0] else {
6239            panic!("expected let");
6240        };
6241        assert_eq!(ls.name, "x");
6242        let Some(Pattern::RecordDestructure { fields, .. }) = &ls.pattern else {
6243            panic!("expected record destructure pattern, got {:?}", ls.pattern);
6244        };
6245        assert_eq!(fields.len(), 2);
6246        assert_eq!(fields[0].0, "x");
6247        assert_eq!(fields[1].0, "y");
6248    }
6249
6250    #[test]
6251    fn test_for_loop_simple_var_no_pattern() {
6252        let src = r#"cell test(items: list[Int]) -> Int
6253  for item in items
6254    print(item)
6255  end
6256  return 0
6257end"#;
6258        let prog = parse_src(src).unwrap();
6259        let Item::Cell(c) = &prog.items[0] else {
6260            panic!("expected cell");
6261        };
6262        let Stmt::For(fs) = &c.body[0] else {
6263            panic!("expected for");
6264        };
6265        assert_eq!(fs.var, "item");
6266        assert!(fs.pattern.is_none());
6267    }
6268
6269    // ── Error Recovery Tests ──
6270
6271    #[test]
6272    fn test_error_recovery_multiple_errors() {
6273        // Multiple parse errors in same file
6274        let src = r#"
6275cell bad1() -> Int
6276  let x =
6277
6278cell good() -> Int
6279  return 42
6280end
6281
6282cell bad2() -> String
6283  return
6284
6285enum GoodEnum
6286  A
6287  B
6288end
6289"#;
6290        let mut lexer = Lexer::new(src, 1, 0);
6291        let tokens = lexer.tokenize().unwrap();
6292        let (program, errors) = parse_with_recovery(tokens, vec![]);
6293
6294        // Should have parse errors from bad1 and bad2
6295        assert!(!errors.is_empty(), "Expected at least 1 parse error");
6296
6297        // Should still parse valid declarations
6298        let has_good_cell = program
6299            .items
6300            .iter()
6301            .any(|item| matches!(item, Item::Cell(c) if c.name == "good"));
6302        let has_good_enum = program
6303            .items
6304            .iter()
6305            .any(|item| matches!(item, Item::Enum(e) if e.name == "GoodEnum"));
6306
6307        assert!(
6308            has_good_cell || has_good_enum,
6309            "Should parse at least one valid declaration after errors"
6310        );
6311    }
6312
6313    #[test]
6314    fn test_error_recovery_continues_after_bad_declaration() {
6315        let src = r#"
6316record Point
6317  x: Int
6318  invalid: something wrong
6319
6320cell get_x() -> Int
6321  return 1
6322end
6323
6324record Color
6325  r: Int
6326  g: Int
6327  b: Int
6328end
6329"#;
6330        let mut lexer = Lexer::new(src, 1, 0);
6331        let tokens = lexer.tokenize().unwrap();
6332        let (program, _errors) = parse_with_recovery(tokens, vec![]);
6333
6334        // Should have at least one error (from Point)
6335        // Note: Some errors might be recovered gracefully, so we check for any valid parsing
6336        let has_cell = program
6337            .items
6338            .iter()
6339            .any(|item| matches!(item, Item::Cell(c) if c.name == "get_x"));
6340        let has_color = program
6341            .items
6342            .iter()
6343            .any(|item| matches!(item, Item::Record(r) if r.name == "Color"));
6344
6345        assert!(
6346            has_cell || has_color,
6347            "Should parse declarations after error in Point"
6348        );
6349    }
6350
6351    #[test]
6352    fn test_error_recovery_valid_declarations_after_errors() {
6353        let src = r#"
6354cell bad_one() -> Int
6355  let x =
6356  // Incomplete statement
6357
6358cell good_one() -> Int
6359  return 42
6360end
6361
6362cell bad_two() -> String
6363  return
6364
6365cell good_two() -> Bool
6366  return false
6367end
6368"#;
6369        let mut lexer = Lexer::new(src, 1, 0);
6370        let tokens = lexer.tokenize().unwrap();
6371        let (program, errors) = parse_with_recovery(tokens, vec![]);
6372
6373        // Should have errors
6374        assert!(!errors.is_empty(), "Expected parse errors");
6375
6376        // Should parse valid cells
6377        let good_cells: Vec<_> = program
6378            .items
6379            .iter()
6380            .filter_map(|item| {
6381                if let Item::Cell(c) = item {
6382                    Some(c.name.as_str())
6383                } else {
6384                    None
6385                }
6386            })
6387            .collect();
6388
6389        assert!(
6390            good_cells.contains(&"good_one") || good_cells.contains(&"good_two"),
6391            "Should parse at least one valid cell after errors"
6392        );
6393    }
6394
6395    #[test]
6396    fn test_error_recovery_single_error() {
6397        // Single error should work the same way
6398        let src = r#"
6399cell test() -> Int
6400  return 1
6401  // Missing 'end'
6402"#;
6403        let mut lexer = Lexer::new(src, 1, 0);
6404        let tokens = lexer.tokenize().unwrap();
6405        let (_, errors) = parse_with_recovery(tokens, vec![]);
6406
6407        assert!(!errors.is_empty(), "Should report at least 1 error");
6408    }
6409
6410    #[test]
6411    fn test_error_recovery_empty_file() {
6412        let src = "";
6413        let mut lexer = Lexer::new(src, 1, 0);
6414        let tokens = lexer.tokenize().unwrap();
6415        let (program, errors) = parse_with_recovery(tokens, vec![]);
6416
6417        assert!(errors.is_empty(), "Empty file should have no errors");
6418        assert!(program.items.is_empty(), "Empty file should have no items");
6419    }
6420
6421    #[test]
6422    fn test_error_recovery_synchronizes_on_keywords() {
6423        let src = r#"
6424cell bad() -> Int
6425  return
6426
6427enum Color
6428  Red
6429  Green
6430  Blue
6431end
6432
6433cell process() -> Int
6434  return 1
6435end
6436"#;
6437        let mut lexer = Lexer::new(src, 1, 0);
6438        let tokens = lexer.tokenize().unwrap();
6439        let (program, _errors) = parse_with_recovery(tokens, vec![]);
6440
6441        // Parser should attempt recovery and continue
6442        // Should parse enum and cell after error in bad()
6443        let has_enum = program
6444            .items
6445            .iter()
6446            .any(|item| matches!(item, Item::Enum(e) if e.name == "Color"));
6447        let has_cell = program
6448            .items
6449            .iter()
6450            .any(|item| matches!(item, Item::Cell(c) if c.name == "process"));
6451
6452        assert!(
6453            has_enum || has_cell,
6454            "Should parse at least one declaration after synchronization"
6455        );
6456    }
6457
6458    #[test]
6459    fn test_error_recovery_mixed_process_types() {
6460        let src = r#"
6461cell bad() -> Int
6462  let x =
6463  return 1
6464end
6465
6466record GoodRecord
6467  count: Int
6468end
6469"#;
6470        let mut lexer = Lexer::new(src, 1, 0);
6471        let tokens = lexer.tokenize().unwrap();
6472        let (program, _errors) = parse_with_recovery(tokens, vec![]);
6473
6474        // Should continue parsing after error in bad cell
6475        let has_record = program
6476            .items
6477            .iter()
6478            .any(|item| matches!(item, Item::Record(r) if r.name == "GoodRecord"));
6479
6480        assert!(has_record, "Should parse record after error in cell");
6481    }
6482
6483    // ===== BULLETPROOF ERROR RECOVERY TESTS =====
6484
6485    #[test]
6486    fn test_recovery_missing_type_annotation() {
6487        let src = r#"
6488cell test() -> Int
6489  let x: = 5
6490  return x
6491end
6492
6493cell good() -> Int
6494  return 42
6495end
6496"#;
6497        let mut lexer = Lexer::new(src, 1, 0);
6498        let tokens = lexer.tokenize().unwrap();
6499        let (program, errors) = parse_with_recovery(tokens, vec![]);
6500
6501        // Should report error about missing type
6502        assert!(!errors.is_empty(), "Should report missing type error");
6503
6504        // Should still parse the good cell
6505        let has_good = program
6506            .items
6507            .iter()
6508            .any(|item| matches!(item, Item::Cell(c) if c.name == "good"));
6509        assert!(has_good, "Should parse valid cell after error");
6510    }
6511
6512    #[test]
6513    fn test_recovery_multiple_independent_errors() {
6514        let src = r#"
6515cell bad1() -> Int
6516  let x =
6517  return 1
6518end
6519
6520cell bad2() -> String
6521  return
6522end
6523
6524cell good() -> Bool
6525  return true
6526end
6527
6528record Bad
6529  x:
6530  y: Int
6531end
6532
6533record Good
6534  a: Int
6535  b: String
6536end
6537"#;
6538        let mut lexer = Lexer::new(src, 1, 0);
6539        let tokens = lexer.tokenize().unwrap();
6540        let (program, errors) = parse_with_recovery(tokens, vec![]);
6541
6542        // Should report multiple errors
6543        assert!(
6544            errors.len() >= 2,
6545            "Should report multiple independent errors"
6546        );
6547
6548        // Should parse valid declarations
6549        let has_good_cell = program
6550            .items
6551            .iter()
6552            .any(|item| matches!(item, Item::Cell(c) if c.name == "good"));
6553        let has_good_record = program
6554            .items
6555            .iter()
6556            .any(|item| matches!(item, Item::Record(r) if r.name == "Good"));
6557
6558        assert!(has_good_cell, "Should parse good cell");
6559        assert!(has_good_record, "Should parse good record");
6560    }
6561
6562    #[test]
6563    fn test_recovery_unclosed_paren() {
6564        let src = r#"
6565cell bad() -> Int
6566  let x = (1 + 2
6567  return x
6568end
6569
6570cell good() -> Int
6571  return 10
6572end
6573"#;
6574        let mut lexer = Lexer::new(src, 1, 0);
6575        let tokens = lexer.tokenize().unwrap();
6576        let (program, errors) = parse_with_recovery(tokens, vec![]);
6577
6578        assert!(!errors.is_empty(), "Should report unclosed paren error");
6579
6580        // Should still attempt to parse following cells
6581        let cell_count = program
6582            .items
6583            .iter()
6584            .filter(|item| matches!(item, Item::Cell(_)))
6585            .count();
6586        assert!(cell_count >= 1, "Should parse at least one cell");
6587    }
6588
6589    #[test]
6590    fn test_recovery_malformed_if_stmt() {
6591        let src = r#"
6592cell test() -> Int
6593  if
6594    return 1
6595  end
6596  return 0
6597end
6598
6599cell good() -> Int
6600  if true
6601    return 5
6602  end
6603  return 0
6604end
6605"#;
6606        let mut lexer = Lexer::new(src, 1, 0);
6607        let tokens = lexer.tokenize().unwrap();
6608        let (program, errors) = parse_with_recovery(tokens, vec![]);
6609
6610        assert!(!errors.is_empty(), "Should report malformed if error");
6611
6612        let has_good = program
6613            .items
6614            .iter()
6615            .any(|item| matches!(item, Item::Cell(c) if c.name == "good"));
6616        assert!(has_good, "Should parse valid cell after error");
6617    }
6618
6619    #[test]
6620    fn test_recovery_exact_error_locations() {
6621        let src = r#"
6622cell test() -> Int
6623  let x: = 5
6624  return x
6625end
6626"#;
6627        let mut lexer = Lexer::new(src, 1, 0);
6628        let tokens = lexer.tokenize().unwrap();
6629        let (_program, errors) = parse_with_recovery(tokens, vec![]);
6630
6631        assert!(!errors.is_empty(), "Should have at least one error");
6632
6633        // Verify error has line and column information
6634        for err in &errors {
6635            match err {
6636                ParseError::Unexpected { line, col, .. } => {
6637                    assert!(*line > 0, "Error should have valid line number");
6638                    assert!(*col > 0, "Error should have valid column number");
6639                }
6640                ParseError::MissingType { line, col } => {
6641                    assert!(*line > 0, "Error should have valid line number");
6642                    assert!(*col > 0, "Error should have valid column number");
6643                }
6644                ParseError::IncompleteExpression { line, col, .. } => {
6645                    assert!(*line > 0, "Error should have valid line number");
6646                    assert!(*col > 0, "Error should have valid column number");
6647                }
6648                ParseError::MalformedConstruct { line, col, .. } => {
6649                    assert!(*line > 0, "Error should have valid line number");
6650                    assert!(*col > 0, "Error should have valid column number");
6651                }
6652                ParseError::UnclosedBracket {
6653                    open_line,
6654                    open_col,
6655                    current_line,
6656                    current_col,
6657                    ..
6658                } => {
6659                    assert!(
6660                        *open_line > 0 && *current_line > 0,
6661                        "Should have valid line numbers"
6662                    );
6663                    assert!(
6664                        *open_col > 0 && *current_col > 0,
6665                        "Should have valid column numbers"
6666                    );
6667                }
6668                ParseError::MissingEnd {
6669                    open_line,
6670                    open_col,
6671                    current_line,
6672                    current_col,
6673                    ..
6674                } => {
6675                    assert!(
6676                        *open_line > 0 && *current_line > 0,
6677                        "Should have valid line numbers"
6678                    );
6679                    assert!(
6680                        *open_col > 0 && *current_col > 0,
6681                        "Should have valid column numbers"
6682                    );
6683                }
6684                _ => {}
6685            }
6686        }
6687    }
6688
6689    #[test]
6690    fn test_recovery_all_errors_collected() {
6691        let src = r#"
6692cell bad1() -> Int
6693  let x =
6694  return 1
6695end
6696
6697cell bad2() -> Int
6698  return
6699end
6700
6701cell good() -> Int
6702  return 1
6703end
6704"#;
6705        let mut lexer = Lexer::new(src, 1, 0);
6706        let tokens = lexer.tokenize().unwrap();
6707        let (program, errors) = parse_with_recovery(tokens, vec![]);
6708
6709        // Should collect multiple errors in one pass
6710        assert!(
6711            errors.len() >= 2,
6712            "Should collect multiple errors: got {}",
6713            errors.len()
6714        );
6715
6716        // Should still parse the valid cell
6717        let has_cell = program
6718            .items
6719            .iter()
6720            .any(|item| matches!(item, Item::Cell(c) if c.name == "good"));
6721        assert!(has_cell, "Should parse valid declaration after errors");
6722    }
6723
6724    #[test]
6725    fn test_recovery_no_cascading_undefined_var() {
6726        // This test verifies that parser completes despite type errors
6727        // (Actual cascading prevention happens in type checker, not parser)
6728        let src = r#"
6729cell test() -> Int
6730  let x = (1 + unclosed
6731  let y = x + 1
6732  return x + y
6733end
6734"#;
6735        let mut lexer = Lexer::new(src, 1, 0);
6736        let tokens = lexer.tokenize().unwrap();
6737        let (program, _errors) = parse_with_recovery(tokens, vec![]);
6738
6739        // The program should parse (with error recovery)
6740        let has_cell = program
6741            .items
6742            .iter()
6743            .any(|item| matches!(item, Item::Cell(_)));
6744        assert!(has_cell, "Should parse cell structure despite parse errors");
6745    }
6746
6747    #[test]
6748    fn test_recovery_synchronize_on_newline_stmt() {
6749        let src = r#"
6750cell test() -> Int
6751  let x = 1 +
6752  let y = 2
6753  return y
6754end
6755"#;
6756        let mut lexer = Lexer::new(src, 1, 0);
6757        let tokens = lexer.tokenize().unwrap();
6758        let (program, errors) = parse_with_recovery(tokens, vec![]);
6759
6760        assert!(!errors.is_empty(), "Should report incomplete expression");
6761
6762        let has_cell = program
6763            .items
6764            .iter()
6765            .any(|item| matches!(item, Item::Cell(c) if c.name == "test"));
6766        assert!(has_cell, "Should parse cell with recovery");
6767    }
6768
6769    #[test]
6770    fn test_recovery_nested_errors() {
6771        let src = r#"
6772cell outer() -> Int
6773  if true
6774    let x =
6775    return 1
6776  end
6777  return 0
6778end
6779
6780cell good() -> Bool
6781  return false
6782end
6783"#;
6784        let mut lexer = Lexer::new(src, 1, 0);
6785        let tokens = lexer.tokenize().unwrap();
6786        let (program, errors) = parse_with_recovery(tokens, vec![]);
6787
6788        assert!(!errors.is_empty(), "Should report nested error");
6789
6790        let has_good = program
6791            .items
6792            .iter()
6793            .any(|item| matches!(item, Item::Cell(c) if c.name == "good"));
6794        assert!(has_good, "Should parse cell after nested error");
6795    }
6796
6797    #[test]
6798    fn test_recovery_actionable_error_messages() {
6799        let src = r#"
6800cell test() -> Int
6801  let x: = 5
6802  return x
6803end
6804"#;
6805        let mut lexer = Lexer::new(src, 1, 0);
6806        let tokens = lexer.tokenize().unwrap();
6807        let (_program, errors) = parse_with_recovery(tokens, vec![]);
6808
6809        assert!(!errors.is_empty(), "Should have errors");
6810
6811        // Check that error messages contain useful context
6812        for err in &errors {
6813            let msg = format!("{}", err);
6814            // Error messages should contain line/col info
6815            assert!(
6816                msg.contains("line") || msg.contains("col"),
6817                "Error message should mention line/col: {}",
6818                msg
6819            );
6820        }
6821    }
6822}