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