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 call sites in downstream
1373    // pipeline libraries.
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::RawStringLiteral(s) => {
1771                let s = s.clone();
1772                self.advance();
1773                Ok(spanned(
1774                    Node::RawStringLiteral(s),
1775                    Span::merge(start, self.prev_span()),
1776                ))
1777            }
1778            TokenKind::InterpolatedString(segments) => {
1779                let segments = segments.clone();
1780                self.advance();
1781                Ok(spanned(
1782                    Node::InterpolatedString(segments),
1783                    Span::merge(start, self.prev_span()),
1784                ))
1785            }
1786            TokenKind::IntLiteral(n) => {
1787                let n = *n;
1788                self.advance();
1789                Ok(spanned(
1790                    Node::IntLiteral(n),
1791                    Span::merge(start, self.prev_span()),
1792                ))
1793            }
1794            TokenKind::FloatLiteral(n) => {
1795                let n = *n;
1796                self.advance();
1797                Ok(spanned(
1798                    Node::FloatLiteral(n),
1799                    Span::merge(start, self.prev_span()),
1800                ))
1801            }
1802            TokenKind::True => {
1803                self.advance();
1804                Ok(spanned(
1805                    Node::BoolLiteral(true),
1806                    Span::merge(start, self.prev_span()),
1807                ))
1808            }
1809            TokenKind::False => {
1810                self.advance();
1811                Ok(spanned(
1812                    Node::BoolLiteral(false),
1813                    Span::merge(start, self.prev_span()),
1814                ))
1815            }
1816            TokenKind::Nil => {
1817                self.advance();
1818                Ok(spanned(
1819                    Node::NilLiteral,
1820                    Span::merge(start, self.prev_span()),
1821                ))
1822            }
1823            TokenKind::Identifier(name) => {
1824                let name = name.clone();
1825                self.advance();
1826                Ok(spanned(
1827                    Node::Identifier(name),
1828                    Span::merge(start, self.prev_span()),
1829                ))
1830            }
1831            TokenKind::LParen => {
1832                self.advance();
1833                let expr = self.parse_expression()?;
1834                self.consume(&TokenKind::RParen, ")")?;
1835                Ok(expr)
1836            }
1837            TokenKind::LBracket => self.parse_list_literal(),
1838            TokenKind::LBrace => self.parse_dict_or_closure(),
1839            TokenKind::Parallel => self.parse_parallel(),
1840            TokenKind::ParallelMap => self.parse_parallel_map(),
1841            TokenKind::ParallelSettle => self.parse_parallel_settle(),
1842            TokenKind::Retry => self.parse_retry(),
1843            TokenKind::If => self.parse_if_else(),
1844            TokenKind::Spawn => self.parse_spawn_expr(),
1845            TokenKind::DurationLiteral(ms) => {
1846                let ms = *ms;
1847                self.advance();
1848                Ok(spanned(
1849                    Node::DurationLiteral(ms),
1850                    Span::merge(start, self.prev_span()),
1851                ))
1852            }
1853            TokenKind::Ask => self.parse_ask_expr(),
1854            TokenKind::Deadline => self.parse_deadline(),
1855            TokenKind::Try => self.parse_try_catch(),
1856            TokenKind::Match => self.parse_match(),
1857            TokenKind::Fn => self.parse_fn_expr(),
1858            _ => Err(self.error("expression")),
1859        }
1860    }
1861
1862    /// Parse an anonymous function expression: `fn(params) { body }`
1863    /// Produces a Closure node with `fn_syntax: true` so the formatter
1864    /// can round-trip the original syntax.
1865    fn parse_fn_expr(&mut self) -> Result<SNode, ParserError> {
1866        let start = self.current_span();
1867        self.consume(&TokenKind::Fn, "fn")?;
1868        self.consume(&TokenKind::LParen, "(")?;
1869        let params = self.parse_typed_param_list()?;
1870        self.consume(&TokenKind::RParen, ")")?;
1871        self.consume(&TokenKind::LBrace, "{")?;
1872        let body = self.parse_block()?;
1873        self.consume(&TokenKind::RBrace, "}")?;
1874        Ok(spanned(
1875            Node::Closure {
1876                params,
1877                body,
1878                fn_syntax: true,
1879            },
1880            Span::merge(start, self.prev_span()),
1881        ))
1882    }
1883
1884    fn parse_spawn_expr(&mut self) -> Result<SNode, ParserError> {
1885        let start = self.current_span();
1886        self.consume(&TokenKind::Spawn, "spawn")?;
1887        self.consume(&TokenKind::LBrace, "{")?;
1888        let body = self.parse_block()?;
1889        self.consume(&TokenKind::RBrace, "}")?;
1890        Ok(spanned(
1891            Node::SpawnExpr { body },
1892            Span::merge(start, self.prev_span()),
1893        ))
1894    }
1895
1896    fn parse_list_literal(&mut self) -> Result<SNode, ParserError> {
1897        let start = self.current_span();
1898        self.consume(&TokenKind::LBracket, "[")?;
1899        let mut elements = Vec::new();
1900        self.skip_newlines();
1901
1902        while !self.is_at_end() && !self.check(&TokenKind::RBracket) {
1903            // Check for spread: ...expr
1904            if self.check(&TokenKind::Dot) {
1905                let saved_pos = self.pos;
1906                self.advance(); // first .
1907                if self.check(&TokenKind::Dot) {
1908                    self.advance(); // second .
1909                    self.consume(&TokenKind::Dot, ".")?; // third .
1910                    let spread_start = self.tokens[saved_pos].span;
1911                    let expr = self.parse_expression()?;
1912                    elements.push(spanned(
1913                        Node::Spread(Box::new(expr)),
1914                        Span::merge(spread_start, self.prev_span()),
1915                    ));
1916                } else {
1917                    // Not a spread, restore and parse as expression
1918                    self.pos = saved_pos;
1919                    elements.push(self.parse_expression()?);
1920                }
1921            } else {
1922                elements.push(self.parse_expression()?);
1923            }
1924            self.skip_newlines();
1925            if self.check(&TokenKind::Comma) {
1926                self.advance();
1927                self.skip_newlines();
1928            }
1929        }
1930
1931        self.consume(&TokenKind::RBracket, "]")?;
1932        Ok(spanned(
1933            Node::ListLiteral(elements),
1934            Span::merge(start, self.prev_span()),
1935        ))
1936    }
1937
1938    fn parse_dict_or_closure(&mut self) -> Result<SNode, ParserError> {
1939        let start = self.current_span();
1940        self.consume(&TokenKind::LBrace, "{")?;
1941        self.skip_newlines();
1942
1943        // Empty dict
1944        if self.check(&TokenKind::RBrace) {
1945            self.advance();
1946            return Ok(spanned(
1947                Node::DictLiteral(Vec::new()),
1948                Span::merge(start, self.prev_span()),
1949            ));
1950        }
1951
1952        // Lookahead: scan for -> before } to disambiguate closure from dict.
1953        let saved = self.pos;
1954        if self.is_closure_lookahead() {
1955            self.pos = saved;
1956            return self.parse_closure_body(start);
1957        }
1958        self.pos = saved;
1959        self.parse_dict_literal(start)
1960    }
1961
1962    /// Scan forward to determine if this is a closure (has -> before matching }).
1963    /// Does not consume tokens (caller saves/restores pos).
1964    fn is_closure_lookahead(&mut self) -> bool {
1965        let mut depth = 0;
1966        while !self.is_at_end() {
1967            if let Some(tok) = self.current() {
1968                match &tok.kind {
1969                    TokenKind::Arrow if depth == 0 => return true,
1970                    TokenKind::LBrace | TokenKind::LParen | TokenKind::LBracket => depth += 1,
1971                    TokenKind::RBrace if depth == 0 => return false,
1972                    TokenKind::RBrace => depth -= 1,
1973                    TokenKind::RParen | TokenKind::RBracket => {
1974                        if depth > 0 {
1975                            depth -= 1;
1976                        }
1977                    }
1978                    _ => {}
1979                }
1980                self.advance();
1981            } else {
1982                return false;
1983            }
1984        }
1985        false
1986    }
1987
1988    /// Parse closure params and body (after opening { has been consumed).
1989    fn parse_closure_body(&mut self, start: Span) -> Result<SNode, ParserError> {
1990        let params = self.parse_typed_param_list_until_arrow()?;
1991        self.consume(&TokenKind::Arrow, "->")?;
1992        let body = self.parse_block()?;
1993        self.consume(&TokenKind::RBrace, "}")?;
1994        Ok(spanned(
1995            Node::Closure {
1996                params,
1997                body,
1998                fn_syntax: false,
1999            },
2000            Span::merge(start, self.prev_span()),
2001        ))
2002    }
2003
2004    /// Parse typed params until we see ->. Handles: `x`, `x: int`, `x, y`, `x: int, y: string`.
2005    fn parse_typed_param_list_until_arrow(&mut self) -> Result<Vec<TypedParam>, ParserError> {
2006        self.parse_typed_params_until(|tok| tok == &TokenKind::Arrow)
2007    }
2008
2009    fn parse_dict_literal(&mut self, start: Span) -> Result<SNode, ParserError> {
2010        let mut entries = Vec::new();
2011        self.skip_newlines();
2012
2013        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
2014            // Check for spread: ...expr
2015            if self.check(&TokenKind::Dot) {
2016                let saved_pos = self.pos;
2017                self.advance(); // first .
2018                if self.check(&TokenKind::Dot) {
2019                    self.advance(); // second .
2020                    if self.check(&TokenKind::Dot) {
2021                        self.advance(); // third .
2022                        let spread_start = self.tokens[saved_pos].span;
2023                        let expr = self.parse_expression()?;
2024                        entries.push(DictEntry {
2025                            key: spanned(Node::NilLiteral, spread_start),
2026                            value: spanned(
2027                                Node::Spread(Box::new(expr)),
2028                                Span::merge(spread_start, self.prev_span()),
2029                            ),
2030                        });
2031                        self.skip_newlines();
2032                        if self.check(&TokenKind::Comma) {
2033                            self.advance();
2034                            self.skip_newlines();
2035                        }
2036                        continue;
2037                    }
2038                    // Not three dots — restore
2039                    self.pos = saved_pos;
2040                } else {
2041                    self.pos = saved_pos;
2042                }
2043            }
2044            let key = if self.check(&TokenKind::LBracket) {
2045                // Computed key: [expression]
2046                self.advance();
2047                let k = self.parse_expression()?;
2048                self.consume(&TokenKind::RBracket, "]")?;
2049                k
2050            } else if matches!(
2051                self.current().map(|t| &t.kind),
2052                Some(TokenKind::StringLiteral(_))
2053            ) {
2054                // Quoted string key: {"key": value}
2055                let key_span = self.current_span();
2056                let name =
2057                    if let Some(TokenKind::StringLiteral(s)) = self.current().map(|t| &t.kind) {
2058                        s.clone()
2059                    } else {
2060                        unreachable!()
2061                    };
2062                self.advance();
2063                spanned(Node::StringLiteral(name), key_span)
2064            } else {
2065                // Static key: identifier or keyword -> string literal
2066                let key_span = self.current_span();
2067                let name = self.consume_identifier_or_keyword("dict key")?;
2068                spanned(Node::StringLiteral(name), key_span)
2069            };
2070            self.consume(&TokenKind::Colon, ":")?;
2071            let value = self.parse_expression()?;
2072            entries.push(DictEntry { key, value });
2073            self.skip_newlines();
2074            if self.check(&TokenKind::Comma) {
2075                self.advance();
2076                self.skip_newlines();
2077            }
2078        }
2079
2080        self.consume(&TokenKind::RBrace, "}")?;
2081        Ok(spanned(
2082            Node::DictLiteral(entries),
2083            Span::merge(start, self.prev_span()),
2084        ))
2085    }
2086
2087    // --- Helpers ---
2088
2089    /// Parse untyped parameter list (for pipelines, overrides).
2090    fn parse_param_list(&mut self) -> Result<Vec<String>, ParserError> {
2091        let mut params = Vec::new();
2092        self.skip_newlines();
2093
2094        while !self.is_at_end() && !self.check(&TokenKind::RParen) {
2095            params.push(self.consume_identifier("parameter name")?);
2096            if self.check(&TokenKind::Comma) {
2097                self.advance();
2098                self.skip_newlines();
2099            }
2100        }
2101        Ok(params)
2102    }
2103
2104    /// Parse typed parameter list (for fn declarations).
2105    fn parse_typed_param_list(&mut self) -> Result<Vec<TypedParam>, ParserError> {
2106        self.parse_typed_params_until(|tok| tok == &TokenKind::RParen)
2107    }
2108
2109    /// Shared implementation: parse typed params with optional defaults until
2110    /// a terminator token is reached.
2111    fn parse_typed_params_until(
2112        &mut self,
2113        is_terminator: impl Fn(&TokenKind) -> bool,
2114    ) -> Result<Vec<TypedParam>, ParserError> {
2115        let mut params = Vec::new();
2116        let mut seen_default = false;
2117        self.skip_newlines();
2118
2119        while !self.is_at_end() {
2120            if let Some(tok) = self.current() {
2121                if is_terminator(&tok.kind) {
2122                    break;
2123                }
2124            } else {
2125                break;
2126            }
2127            // Check for rest parameter: ...name
2128            let is_rest = if self.check(&TokenKind::Dot) {
2129                // Peek ahead for two more dots
2130                let p1 = self.pos + 1;
2131                let p2 = self.pos + 2;
2132                let is_ellipsis = p1 < self.tokens.len()
2133                    && p2 < self.tokens.len()
2134                    && self.tokens[p1].kind == TokenKind::Dot
2135                    && self.tokens[p2].kind == TokenKind::Dot;
2136                if is_ellipsis {
2137                    self.advance(); // skip .
2138                    self.advance(); // skip .
2139                    self.advance(); // skip .
2140                    true
2141                } else {
2142                    false
2143                }
2144            } else {
2145                false
2146            };
2147            let name = self.consume_identifier("parameter name")?;
2148            let type_expr = self.try_parse_type_annotation()?;
2149            let default_value = if self.check(&TokenKind::Assign) {
2150                self.advance();
2151                seen_default = true;
2152                Some(Box::new(self.parse_expression()?))
2153            } else {
2154                if seen_default && !is_rest {
2155                    return Err(self.error(
2156                        "Required parameter cannot follow a parameter with a default value",
2157                    ));
2158                }
2159                None
2160            };
2161            if is_rest
2162                && !is_terminator(
2163                    &self
2164                        .current()
2165                        .map(|t| t.kind.clone())
2166                        .unwrap_or(TokenKind::Eof),
2167                )
2168            {
2169                return Err(self.error("Rest parameter must be the last parameter"));
2170            }
2171            params.push(TypedParam {
2172                name,
2173                type_expr,
2174                default_value,
2175                rest: is_rest,
2176            });
2177            if self.check(&TokenKind::Comma) {
2178                self.advance();
2179                self.skip_newlines();
2180            }
2181        }
2182        Ok(params)
2183    }
2184
2185    /// Parse a comma-separated list of type parameter names until `>`.
2186    fn parse_type_param_list(&mut self) -> Result<Vec<TypeParam>, ParserError> {
2187        let mut params = Vec::new();
2188        self.skip_newlines();
2189        while !self.is_at_end() && !self.check(&TokenKind::Gt) {
2190            let name = self.consume_identifier("type parameter name")?;
2191            params.push(TypeParam { name });
2192            if self.check(&TokenKind::Comma) {
2193                self.advance();
2194                self.skip_newlines();
2195            }
2196        }
2197        self.consume(&TokenKind::Gt, ">")?;
2198        Ok(params)
2199    }
2200
2201    /// Parse an optional `where T: bound, U: bound` clause.
2202    /// Looks for an identifier "where" before `{`.
2203    fn parse_where_clauses(&mut self) -> Result<Vec<WhereClause>, ParserError> {
2204        // Check if the next identifier is "where"
2205        if let Some(tok) = self.current() {
2206            if let TokenKind::Identifier(ref id) = tok.kind {
2207                if id == "where" {
2208                    self.advance(); // skip "where"
2209                    let mut clauses = Vec::new();
2210                    loop {
2211                        self.skip_newlines();
2212                        // Stop if we hit `{` or EOF
2213                        if self.check(&TokenKind::LBrace) || self.is_at_end() {
2214                            break;
2215                        }
2216                        let type_name = self.consume_identifier("type parameter name")?;
2217                        self.consume(&TokenKind::Colon, ":")?;
2218                        let bound = self.consume_identifier("type bound")?;
2219                        clauses.push(WhereClause { type_name, bound });
2220                        if self.check(&TokenKind::Comma) {
2221                            self.advance();
2222                        } else {
2223                            break;
2224                        }
2225                    }
2226                    return Ok(clauses);
2227                }
2228            }
2229        }
2230        Ok(Vec::new())
2231    }
2232
2233    /// Try to parse an optional type annotation (`: type`).
2234    /// Returns None if no colon follows.
2235    fn try_parse_type_annotation(&mut self) -> Result<Option<TypeExpr>, ParserError> {
2236        if !self.check(&TokenKind::Colon) {
2237            return Ok(None);
2238        }
2239        self.advance(); // skip :
2240        Ok(Some(self.parse_type_expr()?))
2241    }
2242
2243    /// Parse a type expression: `int`, `string | nil`, `{name: string, age?: int}`.
2244    fn parse_type_expr(&mut self) -> Result<TypeExpr, ParserError> {
2245        self.skip_newlines();
2246        let first = self.parse_type_primary()?;
2247
2248        // Check for union: type | type | ...
2249        if self.check(&TokenKind::Bar) {
2250            let mut types = vec![first];
2251            while self.check(&TokenKind::Bar) {
2252                self.advance(); // skip |
2253                types.push(self.parse_type_primary()?);
2254            }
2255            return Ok(TypeExpr::Union(types));
2256        }
2257
2258        Ok(first)
2259    }
2260
2261    /// Parse a primary type: named type or shape type.
2262    /// Accepts identifiers and certain keywords (nil, bool, etc.) as type names.
2263    fn parse_type_primary(&mut self) -> Result<TypeExpr, ParserError> {
2264        self.skip_newlines();
2265        if self.check(&TokenKind::LBrace) {
2266            return self.parse_shape_type();
2267        }
2268        // Accept keyword type names: nil, true, false map to their type names
2269        if let Some(tok) = self.current() {
2270            let type_name = match &tok.kind {
2271                TokenKind::Nil => {
2272                    self.advance();
2273                    return Ok(TypeExpr::Named("nil".to_string()));
2274                }
2275                TokenKind::True | TokenKind::False => {
2276                    self.advance();
2277                    return Ok(TypeExpr::Named("bool".to_string()));
2278                }
2279                _ => None,
2280            };
2281            if let Some(name) = type_name {
2282                return Ok(TypeExpr::Named(name));
2283            }
2284        }
2285        // Function type: fn(T, U) -> R
2286        if self.check(&TokenKind::Fn) {
2287            self.advance(); // skip `fn`
2288            self.consume(&TokenKind::LParen, "(")?;
2289            let mut params = Vec::new();
2290            self.skip_newlines();
2291            while !self.is_at_end() && !self.check(&TokenKind::RParen) {
2292                params.push(self.parse_type_expr()?);
2293                self.skip_newlines();
2294                if self.check(&TokenKind::Comma) {
2295                    self.advance();
2296                    self.skip_newlines();
2297                }
2298            }
2299            self.consume(&TokenKind::RParen, ")")?;
2300            self.consume(&TokenKind::Arrow, "->")?;
2301            let return_type = self.parse_type_expr()?;
2302            return Ok(TypeExpr::FnType {
2303                params,
2304                return_type: Box::new(return_type),
2305            });
2306        }
2307        let name = self.consume_identifier("type name")?;
2308        // Check for generic type parameters: list<int>, dict<string, int>
2309        if self.check(&TokenKind::Lt) {
2310            self.advance(); // skip <
2311            let first_param = self.parse_type_expr()?;
2312            if name == "list" {
2313                self.consume(&TokenKind::Gt, ">")?;
2314                return Ok(TypeExpr::List(Box::new(first_param)));
2315            } else if name == "dict" {
2316                self.consume(&TokenKind::Comma, ",")?;
2317                let second_param = self.parse_type_expr()?;
2318                self.consume(&TokenKind::Gt, ">")?;
2319                return Ok(TypeExpr::DictType(
2320                    Box::new(first_param),
2321                    Box::new(second_param),
2322                ));
2323            }
2324            // Unknown generic — just consume > and treat as Named
2325            self.consume(&TokenKind::Gt, ">")?;
2326        }
2327        Ok(TypeExpr::Named(name))
2328    }
2329
2330    /// Parse a shape type: `{ name: string, age: int, active?: bool }`.
2331    fn parse_shape_type(&mut self) -> Result<TypeExpr, ParserError> {
2332        self.consume(&TokenKind::LBrace, "{")?;
2333        let mut fields = Vec::new();
2334        self.skip_newlines();
2335
2336        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
2337            let name = self.consume_identifier("field name")?;
2338            let optional = if self.check(&TokenKind::Question) {
2339                self.advance();
2340                true
2341            } else {
2342                false
2343            };
2344            self.consume(&TokenKind::Colon, ":")?;
2345            let type_expr = self.parse_type_expr()?;
2346            fields.push(ShapeField {
2347                name,
2348                type_expr,
2349                optional,
2350            });
2351            self.skip_newlines();
2352            if self.check(&TokenKind::Comma) {
2353                self.advance();
2354                self.skip_newlines();
2355            }
2356        }
2357
2358        self.consume(&TokenKind::RBrace, "}")?;
2359        Ok(TypeExpr::Shape(fields))
2360    }
2361
2362    fn parse_arg_list(&mut self) -> Result<Vec<SNode>, ParserError> {
2363        let mut args = Vec::new();
2364        self.skip_newlines();
2365
2366        while !self.is_at_end() && !self.check(&TokenKind::RParen) {
2367            // Check for spread: ...expr
2368            if self.check(&TokenKind::Dot) {
2369                let saved_pos = self.pos;
2370                self.advance(); // first .
2371                if self.check(&TokenKind::Dot) {
2372                    self.advance(); // second .
2373                    self.consume(&TokenKind::Dot, ".")?; // third .
2374                    let spread_start = self.tokens[saved_pos].span;
2375                    let expr = self.parse_expression()?;
2376                    args.push(spanned(
2377                        Node::Spread(Box::new(expr)),
2378                        Span::merge(spread_start, self.prev_span()),
2379                    ));
2380                } else {
2381                    // Not a spread, restore and parse as expression
2382                    self.pos = saved_pos;
2383                    args.push(self.parse_expression()?);
2384                }
2385            } else {
2386                args.push(self.parse_expression()?);
2387            }
2388            self.skip_newlines();
2389            if self.check(&TokenKind::Comma) {
2390                self.advance();
2391                self.skip_newlines();
2392            }
2393        }
2394        Ok(args)
2395    }
2396
2397    fn is_at_end(&self) -> bool {
2398        self.pos >= self.tokens.len()
2399            || matches!(self.tokens.get(self.pos), Some(t) if t.kind == TokenKind::Eof)
2400    }
2401
2402    fn current(&self) -> Option<&Token> {
2403        self.tokens.get(self.pos)
2404    }
2405
2406    fn peek_kind(&self) -> Option<&TokenKind> {
2407        self.tokens.get(self.pos + 1).map(|t| &t.kind)
2408    }
2409
2410    fn check(&self, kind: &TokenKind) -> bool {
2411        self.current()
2412            .map(|t| std::mem::discriminant(&t.kind) == std::mem::discriminant(kind))
2413            .unwrap_or(false)
2414    }
2415
2416    /// Check for a token kind, skipping past any newlines first.
2417    /// Used for binary operators like `||` and `&&` that can span lines.
2418    fn check_skip_newlines(&mut self, kind: &TokenKind) -> bool {
2419        let saved = self.pos;
2420        self.skip_newlines();
2421        if self.check(kind) {
2422            true
2423        } else {
2424            self.pos = saved;
2425            false
2426        }
2427    }
2428
2429    /// Check if current token is an identifier with the given name (without consuming it).
2430    fn check_identifier(&self, name: &str) -> bool {
2431        matches!(self.current().map(|t| &t.kind), Some(TokenKind::Identifier(s)) if s == name)
2432    }
2433
2434    fn advance(&mut self) {
2435        if self.pos < self.tokens.len() {
2436            self.pos += 1;
2437        }
2438    }
2439
2440    fn consume(&mut self, kind: &TokenKind, expected: &str) -> Result<Token, ParserError> {
2441        self.skip_newlines();
2442        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
2443        if std::mem::discriminant(&tok.kind) != std::mem::discriminant(kind) {
2444            return Err(self.make_error(expected));
2445        }
2446        let tok = tok.clone();
2447        self.advance();
2448        Ok(tok)
2449    }
2450
2451    fn consume_identifier(&mut self, expected: &str) -> Result<String, ParserError> {
2452        self.skip_newlines();
2453        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
2454        if let TokenKind::Identifier(name) = &tok.kind {
2455            let name = name.clone();
2456            self.advance();
2457            Ok(name)
2458        } else {
2459            // Produce a clearer error when a reserved keyword is used where
2460            // an identifier is expected (e.g. `for tool in list`).
2461            let kw_name = harn_lexer::KEYWORDS
2462                .iter()
2463                .find(|&&kw| kw == tok.kind.to_string());
2464            if let Some(kw) = kw_name {
2465                Err(ParserError::Unexpected {
2466                    got: format!("'{kw}' (reserved keyword)"),
2467                    expected: expected.into(),
2468                    span: tok.span,
2469                })
2470            } else {
2471                Err(self.make_error(expected))
2472            }
2473        }
2474    }
2475
2476    /// Like `consume_identifier`, but also accepts keywords as identifiers.
2477    /// Used for property access (e.g., `obj.type`) and dict keys where
2478    /// keywords are valid member names.
2479    fn consume_identifier_or_keyword(&mut self, expected: &str) -> Result<String, ParserError> {
2480        self.skip_newlines();
2481        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
2482        if let TokenKind::Identifier(name) = &tok.kind {
2483            let name = name.clone();
2484            self.advance();
2485            return Ok(name);
2486        }
2487        // Accept any keyword token as an identifier
2488        let name = match &tok.kind {
2489            TokenKind::Pipeline => "pipeline",
2490            TokenKind::Extends => "extends",
2491            TokenKind::Override => "override",
2492            TokenKind::Let => "let",
2493            TokenKind::Var => "var",
2494            TokenKind::If => "if",
2495            TokenKind::Else => "else",
2496            TokenKind::For => "for",
2497            TokenKind::In => "in",
2498            TokenKind::Match => "match",
2499            TokenKind::Retry => "retry",
2500            TokenKind::Parallel => "parallel",
2501            TokenKind::ParallelMap => "parallel_map",
2502            TokenKind::ParallelSettle => "parallel_settle",
2503            TokenKind::Return => "return",
2504            TokenKind::Import => "import",
2505            TokenKind::True => "true",
2506            TokenKind::False => "false",
2507            TokenKind::Nil => "nil",
2508            TokenKind::Try => "try",
2509            TokenKind::Catch => "catch",
2510            TokenKind::Throw => "throw",
2511            TokenKind::Fn => "fn",
2512            TokenKind::Spawn => "spawn",
2513            TokenKind::While => "while",
2514            TokenKind::TypeKw => "type",
2515            TokenKind::Enum => "enum",
2516            TokenKind::Struct => "struct",
2517            TokenKind::Interface => "interface",
2518            TokenKind::Pub => "pub",
2519            TokenKind::From => "from",
2520            TokenKind::Thru => "thru",
2521            TokenKind::Tool => "tool",
2522            TokenKind::Upto => "upto",
2523            TokenKind::Guard => "guard",
2524            TokenKind::Ask => "ask",
2525            TokenKind::Deadline => "deadline",
2526            TokenKind::Yield => "yield",
2527            TokenKind::Mutex => "mutex",
2528            TokenKind::Break => "break",
2529            TokenKind::Continue => "continue",
2530            TokenKind::Impl => "impl",
2531            _ => return Err(self.make_error(expected)),
2532        };
2533        let name = name.to_string();
2534        self.advance();
2535        Ok(name)
2536    }
2537
2538    fn skip_newlines(&mut self) {
2539        while self.pos < self.tokens.len() && self.tokens[self.pos].kind == TokenKind::Newline {
2540            self.pos += 1;
2541        }
2542    }
2543
2544    fn make_error(&self, expected: &str) -> ParserError {
2545        if let Some(tok) = self.tokens.get(self.pos) {
2546            if tok.kind == TokenKind::Eof {
2547                return ParserError::UnexpectedEof {
2548                    expected: expected.into(),
2549                    span: tok.span,
2550                };
2551            }
2552            ParserError::Unexpected {
2553                got: tok.kind.to_string(),
2554                expected: expected.into(),
2555                span: tok.span,
2556            }
2557        } else {
2558            ParserError::UnexpectedEof {
2559                expected: expected.into(),
2560                span: self.prev_span(),
2561            }
2562        }
2563    }
2564
2565    fn error(&self, expected: &str) -> ParserError {
2566        self.make_error(expected)
2567    }
2568}
2569
2570#[cfg(test)]
2571mod tests {
2572    use super::*;
2573    use harn_lexer::Lexer;
2574
2575    fn parse_source(source: &str) -> Result<Vec<SNode>, ParserError> {
2576        let mut lexer = Lexer::new(source);
2577        let tokens = lexer.tokenize().unwrap();
2578        let mut parser = Parser::new(tokens);
2579        parser.parse()
2580    }
2581
2582    #[test]
2583    fn parses_match_expression_with_let_in_arm_body() {
2584        let source = r#"
2585pipeline p() {
2586  let x = match 1 {
2587    1 -> {
2588      let a = 1
2589      a
2590    }
2591    _ -> { 0 }
2592  }
2593}
2594"#;
2595
2596        assert!(parse_source(source).is_ok());
2597    }
2598
2599    #[test]
2600    fn parses_public_declarations_and_generic_interfaces() {
2601        let source = r#"
2602pub pipeline build(task) extends base {
2603  return
2604}
2605
2606pub enum Result {
2607  Ok(value: string),
2608  Err(message: string, code: int),
2609}
2610
2611pub struct Config {
2612  host: string
2613  port?: int
2614}
2615
2616interface Repository<T> {
2617  fn get(id: string) -> T
2618  fn map<U>(value: T, f: fn(T) -> U) -> U
2619}
2620"#;
2621
2622        let program = parse_source(source).expect("should parse");
2623        assert!(matches!(
2624            &program[0].node,
2625            Node::Pipeline {
2626                is_pub: true,
2627                extends: Some(base),
2628                ..
2629            } if base == "base"
2630        ));
2631        assert!(matches!(
2632            &program[1].node,
2633            Node::EnumDecl { is_pub: true, .. }
2634        ));
2635        assert!(matches!(
2636            &program[2].node,
2637            Node::StructDecl { is_pub: true, .. }
2638        ));
2639        assert!(matches!(
2640            &program[3].node,
2641            Node::InterfaceDecl { type_params, methods, .. }
2642                if type_params.len() == 1
2643                    && methods.len() == 2
2644                    && methods[1].type_params.len() == 1
2645        ));
2646    }
2647}