Skip to main content

harn_parser/
parser.rs

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