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