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) || self.check(&TokenKind::QuestionDot) {
1170                let optional = self.check(&TokenKind::QuestionDot);
1171                let start = expr.span;
1172                self.advance();
1173                let member = self.consume_identifier_or_keyword("member name")?;
1174                if self.check(&TokenKind::LParen) {
1175                    self.advance();
1176                    let args = self.parse_arg_list()?;
1177                    self.consume(&TokenKind::RParen, ")")?;
1178                    if optional {
1179                        expr = spanned(
1180                            Node::OptionalMethodCall {
1181                                object: Box::new(expr),
1182                                method: member,
1183                                args,
1184                            },
1185                            Span::merge(start, self.prev_span()),
1186                        );
1187                    } else {
1188                        expr = spanned(
1189                            Node::MethodCall {
1190                                object: Box::new(expr),
1191                                method: member,
1192                                args,
1193                            },
1194                            Span::merge(start, self.prev_span()),
1195                        );
1196                    }
1197                } else if optional {
1198                    expr = spanned(
1199                        Node::OptionalPropertyAccess {
1200                            object: Box::new(expr),
1201                            property: member,
1202                        },
1203                        Span::merge(start, self.prev_span()),
1204                    );
1205                } else {
1206                    expr = spanned(
1207                        Node::PropertyAccess {
1208                            object: Box::new(expr),
1209                            property: member,
1210                        },
1211                        Span::merge(start, self.prev_span()),
1212                    );
1213                }
1214            } else if self.check(&TokenKind::LBracket) {
1215                let start = expr.span;
1216                self.advance();
1217
1218                // Check for slice vs subscript:
1219                // [:end] — slice with no start
1220                // [start:end] or [start:] — slice with start
1221                // [index] — normal subscript
1222                if self.check(&TokenKind::Colon) {
1223                    // [:end] or [:]
1224                    self.advance(); // consume ':'
1225                    let end_expr = if self.check(&TokenKind::RBracket) {
1226                        None
1227                    } else {
1228                        Some(Box::new(self.parse_expression()?))
1229                    };
1230                    self.consume(&TokenKind::RBracket, "]")?;
1231                    expr = spanned(
1232                        Node::SliceAccess {
1233                            object: Box::new(expr),
1234                            start: None,
1235                            end: end_expr,
1236                        },
1237                        Span::merge(start, self.prev_span()),
1238                    );
1239                } else {
1240                    let index = self.parse_expression()?;
1241                    if self.check(&TokenKind::Colon) {
1242                        // [start:end] or [start:]
1243                        self.advance(); // consume ':'
1244                        let end_expr = if self.check(&TokenKind::RBracket) {
1245                            None
1246                        } else {
1247                            Some(Box::new(self.parse_expression()?))
1248                        };
1249                        self.consume(&TokenKind::RBracket, "]")?;
1250                        expr = spanned(
1251                            Node::SliceAccess {
1252                                object: Box::new(expr),
1253                                start: Some(Box::new(index)),
1254                                end: end_expr,
1255                            },
1256                            Span::merge(start, self.prev_span()),
1257                        );
1258                    } else {
1259                        self.consume(&TokenKind::RBracket, "]")?;
1260                        expr = spanned(
1261                            Node::SubscriptAccess {
1262                                object: Box::new(expr),
1263                                index: Box::new(index),
1264                            },
1265                            Span::merge(start, self.prev_span()),
1266                        );
1267                    }
1268                }
1269            } else if self.check(&TokenKind::LParen) && matches!(expr.node, Node::Identifier(_)) {
1270                let start = expr.span;
1271                self.advance();
1272                let args = self.parse_arg_list()?;
1273                self.consume(&TokenKind::RParen, ")")?;
1274                if let Node::Identifier(name) = expr.node {
1275                    expr = spanned(
1276                        Node::FunctionCall { name, args },
1277                        Span::merge(start, self.prev_span()),
1278                    );
1279                }
1280            } else {
1281                break;
1282            }
1283        }
1284
1285        Ok(expr)
1286    }
1287
1288    fn parse_primary(&mut self) -> Result<SNode, ParserError> {
1289        let tok = self.current().ok_or_else(|| ParserError::UnexpectedEof {
1290            expected: "expression".into(),
1291        })?;
1292        let start = self.current_span();
1293
1294        match &tok.kind {
1295            TokenKind::StringLiteral(s) => {
1296                let s = s.clone();
1297                self.advance();
1298                Ok(spanned(
1299                    Node::StringLiteral(s),
1300                    Span::merge(start, self.prev_span()),
1301                ))
1302            }
1303            TokenKind::InterpolatedString(segments) => {
1304                let segments = segments.clone();
1305                self.advance();
1306                Ok(spanned(
1307                    Node::InterpolatedString(segments),
1308                    Span::merge(start, self.prev_span()),
1309                ))
1310            }
1311            TokenKind::IntLiteral(n) => {
1312                let n = *n;
1313                self.advance();
1314                Ok(spanned(
1315                    Node::IntLiteral(n),
1316                    Span::merge(start, self.prev_span()),
1317                ))
1318            }
1319            TokenKind::FloatLiteral(n) => {
1320                let n = *n;
1321                self.advance();
1322                Ok(spanned(
1323                    Node::FloatLiteral(n),
1324                    Span::merge(start, self.prev_span()),
1325                ))
1326            }
1327            TokenKind::True => {
1328                self.advance();
1329                Ok(spanned(
1330                    Node::BoolLiteral(true),
1331                    Span::merge(start, self.prev_span()),
1332                ))
1333            }
1334            TokenKind::False => {
1335                self.advance();
1336                Ok(spanned(
1337                    Node::BoolLiteral(false),
1338                    Span::merge(start, self.prev_span()),
1339                ))
1340            }
1341            TokenKind::Nil => {
1342                self.advance();
1343                Ok(spanned(
1344                    Node::NilLiteral,
1345                    Span::merge(start, self.prev_span()),
1346                ))
1347            }
1348            TokenKind::Identifier(name) => {
1349                let name = name.clone();
1350                self.advance();
1351                Ok(spanned(
1352                    Node::Identifier(name),
1353                    Span::merge(start, self.prev_span()),
1354                ))
1355            }
1356            TokenKind::LParen => {
1357                self.advance();
1358                let expr = self.parse_expression()?;
1359                self.consume(&TokenKind::RParen, ")")?;
1360                Ok(expr)
1361            }
1362            TokenKind::LBracket => self.parse_list_literal(),
1363            TokenKind::LBrace => self.parse_dict_or_closure(),
1364            TokenKind::Parallel => self.parse_parallel(),
1365            TokenKind::ParallelMap => self.parse_parallel_map(),
1366            TokenKind::Retry => self.parse_retry(),
1367            TokenKind::If => self.parse_if_else(),
1368            TokenKind::Spawn => self.parse_spawn_expr(),
1369            TokenKind::DurationLiteral(ms) => {
1370                let ms = *ms;
1371                self.advance();
1372                Ok(spanned(
1373                    Node::DurationLiteral(ms),
1374                    Span::merge(start, self.prev_span()),
1375                ))
1376            }
1377            TokenKind::Ask => self.parse_ask_expr(),
1378            TokenKind::Deadline => self.parse_deadline(),
1379            _ => Err(self.error("expression")),
1380        }
1381    }
1382
1383    fn parse_spawn_expr(&mut self) -> Result<SNode, ParserError> {
1384        let start = self.current_span();
1385        self.consume(&TokenKind::Spawn, "spawn")?;
1386        self.consume(&TokenKind::LBrace, "{")?;
1387        let body = self.parse_block()?;
1388        self.consume(&TokenKind::RBrace, "}")?;
1389        Ok(spanned(
1390            Node::SpawnExpr { body },
1391            Span::merge(start, self.prev_span()),
1392        ))
1393    }
1394
1395    fn parse_list_literal(&mut self) -> Result<SNode, ParserError> {
1396        let start = self.current_span();
1397        self.consume(&TokenKind::LBracket, "[")?;
1398        let mut elements = Vec::new();
1399        self.skip_newlines();
1400
1401        while !self.is_at_end() && !self.check(&TokenKind::RBracket) {
1402            elements.push(self.parse_expression()?);
1403            self.skip_newlines();
1404            if self.check(&TokenKind::Comma) {
1405                self.advance();
1406                self.skip_newlines();
1407            }
1408        }
1409
1410        self.consume(&TokenKind::RBracket, "]")?;
1411        Ok(spanned(
1412            Node::ListLiteral(elements),
1413            Span::merge(start, self.prev_span()),
1414        ))
1415    }
1416
1417    fn parse_dict_or_closure(&mut self) -> Result<SNode, ParserError> {
1418        let start = self.current_span();
1419        self.consume(&TokenKind::LBrace, "{")?;
1420        self.skip_newlines();
1421
1422        // Empty dict
1423        if self.check(&TokenKind::RBrace) {
1424            self.advance();
1425            return Ok(spanned(
1426                Node::DictLiteral(Vec::new()),
1427                Span::merge(start, self.prev_span()),
1428            ));
1429        }
1430
1431        // Lookahead: scan for -> before } to disambiguate closure from dict.
1432        let saved = self.pos;
1433        if self.is_closure_lookahead() {
1434            self.pos = saved;
1435            return self.parse_closure_body(start);
1436        }
1437        self.pos = saved;
1438        self.parse_dict_literal(start)
1439    }
1440
1441    /// Scan forward to determine if this is a closure (has -> before matching }).
1442    /// Does not consume tokens (caller saves/restores pos).
1443    fn is_closure_lookahead(&mut self) -> bool {
1444        let mut depth = 0;
1445        while !self.is_at_end() {
1446            if let Some(tok) = self.current() {
1447                match &tok.kind {
1448                    TokenKind::Arrow if depth == 0 => return true,
1449                    TokenKind::LBrace | TokenKind::LParen | TokenKind::LBracket => depth += 1,
1450                    TokenKind::RBrace if depth == 0 => return false,
1451                    TokenKind::RBrace => depth -= 1,
1452                    TokenKind::RParen | TokenKind::RBracket => {
1453                        if depth > 0 {
1454                            depth -= 1;
1455                        }
1456                    }
1457                    _ => {}
1458                }
1459                self.advance();
1460            } else {
1461                return false;
1462            }
1463        }
1464        false
1465    }
1466
1467    /// Parse closure params and body (after opening { has been consumed).
1468    fn parse_closure_body(&mut self, start: Span) -> Result<SNode, ParserError> {
1469        let params = self.parse_typed_param_list_until_arrow()?;
1470        self.consume(&TokenKind::Arrow, "->")?;
1471        let body = self.parse_block()?;
1472        self.consume(&TokenKind::RBrace, "}")?;
1473        Ok(spanned(
1474            Node::Closure { params, body },
1475            Span::merge(start, self.prev_span()),
1476        ))
1477    }
1478
1479    /// Parse typed params until we see ->. Handles: `x`, `x: int`, `x, y`, `x: int, y: string`.
1480    fn parse_typed_param_list_until_arrow(&mut self) -> Result<Vec<TypedParam>, ParserError> {
1481        let mut params = Vec::new();
1482        self.skip_newlines();
1483        while !self.is_at_end() && !self.check(&TokenKind::Arrow) {
1484            let name = self.consume_identifier("parameter name")?;
1485            let type_expr = self.try_parse_type_annotation()?;
1486            params.push(TypedParam { name, type_expr });
1487            if self.check(&TokenKind::Comma) {
1488                self.advance();
1489                self.skip_newlines();
1490            }
1491        }
1492        Ok(params)
1493    }
1494
1495    fn parse_dict_literal(&mut self, start: Span) -> Result<SNode, ParserError> {
1496        let mut entries = Vec::new();
1497        self.skip_newlines();
1498
1499        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
1500            let key = if self.check(&TokenKind::LBracket) {
1501                // Computed key: [expression]
1502                self.advance();
1503                let k = self.parse_expression()?;
1504                self.consume(&TokenKind::RBracket, "]")?;
1505                k
1506            } else if matches!(
1507                self.current().map(|t| &t.kind),
1508                Some(TokenKind::StringLiteral(_))
1509            ) {
1510                // Quoted string key: {"key": value}
1511                let key_span = self.current_span();
1512                let name =
1513                    if let Some(TokenKind::StringLiteral(s)) = self.current().map(|t| &t.kind) {
1514                        s.clone()
1515                    } else {
1516                        unreachable!()
1517                    };
1518                self.advance();
1519                spanned(Node::StringLiteral(name), key_span)
1520            } else {
1521                // Static key: identifier or keyword -> string literal
1522                let key_span = self.current_span();
1523                let name = self.consume_identifier_or_keyword("dict key")?;
1524                spanned(Node::StringLiteral(name), key_span)
1525            };
1526            self.consume(&TokenKind::Colon, ":")?;
1527            let value = self.parse_expression()?;
1528            entries.push(DictEntry { key, value });
1529            self.skip_newlines();
1530            if self.check(&TokenKind::Comma) {
1531                self.advance();
1532                self.skip_newlines();
1533            }
1534        }
1535
1536        self.consume(&TokenKind::RBrace, "}")?;
1537        Ok(spanned(
1538            Node::DictLiteral(entries),
1539            Span::merge(start, self.prev_span()),
1540        ))
1541    }
1542
1543    // --- Helpers ---
1544
1545    /// Parse untyped parameter list (for pipelines, overrides).
1546    fn parse_param_list(&mut self) -> Result<Vec<String>, ParserError> {
1547        let mut params = Vec::new();
1548        self.skip_newlines();
1549
1550        while !self.is_at_end() && !self.check(&TokenKind::RParen) {
1551            params.push(self.consume_identifier("parameter name")?);
1552            if self.check(&TokenKind::Comma) {
1553                self.advance();
1554                self.skip_newlines();
1555            }
1556        }
1557        Ok(params)
1558    }
1559
1560    /// Parse typed parameter list (for fn declarations).
1561    fn parse_typed_param_list(&mut self) -> Result<Vec<TypedParam>, ParserError> {
1562        let mut params = Vec::new();
1563        self.skip_newlines();
1564
1565        while !self.is_at_end() && !self.check(&TokenKind::RParen) {
1566            let name = self.consume_identifier("parameter name")?;
1567            let type_expr = self.try_parse_type_annotation()?;
1568            params.push(TypedParam { name, type_expr });
1569            if self.check(&TokenKind::Comma) {
1570                self.advance();
1571                self.skip_newlines();
1572            }
1573        }
1574        Ok(params)
1575    }
1576
1577    /// Try to parse an optional type annotation (`: type`).
1578    /// Returns None if no colon follows.
1579    fn try_parse_type_annotation(&mut self) -> Result<Option<TypeExpr>, ParserError> {
1580        if !self.check(&TokenKind::Colon) {
1581            return Ok(None);
1582        }
1583        self.advance(); // skip :
1584        Ok(Some(self.parse_type_expr()?))
1585    }
1586
1587    /// Parse a type expression: `int`, `string | nil`, `{name: string, age?: int}`.
1588    fn parse_type_expr(&mut self) -> Result<TypeExpr, ParserError> {
1589        self.skip_newlines();
1590        let first = self.parse_type_primary()?;
1591
1592        // Check for union: type | type | ...
1593        if self.check(&TokenKind::Bar) {
1594            let mut types = vec![first];
1595            while self.check(&TokenKind::Bar) {
1596                self.advance(); // skip |
1597                types.push(self.parse_type_primary()?);
1598            }
1599            return Ok(TypeExpr::Union(types));
1600        }
1601
1602        Ok(first)
1603    }
1604
1605    /// Parse a primary type: named type or shape type.
1606    /// Accepts identifiers and certain keywords (nil, bool, etc.) as type names.
1607    fn parse_type_primary(&mut self) -> Result<TypeExpr, ParserError> {
1608        self.skip_newlines();
1609        if self.check(&TokenKind::LBrace) {
1610            return self.parse_shape_type();
1611        }
1612        // Accept keyword type names: nil, true, false map to their type names
1613        if let Some(tok) = self.current() {
1614            let type_name = match &tok.kind {
1615                TokenKind::Nil => {
1616                    self.advance();
1617                    return Ok(TypeExpr::Named("nil".to_string()));
1618                }
1619                TokenKind::True | TokenKind::False => {
1620                    self.advance();
1621                    return Ok(TypeExpr::Named("bool".to_string()));
1622                }
1623                _ => None,
1624            };
1625            if let Some(name) = type_name {
1626                return Ok(TypeExpr::Named(name));
1627            }
1628        }
1629        let name = self.consume_identifier("type name")?;
1630        // Check for generic type parameters: list[int], dict[string, int]
1631        if self.check(&TokenKind::LBracket) {
1632            self.advance(); // skip [
1633            let first_param = self.parse_type_expr()?;
1634            if name == "list" {
1635                self.consume(&TokenKind::RBracket, "]")?;
1636                return Ok(TypeExpr::List(Box::new(first_param)));
1637            } else if name == "dict" {
1638                self.consume(&TokenKind::Comma, ",")?;
1639                let second_param = self.parse_type_expr()?;
1640                self.consume(&TokenKind::RBracket, "]")?;
1641                return Ok(TypeExpr::DictType(
1642                    Box::new(first_param),
1643                    Box::new(second_param),
1644                ));
1645            }
1646            // Unknown generic — just consume ] and treat as Named
1647            self.consume(&TokenKind::RBracket, "]")?;
1648        }
1649        Ok(TypeExpr::Named(name))
1650    }
1651
1652    /// Parse a shape type: `{ name: string, age: int, active?: bool }`.
1653    fn parse_shape_type(&mut self) -> Result<TypeExpr, ParserError> {
1654        self.consume(&TokenKind::LBrace, "{")?;
1655        let mut fields = Vec::new();
1656        self.skip_newlines();
1657
1658        while !self.is_at_end() && !self.check(&TokenKind::RBrace) {
1659            let name = self.consume_identifier("field name")?;
1660            let optional = if self.check(&TokenKind::Question) {
1661                self.advance();
1662                true
1663            } else {
1664                false
1665            };
1666            self.consume(&TokenKind::Colon, ":")?;
1667            let type_expr = self.parse_type_expr()?;
1668            fields.push(ShapeField {
1669                name,
1670                type_expr,
1671                optional,
1672            });
1673            self.skip_newlines();
1674            if self.check(&TokenKind::Comma) {
1675                self.advance();
1676                self.skip_newlines();
1677            }
1678        }
1679
1680        self.consume(&TokenKind::RBrace, "}")?;
1681        Ok(TypeExpr::Shape(fields))
1682    }
1683
1684    fn parse_arg_list(&mut self) -> Result<Vec<SNode>, ParserError> {
1685        let mut args = Vec::new();
1686        self.skip_newlines();
1687
1688        while !self.is_at_end() && !self.check(&TokenKind::RParen) {
1689            args.push(self.parse_expression()?);
1690            self.skip_newlines();
1691            if self.check(&TokenKind::Comma) {
1692                self.advance();
1693                self.skip_newlines();
1694            }
1695        }
1696        Ok(args)
1697    }
1698
1699    fn is_at_end(&self) -> bool {
1700        self.pos >= self.tokens.len()
1701            || matches!(self.tokens.get(self.pos), Some(t) if t.kind == TokenKind::Eof)
1702    }
1703
1704    fn current(&self) -> Option<&Token> {
1705        self.tokens.get(self.pos)
1706    }
1707
1708    fn peek_kind(&self) -> Option<&TokenKind> {
1709        self.tokens.get(self.pos + 1).map(|t| &t.kind)
1710    }
1711
1712    fn check(&self, kind: &TokenKind) -> bool {
1713        self.current()
1714            .map(|t| std::mem::discriminant(&t.kind) == std::mem::discriminant(kind))
1715            .unwrap_or(false)
1716    }
1717
1718    fn advance(&mut self) {
1719        if self.pos < self.tokens.len() {
1720            self.pos += 1;
1721        }
1722    }
1723
1724    fn consume(&mut self, kind: &TokenKind, expected: &str) -> Result<Token, ParserError> {
1725        self.skip_newlines();
1726        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
1727        if std::mem::discriminant(&tok.kind) != std::mem::discriminant(kind) {
1728            return Err(self.make_error(expected));
1729        }
1730        let tok = tok.clone();
1731        self.advance();
1732        Ok(tok)
1733    }
1734
1735    fn consume_identifier(&mut self, expected: &str) -> Result<String, ParserError> {
1736        self.skip_newlines();
1737        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
1738        if let TokenKind::Identifier(name) = &tok.kind {
1739            let name = name.clone();
1740            self.advance();
1741            Ok(name)
1742        } else {
1743            Err(self.make_error(expected))
1744        }
1745    }
1746
1747    /// Like `consume_identifier`, but also accepts keywords as identifiers.
1748    /// Used for property access (e.g., `obj.type`) and dict keys where
1749    /// keywords are valid member names.
1750    fn consume_identifier_or_keyword(&mut self, expected: &str) -> Result<String, ParserError> {
1751        self.skip_newlines();
1752        let tok = self.current().ok_or_else(|| self.make_error(expected))?;
1753        if let TokenKind::Identifier(name) = &tok.kind {
1754            let name = name.clone();
1755            self.advance();
1756            return Ok(name);
1757        }
1758        // Accept any keyword token as an identifier
1759        let name = match &tok.kind {
1760            TokenKind::Pipeline => "pipeline",
1761            TokenKind::Extends => "extends",
1762            TokenKind::Override => "override",
1763            TokenKind::Let => "let",
1764            TokenKind::Var => "var",
1765            TokenKind::If => "if",
1766            TokenKind::Else => "else",
1767            TokenKind::For => "for",
1768            TokenKind::In => "in",
1769            TokenKind::Match => "match",
1770            TokenKind::Retry => "retry",
1771            TokenKind::Parallel => "parallel",
1772            TokenKind::ParallelMap => "parallel_map",
1773            TokenKind::Return => "return",
1774            TokenKind::Import => "import",
1775            TokenKind::True => "true",
1776            TokenKind::False => "false",
1777            TokenKind::Nil => "nil",
1778            TokenKind::Try => "try",
1779            TokenKind::Catch => "catch",
1780            TokenKind::Throw => "throw",
1781            TokenKind::Fn => "fn",
1782            TokenKind::Spawn => "spawn",
1783            TokenKind::While => "while",
1784            TokenKind::TypeKw => "type",
1785            TokenKind::Enum => "enum",
1786            TokenKind::Struct => "struct",
1787            TokenKind::Interface => "interface",
1788            TokenKind::Pub => "pub",
1789            TokenKind::From => "from",
1790            TokenKind::Thru => "thru",
1791            TokenKind::Upto => "upto",
1792            TokenKind::Guard => "guard",
1793            TokenKind::Ask => "ask",
1794            TokenKind::Deadline => "deadline",
1795            TokenKind::Yield => "yield",
1796            TokenKind::Mutex => "mutex",
1797            TokenKind::Break => "break",
1798            TokenKind::Continue => "continue",
1799            _ => return Err(self.make_error(expected)),
1800        };
1801        let name = name.to_string();
1802        self.advance();
1803        Ok(name)
1804    }
1805
1806    fn skip_newlines(&mut self) {
1807        while self.pos < self.tokens.len() && self.tokens[self.pos].kind == TokenKind::Newline {
1808            self.pos += 1;
1809        }
1810    }
1811
1812    fn make_error(&self, expected: &str) -> ParserError {
1813        if let Some(tok) = self.tokens.get(self.pos) {
1814            if tok.kind == TokenKind::Eof {
1815                return ParserError::UnexpectedEof {
1816                    expected: expected.into(),
1817                };
1818            }
1819            ParserError::Unexpected {
1820                got: tok.kind.to_string(),
1821                expected: expected.into(),
1822                span: tok.span,
1823            }
1824        } else {
1825            ParserError::UnexpectedEof {
1826                expected: expected.into(),
1827            }
1828        }
1829    }
1830
1831    fn error(&self, expected: &str) -> ParserError {
1832        self.make_error(expected)
1833    }
1834}