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