Skip to main content

aura_lang/parser/
mod.rs

1//! Recursive descent + Pratt expression parser (SPEC §3).
2
3pub mod ast;
4
5use crate::error::Diagnostic;
6use crate::lexer::{Token, TokenKind};
7use crate::span::Span;
8use ast::*;
9
10/// lbp of the ternary `? :`; right associativity — the else branch parses with min_bp = TERNARY_LBP - 1.
11const TERNARY_LBP: u8 = 2;
12const UNARY_RBP: u8 = 15;
13
14fn infix_bp(kind: &TokenKind<'_>) -> Option<(u8, u8, BinOp)> {
15    use TokenKind::*;
16    Some(match kind {
17        Or => (3, 4, BinOp::Or),
18        And => (5, 6, BinOp::And),
19        EqEq => (7, 8, BinOp::Eq),
20        NotEq => (7, 8, BinOp::Ne),
21        Lt => (9, 10, BinOp::Lt),
22        Gt => (9, 10, BinOp::Gt),
23        LtEq => (9, 10, BinOp::Le),
24        GtEq => (9, 10, BinOp::Ge),
25        Plus => (11, 12, BinOp::Add),
26        Minus => (11, 12, BinOp::Sub),
27        Star => (13, 14, BinOp::Mul),
28        Slash => (13, 14, BinOp::Div),
29        Percent => (13, 14, BinOp::Rem),
30        _ => return None,
31    })
32}
33
34/// Recursion-depth cap for the recursive-descent parser: crafted deeply-nested
35/// input (`((((…`, `[[[[…`, nested blocks/objects) would otherwise overflow the
36/// stack — a DoS on untrusted input (e.g. a package pulled via `aura add`).
37const MAX_PARSE_DEPTH: u32 = 256;
38
39pub struct Parser<'a> {
40    toks: Vec<Token<'a>>,
41    pos: usize,
42    diags: Vec<Diagnostic>,
43    depth: u32,
44}
45
46impl<'a> Parser<'a> {
47    pub fn new(toks: Vec<Token<'a>>) -> Self {
48        debug_assert!(matches!(toks.last().map(|t| &t.kind), Some(TokenKind::Eof)));
49        Parser {
50            toks,
51            pos: 0,
52            diags: Vec::new(),
53            depth: 0,
54        }
55    }
56
57    /// Enter one nesting level; E0208 past the cap. Paired with `leave()`.
58    fn enter(&mut self) -> Result<(), Diagnostic> {
59        self.depth += 1;
60        if self.depth > MAX_PARSE_DEPTH {
61            self.depth -= 1;
62            return Err(self.err(
63                "E0208",
64                "nesting too deep",
65                "simplify or split this expression/block",
66            ));
67        }
68        Ok(())
69    }
70
71    fn leave(&mut self) {
72        self.depth -= 1;
73    }
74
75    // ---- Token navigation ----
76
77    fn peek(&self) -> &TokenKind<'a> {
78        &self.toks[self.pos].kind
79    }
80
81    fn peek_at(&self, off: usize) -> &TokenKind<'a> {
82        self.toks
83            .get(self.pos + off)
84            .map(|t| &t.kind)
85            .unwrap_or(&TokenKind::Eof)
86    }
87
88    fn span(&self) -> Span {
89        self.toks[self.pos].span
90    }
91
92    fn prev_end_span(&self) -> Span {
93        self.toks[self.pos.saturating_sub(1)].span
94    }
95
96    fn bump(&mut self) {
97        if !matches!(self.peek(), TokenKind::Eof) {
98            self.pos += 1;
99        }
100    }
101
102    fn eat(&mut self, kind: &TokenKind<'_>) -> bool {
103        if self.peek() == kind {
104            self.bump();
105            true
106        } else {
107            false
108        }
109    }
110
111    fn join(&self, from: Span) -> Span {
112        Span {
113            source: from.source,
114            start: from.start,
115            end: self.prev_end_span().end,
116        }
117    }
118
119    fn err(
120        &self,
121        code: &'static str,
122        msg: impl Into<String>,
123        label: impl Into<String>,
124    ) -> Diagnostic {
125        Diagnostic::error(code, msg, self.span(), label)
126    }
127
128    fn expect(&mut self, kind: &TokenKind<'_>, what: &str) -> Result<(), Diagnostic> {
129        if self.eat(kind) {
130            Ok(())
131        } else {
132            Err(self.err(
133                "E0200",
134                format!("expected {what}"),
135                format!("found {:?}", self.peek()),
136            ))
137        }
138    }
139
140    fn expect_ident(&mut self, what: &str) -> Result<(&'a str, Span), Diagnostic> {
141        if let TokenKind::Ident(s) = self.peek() {
142            let (s, sp) = (*s, self.span());
143            self.bump();
144            Ok((s, sp))
145        } else {
146            Err(self.err(
147                "E0200",
148                format!("expected {what}"),
149                format!("found {:?}", self.peek()),
150            ))
151        }
152    }
153
154    /// Property key: identifier or string (D11: `"app.kubernetes.io/name": ...`).
155    fn expect_key(&mut self) -> Result<(&'a str, Span), Diagnostic> {
156        match self.peek() {
157            TokenKind::Ident(s) | TokenKind::Str(s) => {
158                let (s, sp) = (*s, self.span());
159                self.bump();
160                Ok((s, sp))
161            }
162            _ => Err(self.err(
163                "E0200",
164                "expected property key",
165                format!("found {:?}", self.peek()),
166            )),
167        }
168    }
169
170    fn skip_newlines(&mut self) {
171        while matches!(self.peek(), TokenKind::Newline) {
172            self.bump();
173        }
174    }
175
176    /// Statement separator: Newline, or an allowed boundary (`end`/Eof).
177    fn eat_separator(&mut self) -> Result<(), Diagnostic> {
178        match self.peek() {
179            TokenKind::Newline => {
180                self.skip_newlines();
181                Ok(())
182            }
183            TokenKind::End | TokenKind::Eof => Ok(()),
184            _ => Err(self.err(
185                "E0205",
186                "expected end of statement",
187                "statements are separated by newlines",
188            )),
189        }
190    }
191
192    /// Error recovery: skip to the next Newline / `end` / Eof (SPEC §3.4).
193    fn recover(&mut self) {
194        while !matches!(
195            self.peek(),
196            TokenKind::Newline | TokenKind::End | TokenKind::Eof
197        ) {
198            self.bump();
199        }
200        self.skip_newlines();
201    }
202
203    /// Entry point for a single expression (`#{...}` interpolations, REPL).
204    pub fn parse_expression(mut self) -> Result<Expr<'a>, Diagnostic> {
205        let e = self.parse_expr(0)?;
206        self.skip_newlines();
207        if !matches!(self.peek(), TokenKind::Eof) {
208            return Err(self.err(
209                "E0204",
210                "unexpected trailing tokens after expression",
211                "expected end of expression",
212            ));
213        }
214        Ok(e)
215    }
216
217    // ---- Module ----
218
219    /// Debug builds parse on a thread with a 64 MiB stack. `MAX_PARSE_DEPTH`
220    /// already caps the recursion at 256 frames, which a release build clears in
221    /// tens of kilobytes — but a debug frame is roughly ten times larger, and 256
222    /// of them can approach the ~1 MiB default stack on Windows before the guard
223    /// trips.
224    ///
225    /// Release builds — and wasm, which has no threads at all — recurse on the
226    /// stack they are given. This is not a weakening: the guard is what makes the
227    /// parser DoS-safe, and it is unconditional. The thread only ever bought head
228    /// room for fat debug frames.
229    ///
230    /// It bought that head room expensively. Spawning the thread costs about
231    /// 115 µs, against 7.8 µs for parsing the reference manifest — sixteen times
232    /// the work it protects, paid on every `check`, every `eval`, and every
233    /// reparse the language server does while someone types.
234    #[cfg(all(not(target_family = "wasm"), debug_assertions))]
235    pub fn parse_module(self) -> Result<Module<'a>, Vec<Diagnostic>> {
236        std::thread::scope(|s| {
237            std::thread::Builder::new()
238                .stack_size(64 * 1024 * 1024)
239                .spawn_scoped(s, move || self.parse_module_impl())
240                .expect("spawn parser thread")
241                .join()
242                .expect("parser thread panicked")
243        })
244    }
245
246    /// See the note on the threaded version above.
247    #[cfg(any(target_family = "wasm", not(debug_assertions)))]
248    pub fn parse_module(self) -> Result<Module<'a>, Vec<Diagnostic>> {
249        self.parse_module_impl()
250    }
251
252    fn parse_module_impl(mut self) -> Result<Module<'a>, Vec<Diagnostic>> {
253        let start = self.span();
254        let mut imports = Vec::new();
255        let mut stmts = Vec::new();
256        self.skip_newlines();
257        while matches!(self.peek(), TokenKind::Import) {
258            match self.parse_import() {
259                Ok(i) => {
260                    imports.push(i);
261                    if let Err(d) = self.eat_separator() {
262                        self.diags.push(d);
263                        self.recover();
264                    }
265                }
266                Err(d) => {
267                    self.diags.push(d);
268                    self.recover();
269                }
270            }
271        }
272        while !matches!(self.peek(), TokenKind::Eof) {
273            let before = self.pos;
274            match self.parse_stmt().and_then(|s| {
275                self.eat_separator()?;
276                Ok(s)
277            }) {
278                Ok(s) => stmts.push(s),
279                Err(d) => {
280                    self.diags.push(d);
281                    self.recover();
282                    // Guarantee forward progress: if the error left us on a token that
283                    // `recover()` treats as a boundary (e.g. a stray `end`), consume it so
284                    // the loop can't spin forever pushing the same diagnostic.
285                    if self.pos == before {
286                        self.bump();
287                    }
288                }
289            }
290        }
291        if self.diags.is_empty() {
292            Ok(Module {
293                imports,
294                stmts,
295                span: self.join(start),
296            })
297        } else {
298            Err(self.diags)
299        }
300    }
301
302    fn parse_import(&mut self) -> Result<Import<'a>, Diagnostic> {
303        let start = self.span();
304        self.bump(); // import
305        let source = match self.peek() {
306            TokenKind::ImportPath { path, version } => {
307                let s = ImportSource::Registry { path, version };
308                self.bump();
309                s
310            }
311            TokenKind::Str(p) => {
312                let s = ImportSource::File(p);
313                self.bump();
314                s
315            }
316            _ => {
317                return Err(self.err(
318                    "E0200",
319                    "expected import path or \"file.aura\"",
320                    "invalid import source",
321                ))
322            }
323        };
324        self.expect(&TokenKind::As, "`as` after import source")?;
325        let (alias, _) = self.expect_ident("import alias")?;
326        Ok(Import {
327            source,
328            alias,
329            span: self.join(start),
330        })
331    }
332
333    // ---- Statements ----
334
335    fn parse_stmt(&mut self) -> Result<Stmt<'a>, Diagnostic> {
336        self.enter()?;
337        let r = self.parse_stmt_impl();
338        self.leave();
339        r
340    }
341
342    fn parse_stmt_impl(&mut self) -> Result<Stmt<'a>, Diagnostic> {
343        match self.peek() {
344            TokenKind::Type => self.parse_type_decl(false),
345            TokenKind::Enum => self.parse_enum_decl(false),
346            TokenKind::Def => self.parse_func_decl(false),
347            // D12: `pub` is only allowed before def/type
348            TokenKind::Pub => {
349                self.bump();
350                match self.peek() {
351                    TokenKind::Def => self.parse_func_decl(true),
352                    TokenKind::Type => self.parse_type_decl(true),
353                    TokenKind::Enum => self.parse_enum_decl(true),
354                    _ => Err(self.err(
355                        "E0206",
356                        "`pub` is only allowed before `def`, `type` or `enum`",
357                        "properties are exported by default; `=` bindings are always private",
358                    )),
359                }
360            }
361            TokenKind::Domain => Ok(Stmt::Block(self.parse_block(BlockKind::Domain)?)),
362            TokenKind::Assert => self.parse_assert(),
363            TokenKind::Shadow => {
364                let start = self.span();
365                self.bump();
366                let (name, _) = self.expect_ident("variable name after `shadow`")?;
367                self.expect(&TokenKind::Assign, "`=` in shadow assignment")?;
368                let value = self.parse_expr(0)?;
369                Ok(Stmt::Assign {
370                    name,
371                    shadow: true,
372                    value,
373                    span: self.join(start),
374                })
375            }
376            // D11: property with a string key — `"app.io/name": value`
377            TokenKind::Str(_) if matches!(self.peek_at(1), TokenKind::Colon) => {
378                let start = self.span();
379                let (key, _) = self.expect_key()?;
380                self.bump(); // :
381                let value = self.parse_property_value()?;
382                Ok(Stmt::Property {
383                    key,
384                    value,
385                    span: self.join(start),
386                })
387            }
388            TokenKind::Ident(_) => {
389                let start = self.span();
390                match (self.peek_at(1), self.peek_at(2)) {
391                    (TokenKind::Assign, _) => {
392                        let (name, _) = self.expect_ident("name")?;
393                        self.bump(); // =
394                        let value = self.parse_expr(0)?;
395                        Ok(Stmt::Assign {
396                            name,
397                            shadow: false,
398                            value,
399                            span: self.join(start),
400                        })
401                    }
402                    (TokenKind::Colon, _) => {
403                        let (key, _) = self.expect_ident("key")?;
404                        self.bump(); // :
405                        let value = self.parse_property_value()?;
406                        Ok(Stmt::Property {
407                            key,
408                            value,
409                            span: self.join(start),
410                        })
411                    }
412                    // v1.1 inline block removed (D3): `metrics port: 9090 ...`
413                    (TokenKind::Ident(_), TokenKind::Colon) => {
414                        let mut d = self.err(
415                            "E0201",
416                            "inline blocks were removed in Aura v1.2",
417                            "this looks like an inline block",
418                        );
419                        if let TokenKind::Ident(name) = self.peek() {
420                            d.help = Some(format!(
421                                "write it as an object block:\n{name}:\n  key: value\nend"
422                            ));
423                        }
424                        Err(d)
425                    }
426                    _ => Ok(Stmt::Expr(self.parse_expr(0)?)),
427                }
428            }
429            _ => Ok(Stmt::Expr(self.parse_expr(0)?)),
430        }
431    }
432
433    /// Property value: an expr on the same line, or Newline → nested object block (SPEC §3.2).
434    fn parse_property_value(&mut self) -> Result<Expr<'a>, Diagnostic> {
435        if matches!(self.peek(), TokenKind::Newline) {
436            self.bump();
437            Ok(Expr::ObjectLiteral(self.parse_object_body()?))
438        } else {
439            self.parse_expr(0)
440        }
441    }
442
443    /// Properties up to a closing `end` (def body / nested object / new Schema).
444    /// A code body: statements until `end` (D17). Shared by blocks, `def`
445    /// bodies and lambda bodies, so all three are scopes that accept `=`,
446    /// `shadow` and `assert` — unlike an object literal, which is data only.
447    fn parse_stmt_body(&mut self, what: &'static str) -> Result<Vec<Stmt<'a>>, Diagnostic> {
448        self.enter()?;
449        let r = self.parse_stmt_body_impl(what);
450        self.leave();
451        r
452    }
453
454    fn parse_stmt_body_impl(&mut self, what: &'static str) -> Result<Vec<Stmt<'a>>, Diagnostic> {
455        let mut body = Vec::new();
456        loop {
457            self.skip_newlines();
458            if self.eat(&TokenKind::End) {
459                return Ok(body);
460            }
461            if matches!(self.peek(), TokenKind::Eof) {
462                return Err(self.err(
463                    "E0203",
464                    "missing `end`",
465                    match what {
466                        "block" => "block is not closed",
467                        "function" => "function body is not closed",
468                        _ => "lambda body is not closed",
469                    },
470                ));
471            }
472            let stmt = self.parse_stmt()?;
473            self.eat_separator()?;
474            body.push(stmt);
475        }
476    }
477
478    fn parse_object_body(&mut self) -> Result<ObjectBody<'a>, Diagnostic> {
479        self.enter()?;
480        let r = self.parse_object_body_impl();
481        self.leave();
482        r
483    }
484
485    fn parse_object_body_impl(&mut self) -> Result<ObjectBody<'a>, Diagnostic> {
486        let mut props = Vec::new();
487        loop {
488            self.skip_newlines();
489            if self.eat(&TokenKind::End) {
490                return Ok(ObjectBody { props });
491            }
492            if matches!(self.peek(), TokenKind::Eof) {
493                return Err(self.err("E0203", "missing `end`", "object block is not closed"));
494            }
495            let (key, kspan) = self.expect_key()?;
496            self.expect(&TokenKind::Colon, "`:` after property key")?;
497            let value = self.parse_property_value()?;
498            props.push((key, value, kspan));
499            if !matches!(self.peek(), TokenKind::Newline | TokenKind::End) {
500                return Err(self.err(
501                    "E0205",
502                    "expected end of property",
503                    "properties are separated by newlines",
504                ));
505            }
506        }
507    }
508
509    /// D18: `enum Name` + one string member per line, until `end`.
510    fn parse_enum_decl(&mut self, public: bool) -> Result<Stmt<'a>, Diagnostic> {
511        let start = self.span();
512        self.bump(); // enum
513        let (name, _) = self.expect_ident("enum name")?;
514        self.expect(&TokenKind::Newline, "newline after enum name")?;
515        let mut members: Vec<&'a str> = Vec::new();
516        loop {
517            self.skip_newlines();
518            if self.eat(&TokenKind::End) {
519                break;
520            }
521            let TokenKind::Str(m) = self.peek() else {
522                return Err(self.err(
523                    "E0211",
524                    "expected an enum member",
525                    "members are quoted strings, one per line",
526                ));
527            };
528            if members.contains(m) {
529                return Err(self.err(
530                    "E0212",
531                    format!("duplicate enum member '{m}'"),
532                    "each member must be listed once",
533                ));
534            }
535            members.push(m);
536            self.bump();
537            if !matches!(self.peek(), TokenKind::Newline | TokenKind::End) {
538                return Err(self.err(
539                    "E0205",
540                    "expected end of enum member",
541                    "members are separated by newlines",
542                ));
543            }
544        }
545        if members.is_empty() {
546            return Err(self.err(
547                "E0213",
548                "enum has no members",
549                "an empty enum could never be satisfied",
550            ));
551        }
552        Ok(Stmt::EnumDecl(EnumDeclaration {
553            name,
554            members,
555            public,
556            span: self.join(start),
557        }))
558    }
559
560    fn parse_type_decl(&mut self, public: bool) -> Result<Stmt<'a>, Diagnostic> {
561        let start = self.span();
562        self.bump(); // type
563        let (name, _) = self.expect_ident("schema name")?;
564        self.expect(&TokenKind::Newline, "newline after schema name")?;
565        let mut fields = Vec::new();
566        loop {
567            self.skip_newlines();
568            if self.eat(&TokenKind::End) {
569                break;
570            }
571            let (field, _) = self.expect_ident("field name")?;
572            self.expect(&TokenKind::Colon, "`:` after field name")?;
573            let (ty, _) = self.expect_ident("field type")?;
574            let ty = match ty {
575                "String" => TypeName::String,
576                "Int" => TypeName::Int,
577                "Float" => TypeName::Float,
578                "Bool" => TypeName::Bool,
579                "List" => TypeName::List,
580                "Object" => TypeName::Object,
581                other => TypeName::Custom(other),
582            };
583            // `name: Type = default` makes the field optional (default in the instance scope).
584            let default = if self.eat(&TokenKind::Assign) {
585                Some(self.parse_expr(0)?)
586            } else {
587                None
588            };
589            fields.push(SchemaField {
590                name: field,
591                ty,
592                default,
593            });
594        }
595        Ok(Stmt::TypeDecl(SchemaDeclaration {
596            name,
597            fields,
598            public,
599            span: self.join(start),
600        }))
601    }
602
603    fn parse_func_decl(&mut self, public: bool) -> Result<Stmt<'a>, Diagnostic> {
604        let start = self.span();
605        self.bump(); // def
606        let (name, _) = self.expect_ident("function name")?;
607        self.expect(&TokenKind::LParen, "`(` after function name")?;
608        let params = self.parse_param_list()?;
609        self.expect(&TokenKind::Newline, "newline after function signature")?;
610        let body = self.parse_stmt_body("function")?;
611        Ok(Stmt::FuncDecl {
612            name,
613            params,
614            body,
615            public,
616            span: self.join(start),
617        })
618    }
619
620    fn parse_param_list(&mut self) -> Result<Vec<&'a str>, Diagnostic> {
621        let mut params = Vec::new();
622        if !self.eat(&TokenKind::RParen) {
623            loop {
624                let (p, _) = self.expect_ident("parameter name")?;
625                params.push(p);
626                if !self.eat(&TokenKind::Comma) {
627                    break;
628                }
629            }
630            self.expect(&TokenKind::RParen, "`)` after parameters")?;
631        }
632        Ok(params)
633    }
634
635    fn parse_assert(&mut self) -> Result<Stmt<'a>, Diagnostic> {
636        let start = self.span();
637        self.bump(); // assert
638        let cond = self.parse_expr(0)?;
639        let message = if self.eat(&TokenKind::Comma) {
640            Some(self.parse_expr(0)?)
641        } else {
642            None
643        };
644        Ok(Stmt::Assert {
645            cond,
646            message,
647            span: self.join(start),
648        })
649    }
650
651    fn parse_block(&mut self, kind: BlockKind) -> Result<BlockDeclaration<'a>, Diagnostic> {
652        let start = self.span();
653        self.bump(); // domain | component
654        let label = self.parse_expr(0)?;
655        self.expect(&TokenKind::Newline, "newline after block label")?;
656        let body = self.parse_stmt_body("block")?;
657        Ok(BlockDeclaration {
658            kind,
659            label,
660            body,
661            span: self.join(start),
662        })
663    }
664
665    // ---- Expressions (Pratt, SPEC §3.3) ----
666
667    fn parse_expr(&mut self, min_bp: u8) -> Result<Expr<'a>, Diagnostic> {
668        self.enter()?;
669        let r = self.parse_expr_impl(min_bp);
670        self.leave();
671        r
672    }
673
674    fn parse_expr_impl(&mut self, min_bp: u8) -> Result<Expr<'a>, Diagnostic> {
675        let start = self.span();
676        let mut lhs = self.parse_prefix()?;
677        loop {
678            match self.peek() {
679                TokenKind::Dot => {
680                    lhs = self.parse_postfix_dot(lhs, start)?;
681                }
682                TokenKind::LParen => {
683                    let args = self.parse_args()?;
684                    lhs = Expr::Call {
685                        callee: Box::new(lhs),
686                        args,
687                        span: self.join(start),
688                    };
689                }
690                // List indexing `xs[0]` (D11)
691                TokenKind::LBracket => {
692                    self.bump();
693                    // obj["key"] — hint towards dot form (E0318)
694                    if matches!(self.peek(), TokenKind::Str(_))
695                        && matches!(self.peek_at(1), TokenKind::RBracket)
696                    {
697                        let mut d = self.err(
698                            "E0318",
699                            "bracket access on objects is not supported",
700                            "string key in brackets",
701                        );
702                        if let TokenKind::Str(k) = self.peek() {
703                            d.help = Some(format!("use dot access instead: `.\"{k}\"`"));
704                        }
705                        return Err(d);
706                    }
707                    let key = self.parse_expr(0)?;
708                    self.expect(&TokenKind::RBracket, "closing `]` in index")?;
709                    lhs = Expr::Index {
710                        recv: Box::new(lhs),
711                        key: Box::new(key),
712                        bracket: true,
713                        span: self.join(start),
714                    };
715                }
716                TokenKind::Question if TERNARY_LBP >= min_bp => {
717                    self.bump();
718                    let then = self.parse_expr(0)?;
719                    self.expect(&TokenKind::Colon, "`:` in ternary expression")?;
720                    let otherwise = self.parse_expr(TERNARY_LBP - 1)?; // right associativity
721                    lhs = Expr::Ternary {
722                        cond: Box::new(lhs),
723                        then: Box::new(then),
724                        otherwise: Box::new(otherwise),
725                        span: self.join(start),
726                    };
727                }
728                k => {
729                    let Some((lbp, rbp, op)) = infix_bp(k) else {
730                        break;
731                    };
732                    if lbp < min_bp {
733                        break;
734                    }
735                    self.bump();
736                    let rhs = self.parse_expr(rbp)?;
737                    lhs = Expr::Binary {
738                        op,
739                        lhs: Box::new(lhs),
740                        rhs: Box::new(rhs),
741                        span: self.join(start),
742                    };
743                }
744            }
745        }
746        Ok(lhs)
747    }
748
749    fn parse_prefix(&mut self) -> Result<Expr<'a>, Diagnostic> {
750        let sp = self.span();
751        match self.peek() {
752            TokenKind::Int(n) => {
753                let n = *n;
754                self.bump();
755                Ok(Expr::Literal(LitValue::Int(n), sp))
756            }
757            TokenKind::Float(n) => {
758                let n = *n;
759                self.bump();
760                Ok(Expr::Literal(LitValue::Float(n), sp))
761            }
762            TokenKind::Str(s) => {
763                let s = *s;
764                self.bump();
765                Ok(Expr::Literal(LitValue::Str(s), sp))
766            }
767            TokenKind::InterpStr(parts) => {
768                let parts = parts.clone();
769                self.bump();
770                Ok(Expr::Literal(LitValue::InterpStr(parts), sp))
771            }
772            TokenKind::True => {
773                self.bump();
774                Ok(Expr::Literal(LitValue::Bool(true), sp))
775            }
776            TokenKind::False => {
777                self.bump();
778                Ok(Expr::Literal(LitValue::Bool(false), sp))
779            }
780            TokenKind::Null => {
781                self.bump();
782                Ok(Expr::Literal(LitValue::Null, sp))
783            }
784            TokenKind::Ident(s) => {
785                let s = *s;
786                self.bump();
787                Ok(Expr::Variable(s, sp))
788            }
789            TokenKind::Minus => {
790                self.bump();
791                let rhs = self.parse_expr(UNARY_RBP)?;
792                Ok(Expr::Unary {
793                    op: UnaryOp::Neg,
794                    rhs: Box::new(rhs),
795                    span: self.join(sp),
796                })
797            }
798            TokenKind::Not => {
799                self.bump();
800                let rhs = self.parse_expr(UNARY_RBP)?;
801                Ok(Expr::Unary {
802                    op: UnaryOp::Not,
803                    rhs: Box::new(rhs),
804                    span: self.join(sp),
805                })
806            }
807            TokenKind::LParen => {
808                if self.lambda_ahead() {
809                    self.parse_lambda()
810                } else {
811                    self.bump();
812                    let e = self.parse_expr(0)?;
813                    self.expect(&TokenKind::RParen, "closing `)`")?;
814                    Ok(e)
815                }
816            }
817            TokenKind::LBracket => self.parse_list(),
818            TokenKind::New => {
819                self.bump();
820                let (first, _) = self.expect_ident("schema name after `new`")?;
821                // D12: `new alias.Schema` — an imported schema
822                let (schema, schema_alias) = if self.eat(&TokenKind::Dot) {
823                    let (name, _) = self.expect_ident("schema name after module alias")?;
824                    (name, Some(first))
825                } else {
826                    (first, None)
827                };
828                self.expect(&TokenKind::Newline, "newline after `new SchemaName`")?;
829                let body = self.parse_object_body()?;
830                Ok(Expr::SchemaInstance {
831                    schema,
832                    schema_alias,
833                    body,
834                    span: self.join(sp),
835                })
836            }
837            TokenKind::Cond => self.parse_cond(),
838            _ => Err(self.err(
839                "E0204",
840                "expected expression",
841                format!("found {:?}", self.peek()),
842            )),
843        }
844    }
845
846    /// Lookahead from the current position (LParen): `(` [Ident (`,` Ident)*] `)` `->` ?
847    fn lambda_ahead(&self) -> bool {
848        let mut i = self.pos + 1;
849        loop {
850            match self.toks.get(i).map(|t| &t.kind) {
851                Some(TokenKind::RParen) => {
852                    return matches!(
853                        self.toks.get(i + 1).map(|t| &t.kind),
854                        Some(TokenKind::Arrow)
855                    )
856                }
857                Some(TokenKind::Ident(_)) => match self.toks.get(i + 1).map(|t| &t.kind) {
858                    Some(TokenKind::Comma) => i += 2,
859                    Some(TokenKind::RParen) => {
860                        return matches!(
861                            self.toks.get(i + 2).map(|t| &t.kind),
862                            Some(TokenKind::Arrow)
863                        )
864                    }
865                    _ => return false,
866                },
867                _ => return false,
868            }
869        }
870    }
871
872    /// `(params) -> body end`; body is an expression or an object body (SPEC §3.1 LambdaBody).
873    fn parse_lambda(&mut self) -> Result<Expr<'a>, Diagnostic> {
874        let start = self.span();
875        self.bump(); // (
876        let params = self.parse_param_list()?;
877        self.expect(&TokenKind::Arrow, "`->` in lambda")?;
878        // A statement body (`key: v`, `x = e`, `assert ...`) or a single expression.
879        let stmt_body = matches!(
880            (self.peek(), self.peek_at(1)),
881            (TokenKind::Ident(_) | TokenKind::Str(_), TokenKind::Colon)
882                | (TokenKind::Ident(_), TokenKind::Assign)
883                | (
884                    TokenKind::Shadow
885                        | TokenKind::Assert
886                        | TokenKind::Def
887                        | TokenKind::Type
888                        | TokenKind::Pub
889                        | TokenKind::Domain,
890                    _
891                )
892        );
893        let body = if stmt_body {
894            LambdaBody::Block(self.parse_stmt_body("lambda")?)
895        } else {
896            let e = self.parse_expr(0)?;
897            self.skip_newlines();
898            self.expect(&TokenKind::End, "`end` closing lambda body")?;
899            LambdaBody::Expr(Box::new(e))
900        };
901        Ok(Expr::Lambda {
902            params,
903            body,
904            span: self.join(start),
905        })
906    }
907
908    fn parse_list(&mut self) -> Result<Expr<'a>, Diagnostic> {
909        let start = self.span();
910        self.bump(); // [
911        let mut items = Vec::new();
912        loop {
913            while matches!(self.peek(), TokenKind::Newline | TokenKind::Comma) {
914                self.bump();
915            }
916            if self.eat(&TokenKind::RBracket) {
917                return Ok(Expr::ListLiteral(items, self.join(start)));
918            }
919            if matches!(self.peek(), TokenKind::Eof) {
920                return Err(self.err("E0203", "missing `]`", "list is not closed"));
921            }
922            items.push(self.parse_expr(0)?);
923            if !matches!(
924                self.peek(),
925                TokenKind::Newline | TokenKind::Comma | TokenKind::RBracket
926            ) {
927                return Err(self.err(
928                    "E0205",
929                    "expected list separator",
930                    "elements are separated by newlines or commas",
931                ));
932            }
933        }
934    }
935
936    /// `cond \n (bool -> value)+ else -> value \n end` (D14).
937    fn parse_cond(&mut self) -> Result<Expr<'a>, Diagnostic> {
938        let start = self.span();
939        self.bump(); // cond
940        self.expect(&TokenKind::Newline, "newline after `cond`")?;
941        let mut arms = Vec::new();
942        loop {
943            self.skip_newlines();
944            if matches!(self.peek(), TokenKind::Else) {
945                break;
946            }
947            if matches!(self.peek(), TokenKind::End | TokenKind::Eof) {
948                return Err(self.err(
949                    "E0207",
950                    "`cond` requires an `else` arm",
951                    "add `else -> ...` before `end`",
952                ));
953            }
954            let condition = self.parse_expr(0)?;
955            self.expect(&TokenKind::Arrow, "`->` after a cond condition")?;
956            let value = self.parse_expr(0)?;
957            arms.push((condition, value));
958            self.eat_separator()?;
959        }
960        self.bump(); // else
961        self.expect(&TokenKind::Arrow, "`->` after `else`")?;
962        let otherwise = self.parse_expr(0)?;
963        self.skip_newlines();
964        self.expect(&TokenKind::End, "`end` to close `cond`")?;
965        Ok(Expr::Cond {
966            arms,
967            otherwise: Box::new(otherwise),
968            span: self.join(start),
969        })
970    }
971
972    fn parse_args(&mut self) -> Result<Vec<Expr<'a>>, Diagnostic> {
973        self.bump(); // (
974        let mut args = Vec::new();
975        if !self.eat(&TokenKind::RParen) {
976            loop {
977                args.push(self.parse_expr(0)?);
978                if !self.eat(&TokenKind::Comma) {
979                    break;
980                }
981            }
982            self.expect(&TokenKind::RParen, "`)` after arguments")?;
983        }
984        Ok(args)
985    }
986
987    /// `.field` | `."string key"` | `."#{dynamic}"` | `.method(args)`
988    /// | `.method (params) -> ... end` (trailing lambda)
989    fn parse_postfix_dot(&mut self, recv: Expr<'a>, start: Span) -> Result<Expr<'a>, Diagnostic> {
990        self.bump(); // .
991                     // D11: string key after the dot — access to an arbitrary field name
992        match self.peek() {
993            TokenKind::Str(key) => {
994                let key = *key;
995                self.bump();
996                return Ok(Expr::FieldAccess {
997                    recv: Box::new(recv),
998                    field: key,
999                    span: self.join(start),
1000                });
1001            }
1002            TokenKind::InterpStr(parts) => {
1003                let parts = parts.clone();
1004                let ksp = self.span();
1005                self.bump();
1006                let key = Expr::Literal(LitValue::InterpStr(parts), ksp);
1007                return Ok(Expr::Index {
1008                    recv: Box::new(recv),
1009                    key: Box::new(key),
1010                    bracket: false,
1011                    span: self.join(start),
1012                });
1013            }
1014            _ => {}
1015        }
1016        let (name, _) = self.expect_ident("field or method name after `.`")?;
1017        if matches!(self.peek(), TokenKind::LParen) {
1018            let (args, lambda) = if self.lambda_ahead() {
1019                (Vec::new(), Some(Box::new(self.parse_lambda()?)))
1020            } else {
1021                let args = self.parse_args()?;
1022                let lambda = if matches!(self.peek(), TokenKind::LParen) && self.lambda_ahead() {
1023                    Some(Box::new(self.parse_lambda()?))
1024                } else {
1025                    None
1026                };
1027                (args, lambda)
1028            };
1029            Ok(Expr::MethodCall {
1030                recv: Box::new(recv),
1031                method: name,
1032                args,
1033                lambda,
1034                span: self.join(start),
1035            })
1036        } else {
1037            Ok(Expr::FieldAccess {
1038                recv: Box::new(recv),
1039                field: name,
1040                span: self.join(start),
1041            })
1042        }
1043    }
1044}
1045
1046#[cfg(test)]
1047mod tests {
1048    use super::*;
1049    use crate::lexer::Lexer;
1050
1051    fn module(src: &str) -> Module<'_> {
1052        let toks = Lexer::new(src, 0).tokenize().expect("lex ok");
1053        Parser::new(toks).parse_module().expect("parse ok")
1054    }
1055
1056    fn expr(src: &str) -> Expr<'_> {
1057        let m = module(src);
1058        match m.stmts.into_iter().next().expect("one stmt") {
1059            Stmt::Expr(e) => e,
1060            other => panic!("expected expr stmt, got {other:?}"),
1061        }
1062    }
1063
1064    fn first_err(src: &str) -> &'static str {
1065        let toks = Lexer::new(src, 0).tokenize().expect("lex ok");
1066        Parser::new(toks)
1067            .parse_module()
1068            .expect_err("parse must fail")[0]
1069            .code
1070    }
1071
1072    #[test]
1073    fn deep_nesting_is_e0208_not_a_crash() {
1074        // Crafted deeply-nested input must be rejected (E0208), never overflow the
1075        // stack. Worth running under `cargo test --release` as well as in debug:
1076        // release has no parser thread, so that run is what proves 256 frames fit
1077        // on an ordinary stack.
1078        let parens = format!("x: {}1{}", "(".repeat(2000), ")".repeat(2000));
1079        assert_eq!(first_err(&parens), "E0208");
1080        let brackets = format!("x: {}", "[".repeat(2000));
1081        assert_eq!(first_err(&brackets), "E0208");
1082        let blocks = format!(
1083            "{}\n{}",
1084            (0..2000)
1085                .map(|i| format!("domain \"d{i}\""))
1086                .collect::<Vec<_>>()
1087                .join("\n"),
1088            "end\n".repeat(2000)
1089        );
1090        assert_eq!(first_err(&blocks), "E0208");
1091        // realistic nesting (10 levels of parens) stays well under the cap
1092        let ok = format!("x: {}1{}", "(".repeat(10), ")".repeat(10));
1093        module(&ok);
1094    }
1095
1096    #[test]
1097    fn enum_parses_d18() {
1098        let m = module(
1099            "enum Tier
1100  \"frontend\"
1101  \"backend\"
1102end
1103",
1104        );
1105        let Stmt::EnumDecl(e) = &m.stmts[0] else {
1106            panic!("expected an enum declaration")
1107        };
1108        assert_eq!(e.name, "Tier");
1109        assert_eq!(e.members, vec!["frontend", "backend"]);
1110        assert!(!e.public);
1111    }
1112
1113    #[test]
1114    fn enum_errors_are_specific() {
1115        assert_eq!(
1116            first_err(
1117                "enum T
1118  bare
1119end
1120"
1121            ),
1122            "E0211"
1123        ); // not a string
1124        assert_eq!(
1125            first_err(
1126                "enum T
1127  \"a\"
1128  \"a\"
1129end
1130"
1131            ),
1132            "E0212"
1133        ); // duplicate
1134        assert_eq!(
1135            first_err(
1136                "enum T
1137end
1138"
1139            ),
1140            "E0213"
1141        ); // empty
1142    }
1143
1144    #[test]
1145    fn cond_parses_d14() {
1146        let m = module("t: cond\n  a == 1 -> \"x\"\n  else -> \"y\"\nend");
1147        let Stmt::Property {
1148            value: Expr::Cond { arms, .. },
1149            ..
1150        } = &m.stmts[0]
1151        else {
1152            panic!("expected cond property, got {:?}", m.stmts[0])
1153        };
1154        assert_eq!(arms.len(), 1);
1155        // a missing `else` arm is a parse error (D14)
1156        assert_eq!(first_err("x: cond\n  true -> 1\nend"), "E0207");
1157    }
1158
1159    #[test]
1160    fn mul_binds_tighter_than_add() {
1161        let Expr::Binary {
1162            op: BinOp::Add,
1163            rhs,
1164            ..
1165        } = expr("1 + 2 * 3")
1166        else {
1167            panic!()
1168        };
1169        assert!(matches!(*rhs, Expr::Binary { op: BinOp::Mul, .. }));
1170    }
1171
1172    #[test]
1173    fn comparison_vs_logic_precedence() {
1174        // a == b && c < d  →  (a == b) && (c < d)
1175        let Expr::Binary {
1176            op: BinOp::And,
1177            lhs,
1178            rhs,
1179            ..
1180        } = expr("a == b && c < d")
1181        else {
1182            panic!()
1183        };
1184        assert!(matches!(*lhs, Expr::Binary { op: BinOp::Eq, .. }));
1185        assert!(matches!(*rhs, Expr::Binary { op: BinOp::Lt, .. }));
1186    }
1187
1188    #[test]
1189    fn ternary_is_right_associative() {
1190        // a ? b : c ? d : e  →  a ? b : (c ? d : e)
1191        let Expr::Ternary { otherwise, .. } = expr("a ? b : c ? d : e") else {
1192            panic!()
1193        };
1194        assert!(matches!(*otherwise, Expr::Ternary { .. }));
1195    }
1196
1197    #[test]
1198    fn method_chains_are_left_associative() {
1199        // xs.compact().uniq() → MethodCall{ recv: MethodCall{ recv: xs, compact }, uniq }
1200        let Expr::MethodCall {
1201            method: "uniq",
1202            recv,
1203            ..
1204        } = expr("xs.compact().uniq()")
1205        else {
1206            panic!()
1207        };
1208        assert!(matches!(
1209            *recv,
1210            Expr::MethodCall {
1211                method: "compact",
1212                ..
1213            }
1214        ));
1215    }
1216
1217    #[test]
1218    fn field_access_chain() {
1219        let Expr::FieldAccess {
1220            field: "version",
1221            recv,
1222            ..
1223        } = expr("cargo_data.package.version")
1224        else {
1225            panic!()
1226        };
1227        assert!(matches!(
1228            *recv,
1229            Expr::FieldAccess {
1230                field: "package",
1231                ..
1232            }
1233        ));
1234    }
1235
1236    #[test]
1237    fn trailing_lambda_on_map() {
1238        let src = "xs.map (name, index) -> name end";
1239        let Expr::MethodCall {
1240            method: "map",
1241            args,
1242            lambda: Some(l),
1243            ..
1244        } = expr(src)
1245        else {
1246            panic!()
1247        };
1248        assert!(args.is_empty());
1249        let Expr::Lambda { params, .. } = *l else {
1250            panic!()
1251        };
1252        assert_eq!(params, vec!["name", "index"]);
1253    }
1254
1255    #[test]
1256    fn list_with_unary_minus_element_d2() {
1257        let Expr::ListLiteral(items, _) = expr("[a\n-b]") else {
1258            panic!()
1259        };
1260        assert_eq!(items.len(), 2);
1261        assert!(matches!(
1262            items[1],
1263            Expr::Unary {
1264                op: UnaryOp::Neg,
1265                ..
1266            }
1267        ));
1268    }
1269
1270    #[test]
1271    fn inline_block_is_e0201() {
1272        assert_eq!(first_err("metrics port: 9090 path: \"/metrics\""), "E0201");
1273    }
1274
1275    #[test]
1276    fn shadow_assignment_d7() {
1277        let m = module("shadow x = 1");
1278        assert!(matches!(
1279            m.stmts[0],
1280            Stmt::Assign {
1281                name: "x",
1282                shadow: true,
1283                ..
1284            }
1285        ));
1286    }
1287
1288    #[test]
1289    fn new_schema_instance_d4() {
1290        let m = module("m = new ServiceMeta\n  name: \"auth\"\n  port: 8001\nend");
1291        let Stmt::Assign {
1292            value:
1293                Expr::SchemaInstance {
1294                    schema: "ServiceMeta",
1295                    body,
1296                    ..
1297                },
1298            ..
1299        } = &m.stmts[0]
1300        else {
1301            panic!()
1302        };
1303        assert_eq!(body.props.len(), 2);
1304    }
1305
1306    #[test]
1307    fn assert_with_message_d5() {
1308        let m = module("assert xs.len() >= 1, \"too few\"");
1309        assert!(matches!(
1310            m.stmts[0],
1311            Stmt::Assert {
1312                message: Some(_),
1313                ..
1314            }
1315        ));
1316    }
1317
1318    #[test]
1319    fn nested_object_blocks() {
1320        let m = module("domain \"d\"\n  security:\n    tls: true\n    certs:\n      path: \"/x\"\n    end\n  end\nend");
1321        let Stmt::Block(b) = &m.stmts[0] else {
1322            panic!()
1323        };
1324        let Stmt::Property {
1325            key: "security",
1326            value: Expr::ObjectLiteral(body),
1327            ..
1328        } = &b.body[0]
1329        else {
1330            panic!()
1331        };
1332        assert!(matches!(
1333            body.props[1],
1334            ("certs", Expr::ObjectLiteral(_), _)
1335        ));
1336    }
1337
1338    #[test]
1339    fn dot_string_field_access_d11() {
1340        // a."eu west".port → FieldAccess(FieldAccess(a, "eu west"), "port")
1341        let Expr::FieldAccess {
1342            field: "port",
1343            recv,
1344            ..
1345        } = expr("a.\"eu west\".port")
1346        else {
1347            panic!()
1348        };
1349        assert!(matches!(
1350            *recv,
1351            Expr::FieldAccess {
1352                field: "eu west",
1353                ..
1354            }
1355        ));
1356    }
1357
1358    #[test]
1359    fn dynamic_key_desugars_to_index_d11() {
1360        let Expr::Index {
1361            bracket: false,
1362            key,
1363            ..
1364        } = expr("a.\"#{r}\"")
1365        else {
1366            panic!()
1367        };
1368        assert!(matches!(*key, Expr::Literal(LitValue::InterpStr(_), _)));
1369    }
1370
1371    #[test]
1372    fn list_indexing_d11() {
1373        // xs[0][1] — chained indexing
1374        let Expr::Index {
1375            bracket: true,
1376            recv,
1377            ..
1378        } = expr("xs[0][1]")
1379        else {
1380            panic!()
1381        };
1382        assert!(matches!(*recv, Expr::Index { bracket: true, .. }));
1383    }
1384
1385    #[test]
1386    fn bracket_string_key_is_e0318() {
1387        assert_eq!(first_err("x = a[\"key\"]"), "E0318");
1388    }
1389
1390    #[test]
1391    fn string_property_keys_d11() {
1392        let m = module("domain \"d\"\n  \"app.kubernetes.io/name\": \"auth\"\nend");
1393        let Stmt::Block(b) = &m.stmts[0] else {
1394            panic!()
1395        };
1396        assert!(matches!(
1397            b.body[0],
1398            Stmt::Property {
1399                key: "app.kubernetes.io/name",
1400                ..
1401            }
1402        ));
1403    }
1404
1405    #[test]
1406    fn pub_def_and_type_d12() {
1407        let m = module("pub def f(x)\n  a: x\nend\npub type T\n  a: Int\nend\ndef g()\n  b: 1\nend\nassert g().b == 1 && f == f && T == T");
1408        assert!(matches!(m.stmts[0], Stmt::FuncDecl { public: true, .. }));
1409        assert!(matches!(&m.stmts[1], Stmt::TypeDecl(s) if s.public));
1410        assert!(matches!(m.stmts[2], Stmt::FuncDecl { public: false, .. }));
1411        // pub not before def/type — an error
1412        assert_eq!(first_err("pub x = 1"), "E0206");
1413    }
1414
1415    #[test]
1416    fn new_with_module_alias_d12() {
1417        let m = module("m: new pkg.Meta\n  a: 1\nend");
1418        let Stmt::Property {
1419            value:
1420                Expr::SchemaInstance {
1421                    schema: "Meta",
1422                    schema_alias: Some("pkg"),
1423                    ..
1424                },
1425            ..
1426        } = &m.stmts[0]
1427        else {
1428            panic!()
1429        };
1430    }
1431
1432    #[test]
1433    fn missing_end_is_e0203() {
1434        assert_eq!(first_err("domain \"d\"\n  x = 1\n"), "E0203");
1435    }
1436}