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