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