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