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