beck_syntax/
parser.rs

1//! The Python surface: recursive descent for statements, Pratt for expressions.
2//!
3//! [`docs/02-syntax.md`](../../../../../docs/02-syntax.md) §2.8: "hand-written recursive descent +
4//! Pratt for expressions. **Not** a parser generator. Rationale: error messages and error
5//! *recovery* are the top-two UX properties of a new language."
6//!
7//! The output is the *same* `Node` tree the S-expression reader produces — that equivalence is
8//! asserted directly in `tests/surfaces.rs`, and it is the whole claim of §2.2. Two surface rules
9//! carry most of the weight:
10//!
11//! * **The block rule** (§2.3): any call written `f(args):` followed by an indented block desugars
12//!   to `f(args, do=quote(block))`. That single rule buys the entire Lisp special-form vocabulary
13//!   with Python punctuation — `ui:`, `atomically:`, `retry(times=3):` are all ordinary calls.
14//! * **Decorators are AST transforms** (§2.3): `@on(server)` before a `def` becomes
15//!   `(decorate (on server) (def ...))`, so the decorator receives the definition's AST rather
16//!   than a function object.
17
18use beck_diag::depth::Nesting;
19use beck_diag::{Diagnostic, Diagnostics, FileId, Span};
20
21use crate::lexer::{lex, Raw, Tok, Token};
22use crate::node::{sym, Head, Lit, Node, Symbol};
23
24pub struct Parser<'a> {
25    toks: Vec<Token>,
26    pos: usize,
27    diags: &'a mut Diagnostics,
28    file: FileId,
29    /// Set once the parser has bailed out of a construct, so a cascade of follow-on errors from
30    /// one real mistake does not bury it.
31    poisoned: bool,
32    /// Set by [`Parser::attach_block`] so the statement parser knows the expression is finished.
33    attached_block: bool,
34    /// How deep the parser is inside brackets and indentation, against the ceiling every part of
35    /// the front end shares. Unlike the `depth` locals in this file — which are balance counters
36    /// serving error recovery and the layout algorithm — this one is a bound.
37    nesting: Nesting,
38    /// Non-zero inside a `test`/`property` body, where `given`, `when`, `expect` and `stub` are
39    /// clause keywords. They are *not* reserved anywhere else: a program with a function called
40    /// `expect` keeps working, and §21.2's construct does not cost the language four words.
41    in_test: usize,
42}
43
44/// Parse a whole module.
45pub fn parse_module(file: FileId, name: &str, src: &str, diags: &mut Diagnostics) -> Node {
46    let toks = lex(file, src, diags);
47    let mut p = Parser {
48        toks,
49        pos: 0,
50        diags,
51        file,
52        poisoned: false,
53        attached_block: false,
54        nesting: Nesting::new(),
55        in_test: 0,
56    };
57    let mut items = vec![Node::sym(name, Span::new(file, 0..0))];
58    while !p.at_eof() {
59        p.skip_newlines();
60        if p.at_eof() {
61            break;
62        }
63        match p.item() {
64            Some(item) => items.push(item),
65            None => p.recover_to_next_item(),
66        }
67    }
68    Node::form(sym::MODULE, items, Span::new(file, 0..src.len()))
69}
70
71/// Parse a single expression — used by `beck ast` and by tests.
72pub fn parse_expr_str(file: FileId, src: &str, diags: &mut Diagnostics) -> Option<Node> {
73    let toks = lex(file, src, diags);
74    let mut p = Parser {
75        toks,
76        pos: 0,
77        diags,
78        file,
79        poisoned: false,
80        attached_block: false,
81        nesting: Nesting::new(),
82        in_test: 0,
83    };
84    p.skip_newlines();
85    p.expr()
86}
87
88impl<'a> Parser<'a> {
89    // ---------------------------------------------------------------- token helpers
90
91    fn cur(&self) -> &Token {
92        &self.toks[self.pos.min(self.toks.len() - 1)]
93    }
94
95    fn at_eof(&self) -> bool {
96        matches!(self.cur().tok, Tok::Eof)
97    }
98
99    fn span(&self) -> Span {
100        self.cur().span
101    }
102
103    fn bump(&mut self) -> Token {
104        let t = self.cur().clone();
105        if self.pos < self.toks.len() - 1 {
106            self.pos += 1;
107        }
108        t
109    }
110
111    fn at(&self, r: &Raw) -> bool {
112        self.cur().raw() == Some(r)
113    }
114
115    fn at_kw(&self, name: &str) -> bool {
116        self.cur().is_ident(name)
117    }
118
119    fn eat(&mut self, r: &Raw) -> bool {
120        if self.at(r) {
121            self.bump();
122            true
123        } else {
124            false
125        }
126    }
127
128    fn eat_kw(&mut self, name: &str) -> bool {
129        if self.at_kw(name) {
130            self.bump();
131            true
132        } else {
133            false
134        }
135    }
136
137    fn expect(&mut self, r: &Raw, what: &str) -> bool {
138        if self.eat(r) {
139            return true;
140        }
141        self.error(format!("expected {what}, found {}", self.cur().describe()));
142        false
143    }
144
145    fn error(&mut self, msg: impl Into<String>) {
146        if self.poisoned {
147            return;
148        }
149        self.poisoned = true;
150        let span = self.span();
151        self.diags.push(
152            Diagnostic::error("B0120", msg.into(), span).with_primary_label("unexpected here"),
153        );
154    }
155
156    fn skip_newlines(&mut self) {
157        while matches!(self.cur().tok, Tok::Newline) {
158            self.bump();
159        }
160    }
161
162    /// Skip forward to something that can start a top-level item, so one bad line does not make
163    /// the rest of the file unparseable.
164    fn recover_to_next_item(&mut self) {
165        self.poisoned = false;
166        let mut depth = 0i32;
167        loop {
168            match &self.cur().tok {
169                Tok::Eof => return,
170                Tok::Indent => {
171                    depth += 1;
172                    self.bump();
173                }
174                Tok::Dedent => {
175                    depth -= 1;
176                    self.bump();
177                    if depth <= 0 {
178                        return;
179                    }
180                }
181                Tok::Newline if depth <= 0 => {
182                    self.bump();
183                    return;
184                }
185                _ => {
186                    self.bump();
187                }
188            }
189        }
190    }
191
192    fn ident(&mut self, what: &str) -> Option<(String, Span)> {
193        let span = self.span();
194        match self.cur().raw() {
195            Some(Raw::Ident(s)) => {
196                let s = s.clone();
197                self.bump();
198                Some((s, span))
199            }
200            _ => {
201                self.error(format!("expected {what}, found {}", self.cur().describe()));
202                None
203            }
204        }
205    }
206
207    // ---------------------------------------------------------------- items
208
209    fn item(&mut self) -> Option<Node> {
210        if self.at(&Raw::At) {
211            return self.decorated();
212        }
213        let start = self.span();
214        if self.at_kw("def") {
215            return self.def_item();
216        }
217        // `row Failure = raises(FormError), log` — Koka's community supplies the argument for this
218        // being in the design from the start rather than added when rows get long (`docs/38`
219        // §38.4): five- and six-label rows are ordinary, and a language that makes you write them
220        // out is a language whose signatures nobody reads.
221        if self.at_kw("row") {
222            self.bump();
223            let (name, name_span) = self.ident("a row name")?;
224            self.expect(&Raw::Eq, "`=`");
225            let mut atoms = Vec::new();
226            loop {
227                atoms.push(self.expr()?);
228                if !self.eat(&Raw::Comma) {
229                    break;
230                }
231            }
232            let span = start.to(atoms.last().map(|a| a.span()).unwrap_or(name_span));
233            self.end_of_line();
234            let mut items = vec![Node::sym(name, name_span)];
235            items.extend(atoms);
236            return Some(Node::form(sym::ROW, items, span));
237        }
238        // `identity = external(issuer="https://login.acme.com")` — D6's block, as a declaration.
239        //
240        // Guarded on the `=` rather than on the word, because `identity` is an ordinary name a
241        // program may already use: SICP's §1.3.3 defines `def identity(n: Int)`, which starts with
242        // `def` and never reaches here, and a signal called `identity` would be followed by a `:`.
243        // Only `identity =` at the top level is this.
244        if self.at_kw("identity") && matches!(self.peek_raw(1), Some(Raw::Eq)) {
245            self.bump();
246            self.expect(&Raw::Eq, "`=`");
247            let provider = self.expr()?;
248            let span = start.to(provider.span());
249            self.end_of_line();
250            return Some(Node::form(sym::IDENTITY, vec![provider], span));
251        }
252        if self.at_kw("macro") {
253            return self.macro_item();
254        }
255        if self.at_kw("model") {
256            return self.model_item();
257        }
258        if self.at_kw("union") {
259            return self.union_item();
260        }
261        if self.at_kw("trait") {
262            return self.trait_item();
263        }
264        if self.at_kw("impl") {
265            return self.impl_item();
266        }
267        if self.at_kw("type") {
268            return self.type_item();
269        }
270        if self.at_kw("import") {
271            self.bump();
272            let (name, s) = self.ident("a module name")?;
273            let mut path = name;
274            let mut span = start.to(s);
275            while self.at(&Raw::Dot) {
276                self.bump();
277                let (seg, s2) = self.ident("a module name")?;
278                path.push('.');
279                path.push_str(&seg);
280                span = span.to(s2);
281            }
282            self.end_of_line();
283            return Some(Node::form(sym::IMPORT, vec![Node::sym(path, span)], span));
284        }
285        if self.at_kw("test") {
286            self.bump();
287            let (name, name_span) = self.quoted_name("a test name")?;
288            self.expect(&Raw::Colon, "`:`");
289            let body = self.test_body()?;
290            let span = start.to(body.span());
291            return Some(Node::form(
292                sym::TEST,
293                vec![Node::lit(Lit::Str(name.into()), name_span), body],
294                span,
295            ));
296        }
297        // `property "…" (events: list[Event]):` — §11.10. The same clauses as a `test`, with the
298        // parameters supplied by the generator instead of written out.
299        if self.at_kw("property") && matches!(self.peek_raw(1), Some(Raw::Str(_))) {
300            self.bump();
301            let (name, name_span) = self.quoted_name("a property name")?;
302            let params = self.params()?;
303            self.expect(&Raw::Colon, "`:`");
304            let body = self.test_body()?;
305            let span = start.to(body.span());
306            return Some(Node::form(
307                sym::PROPERTY,
308                vec![Node::lit(Lit::Str(name.into()), name_span), params, body],
309                span,
310            ));
311        }
312        // Anything else at top level is a statement — a module-level `let`, or an expression.
313        self.statement()
314    }
315
316    fn decorated(&mut self) -> Option<Node> {
317        let start = self.span();
318        self.bump(); // @
319        let deco = self.postfix(false)?;
320        self.end_of_line();
321        self.skip_newlines();
322        let inner = self.item()?;
323        let span = start.to(inner.span());
324        Some(Node::form(sym::DECORATE, vec![deco, inner], span))
325    }
326
327    /// `def name(params) -> Ret uses eff, eff:` + block
328    fn def_item(&mut self) -> Option<Node> {
329        let start = self.span();
330        self.bump(); // def
331        let (name, name_span) = self.ident("a function name")?;
332        // `def map[T, U](…)` — §3.1's "full inference inside bodies; mandatory annotations on
333        // public signatures", which means a *user's* abstraction says what it is polymorphic in
334        // rather than having it guessed (`docs/32` §32.7).
335        let typarams = self.typarams(name_span);
336        let params = self.params()?;
337        let returns = if self.eat(&Raw::Arrow) {
338            let t = self.type_expr()?;
339            let s = t.span();
340            Node::form(sym::RETURNS, vec![t], s)
341        } else {
342            Node::form(sym::RETURNS, vec![], name_span)
343        };
344
345        // §2.9: effect and placement annotations read better as signature clauses than as
346        // decorators, and they are part of the published module interface (§3.6).
347        let mut uses = Vec::new();
348        if self.eat_kw("uses") {
349            loop {
350                uses.push(self.expr()?);
351                if !self.eat(&Raw::Comma) {
352                    break;
353                }
354            }
355        }
356        let uses_span = uses.first().map(|n| n.span()).unwrap_or(name_span);
357        let uses = Node::form("uses", uses, uses_span);
358
359        // A `def` with no body is a **declaration**: a signature with nothing behind it. It is
360        // what a `.becki` interface file is made of (§3.6), and it is an error in an ordinary
361        // module — but that is `check`'s judgement to make, not the parser's, because the parser
362        // does not know which kind of file it is reading.
363        if !self.at(&Raw::Colon) {
364            let span = start.to(uses.span());
365            return Some(Node::form(
366                sym::DEF,
367                vec![Node::sym(name, name_span), typarams, params, returns, uses],
368                span,
369            ));
370        }
371        self.expect(&Raw::Colon, "`:` before the function body");
372        let body = self.block()?;
373        let span = start.to(body.span());
374        Some(Node::form(
375            sym::DEF,
376            vec![
377                Node::sym(name, name_span),
378                typarams,
379                params,
380                returns,
381                uses,
382                body,
383            ],
384            span,
385        ))
386    }
387
388    fn macro_item(&mut self) -> Option<Node> {
389        let start = self.span();
390        self.bump(); // macro
391        let (name, name_span) = self.ident("a macro name")?;
392        let params = self.params()?;
393        self.expect(&Raw::Colon, "`:` before the macro body");
394        let body = self.block()?;
395        let span = start.to(body.span());
396        Some(Node::form(
397            sym::MACRO,
398            vec![Node::sym(name, name_span), params, body],
399            span,
400        ))
401    }
402
403    /// `[T, U]` or `[T: Show + Eq, U]`, or nothing.
404    ///
405    /// The same list follows the name of a `def`, a `model`, a `union` and a `type`, so a
406    /// declaration and a definition are quantified by the same notation.
407    ///
408    /// A **bound** says which traits the parameter's argument must implement, and it is what lets a
409    /// generic body call a trait method: `[T: Show]` reads as `(: T Show)`, and an unbounded
410    /// parameter stays a bare symbol so that every form that never carries one is unchanged.
411    fn typarams(&mut self, at: Span) -> Node {
412        let start = self.span();
413        if !self.at(&Raw::LBracket) {
414            return Node::form(sym::TYPARAMS, Vec::new(), at);
415        }
416        self.bump();
417        let mut out = Vec::new();
418        while !self.at(&Raw::RBracket) && !self.at_eof() {
419            let Some((name, span)) = self.ident("a type parameter") else {
420                break;
421            };
422            if self.eat(&Raw::Colon) {
423                let mut parts = vec![Node::sym(name, span)];
424                while let Some((t, tspan)) = self.ident("a trait name") {
425                    parts.push(Node::sym(t, tspan));
426                    if !self.eat(&Raw::Plus) {
427                        break;
428                    }
429                }
430                let end = self.span();
431                out.push(Node::form(sym::ANNOT, parts, span.to(end)));
432            } else {
433                out.push(Node::sym(name, span));
434            }
435            if !self.eat(&Raw::Comma) {
436                break;
437            }
438        }
439        let end = self.span();
440        self.expect(&Raw::RBracket, "`]`");
441        Node::form(sym::TYPARAMS, out, start.to(end))
442    }
443
444    fn params(&mut self) -> Option<Node> {
445        let start = self.span();
446        self.expect(&Raw::LParen, "`(`");
447        let mut out = Vec::new();
448        while !self.at(&Raw::RParen) && !self.at_eof() {
449            let (name, name_span) = self.ident("a parameter name")?;
450            let ty = if self.eat(&Raw::Colon) {
451                Some(self.type_expr()?)
452            } else {
453                None
454            };
455            let span = ty
456                .as_ref()
457                .map(|t| name_span.to(t.span()))
458                .unwrap_or(name_span);
459            out.push(match ty {
460                Some(t) => Node::form(sym::ANNOT, vec![Node::sym(name, name_span), t], span),
461                None => Node::sym(name, name_span),
462            });
463            if !self.eat(&Raw::Comma) {
464                break;
465            }
466        }
467        let end = self.span();
468        self.expect(&Raw::RParen, "`)`");
469        Some(Node::form(sym::PARAMS, out, start.to(end)))
470    }
471
472    fn model_item(&mut self) -> Option<Node> {
473        let start = self.span();
474        self.bump(); // model
475        let (name, name_span) = self.ident("a model name")?;
476        let typarams = self.typarams(name_span);
477        self.expect(&Raw::Colon, "`:`");
478        let mut fields = vec![Node::sym(name, name_span), typarams];
479        for line in self.indented_lines()? {
480            let mut p = self.sub(line);
481            if let Some(f) = p.field_decl() {
482                fields.push(f);
483            }
484        }
485        Some(Node::form(sym::MODEL, fields, start))
486    }
487
488    fn union_item(&mut self) -> Option<Node> {
489        let start = self.span();
490        self.bump(); // union
491        let (name, name_span) = self.ident("a union name")?;
492        let typarams = self.typarams(name_span);
493        self.expect(&Raw::Colon, "`:`");
494        let mut variants = vec![Node::sym(name, name_span), typarams];
495        for line in self.indented_lines()? {
496            let mut p = self.sub(line);
497            if let Some(v) = p.variant_decl() {
498                variants.push(v);
499            }
500        }
501        Some(Node::form(sym::UNION, variants, start))
502    }
503
504    fn trait_item(&mut self) -> Option<Node> {
505        let start = self.span();
506        self.bump(); // trait
507        let (name, name_span) = self.ident("a trait name")?;
508        self.expect(&Raw::Colon, "`:`");
509        let body = self.block()?;
510        let mut items = vec![Node::sym(name, name_span)];
511        items.extend(body.args);
512        Some(Node::form(sym::TRAIT, items, start))
513    }
514
515    /// `impl[T] Show for Tree[T]:` — the list binds the names the *target* is written in terms of.
516    ///
517    /// It goes after `impl` rather than after the trait name because that is what it quantifies:
518    /// `Tree[T]` is one impl covering every `T`, and `Show` is not parameterised at all.
519    fn impl_item(&mut self) -> Option<Node> {
520        let start = self.span();
521        self.bump(); // impl
522        let typarams = self.typarams(start);
523        let (trait_name, tspan) = self.ident("a trait name")?;
524        if !self.eat_kw("for") {
525            self.error("expected `for` in an impl declaration");
526            return None;
527        }
528        let ty = self.type_expr()?;
529        let mut items = vec![Node::sym(trait_name, tspan), typarams, ty];
530        // `impl Priced for Item` with nothing after it is a *declaration*, which is what a `.becki`
531        // publishes: an importing module needs to know the implementation exists and what its
532        // signature is, and the bodies stay in the module that wrote them.
533        if self.eat(&Raw::Colon) {
534            let body = self.block()?;
535            items.extend(body.args);
536        } else {
537            self.end_of_line();
538        }
539        Some(Node::form(sym::IMPL, items, start))
540    }
541
542    fn type_item(&mut self) -> Option<Node> {
543        let start = self.span();
544        self.bump(); // type
545        let (name, name_span) = self.ident("a type name")?;
546        let typarams = self.typarams(name_span);
547        self.expect(&Raw::Eq, "`=`");
548        // `type CustomerId = newtype[u64]` — §3.1's zero-cost nominal newtype.
549        if self.at_kw("newtype") {
550            self.bump();
551            self.expect(&Raw::LBracket, "`[`");
552            let inner = self.type_expr()?;
553            self.expect(&Raw::RBracket, "`]`");
554            self.end_of_line();
555            return Some(Node::form(
556                sym::NEWTYPE,
557                vec![Node::sym(name, name_span), typarams, inner],
558                start,
559            ));
560        }
561        let ty = self.type_expr()?;
562        self.end_of_line();
563        Some(Node::form(
564            sym::TYPE,
565            vec![Node::sym(name, name_span), typarams, ty],
566            start,
567        ))
568    }
569
570    fn field_decl(&mut self) -> Option<Node> {
571        let (name, name_span) = self.ident("a field name")?;
572        self.expect(&Raw::Colon, "`:`");
573        let ty = self.type_expr()?;
574        let span = name_span.to(ty.span());
575        Some(Node::form(
576            sym::FIELD,
577            vec![Node::sym(name, name_span), ty],
578            span,
579        ))
580    }
581
582    fn variant_decl(&mut self) -> Option<Node> {
583        let (name, name_span) = self.ident("a variant name")?;
584        let mut items = vec![Node::sym(name, name_span)];
585        if self.eat(&Raw::LParen) {
586            while !self.at(&Raw::RParen) && !self.at_eof() {
587                items.push(self.field_decl()?);
588                if !self.eat(&Raw::Comma) {
589                    break;
590                }
591            }
592            self.expect(&Raw::RParen, "`)`");
593        }
594        Some(Node::form(sym::VARIANT, items, name_span))
595    }
596
597    // ---------------------------------------------------------------- statements
598
599    fn peek_raw(&self, n: usize) -> Option<&Raw> {
600        self.toks
601            .get((self.pos + n).min(self.toks.len() - 1))?
602            .raw()
603    }
604
605    fn quoted_name(&mut self, what: &str) -> Option<(String, Span)> {
606        let span = self.span();
607        match self.cur().raw() {
608            Some(Raw::Str(s)) => {
609                let s = s.clone();
610                self.bump();
611                Some((s, span))
612            }
613            _ => {
614                self.error(format!("expected {what} in quotes"));
615                None
616            }
617        }
618    }
619
620    /// A `test`/`property` body: an ordinary block, parsed with the four clause keywords live.
621    fn test_body(&mut self) -> Option<Node> {
622        self.in_test += 1;
623        let out = self.block();
624        self.in_test -= 1;
625        out
626    }
627
628    // ------------------------------------------------------------ §21.2's clauses
629    //
630    // A test names a log, an input and an expectation, so each is a clause rather than a call: the
631    // checker binds `state`, `events`, `result` and `page` around them, and none of the four words
632    // is reserved outside a test body.
633
634    /// `given <list[Event]>` or `given <list[Event]> by "actor"`.
635    fn given_clause(&mut self) -> Option<Node> {
636        let start = self.span();
637        self.bump(); // given
638        let events = self.expr()?;
639        let mut args = vec![events];
640        if self.eat_kw("by") {
641            let (actor, span) = self.quoted_name("an actor name")?;
642            args.push(Node::lit(Lit::Str(actor.into()), span));
643        }
644        let span = start.to(args.last().map(|a| a.span()).unwrap_or(start));
645        self.end_of_line();
646        Some(Node::form(sym::GIVEN, args, span))
647    }
648
649    /// `when c1, c2` or `when session("ana") sends c1, c2`.
650    ///
651    /// The session slot is always present — `_` when the test did not name one — so the form has
652    /// one shape and the printer has one case. It holds the *actor*, a string literal, rather than
653    /// a `Session` expression: a session is minted by the identity subsystem (§3.7) and a test that
654    /// could build one out of an expression would be a way to forge one.
655    fn when_clause(&mut self) -> Option<Node> {
656        let start = self.span();
657        self.bump(); // when
658                     // `session("ana") sends c` — look ahead for `sends` rather than committing, so that a
659                     // command called `session` is still a command.
660        let session = if self.at_kw("session") && self.line_has_ident("sends") {
661            let (actor, span) = self.session_actor()?;
662            if !self.eat_kw("sends") {
663                self.error("expected `sends` after the session");
664                return None;
665            }
666            Node::lit(Lit::Str(actor.into()), span)
667        } else {
668            Node::sym(sym::WILDCARD, start)
669        };
670        let mut args = vec![session];
671        loop {
672            args.push(self.expr()?);
673            if !self.eat(&Raw::Comma) {
674                break;
675            }
676        }
677        let span = start.to(args.last().map(|a| a.span()).unwrap_or(start));
678        self.end_of_line();
679        Some(Node::form(sym::WHEN, args, span))
680    }
681
682    /// `stub <effect atom>: <value>`, or a block that answers from the call's arguments.
683    ///
684    /// §21.3 rule 2 is the one-line form; rule 3 is the block:
685    ///
686    /// ```text
687    /// stub net.out(payments.example.com):
688    ///     case Charge(amount): Declined
689    ///     case _: Approved
690    /// ```
691    ///
692    /// A block of `case` arms matches on the stubbed definition's parameter — "ordinary Beck
693    /// pattern matching … there is nothing to learn, nothing that composes differently from the
694    /// rest of the language, and no `Expression<Func<…>>` to satisfy". A block of anything else is
695    /// an ordinary body with those parameters in scope, which is the general form the `case` sugar
696    /// is a case of.
697    fn stub_clause(&mut self) -> Option<Node> {
698        let start = self.span();
699        self.bump(); // stub
700        let (atom, atom_span) = self.effect_atom()?;
701        self.expect(&Raw::Colon, "`:`");
702
703        let value = if matches!(self.cur().tok, Tok::Newline) {
704            // `case` directly under `stub` is the doc's notation and has no scrutinee written: the
705            // checker supplies it, because only the checker knows what performs the effect.
706            if self.block_starts_with("case") {
707                self.skip_newlines();
708                self.bump(); // INDENT
709                let arms = self.case_arms()?;
710                Node::form(sym::STUB_ARMS, arms, start.to(self.span()))
711            } else {
712                self.block()?
713            }
714        } else {
715            let e = self.expr()?;
716            self.end_of_line();
717            e
718        };
719
720        let span = start.to(value.span());
721        Some(Node::form(
722            sym::STUB,
723            vec![Node::lit(Lit::Str(atom.into()), atom_span), value],
724            span,
725        ))
726    }
727
728    /// The six shapes of `expect`. Five are decided by a leading keyword; the sixth is an ordinary
729    /// `Bool` expression, optionally followed by `contains`.
730    fn expect_clause(&mut self) -> Option<Node> {
731        let start = self.span();
732        self.bump(); // expect
733
734        // `expect no net.out` — §21.3 rule 4.
735        if self.at_kw("no") {
736            self.bump();
737            let (atom, _) = self.effect_atom()?;
738            let span = start.to(self.span());
739            self.end_of_line();
740            return Some(Node::form(
741                sym::EXPECT_EFFECT,
742                vec![
743                    Node::lit(Lit::Str(atom.into()), span),
744                    Node::sym("none", span),
745                ],
746                span,
747            ));
748        }
749
750        // `expect wire_compatible_with "orders.v1.becki"` — answered from `beck check --wire-compat`'s
751        // own data, without running anything.
752        if self.at_kw("wire_compatible_with") {
753            self.bump();
754            let (path, pspan) = self.quoted_name("a `.becki` path")?;
755            let span = start.to(pspan);
756            self.end_of_line();
757            return Some(Node::form(
758                sym::EXPECT_WIRE,
759                vec![Node::lit(Lit::Str(path.into()), pspan)],
760                span,
761            ));
762        }
763
764        // `expect place(charge) == server` — §3.4's assertability guardrail, beside the code.
765        if self.at_kw("place") && matches!(self.peek_raw(1), Some(Raw::LParen)) {
766            self.bump();
767            self.expect(&Raw::LParen, "`(`");
768            let (name, nspan) = self.ident("a definition or signal name")?;
769            self.expect(&Raw::RParen, "`)`");
770            if !self.eat(&Raw::EqEq) {
771                self.error("expected `==` and a tier");
772                return None;
773            }
774            let (tier, tspan) = self.ident("a tier")?;
775            let span = start.to(tspan);
776            self.end_of_line();
777            return Some(Node::form(
778                sym::EXPECT_PLACE,
779                vec![Node::sym(name, nspan), Node::sym(tier, tspan)],
780                span,
781            ));
782        }
783
784        // `expect flow(ApiKey) reaches nothing on client`.
785        if self.at_kw("flow") && matches!(self.peek_raw(1), Some(Raw::LParen)) {
786            self.bump();
787            self.expect(&Raw::LParen, "`(`");
788            let (name, nspan) = self.ident("a type name")?;
789            self.expect(&Raw::RParen, "`)`");
790            if !(self.eat_kw("reaches") && self.eat_kw("nothing") && self.eat_kw("on")) {
791                self.error("expected `reaches nothing on <tier>`");
792                return None;
793            }
794            let (tier, tspan) = self.ident("a tier")?;
795            let span = start.to(tspan);
796            self.end_of_line();
797            return Some(Node::form(
798                sym::EXPECT_FLOW,
799                vec![Node::sym(name, nspan), Node::sym(tier, tspan)],
800                span,
801            ));
802        }
803
804        // `expect net.out(h) once` / `… times 2` / `… with Charge(amount=2000)`.
805        if self.at_effect_atom() {
806            let (atom, aspan) = self.effect_atom()?;
807            let how = if self.eat_kw("once") {
808                Node::form(
809                    "times",
810                    vec![Node::lit(Lit::Int(1), self.span())],
811                    self.span(),
812                )
813            } else if self.eat_kw("times") {
814                let span = self.span();
815                match self.cur().raw() {
816                    Some(Raw::Int(n)) => {
817                        let n = *n;
818                        self.bump();
819                        Node::form("times", vec![Node::lit(Lit::Int(n), span)], span)
820                    }
821                    _ => {
822                        self.error("expected a count after `times`");
823                        return None;
824                    }
825                }
826            } else if self.eat_kw("with") {
827                let e = self.expr()?;
828                let s = e.span();
829                Node::form("with", vec![e], s)
830            } else {
831                self.error("expected `once`, `times <n>` or `with <value>` after an effect atom");
832                return None;
833            };
834            let span = start.to(how.span());
835            self.end_of_line();
836            return Some(Node::form(
837                sym::EXPECT_EFFECT,
838                vec![Node::lit(Lit::Str(atom.into()), aspan), how],
839                span,
840            ));
841        }
842
843        // `expect page contains "milk"` / `expect page(session("bo")) contains "milk"`. The page is
844        // the subject rather than an expression because rendering one is `per_session(state, view)`
845        // applied — a role the runtime drives, not a function the test scope can hold.
846        if self.at_kw("page") {
847            self.bump();
848            let mut args = Vec::new();
849            if self.at(&Raw::LParen) {
850                self.bump();
851                if !self.eat_kw("session") {
852                    self.error("expected `session(\"actor\")`");
853                    return None;
854                }
855                let (actor, aspan) = self.parenthesised_string("an actor name")?;
856                self.expect(&Raw::RParen, "`)`");
857                args.push(Node::lit(Lit::Str(actor.into()), aspan));
858            }
859            // `expect page matches snapshot` / `… matches snapshot "after checkout"` — §21.2's
860            // golden assertion. The name is optional and defaults to the test's own, which is what
861            // makes the common case a single line; a second unnamed snapshot in one test is a
862            // diagnostic rather than a positional index, because an index rots when arms move.
863            if self.eat_kw("matches") {
864                if !self.eat_kw("snapshot") {
865                    self.error("expected `snapshot`");
866                    return None;
867                }
868                let mut span = start.to(self.span());
869                // Two slots, always, with `none` for the one that was not written. A form whose
870                // arity varies with which optional part is present cannot be read back without
871                // guessing which one it was — the same reason `expect no <atom>` carries a `none`.
872                let name = if matches!(self.peek_raw(0), Some(Raw::Str(_))) {
873                    let (name, nspan) = self.quoted_name("a snapshot name")?;
874                    span = start.to(nspan);
875                    Node::lit(Lit::Str(name.into()), nspan)
876                } else {
877                    Node::sym("none", span)
878                };
879                let actor = args.pop().unwrap_or_else(|| Node::sym("none", span));
880                self.end_of_line();
881                return Some(Node::form(sym::EXPECT_SNAPSHOT, vec![name, actor], span));
882            }
883            if !self.eat_kw("contains") {
884                self.error("expected `contains` or `matches snapshot`");
885                return None;
886            }
887            let needle = self.expr()?;
888            let span = start.to(needle.span());
889            self.end_of_line();
890            args.insert(0, needle);
891            return Some(Node::form(sym::EXPECT_CONTAINS, args, span));
892        }
893
894        // `expect state == fold_of [ … ]` — §21.2's identity test. Folding a log is what the data
895        // tier does, so the comparison names the log and lets the harness fold it.
896        if self.at_kw("state")
897            && matches!(self.peek_raw(1), Some(Raw::EqEq))
898            && matches!(self.peek_raw(2), Some(Raw::Ident(s)) if s == "fold_of")
899        {
900            self.bump(); // state
901            self.bump(); // ==
902            self.bump(); // fold_of
903            let events = self.expr()?;
904            let mut args = vec![events];
905            if self.eat_kw("by") {
906                let (actor, span) = self.quoted_name("an actor name")?;
907                args.push(Node::lit(Lit::Str(actor.into()), span));
908            }
909            let span = start.to(args.last().map(|a| a.span()).unwrap_or(start));
910            self.end_of_line();
911            return Some(Node::form(sym::EXPECT_FOLD, args, span));
912        }
913
914        // The ordinary case: a `Bool` expression, in a scope where `state`, `events` and `result`
915        // are bound. `expect Ok(…)`/`expect Err(…)` is shorthand for `result == …`.
916        let e = self.expr()?;
917        let e = match e.head_name() {
918            Some("Ok" | "Err") if e.applied => {
919                let span = e.span();
920                Node::form("==", vec![Node::sym("result", span), e], span)
921            }
922            _ => e,
923        };
924        let span = start.to(e.span());
925        self.end_of_line();
926        Some(Node::form(sym::EXPECT, vec![e], span))
927    }
928
929    /// The heads an effect atom can start with. Deliberately a closed list: it is what makes
930    /// `expect net.out(h) once` and `expect is_done(state)` decidable without backtracking.
931    const EFFECT_HEADS: &'static [&'static str] = &[
932        "ingress", "durable", "dom", "nondet", "net", "fs", "env", "spawn", "cap", "partial",
933        "external", "log", "metrics",
934    ];
935
936    fn at_effect_atom(&self) -> bool {
937        match self.cur().raw() {
938            Some(Raw::Ident(s)) => Self::EFFECT_HEADS.contains(&s.as_str()),
939            _ => false,
940        }
941    }
942
943    /// `net.out(payments.example.com)`, `cap.session`, `fs.read(/tmp)`, `env`.
944    ///
945    /// Reassembled from tokens rather than sliced from the source, because the parser does not hold
946    /// the source; the atom vocabulary is small enough that this is exact.
947    fn effect_atom(&mut self) -> Option<(String, Span)> {
948        let start = self.span();
949        let (head, _) = self.ident("an effect atom")?;
950        let mut out = head;
951        while self.at(&Raw::Dot) {
952            self.bump();
953            let (seg, _) = self.ident("an effect atom")?;
954            out.push('.');
955            out.push_str(&seg);
956        }
957        let mut end = self.span();
958        if self.at(&Raw::LParen) {
959            self.bump();
960            out.push('(');
961            let mut depth = 1;
962            loop {
963                match self.cur().raw() {
964                    Some(Raw::LParen) => depth += 1,
965                    Some(Raw::RParen) => {
966                        depth -= 1;
967                        if depth == 0 {
968                            end = self.span();
969                            self.bump();
970                            break;
971                        }
972                    }
973                    None => {
974                        self.error("unterminated effect atom");
975                        return None;
976                    }
977                    _ => {}
978                }
979                out.push_str(&token_text(self.cur()));
980                self.bump();
981            }
982            out.push(')');
983        }
984        Some((out, start.to(end)))
985    }
986
987    /// Does the block about to be parsed — newlines, then `INDENT` — open with this keyword?
988    ///
989    /// Lookahead without consuming, because the caller may still want [`Parser::block`] to handle
990    /// the layout tokens itself.
991    fn block_starts_with(&self, kw: &str) -> bool {
992        let mut i = self.pos;
993        while matches!(self.toks.get(i).map(|t| &t.tok), Some(Tok::Newline)) {
994            i += 1;
995        }
996        if !matches!(self.toks.get(i).map(|t| &t.tok), Some(Tok::Indent)) {
997            return false;
998        }
999        matches!(self.toks.get(i + 1).and_then(|t| t.raw()), Some(Raw::Ident(s)) if s == kw)
1000    }
1001
1002    /// `session("ana")`, already known to be there.
1003    fn session_actor(&mut self) -> Option<(String, Span)> {
1004        self.bump(); // session
1005        self.parenthesised_string("an actor name")
1006    }
1007
1008    /// `("ana")` — the parentheses and the string inside them.
1009    fn parenthesised_string(&mut self, what: &str) -> Option<(String, Span)> {
1010        self.expect(&Raw::LParen, "`(`");
1011        let out = self.quoted_name(what)?;
1012        self.expect(&Raw::RParen, "`)`");
1013        Some(out)
1014    }
1015
1016    /// Is `name` an identifier on the rest of this logical line, outside brackets?
1017    fn line_has_ident(&self, name: &str) -> bool {
1018        let mut depth = 0i32;
1019        for t in &self.toks[self.pos..] {
1020            match &t.tok {
1021                Tok::Newline | Tok::Indent | Tok::Dedent | Tok::Eof if depth == 0 => return false,
1022                Tok::Raw(Raw::LParen | Raw::LBracket | Raw::LBrace) => depth += 1,
1023                Tok::Raw(Raw::RParen | Raw::RBracket | Raw::RBrace) => depth -= 1,
1024                Tok::Raw(Raw::Ident(s)) if depth == 0 && s == name => return true,
1025                _ => {}
1026            }
1027        }
1028        false
1029    }
1030
1031    /// Descend one level of user-chosen structure, or refuse.
1032    ///
1033    /// `false` means the ceiling is reached: the caller returns `None` without recursing and
1034    /// without leaving, and the parser's ordinary recovery takes it from there. The two callers are
1035    /// [`Parser::block`] and [`Parser::primary`] — the two places the parser re-enters itself, one
1036    /// per level of indentation and one per level of brackets.
1037    fn enter(&mut self) -> bool {
1038        if self.nesting.enter() {
1039            return true;
1040        }
1041        if self.nesting.should_report() {
1042            let span = self.span();
1043            let note = self.nesting.note();
1044            self.diags.push(
1045                Diagnostic::error("B0121", "nesting is too deep to read", span)
1046                    .with_primary_label("the parser gave up here")
1047                    .with_note(note),
1048            );
1049        }
1050        self.poisoned = true;
1051        false
1052    }
1053
1054    fn block(&mut self) -> Option<Node> {
1055        if !self.enter() {
1056            return None;
1057        }
1058        let out = self.block_inner();
1059        self.nesting.leave();
1060        out
1061    }
1062
1063    fn block_inner(&mut self) -> Option<Node> {
1064        let start = self.span();
1065        // `f(x): expr` — the single-line form of the block rule (§2.3).
1066        if !matches!(self.cur().tok, Tok::Newline) {
1067            let e = self.statement()?;
1068            let s = e.span();
1069            return Some(Node::form(sym::DO, vec![e], start.to(s)));
1070        }
1071        self.skip_newlines();
1072        if !matches!(self.cur().tok, Tok::Indent) {
1073            self.error("expected an indented block");
1074            return None;
1075        }
1076        self.bump(); // INDENT
1077        let mut stmts = Vec::new();
1078        loop {
1079            self.skip_newlines();
1080            match self.cur().tok {
1081                Tok::Dedent => {
1082                    self.bump();
1083                    break;
1084                }
1085                Tok::Eof => break,
1086                _ => match self.statement() {
1087                    Some(s) => stmts.push(s),
1088                    None => {
1089                        self.recover_in_block();
1090                        if matches!(self.cur().tok, Tok::Dedent) {
1091                            self.bump();
1092                            break;
1093                        }
1094                        if self.at_eof() {
1095                            break;
1096                        }
1097                    }
1098                },
1099            }
1100        }
1101        let end = self.span();
1102        Some(Node::form(sym::DO, stmts, start.to(end)))
1103    }
1104
1105    fn recover_in_block(&mut self) {
1106        self.poisoned = false;
1107        let mut depth = 0i32;
1108        loop {
1109            match &self.cur().tok {
1110                Tok::Eof => return,
1111                Tok::Indent => {
1112                    depth += 1;
1113                    self.bump();
1114                }
1115                Tok::Dedent if depth > 0 => {
1116                    depth -= 1;
1117                    self.bump();
1118                }
1119                Tok::Dedent => return,
1120                Tok::Newline if depth == 0 => {
1121                    self.bump();
1122                    return;
1123                }
1124                _ => {
1125                    self.bump();
1126                }
1127            }
1128        }
1129    }
1130
1131    /// Collect the raw token runs of an indented block's lines. Used by `model`/`union`, whose
1132    /// bodies are declarations rather than expressions.
1133    fn indented_lines(&mut self) -> Option<Vec<Vec<Token>>> {
1134        self.skip_newlines();
1135        if !matches!(self.cur().tok, Tok::Indent) {
1136            self.error("expected an indented block");
1137            return None;
1138        }
1139        self.bump();
1140        let mut lines = Vec::new();
1141        let mut cur: Vec<Token> = Vec::new();
1142        let mut depth = 0i32;
1143        loop {
1144            match &self.cur().tok {
1145                Tok::Eof => break,
1146                Tok::Dedent if depth == 0 => {
1147                    self.bump();
1148                    break;
1149                }
1150                Tok::Dedent => {
1151                    depth -= 1;
1152                    cur.push(self.bump());
1153                }
1154                Tok::Indent => {
1155                    depth += 1;
1156                    cur.push(self.bump());
1157                }
1158                Tok::Newline if depth == 0 => {
1159                    self.bump();
1160                    if !cur.is_empty() {
1161                        lines.push(std::mem::take(&mut cur));
1162                    }
1163                }
1164                _ => cur.push(self.bump()),
1165            }
1166        }
1167        if !cur.is_empty() {
1168            lines.push(cur);
1169        }
1170        Some(lines)
1171    }
1172
1173    /// A sub-parser over a captured token run, sharing the diagnostics sink.
1174    fn sub(&mut self, mut toks: Vec<Token>) -> Parser<'_> {
1175        let end = toks.last().map(|t| t.span).unwrap_or(Span::NONE);
1176        toks.push(Token {
1177            tok: Tok::Eof,
1178            span: end,
1179        });
1180        Parser {
1181            toks,
1182            pos: 0,
1183            diags: self.diags,
1184            file: self.file,
1185            poisoned: false,
1186            attached_block: false,
1187            nesting: self.nesting.resumed(),
1188            in_test: self.in_test,
1189        }
1190    }
1191
1192    /// Does the current logical line contain a binding `=` outside brackets?
1193    ///
1194    /// `=` inside brackets is a keyword argument (`f(x=1)`), not a binding, so bracket depth is
1195    /// tracked; `==` is a separate token and never matches.
1196    fn line_has_assignment(&self) -> bool {
1197        let mut depth = 0i32;
1198        for t in &self.toks[self.pos..] {
1199            match &t.tok {
1200                Tok::Newline | Tok::Indent | Tok::Dedent | Tok::Eof if depth == 0 => return false,
1201                Tok::Raw(Raw::LParen | Raw::LBracket | Raw::LBrace) => depth += 1,
1202                Tok::Raw(Raw::RParen | Raw::RBracket | Raw::RBrace) => depth -= 1,
1203                Tok::Raw(Raw::Eq) if depth == 0 => return true,
1204                _ => {}
1205            }
1206        }
1207        false
1208    }
1209
1210    fn end_of_line(&mut self) {
1211        if matches!(self.cur().tok, Tok::Newline) {
1212            self.bump();
1213        }
1214    }
1215
1216    fn statement(&mut self) -> Option<Node> {
1217        let start = self.span();
1218
1219        // §21.2's clauses, live only inside a `test`/`property` body.
1220        if self.in_test > 0 {
1221            if self.at_kw("given") {
1222                return self.given_clause();
1223            }
1224            if self.at_kw("when") {
1225                return self.when_clause();
1226            }
1227            if self.at_kw("expect") {
1228                return self.expect_clause();
1229            }
1230            if self.at_kw("stub") {
1231                return self.stub_clause();
1232            }
1233        }
1234
1235        if self.at(&Raw::At) {
1236            return self.decorated();
1237        }
1238        if self.at_kw("def") {
1239            return self.def_item();
1240        }
1241        if self.at_kw("return") {
1242            self.bump();
1243            if matches!(self.cur().tok, Tok::Newline | Tok::Dedent | Tok::Eof) {
1244                self.end_of_line();
1245                return Some(Node::form(sym::RETURN, vec![], start));
1246            }
1247            let e = self.expr_stmt()?;
1248            let span = start.to(e.span());
1249            self.end_of_line();
1250            return Some(Node::form(sym::RETURN, vec![e], span));
1251        }
1252        if self.at_kw("if") {
1253            return self.if_stmt();
1254        }
1255        if self.at_kw("for") {
1256            self.bump();
1257            let (name, name_span) = self.ident("a loop variable")?;
1258            if !self.eat_kw("in") {
1259                self.error("expected `in`");
1260                return None;
1261            }
1262            let seq = self.expr()?;
1263            self.expect(&Raw::Colon, "`:`");
1264            let body = self.block()?;
1265            let span = start.to(body.span());
1266            return Some(Node::form(
1267                sym::FOR,
1268                vec![Node::sym(name, name_span), seq, body],
1269                span,
1270            ));
1271        }
1272        if self.at_kw("while") {
1273            self.bump();
1274            let c = self.expr()?;
1275            self.expect(&Raw::Colon, "`:`");
1276            let body = self.block()?;
1277            let span = start.to(body.span());
1278            return Some(Node::form(sym::WHILE, vec![c, body], span));
1279        }
1280        if self.at_kw("match") {
1281            return self.match_stmt();
1282        }
1283        if self.at_kw("var") {
1284            self.bump();
1285            let (name, name_span) = self.ident("a variable name")?;
1286            let ty = if self.eat(&Raw::Colon) {
1287                Some(self.type_expr()?)
1288            } else {
1289                None
1290            };
1291            self.expect(&Raw::Eq, "`=`");
1292            let e = self.expr_stmt()?;
1293            let span = start.to(e.span());
1294            self.end_of_line();
1295            let target = match ty {
1296                Some(t) => Node::form(sym::ANNOT, vec![Node::sym(name, name_span), t], name_span),
1297                None => Node::sym(name, name_span),
1298            };
1299            return Some(Node::form(sym::VAR, vec![target, e], span));
1300        }
1301        if self.at_kw("quote") {
1302            // A bare `quote:` block is an expression statement; fall through to `expr`.
1303        }
1304
1305        // Assignment or bare expression. `x = e` and `x: T = e` both bind.
1306        //
1307        // The lookahead is decided *before* committing, by scanning the logical line for a
1308        // top-level `=`. Without that, `h1: "todos"` — a block call in the `ui:` vocabulary —
1309        // enters the annotated-binding path and reports a bogus "expected a type".
1310        let save = self.pos;
1311        if self.line_has_assignment() {
1312            if let Some(Raw::Ident(name)) = self.cur().raw().cloned() {
1313                let name_span = self.span();
1314                self.bump();
1315                let ty = if self.at(&Raw::Colon) {
1316                    self.bump();
1317                    match self.type_expr() {
1318                        Some(t) => Some(t),
1319                        None => {
1320                            self.pos = save;
1321                            self.poisoned = false;
1322                            None
1323                        }
1324                    }
1325                } else {
1326                    None
1327                };
1328                if self.at(&Raw::Eq) {
1329                    self.bump();
1330                    let e = self.expr_stmt()?;
1331                    let span = start.to(e.span());
1332                    self.end_of_line();
1333                    let target = match ty {
1334                        Some(t) => {
1335                            Node::form(sym::ANNOT, vec![Node::sym(&name, name_span), t], name_span)
1336                        }
1337                        None => Node::sym(&name, name_span),
1338                    };
1339                    return Some(Node::form(sym::LET, vec![target, e], span));
1340                }
1341                self.pos = save;
1342                self.poisoned = false;
1343            }
1344        }
1345
1346        let e = self.expr_stmt()?;
1347        self.end_of_line();
1348        Some(e)
1349    }
1350
1351    fn if_stmt(&mut self) -> Option<Node> {
1352        let start = self.span();
1353        self.bump(); // if / elif
1354        let cond = self.expr()?;
1355        self.expect(&Raw::Colon, "`:`");
1356        let then = self.block()?;
1357        self.skip_newlines();
1358        let mut args = vec![cond, then];
1359        if self.at_kw("elif") {
1360            let e = self.if_stmt()?;
1361            let s = e.span();
1362            args.push(Node::form(sym::DO, vec![e], s));
1363        } else if self.at_kw("else") {
1364            self.bump();
1365            self.expect(&Raw::Colon, "`:`");
1366            args.push(self.block()?);
1367        }
1368        let span = start.to(args.last().map(|n| n.span()).unwrap_or(start));
1369        Some(Node::form(sym::IF, args, span))
1370    }
1371
1372    fn match_stmt(&mut self) -> Option<Node> {
1373        let start = self.span();
1374        self.bump(); // match
1375        let scrutinee = self.expr()?;
1376        self.expect(&Raw::Colon, "`:`");
1377        self.skip_newlines();
1378        if !matches!(self.cur().tok, Tok::Indent) {
1379            self.error("expected an indented block of `case` arms");
1380            return None;
1381        }
1382        self.bump();
1383        let mut arms = vec![scrutinee];
1384        arms.extend(self.case_arms()?);
1385        let span = start.to(self.span());
1386        Some(Node::form(sym::MATCH, arms, span))
1387    }
1388
1389    /// The `case` arms of a block whose `INDENT` has already been consumed.
1390    ///
1391    /// Shared by `match` and by §21.3 rule 3's `stub`, so a stub's arms are the language's own
1392    /// pattern matching rather than a second, drifting notation.
1393    fn case_arms(&mut self) -> Option<Vec<Node>> {
1394        let mut arms = Vec::new();
1395        loop {
1396            self.skip_newlines();
1397            match self.cur().tok {
1398                Tok::Dedent => {
1399                    self.bump();
1400                    break;
1401                }
1402                Tok::Eof => break,
1403                _ => {}
1404            }
1405            let arm_start = self.span();
1406            if !self.eat_kw("case") {
1407                self.error("expected `case`");
1408                self.recover_in_block();
1409                continue;
1410            }
1411            // Patterns are ordinary `Node`s: `Added(id, text)` is the form `(Added id text)`,
1412            // `_` is the wildcard symbol, a literal is a literal. Nothing new to represent.
1413            //
1414            // Read at binding power 1 rather than 0, which is what makes `case p if g:` readable
1415            // at all: the postfix conditional `a if c else b` is only offered at 0, so the `if`
1416            // here is the guard's and not a conditional expression missing its `else`. An
1417            // or-pattern's `|` binds at 1 and so is still read as part of the pattern.
1418            let pat = self.expr_bp(1)?;
1419            let guard = if self.eat_kw("if") {
1420                Some(self.expr()?)
1421            } else {
1422                None
1423            };
1424            self.expect(&Raw::Colon, "`:`");
1425            let body = self.block()?;
1426            let span = arm_start.to(body.span());
1427            let mut args = vec![pat, body];
1428            args.extend(guard);
1429            arms.push(Node::form(sym::CASE, args, span));
1430        }
1431        Some(arms)
1432    }
1433
1434    // ---------------------------------------------------------------- expressions (Pratt)
1435
1436    pub fn expr(&mut self) -> Option<Node> {
1437        self.expr_bp(0)
1438    }
1439
1440    /// An expression in *statement* position, where the block rule applies.
1441    ///
1442    /// §2.7's fourth honest loss is "a trailing-block ambiguity when a call with a block is itself
1443    /// an argument to another call", mitigated by "a hard syntax rule — a block-form call may not
1444    /// appear as a non-final argument". That rule is enforced here by construction: `:` only opens
1445    /// a block on the outermost call of a statement, so `for t in todos:` parses its sequence as an
1446    /// ordinary expression rather than swallowing the loop body.
1447    fn expr_stmt(&mut self) -> Option<Node> {
1448        if self.at_kw("not")
1449            || self.at(&Raw::Minus)
1450            || self.at(&Raw::Dollar)
1451            || self.at(&Raw::DollarStar)
1452        {
1453            return self.expr();
1454        }
1455        let first = self.postfix(true)?;
1456        if self.attached_block {
1457            self.attached_block = false;
1458            return Some(first);
1459        }
1460        self.expr_bp_from(first, 0)
1461    }
1462
1463    fn expr_bp(&mut self, min_bp: u8) -> Option<Node> {
1464        let lhs = self.unary()?;
1465        self.expr_bp_from(lhs, min_bp)
1466    }
1467
1468    /// The Pratt loop. **Iterative**, and that is why it needs a counter of its own.
1469    ///
1470    /// A left-associative chain — `1 + 1 + 1 + …` — is flat in source and builds a *left-leaning
1471    /// tree of the same depth*, one level per operator. Because the loop does not recurse, none of
1472    /// the parser's recursion counters ever sees it: `docs/85` measured 300,000 terms aborting the
1473    /// process while 120,000 was refused by the macro expander's ceiling, which is the wrong
1474    /// counter catching it for the wrong reason and only sometimes.
1475    ///
1476    /// This is the same axis `beck_diag::depth::MAX_BLOCK` bounds for a block of sequential
1477    /// bindings, and it takes the same ceiling for the same reason: a flat run of `n` things that
1478    /// costs one tree level each. A counter on *tree depth built* rather than on *recursion done*
1479    /// is the general lesson, and the one place the two differ is exactly here.
1480    fn expr_bp_from(&mut self, lhs: Node, min_bp: u8) -> Option<Node> {
1481        let mut lhs = lhs;
1482        let mut chain: u32 = 0;
1483
1484        loop {
1485            chain += 1;
1486            if chain > beck_diag::depth::MAX_BLOCK {
1487                if self.nesting.should_report() {
1488                    let span = lhs.span();
1489                    let note = self.nesting.note_about("operators in one chain").replace(
1490                        &format!("{} operators", self.nesting.limit()),
1491                        &format!("{} operators", beck_diag::depth::MAX_BLOCK),
1492                    );
1493                    self.diags.push(
1494                        beck_diag::Diagnostic::error(
1495                            "B0122",
1496                            "this expression chains too many operators to read",
1497                            span,
1498                        )
1499                        .with_primary_label("the reader gave up here")
1500                        .with_note(note),
1501                    );
1502                }
1503                return None;
1504            }
1505            // `a if c else b` — Python's conditional expression, at the lowest precedence, which
1506            // is what makes `x = if c: 1 else: 2` (§2.6) expressible without a statement.
1507            if self.at_kw("if") && min_bp == 0 {
1508                self.bump();
1509                let cond = self.expr_bp(1)?;
1510                if !self.eat_kw("else") {
1511                    self.error("expected `else` in a conditional expression");
1512                    return None;
1513                }
1514                let alt = self.expr_bp(0)?;
1515                let span = lhs.span().to(alt.span());
1516                lhs = Node::form(sym::IF, vec![cond, lhs, alt], span);
1517                continue;
1518            }
1519
1520            let (op, lbp, rbp) = match self.infix_op() {
1521                Some(x) => x,
1522                None => break,
1523            };
1524            if lbp < min_bp {
1525                break;
1526            }
1527            self.bump();
1528            let rhs = self.expr_bp(rbp)?;
1529            let span = lhs.span().to(rhs.span());
1530            lhs = Node::form(op, vec![lhs, rhs], span);
1531        }
1532        Some(lhs)
1533    }
1534
1535    /// §2.6: "Fixed precedence table; user-defined operators allowed but only at existing
1536    /// precedence levels."
1537    fn infix_op(&self) -> Option<(&'static str, u8, u8)> {
1538        if self.at_kw("or") {
1539            return Some(("or", 1, 2));
1540        }
1541        if self.at_kw("and") {
1542            return Some(("and", 3, 4));
1543        }
1544        if self.at_kw("in") {
1545            return Some(("contains", 5, 6));
1546        }
1547        let r = self.cur().raw()?;
1548        Some(match r {
1549            // An **or-pattern**, and only that: `case Circle(r) | Square(r):`. It is in the
1550            // expression grammar rather than in a pattern grammar of its own for §2.6's reason —
1551            // patterns *are* expressions, "nothing new to represent" — and the checker is what
1552            // refuses it where a pattern is not wanted, exactly as it does for `*rest`. Beck has
1553            // no bitwise operators (`docs/53` §53.6), so the token was free.
1554            Raw::Pipe => ("|", 1, 2),
1555            // `case whole @ Circle(r):` — a name for the value a pattern is taking apart. Binds
1556            // tighter than `|`, so `A | b @ B` is `A | (b @ B)`. `@` is only special at the start
1557            // of a statement, where it opens a decorator, so nothing here is ambiguous.
1558            Raw::At => ("@", 3, 4),
1559            Raw::EqEq => ("==", 5, 6),
1560            Raw::NotEq => ("!=", 5, 6),
1561            Raw::Lt => ("<", 5, 6),
1562            Raw::LtEq => ("<=", 5, 6),
1563            Raw::Gt => (">", 5, 6),
1564            Raw::GtEq => (">=", 5, 6),
1565            Raw::Plus => ("+", 7, 8),
1566            Raw::Minus => ("-", 7, 8),
1567            Raw::Star => ("*", 9, 10),
1568            Raw::Slash => ("/", 9, 10),
1569            Raw::Percent => ("%", 9, 10),
1570            _ => return None,
1571        })
1572    }
1573
1574    fn unary(&mut self) -> Option<Node> {
1575        let start = self.span();
1576        if self.at_kw("not") {
1577            self.bump();
1578            let e = self.unary()?;
1579            let span = start.to(e.span());
1580            return Some(Node::form("not", vec![e], span));
1581        }
1582        if self.at(&Raw::Minus) {
1583            self.bump();
1584            let e = self.unary()?;
1585            let span = start.to(e.span());
1586            return Some(Node::form("negate", vec![e], span));
1587        }
1588        if self.at(&Raw::Dollar) {
1589            self.bump();
1590            let e = self.unary()?;
1591            let span = start.to(e.span());
1592            return Some(Node::form(sym::UNQUOTE, vec![e], span));
1593        }
1594        if self.at(&Raw::DollarStar) {
1595            self.bump();
1596            let e = self.unary()?;
1597            let span = start.to(e.span());
1598            return Some(Node::form(sym::SPLICE, vec![e], span));
1599        }
1600        self.postfix(false)
1601    }
1602
1603    /// A primary and everything applied to it — `.field`, `(args)`, `[index]`, `: block`.
1604    ///
1605    /// **Counted**, and that is not a duplicate of [`Parser::primary`]'s counter. `primary` enters
1606    /// and *leaves* around the leaf; the recursion that makes `g(g(g(…)))` deep happens afterwards,
1607    /// in the loop below, through `call_args` → `expr` → `postfix`. So the depth returned to zero
1608    /// at every level and 80,000 nested calls aborted the process while 80,000 nested *parens* —
1609    /// which recurse inside `primary_inner`, where the counter is still held — were refused with a
1610    /// span. `docs/85` found it with a generator; `docs/42` §42.2 named the shape in advance,
1611    /// quoting the Scriban advisory: a limit at the one production somebody thought of is bypassed
1612    /// through a different one.
1613    ///
1614    /// The cost is that a nested expression spends two levels of the ceiling rather than one. That
1615    /// is affordable at 256 against a corpus whose deepest expression is 11 and a SICP chapter's
1616    /// under 20, and the ceiling exists "to turn an abort into a message, not to have an opinion
1617    /// about style".
1618    fn postfix(&mut self, allow_block: bool) -> Option<Node> {
1619        if !self.enter() {
1620            return None;
1621        }
1622        let out = self.postfix_inner(allow_block);
1623        self.nesting.leave();
1624        out
1625    }
1626
1627    fn postfix_inner(&mut self, allow_block: bool) -> Option<Node> {
1628        let mut e = self.primary()?;
1629        loop {
1630            if self.at(&Raw::Dot) {
1631                self.bump();
1632                let (name, name_span) = self.ident("a field or method name")?;
1633                let span = e.span().to(name_span);
1634                // `(. obj name)` is a field read; `(. obj name args...)` a method call — exactly
1635                // the notation §2.2 prints.
1636                if self.at(&Raw::LParen) {
1637                    let (args, aspan) = self.call_args()?;
1638                    let mut items = vec![e, Node::sym(name, name_span)];
1639                    items.extend(args);
1640                    e = Node::form(sym::DOT, items, span.to(aspan));
1641                } else {
1642                    e = Node::form(sym::DOT, vec![e, Node::sym(name, name_span)], span);
1643                }
1644                continue;
1645            }
1646            if self.at(&Raw::LParen) {
1647                let (args, aspan) = self.call_args()?;
1648                let span = e.span().to(aspan);
1649                e = match e.head {
1650                    // A plain name applies directly: `(update_at todos id ...)`, as written.
1651                    Head::Sym(s) if e.args.is_empty() => Node::form_sym(s, args, span),
1652                    _ => {
1653                        let mut items = vec![e];
1654                        items.extend(args);
1655                        Node::form(sym::CALL, items, span)
1656                    }
1657                };
1658                // The block rule (§2.3): a call directly followed by `:` takes the indented block
1659                // as a quoted `do=` argument.
1660                if allow_block && self.at(&Raw::Colon) {
1661                    e = self.attach_block(e)?;
1662                    break;
1663                }
1664                continue;
1665            }
1666            if self.at(&Raw::LBracket) {
1667                self.bump();
1668                let idx = self.expr()?;
1669                let end = self.span();
1670                self.expect(&Raw::RBracket, "`]`");
1671                let span = e.span().to(end);
1672                e = Node::form("index", vec![e, idx], span);
1673                continue;
1674            }
1675            // A bare name followed by `:` and a block is also a call: `main:` is `main(do=...)`.
1676            if allow_block && self.at(&Raw::Colon) && e.as_var().is_some() {
1677                e = self.attach_block(e)?;
1678                break;
1679            }
1680            break;
1681        }
1682        Some(e)
1683    }
1684
1685    /// `f(x):` + block  ⇒  `f(x, do=quote(block))`.
1686    fn attach_block(&mut self, callee: Node) -> Option<Node> {
1687        let colon = self.span();
1688        self.bump(); // :
1689        let body = self.block()?;
1690        let bspan = body.span();
1691        let quoted = Node::form(sym::QUOTE, vec![body], bspan);
1692        let kw = Node::form(
1693            sym::KW_ARG,
1694            vec![Node::sym("do", colon), quoted],
1695            colon.to(bspan),
1696        );
1697        let span = callee.span().to(bspan);
1698        let mut n = callee;
1699        n.args.push(kw);
1700        n.applied = true;
1701        n.meta.span = span;
1702        self.attached_block = true;
1703        Some(n)
1704    }
1705
1706    fn call_args(&mut self) -> Option<(Vec<Node>, Span)> {
1707        let start = self.span();
1708        self.expect(&Raw::LParen, "`(`");
1709        let mut args = Vec::new();
1710        while !self.at(&Raw::RParen) && !self.at_eof() {
1711            // `name=value` — a keyword argument, which is also how the block rule passes `do`.
1712            let save = self.pos;
1713            if let Some(Raw::Ident(name)) = self.cur().raw().cloned() {
1714                let nspan = self.span();
1715                self.bump();
1716                if self.at(&Raw::Eq) {
1717                    self.bump();
1718                    let v = self.expr()?;
1719                    let span = nspan.to(v.span());
1720                    args.push(Node::form(
1721                        sym::KW_ARG,
1722                        vec![Node::sym(&name, nspan), v],
1723                        span,
1724                    ));
1725                    if !self.eat(&Raw::Comma) {
1726                        break;
1727                    }
1728                    continue;
1729                }
1730                self.pos = save;
1731            }
1732            args.push(self.expr()?);
1733            if !self.eat(&Raw::Comma) {
1734                break;
1735            }
1736        }
1737        let end = self.span();
1738        self.expect(&Raw::RParen, "`)`");
1739        Some((args, start.to(end)))
1740    }
1741
1742    fn primary(&mut self) -> Option<Node> {
1743        if !self.enter() {
1744            return None;
1745        }
1746        let out = self.primary_inner();
1747        self.nesting.leave();
1748        out
1749    }
1750
1751    fn primary_inner(&mut self) -> Option<Node> {
1752        let span = self.span();
1753        if self.at_kw("lambda") {
1754            self.bump();
1755            let mut params = Vec::new();
1756            while !self.at(&Raw::Colon) && !self.at_eof() {
1757                let (name, nspan) = self.ident("a parameter name")?;
1758                params.push(Node::sym(name, nspan));
1759                if !self.eat(&Raw::Comma) {
1760                    break;
1761                }
1762            }
1763            self.expect(&Raw::Colon, "`:`");
1764            let body = self.expr()?;
1765            let bspan = body.span();
1766            return Some(Node::form(
1767                sym::FN,
1768                vec![
1769                    Node::form(sym::PARAMS, params, span),
1770                    Node::form(sym::DO, vec![body], bspan),
1771                ],
1772                span.to(bspan),
1773            ));
1774        }
1775        // `raise e` and `try: block` are expressions, not statements: `x = try: f()` is the form
1776        // that makes a `Result` out of a failure, and a `raise` in the middle of an expression is
1777        // exactly where a fallible branch wants to be.
1778        if self.at_kw("raise") {
1779            self.bump();
1780            let e = self.expr()?;
1781            let espan = e.span();
1782            return Some(Node::form(sym::RAISE, vec![e], span.to(espan)));
1783        }
1784        if self.at_kw("try") {
1785            self.bump();
1786            self.expect(&Raw::Colon, "`:` after `try`");
1787            let body = self.block()?;
1788            let bspan = body.span();
1789            return Some(Node::form(sym::TRY, vec![body], span.to(bspan)));
1790        }
1791        if self.at_kw("parallel") {
1792            self.bump();
1793            self.expect(&Raw::Colon, "`:` after `parallel`");
1794            let body = self.block()?;
1795            let bspan = body.span();
1796            return Some(Node::form(sym::PARALLEL, vec![body], span.to(bspan)));
1797        }
1798        if self.at_kw("quote") {
1799            self.bump();
1800            self.expect(&Raw::Colon, "`:` after `quote`");
1801            let body = self.block()?;
1802            let bspan = body.span();
1803            return Some(Node::form(sym::QUOTE, vec![body], span.to(bspan)));
1804        }
1805
1806        match self.cur().raw().cloned() {
1807            Some(Raw::Int(n)) => {
1808                self.bump();
1809                Some(Node::lit(Lit::Int(n), span))
1810            }
1811            Some(Raw::Float(n)) => {
1812                self.bump();
1813                Some(Node::lit(Lit::Float(n), span))
1814            }
1815            Some(Raw::Str(s)) => {
1816                self.bump();
1817                Some(Node::lit(Lit::Str(s.into()), span))
1818            }
1819            Some(Raw::Keyword(k)) => {
1820                self.bump();
1821                Some(Node::lit(Lit::Keyword(k.into()), span))
1822            }
1823            Some(Raw::Ident(name)) => {
1824                self.bump();
1825                match name.as_str() {
1826                    "True" | "true" => Some(Node::lit(Lit::Bool(true), span)),
1827                    "False" | "false" => Some(Node::lit(Lit::Bool(false), span)),
1828                    _ => Some(Node::symbol(Symbol::new(&name), span)),
1829                }
1830            }
1831            Some(Raw::LParen) => {
1832                self.bump();
1833                if self.at(&Raw::RParen) {
1834                    let end = self.span();
1835                    self.bump();
1836                    return Some(Node::sym("unit", span.to(end)));
1837                }
1838                let e = self.expr()?;
1839                self.expect(&Raw::RParen, "`)`");
1840                Some(e)
1841            }
1842            Some(Raw::LBracket) => {
1843                self.bump();
1844                let mut items = Vec::new();
1845                while !self.at(&Raw::RBracket) && !self.at_eof() {
1846                    // `*rest` — only meaningful in a `case` pattern, and parsed here rather than in
1847                    // a separate pattern grammar because §2.6's patterns *are* expressions:
1848                    // "`Added(id, text)` is the form `(Added id text)` … Nothing new to
1849                    // represent". The checker is what refuses it outside a pattern
1850                    // (`docs/33` §33.5).
1851                    if self.at(&Raw::Star) {
1852                        let star = self.span();
1853                        self.bump();
1854                        let e = self.postfix(false)?;
1855                        let sp = star.to(e.span());
1856                        items.push(Node::form(sym::REST, vec![e], sp));
1857                    } else {
1858                        items.push(self.expr()?);
1859                    }
1860                    if !self.eat(&Raw::Comma) {
1861                        break;
1862                    }
1863                }
1864                let end = self.span();
1865                self.expect(&Raw::RBracket, "`]`");
1866                Some(Node::form(sym::LIST, items, span.to(end)))
1867            }
1868            Some(Raw::LBrace) => {
1869                self.bump();
1870                // `{name: value}` is a record literal; `{key_expr: value}` a map literal. The
1871                // discriminator is whether the key is a bare identifier, which is exactly how the
1872                // sketch's `{:id id :text text}` reads in the S-expression surface.
1873                let mut items = Vec::new();
1874                let mut is_record = true;
1875                while !self.at(&Raw::RBrace) && !self.at_eof() {
1876                    let key_span = self.span();
1877                    let key = match self.cur().raw().cloned() {
1878                        Some(Raw::Ident(name))
1879                            if self.toks.get(self.pos + 1).map(|t| t.raw())
1880                                == Some(Some(&Raw::Colon)) =>
1881                        {
1882                            self.bump();
1883                            Node::lit(Lit::Keyword(name.into()), key_span)
1884                        }
1885                        _ => {
1886                            is_record = false;
1887                            self.expr()?
1888                        }
1889                    };
1890                    self.expect(&Raw::Colon, "`:`");
1891                    let value = self.expr()?;
1892                    items.push(key);
1893                    items.push(value);
1894                    if !self.eat(&Raw::Comma) {
1895                        break;
1896                    }
1897                }
1898                let end = self.span();
1899                self.expect(&Raw::RBrace, "`}`");
1900                let head = if is_record { sym::RECORD } else { sym::MAP };
1901                Some(Node::form(head, items, span.to(end)))
1902            }
1903            _ => {
1904                self.error(format!(
1905                    "expected an expression, found {}",
1906                    self.cur().describe()
1907                ));
1908                None
1909            }
1910        }
1911    }
1912
1913    // ---------------------------------------------------------------- types
1914
1915    /// A type is a name, a generic application `Map[K, V]`, or a function type `(A, B) -> R`.
1916    /// It reads to the same `Node` shape as any other application: `(Map K V)`.
1917    /// A type, which nests exactly as an expression does — `list[list[list[Int]]]`, `(A, B) -> C`
1918    /// — and recurses in four places below.
1919    ///
1920    /// Counted here rather than only in `expr`/`primary`, because bounding one production is not
1921    /// bounding the grammar: `docs/42` §42.2 quotes the Scriban advisory for that lesson and
1922    /// `docs/85` is where this project learned it the same way, from a generator that reached
1923    /// 80,000 levels of `list[` and aborted the process while every other shape was refused with a
1924    /// span.
1925    fn type_expr(&mut self) -> Option<Node> {
1926        if !self.enter() {
1927            return None;
1928        }
1929        let out = self.type_expr_inner();
1930        self.nesting.leave();
1931        out
1932    }
1933
1934    fn type_expr_inner(&mut self) -> Option<Node> {
1935        let start = self.span();
1936        if self.at(&Raw::LParen) {
1937            self.bump();
1938            let mut params = Vec::new();
1939            while !self.at(&Raw::RParen) && !self.at_eof() {
1940                params.push(self.type_expr()?);
1941                if !self.eat(&Raw::Comma) {
1942                    break;
1943                }
1944            }
1945            self.expect(&Raw::RParen, "`)`");
1946            if self.eat(&Raw::Arrow) {
1947                let ret = self.type_expr()?;
1948                let span = start.to(ret.span());
1949                let mut items = params;
1950                items.push(ret);
1951                return Some(Node::form("fn-type", items, span));
1952            }
1953            // A parenthesised type with one member is just that type.
1954            if params.len() == 1 {
1955                return params.pop();
1956            }
1957            let span = start.to(self.span());
1958            return Some(Node::form("tuple-type", params, span));
1959        }
1960
1961        let (name, name_span) = self.ident("a type")?;
1962        let mut node = Node::sym(&name, name_span);
1963        if self.at(&Raw::LBracket) {
1964            self.bump();
1965            let mut args = Vec::new();
1966            while !self.at(&Raw::RBracket) && !self.at_eof() {
1967                args.push(self.type_expr()?);
1968                if !self.eat(&Raw::Comma) {
1969                    break;
1970                }
1971            }
1972            let end = self.span();
1973            self.expect(&Raw::RBracket, "`]`");
1974            node = Node::form(&name, args, name_span.to(end));
1975        }
1976        // `T -> U` for a one-argument function type.
1977        if self.eat(&Raw::Arrow) {
1978            let ret = self.type_expr()?;
1979            let span = node.span().to(ret.span());
1980            return Some(Node::form("fn-type", vec![node, ret], span));
1981        }
1982        Some(node)
1983    }
1984}
1985
1986/// A token's source text, for the one place the parser has to reassemble it: the inside of an
1987/// effect atom's parentheses, where `payments.example.com` is three tokens and one host name.
1988fn token_text(t: &Token) -> String {
1989    match &t.tok {
1990        Tok::Raw(Raw::Ident(s)) => s.clone(),
1991        Tok::Raw(Raw::Str(s)) => s.clone(),
1992        Tok::Raw(Raw::Keyword(s)) => format!(":{s}"),
1993        Tok::Raw(Raw::Int(n)) => n.to_string(),
1994        Tok::Raw(Raw::Float(n)) => n.to_string(),
1995        Tok::Raw(Raw::Dot) => ".".into(),
1996        Tok::Raw(Raw::Slash) => "/".into(),
1997        Tok::Raw(Raw::Minus) => "-".into(),
1998        Tok::Raw(Raw::Star) => "*".into(),
1999        Tok::Raw(Raw::Comma) => ",".into(),
2000        Tok::Raw(Raw::Colon) => ":".into(),
2001        _ => String::new(),
2002    }
2003}
2004
2005#[cfg(test)]
2006mod tests {
2007    use super::*;
2008    use crate::print;
2009
2010    fn parse(src: &str) -> (Node, beck_diag::SourceMap) {
2011        let mut map = beck_diag::SourceMap::new();
2012        let f = map.add("t.beck", src);
2013        let mut d = Diagnostics::new();
2014        let n = parse_module(f, "t", src, &mut d);
2015        assert!(!d.has_errors(), "{}", d.render(&map));
2016        (n, map)
2017    }
2018
2019    fn sx(src: &str) -> String {
2020        let (n, _) = parse(src);
2021        // Drop the `(module t ...)` wrapper for readability in assertions.
2022        print::to_sexpr(&n.args[1])
2023    }
2024
2025    #[test]
2026    fn a_def_carries_params_returns_and_effects() {
2027        assert_eq!(
2028            sx("def toggle(t: Todo) -> Todo uses durable:\n    return t\n"),
2029            "(def toggle (typarams) (params (: t Todo)) (returns Todo) (uses durable) (do (return t)))"
2030        );
2031    }
2032
2033    #[test]
2034    fn the_doc_example_produces_the_documented_tree() {
2035        // §2.2's side-by-side, verbatim.
2036        assert_eq!(
2037            sx("def toggle(todos: Map[Id, Todo], e: Toggled) -> Map[Id, Todo]:\n\
2038                \x20   return todos.update(e.id, lambda t: t.with(done=not t.done))\n"),
2039            "(def toggle (typarams) (params (: todos (Map Id Todo)) (: e Toggled)) (returns (Map Id Todo)) \
2040             (uses) (do (return (. todos update (. e id) (fn (params t) (do (. t with (kw done (not (. t done)))))))))) "
2041                .trim_end()
2042        );
2043    }
2044
2045    #[test]
2046    fn precedence_is_conventional() {
2047        let (n, _) = parse("x = 1 + 2 * 3 < 4 and not b\n");
2048        assert_eq!(
2049            print::to_sexpr(&n.args[1]),
2050            "(let x (and (< (+ 1 (* 2 3)) 4) (not b)))"
2051        );
2052    }
2053
2054    #[test]
2055    fn the_block_rule_passes_the_body_as_a_quoted_argument() {
2056        assert_eq!(
2057            sx("retry(times=3):\n    charge(card)\n"),
2058            "(retry (kw times 3) (kw do (quote (do (charge card)))))"
2059        );
2060        // Bare-name form: `main:` is a call too.
2061        assert_eq!(
2062            sx("main:\n    h1: \"todos\"\n"),
2063            "(main (kw do (quote (do (h1 (kw do (quote (do \"todos\"))))))))"
2064        );
2065    }
2066
2067    #[test]
2068    fn decorators_receive_the_definitions_ast() {
2069        assert_eq!(
2070            sx("@on(server)\ndef f() -> Int:\n    return 1\n"),
2071            "(decorate (on server) (def f (typarams) (params) (returns Int) (uses) (do (return 1))))"
2072        );
2073    }
2074
2075    #[test]
2076    fn models_unions_and_newtypes() {
2077        assert_eq!(
2078            sx("model Todo:\n    id: Id\n    done: Bool\n"),
2079            "(model Todo (typarams) (field id Id) (field done Bool))"
2080        );
2081        assert_eq!(
2082            sx("union Event:\n    Added(id: Id, text: Str)\n    Toggled(id: Id)\n"),
2083            "(union Event (typarams) (variant Added (field id Id) (field text Str)) \
2084             (variant Toggled (field id Id)))"
2085        );
2086        assert_eq!(
2087            sx("type Id = newtype[Uuid]\n"),
2088            "(newtype Id (typarams) Uuid)"
2089        );
2090        assert_eq!(
2091            sx("type Ids = list[Id]\n"),
2092            "(type Ids (typarams) (list Id))"
2093        );
2094    }
2095
2096    #[test]
2097    fn match_arms_are_ordinary_nodes() {
2098        assert_eq!(
2099            sx("match e:\n    case Added(id, text):\n        return 1\n    case _:\n        return 2\n"),
2100            "(match e (case (Added id text) (do (return 1))) (case _ (do (return 2))))"
2101        );
2102    }
2103
2104    #[test]
2105    fn conditional_expressions_and_collections() {
2106        assert_eq!(sx("x = 1 if c else 2\n"), "(let x (if c 1 2))");
2107        assert_eq!(sx("x = [1, 2]\n"), "(let x (list 1 2))");
2108        assert_eq!(sx("x = {id: 1}\n"), "(let x (record :id 1))");
2109        assert_eq!(sx("x = {k: 1}[k]\n"), "(let x (index (record :k 1) k))");
2110    }
2111
2112    #[test]
2113    fn quote_and_unquote() {
2114        assert_eq!(
2115            sx("macro unless(cond, do):\n    return quote:\n        if not $cond:\n            $do\n"),
2116            "(macro unless (params cond do) (do (return (quote (do (if (not (unquote cond)) (do (unquote do))))))))"
2117        );
2118    }
2119
2120    #[test]
2121    fn errors_recover_so_later_items_still_parse() {
2122        // A missing comma between parameters. Note the mistake keeps brackets balanced: an
2123        // *unclosed* bracket suppresses layout for the rest of the file, so there are no line
2124        // boundaries left to recover to — the same failure mode Python has, and not one a parser
2125        // can paper over.
2126        let src = "def a(x: Int y: Int) -> Int:\n    return 1\n\ndef b() -> Int:\n    return 2\n";
2127        let mut map = beck_diag::SourceMap::new();
2128        let f = map.add("t.beck", src);
2129        let mut d = Diagnostics::new();
2130        let n = parse_module(f, "t", src, &mut d);
2131        assert!(d.has_errors());
2132        let names: Vec<String> = n
2133            .args
2134            .iter()
2135            .skip(1)
2136            .filter(|i| i.is_form(sym::DEF))
2137            .map(|i| i.args[0].as_var().unwrap().as_str().to_string())
2138            .collect();
2139        assert!(
2140            names.contains(&"b".to_string()),
2141            "the definition after the error must still be parsed, got {names:?}"
2142        );
2143    }
2144}
2145
2146#[cfg(test)]
2147mod test_clause_tests {
2148    use super::*;
2149
2150    fn sx(src: &str) -> String {
2151        let mut map = beck_diag::SourceMap::new();
2152        let f = map.add("t.beck", src);
2153        let mut d = Diagnostics::new();
2154        let n = parse_module(f, "t", src, &mut d);
2155        assert!(!d.has_errors(), "{}", d.render(&map));
2156        crate::print::to_sexpr(&n.args[1])
2157    }
2158
2159    #[test]
2160    fn the_four_clauses_read_as_forms() {
2161        assert_eq!(
2162            sx("test \"x\":\n    given []\n"),
2163            "(test \"x\" (do (given (list))))"
2164        );
2165        assert_eq!(
2166            sx("test \"x\":\n    given [a] by \"ana\"\n"),
2167            "(test \"x\" (do (given (list a) \"ana\")))"
2168        );
2169        assert_eq!(
2170            sx("test \"x\":\n    when A(id=1), B(id=2)\n"),
2171            "(test \"x\" (do (when _ (A (kw id 1)) (B (kw id 2)))))"
2172        );
2173        assert_eq!(
2174            sx("test \"x\":\n    when session(\"ana\") sends A(id=1)\n"),
2175            "(test \"x\" (do (when \"ana\" (A (kw id 1)))))"
2176        );
2177        assert_eq!(
2178            sx("test \"x\":\n    stub net.out(payments.example.com): Declined\n"),
2179            "(test \"x\" (do (stub \"net.out(payments.example.com)\" Declined)))"
2180        );
2181    }
2182
2183    #[test]
2184    fn expect_has_six_shapes_and_they_are_decided_without_backtracking() {
2185        assert_eq!(
2186            sx("test \"x\":\n    expect page contains \"milk\"\n"),
2187            "(test \"x\" (do (expect-contains \"milk\")))"
2188        );
2189        assert_eq!(
2190            sx("test \"x\":\n    expect place(charge) == server\n"),
2191            "(test \"x\" (do (expect-place charge server)))"
2192        );
2193        assert_eq!(
2194            sx("test \"x\":\n    expect flow(ApiKey) reaches nothing on client\n"),
2195            "(test \"x\" (do (expect-flow ApiKey client)))"
2196        );
2197        assert_eq!(
2198            sx("test \"x\":\n    expect wire_compatible_with \"o.becki\"\n"),
2199            "(test \"x\" (do (expect-wire \"o.becki\")))"
2200        );
2201        assert_eq!(
2202            sx("test \"x\":\n    expect no net.out\n"),
2203            "(test \"x\" (do (expect-effect \"net.out\" none)))"
2204        );
2205        assert_eq!(
2206            sx("test \"x\":\n    expect net.out(h.example.com) once\n"),
2207            "(test \"x\" (do (expect-effect \"net.out(h.example.com)\" (times 1))))"
2208        );
2209        // …and the ordinary case is an ordinary expression.
2210        assert_eq!(
2211            sx("test \"x\":\n    expect list_len(events) == 1\n"),
2212            "(test \"x\" (do (expect (== (list_len events) 1))))"
2213        );
2214        // `expect Err(...)` is shorthand for `result == Err(...)`.
2215        assert_eq!(
2216            sx("test \"x\":\n    expect Err(error=BlankText)\n"),
2217            "(test \"x\" (do (expect (== result (Err (kw error BlankText))))))"
2218        );
2219    }
2220
2221    #[test]
2222    fn the_clause_keywords_are_not_reserved_outside_a_test() {
2223        // A program with a definition called `expect` still parses as a call, because the four
2224        // words are live only inside a `test` body.
2225        assert_eq!(
2226            sx("def f() -> Int:\n    return expect(1)\n"),
2227            "(def f (typarams) (params) (returns Int) (uses) (do (return (expect 1))))"
2228        );
2229    }
2230
2231    #[test]
2232    fn a_property_carries_its_generated_parameters() {
2233        assert_eq!(
2234            sx("property \"p\"(events: list[Event]):\n    given events\n"),
2235            "(property \"p\" (params (: events (list Event))) (do (given events)))"
2236        );
2237    }
2238}
2239
2240/// The front end's recursion bound, from the outside: what a program past the ceiling gets, and
2241/// whether the stack the ceiling is declared to need actually covers it.
2242///
2243/// `docs/42` §42.2 is what these are about — an ~7.6 KB file that aborted `beck check` in a debug
2244/// build, on the 64 MiB stack `adr/0007` declared for a *different* recursive consumer of it.
2245#[cfg(test)]
2246mod nesting_tests {
2247    use super::*;
2248    use beck_diag::depth::{MAX_NESTING, STACK_BYTES};
2249
2250    /// `(((…1…)))`, nested `n` deep, as a whole module.
2251    fn nested_parens(n: usize) -> String {
2252        format!(
2253            "def f() -> Int:\n    return {}1{}\n",
2254            "(".repeat(n),
2255            ")".repeat(n)
2256        )
2257    }
2258
2259    /// On the stack the front end declares it needs — which is the whole contract: the ceiling is
2260    /// only a bound if somebody guarantees it is reachable.
2261    fn diagnose(src: &str) -> Vec<String> {
2262        beck_diag::depth::on_the_front_end_stack(|| {
2263            let mut map = beck_diag::SourceMap::new();
2264            let f = map.add("deep.beck", src);
2265            let mut d = Diagnostics::new();
2266            let _ = parse_module(f, "deep", src, &mut d);
2267            d.iter().map(|x| x.code.to_string()).collect()
2268        })
2269    }
2270
2271    #[test]
2272    fn one_level_past_the_ceiling_is_a_diagnostic_rather_than_an_abort() {
2273        // Two levels of the ceiling are spent on the module and the `def`'s block before the
2274        // expression starts, so "one past" is stated as a wide margin rather than as arithmetic
2275        // about the parser's own frames.
2276        let codes = diagnose(&nested_parens(MAX_NESTING as usize + 8));
2277        assert!(
2278            codes.contains(&"B0121".to_string()),
2279            "a program past the ceiling should be refused with B0121, got {codes:?}"
2280        );
2281    }
2282
2283    #[test]
2284    fn the_refusal_is_one_diagnostic_and_not_one_per_level() {
2285        let codes = diagnose(&nested_parens(MAX_NESTING as usize + 8));
2286        assert_eq!(
2287            codes.iter().filter(|c| *c == "B0121").count(),
2288            1,
2289            "got {codes:?}"
2290        );
2291    }
2292
2293    #[test]
2294    fn nesting_a_person_would_write_is_still_read() {
2295        let mut map = beck_diag::SourceMap::new();
2296        let src = nested_parens(64);
2297        let f = map.add("ok.beck", &src);
2298        let mut d = Diagnostics::new();
2299        let _ = parse_module(f, "ok", &src, &mut d);
2300        assert!(!d.has_errors(), "{}", d.render(&map));
2301    }
2302
2303    /// The `beck-eval` pair, for the reader: measure what one level costs and hold the declaration
2304    /// to it, rather than trusting a number somebody wrote down once.
2305    #[test]
2306    fn the_ceiling_fits_the_declared_stack() {
2307        const PROBE_DEPTH: usize = 200;
2308        // Measured on a stack far larger than the one whose adequacy is being concluded, so the
2309        // measurement is never the thing that overflows.
2310        let spent = std::thread::Builder::new()
2311            .stack_size(256 * 1024 * 1024)
2312            .spawn(|| {
2313                let src = nested_parens(PROBE_DEPTH);
2314                beck_diag::depth::probe::stack_spent(|| {
2315                    let mut map = beck_diag::SourceMap::new();
2316                    let f = map.add("probe.beck", &src);
2317                    let mut d = Diagnostics::new();
2318                    parse_module(f, "probe", &src, &mut d)
2319                })
2320            })
2321            .expect("a thread")
2322            .join()
2323            .expect("the probe parses");
2324
2325        let per_level = spent / PROBE_DEPTH;
2326        println!("parser: {spent} bytes for {PROBE_DEPTH} levels ({per_level} per level)");
2327        // Twice over, as the evaluator's does: whoever drives the parser has as much stack again
2328        // above the ceiling as the ceiling itself needs.
2329        let needed = MAX_NESTING as usize * per_level * 2;
2330        assert!(
2331            needed < STACK_BYTES,
2332            "a ceiling of {MAX_NESTING} levels at {per_level} bytes each needs {needed} bytes \
2333             with the margin, against a declared STACK_BYTES of {STACK_BYTES} — raise the \
2334             declaration or lower the ceiling"
2335        );
2336    }
2337}