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    },
16}
17
18impl fmt::Display for ParserError {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        match self {
21            ParserError::Unexpected {
22                got,
23                expected,
24                span,
25            } => write!(
26                f,
27                "Expected {expected}, got {got} at {}:{}",
28                span.line, span.column
29            ),
30            ParserError::UnexpectedEof { expected } => {
31                write!(f, "Unexpected end of file, expected {expected}")
32            }
33        }
34    }
35}
36
37impl std::error::Error for ParserError {}
38
39/// Recursive descent parser for Harn.
40pub struct Parser {
41    tokens: Vec<Token>,
42    pos: usize,
43    errors: Vec<ParserError>,
44}
45
46impl Parser {
47    pub fn new(tokens: Vec<Token>) -> Self {
48        Self {
49            tokens,
50            pos: 0,
51            errors: Vec::new(),
52        }
53    }
54
55    fn current_span(&self) -> Span {
56        self.tokens
57            .get(self.pos)
58            .map(|t| t.span)
59            .unwrap_or(Span::dummy())
60    }
61
62    fn current_kind(&self) -> Option<&TokenKind> {
63        self.tokens.get(self.pos).map(|t| &t.kind)
64    }
65
66    fn prev_span(&self) -> Span {
67        if self.pos > 0 {
68            self.tokens[self.pos - 1].span
69        } else {
70            Span::dummy()
71        }
72    }
73
74    /// Parse a complete .harn file. Reports multiple errors via recovery.
75    pub fn parse(&mut self) -> Result<Vec<SNode>, ParserError> {
76        let mut nodes = Vec::new();
77        self.skip_newlines();
78
79        while !self.is_at_end() {
80            // Skip any stray closing braces at top level (after recovery)
81            if self.check(&TokenKind::RBrace) {
82                self.advance();
83                self.skip_newlines();
84                continue;
85            }
86
87            let result = if self.check(&TokenKind::Import) {
88                self.parse_import()
89            } else if self.check(&TokenKind::Pipeline) {
90                self.parse_pipeline()
91            } else {
92                self.parse_statement()
93            };
94
95            match result {
96                Ok(node) => nodes.push(node),
97                Err(err) => {
98                    self.errors.push(err);
99                    self.synchronize();
100                }
101            }
102            self.skip_newlines();
103        }
104
105        if let Some(first) = self.errors.first() {
106            return Err(first.clone());
107        }
108        Ok(nodes)
109    }
110
111    /// Return all accumulated parser errors (after `parse()` returns).
112    pub fn all_errors(&self) -> &[ParserError] {
113        &self.errors
114    }
115
116    /// Check if the current token is one that starts a statement.
117    fn is_statement_start(&self) -> bool {
118        matches!(
119            self.current_kind(),
120            Some(
121                TokenKind::Let
122                    | TokenKind::Var
123                    | TokenKind::If
124                    | TokenKind::For
125                    | TokenKind::While
126                    | TokenKind::Match
127                    | TokenKind::Retry
128                    | TokenKind::Return
129                    | TokenKind::Throw
130                    | TokenKind::Fn
131                    | TokenKind::Pub
132                    | TokenKind::Try
133                    | TokenKind::Pipeline
134                    | TokenKind::Import
135                    | TokenKind::Parallel
136                    | TokenKind::ParallelMap
137                    | TokenKind::Enum
138                    | TokenKind::Struct
139                    | TokenKind::Interface
140                    | TokenKind::Guard
141                    | TokenKind::Deadline
142                    | TokenKind::Yield
143                    | TokenKind::Mutex
144            )
145        )
146    }
147
148    /// Advance past tokens until we reach a likely statement boundary.
149    fn synchronize(&mut self) {
150        while !self.is_at_end() {
151            if self.check(&TokenKind::Newline) {
152                self.advance();
153                if self.is_at_end() || self.is_statement_start() {
154                    return;
155                }
156                continue;
157            }
158            if self.check(&TokenKind::RBrace) {
159                return;
160            }
161            self.advance();
162        }
163    }
164
165    /// Parse a single expression (for string interpolation).
166    pub fn parse_single_expression(&mut self) -> Result<SNode, ParserError> {
167        self.skip_newlines();
168        self.parse_expression()
169    }
170
171    // --- Declarations ---
172
173    fn parse_pipeline(&mut self) -> Result<SNode, ParserError> {
174        let start = self.current_span();
175        self.consume(&TokenKind::Pipeline, "pipeline")?;
176        let name = self.consume_identifier("pipeline name")?;
177
178        self.consume(&TokenKind::LParen, "(")?;
179        let params = self.parse_param_list()?;
180        self.consume(&TokenKind::RParen, ")")?;
181
182        let extends = if self.check(&TokenKind::Extends) {
183            self.advance();
184            Some(self.consume_identifier("parent pipeline name")?)
185        } else {
186            None
187        };
188
189        self.consume(&TokenKind::LBrace, "{")?;
190        let body = self.parse_block()?;
191        self.consume(&TokenKind::RBrace, "}")?;
192
193        Ok(spanned(
194            Node::Pipeline {
195                name,
196                params,
197                body,
198                extends,
199            },
200            Span::merge(start, self.prev_span()),
201        ))
202    }
203
204    fn parse_import(&mut self) -> Result<SNode, ParserError> {
205        let start = self.current_span();
206        self.consume(&TokenKind::Import, "import")?;
207
208        // Check for selective import: import { foo, bar } from "module"
209        if self.check(&TokenKind::LBrace) {
210            self.advance(); // skip {
211            let mut names = Vec::new();
212            while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
213                let name = self.consume_identifier("import name")?;
214                names.push(name);
215                if self.check(&TokenKind::Comma) {
216                    self.advance();
217                }
218            }
219            self.consume(&TokenKind::RBrace, "}")?;
220            self.consume(&TokenKind::From, "from")?;
221            if let Some(tok) = self.current() {
222                if let TokenKind::StringLiteral(path) = &tok.kind {
223                    let path = path.clone();
224                    self.advance();
225                    return Ok(spanned(
226                        Node::SelectiveImport { names, path },
227                        Span::merge(start, self.prev_span()),
228                    ));
229                }
230            }
231            return Err(self.error("import path string"));
232        }
233
234        if let Some(tok) = self.current() {
235            if let TokenKind::StringLiteral(path) = &tok.kind {
236                let path = path.clone();
237                self.advance();
238                return Ok(spanned(
239                    Node::ImportDecl { path },
240                    Span::merge(start, self.prev_span()),
241                ));
242            }
243        }
244        Err(self.error("import path string"))
245    }
246
247    // --- Statements ---
248
249    fn parse_block(&mut self) -> Result<Vec<SNode>, ParserError> {
250        let mut stmts = Vec::new();
251        self.skip_newlines();
252
253        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
254            stmts.push(self.parse_statement()?);
255            self.skip_newlines();
256        }
257        Ok(stmts)
258    }
259
260    fn parse_statement(&mut self) -> Result<SNode, ParserError> {
261        self.skip_newlines();
262
263        let tok = self.current().ok_or_else(|| ParserError::UnexpectedEof {
264            expected: "statement".into(),
265        })?;
266
267        match &tok.kind {
268            TokenKind::Let => self.parse_let_binding(),
269            TokenKind::Var => self.parse_var_binding(),
270            TokenKind::If => self.parse_if_else(),
271            TokenKind::For => self.parse_for_in(),
272            TokenKind::Match => self.parse_match(),
273            TokenKind::Retry => self.parse_retry(),
274            TokenKind::While => self.parse_while_loop(),
275            TokenKind::Parallel => self.parse_parallel(),
276            TokenKind::ParallelMap => self.parse_parallel_map(),
277            TokenKind::Return => self.parse_return(),
278            TokenKind::Throw => self.parse_throw(),
279            TokenKind::Override => self.parse_override(),
280            TokenKind::Try => self.parse_try_catch(),
281            TokenKind::Fn => self.parse_fn_decl_with_pub(false),
282            TokenKind::Pub => {
283                self.advance(); // consume 'pub'
284                let tok = self.current().ok_or_else(|| ParserError::UnexpectedEof {
285                    expected: "fn, struct, enum, or pipeline after pub".into(),
286                })?;
287                match &tok.kind {
288                    TokenKind::Fn => self.parse_fn_decl_with_pub(true),
289                    _ => Err(self.error("fn, struct, enum, or pipeline after pub")),
290                }
291            }
292            TokenKind::TypeKw => self.parse_type_decl(),
293            TokenKind::Enum => self.parse_enum_decl(),
294            TokenKind::Struct => self.parse_struct_decl(),
295            TokenKind::Interface => self.parse_interface_decl(),
296            TokenKind::Guard => self.parse_guard(),
297            TokenKind::Deadline => self.parse_deadline(),
298            TokenKind::Yield => self.parse_yield(),
299            TokenKind::Mutex => self.parse_mutex(),
300            TokenKind::Break => {
301                let span = self.current_span();
302                self.advance();
303                Ok(spanned(Node::BreakStmt, span))
304            }
305            TokenKind::Continue => {
306                let span = self.current_span();
307                self.advance();
308                Ok(spanned(Node::ContinueStmt, span))
309            }
310            _ => self.parse_expression_statement(),
311        }
312    }
313
314    fn parse_let_binding(&mut self) -> Result<SNode, ParserError> {
315        let start = self.current_span();
316        self.consume(&TokenKind::Let, "let")?;
317        let name = self.consume_identifier("variable name")?;
318        let type_ann = self.try_parse_type_annotation()?;
319        self.consume(&TokenKind::Assign, "=")?;
320        let value = self.parse_expression()?;
321        Ok(spanned(
322            Node::LetBinding {
323                name,
324                type_ann,
325                value: Box::new(value),
326            },
327            Span::merge(start, self.prev_span()),
328        ))
329    }
330
331    fn parse_var_binding(&mut self) -> Result<SNode, ParserError> {
332        let start = self.current_span();
333        self.consume(&TokenKind::Var, "var")?;
334        let name = self.consume_identifier("variable name")?;
335        let type_ann = self.try_parse_type_annotation()?;
336        self.consume(&TokenKind::Assign, "=")?;
337        let value = self.parse_expression()?;
338        Ok(spanned(
339            Node::VarBinding {
340                name,
341                type_ann,
342                value: Box::new(value),
343            },
344            Span::merge(start, self.prev_span()),
345        ))
346    }
347
348    fn parse_if_else(&mut self) -> Result<SNode, ParserError> {
349        let start = self.current_span();
350        self.consume(&TokenKind::If, "if")?;
351        let condition = self.parse_expression()?;
352        self.consume(&TokenKind::LBrace, "{")?;
353        let then_body = self.parse_block()?;
354        self.consume(&TokenKind::RBrace, "}")?;
355        self.skip_newlines();
356
357        let else_body = if self.check(&TokenKind::Else) {
358            self.advance();
359            if self.check(&TokenKind::If) {
360                Some(vec![self.parse_if_else()?])
361            } else {
362                self.consume(&TokenKind::LBrace, "{")?;
363                let body = self.parse_block()?;
364                self.consume(&TokenKind::RBrace, "}")?;
365                Some(body)
366            }
367        } else {
368            None
369        };
370
371        Ok(spanned(
372            Node::IfElse {
373                condition: Box::new(condition),
374                then_body,
375                else_body,
376            },
377            Span::merge(start, self.prev_span()),
378        ))
379    }
380
381    fn parse_for_in(&mut self) -> Result<SNode, ParserError> {
382        let start = self.current_span();
383        self.consume(&TokenKind::For, "for")?;
384        let variable = self.consume_identifier("loop variable")?;
385        self.consume(&TokenKind::In, "in")?;
386        let iterable = self.parse_expression()?;
387        self.consume(&TokenKind::LBrace, "{")?;
388        let body = self.parse_block()?;
389        self.consume(&TokenKind::RBrace, "}")?;
390        Ok(spanned(
391            Node::ForIn {
392                variable,
393                iterable: Box::new(iterable),
394                body,
395            },
396            Span::merge(start, self.prev_span()),
397        ))
398    }
399
400    fn parse_match(&mut self) -> Result<SNode, ParserError> {
401        let start = self.current_span();
402        self.consume(&TokenKind::Match, "match")?;
403        let value = self.parse_expression()?;
404        self.consume(&TokenKind::LBrace, "{")?;
405        self.skip_newlines();
406
407        let mut arms = Vec::new();
408        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
409            let pattern = self.parse_expression()?;
410            self.consume(&TokenKind::Arrow, "->")?;
411            self.consume(&TokenKind::LBrace, "{")?;
412            let body = self.parse_block()?;
413            self.consume(&TokenKind::RBrace, "}")?;
414            arms.push(MatchArm { pattern, body });
415            self.skip_newlines();
416        }
417
418        self.consume(&TokenKind::RBrace, "}")?;
419        Ok(spanned(
420            Node::MatchExpr {
421                value: Box::new(value),
422                arms,
423            },
424            Span::merge(start, self.prev_span()),
425        ))
426    }
427
428    fn parse_while_loop(&mut self) -> Result<SNode, ParserError> {
429        let start = self.current_span();
430        self.consume(&TokenKind::While, "while")?;
431        let condition = if self.check(&TokenKind::LParen) {
432            self.advance();
433            let c = self.parse_expression()?;
434            self.consume(&TokenKind::RParen, ")")?;
435            c
436        } else {
437            self.parse_expression()?
438        };
439        self.consume(&TokenKind::LBrace, "{")?;
440        let body = self.parse_block()?;
441        self.consume(&TokenKind::RBrace, "}")?;
442        Ok(spanned(
443            Node::WhileLoop {
444                condition: Box::new(condition),
445                body,
446            },
447            Span::merge(start, self.prev_span()),
448        ))
449    }
450
451    fn parse_retry(&mut self) -> Result<SNode, ParserError> {
452        let start = self.current_span();
453        self.consume(&TokenKind::Retry, "retry")?;
454        let count = if self.check(&TokenKind::LParen) {
455            self.advance();
456            let c = self.parse_expression()?;
457            self.consume(&TokenKind::RParen, ")")?;
458            c
459        } else {
460            self.parse_primary()?
461        };
462        self.consume(&TokenKind::LBrace, "{")?;
463        let body = self.parse_block()?;
464        self.consume(&TokenKind::RBrace, "}")?;
465        Ok(spanned(
466            Node::Retry {
467                count: Box::new(count),
468                body,
469            },
470            Span::merge(start, self.prev_span()),
471        ))
472    }
473
474    fn parse_parallel(&mut self) -> Result<SNode, ParserError> {
475        let start = self.current_span();
476        self.consume(&TokenKind::Parallel, "parallel")?;
477        self.consume(&TokenKind::LParen, "(")?;
478        let count = self.parse_expression()?;
479        self.consume(&TokenKind::RParen, ")")?;
480        self.consume(&TokenKind::LBrace, "{")?;
481
482        // Optional closure parameter: { i ->
483        let mut variable = None;
484        self.skip_newlines();
485        if let Some(tok) = self.current() {
486            if let TokenKind::Identifier(name) = &tok.kind {
487                if self.peek_kind() == Some(&TokenKind::Arrow) {
488                    let name = name.clone();
489                    self.advance(); // skip identifier
490                    self.advance(); // skip ->
491                    variable = Some(name);
492                }
493            }
494        }
495
496        let body = self.parse_block()?;
497        self.consume(&TokenKind::RBrace, "}")?;
498        Ok(spanned(
499            Node::Parallel {
500                count: Box::new(count),
501                variable,
502                body,
503            },
504            Span::merge(start, self.prev_span()),
505        ))
506    }
507
508    fn parse_parallel_map(&mut self) -> Result<SNode, ParserError> {
509        let start = self.current_span();
510        self.consume(&TokenKind::ParallelMap, "parallel_map")?;
511        self.consume(&TokenKind::LParen, "(")?;
512        let list = self.parse_expression()?;
513        self.consume(&TokenKind::RParen, ")")?;
514        self.consume(&TokenKind::LBrace, "{")?;
515
516        self.skip_newlines();
517        let variable = self.consume_identifier("map variable")?;
518        self.consume(&TokenKind::Arrow, "->")?;
519
520        let body = self.parse_block()?;
521        self.consume(&TokenKind::RBrace, "}")?;
522        Ok(spanned(
523            Node::ParallelMap {
524                list: Box::new(list),
525                variable,
526                body,
527            },
528            Span::merge(start, self.prev_span()),
529        ))
530    }
531
532    fn parse_return(&mut self) -> Result<SNode, ParserError> {
533        let start = self.current_span();
534        self.consume(&TokenKind::Return, "return")?;
535        if self.is_at_end() || self.check(&TokenKind::Newline) || self.check(&TokenKind::RBrace) {
536            return Ok(spanned(
537                Node::ReturnStmt { value: None },
538                Span::merge(start, self.prev_span()),
539            ));
540        }
541        let value = self.parse_expression()?;
542        Ok(spanned(
543            Node::ReturnStmt {
544                value: Some(Box::new(value)),
545            },
546            Span::merge(start, self.prev_span()),
547        ))
548    }
549
550    fn parse_throw(&mut self) -> Result<SNode, ParserError> {
551        let start = self.current_span();
552        self.consume(&TokenKind::Throw, "throw")?;
553        let value = self.parse_expression()?;
554        Ok(spanned(
555            Node::ThrowStmt {
556                value: Box::new(value),
557            },
558            Span::merge(start, self.prev_span()),
559        ))
560    }
561
562    fn parse_override(&mut self) -> Result<SNode, ParserError> {
563        let start = self.current_span();
564        self.consume(&TokenKind::Override, "override")?;
565        let name = self.consume_identifier("override name")?;
566        self.consume(&TokenKind::LParen, "(")?;
567        let params = self.parse_param_list()?;
568        self.consume(&TokenKind::RParen, ")")?;
569        self.consume(&TokenKind::LBrace, "{")?;
570        let body = self.parse_block()?;
571        self.consume(&TokenKind::RBrace, "}")?;
572        Ok(spanned(
573            Node::OverrideDecl { name, params, body },
574            Span::merge(start, self.prev_span()),
575        ))
576    }
577
578    fn parse_try_catch(&mut self) -> Result<SNode, ParserError> {
579        let start = self.current_span();
580        self.consume(&TokenKind::Try, "try")?;
581        self.consume(&TokenKind::LBrace, "{")?;
582        let body = self.parse_block()?;
583        self.consume(&TokenKind::RBrace, "}")?;
584        self.skip_newlines();
585        self.consume(&TokenKind::Catch, "catch")?;
586
587        let (error_var, error_type) = if self.check(&TokenKind::LParen) {
588            self.advance();
589            let name = self.consume_identifier("error variable")?;
590            let ty = self.try_parse_type_annotation()?;
591            self.consume(&TokenKind::RParen, ")")?;
592            (Some(name), ty)
593        } else {
594            (None, None)
595        };
596
597        self.consume(&TokenKind::LBrace, "{")?;
598        let catch_body = self.parse_block()?;
599        self.consume(&TokenKind::RBrace, "}")?;
600        Ok(spanned(
601            Node::TryCatch {
602                body,
603                error_var,
604                error_type,
605                catch_body,
606            },
607            Span::merge(start, self.prev_span()),
608        ))
609    }
610
611    fn parse_fn_decl_with_pub(&mut self, is_pub: bool) -> Result<SNode, ParserError> {
612        let start = self.current_span();
613        self.consume(&TokenKind::Fn, "fn")?;
614        let name = self.consume_identifier("function name")?;
615        self.consume(&TokenKind::LParen, "(")?;
616        let params = self.parse_typed_param_list()?;
617        self.consume(&TokenKind::RParen, ")")?;
618        // Optional return type: -> type
619        let return_type = if self.check(&TokenKind::Arrow) {
620            self.advance();
621            Some(self.parse_type_expr()?)
622        } else {
623            None
624        };
625        self.consume(&TokenKind::LBrace, "{")?;
626        let body = self.parse_block()?;
627        self.consume(&TokenKind::RBrace, "}")?;
628        Ok(spanned(
629            Node::FnDecl {
630                name,
631                params,
632                return_type,
633                body,
634                is_pub,
635            },
636            Span::merge(start, self.prev_span()),
637        ))
638    }
639
640    fn parse_type_decl(&mut self) -> Result<SNode, ParserError> {
641        let start = self.current_span();
642        self.consume(&TokenKind::TypeKw, "type")?;
643        let name = self.consume_identifier("type name")?;
644        self.consume(&TokenKind::Assign, "=")?;
645        let type_expr = self.parse_type_expr()?;
646        Ok(spanned(
647            Node::TypeDecl { name, type_expr },
648            Span::merge(start, self.prev_span()),
649        ))
650    }
651
652    fn parse_enum_decl(&mut self) -> Result<SNode, ParserError> {
653        let start = self.current_span();
654        self.consume(&TokenKind::Enum, "enum")?;
655        let name = self.consume_identifier("enum name")?;
656        self.consume(&TokenKind::LBrace, "{")?;
657        self.skip_newlines();
658
659        let mut variants = Vec::new();
660        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
661            let variant_name = self.consume_identifier("variant name")?;
662            let fields = if self.check(&TokenKind::LParen) {
663                self.advance();
664                let params = self.parse_typed_param_list()?;
665                self.consume(&TokenKind::RParen, ")")?;
666                params
667            } else {
668                Vec::new()
669            };
670            variants.push(EnumVariant {
671                name: variant_name,
672                fields,
673            });
674            self.skip_newlines();
675            if self.check(&TokenKind::Comma) {
676                self.advance();
677                self.skip_newlines();
678            }
679        }
680
681        self.consume(&TokenKind::RBrace, "}")?;
682        Ok(spanned(
683            Node::EnumDecl { name, variants },
684            Span::merge(start, self.prev_span()),
685        ))
686    }
687
688    fn parse_struct_decl(&mut self) -> Result<SNode, ParserError> {
689        let start = self.current_span();
690        self.consume(&TokenKind::Struct, "struct")?;
691        let name = self.consume_identifier("struct name")?;
692        self.consume(&TokenKind::LBrace, "{")?;
693        self.skip_newlines();
694
695        let mut fields = Vec::new();
696        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
697            let field_name = self.consume_identifier("field name")?;
698            let optional = if self.check(&TokenKind::Question) {
699                self.advance();
700                true
701            } else {
702                false
703            };
704            let type_expr = self.try_parse_type_annotation()?;
705            fields.push(StructField {
706                name: field_name,
707                type_expr,
708                optional,
709            });
710            self.skip_newlines();
711            if self.check(&TokenKind::Comma) {
712                self.advance();
713                self.skip_newlines();
714            }
715        }
716
717        self.consume(&TokenKind::RBrace, "}")?;
718        Ok(spanned(
719            Node::StructDecl { name, fields },
720            Span::merge(start, self.prev_span()),
721        ))
722    }
723
724    fn parse_interface_decl(&mut self) -> Result<SNode, ParserError> {
725        let start = self.current_span();
726        self.consume(&TokenKind::Interface, "interface")?;
727        let name = self.consume_identifier("interface name")?;
728        self.consume(&TokenKind::LBrace, "{")?;
729        self.skip_newlines();
730
731        let mut methods = Vec::new();
732        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
733            self.consume(&TokenKind::Fn, "fn")?;
734            let method_name = self.consume_identifier("method name")?;
735            self.consume(&TokenKind::LParen, "(")?;
736            let params = self.parse_typed_param_list()?;
737            self.consume(&TokenKind::RParen, ")")?;
738            // Optional return type: -> type
739            let return_type = if self.check(&TokenKind::Arrow) {
740                self.advance();
741                Some(self.parse_type_expr()?)
742            } else {
743                None
744            };
745            methods.push(InterfaceMethod {
746                name: method_name,
747                params,
748                return_type,
749            });
750            self.skip_newlines();
751        }
752
753        self.consume(&TokenKind::RBrace, "}")?;
754        Ok(spanned(
755            Node::InterfaceDecl { name, methods },
756            Span::merge(start, self.prev_span()),
757        ))
758    }
759
760    fn parse_guard(&mut self) -> Result<SNode, ParserError> {
761        let start = self.current_span();
762        self.consume(&TokenKind::Guard, "guard")?;
763        let condition = self.parse_expression()?;
764        // Consume "else" — we reuse the Else keyword
765        self.consume(&TokenKind::Else, "else")?;
766        self.consume(&TokenKind::LBrace, "{")?;
767        let else_body = self.parse_block()?;
768        self.consume(&TokenKind::RBrace, "}")?;
769        Ok(spanned(
770            Node::GuardStmt {
771                condition: Box::new(condition),
772                else_body,
773            },
774            Span::merge(start, self.prev_span()),
775        ))
776    }
777
778    fn parse_deadline(&mut self) -> Result<SNode, ParserError> {
779        let start = self.current_span();
780        self.consume(&TokenKind::Deadline, "deadline")?;
781        let duration = self.parse_primary()?;
782        self.consume(&TokenKind::LBrace, "{")?;
783        let body = self.parse_block()?;
784        self.consume(&TokenKind::RBrace, "}")?;
785        Ok(spanned(
786            Node::DeadlineBlock {
787                duration: Box::new(duration),
788                body,
789            },
790            Span::merge(start, self.prev_span()),
791        ))
792    }
793
794    fn parse_yield(&mut self) -> Result<SNode, ParserError> {
795        let start = self.current_span();
796        self.consume(&TokenKind::Yield, "yield")?;
797        if self.is_at_end() || self.check(&TokenKind::Newline) || self.check(&TokenKind::RBrace) {
798            return Ok(spanned(
799                Node::YieldExpr { value: None },
800                Span::merge(start, self.prev_span()),
801            ));
802        }
803        let value = self.parse_expression()?;
804        Ok(spanned(
805            Node::YieldExpr {
806                value: Some(Box::new(value)),
807            },
808            Span::merge(start, self.prev_span()),
809        ))
810    }
811
812    fn parse_mutex(&mut self) -> Result<SNode, ParserError> {
813        let start = self.current_span();
814        self.consume(&TokenKind::Mutex, "mutex")?;
815        self.consume(&TokenKind::LBrace, "{")?;
816        let body = self.parse_block()?;
817        self.consume(&TokenKind::RBrace, "}")?;
818        Ok(spanned(
819            Node::MutexBlock { body },
820            Span::merge(start, self.prev_span()),
821        ))
822    }
823
824    fn parse_ask_expr(&mut self) -> Result<SNode, ParserError> {
825        let start = self.current_span();
826        self.consume(&TokenKind::Ask, "ask")?;
827        self.consume(&TokenKind::LBrace, "{")?;
828        // Parse as dict entries
829        let mut entries = Vec::new();
830        self.skip_newlines();
831        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
832            let key_span = self.current_span();
833            let name = self.consume_identifier("ask field")?;
834            let key = spanned(Node::StringLiteral(name), key_span);
835            self.consume(&TokenKind::Colon, ":")?;
836            let value = self.parse_expression()?;
837            entries.push(DictEntry { key, value });
838            self.skip_newlines();
839            if self.check(&TokenKind::Comma) {
840                self.advance();
841                self.skip_newlines();
842            }
843        }
844        self.consume(&TokenKind::RBrace, "}")?;
845        Ok(spanned(
846            Node::AskExpr { fields: entries },
847            Span::merge(start, self.prev_span()),
848        ))
849    }
850
851    // --- Expressions (precedence climbing) ---
852
853    fn parse_expression_statement(&mut self) -> Result<SNode, ParserError> {
854        let start = self.current_span();
855        let expr = self.parse_expression()?;
856
857        // Check for assignment or compound assignment on valid targets:
858        // identifier, property access (obj.field), subscript access (obj[key])
859        let is_assignable = matches!(
860            expr.node,
861            Node::Identifier(_) | Node::PropertyAccess { .. } | Node::SubscriptAccess { .. }
862        );
863        if is_assignable {
864            if self.check(&TokenKind::Assign) {
865                self.advance();
866                let value = self.parse_expression()?;
867                return Ok(spanned(
868                    Node::Assignment {
869                        target: Box::new(expr),
870                        value: Box::new(value),
871                        op: None,
872                    },
873                    Span::merge(start, self.prev_span()),
874                ));
875            }
876            let compound_op = if self.check(&TokenKind::PlusAssign) {
877                Some("+")
878            } else if self.check(&TokenKind::MinusAssign) {
879                Some("-")
880            } else if self.check(&TokenKind::StarAssign) {
881                Some("*")
882            } else if self.check(&TokenKind::SlashAssign) {
883                Some("/")
884            } else if self.check(&TokenKind::PercentAssign) {
885                Some("%")
886            } else {
887                None
888            };
889            if let Some(op) = compound_op {
890                self.advance();
891                let value = self.parse_expression()?;
892                return Ok(spanned(
893                    Node::Assignment {
894                        target: Box::new(expr),
895                        value: Box::new(value),
896                        op: Some(op.into()),
897                    },
898                    Span::merge(start, self.prev_span()),
899                ));
900            }
901        }
902
903        Ok(expr)
904    }
905
906    fn parse_expression(&mut self) -> Result<SNode, ParserError> {
907        self.skip_newlines();
908        self.parse_pipe()
909    }
910
911    fn parse_pipe(&mut self) -> Result<SNode, ParserError> {
912        let mut left = self.parse_range()?;
913        while self.check(&TokenKind::Pipe) {
914            let start = left.span;
915            self.advance();
916            let right = self.parse_range()?;
917            left = spanned(
918                Node::BinaryOp {
919                    op: "|>".into(),
920                    left: Box::new(left),
921                    right: Box::new(right),
922                },
923                Span::merge(start, self.prev_span()),
924            );
925        }
926        Ok(left)
927    }
928
929    fn parse_range(&mut self) -> Result<SNode, ParserError> {
930        let left = self.parse_ternary()?;
931        if self.check(&TokenKind::Thru) {
932            let start = left.span;
933            self.advance();
934            let right = self.parse_ternary()?;
935            return Ok(spanned(
936                Node::RangeExpr {
937                    start: Box::new(left),
938                    end: Box::new(right),
939                    inclusive: true,
940                },
941                Span::merge(start, self.prev_span()),
942            ));
943        }
944        if self.check(&TokenKind::Upto) {
945            let start = left.span;
946            self.advance();
947            let right = self.parse_ternary()?;
948            return Ok(spanned(
949                Node::RangeExpr {
950                    start: Box::new(left),
951                    end: Box::new(right),
952                    inclusive: false,
953                },
954                Span::merge(start, self.prev_span()),
955            ));
956        }
957        Ok(left)
958    }
959
960    fn parse_ternary(&mut self) -> Result<SNode, ParserError> {
961        let condition = self.parse_nil_coalescing()?;
962        if !self.check(&TokenKind::Question) {
963            return Ok(condition);
964        }
965        let start = condition.span;
966        self.advance(); // skip ?
967        let true_val = self.parse_nil_coalescing()?;
968        self.consume(&TokenKind::Colon, ":")?;
969        let false_val = self.parse_nil_coalescing()?;
970        Ok(spanned(
971            Node::Ternary {
972                condition: Box::new(condition),
973                true_expr: Box::new(true_val),
974                false_expr: Box::new(false_val),
975            },
976            Span::merge(start, self.prev_span()),
977        ))
978    }
979
980    fn parse_nil_coalescing(&mut self) -> Result<SNode, ParserError> {
981        let mut left = self.parse_logical_or()?;
982        while self.check(&TokenKind::NilCoal) {
983            let start = left.span;
984            self.advance();
985            let right = self.parse_logical_or()?;
986            left = spanned(
987                Node::BinaryOp {
988                    op: "??".into(),
989                    left: Box::new(left),
990                    right: Box::new(right),
991                },
992                Span::merge(start, self.prev_span()),
993            );
994        }
995        Ok(left)
996    }
997
998    fn parse_logical_or(&mut self) -> Result<SNode, ParserError> {
999        let mut left = self.parse_logical_and()?;
1000        while self.check(&TokenKind::Or) {
1001            let start = left.span;
1002            self.advance();
1003            let right = self.parse_logical_and()?;
1004            left = spanned(
1005                Node::BinaryOp {
1006                    op: "||".into(),
1007                    left: Box::new(left),
1008                    right: Box::new(right),
1009                },
1010                Span::merge(start, self.prev_span()),
1011            );
1012        }
1013        Ok(left)
1014    }
1015
1016    fn parse_logical_and(&mut self) -> Result<SNode, ParserError> {
1017        let mut left = self.parse_equality()?;
1018        while self.check(&TokenKind::And) {
1019            let start = left.span;
1020            self.advance();
1021            let right = self.parse_equality()?;
1022            left = spanned(
1023                Node::BinaryOp {
1024                    op: "&&".into(),
1025                    left: Box::new(left),
1026                    right: Box::new(right),
1027                },
1028                Span::merge(start, self.prev_span()),
1029            );
1030        }
1031        Ok(left)
1032    }
1033
1034    fn parse_equality(&mut self) -> Result<SNode, ParserError> {
1035        let mut left = self.parse_comparison()?;
1036        while self.check(&TokenKind::Eq) || self.check(&TokenKind::Neq) {
1037            let start = left.span;
1038            let op = if self.check(&TokenKind::Eq) {
1039                "=="
1040            } else {
1041                "!="
1042            };
1043            self.advance();
1044            let right = self.parse_comparison()?;
1045            left = spanned(
1046                Node::BinaryOp {
1047                    op: op.into(),
1048                    left: Box::new(left),
1049                    right: Box::new(right),
1050                },
1051                Span::merge(start, self.prev_span()),
1052            );
1053        }
1054        Ok(left)
1055    }
1056
1057    fn parse_comparison(&mut self) -> Result<SNode, ParserError> {
1058        let mut left = self.parse_additive()?;
1059        while self.check(&TokenKind::Lt)
1060            || self.check(&TokenKind::Gt)
1061            || self.check(&TokenKind::Lte)
1062            || self.check(&TokenKind::Gte)
1063        {
1064            let start = left.span;
1065            let op = match self.current().map(|t| &t.kind) {
1066                Some(TokenKind::Lt) => "<",
1067                Some(TokenKind::Gt) => ">",
1068                Some(TokenKind::Lte) => "<=",
1069                Some(TokenKind::Gte) => ">=",
1070                _ => "<",
1071            };
1072            self.advance();
1073            let right = self.parse_additive()?;
1074            left = spanned(
1075                Node::BinaryOp {
1076                    op: op.into(),
1077                    left: Box::new(left),
1078                    right: Box::new(right),
1079                },
1080                Span::merge(start, self.prev_span()),
1081            );
1082        }
1083        Ok(left)
1084    }
1085
1086    fn parse_additive(&mut self) -> Result<SNode, ParserError> {
1087        let mut left = self.parse_multiplicative()?;
1088        while self.check(&TokenKind::Plus) || self.check(&TokenKind::Minus) {
1089            let start = left.span;
1090            let op = if self.check(&TokenKind::Plus) {
1091                "+"
1092            } else {
1093                "-"
1094            };
1095            self.advance();
1096            let right = self.parse_multiplicative()?;
1097            left = spanned(
1098                Node::BinaryOp {
1099                    op: op.into(),
1100                    left: Box::new(left),
1101                    right: Box::new(right),
1102                },
1103                Span::merge(start, self.prev_span()),
1104            );
1105        }
1106        Ok(left)
1107    }
1108
1109    fn parse_multiplicative(&mut self) -> Result<SNode, ParserError> {
1110        let mut left = self.parse_unary()?;
1111        while self.check(&TokenKind::Star)
1112            || self.check(&TokenKind::Slash)
1113            || self.check(&TokenKind::Percent)
1114        {
1115            let start = left.span;
1116            let op = if self.check(&TokenKind::Star) {
1117                "*"
1118            } else if self.check(&TokenKind::Slash) {
1119                "/"
1120            } else {
1121                "%"
1122            };
1123            self.advance();
1124            let right = self.parse_unary()?;
1125            left = spanned(
1126                Node::BinaryOp {
1127                    op: op.into(),
1128                    left: Box::new(left),
1129                    right: Box::new(right),
1130                },
1131                Span::merge(start, self.prev_span()),
1132            );
1133        }
1134        Ok(left)
1135    }
1136
1137    fn parse_unary(&mut self) -> Result<SNode, ParserError> {
1138        if self.check(&TokenKind::Not) {
1139            let start = self.current_span();
1140            self.advance();
1141            let operand = self.parse_unary()?;
1142            return Ok(spanned(
1143                Node::UnaryOp {
1144                    op: "!".into(),
1145                    operand: Box::new(operand),
1146                },
1147                Span::merge(start, self.prev_span()),
1148            ));
1149        }
1150        if self.check(&TokenKind::Minus) {
1151            let start = self.current_span();
1152            self.advance();
1153            let operand = self.parse_unary()?;
1154            return Ok(spanned(
1155                Node::UnaryOp {
1156                    op: "-".into(),
1157                    operand: Box::new(operand),
1158                },
1159                Span::merge(start, self.prev_span()),
1160            ));
1161        }
1162        self.parse_postfix()
1163    }
1164
1165    fn parse_postfix(&mut self) -> Result<SNode, ParserError> {
1166        let mut expr = self.parse_primary()?;
1167
1168        loop {
1169            if self.check(&TokenKind::Dot) {
1170                let start = expr.span;
1171                self.advance();
1172                let member = self.consume_identifier_or_keyword("member name")?;
1173                if self.check(&TokenKind::LParen) {
1174                    self.advance();
1175                    let args = self.parse_arg_list()?;
1176                    self.consume(&TokenKind::RParen, ")")?;
1177                    expr = spanned(
1178                        Node::MethodCall {
1179                            object: Box::new(expr),
1180                            method: member,
1181                            args,
1182                        },
1183                        Span::merge(start, self.prev_span()),
1184                    );
1185                } else {
1186                    expr = spanned(
1187                        Node::PropertyAccess {
1188                            object: Box::new(expr),
1189                            property: member,
1190                        },
1191                        Span::merge(start, self.prev_span()),
1192                    );
1193                }
1194            } else if self.check(&TokenKind::LBracket) {
1195                let start = expr.span;
1196                self.advance();
1197                let index = self.parse_expression()?;
1198                self.consume(&TokenKind::RBracket, "]")?;
1199                expr = spanned(
1200                    Node::SubscriptAccess {
1201                        object: Box::new(expr),
1202                        index: Box::new(index),
1203                    },
1204                    Span::merge(start, self.prev_span()),
1205                );
1206            } else if self.check(&TokenKind::LParen) && matches!(expr.node, Node::Identifier(_)) {
1207                let start = expr.span;
1208                self.advance();
1209                let args = self.parse_arg_list()?;
1210                self.consume(&TokenKind::RParen, ")")?;
1211                if let Node::Identifier(name) = expr.node {
1212                    expr = spanned(
1213                        Node::FunctionCall { name, args },
1214                        Span::merge(start, self.prev_span()),
1215                    );
1216                }
1217            } else {
1218                break;
1219            }
1220        }
1221
1222        Ok(expr)
1223    }
1224
1225    fn parse_primary(&mut self) -> Result<SNode, ParserError> {
1226        let tok = self.current().ok_or_else(|| ParserError::UnexpectedEof {
1227            expected: "expression".into(),
1228        })?;
1229        let start = self.current_span();
1230
1231        match &tok.kind {
1232            TokenKind::StringLiteral(s) => {
1233                let s = s.clone();
1234                self.advance();
1235                Ok(spanned(
1236                    Node::StringLiteral(s),
1237                    Span::merge(start, self.prev_span()),
1238                ))
1239            }
1240            TokenKind::InterpolatedString(segments) => {
1241                let segments = segments.clone();
1242                self.advance();
1243                Ok(spanned(
1244                    Node::InterpolatedString(segments),
1245                    Span::merge(start, self.prev_span()),
1246                ))
1247            }
1248            TokenKind::IntLiteral(n) => {
1249                let n = *n;
1250                self.advance();
1251                Ok(spanned(
1252                    Node::IntLiteral(n),
1253                    Span::merge(start, self.prev_span()),
1254                ))
1255            }
1256            TokenKind::FloatLiteral(n) => {
1257                let n = *n;
1258                self.advance();
1259                Ok(spanned(
1260                    Node::FloatLiteral(n),
1261                    Span::merge(start, self.prev_span()),
1262                ))
1263            }
1264            TokenKind::True => {
1265                self.advance();
1266                Ok(spanned(
1267                    Node::BoolLiteral(true),
1268                    Span::merge(start, self.prev_span()),
1269                ))
1270            }
1271            TokenKind::False => {
1272                self.advance();
1273                Ok(spanned(
1274                    Node::BoolLiteral(false),
1275                    Span::merge(start, self.prev_span()),
1276                ))
1277            }
1278            TokenKind::Nil => {
1279                self.advance();
1280                Ok(spanned(
1281                    Node::NilLiteral,
1282                    Span::merge(start, self.prev_span()),
1283                ))
1284            }
1285            TokenKind::Identifier(name) => {
1286                let name = name.clone();
1287                self.advance();
1288                Ok(spanned(
1289                    Node::Identifier(name),
1290                    Span::merge(start, self.prev_span()),
1291                ))
1292            }
1293            TokenKind::LParen => {
1294                self.advance();
1295                let expr = self.parse_expression()?;
1296                self.consume(&TokenKind::RParen, ")")?;
1297                Ok(expr)
1298            }
1299            TokenKind::LBracket => self.parse_list_literal(),
1300            TokenKind::LBrace => self.parse_dict_or_closure(),
1301            TokenKind::Parallel => self.parse_parallel(),
1302            TokenKind::ParallelMap => self.parse_parallel_map(),
1303            TokenKind::Retry => self.parse_retry(),
1304            TokenKind::If => self.parse_if_else(),
1305            TokenKind::Spawn => self.parse_spawn_expr(),
1306            TokenKind::DurationLiteral(ms) => {
1307                let ms = *ms;
1308                self.advance();
1309                Ok(spanned(
1310                    Node::DurationLiteral(ms),
1311                    Span::merge(start, self.prev_span()),
1312                ))
1313            }
1314            TokenKind::Ask => self.parse_ask_expr(),
1315            TokenKind::Deadline => self.parse_deadline(),
1316            _ => Err(self.error("expression")),
1317        }
1318    }
1319
1320    fn parse_spawn_expr(&mut self) -> Result<SNode, ParserError> {
1321        let start = self.current_span();
1322        self.consume(&TokenKind::Spawn, "spawn")?;
1323        self.consume(&TokenKind::LBrace, "{")?;
1324        let body = self.parse_block()?;
1325        self.consume(&TokenKind::RBrace, "}")?;
1326        Ok(spanned(
1327            Node::SpawnExpr { body },
1328            Span::merge(start, self.prev_span()),
1329        ))
1330    }
1331
1332    fn parse_list_literal(&mut self) -> Result<SNode, ParserError> {
1333        let start = self.current_span();
1334        self.consume(&TokenKind::LBracket, "[")?;
1335        let mut elements = Vec::new();
1336        self.skip_newlines();
1337
1338        while !self.is_at_end() && !self.check(&TokenKind::RBracket) {
1339            elements.push(self.parse_expression()?);
1340            self.skip_newlines();
1341            if self.check(&TokenKind::Comma) {
1342                self.advance();
1343                self.skip_newlines();
1344            }
1345        }
1346
1347        self.consume(&TokenKind::RBracket, "]")?;
1348        Ok(spanned(
1349            Node::ListLiteral(elements),
1350            Span::merge(start, self.prev_span()),
1351        ))
1352    }
1353
1354    fn parse_dict_or_closure(&mut self) -> Result<SNode, ParserError> {
1355        let start = self.current_span();
1356        self.consume(&TokenKind::LBrace, "{")?;
1357        self.skip_newlines();
1358
1359        // Empty dict
1360        if self.check(&TokenKind::RBrace) {
1361            self.advance();
1362            return Ok(spanned(
1363                Node::DictLiteral(Vec::new()),
1364                Span::merge(start, self.prev_span()),
1365            ));
1366        }
1367
1368        // Lookahead: scan for -> before } to disambiguate closure from dict.
1369        let saved = self.pos;
1370        if self.is_closure_lookahead() {
1371            self.pos = saved;
1372            return self.parse_closure_body(start);
1373        }
1374        self.pos = saved;
1375        self.parse_dict_literal(start)
1376    }
1377
1378    /// Scan forward to determine if this is a closure (has -> before matching }).
1379    /// Does not consume tokens (caller saves/restores pos).
1380    fn is_closure_lookahead(&mut self) -> bool {
1381        let mut depth = 0;
1382        while !self.is_at_end() {
1383            if let Some(tok) = self.current() {
1384                match &tok.kind {
1385                    TokenKind::Arrow if depth == 0 => return true,
1386                    TokenKind::LBrace | TokenKind::LParen | TokenKind::LBracket => depth += 1,
1387                    TokenKind::RBrace if depth == 0 => return false,
1388                    TokenKind::RBrace => depth -= 1,
1389                    TokenKind::RParen | TokenKind::RBracket => {
1390                        if depth > 0 {
1391                            depth -= 1;
1392                        }
1393                    }
1394                    _ => {}
1395                }
1396                self.advance();
1397            } else {
1398                return false;
1399            }
1400        }
1401        false
1402    }
1403
1404    /// Parse closure params and body (after opening { has been consumed).
1405    fn parse_closure_body(&mut self, start: Span) -> Result<SNode, ParserError> {
1406        let params = self.parse_typed_param_list_until_arrow()?;
1407        self.consume(&TokenKind::Arrow, "->")?;
1408        let body = self.parse_block()?;
1409        self.consume(&TokenKind::RBrace, "}")?;
1410        Ok(spanned(
1411            Node::Closure { params, body },
1412            Span::merge(start, self.prev_span()),
1413        ))
1414    }
1415
1416    /// Parse typed params until we see ->. Handles: `x`, `x: int`, `x, y`, `x: int, y: string`.
1417    fn parse_typed_param_list_until_arrow(&mut self) -> Result<Vec<TypedParam>, ParserError> {
1418        let mut params = Vec::new();
1419        self.skip_newlines();
1420        while !self.is_at_end() && !self.check(&TokenKind::Arrow) {
1421            let name = self.consume_identifier("parameter name")?;
1422            let type_expr = self.try_parse_type_annotation()?;
1423            params.push(TypedParam { name, type_expr });
1424            if self.check(&TokenKind::Comma) {
1425                self.advance();
1426                self.skip_newlines();
1427            }
1428        }
1429        Ok(params)
1430    }
1431
1432    fn parse_dict_literal(&mut self, start: Span) -> Result<SNode, ParserError> {
1433        let mut entries = Vec::new();
1434        self.skip_newlines();
1435
1436        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
1437            let key = if self.check(&TokenKind::LBracket) {
1438                // Computed key: [expression]
1439                self.advance();
1440                let k = self.parse_expression()?;
1441                self.consume(&TokenKind::RBracket, "]")?;
1442                k
1443            } else if matches!(
1444                self.current().map(|t| &t.kind),
1445                Some(TokenKind::StringLiteral(_))
1446            ) {
1447                // Quoted string key: {"key": value}
1448                let key_span = self.current_span();
1449                let name =
1450                    if let Some(TokenKind::StringLiteral(s)) = self.current().map(|t| &t.kind) {
1451                        s.clone()
1452                    } else {
1453                        unreachable!()
1454                    };
1455                self.advance();
1456                spanned(Node::StringLiteral(name), key_span)
1457            } else {
1458                // Static key: identifier or keyword -> string literal
1459                let key_span = self.current_span();
1460                let name = self.consume_identifier_or_keyword("dict key")?;
1461                spanned(Node::StringLiteral(name), key_span)
1462            };
1463            self.consume(&TokenKind::Colon, ":")?;
1464            let value = self.parse_expression()?;
1465            entries.push(DictEntry { key, value });
1466            self.skip_newlines();
1467            if self.check(&TokenKind::Comma) {
1468                self.advance();
1469                self.skip_newlines();
1470            }
1471        }
1472
1473        self.consume(&TokenKind::RBrace, "}")?;
1474        Ok(spanned(
1475            Node::DictLiteral(entries),
1476            Span::merge(start, self.prev_span()),
1477        ))
1478    }
1479
1480    // --- Helpers ---
1481
1482    /// Parse untyped parameter list (for pipelines, overrides).
1483    fn parse_param_list(&mut self) -> Result<Vec<String>, ParserError> {
1484        let mut params = Vec::new();
1485        self.skip_newlines();
1486
1487        while !self.is_at_end() && !self.check(&TokenKind::RParen) {
1488            params.push(self.consume_identifier("parameter name")?);
1489            if self.check(&TokenKind::Comma) {
1490                self.advance();
1491                self.skip_newlines();
1492            }
1493        }
1494        Ok(params)
1495    }
1496
1497    /// Parse typed parameter list (for fn declarations).
1498    fn parse_typed_param_list(&mut self) -> Result<Vec<TypedParam>, ParserError> {
1499        let mut params = Vec::new();
1500        self.skip_newlines();
1501
1502        while !self.is_at_end() && !self.check(&TokenKind::RParen) {
1503            let name = self.consume_identifier("parameter name")?;
1504            let type_expr = self.try_parse_type_annotation()?;
1505            params.push(TypedParam { name, type_expr });
1506            if self.check(&TokenKind::Comma) {
1507                self.advance();
1508                self.skip_newlines();
1509            }
1510        }
1511        Ok(params)
1512    }
1513
1514    /// Try to parse an optional type annotation (`: type`).
1515    /// Returns None if no colon follows.
1516    fn try_parse_type_annotation(&mut self) -> Result<Option<TypeExpr>, ParserError> {
1517        if !self.check(&TokenKind::Colon) {
1518            return Ok(None);
1519        }
1520        self.advance(); // skip :
1521        Ok(Some(self.parse_type_expr()?))
1522    }
1523
1524    /// Parse a type expression: `int`, `string | nil`, `{name: string, age?: int}`.
1525    fn parse_type_expr(&mut self) -> Result<TypeExpr, ParserError> {
1526        self.skip_newlines();
1527        let first = self.parse_type_primary()?;
1528
1529        // Check for union: type | type | ...
1530        if self.check(&TokenKind::Bar) {
1531            let mut types = vec![first];
1532            while self.check(&TokenKind::Bar) {
1533                self.advance(); // skip |
1534                types.push(self.parse_type_primary()?);
1535            }
1536            return Ok(TypeExpr::Union(types));
1537        }
1538
1539        Ok(first)
1540    }
1541
1542    /// Parse a primary type: named type or shape type.
1543    /// Accepts identifiers and certain keywords (nil, bool, etc.) as type names.
1544    fn parse_type_primary(&mut self) -> Result<TypeExpr, ParserError> {
1545        self.skip_newlines();
1546        if self.check(&TokenKind::LBrace) {
1547            return self.parse_shape_type();
1548        }
1549        // Accept keyword type names: nil, true, false map to their type names
1550        if let Some(tok) = self.current() {
1551            let type_name = match &tok.kind {
1552                TokenKind::Nil => {
1553                    self.advance();
1554                    return Ok(TypeExpr::Named("nil".to_string()));
1555                }
1556                TokenKind::True | TokenKind::False => {
1557                    self.advance();
1558                    return Ok(TypeExpr::Named("bool".to_string()));
1559                }
1560                _ => None,
1561            };
1562            if let Some(name) = type_name {
1563                return Ok(TypeExpr::Named(name));
1564            }
1565        }
1566        let name = self.consume_identifier("type name")?;
1567        // Check for generic type parameters: list[int], dict[string, int]
1568        if self.check(&TokenKind::LBracket) {
1569            self.advance(); // skip [
1570            let first_param = self.parse_type_expr()?;
1571            if name == "list" {
1572                self.consume(&TokenKind::RBracket, "]")?;
1573                return Ok(TypeExpr::List(Box::new(first_param)));
1574            } else if name == "dict" {
1575                self.consume(&TokenKind::Comma, ",")?;
1576                let second_param = self.parse_type_expr()?;
1577                self.consume(&TokenKind::RBracket, "]")?;
1578                return Ok(TypeExpr::DictType(
1579                    Box::new(first_param),
1580                    Box::new(second_param),
1581                ));
1582            }
1583            // Unknown generic — just consume ] and treat as Named
1584            self.consume(&TokenKind::RBracket, "]")?;
1585        }
1586        Ok(TypeExpr::Named(name))
1587    }
1588
1589    /// Parse a shape type: `{ name: string, age: int, active?: bool }`.
1590    fn parse_shape_type(&mut self) -> Result<TypeExpr, ParserError> {
1591        self.consume(&TokenKind::LBrace, "{")?;
1592        let mut fields = Vec::new();
1593        self.skip_newlines();
1594
1595        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
1596            let name = self.consume_identifier("field name")?;
1597            let optional = if self.check(&TokenKind::Question) {
1598                self.advance();
1599                true
1600            } else {
1601                false
1602            };
1603            self.consume(&TokenKind::Colon, ":")?;
1604            let type_expr = self.parse_type_expr()?;
1605            fields.push(ShapeField {
1606                name,
1607                type_expr,
1608                optional,
1609            });
1610            self.skip_newlines();
1611            if self.check(&TokenKind::Comma) {
1612                self.advance();
1613                self.skip_newlines();
1614            }
1615        }
1616
1617        self.consume(&TokenKind::RBrace, "}")?;
1618        Ok(TypeExpr::Shape(fields))
1619    }
1620
1621    fn parse_arg_list(&mut self) -> Result<Vec<SNode>, ParserError> {
1622        let mut args = Vec::new();
1623        self.skip_newlines();
1624
1625        while !self.is_at_end() && !self.check(&TokenKind::RParen) {
1626            args.push(self.parse_expression()?);
1627            self.skip_newlines();
1628            if self.check(&TokenKind::Comma) {
1629                self.advance();
1630                self.skip_newlines();
1631            }
1632        }
1633        Ok(args)
1634    }
1635
1636    fn is_at_end(&self) -> bool {
1637        self.pos >= self.tokens.len()
1638            || matches!(self.tokens.get(self.pos), Some(t) if t.kind == TokenKind::Eof)
1639    }
1640
1641    fn current(&self) -> Option<&Token> {
1642        self.tokens.get(self.pos)
1643    }
1644
1645    fn peek_kind(&self) -> Option<&TokenKind> {
1646        self.tokens.get(self.pos + 1).map(|t| &t.kind)
1647    }
1648
1649    fn check(&self, kind: &TokenKind) -> bool {
1650        self.current()
1651            .map(|t| std::mem::discriminant(&t.kind) == std::mem::discriminant(kind))
1652            .unwrap_or(false)
1653    }
1654
1655    fn advance(&mut self) {
1656        if self.pos < self.tokens.len() {
1657            self.pos += 1;
1658        }
1659    }
1660
1661    fn consume(&mut self, kind: &TokenKind, expected: &str) -> Result<Token, ParserError> {
1662        self.skip_newlines();
1663        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
1664        if std::mem::discriminant(&tok.kind) != std::mem::discriminant(kind) {
1665            return Err(self.make_error(expected));
1666        }
1667        let tok = tok.clone();
1668        self.advance();
1669        Ok(tok)
1670    }
1671
1672    fn consume_identifier(&mut self, expected: &str) -> Result<String, ParserError> {
1673        self.skip_newlines();
1674        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
1675        if let TokenKind::Identifier(name) = &tok.kind {
1676            let name = name.clone();
1677            self.advance();
1678            Ok(name)
1679        } else {
1680            Err(self.make_error(expected))
1681        }
1682    }
1683
1684    /// Like `consume_identifier`, but also accepts keywords as identifiers.
1685    /// Used for property access (e.g., `obj.type`) and dict keys where
1686    /// keywords are valid member names.
1687    fn consume_identifier_or_keyword(&mut self, expected: &str) -> Result<String, ParserError> {
1688        self.skip_newlines();
1689        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
1690        if let TokenKind::Identifier(name) = &tok.kind {
1691            let name = name.clone();
1692            self.advance();
1693            return Ok(name);
1694        }
1695        // Accept any keyword token as an identifier
1696        let name = match &tok.kind {
1697            TokenKind::Pipeline => "pipeline",
1698            TokenKind::Extends => "extends",
1699            TokenKind::Override => "override",
1700            TokenKind::Let => "let",
1701            TokenKind::Var => "var",
1702            TokenKind::If => "if",
1703            TokenKind::Else => "else",
1704            TokenKind::For => "for",
1705            TokenKind::In => "in",
1706            TokenKind::Match => "match",
1707            TokenKind::Retry => "retry",
1708            TokenKind::Parallel => "parallel",
1709            TokenKind::ParallelMap => "parallel_map",
1710            TokenKind::Return => "return",
1711            TokenKind::Import => "import",
1712            TokenKind::True => "true",
1713            TokenKind::False => "false",
1714            TokenKind::Nil => "nil",
1715            TokenKind::Try => "try",
1716            TokenKind::Catch => "catch",
1717            TokenKind::Throw => "throw",
1718            TokenKind::Fn => "fn",
1719            TokenKind::Spawn => "spawn",
1720            TokenKind::While => "while",
1721            TokenKind::TypeKw => "type",
1722            TokenKind::Enum => "enum",
1723            TokenKind::Struct => "struct",
1724            TokenKind::Interface => "interface",
1725            TokenKind::Pub => "pub",
1726            TokenKind::From => "from",
1727            TokenKind::Thru => "thru",
1728            TokenKind::Upto => "upto",
1729            TokenKind::Guard => "guard",
1730            TokenKind::Ask => "ask",
1731            TokenKind::Deadline => "deadline",
1732            TokenKind::Yield => "yield",
1733            TokenKind::Mutex => "mutex",
1734            TokenKind::Break => "break",
1735            TokenKind::Continue => "continue",
1736            _ => return Err(self.make_error(expected)),
1737        };
1738        let name = name.to_string();
1739        self.advance();
1740        Ok(name)
1741    }
1742
1743    fn skip_newlines(&mut self) {
1744        while self.pos < self.tokens.len() && self.tokens[self.pos].kind == TokenKind::Newline {
1745            self.pos += 1;
1746        }
1747    }
1748
1749    fn make_error(&self, expected: &str) -> ParserError {
1750        if let Some(tok) = self.tokens.get(self.pos) {
1751            if tok.kind == TokenKind::Eof {
1752                return ParserError::UnexpectedEof {
1753                    expected: expected.into(),
1754                };
1755            }
1756            ParserError::Unexpected {
1757                got: tok.kind.to_string(),
1758                expected: expected.into(),
1759                span: tok.span,
1760            }
1761        } else {
1762            ParserError::UnexpectedEof {
1763                expected: expected.into(),
1764            }
1765        }
1766    }
1767
1768    fn error(&self, expected: &str) -> ParserError {
1769        self.make_error(expected)
1770    }
1771}