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