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