Skip to main content

harn_parser/
parser.rs

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