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