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_exponent()?;
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_exponent()?;
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_exponent(&mut self) -> Result<SNode, ParserError> {
1572        let left = self.parse_unary()?;
1573        if !self.check_skip_newlines(&TokenKind::Pow) {
1574            return Ok(left);
1575        }
1576
1577        let start = left.span;
1578        self.advance();
1579        let right = self.parse_exponent()?;
1580        Ok(spanned(
1581            Node::BinaryOp {
1582                op: "**".into(),
1583                left: Box::new(left),
1584                right: Box::new(right),
1585            },
1586            Span::merge(start, self.prev_span()),
1587        ))
1588    }
1589
1590    fn parse_unary(&mut self) -> Result<SNode, ParserError> {
1591        if self.check(&TokenKind::Not) {
1592            let start = self.current_span();
1593            self.advance();
1594            let operand = self.parse_unary()?;
1595            return Ok(spanned(
1596                Node::UnaryOp {
1597                    op: "!".into(),
1598                    operand: Box::new(operand),
1599                },
1600                Span::merge(start, self.prev_span()),
1601            ));
1602        }
1603        if self.check(&TokenKind::Minus) {
1604            let start = self.current_span();
1605            self.advance();
1606            let operand = self.parse_unary()?;
1607            return Ok(spanned(
1608                Node::UnaryOp {
1609                    op: "-".into(),
1610                    operand: Box::new(operand),
1611                },
1612                Span::merge(start, self.prev_span()),
1613            ));
1614        }
1615        self.parse_postfix()
1616    }
1617
1618    fn parse_postfix(&mut self) -> Result<SNode, ParserError> {
1619        let mut expr = self.parse_primary()?;
1620
1621        loop {
1622            if self.check_skip_newlines(&TokenKind::Dot)
1623                || self.check_skip_newlines(&TokenKind::QuestionDot)
1624            {
1625                let optional = self.check(&TokenKind::QuestionDot);
1626                let start = expr.span;
1627                self.advance();
1628                let member = self.consume_identifier_or_keyword("member name")?;
1629                if self.check(&TokenKind::LParen) {
1630                    self.advance();
1631                    let args = self.parse_arg_list()?;
1632                    self.consume(&TokenKind::RParen, ")")?;
1633                    if optional {
1634                        expr = spanned(
1635                            Node::OptionalMethodCall {
1636                                object: Box::new(expr),
1637                                method: member,
1638                                args,
1639                            },
1640                            Span::merge(start, self.prev_span()),
1641                        );
1642                    } else {
1643                        expr = spanned(
1644                            Node::MethodCall {
1645                                object: Box::new(expr),
1646                                method: member,
1647                                args,
1648                            },
1649                            Span::merge(start, self.prev_span()),
1650                        );
1651                    }
1652                } else if optional {
1653                    expr = spanned(
1654                        Node::OptionalPropertyAccess {
1655                            object: Box::new(expr),
1656                            property: member,
1657                        },
1658                        Span::merge(start, self.prev_span()),
1659                    );
1660                } else {
1661                    expr = spanned(
1662                        Node::PropertyAccess {
1663                            object: Box::new(expr),
1664                            property: member,
1665                        },
1666                        Span::merge(start, self.prev_span()),
1667                    );
1668                }
1669            } else if self.check(&TokenKind::LBracket) {
1670                let start = expr.span;
1671                self.advance();
1672
1673                // Check for slice vs subscript:
1674                // [:end] — slice with no start
1675                // [start:end] or [start:] — slice with start
1676                // [index] — normal subscript
1677                if self.check(&TokenKind::Colon) {
1678                    // [:end] or [:]
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: None,
1690                            end: end_expr,
1691                        },
1692                        Span::merge(start, self.prev_span()),
1693                    );
1694                } else {
1695                    let index = self.parse_expression()?;
1696                    if self.check(&TokenKind::Colon) {
1697                        // [start:end] or [start:]
1698                        self.advance(); // consume ':'
1699                        let end_expr = if self.check(&TokenKind::RBracket) {
1700                            None
1701                        } else {
1702                            Some(Box::new(self.parse_expression()?))
1703                        };
1704                        self.consume(&TokenKind::RBracket, "]")?;
1705                        expr = spanned(
1706                            Node::SliceAccess {
1707                                object: Box::new(expr),
1708                                start: Some(Box::new(index)),
1709                                end: end_expr,
1710                            },
1711                            Span::merge(start, self.prev_span()),
1712                        );
1713                    } else {
1714                        self.consume(&TokenKind::RBracket, "]")?;
1715                        expr = spanned(
1716                            Node::SubscriptAccess {
1717                                object: Box::new(expr),
1718                                index: Box::new(index),
1719                            },
1720                            Span::merge(start, self.prev_span()),
1721                        );
1722                    }
1723                }
1724            } else if self.check(&TokenKind::LBrace)
1725                && matches!(&expr.node, Node::Identifier(name) if self.struct_names.contains(name))
1726            {
1727                let start = expr.span;
1728                let struct_name = match expr.node {
1729                    Node::Identifier(name) => name,
1730                    _ => unreachable!("checked above"),
1731                };
1732                self.advance();
1733                let dict = self.parse_dict_literal(start)?;
1734                let fields = match dict.node {
1735                    Node::DictLiteral(fields) => fields,
1736                    _ => unreachable!("dict parser must return a dict literal"),
1737                };
1738                expr = spanned(
1739                    Node::StructConstruct {
1740                        struct_name,
1741                        fields,
1742                    },
1743                    dict.span,
1744                );
1745            } else if self.check(&TokenKind::LParen) && matches!(expr.node, Node::Identifier(_)) {
1746                let start = expr.span;
1747                self.advance();
1748                let args = self.parse_arg_list()?;
1749                self.consume(&TokenKind::RParen, ")")?;
1750                if let Node::Identifier(name) = expr.node {
1751                    expr = spanned(
1752                        Node::FunctionCall { name, args },
1753                        Span::merge(start, self.prev_span()),
1754                    );
1755                }
1756            } else if self.check(&TokenKind::Question) {
1757                // Distinguish postfix try operator (expr?) from ternary (expr ? a : b).
1758                // If the token after ? could start a ternary branch, leave it for parse_ternary.
1759                let next_pos = self.pos + 1;
1760                let is_ternary = self.tokens.get(next_pos).is_some_and(|t| {
1761                    matches!(
1762                        t.kind,
1763                        TokenKind::Identifier(_)
1764                            | TokenKind::IntLiteral(_)
1765                            | TokenKind::FloatLiteral(_)
1766                            | TokenKind::StringLiteral(_)
1767                            | TokenKind::InterpolatedString(_)
1768                            | TokenKind::True
1769                            | TokenKind::False
1770                            | TokenKind::Nil
1771                            | TokenKind::LParen
1772                            | TokenKind::LBracket
1773                            | TokenKind::LBrace
1774                            | TokenKind::Not
1775                            | TokenKind::Minus
1776                            | TokenKind::Fn
1777                    )
1778                });
1779                if is_ternary {
1780                    break;
1781                }
1782                let start = expr.span;
1783                self.advance(); // consume ?
1784                expr = spanned(
1785                    Node::TryOperator {
1786                        operand: Box::new(expr),
1787                    },
1788                    Span::merge(start, self.prev_span()),
1789                );
1790            } else {
1791                break;
1792            }
1793        }
1794
1795        Ok(expr)
1796    }
1797
1798    fn parse_primary(&mut self) -> Result<SNode, ParserError> {
1799        let tok = self.current().ok_or_else(|| ParserError::UnexpectedEof {
1800            expected: "expression".into(),
1801            span: self.prev_span(),
1802        })?;
1803        let start = self.current_span();
1804
1805        match &tok.kind {
1806            TokenKind::StringLiteral(s) => {
1807                let s = s.clone();
1808                self.advance();
1809                Ok(spanned(
1810                    Node::StringLiteral(s),
1811                    Span::merge(start, self.prev_span()),
1812                ))
1813            }
1814            TokenKind::RawStringLiteral(s) => {
1815                let s = s.clone();
1816                self.advance();
1817                Ok(spanned(
1818                    Node::RawStringLiteral(s),
1819                    Span::merge(start, self.prev_span()),
1820                ))
1821            }
1822            TokenKind::InterpolatedString(segments) => {
1823                let segments = segments.clone();
1824                self.advance();
1825                Ok(spanned(
1826                    Node::InterpolatedString(segments),
1827                    Span::merge(start, self.prev_span()),
1828                ))
1829            }
1830            TokenKind::IntLiteral(n) => {
1831                let n = *n;
1832                self.advance();
1833                Ok(spanned(
1834                    Node::IntLiteral(n),
1835                    Span::merge(start, self.prev_span()),
1836                ))
1837            }
1838            TokenKind::FloatLiteral(n) => {
1839                let n = *n;
1840                self.advance();
1841                Ok(spanned(
1842                    Node::FloatLiteral(n),
1843                    Span::merge(start, self.prev_span()),
1844                ))
1845            }
1846            TokenKind::True => {
1847                self.advance();
1848                Ok(spanned(
1849                    Node::BoolLiteral(true),
1850                    Span::merge(start, self.prev_span()),
1851                ))
1852            }
1853            TokenKind::False => {
1854                self.advance();
1855                Ok(spanned(
1856                    Node::BoolLiteral(false),
1857                    Span::merge(start, self.prev_span()),
1858                ))
1859            }
1860            TokenKind::Nil => {
1861                self.advance();
1862                Ok(spanned(
1863                    Node::NilLiteral,
1864                    Span::merge(start, self.prev_span()),
1865                ))
1866            }
1867            TokenKind::Identifier(name) => {
1868                let name = name.clone();
1869                self.advance();
1870                Ok(spanned(
1871                    Node::Identifier(name),
1872                    Span::merge(start, self.prev_span()),
1873                ))
1874            }
1875            TokenKind::LParen => {
1876                self.advance();
1877                let expr = self.parse_expression()?;
1878                self.consume(&TokenKind::RParen, ")")?;
1879                Ok(expr)
1880            }
1881            TokenKind::LBracket => self.parse_list_literal(),
1882            TokenKind::LBrace => self.parse_dict_or_closure(),
1883            TokenKind::Parallel => self.parse_parallel(),
1884            TokenKind::Retry => self.parse_retry(),
1885            TokenKind::If => self.parse_if_else(),
1886            TokenKind::Spawn => self.parse_spawn_expr(),
1887            TokenKind::DurationLiteral(ms) => {
1888                let ms = *ms;
1889                self.advance();
1890                Ok(spanned(
1891                    Node::DurationLiteral(ms),
1892                    Span::merge(start, self.prev_span()),
1893                ))
1894            }
1895            TokenKind::Deadline => self.parse_deadline(),
1896            TokenKind::Try => self.parse_try_catch(),
1897            TokenKind::Match => self.parse_match(),
1898            TokenKind::Fn => self.parse_fn_expr(),
1899            _ => Err(self.error("expression")),
1900        }
1901    }
1902
1903    /// Parse an anonymous function expression: `fn(params) { body }`
1904    /// Produces a Closure node with `fn_syntax: true` so the formatter
1905    /// can round-trip the original syntax.
1906    fn parse_fn_expr(&mut self) -> Result<SNode, ParserError> {
1907        let start = self.current_span();
1908        self.consume(&TokenKind::Fn, "fn")?;
1909        self.consume(&TokenKind::LParen, "(")?;
1910        let params = self.parse_typed_param_list()?;
1911        self.consume(&TokenKind::RParen, ")")?;
1912        self.consume(&TokenKind::LBrace, "{")?;
1913        let body = self.parse_block()?;
1914        self.consume(&TokenKind::RBrace, "}")?;
1915        Ok(spanned(
1916            Node::Closure {
1917                params,
1918                body,
1919                fn_syntax: true,
1920            },
1921            Span::merge(start, self.prev_span()),
1922        ))
1923    }
1924
1925    fn parse_spawn_expr(&mut self) -> Result<SNode, ParserError> {
1926        let start = self.current_span();
1927        self.consume(&TokenKind::Spawn, "spawn")?;
1928        self.consume(&TokenKind::LBrace, "{")?;
1929        let body = self.parse_block()?;
1930        self.consume(&TokenKind::RBrace, "}")?;
1931        Ok(spanned(
1932            Node::SpawnExpr { body },
1933            Span::merge(start, self.prev_span()),
1934        ))
1935    }
1936
1937    fn parse_list_literal(&mut self) -> Result<SNode, ParserError> {
1938        let start = self.current_span();
1939        self.consume(&TokenKind::LBracket, "[")?;
1940        let mut elements = Vec::new();
1941        self.skip_newlines();
1942
1943        while !self.is_at_end() && !self.check(&TokenKind::RBracket) {
1944            // Check for spread: ...expr
1945            if self.check(&TokenKind::Dot) {
1946                let saved_pos = self.pos;
1947                self.advance(); // first .
1948                if self.check(&TokenKind::Dot) {
1949                    self.advance(); // second .
1950                    self.consume(&TokenKind::Dot, ".")?; // third .
1951                    let spread_start = self.tokens[saved_pos].span;
1952                    let expr = self.parse_expression()?;
1953                    elements.push(spanned(
1954                        Node::Spread(Box::new(expr)),
1955                        Span::merge(spread_start, self.prev_span()),
1956                    ));
1957                } else {
1958                    // Not a spread, restore and parse as expression
1959                    self.pos = saved_pos;
1960                    elements.push(self.parse_expression()?);
1961                }
1962            } else {
1963                elements.push(self.parse_expression()?);
1964            }
1965            self.skip_newlines();
1966            if self.check(&TokenKind::Comma) {
1967                self.advance();
1968                self.skip_newlines();
1969            }
1970        }
1971
1972        self.consume(&TokenKind::RBracket, "]")?;
1973        Ok(spanned(
1974            Node::ListLiteral(elements),
1975            Span::merge(start, self.prev_span()),
1976        ))
1977    }
1978
1979    fn parse_dict_or_closure(&mut self) -> Result<SNode, ParserError> {
1980        let start = self.current_span();
1981        self.consume(&TokenKind::LBrace, "{")?;
1982        self.skip_newlines();
1983
1984        // Empty dict
1985        if self.check(&TokenKind::RBrace) {
1986            self.advance();
1987            return Ok(spanned(
1988                Node::DictLiteral(Vec::new()),
1989                Span::merge(start, self.prev_span()),
1990            ));
1991        }
1992
1993        // Lookahead: scan for -> before } to disambiguate closure from dict.
1994        let saved = self.pos;
1995        if self.is_closure_lookahead() {
1996            self.pos = saved;
1997            return self.parse_closure_body(start);
1998        }
1999        self.pos = saved;
2000        self.parse_dict_literal(start)
2001    }
2002
2003    /// Scan forward to determine if this is a closure (has -> before matching }).
2004    /// Does not consume tokens (caller saves/restores pos).
2005    fn is_closure_lookahead(&mut self) -> bool {
2006        let mut depth = 0;
2007        while !self.is_at_end() {
2008            if let Some(tok) = self.current() {
2009                match &tok.kind {
2010                    TokenKind::Arrow if depth == 0 => return true,
2011                    TokenKind::LBrace | TokenKind::LParen | TokenKind::LBracket => depth += 1,
2012                    TokenKind::RBrace if depth == 0 => return false,
2013                    TokenKind::RBrace => depth -= 1,
2014                    TokenKind::RParen | TokenKind::RBracket => {
2015                        if depth > 0 {
2016                            depth -= 1;
2017                        }
2018                    }
2019                    _ => {}
2020                }
2021                self.advance();
2022            } else {
2023                return false;
2024            }
2025        }
2026        false
2027    }
2028
2029    /// Parse closure params and body (after opening { has been consumed).
2030    fn parse_closure_body(&mut self, start: Span) -> Result<SNode, ParserError> {
2031        let params = self.parse_typed_param_list_until_arrow()?;
2032        self.consume(&TokenKind::Arrow, "->")?;
2033        let body = self.parse_block()?;
2034        self.consume(&TokenKind::RBrace, "}")?;
2035        Ok(spanned(
2036            Node::Closure {
2037                params,
2038                body,
2039                fn_syntax: false,
2040            },
2041            Span::merge(start, self.prev_span()),
2042        ))
2043    }
2044
2045    /// Parse typed params until we see ->. Handles: `x`, `x: int`, `x, y`, `x: int, y: string`.
2046    fn parse_typed_param_list_until_arrow(&mut self) -> Result<Vec<TypedParam>, ParserError> {
2047        self.parse_typed_params_until(|tok| tok == &TokenKind::Arrow)
2048    }
2049
2050    fn parse_dict_literal(&mut self, start: Span) -> Result<SNode, ParserError> {
2051        let entries = self.parse_dict_entries()?;
2052        Ok(spanned(
2053            Node::DictLiteral(entries),
2054            Span::merge(start, self.prev_span()),
2055        ))
2056    }
2057
2058    fn parse_dict_entries(&mut self) -> Result<Vec<DictEntry>, ParserError> {
2059        let mut entries = Vec::new();
2060        self.skip_newlines();
2061
2062        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
2063            // Check for spread: ...expr
2064            if self.check(&TokenKind::Dot) {
2065                let saved_pos = self.pos;
2066                self.advance(); // first .
2067                if self.check(&TokenKind::Dot) {
2068                    self.advance(); // second .
2069                    if self.check(&TokenKind::Dot) {
2070                        self.advance(); // third .
2071                        let spread_start = self.tokens[saved_pos].span;
2072                        let expr = self.parse_expression()?;
2073                        entries.push(DictEntry {
2074                            key: spanned(Node::NilLiteral, spread_start),
2075                            value: spanned(
2076                                Node::Spread(Box::new(expr)),
2077                                Span::merge(spread_start, self.prev_span()),
2078                            ),
2079                        });
2080                        self.skip_newlines();
2081                        if self.check(&TokenKind::Comma) {
2082                            self.advance();
2083                            self.skip_newlines();
2084                        }
2085                        continue;
2086                    }
2087                    // Not three dots — restore
2088                    self.pos = saved_pos;
2089                } else {
2090                    self.pos = saved_pos;
2091                }
2092            }
2093            let key = if self.check(&TokenKind::LBracket) {
2094                // Computed key: [expression]
2095                self.advance();
2096                let k = self.parse_expression()?;
2097                self.consume(&TokenKind::RBracket, "]")?;
2098                k
2099            } else if matches!(
2100                self.current().map(|t| &t.kind),
2101                Some(TokenKind::StringLiteral(_))
2102            ) {
2103                // Quoted string key: {"key": value}
2104                let key_span = self.current_span();
2105                let name =
2106                    if let Some(TokenKind::StringLiteral(s)) = self.current().map(|t| &t.kind) {
2107                        s.clone()
2108                    } else {
2109                        unreachable!()
2110                    };
2111                self.advance();
2112                spanned(Node::StringLiteral(name), key_span)
2113            } else {
2114                // Static key: identifier or keyword -> string literal
2115                let key_span = self.current_span();
2116                let name = self.consume_identifier_or_keyword("dict key")?;
2117                spanned(Node::StringLiteral(name), key_span)
2118            };
2119            self.consume(&TokenKind::Colon, ":")?;
2120            let value = self.parse_expression()?;
2121            entries.push(DictEntry { key, value });
2122            self.skip_newlines();
2123            if self.check(&TokenKind::Comma) {
2124                self.advance();
2125                self.skip_newlines();
2126            }
2127        }
2128
2129        self.consume(&TokenKind::RBrace, "}")?;
2130        Ok(entries)
2131    }
2132
2133    // --- Helpers ---
2134
2135    /// Parse untyped parameter list (for pipelines, overrides).
2136    fn parse_param_list(&mut self) -> Result<Vec<String>, ParserError> {
2137        let mut params = Vec::new();
2138        self.skip_newlines();
2139
2140        while !self.is_at_end() && !self.check(&TokenKind::RParen) {
2141            params.push(self.consume_identifier("parameter name")?);
2142            if self.check(&TokenKind::Comma) {
2143                self.advance();
2144                self.skip_newlines();
2145            }
2146        }
2147        Ok(params)
2148    }
2149
2150    /// Parse typed parameter list (for fn declarations).
2151    fn parse_typed_param_list(&mut self) -> Result<Vec<TypedParam>, ParserError> {
2152        self.parse_typed_params_until(|tok| tok == &TokenKind::RParen)
2153    }
2154
2155    /// Shared implementation: parse typed params with optional defaults until
2156    /// a terminator token is reached.
2157    fn parse_typed_params_until(
2158        &mut self,
2159        is_terminator: impl Fn(&TokenKind) -> bool,
2160    ) -> Result<Vec<TypedParam>, ParserError> {
2161        let mut params = Vec::new();
2162        let mut seen_default = false;
2163        self.skip_newlines();
2164
2165        while !self.is_at_end() {
2166            if let Some(tok) = self.current() {
2167                if is_terminator(&tok.kind) {
2168                    break;
2169                }
2170            } else {
2171                break;
2172            }
2173            // Check for rest parameter: ...name
2174            let is_rest = if self.check(&TokenKind::Dot) {
2175                // Peek ahead for two more dots
2176                let p1 = self.pos + 1;
2177                let p2 = self.pos + 2;
2178                let is_ellipsis = p1 < self.tokens.len()
2179                    && p2 < self.tokens.len()
2180                    && self.tokens[p1].kind == TokenKind::Dot
2181                    && self.tokens[p2].kind == TokenKind::Dot;
2182                if is_ellipsis {
2183                    self.advance(); // skip .
2184                    self.advance(); // skip .
2185                    self.advance(); // skip .
2186                    true
2187                } else {
2188                    false
2189                }
2190            } else {
2191                false
2192            };
2193            let name = self.consume_identifier("parameter name")?;
2194            let type_expr = self.try_parse_type_annotation()?;
2195            let default_value = if self.check(&TokenKind::Assign) {
2196                self.advance();
2197                seen_default = true;
2198                Some(Box::new(self.parse_expression()?))
2199            } else {
2200                if seen_default && !is_rest {
2201                    return Err(self.error(
2202                        "Required parameter cannot follow a parameter with a default value",
2203                    ));
2204                }
2205                None
2206            };
2207            if is_rest
2208                && !is_terminator(
2209                    &self
2210                        .current()
2211                        .map(|t| t.kind.clone())
2212                        .unwrap_or(TokenKind::Eof),
2213                )
2214            {
2215                return Err(self.error("Rest parameter must be the last parameter"));
2216            }
2217            params.push(TypedParam {
2218                name,
2219                type_expr,
2220                default_value,
2221                rest: is_rest,
2222            });
2223            if self.check(&TokenKind::Comma) {
2224                self.advance();
2225                self.skip_newlines();
2226            }
2227        }
2228        Ok(params)
2229    }
2230
2231    /// Parse a comma-separated list of type parameter names until `>`.
2232    fn parse_type_param_list(&mut self) -> Result<Vec<TypeParam>, ParserError> {
2233        let mut params = Vec::new();
2234        self.skip_newlines();
2235        while !self.is_at_end() && !self.check(&TokenKind::Gt) {
2236            let name = self.consume_identifier("type parameter name")?;
2237            params.push(TypeParam { name });
2238            if self.check(&TokenKind::Comma) {
2239                self.advance();
2240                self.skip_newlines();
2241            }
2242        }
2243        self.consume(&TokenKind::Gt, ">")?;
2244        Ok(params)
2245    }
2246
2247    /// Parse an optional `where T: bound, U: bound` clause.
2248    /// Looks for an identifier "where" before `{`.
2249    fn parse_where_clauses(&mut self) -> Result<Vec<WhereClause>, ParserError> {
2250        // Check if the next identifier is "where"
2251        if let Some(tok) = self.current() {
2252            if let TokenKind::Identifier(ref id) = tok.kind {
2253                if id == "where" {
2254                    self.advance(); // skip "where"
2255                    let mut clauses = Vec::new();
2256                    loop {
2257                        self.skip_newlines();
2258                        // Stop if we hit `{` or EOF
2259                        if self.check(&TokenKind::LBrace) || self.is_at_end() {
2260                            break;
2261                        }
2262                        let type_name = self.consume_identifier("type parameter name")?;
2263                        self.consume(&TokenKind::Colon, ":")?;
2264                        let bound = self.consume_identifier("type bound")?;
2265                        clauses.push(WhereClause { type_name, bound });
2266                        if self.check(&TokenKind::Comma) {
2267                            self.advance();
2268                        } else {
2269                            break;
2270                        }
2271                    }
2272                    return Ok(clauses);
2273                }
2274            }
2275        }
2276        Ok(Vec::new())
2277    }
2278
2279    /// Try to parse an optional type annotation (`: type`).
2280    /// Returns None if no colon follows.
2281    fn try_parse_type_annotation(&mut self) -> Result<Option<TypeExpr>, ParserError> {
2282        if !self.check(&TokenKind::Colon) {
2283            return Ok(None);
2284        }
2285        self.advance(); // skip :
2286        Ok(Some(self.parse_type_expr()?))
2287    }
2288
2289    /// Parse a type expression: `int`, `string | nil`, `{name: string, age?: int}`.
2290    fn parse_type_expr(&mut self) -> Result<TypeExpr, ParserError> {
2291        self.skip_newlines();
2292        let first = self.parse_type_primary()?;
2293
2294        // Check for union: type | type | ...
2295        if self.check(&TokenKind::Bar) {
2296            let mut types = vec![first];
2297            while self.check(&TokenKind::Bar) {
2298                self.advance(); // skip |
2299                types.push(self.parse_type_primary()?);
2300            }
2301            return Ok(TypeExpr::Union(types));
2302        }
2303
2304        Ok(first)
2305    }
2306
2307    /// Parse a primary type: named type or shape type.
2308    /// Accepts identifiers and certain keywords (nil, bool, etc.) as type names.
2309    fn parse_type_primary(&mut self) -> Result<TypeExpr, ParserError> {
2310        self.skip_newlines();
2311        if self.check(&TokenKind::LBrace) {
2312            return self.parse_shape_type();
2313        }
2314        // Accept keyword type names: nil, true, false map to their type names
2315        if let Some(tok) = self.current() {
2316            let type_name = match &tok.kind {
2317                TokenKind::Nil => {
2318                    self.advance();
2319                    return Ok(TypeExpr::Named("nil".to_string()));
2320                }
2321                TokenKind::True | TokenKind::False => {
2322                    self.advance();
2323                    return Ok(TypeExpr::Named("bool".to_string()));
2324                }
2325                _ => None,
2326            };
2327            if let Some(name) = type_name {
2328                return Ok(TypeExpr::Named(name));
2329            }
2330        }
2331        // Function type: fn(T, U) -> R
2332        if self.check(&TokenKind::Fn) {
2333            self.advance(); // skip `fn`
2334            self.consume(&TokenKind::LParen, "(")?;
2335            let mut params = Vec::new();
2336            self.skip_newlines();
2337            while !self.is_at_end() && !self.check(&TokenKind::RParen) {
2338                params.push(self.parse_type_expr()?);
2339                self.skip_newlines();
2340                if self.check(&TokenKind::Comma) {
2341                    self.advance();
2342                    self.skip_newlines();
2343                }
2344            }
2345            self.consume(&TokenKind::RParen, ")")?;
2346            self.consume(&TokenKind::Arrow, "->")?;
2347            let return_type = self.parse_type_expr()?;
2348            return Ok(TypeExpr::FnType {
2349                params,
2350                return_type: Box::new(return_type),
2351            });
2352        }
2353        let name = self.consume_identifier("type name")?;
2354        // Bottom type
2355        if name == "never" {
2356            return Ok(TypeExpr::Never);
2357        }
2358        // Check for generic type parameters: list<int>, dict<string, int>, Result<T, E>
2359        if self.check(&TokenKind::Lt) {
2360            self.advance(); // skip <
2361            let mut type_args = vec![self.parse_type_expr()?];
2362            while self.check(&TokenKind::Comma) {
2363                self.advance();
2364                type_args.push(self.parse_type_expr()?);
2365            }
2366            self.consume(&TokenKind::Gt, ">")?;
2367            if name == "list" && type_args.len() == 1 {
2368                return Ok(TypeExpr::List(Box::new(type_args.remove(0))));
2369            } else if name == "dict" && type_args.len() == 2 {
2370                return Ok(TypeExpr::DictType(
2371                    Box::new(type_args.remove(0)),
2372                    Box::new(type_args.remove(0)),
2373                ));
2374            }
2375            return Ok(TypeExpr::Applied {
2376                name,
2377                args: type_args,
2378            });
2379        }
2380        Ok(TypeExpr::Named(name))
2381    }
2382
2383    /// Parse a shape type: `{ name: string, age: int, active?: bool }`.
2384    fn parse_shape_type(&mut self) -> Result<TypeExpr, ParserError> {
2385        self.consume(&TokenKind::LBrace, "{")?;
2386        let mut fields = Vec::new();
2387        self.skip_newlines();
2388
2389        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
2390            let name = self.consume_identifier("field name")?;
2391            let optional = if self.check(&TokenKind::Question) {
2392                self.advance();
2393                true
2394            } else {
2395                false
2396            };
2397            self.consume(&TokenKind::Colon, ":")?;
2398            let type_expr = self.parse_type_expr()?;
2399            fields.push(ShapeField {
2400                name,
2401                type_expr,
2402                optional,
2403            });
2404            self.skip_newlines();
2405            if self.check(&TokenKind::Comma) {
2406                self.advance();
2407                self.skip_newlines();
2408            }
2409        }
2410
2411        self.consume(&TokenKind::RBrace, "}")?;
2412        Ok(TypeExpr::Shape(fields))
2413    }
2414
2415    fn parse_arg_list(&mut self) -> Result<Vec<SNode>, ParserError> {
2416        let mut args = Vec::new();
2417        self.skip_newlines();
2418
2419        while !self.is_at_end() && !self.check(&TokenKind::RParen) {
2420            // Check for spread: ...expr
2421            if self.check(&TokenKind::Dot) {
2422                let saved_pos = self.pos;
2423                self.advance(); // first .
2424                if self.check(&TokenKind::Dot) {
2425                    self.advance(); // second .
2426                    self.consume(&TokenKind::Dot, ".")?; // third .
2427                    let spread_start = self.tokens[saved_pos].span;
2428                    let expr = self.parse_expression()?;
2429                    args.push(spanned(
2430                        Node::Spread(Box::new(expr)),
2431                        Span::merge(spread_start, self.prev_span()),
2432                    ));
2433                } else {
2434                    // Not a spread, restore and parse as expression
2435                    self.pos = saved_pos;
2436                    args.push(self.parse_expression()?);
2437                }
2438            } else {
2439                args.push(self.parse_expression()?);
2440            }
2441            self.skip_newlines();
2442            if self.check(&TokenKind::Comma) {
2443                self.advance();
2444                self.skip_newlines();
2445            }
2446        }
2447        Ok(args)
2448    }
2449
2450    fn is_at_end(&self) -> bool {
2451        self.pos >= self.tokens.len()
2452            || matches!(self.tokens.get(self.pos), Some(t) if t.kind == TokenKind::Eof)
2453    }
2454
2455    fn current(&self) -> Option<&Token> {
2456        self.tokens.get(self.pos)
2457    }
2458
2459    fn peek_kind(&self) -> Option<&TokenKind> {
2460        self.tokens.get(self.pos + 1).map(|t| &t.kind)
2461    }
2462
2463    fn check(&self, kind: &TokenKind) -> bool {
2464        self.current()
2465            .map(|t| std::mem::discriminant(&t.kind) == std::mem::discriminant(kind))
2466            .unwrap_or(false)
2467    }
2468
2469    /// Check for a token kind, skipping past any newlines first.
2470    /// Used for binary operators like `||` and `&&` that can span lines.
2471    fn check_skip_newlines(&mut self, kind: &TokenKind) -> bool {
2472        let saved = self.pos;
2473        self.skip_newlines();
2474        if self.check(kind) {
2475            true
2476        } else {
2477            self.pos = saved;
2478            false
2479        }
2480    }
2481
2482    /// Check if current token is an identifier with the given name (without consuming it).
2483    fn check_identifier(&self, name: &str) -> bool {
2484        matches!(self.current().map(|t| &t.kind), Some(TokenKind::Identifier(s)) if s == name)
2485    }
2486
2487    fn advance(&mut self) {
2488        if self.pos < self.tokens.len() {
2489            self.pos += 1;
2490        }
2491    }
2492
2493    fn consume(&mut self, kind: &TokenKind, expected: &str) -> Result<Token, ParserError> {
2494        self.skip_newlines();
2495        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
2496        if std::mem::discriminant(&tok.kind) != std::mem::discriminant(kind) {
2497            return Err(self.make_error(expected));
2498        }
2499        let tok = tok.clone();
2500        self.advance();
2501        Ok(tok)
2502    }
2503
2504    fn consume_identifier(&mut self, expected: &str) -> Result<String, ParserError> {
2505        self.skip_newlines();
2506        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
2507        if let TokenKind::Identifier(name) = &tok.kind {
2508            let name = name.clone();
2509            self.advance();
2510            Ok(name)
2511        } else {
2512            // Produce a clearer error when a reserved keyword is used where
2513            // an identifier is expected (e.g. `for tool in list`).
2514            let kw_name = harn_lexer::KEYWORDS
2515                .iter()
2516                .find(|&&kw| kw == tok.kind.to_string());
2517            if let Some(kw) = kw_name {
2518                Err(ParserError::Unexpected {
2519                    got: format!("'{kw}' (reserved keyword)"),
2520                    expected: expected.into(),
2521                    span: tok.span,
2522                })
2523            } else {
2524                Err(self.make_error(expected))
2525            }
2526        }
2527    }
2528
2529    /// Like `consume_identifier`, but also accepts keywords as identifiers.
2530    /// Used for property access (e.g., `obj.type`) and dict keys where
2531    /// keywords are valid member names.
2532    fn consume_identifier_or_keyword(&mut self, expected: &str) -> Result<String, ParserError> {
2533        self.skip_newlines();
2534        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
2535        if let TokenKind::Identifier(name) = &tok.kind {
2536            let name = name.clone();
2537            self.advance();
2538            return Ok(name);
2539        }
2540        // Accept any keyword token as an identifier
2541        let name = match &tok.kind {
2542            TokenKind::Pipeline => "pipeline",
2543            TokenKind::Extends => "extends",
2544            TokenKind::Override => "override",
2545            TokenKind::Let => "let",
2546            TokenKind::Var => "var",
2547            TokenKind::If => "if",
2548            TokenKind::Else => "else",
2549            TokenKind::For => "for",
2550            TokenKind::In => "in",
2551            TokenKind::Match => "match",
2552            TokenKind::Retry => "retry",
2553            TokenKind::Parallel => "parallel",
2554            TokenKind::Return => "return",
2555            TokenKind::Import => "import",
2556            TokenKind::True => "true",
2557            TokenKind::False => "false",
2558            TokenKind::Nil => "nil",
2559            TokenKind::Try => "try",
2560            TokenKind::Catch => "catch",
2561            TokenKind::Throw => "throw",
2562            TokenKind::Fn => "fn",
2563            TokenKind::Spawn => "spawn",
2564            TokenKind::While => "while",
2565            TokenKind::TypeKw => "type",
2566            TokenKind::Enum => "enum",
2567            TokenKind::Struct => "struct",
2568            TokenKind::Interface => "interface",
2569            TokenKind::Pub => "pub",
2570            TokenKind::From => "from",
2571            TokenKind::Thru => "thru",
2572            TokenKind::Tool => "tool",
2573            TokenKind::Upto => "upto",
2574            TokenKind::Guard => "guard",
2575            TokenKind::Deadline => "deadline",
2576            TokenKind::Defer => "defer",
2577            TokenKind::Yield => "yield",
2578            TokenKind::Mutex => "mutex",
2579            TokenKind::Break => "break",
2580            TokenKind::Continue => "continue",
2581            TokenKind::Impl => "impl",
2582            _ => return Err(self.make_error(expected)),
2583        };
2584        let name = name.to_string();
2585        self.advance();
2586        Ok(name)
2587    }
2588
2589    fn skip_newlines(&mut self) {
2590        while self.pos < self.tokens.len() && self.tokens[self.pos].kind == TokenKind::Newline {
2591            self.pos += 1;
2592        }
2593    }
2594
2595    fn make_error(&self, expected: &str) -> ParserError {
2596        if let Some(tok) = self.tokens.get(self.pos) {
2597            if tok.kind == TokenKind::Eof {
2598                return ParserError::UnexpectedEof {
2599                    expected: expected.into(),
2600                    span: tok.span,
2601                };
2602            }
2603            ParserError::Unexpected {
2604                got: tok.kind.to_string(),
2605                expected: expected.into(),
2606                span: tok.span,
2607            }
2608        } else {
2609            ParserError::UnexpectedEof {
2610                expected: expected.into(),
2611                span: self.prev_span(),
2612            }
2613        }
2614    }
2615
2616    fn error(&self, expected: &str) -> ParserError {
2617        self.make_error(expected)
2618    }
2619}
2620
2621#[cfg(test)]
2622mod tests {
2623    use super::*;
2624    use harn_lexer::Lexer;
2625
2626    fn parse_source(source: &str) -> Result<Vec<SNode>, ParserError> {
2627        let mut lexer = Lexer::new(source);
2628        let tokens = lexer.tokenize().unwrap();
2629        let mut parser = Parser::new(tokens);
2630        parser.parse()
2631    }
2632
2633    #[test]
2634    fn parses_match_expression_with_let_in_arm_body() {
2635        let source = r#"
2636pipeline p() {
2637  let x = match 1 {
2638    1 -> {
2639      let a = 1
2640      a
2641    }
2642    _ -> { 0 }
2643  }
2644}
2645"#;
2646
2647        assert!(parse_source(source).is_ok());
2648    }
2649
2650    #[test]
2651    fn parses_public_declarations_and_generic_interfaces() {
2652        let source = r#"
2653pub pipeline build(task) extends base {
2654  return
2655}
2656
2657pub enum Result {
2658  Ok(value: string),
2659  Err(message: string, code: int),
2660}
2661
2662pub struct Config {
2663  host: string
2664  port?: int
2665}
2666
2667interface Repository<T> {
2668  type Item
2669  fn get(id: string) -> T
2670  fn map<U>(value: T, f: fn(T) -> U) -> U
2671}
2672"#;
2673
2674        let program = parse_source(source).expect("should parse");
2675        assert!(matches!(
2676            &program[0].node,
2677            Node::Pipeline {
2678                is_pub: true,
2679                extends: Some(base),
2680                ..
2681            } if base == "base"
2682        ));
2683        assert!(matches!(
2684            &program[1].node,
2685            Node::EnumDecl {
2686                is_pub: true,
2687                type_params,
2688                ..
2689            } if type_params.is_empty()
2690        ));
2691        assert!(matches!(
2692            &program[2].node,
2693            Node::StructDecl {
2694                is_pub: true,
2695                type_params,
2696                ..
2697            } if type_params.is_empty()
2698        ));
2699        assert!(matches!(
2700            &program[3].node,
2701            Node::InterfaceDecl {
2702                type_params,
2703                associated_types,
2704                methods,
2705                ..
2706            }
2707                if type_params.len() == 1
2708                    && associated_types.len() == 1
2709                    && methods.len() == 2
2710                    && methods[1].type_params.len() == 1
2711        ));
2712    }
2713
2714    #[test]
2715    fn parses_generic_structs_and_enums() {
2716        let source = r#"
2717struct Pair<A, B> {
2718  first: A
2719  second: B
2720}
2721
2722enum Option<T> {
2723  Some(value: T)
2724  None
2725}
2726"#;
2727
2728        let program = parse_source(source).expect("should parse");
2729        assert!(matches!(
2730            &program[0].node,
2731            Node::StructDecl { type_params, .. } if type_params.len() == 2
2732        ));
2733        assert!(matches!(
2734            &program[1].node,
2735            Node::EnumDecl { type_params, .. } if type_params.len() == 1
2736        ));
2737    }
2738
2739    #[test]
2740    fn parses_struct_literal_syntax_for_known_structs() {
2741        let source = r#"
2742struct Point {
2743  x: int
2744  y: int
2745}
2746
2747pipeline test(task) {
2748  let point = Point { x: 3, y: 4 }
2749}
2750"#;
2751
2752        let program = parse_source(source).expect("should parse");
2753        let pipeline = program
2754            .iter()
2755            .find(|node| matches!(node.node, Node::Pipeline { .. }))
2756            .expect("pipeline node");
2757        let body = match &pipeline.node {
2758            Node::Pipeline { body, .. } => body,
2759            _ => unreachable!(),
2760        };
2761        assert!(matches!(
2762            &body[0].node,
2763            Node::LetBinding { value, .. }
2764                if matches!(
2765                    value.node,
2766                    Node::StructConstruct { ref struct_name, ref fields }
2767                        if struct_name == "Point" && fields.len() == 2
2768                )
2769        ));
2770    }
2771
2772    #[test]
2773    fn parses_exponentiation_as_right_associative() {
2774        let mut lexer = Lexer::new("a ** b ** c");
2775        let tokens = lexer.tokenize().expect("tokens");
2776        let mut parser = Parser::new(tokens);
2777        let expr = parser.parse_single_expression().expect("expression");
2778
2779        assert!(matches!(
2780            expr.node,
2781            Node::BinaryOp { ref op, ref left, ref right }
2782                if op == "**"
2783                    && matches!(left.node, Node::Identifier(ref name) if name == "a")
2784                    && matches!(
2785                        right.node,
2786                        Node::BinaryOp { ref op, ref left, ref right }
2787                            if op == "**"
2788                                && matches!(left.node, Node::Identifier(ref name) if name == "b")
2789                                && matches!(right.node, Node::Identifier(ref name) if name == "c")
2790                    )
2791        ));
2792    }
2793
2794    #[test]
2795    fn parses_exponentiation_tighter_than_multiplication() {
2796        let mut lexer = Lexer::new("a * b ** c");
2797        let tokens = lexer.tokenize().expect("tokens");
2798        let mut parser = Parser::new(tokens);
2799        let expr = parser.parse_single_expression().expect("expression");
2800
2801        assert!(matches!(
2802            expr.node,
2803            Node::BinaryOp { ref op, ref left, ref right }
2804                if op == "*"
2805                    && matches!(left.node, Node::Identifier(ref name) if name == "a")
2806                    && matches!(
2807                        right.node,
2808                        Node::BinaryOp { ref op, ref left, ref right }
2809                            if op == "**"
2810                                && matches!(left.node, Node::Identifier(ref name) if name == "b")
2811                                && matches!(right.node, Node::Identifier(ref name) if name == "c")
2812                    )
2813        ));
2814    }
2815}