Skip to main content

harn_parser/
parser.rs

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