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