Skip to main content

harn_parser/
parser.rs

1use crate::ast::*;
2use harn_lexer::{Span, Token, TokenKind};
3use std::fmt;
4
5/// Parser errors.
6#[derive(Debug, Clone, PartialEq)]
7pub enum ParserError {
8    Unexpected {
9        got: String,
10        expected: String,
11        span: Span,
12    },
13    UnexpectedEof {
14        expected: String,
15    },
16}
17
18impl fmt::Display for ParserError {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        match self {
21            ParserError::Unexpected {
22                got,
23                expected,
24                span,
25            } => write!(
26                f,
27                "Expected {expected}, got {got} at {}:{}",
28                span.line, span.column
29            ),
30            ParserError::UnexpectedEof { expected } => {
31                write!(f, "Unexpected end of file, expected {expected}")
32            }
33        }
34    }
35}
36
37impl std::error::Error for ParserError {}
38
39/// Recursive descent parser for Harn.
40pub struct Parser {
41    tokens: Vec<Token>,
42    pos: usize,
43    errors: Vec<ParserError>,
44}
45
46impl Parser {
47    pub fn new(tokens: Vec<Token>) -> Self {
48        Self {
49            tokens,
50            pos: 0,
51            errors: Vec::new(),
52        }
53    }
54
55    fn current_span(&self) -> Span {
56        self.tokens
57            .get(self.pos)
58            .map(|t| t.span)
59            .unwrap_or(Span::dummy())
60    }
61
62    fn current_kind(&self) -> Option<&TokenKind> {
63        self.tokens.get(self.pos).map(|t| &t.kind)
64    }
65
66    fn prev_span(&self) -> Span {
67        if self.pos > 0 {
68            self.tokens[self.pos - 1].span
69        } else {
70            Span::dummy()
71        }
72    }
73
74    /// Parse a complete .harn file. Reports multiple errors via recovery.
75    pub fn parse(&mut self) -> Result<Vec<SNode>, ParserError> {
76        let mut nodes = Vec::new();
77        self.skip_newlines();
78
79        while !self.is_at_end() {
80            // Skip any stray closing braces at top level (after recovery)
81            if self.check(&TokenKind::RBrace) {
82                self.advance();
83                self.skip_newlines();
84                continue;
85            }
86
87            let result = if self.check(&TokenKind::Import) {
88                self.parse_import()
89            } else if self.check(&TokenKind::Pipeline) {
90                self.parse_pipeline()
91            } else {
92                self.parse_statement()
93            };
94
95            match result {
96                Ok(node) => nodes.push(node),
97                Err(err) => {
98                    self.errors.push(err);
99                    self.synchronize();
100                }
101            }
102            self.skip_newlines();
103        }
104
105        if let Some(first) = self.errors.first() {
106            return Err(first.clone());
107        }
108        Ok(nodes)
109    }
110
111    /// Return all accumulated parser errors (after `parse()` returns).
112    pub fn all_errors(&self) -> &[ParserError] {
113        &self.errors
114    }
115
116    /// Check if the current token is one that starts a statement.
117    fn is_statement_start(&self) -> bool {
118        matches!(
119            self.current_kind(),
120            Some(
121                TokenKind::Let
122                    | TokenKind::Var
123                    | TokenKind::If
124                    | TokenKind::For
125                    | TokenKind::While
126                    | TokenKind::Match
127                    | TokenKind::Retry
128                    | TokenKind::Return
129                    | TokenKind::Throw
130                    | TokenKind::Fn
131                    | TokenKind::Pub
132                    | TokenKind::Try
133                    | TokenKind::Select
134                    | TokenKind::Pipeline
135                    | TokenKind::Import
136                    | TokenKind::Parallel
137                    | TokenKind::ParallelMap
138                    | TokenKind::ParallelSettle
139                    | TokenKind::Enum
140                    | TokenKind::Struct
141                    | TokenKind::Interface
142                    | TokenKind::Guard
143                    | TokenKind::Deadline
144                    | TokenKind::Yield
145                    | TokenKind::Mutex
146            )
147        )
148    }
149
150    /// Advance past tokens until we reach a likely statement boundary.
151    fn synchronize(&mut self) {
152        while !self.is_at_end() {
153            if self.check(&TokenKind::Newline) {
154                self.advance();
155                if self.is_at_end() || self.is_statement_start() {
156                    return;
157                }
158                continue;
159            }
160            if self.check(&TokenKind::RBrace) {
161                return;
162            }
163            self.advance();
164        }
165    }
166
167    /// Parse a single expression (for string interpolation).
168    pub fn parse_single_expression(&mut self) -> Result<SNode, ParserError> {
169        self.skip_newlines();
170        self.parse_expression()
171    }
172
173    // --- Declarations ---
174
175    fn parse_pipeline(&mut self) -> Result<SNode, ParserError> {
176        let start = self.current_span();
177        self.consume(&TokenKind::Pipeline, "pipeline")?;
178        let name = self.consume_identifier("pipeline name")?;
179
180        self.consume(&TokenKind::LParen, "(")?;
181        let params = self.parse_param_list()?;
182        self.consume(&TokenKind::RParen, ")")?;
183
184        let extends = if self.check(&TokenKind::Extends) {
185            self.advance();
186            Some(self.consume_identifier("parent pipeline name")?)
187        } else {
188            None
189        };
190
191        self.consume(&TokenKind::LBrace, "{")?;
192        let body = self.parse_block()?;
193        self.consume(&TokenKind::RBrace, "}")?;
194
195        Ok(spanned(
196            Node::Pipeline {
197                name,
198                params,
199                body,
200                extends,
201            },
202            Span::merge(start, self.prev_span()),
203        ))
204    }
205
206    fn parse_import(&mut self) -> Result<SNode, ParserError> {
207        let start = self.current_span();
208        self.consume(&TokenKind::Import, "import")?;
209
210        // Check for selective import: import { foo, bar } from "module"
211        if self.check(&TokenKind::LBrace) {
212            self.advance(); // skip {
213            let mut names = Vec::new();
214            while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
215                let name = self.consume_identifier("import name")?;
216                names.push(name);
217                if self.check(&TokenKind::Comma) {
218                    self.advance();
219                }
220            }
221            self.consume(&TokenKind::RBrace, "}")?;
222            self.consume(&TokenKind::From, "from")?;
223            if let Some(tok) = self.current() {
224                if let TokenKind::StringLiteral(path) = &tok.kind {
225                    let path = path.clone();
226                    self.advance();
227                    return Ok(spanned(
228                        Node::SelectiveImport { names, path },
229                        Span::merge(start, self.prev_span()),
230                    ));
231                }
232            }
233            return Err(self.error("import path string"));
234        }
235
236        if let Some(tok) = self.current() {
237            if let TokenKind::StringLiteral(path) = &tok.kind {
238                let path = path.clone();
239                self.advance();
240                return Ok(spanned(
241                    Node::ImportDecl { path },
242                    Span::merge(start, self.prev_span()),
243                ));
244            }
245        }
246        Err(self.error("import path string"))
247    }
248
249    // --- Statements ---
250
251    fn parse_block(&mut self) -> Result<Vec<SNode>, ParserError> {
252        let mut stmts = Vec::new();
253        self.skip_newlines();
254
255        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
256            stmts.push(self.parse_statement()?);
257            self.skip_newlines();
258        }
259        Ok(stmts)
260    }
261
262    fn parse_statement(&mut self) -> Result<SNode, ParserError> {
263        self.skip_newlines();
264
265        let tok = self.current().ok_or_else(|| ParserError::UnexpectedEof {
266            expected: "statement".into(),
267        })?;
268
269        match &tok.kind {
270            TokenKind::Let => self.parse_let_binding(),
271            TokenKind::Var => self.parse_var_binding(),
272            TokenKind::If => self.parse_if_else(),
273            TokenKind::For => self.parse_for_in(),
274            TokenKind::Match => self.parse_match(),
275            TokenKind::Retry => self.parse_retry(),
276            TokenKind::While => self.parse_while_loop(),
277            TokenKind::Parallel => self.parse_parallel(),
278            TokenKind::ParallelMap => self.parse_parallel_map(),
279            TokenKind::ParallelSettle => self.parse_parallel_settle(),
280            TokenKind::Return => self.parse_return(),
281            TokenKind::Throw => self.parse_throw(),
282            TokenKind::Override => self.parse_override(),
283            TokenKind::Try => self.parse_try_catch(),
284            TokenKind::Select => self.parse_select(),
285            TokenKind::Fn => self.parse_fn_decl_with_pub(false),
286            TokenKind::Pub => {
287                self.advance(); // consume 'pub'
288                let tok = self.current().ok_or_else(|| ParserError::UnexpectedEof {
289                    expected: "fn, struct, enum, or pipeline after pub".into(),
290                })?;
291                match &tok.kind {
292                    TokenKind::Fn => self.parse_fn_decl_with_pub(true),
293                    _ => Err(self.error("fn, struct, enum, or pipeline after pub")),
294                }
295            }
296            TokenKind::TypeKw => self.parse_type_decl(),
297            TokenKind::Enum => self.parse_enum_decl(),
298            TokenKind::Struct => self.parse_struct_decl(),
299            TokenKind::Interface => self.parse_interface_decl(),
300            TokenKind::Impl => self.parse_impl_block(),
301            TokenKind::Guard => self.parse_guard(),
302            TokenKind::Deadline => self.parse_deadline(),
303            TokenKind::Yield => self.parse_yield(),
304            TokenKind::Mutex => self.parse_mutex(),
305            TokenKind::Break => {
306                let span = self.current_span();
307                self.advance();
308                Ok(spanned(Node::BreakStmt, span))
309            }
310            TokenKind::Continue => {
311                let span = self.current_span();
312                self.advance();
313                Ok(spanned(Node::ContinueStmt, span))
314            }
315            _ => self.parse_expression_statement(),
316        }
317    }
318
319    fn parse_let_binding(&mut self) -> Result<SNode, ParserError> {
320        let start = self.current_span();
321        self.consume(&TokenKind::Let, "let")?;
322        let pattern = self.parse_binding_pattern()?;
323        let type_ann = if matches!(pattern, BindingPattern::Identifier(_)) {
324            self.try_parse_type_annotation()?
325        } else {
326            None
327        };
328        self.consume(&TokenKind::Assign, "=")?;
329        let value = self.parse_expression()?;
330        Ok(spanned(
331            Node::LetBinding {
332                pattern,
333                type_ann,
334                value: Box::new(value),
335            },
336            Span::merge(start, self.prev_span()),
337        ))
338    }
339
340    fn parse_var_binding(&mut self) -> Result<SNode, ParserError> {
341        let start = self.current_span();
342        self.consume(&TokenKind::Var, "var")?;
343        let pattern = self.parse_binding_pattern()?;
344        let type_ann = if matches!(pattern, BindingPattern::Identifier(_)) {
345            self.try_parse_type_annotation()?
346        } else {
347            None
348        };
349        self.consume(&TokenKind::Assign, "=")?;
350        let value = self.parse_expression()?;
351        Ok(spanned(
352            Node::VarBinding {
353                pattern,
354                type_ann,
355                value: Box::new(value),
356            },
357            Span::merge(start, self.prev_span()),
358        ))
359    }
360
361    fn parse_if_else(&mut self) -> Result<SNode, ParserError> {
362        let start = self.current_span();
363        self.consume(&TokenKind::If, "if")?;
364        let condition = self.parse_expression()?;
365        self.consume(&TokenKind::LBrace, "{")?;
366        let then_body = self.parse_block()?;
367        self.consume(&TokenKind::RBrace, "}")?;
368        self.skip_newlines();
369
370        let else_body = if self.check(&TokenKind::Else) {
371            self.advance();
372            if self.check(&TokenKind::If) {
373                Some(vec![self.parse_if_else()?])
374            } else {
375                self.consume(&TokenKind::LBrace, "{")?;
376                let body = self.parse_block()?;
377                self.consume(&TokenKind::RBrace, "}")?;
378                Some(body)
379            }
380        } else {
381            None
382        };
383
384        Ok(spanned(
385            Node::IfElse {
386                condition: Box::new(condition),
387                then_body,
388                else_body,
389            },
390            Span::merge(start, self.prev_span()),
391        ))
392    }
393
394    fn parse_for_in(&mut self) -> Result<SNode, ParserError> {
395        let start = self.current_span();
396        self.consume(&TokenKind::For, "for")?;
397        let pattern = self.parse_binding_pattern()?;
398        self.consume(&TokenKind::In, "in")?;
399        let iterable = self.parse_expression()?;
400        self.consume(&TokenKind::LBrace, "{")?;
401        let body = self.parse_block()?;
402        self.consume(&TokenKind::RBrace, "}")?;
403        Ok(spanned(
404            Node::ForIn {
405                pattern,
406                iterable: Box::new(iterable),
407                body,
408            },
409            Span::merge(start, self.prev_span()),
410        ))
411    }
412
413    /// Parse a binding pattern for let/var/for-in:
414    ///   identifier | { fields } | [ elements ]
415    fn parse_binding_pattern(&mut self) -> Result<BindingPattern, ParserError> {
416        self.skip_newlines();
417        if self.check(&TokenKind::LBrace) {
418            // Dict destructuring: { key, key: alias, ...rest }
419            self.advance(); // consume {
420            let mut fields = Vec::new();
421            while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
422                // Check for rest pattern: ...ident
423                if self.check(&TokenKind::Dot) {
424                    // Consume three dots
425                    self.advance(); // .
426                    self.consume(&TokenKind::Dot, ".")?;
427                    self.consume(&TokenKind::Dot, ".")?;
428                    let name = self.consume_identifier("rest variable name")?;
429                    fields.push(DictPatternField {
430                        key: name,
431                        alias: None,
432                        is_rest: true,
433                    });
434                    // Rest must be last
435                    break;
436                }
437                let key = self.consume_identifier("field name")?;
438                let alias = if self.check(&TokenKind::Colon) {
439                    self.advance(); // consume :
440                    Some(self.consume_identifier("alias name")?)
441                } else {
442                    None
443                };
444                fields.push(DictPatternField {
445                    key,
446                    alias,
447                    is_rest: false,
448                });
449                if self.check(&TokenKind::Comma) {
450                    self.advance();
451                }
452            }
453            self.consume(&TokenKind::RBrace, "}")?;
454            Ok(BindingPattern::Dict(fields))
455        } else if self.check(&TokenKind::LBracket) {
456            // List destructuring: [ name, name, ...rest ]
457            self.advance(); // consume [
458            let mut elements = Vec::new();
459            while !self.is_at_end() && !self.check(&TokenKind::RBracket) {
460                // Check for rest pattern: ...ident
461                if self.check(&TokenKind::Dot) {
462                    self.advance(); // .
463                    self.consume(&TokenKind::Dot, ".")?;
464                    self.consume(&TokenKind::Dot, ".")?;
465                    let name = self.consume_identifier("rest variable name")?;
466                    elements.push(ListPatternElement {
467                        name,
468                        is_rest: true,
469                    });
470                    // Rest must be last
471                    break;
472                }
473                let name = self.consume_identifier("element name")?;
474                elements.push(ListPatternElement {
475                    name,
476                    is_rest: false,
477                });
478                if self.check(&TokenKind::Comma) {
479                    self.advance();
480                }
481            }
482            self.consume(&TokenKind::RBracket, "]")?;
483            Ok(BindingPattern::List(elements))
484        } else {
485            // Simple identifier
486            let name = self.consume_identifier("variable name or destructuring pattern")?;
487            Ok(BindingPattern::Identifier(name))
488        }
489    }
490
491    fn parse_match(&mut self) -> Result<SNode, ParserError> {
492        let start = self.current_span();
493        self.consume(&TokenKind::Match, "match")?;
494        let value = self.parse_expression()?;
495        self.consume(&TokenKind::LBrace, "{")?;
496        self.skip_newlines();
497
498        let mut arms = Vec::new();
499        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
500            let pattern = self.parse_expression()?;
501            self.consume(&TokenKind::Arrow, "->")?;
502            self.consume(&TokenKind::LBrace, "{")?;
503            let body = self.parse_block()?;
504            self.consume(&TokenKind::RBrace, "}")?;
505            arms.push(MatchArm { pattern, body });
506            self.skip_newlines();
507        }
508
509        self.consume(&TokenKind::RBrace, "}")?;
510        Ok(spanned(
511            Node::MatchExpr {
512                value: Box::new(value),
513                arms,
514            },
515            Span::merge(start, self.prev_span()),
516        ))
517    }
518
519    fn parse_while_loop(&mut self) -> Result<SNode, ParserError> {
520        let start = self.current_span();
521        self.consume(&TokenKind::While, "while")?;
522        let condition = if self.check(&TokenKind::LParen) {
523            self.advance();
524            let c = self.parse_expression()?;
525            self.consume(&TokenKind::RParen, ")")?;
526            c
527        } else {
528            self.parse_expression()?
529        };
530        self.consume(&TokenKind::LBrace, "{")?;
531        let body = self.parse_block()?;
532        self.consume(&TokenKind::RBrace, "}")?;
533        Ok(spanned(
534            Node::WhileLoop {
535                condition: Box::new(condition),
536                body,
537            },
538            Span::merge(start, self.prev_span()),
539        ))
540    }
541
542    fn parse_retry(&mut self) -> Result<SNode, ParserError> {
543        let start = self.current_span();
544        self.consume(&TokenKind::Retry, "retry")?;
545        let count = if self.check(&TokenKind::LParen) {
546            self.advance();
547            let c = self.parse_expression()?;
548            self.consume(&TokenKind::RParen, ")")?;
549            c
550        } else {
551            self.parse_primary()?
552        };
553        self.consume(&TokenKind::LBrace, "{")?;
554        let body = self.parse_block()?;
555        self.consume(&TokenKind::RBrace, "}")?;
556        Ok(spanned(
557            Node::Retry {
558                count: Box::new(count),
559                body,
560            },
561            Span::merge(start, self.prev_span()),
562        ))
563    }
564
565    fn parse_parallel(&mut self) -> Result<SNode, ParserError> {
566        let start = self.current_span();
567        self.consume(&TokenKind::Parallel, "parallel")?;
568        self.consume(&TokenKind::LParen, "(")?;
569        let count = self.parse_expression()?;
570        self.consume(&TokenKind::RParen, ")")?;
571        self.consume(&TokenKind::LBrace, "{")?;
572
573        // Optional closure parameter: { i ->
574        let mut variable = None;
575        self.skip_newlines();
576        if let Some(tok) = self.current() {
577            if let TokenKind::Identifier(name) = &tok.kind {
578                if self.peek_kind() == Some(&TokenKind::Arrow) {
579                    let name = name.clone();
580                    self.advance(); // skip identifier
581                    self.advance(); // skip ->
582                    variable = Some(name);
583                }
584            }
585        }
586
587        let body = self.parse_block()?;
588        self.consume(&TokenKind::RBrace, "}")?;
589        Ok(spanned(
590            Node::Parallel {
591                count: Box::new(count),
592                variable,
593                body,
594            },
595            Span::merge(start, self.prev_span()),
596        ))
597    }
598
599    fn parse_parallel_map(&mut self) -> Result<SNode, ParserError> {
600        let start = self.current_span();
601        self.consume(&TokenKind::ParallelMap, "parallel_map")?;
602        self.consume(&TokenKind::LParen, "(")?;
603        let list = self.parse_expression()?;
604        self.consume(&TokenKind::RParen, ")")?;
605        self.consume(&TokenKind::LBrace, "{")?;
606
607        self.skip_newlines();
608        let variable = self.consume_identifier("map variable")?;
609        self.consume(&TokenKind::Arrow, "->")?;
610
611        let body = self.parse_block()?;
612        self.consume(&TokenKind::RBrace, "}")?;
613        Ok(spanned(
614            Node::ParallelMap {
615                list: Box::new(list),
616                variable,
617                body,
618            },
619            Span::merge(start, self.prev_span()),
620        ))
621    }
622
623    fn parse_parallel_settle(&mut self) -> Result<SNode, ParserError> {
624        let start = self.current_span();
625        self.consume(&TokenKind::ParallelSettle, "parallel_settle")?;
626        self.consume(&TokenKind::LParen, "(")?;
627        let list = self.parse_expression()?;
628        self.consume(&TokenKind::RParen, ")")?;
629        self.consume(&TokenKind::LBrace, "{")?;
630
631        self.skip_newlines();
632        let variable = self.consume_identifier("settle variable")?;
633        self.consume(&TokenKind::Arrow, "->")?;
634
635        let body = self.parse_block()?;
636        self.consume(&TokenKind::RBrace, "}")?;
637        Ok(spanned(
638            Node::ParallelSettle {
639                list: Box::new(list),
640                variable,
641                body,
642            },
643            Span::merge(start, self.prev_span()),
644        ))
645    }
646
647    fn parse_return(&mut self) -> Result<SNode, ParserError> {
648        let start = self.current_span();
649        self.consume(&TokenKind::Return, "return")?;
650        if self.is_at_end() || self.check(&TokenKind::Newline) || self.check(&TokenKind::RBrace) {
651            return Ok(spanned(
652                Node::ReturnStmt { value: None },
653                Span::merge(start, self.prev_span()),
654            ));
655        }
656        let value = self.parse_expression()?;
657        Ok(spanned(
658            Node::ReturnStmt {
659                value: Some(Box::new(value)),
660            },
661            Span::merge(start, self.prev_span()),
662        ))
663    }
664
665    fn parse_throw(&mut self) -> Result<SNode, ParserError> {
666        let start = self.current_span();
667        self.consume(&TokenKind::Throw, "throw")?;
668        let value = self.parse_expression()?;
669        Ok(spanned(
670            Node::ThrowStmt {
671                value: Box::new(value),
672            },
673            Span::merge(start, self.prev_span()),
674        ))
675    }
676
677    fn parse_override(&mut self) -> Result<SNode, ParserError> {
678        let start = self.current_span();
679        self.consume(&TokenKind::Override, "override")?;
680        let name = self.consume_identifier("override name")?;
681        self.consume(&TokenKind::LParen, "(")?;
682        let params = self.parse_param_list()?;
683        self.consume(&TokenKind::RParen, ")")?;
684        self.consume(&TokenKind::LBrace, "{")?;
685        let body = self.parse_block()?;
686        self.consume(&TokenKind::RBrace, "}")?;
687        Ok(spanned(
688            Node::OverrideDecl { name, params, body },
689            Span::merge(start, self.prev_span()),
690        ))
691    }
692
693    fn parse_try_catch(&mut self) -> Result<SNode, ParserError> {
694        let start = self.current_span();
695        self.consume(&TokenKind::Try, "try")?;
696        self.consume(&TokenKind::LBrace, "{")?;
697        let body = self.parse_block()?;
698        self.consume(&TokenKind::RBrace, "}")?;
699        self.skip_newlines();
700
701        // Parse optional catch block
702        let has_catch = self.check(&TokenKind::Catch);
703        let (error_var, error_type, catch_body) = if has_catch {
704            self.advance();
705            let (ev, et) = if self.check(&TokenKind::LParen) {
706                // catch (e) { ... } or catch (e: Type) { ... }
707                self.advance();
708                let name = self.consume_identifier("error variable")?;
709                let ty = self.try_parse_type_annotation()?;
710                self.consume(&TokenKind::RParen, ")")?;
711                (Some(name), ty)
712            } else if matches!(
713                self.current().map(|t| &t.kind),
714                Some(TokenKind::Identifier(_))
715            ) {
716                // catch e { ... } (no parens)
717                let name = self.consume_identifier("error variable")?;
718                (Some(name), None)
719            } else {
720                (None, None)
721            };
722            self.consume(&TokenKind::LBrace, "{")?;
723            let cb = self.parse_block()?;
724            self.consume(&TokenKind::RBrace, "}")?;
725            (ev, et, cb)
726        } else {
727            (None, None, Vec::new())
728        };
729
730        self.skip_newlines();
731
732        // Parse optional finally block
733        let finally_body = if self.check(&TokenKind::Finally) {
734            self.advance();
735            self.consume(&TokenKind::LBrace, "{")?;
736            let fb = self.parse_block()?;
737            self.consume(&TokenKind::RBrace, "}")?;
738            Some(fb)
739        } else {
740            None
741        };
742
743        // If no catch or finally, this is a try-expression (returns Result)
744        if !has_catch && finally_body.is_none() {
745            return Ok(spanned(
746                Node::TryExpr { body },
747                Span::merge(start, self.prev_span()),
748            ));
749        }
750
751        Ok(spanned(
752            Node::TryCatch {
753                body,
754                error_var,
755                error_type,
756                catch_body,
757                finally_body,
758            },
759            Span::merge(start, self.prev_span()),
760        ))
761    }
762
763    fn parse_select(&mut self) -> Result<SNode, ParserError> {
764        let start = self.current_span();
765        self.consume(&TokenKind::Select, "select")?;
766        self.consume(&TokenKind::LBrace, "{")?;
767        self.skip_newlines();
768
769        let mut cases = Vec::new();
770        let mut timeout = None;
771        let mut default_body = None;
772
773        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
774            self.skip_newlines();
775            // Check for "timeout" (contextual keyword)
776            if let Some(tok) = self.current() {
777                if let TokenKind::Identifier(ref id) = tok.kind {
778                    if id == "timeout" {
779                        self.advance();
780                        let duration = self.parse_expression()?;
781                        self.consume(&TokenKind::LBrace, "{")?;
782                        let body = self.parse_block()?;
783                        self.consume(&TokenKind::RBrace, "}")?;
784                        timeout = Some((Box::new(duration), body));
785                        self.skip_newlines();
786                        continue;
787                    }
788                    if id == "default" {
789                        self.advance();
790                        self.consume(&TokenKind::LBrace, "{")?;
791                        let body = self.parse_block()?;
792                        self.consume(&TokenKind::RBrace, "}")?;
793                        default_body = Some(body);
794                        self.skip_newlines();
795                        continue;
796                    }
797                }
798            }
799            // Regular case: variable from channel { body }
800            let variable = self.consume_identifier("select case variable")?;
801            self.consume(&TokenKind::From, "from")?;
802            let channel = self.parse_expression()?;
803            self.consume(&TokenKind::LBrace, "{")?;
804            let body = self.parse_block()?;
805            self.consume(&TokenKind::RBrace, "}")?;
806            cases.push(SelectCase {
807                variable,
808                channel: Box::new(channel),
809                body,
810            });
811            self.skip_newlines();
812        }
813
814        self.consume(&TokenKind::RBrace, "}")?;
815
816        if cases.is_empty() && timeout.is_none() && default_body.is_none() {
817            return Err(self.error("at least one select case"));
818        }
819        if timeout.is_some() && default_body.is_some() {
820            return Err(self.error("select cannot have both timeout and default"));
821        }
822
823        Ok(spanned(
824            Node::SelectExpr {
825                cases,
826                timeout,
827                default_body,
828            },
829            Span::merge(start, self.prev_span()),
830        ))
831    }
832
833    fn parse_fn_decl_with_pub(&mut self, is_pub: bool) -> Result<SNode, ParserError> {
834        let start = self.current_span();
835        self.consume(&TokenKind::Fn, "fn")?;
836        let name = self.consume_identifier("function name")?;
837
838        // Optional generic type parameters: fn name<T, U>(...)
839        let type_params = if self.check(&TokenKind::Lt) {
840            self.advance(); // skip <
841            self.parse_type_param_list()?
842        } else {
843            Vec::new()
844        };
845
846        self.consume(&TokenKind::LParen, "(")?;
847        let params = self.parse_typed_param_list()?;
848        self.consume(&TokenKind::RParen, ")")?;
849        // Optional return type: -> type
850        let return_type = if self.check(&TokenKind::Arrow) {
851            self.advance();
852            Some(self.parse_type_expr()?)
853        } else {
854            None
855        };
856
857        // Optional where clause: where T: bound, U: bound
858        let where_clauses = self.parse_where_clauses()?;
859
860        self.consume(&TokenKind::LBrace, "{")?;
861        let body = self.parse_block()?;
862        self.consume(&TokenKind::RBrace, "}")?;
863        Ok(spanned(
864            Node::FnDecl {
865                name,
866                type_params,
867                params,
868                return_type,
869                where_clauses,
870                body,
871                is_pub,
872            },
873            Span::merge(start, self.prev_span()),
874        ))
875    }
876
877    fn parse_type_decl(&mut self) -> Result<SNode, ParserError> {
878        let start = self.current_span();
879        self.consume(&TokenKind::TypeKw, "type")?;
880        let name = self.consume_identifier("type name")?;
881        self.consume(&TokenKind::Assign, "=")?;
882        let type_expr = self.parse_type_expr()?;
883        Ok(spanned(
884            Node::TypeDecl { name, type_expr },
885            Span::merge(start, self.prev_span()),
886        ))
887    }
888
889    fn parse_enum_decl(&mut self) -> Result<SNode, ParserError> {
890        let start = self.current_span();
891        self.consume(&TokenKind::Enum, "enum")?;
892        let name = self.consume_identifier("enum name")?;
893        self.consume(&TokenKind::LBrace, "{")?;
894        self.skip_newlines();
895
896        let mut variants = Vec::new();
897        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
898            let variant_name = self.consume_identifier("variant name")?;
899            let fields = if self.check(&TokenKind::LParen) {
900                self.advance();
901                let params = self.parse_typed_param_list()?;
902                self.consume(&TokenKind::RParen, ")")?;
903                params
904            } else {
905                Vec::new()
906            };
907            variants.push(EnumVariant {
908                name: variant_name,
909                fields,
910            });
911            self.skip_newlines();
912            if self.check(&TokenKind::Comma) {
913                self.advance();
914                self.skip_newlines();
915            }
916        }
917
918        self.consume(&TokenKind::RBrace, "}")?;
919        Ok(spanned(
920            Node::EnumDecl { name, variants },
921            Span::merge(start, self.prev_span()),
922        ))
923    }
924
925    fn parse_struct_decl(&mut self) -> Result<SNode, ParserError> {
926        let start = self.current_span();
927        self.consume(&TokenKind::Struct, "struct")?;
928        let name = self.consume_identifier("struct name")?;
929        self.consume(&TokenKind::LBrace, "{")?;
930        self.skip_newlines();
931
932        let mut fields = Vec::new();
933        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
934            let field_name = self.consume_identifier("field name")?;
935            let optional = if self.check(&TokenKind::Question) {
936                self.advance();
937                true
938            } else {
939                false
940            };
941            let type_expr = self.try_parse_type_annotation()?;
942            fields.push(StructField {
943                name: field_name,
944                type_expr,
945                optional,
946            });
947            self.skip_newlines();
948            if self.check(&TokenKind::Comma) {
949                self.advance();
950                self.skip_newlines();
951            }
952        }
953
954        self.consume(&TokenKind::RBrace, "}")?;
955        Ok(spanned(
956            Node::StructDecl { name, fields },
957            Span::merge(start, self.prev_span()),
958        ))
959    }
960
961    fn parse_interface_decl(&mut self) -> Result<SNode, ParserError> {
962        let start = self.current_span();
963        self.consume(&TokenKind::Interface, "interface")?;
964        let name = self.consume_identifier("interface name")?;
965        self.consume(&TokenKind::LBrace, "{")?;
966        self.skip_newlines();
967
968        let mut methods = Vec::new();
969        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
970            self.consume(&TokenKind::Fn, "fn")?;
971            let method_name = self.consume_identifier("method name")?;
972            self.consume(&TokenKind::LParen, "(")?;
973            let params = self.parse_typed_param_list()?;
974            self.consume(&TokenKind::RParen, ")")?;
975            // Optional return type: -> type
976            let return_type = if self.check(&TokenKind::Arrow) {
977                self.advance();
978                Some(self.parse_type_expr()?)
979            } else {
980                None
981            };
982            methods.push(InterfaceMethod {
983                name: method_name,
984                params,
985                return_type,
986            });
987            self.skip_newlines();
988        }
989
990        self.consume(&TokenKind::RBrace, "}")?;
991        Ok(spanned(
992            Node::InterfaceDecl { name, methods },
993            Span::merge(start, self.prev_span()),
994        ))
995    }
996
997    fn parse_impl_block(&mut self) -> Result<SNode, ParserError> {
998        let start = self.current_span();
999        self.consume(&TokenKind::Impl, "impl")?;
1000        let type_name = self.consume_identifier("type name")?;
1001        self.consume(&TokenKind::LBrace, "{")?;
1002        self.skip_newlines();
1003
1004        let mut methods = Vec::new();
1005        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
1006            let is_pub = self.check(&TokenKind::Pub);
1007            if is_pub {
1008                self.advance();
1009            }
1010            let method = self.parse_fn_decl_with_pub(is_pub)?;
1011            methods.push(method);
1012            self.skip_newlines();
1013        }
1014
1015        self.consume(&TokenKind::RBrace, "}")?;
1016        Ok(spanned(
1017            Node::ImplBlock { type_name, methods },
1018            Span::merge(start, self.prev_span()),
1019        ))
1020    }
1021
1022    fn parse_guard(&mut self) -> Result<SNode, ParserError> {
1023        let start = self.current_span();
1024        self.consume(&TokenKind::Guard, "guard")?;
1025        let condition = self.parse_expression()?;
1026        // Consume "else" — we reuse the Else keyword
1027        self.consume(&TokenKind::Else, "else")?;
1028        self.consume(&TokenKind::LBrace, "{")?;
1029        let else_body = self.parse_block()?;
1030        self.consume(&TokenKind::RBrace, "}")?;
1031        Ok(spanned(
1032            Node::GuardStmt {
1033                condition: Box::new(condition),
1034                else_body,
1035            },
1036            Span::merge(start, self.prev_span()),
1037        ))
1038    }
1039
1040    fn parse_deadline(&mut self) -> Result<SNode, ParserError> {
1041        let start = self.current_span();
1042        self.consume(&TokenKind::Deadline, "deadline")?;
1043        let duration = self.parse_primary()?;
1044        self.consume(&TokenKind::LBrace, "{")?;
1045        let body = self.parse_block()?;
1046        self.consume(&TokenKind::RBrace, "}")?;
1047        Ok(spanned(
1048            Node::DeadlineBlock {
1049                duration: Box::new(duration),
1050                body,
1051            },
1052            Span::merge(start, self.prev_span()),
1053        ))
1054    }
1055
1056    fn parse_yield(&mut self) -> Result<SNode, ParserError> {
1057        let start = self.current_span();
1058        self.consume(&TokenKind::Yield, "yield")?;
1059        if self.is_at_end() || self.check(&TokenKind::Newline) || self.check(&TokenKind::RBrace) {
1060            return Ok(spanned(
1061                Node::YieldExpr { value: None },
1062                Span::merge(start, self.prev_span()),
1063            ));
1064        }
1065        let value = self.parse_expression()?;
1066        Ok(spanned(
1067            Node::YieldExpr {
1068                value: Some(Box::new(value)),
1069            },
1070            Span::merge(start, self.prev_span()),
1071        ))
1072    }
1073
1074    fn parse_mutex(&mut self) -> Result<SNode, ParserError> {
1075        let start = self.current_span();
1076        self.consume(&TokenKind::Mutex, "mutex")?;
1077        self.consume(&TokenKind::LBrace, "{")?;
1078        let body = self.parse_block()?;
1079        self.consume(&TokenKind::RBrace, "}")?;
1080        Ok(spanned(
1081            Node::MutexBlock { body },
1082            Span::merge(start, self.prev_span()),
1083        ))
1084    }
1085
1086    fn parse_ask_expr(&mut self) -> Result<SNode, ParserError> {
1087        let start = self.current_span();
1088        self.consume(&TokenKind::Ask, "ask")?;
1089        self.consume(&TokenKind::LBrace, "{")?;
1090        // Parse as dict entries
1091        let mut entries = Vec::new();
1092        self.skip_newlines();
1093        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
1094            let key_span = self.current_span();
1095            let name = self.consume_identifier("ask field")?;
1096            let key = spanned(Node::StringLiteral(name), key_span);
1097            self.consume(&TokenKind::Colon, ":")?;
1098            let value = self.parse_expression()?;
1099            entries.push(DictEntry { key, value });
1100            self.skip_newlines();
1101            if self.check(&TokenKind::Comma) {
1102                self.advance();
1103                self.skip_newlines();
1104            }
1105        }
1106        self.consume(&TokenKind::RBrace, "}")?;
1107        Ok(spanned(
1108            Node::AskExpr { fields: entries },
1109            Span::merge(start, self.prev_span()),
1110        ))
1111    }
1112
1113    // --- Expressions (precedence climbing) ---
1114
1115    fn parse_expression_statement(&mut self) -> Result<SNode, ParserError> {
1116        let start = self.current_span();
1117        let expr = self.parse_expression()?;
1118
1119        // Check for assignment or compound assignment on valid targets:
1120        // identifier, property access (obj.field), subscript access (obj[key])
1121        let is_assignable = matches!(
1122            expr.node,
1123            Node::Identifier(_) | Node::PropertyAccess { .. } | Node::SubscriptAccess { .. }
1124        );
1125        if is_assignable {
1126            if self.check(&TokenKind::Assign) {
1127                self.advance();
1128                let value = self.parse_expression()?;
1129                return Ok(spanned(
1130                    Node::Assignment {
1131                        target: Box::new(expr),
1132                        value: Box::new(value),
1133                        op: None,
1134                    },
1135                    Span::merge(start, self.prev_span()),
1136                ));
1137            }
1138            let compound_op = if self.check(&TokenKind::PlusAssign) {
1139                Some("+")
1140            } else if self.check(&TokenKind::MinusAssign) {
1141                Some("-")
1142            } else if self.check(&TokenKind::StarAssign) {
1143                Some("*")
1144            } else if self.check(&TokenKind::SlashAssign) {
1145                Some("/")
1146            } else if self.check(&TokenKind::PercentAssign) {
1147                Some("%")
1148            } else {
1149                None
1150            };
1151            if let Some(op) = compound_op {
1152                self.advance();
1153                let value = self.parse_expression()?;
1154                return Ok(spanned(
1155                    Node::Assignment {
1156                        target: Box::new(expr),
1157                        value: Box::new(value),
1158                        op: Some(op.into()),
1159                    },
1160                    Span::merge(start, self.prev_span()),
1161                ));
1162            }
1163        }
1164
1165        Ok(expr)
1166    }
1167
1168    fn parse_expression(&mut self) -> Result<SNode, ParserError> {
1169        self.skip_newlines();
1170        self.parse_pipe()
1171    }
1172
1173    fn parse_pipe(&mut self) -> Result<SNode, ParserError> {
1174        let mut left = self.parse_range()?;
1175        while self.check_skip_newlines(&TokenKind::Pipe) {
1176            let start = left.span;
1177            self.advance();
1178            let right = self.parse_range()?;
1179            left = spanned(
1180                Node::BinaryOp {
1181                    op: "|>".into(),
1182                    left: Box::new(left),
1183                    right: Box::new(right),
1184                },
1185                Span::merge(start, self.prev_span()),
1186            );
1187        }
1188        Ok(left)
1189    }
1190
1191    fn parse_range(&mut self) -> Result<SNode, ParserError> {
1192        let left = self.parse_ternary()?;
1193        if self.check(&TokenKind::Thru) {
1194            let start = left.span;
1195            self.advance();
1196            let right = self.parse_ternary()?;
1197            return Ok(spanned(
1198                Node::RangeExpr {
1199                    start: Box::new(left),
1200                    end: Box::new(right),
1201                    inclusive: true,
1202                },
1203                Span::merge(start, self.prev_span()),
1204            ));
1205        }
1206        if self.check(&TokenKind::Upto) {
1207            let start = left.span;
1208            self.advance();
1209            let right = self.parse_ternary()?;
1210            return Ok(spanned(
1211                Node::RangeExpr {
1212                    start: Box::new(left),
1213                    end: Box::new(right),
1214                    inclusive: false,
1215                },
1216                Span::merge(start, self.prev_span()),
1217            ));
1218        }
1219        Ok(left)
1220    }
1221
1222    fn parse_ternary(&mut self) -> Result<SNode, ParserError> {
1223        let condition = self.parse_nil_coalescing()?;
1224        if !self.check(&TokenKind::Question) {
1225            return Ok(condition);
1226        }
1227        let start = condition.span;
1228        self.advance(); // skip ?
1229        let true_val = self.parse_nil_coalescing()?;
1230        self.consume(&TokenKind::Colon, ":")?;
1231        let false_val = self.parse_nil_coalescing()?;
1232        Ok(spanned(
1233            Node::Ternary {
1234                condition: Box::new(condition),
1235                true_expr: Box::new(true_val),
1236                false_expr: Box::new(false_val),
1237            },
1238            Span::merge(start, self.prev_span()),
1239        ))
1240    }
1241
1242    fn parse_nil_coalescing(&mut self) -> Result<SNode, ParserError> {
1243        let mut left = self.parse_logical_or()?;
1244        while self.check(&TokenKind::NilCoal) {
1245            let start = left.span;
1246            self.advance();
1247            let right = self.parse_logical_or()?;
1248            left = spanned(
1249                Node::BinaryOp {
1250                    op: "??".into(),
1251                    left: Box::new(left),
1252                    right: Box::new(right),
1253                },
1254                Span::merge(start, self.prev_span()),
1255            );
1256        }
1257        Ok(left)
1258    }
1259
1260    fn parse_logical_or(&mut self) -> Result<SNode, ParserError> {
1261        let mut left = self.parse_logical_and()?;
1262        while self.check_skip_newlines(&TokenKind::Or) {
1263            let start = left.span;
1264            self.advance();
1265            let right = self.parse_logical_and()?;
1266            left = spanned(
1267                Node::BinaryOp {
1268                    op: "||".into(),
1269                    left: Box::new(left),
1270                    right: Box::new(right),
1271                },
1272                Span::merge(start, self.prev_span()),
1273            );
1274        }
1275        Ok(left)
1276    }
1277
1278    fn parse_logical_and(&mut self) -> Result<SNode, ParserError> {
1279        let mut left = self.parse_equality()?;
1280        while self.check_skip_newlines(&TokenKind::And) {
1281            let start = left.span;
1282            self.advance();
1283            let right = self.parse_equality()?;
1284            left = spanned(
1285                Node::BinaryOp {
1286                    op: "&&".into(),
1287                    left: Box::new(left),
1288                    right: Box::new(right),
1289                },
1290                Span::merge(start, self.prev_span()),
1291            );
1292        }
1293        Ok(left)
1294    }
1295
1296    fn parse_equality(&mut self) -> Result<SNode, ParserError> {
1297        let mut left = self.parse_comparison()?;
1298        while self.check(&TokenKind::Eq) || self.check(&TokenKind::Neq) {
1299            let start = left.span;
1300            let op = if self.check(&TokenKind::Eq) {
1301                "=="
1302            } else {
1303                "!="
1304            };
1305            self.advance();
1306            let right = self.parse_comparison()?;
1307            left = spanned(
1308                Node::BinaryOp {
1309                    op: op.into(),
1310                    left: Box::new(left),
1311                    right: Box::new(right),
1312                },
1313                Span::merge(start, self.prev_span()),
1314            );
1315        }
1316        Ok(left)
1317    }
1318
1319    fn parse_comparison(&mut self) -> Result<SNode, ParserError> {
1320        let mut left = self.parse_additive()?;
1321        loop {
1322            if self.check(&TokenKind::Lt)
1323                || self.check(&TokenKind::Gt)
1324                || self.check(&TokenKind::Lte)
1325                || self.check(&TokenKind::Gte)
1326            {
1327                let start = left.span;
1328                let op = match self.current().map(|t| &t.kind) {
1329                    Some(TokenKind::Lt) => "<",
1330                    Some(TokenKind::Gt) => ">",
1331                    Some(TokenKind::Lte) => "<=",
1332                    Some(TokenKind::Gte) => ">=",
1333                    _ => "<",
1334                };
1335                self.advance();
1336                let right = self.parse_additive()?;
1337                left = spanned(
1338                    Node::BinaryOp {
1339                        op: op.into(),
1340                        left: Box::new(left),
1341                        right: Box::new(right),
1342                    },
1343                    Span::merge(start, self.prev_span()),
1344                );
1345            } else if self.check(&TokenKind::In) {
1346                let start = left.span;
1347                self.advance();
1348                let right = self.parse_additive()?;
1349                left = spanned(
1350                    Node::BinaryOp {
1351                        op: "in".into(),
1352                        left: Box::new(left),
1353                        right: Box::new(right),
1354                    },
1355                    Span::merge(start, self.prev_span()),
1356                );
1357            } else if self.check_identifier("not") {
1358                // Look ahead for "not in"
1359                let saved = self.pos;
1360                self.advance(); // consume "not"
1361                if self.check(&TokenKind::In) {
1362                    let start = left.span;
1363                    self.advance(); // consume "in"
1364                    let right = self.parse_additive()?;
1365                    left = spanned(
1366                        Node::BinaryOp {
1367                            op: "not_in".into(),
1368                            left: Box::new(left),
1369                            right: Box::new(right),
1370                        },
1371                        Span::merge(start, self.prev_span()),
1372                    );
1373                } else {
1374                    self.pos = saved;
1375                    break;
1376                }
1377            } else {
1378                break;
1379            }
1380        }
1381        Ok(left)
1382    }
1383
1384    fn parse_additive(&mut self) -> Result<SNode, ParserError> {
1385        let mut left = self.parse_multiplicative()?;
1386        while self.check_skip_newlines(&TokenKind::Plus) || self.check(&TokenKind::Minus) {
1387            let start = left.span;
1388            let op = if self.check(&TokenKind::Plus) {
1389                "+"
1390            } else {
1391                "-"
1392            };
1393            self.advance();
1394            let right = self.parse_multiplicative()?;
1395            left = spanned(
1396                Node::BinaryOp {
1397                    op: op.into(),
1398                    left: Box::new(left),
1399                    right: Box::new(right),
1400                },
1401                Span::merge(start, self.prev_span()),
1402            );
1403        }
1404        Ok(left)
1405    }
1406
1407    fn parse_multiplicative(&mut self) -> Result<SNode, ParserError> {
1408        let mut left = self.parse_unary()?;
1409        while self.check_skip_newlines(&TokenKind::Star)
1410            || self.check_skip_newlines(&TokenKind::Slash)
1411            || self.check_skip_newlines(&TokenKind::Percent)
1412        {
1413            let start = left.span;
1414            let op = if self.check(&TokenKind::Star) {
1415                "*"
1416            } else if self.check(&TokenKind::Slash) {
1417                "/"
1418            } else {
1419                "%"
1420            };
1421            self.advance();
1422            let right = self.parse_unary()?;
1423            left = spanned(
1424                Node::BinaryOp {
1425                    op: op.into(),
1426                    left: Box::new(left),
1427                    right: Box::new(right),
1428                },
1429                Span::merge(start, self.prev_span()),
1430            );
1431        }
1432        Ok(left)
1433    }
1434
1435    fn parse_unary(&mut self) -> Result<SNode, ParserError> {
1436        if self.check(&TokenKind::Not) {
1437            let start = self.current_span();
1438            self.advance();
1439            let operand = self.parse_unary()?;
1440            return Ok(spanned(
1441                Node::UnaryOp {
1442                    op: "!".into(),
1443                    operand: Box::new(operand),
1444                },
1445                Span::merge(start, self.prev_span()),
1446            ));
1447        }
1448        if self.check(&TokenKind::Minus) {
1449            let start = self.current_span();
1450            self.advance();
1451            let operand = self.parse_unary()?;
1452            return Ok(spanned(
1453                Node::UnaryOp {
1454                    op: "-".into(),
1455                    operand: Box::new(operand),
1456                },
1457                Span::merge(start, self.prev_span()),
1458            ));
1459        }
1460        self.parse_postfix()
1461    }
1462
1463    fn parse_postfix(&mut self) -> Result<SNode, ParserError> {
1464        let mut expr = self.parse_primary()?;
1465
1466        loop {
1467            if self.check_skip_newlines(&TokenKind::Dot)
1468                || self.check_skip_newlines(&TokenKind::QuestionDot)
1469            {
1470                let optional = self.check(&TokenKind::QuestionDot);
1471                let start = expr.span;
1472                self.advance();
1473                let member = self.consume_identifier_or_keyword("member name")?;
1474                if self.check(&TokenKind::LParen) {
1475                    self.advance();
1476                    let args = self.parse_arg_list()?;
1477                    self.consume(&TokenKind::RParen, ")")?;
1478                    if optional {
1479                        expr = spanned(
1480                            Node::OptionalMethodCall {
1481                                object: Box::new(expr),
1482                                method: member,
1483                                args,
1484                            },
1485                            Span::merge(start, self.prev_span()),
1486                        );
1487                    } else {
1488                        expr = spanned(
1489                            Node::MethodCall {
1490                                object: Box::new(expr),
1491                                method: member,
1492                                args,
1493                            },
1494                            Span::merge(start, self.prev_span()),
1495                        );
1496                    }
1497                } else if optional {
1498                    expr = spanned(
1499                        Node::OptionalPropertyAccess {
1500                            object: Box::new(expr),
1501                            property: member,
1502                        },
1503                        Span::merge(start, self.prev_span()),
1504                    );
1505                } else {
1506                    expr = spanned(
1507                        Node::PropertyAccess {
1508                            object: Box::new(expr),
1509                            property: member,
1510                        },
1511                        Span::merge(start, self.prev_span()),
1512                    );
1513                }
1514            } else if self.check(&TokenKind::LBracket) {
1515                let start = expr.span;
1516                self.advance();
1517
1518                // Check for slice vs subscript:
1519                // [:end] — slice with no start
1520                // [start:end] or [start:] — slice with start
1521                // [index] — normal subscript
1522                if self.check(&TokenKind::Colon) {
1523                    // [:end] or [:]
1524                    self.advance(); // consume ':'
1525                    let end_expr = if self.check(&TokenKind::RBracket) {
1526                        None
1527                    } else {
1528                        Some(Box::new(self.parse_expression()?))
1529                    };
1530                    self.consume(&TokenKind::RBracket, "]")?;
1531                    expr = spanned(
1532                        Node::SliceAccess {
1533                            object: Box::new(expr),
1534                            start: None,
1535                            end: end_expr,
1536                        },
1537                        Span::merge(start, self.prev_span()),
1538                    );
1539                } else {
1540                    let index = self.parse_expression()?;
1541                    if self.check(&TokenKind::Colon) {
1542                        // [start:end] or [start:]
1543                        self.advance(); // consume ':'
1544                        let end_expr = if self.check(&TokenKind::RBracket) {
1545                            None
1546                        } else {
1547                            Some(Box::new(self.parse_expression()?))
1548                        };
1549                        self.consume(&TokenKind::RBracket, "]")?;
1550                        expr = spanned(
1551                            Node::SliceAccess {
1552                                object: Box::new(expr),
1553                                start: Some(Box::new(index)),
1554                                end: end_expr,
1555                            },
1556                            Span::merge(start, self.prev_span()),
1557                        );
1558                    } else {
1559                        self.consume(&TokenKind::RBracket, "]")?;
1560                        expr = spanned(
1561                            Node::SubscriptAccess {
1562                                object: Box::new(expr),
1563                                index: Box::new(index),
1564                            },
1565                            Span::merge(start, self.prev_span()),
1566                        );
1567                    }
1568                }
1569            } else if self.check(&TokenKind::LParen) && matches!(expr.node, Node::Identifier(_)) {
1570                let start = expr.span;
1571                self.advance();
1572                let args = self.parse_arg_list()?;
1573                self.consume(&TokenKind::RParen, ")")?;
1574                if let Node::Identifier(name) = expr.node {
1575                    expr = spanned(
1576                        Node::FunctionCall { name, args },
1577                        Span::merge(start, self.prev_span()),
1578                    );
1579                }
1580            } else if self.check(&TokenKind::Question) {
1581                // Distinguish postfix try operator (expr?) from ternary (expr ? a : b).
1582                // If the token after ? could start a ternary branch, leave it for parse_ternary.
1583                let next_pos = self.pos + 1;
1584                let is_ternary = self.tokens.get(next_pos).is_some_and(|t| {
1585                    matches!(
1586                        t.kind,
1587                        TokenKind::Identifier(_)
1588                            | TokenKind::IntLiteral(_)
1589                            | TokenKind::FloatLiteral(_)
1590                            | TokenKind::StringLiteral(_)
1591                            | TokenKind::InterpolatedString(_)
1592                            | TokenKind::True
1593                            | TokenKind::False
1594                            | TokenKind::Nil
1595                            | TokenKind::LParen
1596                            | TokenKind::LBracket
1597                            | TokenKind::LBrace
1598                            | TokenKind::Not
1599                            | TokenKind::Minus
1600                            | TokenKind::Fn
1601                    )
1602                });
1603                if is_ternary {
1604                    break;
1605                }
1606                let start = expr.span;
1607                self.advance(); // consume ?
1608                expr = spanned(
1609                    Node::TryOperator {
1610                        operand: Box::new(expr),
1611                    },
1612                    Span::merge(start, self.prev_span()),
1613                );
1614            } else {
1615                break;
1616            }
1617        }
1618
1619        Ok(expr)
1620    }
1621
1622    fn parse_primary(&mut self) -> Result<SNode, ParserError> {
1623        let tok = self.current().ok_or_else(|| ParserError::UnexpectedEof {
1624            expected: "expression".into(),
1625        })?;
1626        let start = self.current_span();
1627
1628        match &tok.kind {
1629            TokenKind::StringLiteral(s) => {
1630                let s = s.clone();
1631                self.advance();
1632                Ok(spanned(
1633                    Node::StringLiteral(s),
1634                    Span::merge(start, self.prev_span()),
1635                ))
1636            }
1637            TokenKind::InterpolatedString(segments) => {
1638                let segments = segments.clone();
1639                self.advance();
1640                Ok(spanned(
1641                    Node::InterpolatedString(segments),
1642                    Span::merge(start, self.prev_span()),
1643                ))
1644            }
1645            TokenKind::IntLiteral(n) => {
1646                let n = *n;
1647                self.advance();
1648                Ok(spanned(
1649                    Node::IntLiteral(n),
1650                    Span::merge(start, self.prev_span()),
1651                ))
1652            }
1653            TokenKind::FloatLiteral(n) => {
1654                let n = *n;
1655                self.advance();
1656                Ok(spanned(
1657                    Node::FloatLiteral(n),
1658                    Span::merge(start, self.prev_span()),
1659                ))
1660            }
1661            TokenKind::True => {
1662                self.advance();
1663                Ok(spanned(
1664                    Node::BoolLiteral(true),
1665                    Span::merge(start, self.prev_span()),
1666                ))
1667            }
1668            TokenKind::False => {
1669                self.advance();
1670                Ok(spanned(
1671                    Node::BoolLiteral(false),
1672                    Span::merge(start, self.prev_span()),
1673                ))
1674            }
1675            TokenKind::Nil => {
1676                self.advance();
1677                Ok(spanned(
1678                    Node::NilLiteral,
1679                    Span::merge(start, self.prev_span()),
1680                ))
1681            }
1682            TokenKind::Identifier(name) => {
1683                let name = name.clone();
1684                self.advance();
1685                Ok(spanned(
1686                    Node::Identifier(name),
1687                    Span::merge(start, self.prev_span()),
1688                ))
1689            }
1690            TokenKind::LParen => {
1691                self.advance();
1692                let expr = self.parse_expression()?;
1693                self.consume(&TokenKind::RParen, ")")?;
1694                Ok(expr)
1695            }
1696            TokenKind::LBracket => self.parse_list_literal(),
1697            TokenKind::LBrace => self.parse_dict_or_closure(),
1698            TokenKind::Parallel => self.parse_parallel(),
1699            TokenKind::ParallelMap => self.parse_parallel_map(),
1700            TokenKind::ParallelSettle => self.parse_parallel_settle(),
1701            TokenKind::Retry => self.parse_retry(),
1702            TokenKind::If => self.parse_if_else(),
1703            TokenKind::Spawn => self.parse_spawn_expr(),
1704            TokenKind::DurationLiteral(ms) => {
1705                let ms = *ms;
1706                self.advance();
1707                Ok(spanned(
1708                    Node::DurationLiteral(ms),
1709                    Span::merge(start, self.prev_span()),
1710                ))
1711            }
1712            TokenKind::Ask => self.parse_ask_expr(),
1713            TokenKind::Deadline => self.parse_deadline(),
1714            TokenKind::Try => self.parse_try_catch(),
1715            TokenKind::Fn => self.parse_fn_expr(),
1716            _ => Err(self.error("expression")),
1717        }
1718    }
1719
1720    /// Parse an anonymous function expression: `fn(params) { body }`
1721    /// Produces a Closure node with `fn_syntax: true` so the formatter
1722    /// can round-trip the original syntax.
1723    fn parse_fn_expr(&mut self) -> Result<SNode, ParserError> {
1724        let start = self.current_span();
1725        self.consume(&TokenKind::Fn, "fn")?;
1726        self.consume(&TokenKind::LParen, "(")?;
1727        let params = self.parse_typed_param_list()?;
1728        self.consume(&TokenKind::RParen, ")")?;
1729        self.consume(&TokenKind::LBrace, "{")?;
1730        let body = self.parse_block()?;
1731        self.consume(&TokenKind::RBrace, "}")?;
1732        Ok(spanned(
1733            Node::Closure {
1734                params,
1735                body,
1736                fn_syntax: true,
1737            },
1738            Span::merge(start, self.prev_span()),
1739        ))
1740    }
1741
1742    fn parse_spawn_expr(&mut self) -> Result<SNode, ParserError> {
1743        let start = self.current_span();
1744        self.consume(&TokenKind::Spawn, "spawn")?;
1745        self.consume(&TokenKind::LBrace, "{")?;
1746        let body = self.parse_block()?;
1747        self.consume(&TokenKind::RBrace, "}")?;
1748        Ok(spanned(
1749            Node::SpawnExpr { body },
1750            Span::merge(start, self.prev_span()),
1751        ))
1752    }
1753
1754    fn parse_list_literal(&mut self) -> Result<SNode, ParserError> {
1755        let start = self.current_span();
1756        self.consume(&TokenKind::LBracket, "[")?;
1757        let mut elements = Vec::new();
1758        self.skip_newlines();
1759
1760        while !self.is_at_end() && !self.check(&TokenKind::RBracket) {
1761            // Check for spread: ...expr
1762            if self.check(&TokenKind::Dot) {
1763                let saved_pos = self.pos;
1764                self.advance(); // first .
1765                if self.check(&TokenKind::Dot) {
1766                    self.advance(); // second .
1767                    self.consume(&TokenKind::Dot, ".")?; // third .
1768                    let spread_start = self.tokens[saved_pos].span;
1769                    let expr = self.parse_expression()?;
1770                    elements.push(spanned(
1771                        Node::Spread(Box::new(expr)),
1772                        Span::merge(spread_start, self.prev_span()),
1773                    ));
1774                } else {
1775                    // Not a spread, restore and parse as expression
1776                    self.pos = saved_pos;
1777                    elements.push(self.parse_expression()?);
1778                }
1779            } else {
1780                elements.push(self.parse_expression()?);
1781            }
1782            self.skip_newlines();
1783            if self.check(&TokenKind::Comma) {
1784                self.advance();
1785                self.skip_newlines();
1786            }
1787        }
1788
1789        self.consume(&TokenKind::RBracket, "]")?;
1790        Ok(spanned(
1791            Node::ListLiteral(elements),
1792            Span::merge(start, self.prev_span()),
1793        ))
1794    }
1795
1796    fn parse_dict_or_closure(&mut self) -> Result<SNode, ParserError> {
1797        let start = self.current_span();
1798        self.consume(&TokenKind::LBrace, "{")?;
1799        self.skip_newlines();
1800
1801        // Empty dict
1802        if self.check(&TokenKind::RBrace) {
1803            self.advance();
1804            return Ok(spanned(
1805                Node::DictLiteral(Vec::new()),
1806                Span::merge(start, self.prev_span()),
1807            ));
1808        }
1809
1810        // Lookahead: scan for -> before } to disambiguate closure from dict.
1811        let saved = self.pos;
1812        if self.is_closure_lookahead() {
1813            self.pos = saved;
1814            return self.parse_closure_body(start);
1815        }
1816        self.pos = saved;
1817        self.parse_dict_literal(start)
1818    }
1819
1820    /// Scan forward to determine if this is a closure (has -> before matching }).
1821    /// Does not consume tokens (caller saves/restores pos).
1822    fn is_closure_lookahead(&mut self) -> bool {
1823        let mut depth = 0;
1824        while !self.is_at_end() {
1825            if let Some(tok) = self.current() {
1826                match &tok.kind {
1827                    TokenKind::Arrow if depth == 0 => return true,
1828                    TokenKind::LBrace | TokenKind::LParen | TokenKind::LBracket => depth += 1,
1829                    TokenKind::RBrace if depth == 0 => return false,
1830                    TokenKind::RBrace => depth -= 1,
1831                    TokenKind::RParen | TokenKind::RBracket => {
1832                        if depth > 0 {
1833                            depth -= 1;
1834                        }
1835                    }
1836                    _ => {}
1837                }
1838                self.advance();
1839            } else {
1840                return false;
1841            }
1842        }
1843        false
1844    }
1845
1846    /// Parse closure params and body (after opening { has been consumed).
1847    fn parse_closure_body(&mut self, start: Span) -> Result<SNode, ParserError> {
1848        let params = self.parse_typed_param_list_until_arrow()?;
1849        self.consume(&TokenKind::Arrow, "->")?;
1850        let body = self.parse_block()?;
1851        self.consume(&TokenKind::RBrace, "}")?;
1852        Ok(spanned(
1853            Node::Closure {
1854                params,
1855                body,
1856                fn_syntax: false,
1857            },
1858            Span::merge(start, self.prev_span()),
1859        ))
1860    }
1861
1862    /// Parse typed params until we see ->. Handles: `x`, `x: int`, `x, y`, `x: int, y: string`.
1863    fn parse_typed_param_list_until_arrow(&mut self) -> Result<Vec<TypedParam>, ParserError> {
1864        self.parse_typed_params_until(|tok| tok == &TokenKind::Arrow)
1865    }
1866
1867    fn parse_dict_literal(&mut self, start: Span) -> Result<SNode, ParserError> {
1868        let mut entries = Vec::new();
1869        self.skip_newlines();
1870
1871        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
1872            // Check for spread: ...expr
1873            if self.check(&TokenKind::Dot) {
1874                let saved_pos = self.pos;
1875                self.advance(); // first .
1876                if self.check(&TokenKind::Dot) {
1877                    self.advance(); // second .
1878                    if self.check(&TokenKind::Dot) {
1879                        self.advance(); // third .
1880                        let spread_start = self.tokens[saved_pos].span;
1881                        let expr = self.parse_expression()?;
1882                        entries.push(DictEntry {
1883                            key: spanned(Node::NilLiteral, spread_start),
1884                            value: spanned(
1885                                Node::Spread(Box::new(expr)),
1886                                Span::merge(spread_start, self.prev_span()),
1887                            ),
1888                        });
1889                        self.skip_newlines();
1890                        if self.check(&TokenKind::Comma) {
1891                            self.advance();
1892                            self.skip_newlines();
1893                        }
1894                        continue;
1895                    }
1896                    // Not three dots — restore
1897                    self.pos = saved_pos;
1898                } else {
1899                    self.pos = saved_pos;
1900                }
1901            }
1902            let key = if self.check(&TokenKind::LBracket) {
1903                // Computed key: [expression]
1904                self.advance();
1905                let k = self.parse_expression()?;
1906                self.consume(&TokenKind::RBracket, "]")?;
1907                k
1908            } else if matches!(
1909                self.current().map(|t| &t.kind),
1910                Some(TokenKind::StringLiteral(_))
1911            ) {
1912                // Quoted string key: {"key": value}
1913                let key_span = self.current_span();
1914                let name =
1915                    if let Some(TokenKind::StringLiteral(s)) = self.current().map(|t| &t.kind) {
1916                        s.clone()
1917                    } else {
1918                        unreachable!()
1919                    };
1920                self.advance();
1921                spanned(Node::StringLiteral(name), key_span)
1922            } else {
1923                // Static key: identifier or keyword -> string literal
1924                let key_span = self.current_span();
1925                let name = self.consume_identifier_or_keyword("dict key")?;
1926                spanned(Node::StringLiteral(name), key_span)
1927            };
1928            self.consume(&TokenKind::Colon, ":")?;
1929            let value = self.parse_expression()?;
1930            entries.push(DictEntry { key, value });
1931            self.skip_newlines();
1932            if self.check(&TokenKind::Comma) {
1933                self.advance();
1934                self.skip_newlines();
1935            }
1936        }
1937
1938        self.consume(&TokenKind::RBrace, "}")?;
1939        Ok(spanned(
1940            Node::DictLiteral(entries),
1941            Span::merge(start, self.prev_span()),
1942        ))
1943    }
1944
1945    // --- Helpers ---
1946
1947    /// Parse untyped parameter list (for pipelines, overrides).
1948    fn parse_param_list(&mut self) -> Result<Vec<String>, ParserError> {
1949        let mut params = Vec::new();
1950        self.skip_newlines();
1951
1952        while !self.is_at_end() && !self.check(&TokenKind::RParen) {
1953            params.push(self.consume_identifier("parameter name")?);
1954            if self.check(&TokenKind::Comma) {
1955                self.advance();
1956                self.skip_newlines();
1957            }
1958        }
1959        Ok(params)
1960    }
1961
1962    /// Parse typed parameter list (for fn declarations).
1963    fn parse_typed_param_list(&mut self) -> Result<Vec<TypedParam>, ParserError> {
1964        self.parse_typed_params_until(|tok| tok == &TokenKind::RParen)
1965    }
1966
1967    /// Shared implementation: parse typed params with optional defaults until
1968    /// a terminator token is reached.
1969    fn parse_typed_params_until(
1970        &mut self,
1971        is_terminator: impl Fn(&TokenKind) -> bool,
1972    ) -> Result<Vec<TypedParam>, ParserError> {
1973        let mut params = Vec::new();
1974        let mut seen_default = false;
1975        self.skip_newlines();
1976
1977        while !self.is_at_end() {
1978            if let Some(tok) = self.current() {
1979                if is_terminator(&tok.kind) {
1980                    break;
1981                }
1982            } else {
1983                break;
1984            }
1985            let name = self.consume_identifier("parameter name")?;
1986            let type_expr = self.try_parse_type_annotation()?;
1987            let default_value = if self.check(&TokenKind::Assign) {
1988                self.advance();
1989                seen_default = true;
1990                Some(Box::new(self.parse_expression()?))
1991            } else {
1992                if seen_default {
1993                    return Err(self.error(
1994                        "Required parameter cannot follow a parameter with a default value",
1995                    ));
1996                }
1997                None
1998            };
1999            params.push(TypedParam {
2000                name,
2001                type_expr,
2002                default_value,
2003            });
2004            if self.check(&TokenKind::Comma) {
2005                self.advance();
2006                self.skip_newlines();
2007            }
2008        }
2009        Ok(params)
2010    }
2011
2012    /// Parse a comma-separated list of type parameter names until `>`.
2013    fn parse_type_param_list(&mut self) -> Result<Vec<TypeParam>, ParserError> {
2014        let mut params = Vec::new();
2015        self.skip_newlines();
2016        while !self.is_at_end() && !self.check(&TokenKind::Gt) {
2017            let name = self.consume_identifier("type parameter name")?;
2018            params.push(TypeParam { name });
2019            if self.check(&TokenKind::Comma) {
2020                self.advance();
2021                self.skip_newlines();
2022            }
2023        }
2024        self.consume(&TokenKind::Gt, ">")?;
2025        Ok(params)
2026    }
2027
2028    /// Parse an optional `where T: bound, U: bound` clause.
2029    /// Looks for an identifier "where" before `{`.
2030    fn parse_where_clauses(&mut self) -> Result<Vec<WhereClause>, ParserError> {
2031        // Check if the next identifier is "where"
2032        if let Some(tok) = self.current() {
2033            if let TokenKind::Identifier(ref id) = tok.kind {
2034                if id == "where" {
2035                    self.advance(); // skip "where"
2036                    let mut clauses = Vec::new();
2037                    loop {
2038                        self.skip_newlines();
2039                        // Stop if we hit `{` or EOF
2040                        if self.check(&TokenKind::LBrace) || self.is_at_end() {
2041                            break;
2042                        }
2043                        let type_name = self.consume_identifier("type parameter name")?;
2044                        self.consume(&TokenKind::Colon, ":")?;
2045                        let bound = self.consume_identifier("type bound")?;
2046                        clauses.push(WhereClause { type_name, bound });
2047                        if self.check(&TokenKind::Comma) {
2048                            self.advance();
2049                        } else {
2050                            break;
2051                        }
2052                    }
2053                    return Ok(clauses);
2054                }
2055            }
2056        }
2057        Ok(Vec::new())
2058    }
2059
2060    /// Try to parse an optional type annotation (`: type`).
2061    /// Returns None if no colon follows.
2062    fn try_parse_type_annotation(&mut self) -> Result<Option<TypeExpr>, ParserError> {
2063        if !self.check(&TokenKind::Colon) {
2064            return Ok(None);
2065        }
2066        self.advance(); // skip :
2067        Ok(Some(self.parse_type_expr()?))
2068    }
2069
2070    /// Parse a type expression: `int`, `string | nil`, `{name: string, age?: int}`.
2071    fn parse_type_expr(&mut self) -> Result<TypeExpr, ParserError> {
2072        self.skip_newlines();
2073        let first = self.parse_type_primary()?;
2074
2075        // Check for union: type | type | ...
2076        if self.check(&TokenKind::Bar) {
2077            let mut types = vec![first];
2078            while self.check(&TokenKind::Bar) {
2079                self.advance(); // skip |
2080                types.push(self.parse_type_primary()?);
2081            }
2082            return Ok(TypeExpr::Union(types));
2083        }
2084
2085        Ok(first)
2086    }
2087
2088    /// Parse a primary type: named type or shape type.
2089    /// Accepts identifiers and certain keywords (nil, bool, etc.) as type names.
2090    fn parse_type_primary(&mut self) -> Result<TypeExpr, ParserError> {
2091        self.skip_newlines();
2092        if self.check(&TokenKind::LBrace) {
2093            return self.parse_shape_type();
2094        }
2095        // Accept keyword type names: nil, true, false map to their type names
2096        if let Some(tok) = self.current() {
2097            let type_name = match &tok.kind {
2098                TokenKind::Nil => {
2099                    self.advance();
2100                    return Ok(TypeExpr::Named("nil".to_string()));
2101                }
2102                TokenKind::True | TokenKind::False => {
2103                    self.advance();
2104                    return Ok(TypeExpr::Named("bool".to_string()));
2105                }
2106                _ => None,
2107            };
2108            if let Some(name) = type_name {
2109                return Ok(TypeExpr::Named(name));
2110            }
2111        }
2112        // Function type: fn(T, U) -> R
2113        if self.check(&TokenKind::Fn) {
2114            self.advance(); // skip `fn`
2115            self.consume(&TokenKind::LParen, "(")?;
2116            let mut params = Vec::new();
2117            self.skip_newlines();
2118            while !self.is_at_end() && !self.check(&TokenKind::RParen) {
2119                params.push(self.parse_type_expr()?);
2120                self.skip_newlines();
2121                if self.check(&TokenKind::Comma) {
2122                    self.advance();
2123                    self.skip_newlines();
2124                }
2125            }
2126            self.consume(&TokenKind::RParen, ")")?;
2127            self.consume(&TokenKind::Arrow, "->")?;
2128            let return_type = self.parse_type_expr()?;
2129            return Ok(TypeExpr::FnType {
2130                params,
2131                return_type: Box::new(return_type),
2132            });
2133        }
2134        let name = self.consume_identifier("type name")?;
2135        // Check for generic type parameters: list<int>, dict<string, int>
2136        if self.check(&TokenKind::Lt) {
2137            self.advance(); // skip <
2138            let first_param = self.parse_type_expr()?;
2139            if name == "list" {
2140                self.consume(&TokenKind::Gt, ">")?;
2141                return Ok(TypeExpr::List(Box::new(first_param)));
2142            } else if name == "dict" {
2143                self.consume(&TokenKind::Comma, ",")?;
2144                let second_param = self.parse_type_expr()?;
2145                self.consume(&TokenKind::Gt, ">")?;
2146                return Ok(TypeExpr::DictType(
2147                    Box::new(first_param),
2148                    Box::new(second_param),
2149                ));
2150            }
2151            // Unknown generic — just consume > and treat as Named
2152            self.consume(&TokenKind::Gt, ">")?;
2153        }
2154        Ok(TypeExpr::Named(name))
2155    }
2156
2157    /// Parse a shape type: `{ name: string, age: int, active?: bool }`.
2158    fn parse_shape_type(&mut self) -> Result<TypeExpr, ParserError> {
2159        self.consume(&TokenKind::LBrace, "{")?;
2160        let mut fields = Vec::new();
2161        self.skip_newlines();
2162
2163        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
2164            let name = self.consume_identifier("field name")?;
2165            let optional = if self.check(&TokenKind::Question) {
2166                self.advance();
2167                true
2168            } else {
2169                false
2170            };
2171            self.consume(&TokenKind::Colon, ":")?;
2172            let type_expr = self.parse_type_expr()?;
2173            fields.push(ShapeField {
2174                name,
2175                type_expr,
2176                optional,
2177            });
2178            self.skip_newlines();
2179            if self.check(&TokenKind::Comma) {
2180                self.advance();
2181                self.skip_newlines();
2182            }
2183        }
2184
2185        self.consume(&TokenKind::RBrace, "}")?;
2186        Ok(TypeExpr::Shape(fields))
2187    }
2188
2189    fn parse_arg_list(&mut self) -> Result<Vec<SNode>, ParserError> {
2190        let mut args = Vec::new();
2191        self.skip_newlines();
2192
2193        while !self.is_at_end() && !self.check(&TokenKind::RParen) {
2194            // Check for spread: ...expr
2195            if self.check(&TokenKind::Dot) {
2196                let saved_pos = self.pos;
2197                self.advance(); // first .
2198                if self.check(&TokenKind::Dot) {
2199                    self.advance(); // second .
2200                    self.consume(&TokenKind::Dot, ".")?; // third .
2201                    let spread_start = self.tokens[saved_pos].span;
2202                    let expr = self.parse_expression()?;
2203                    args.push(spanned(
2204                        Node::Spread(Box::new(expr)),
2205                        Span::merge(spread_start, self.prev_span()),
2206                    ));
2207                } else {
2208                    // Not a spread, restore and parse as expression
2209                    self.pos = saved_pos;
2210                    args.push(self.parse_expression()?);
2211                }
2212            } else {
2213                args.push(self.parse_expression()?);
2214            }
2215            self.skip_newlines();
2216            if self.check(&TokenKind::Comma) {
2217                self.advance();
2218                self.skip_newlines();
2219            }
2220        }
2221        Ok(args)
2222    }
2223
2224    fn is_at_end(&self) -> bool {
2225        self.pos >= self.tokens.len()
2226            || matches!(self.tokens.get(self.pos), Some(t) if t.kind == TokenKind::Eof)
2227    }
2228
2229    fn current(&self) -> Option<&Token> {
2230        self.tokens.get(self.pos)
2231    }
2232
2233    fn peek_kind(&self) -> Option<&TokenKind> {
2234        self.tokens.get(self.pos + 1).map(|t| &t.kind)
2235    }
2236
2237    fn check(&self, kind: &TokenKind) -> bool {
2238        self.current()
2239            .map(|t| std::mem::discriminant(&t.kind) == std::mem::discriminant(kind))
2240            .unwrap_or(false)
2241    }
2242
2243    /// Check for a token kind, skipping past any newlines first.
2244    /// Used for binary operators like `||` and `&&` that can span lines.
2245    fn check_skip_newlines(&mut self, kind: &TokenKind) -> bool {
2246        let saved = self.pos;
2247        self.skip_newlines();
2248        if self.check(kind) {
2249            true
2250        } else {
2251            self.pos = saved;
2252            false
2253        }
2254    }
2255
2256    /// Check if current token is an identifier with the given name (without consuming it).
2257    fn check_identifier(&self, name: &str) -> bool {
2258        matches!(self.current().map(|t| &t.kind), Some(TokenKind::Identifier(s)) if s == name)
2259    }
2260
2261    fn advance(&mut self) {
2262        if self.pos < self.tokens.len() {
2263            self.pos += 1;
2264        }
2265    }
2266
2267    fn consume(&mut self, kind: &TokenKind, expected: &str) -> Result<Token, ParserError> {
2268        self.skip_newlines();
2269        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
2270        if std::mem::discriminant(&tok.kind) != std::mem::discriminant(kind) {
2271            return Err(self.make_error(expected));
2272        }
2273        let tok = tok.clone();
2274        self.advance();
2275        Ok(tok)
2276    }
2277
2278    fn consume_identifier(&mut self, expected: &str) -> Result<String, ParserError> {
2279        self.skip_newlines();
2280        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
2281        if let TokenKind::Identifier(name) = &tok.kind {
2282            let name = name.clone();
2283            self.advance();
2284            Ok(name)
2285        } else {
2286            Err(self.make_error(expected))
2287        }
2288    }
2289
2290    /// Like `consume_identifier`, but also accepts keywords as identifiers.
2291    /// Used for property access (e.g., `obj.type`) and dict keys where
2292    /// keywords are valid member names.
2293    fn consume_identifier_or_keyword(&mut self, expected: &str) -> Result<String, ParserError> {
2294        self.skip_newlines();
2295        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
2296        if let TokenKind::Identifier(name) = &tok.kind {
2297            let name = name.clone();
2298            self.advance();
2299            return Ok(name);
2300        }
2301        // Accept any keyword token as an identifier
2302        let name = match &tok.kind {
2303            TokenKind::Pipeline => "pipeline",
2304            TokenKind::Extends => "extends",
2305            TokenKind::Override => "override",
2306            TokenKind::Let => "let",
2307            TokenKind::Var => "var",
2308            TokenKind::If => "if",
2309            TokenKind::Else => "else",
2310            TokenKind::For => "for",
2311            TokenKind::In => "in",
2312            TokenKind::Match => "match",
2313            TokenKind::Retry => "retry",
2314            TokenKind::Parallel => "parallel",
2315            TokenKind::ParallelMap => "parallel_map",
2316            TokenKind::ParallelSettle => "parallel_settle",
2317            TokenKind::Return => "return",
2318            TokenKind::Import => "import",
2319            TokenKind::True => "true",
2320            TokenKind::False => "false",
2321            TokenKind::Nil => "nil",
2322            TokenKind::Try => "try",
2323            TokenKind::Catch => "catch",
2324            TokenKind::Throw => "throw",
2325            TokenKind::Fn => "fn",
2326            TokenKind::Spawn => "spawn",
2327            TokenKind::While => "while",
2328            TokenKind::TypeKw => "type",
2329            TokenKind::Enum => "enum",
2330            TokenKind::Struct => "struct",
2331            TokenKind::Interface => "interface",
2332            TokenKind::Pub => "pub",
2333            TokenKind::From => "from",
2334            TokenKind::Thru => "thru",
2335            TokenKind::Upto => "upto",
2336            TokenKind::Guard => "guard",
2337            TokenKind::Ask => "ask",
2338            TokenKind::Deadline => "deadline",
2339            TokenKind::Yield => "yield",
2340            TokenKind::Mutex => "mutex",
2341            TokenKind::Break => "break",
2342            TokenKind::Continue => "continue",
2343            TokenKind::Impl => "impl",
2344            _ => return Err(self.make_error(expected)),
2345        };
2346        let name = name.to_string();
2347        self.advance();
2348        Ok(name)
2349    }
2350
2351    fn skip_newlines(&mut self) {
2352        while self.pos < self.tokens.len() && self.tokens[self.pos].kind == TokenKind::Newline {
2353            self.pos += 1;
2354        }
2355    }
2356
2357    fn make_error(&self, expected: &str) -> ParserError {
2358        if let Some(tok) = self.tokens.get(self.pos) {
2359            if tok.kind == TokenKind::Eof {
2360                return ParserError::UnexpectedEof {
2361                    expected: expected.into(),
2362                };
2363            }
2364            ParserError::Unexpected {
2365                got: tok.kind.to_string(),
2366                expected: expected.into(),
2367                span: tok.span,
2368            }
2369        } else {
2370            ParserError::UnexpectedEof {
2371                expected: expected.into(),
2372            }
2373        }
2374    }
2375
2376    fn error(&self, expected: &str) -> ParserError {
2377        self.make_error(expected)
2378    }
2379}