beck_macro/
interp.rs

1//! The macro interpreter: Beck, evaluated at compile time.
2//!
3//! [`docs/02-syntax.md`](../../../../../docs/02-syntax.md) §2.4: "Macro bodies run at compile time
4//! in the compiler's own Beck interpreter, with a *capability-restricted* environment: pure
5//! computation and reads of the declared module graph, no ambient filesystem or network.
6//! Non-negotiable — build reproducibility and the 'compile once, deploy many' model depend on it,
7//! and it closes a real supply-chain hole that Rust `build.rs` and npm `postinstall` leave open."
8//!
9//! This is that interpreter. A macro body is ordinary Beck — bindings, `if`, `for`, `while`,
10//! lambdas, calls to the pure part of the prelude and to the module's own `def`s — and `quote:`
11//! is the one form whose value is *syntax* rather than a number or a string.
12//!
13//! # Why it is a second interpreter rather than `beck-eval`
14//!
15//! Untyped macros expand **before** the checker runs ([`docs/02`](../../../../../docs/02-syntax.md)
16//! §2.4: "`macro` (untyped AST in, AST out, expands before type checking)"), so there is no
17//! `Core` IR and no type for a macro body to be evaluated against — only [`Node`]. `beck-eval`
18//! evaluates `Core`, and `beck-core` (which lowers to it) depends on this crate, so the dependency
19//! could not run the other way even if the IR existed. The two are held together by a
20//! differential rather than by sharing code: `beck-cli/tests/macro_interp.rs` computes the same
21//! pure expressions at compile time and at run time and fails when the answers differ, which is
22//! the same instrument [`docs/04`](../../../../../docs/04-compiler-architecture.md) §4.8 points at
23//! the backends.
24//!
25//! Where an operation is somebody else's table — case mapping, substring replacement — this calls
26//! `beck-prim`, the crate the evaluator and a compiled program already call
27//! ([`docs/93`](../../../../../docs/93-the-native-backends-report.md) §93.12). Agreement there is
28//! not a property of two implementations being careful; there is one implementation.
29//!
30//! # The sandbox
31//!
32//! The environment is a **whitelist**: a name resolves to a local, to one of the module's own
33//! `def`s, or to one of the pure builtins in [`BUILTINS`] — and to nothing else. There is no name
34//! for opening a file, reading the environment, starting a process or fetching a URL, because
35//! nothing here defines one. The prelude's effectful names are refused *by name* rather than left
36//! to fall out of the whitelist ([`RESTRICTED`]), so that a macro reaching for `now()` is told what
37//! it did wrong instead of being told the name does not exist.
38//!
39//! That refusal is a claim, so it is a gate: `beck-cli/tests/macro_sandbox.rs` enumerates the
40//! prelude and fails if an effectful primitive is missing from [`RESTRICTED`] or reachable from a
41//! macro body.
42//!
43//! # What it does not have
44//!
45//! - **Unions**, and therefore no `Option`: the prelude's `str_index_of` and `list_get` return
46//!   one, so they are not compile-time builtins. Where the operation is needed anyway the
47//!   compile-time half is **total and refuses** rather than answering `None` — indexing (`xs[i]`)
48//!   is that, and so is `str_to_int`, which a macro parsing a typed literal (§2.5) has no other
49//!   way to do.
50//! - **`match`**, for the same reason: its patterns are about variants.
51//! - **The transcendentals.** `sqrt`, `sin` and `cos` would make the compiler's answer depend on
52//!   the host's libm, which is F9's open question ([`docs/35`](../../../../../docs/35-standards-landscape.md)
53//!   §35.5) and not a thing to prejudge from here.
54//! - **Types.** A `typed macro` receives the AST with inferred types attached (§2.4) and needs the
55//!   checker to have run; this interpreter is the untyped half.
56
57use std::collections::HashMap;
58use std::sync::Arc;
59
60use beck_diag::depth::Nesting;
61use beck_diag::{Diagnostic, Diagnostics, Span};
62use beck_syntax::{print, sym, Head, Lit, Node, Symbol};
63
64use crate::typed::{TyRepr, TypeEnv};
65
66/// How many steps one module's macro bodies may take, in total.
67///
68/// The same shape as [`crate::MAX_EXPANSION`] and for the same reason: per module, because that is
69/// what a compile is, and a per-call budget would let a program spend it once per call site.
70///
71/// A step is an expression evaluated or a statement run, so the number is a bound on *compile
72/// time* rather than on a program's size — which is what makes it the answer to `while true:` in a
73/// macro body. `macro_interp.rs::a_macro_body_that_does_not_terminate_is_refused` is the gate, and
74/// two measurements set the size: `the_step_budget_is_far_above_what_a_real_macro_spends`
75/// **prints** what the most expensive macro body here costs — **84 steps**, so a million is about
76/// 12,000× the largest real one — and exhausting the whole budget costs under a second of
77/// `beck check` in an unoptimised build. It is the room a limit wants when what it separates is
78/// *legitimate* from *absurd* rather than big from small.
79pub const MAX_STEPS: u64 = 1_000_000;
80
81/// The prelude names a macro body may not call, with the effect atom each performs.
82///
83/// Reading the tree's own list rather than inventing one: these are the primitives whose scheme
84/// carries an atom in `beck_core::prelude`, plus `http_fetch`, whose `net(host)` atom is derived
85/// at the call site from the host it names
86/// ([`adr/0013`](../../../../../docs/adr/0013-the-host-of-an-outbound-call-is-written-at-the-call-site.md))
87/// rather than written in its scheme.
88///
89/// Nothing here is *reachable* — the interpreter's environment is a whitelist and none of these is
90/// on it — so this list buys a diagnostic rather than a control. It is the difference between "a
91/// macro may not read the clock" and "there is no name `now`", and only the first is true.
92pub const RESTRICTED: &[(&str, &str)] = &[
93    ("awareness", "cap.presence"),
94    ("digest_keyed", "cap.sign"),
95    ("durable", "durable"),
96    ("gestures", "dom"),
97    ("http_fetch", "net(host)"),
98    ("merge_clients", "ingress"),
99    ("now", "nondet"),
100    ("presence", "cap.presence"),
101    ("reveal", "cap.internal"),
102    ("secret_env", "env"),
103    ("uuid", "nondet"),
104];
105
106/// A compile-time value.
107///
108/// [`Val::Syntax`] is the one that makes this a macro interpreter rather than a calculator: a
109/// `quote:` block evaluates to one, `$e` inside a template puts one back, and every other variant
110/// has a [reflection](Interp::reflect) into syntax so that `$n` where `n` is `3` is the literal
111/// `3`.
112#[derive(Clone, Debug)]
113pub enum Val {
114    Unit,
115    Int(i64),
116    Float(f64),
117    Str(Arc<str>),
118    Bool(bool),
119    Keyword(Arc<str>),
120    List(Arc<Vec<Val>>),
121    Record(Arc<Vec<(Arc<str>, Val)>>),
122    Syntax(Node),
123    Fun(Arc<Lambda>),
124    /// What `node_ty(e)` answers with: the type the checker gave an expression
125    /// ([`crate::typed`]). Only a typed macro's body can hold one, because only there has anything
126    /// been inferred.
127    Type(Arc<TyRepr>),
128}
129
130impl Val {
131    pub fn type_name(&self) -> &'static str {
132        match self {
133            Val::Unit => "unit",
134            Val::Int(_) => "Int",
135            Val::Float(_) => "Float",
136            Val::Str(_) => "Str",
137            Val::Bool(_) => "Bool",
138            Val::Keyword(_) => "keyword",
139            Val::List(_) => "list",
140            Val::Record(_) => "record",
141            Val::Syntax(_) => "syntax",
142            Val::Fun(_) => "function",
143            Val::Type(_) => "type",
144        }
145    }
146
147    fn str_(s: impl AsRef<str>) -> Val {
148        Val::Str(Arc::from(s.as_ref()))
149    }
150
151    fn list(xs: Vec<Val>) -> Val {
152        Val::List(Arc::new(xs))
153    }
154}
155
156/// A lambda, with the frame it closed over.
157#[derive(Debug)]
158pub struct Lambda {
159    params: Vec<Arc<str>>,
160    body: Node,
161    captured: HashMap<Arc<str>, Val>,
162}
163
164/// A module-level `def`, callable from a macro body.
165///
166/// The body is the one the parser produced, **before** expansion: a compile-time call runs the
167/// definition as written. A `def` whose body calls a macro is therefore not callable at compile
168/// time, which is honest — the alternative is expansion order that depends on who calls what.
169#[derive(Clone, Debug)]
170pub struct FnDef {
171    pub params: Vec<Arc<str>>,
172    pub body: Node,
173    pub span: Span,
174}
175
176/// Evaluation stopped and a diagnostic has already been reported.
177#[derive(Debug)]
178pub struct Halt;
179
180type Eval<T> = Result<T, Halt>;
181
182/// What a statement did.
183enum Flow {
184    Fell,
185    Returned(Val),
186}
187
188/// The step budget's refusal, in one place: the meter reports it, and so does a checker whose
189/// probe discarded the first report ([`crate::typed::TypedExpander::exhausted`]).
190pub(crate) fn ran_too_long(span: Span) -> Diagnostic {
191    Diagnostic::error("B0215", "a macro body ran too long", span)
192        .with_primary_label("the interpreter stopped here")
193        .with_note(format!(
194            "the budget is {MAX_STEPS} steps for the whole module — a bound on how long a compile \
195             takes, which is what answers a macro body that does not terminate"
196        ))
197}
198
199pub struct Interp<'a> {
200    defs: &'a HashMap<Arc<str>, FnDef>,
201    /// What the checker inferred about the call being expanded, or `None` in the untyped phase —
202    /// which is what makes `node_ty` a name a typed macro's body has and an ordinary one's has not.
203    types: Option<&'a TypeEnv>,
204    /// Where the macro was called. A body's own errors point into the body; a `refuse` is a message
205    /// *to the caller* and points at the call.
206    call: Span,
207    diags: &'a mut Diagnostics,
208    /// What is left of [`MAX_STEPS`] for the whole module.
209    pub steps: u64,
210    /// Whether the budget ran out, so the diagnostic is reported once rather than at every macro
211    /// that would have run afterwards.
212    pub exhausted: bool,
213    /// Host recursion, counted at the site the way
214    /// [`adr/0012`](../../../../../docs/adr/0012-the-front-end-counts-its-own-recursion.md) asks:
215    /// one counter over *every* recursive entry rather than one per grammar rule, so a deep
216    /// expression and a deep chain of compile-time calls spend the same budget.
217    nesting: Nesting,
218}
219
220impl<'a> Interp<'a> {
221    pub fn new(
222        defs: &'a HashMap<Arc<str>, FnDef>,
223        diags: &'a mut Diagnostics,
224        steps: u64,
225        exhausted: bool,
226    ) -> Interp<'a> {
227        Interp {
228            defs,
229            types: None,
230            call: Span::NONE,
231            diags,
232            steps,
233            exhausted,
234            nesting: Nesting::new(),
235        }
236    }
237
238    /// Where the call being expanded was written.
239    pub fn called_at(mut self, span: Span) -> Interp<'a> {
240        self.call = span;
241        self
242    }
243
244    /// The same, with the checker's answers about the call site in reach.
245    pub fn knowing(mut self, types: Option<&'a TypeEnv>) -> Interp<'a> {
246        self.types = types;
247        self
248    }
249
250    /// Run a macro body, given its parameters already bound to the syntax they were called with.
251    ///
252    /// The result is whatever the body returned, reflected into syntax — so `return quote: …` is
253    /// a template and `return 6 * 7` is the literal `42`.
254    pub fn run_body(
255        &mut self,
256        name: &str,
257        body: &Node,
258        env: HashMap<Arc<str>, Val>,
259        def_span: Span,
260    ) -> Option<Node> {
261        let mut frame = env;
262        match self.block(body, &mut frame) {
263            Ok(Flow::Returned(v)) => {
264                let span = body.span();
265                self.reflect(&v, span).ok()
266            }
267            Ok(Flow::Fell) => {
268                self.diags.push(Diagnostic::error(
269                    "B0204",
270                    format!("macro `{name}` returns nothing"),
271                    def_span,
272                ));
273                None
274            }
275            Err(Halt) => None,
276        }
277    }
278
279    // ---------------------------------------------------------------------------- the machinery
280
281    fn step(&mut self, span: Span) -> Eval<()> {
282        if self.steps == 0 {
283            if !self.exhausted {
284                self.exhausted = true;
285                self.diags.push(ran_too_long(span));
286            }
287            return Err(Halt);
288        }
289        self.steps -= 1;
290        Ok(())
291    }
292
293    fn enter(&mut self, span: Span) -> Eval<()> {
294        if self.nesting.enter() {
295            return Ok(());
296        }
297        if self.nesting.should_report() {
298            let note = self.nesting.note();
299            self.diags.push(
300                Diagnostic::error("B0216", "a macro body recursed too deep", span)
301                    .with_primary_label("the interpreter gave up here")
302                    .with_note(note),
303            );
304        }
305        Err(Halt)
306    }
307
308    /// The macro decided it cannot generate for this, and said why.
309    ///
310    /// Distinct from [`Interp::wrong`] because it is not a mistake: a macro that reads a type and
311    /// writes code for it meets types it has no rule for, and the alternative to saying so is
312    /// emitting something that fails to check for a reason nobody can trace back to here.
313    fn refusal(&mut self, msg: Arc<str>, span: Span) -> Halt {
314        let at = match self.call.is_none() {
315            true => span,
316            false => self.call,
317        };
318        self.diags.push(
319            Diagnostic::error("B0224", msg.to_string(), at)
320                .with_primary_label("refused by the macro expanding here")
321                .with_label(span, "the macro said so here"),
322        );
323        Halt
324    }
325
326    /// A refusal that points where the macro said to, rather than at the call.
327    ///
328    /// The fallback matters: a node the macro *built* has no span, and pointing a diagnostic at
329    /// nothing is worse than pointing it at the call site.
330    fn refusal_at(&mut self, msg: Arc<str>, at: Span, span: Span) -> Halt {
331        if at.is_none() {
332            return self.refusal(msg, span);
333        }
334        self.diags.push(
335            Diagnostic::error("B0224", msg.to_string(), at)
336                .with_primary_label("the macro expanding here refused this")
337                .with_label(span, "the macro said so here"),
338        );
339        Halt
340    }
341
342    fn wrong(&mut self, msg: impl Into<String>, span: Span) -> Halt {
343        self.diags.push(
344            Diagnostic::error("B0209", msg, span)
345                .with_primary_label("computed while expanding a macro"),
346        );
347        Halt
348    }
349
350    // ------------------------------------------------------------------------------- statements
351
352    fn block(&mut self, block: &Node, frame: &mut HashMap<Arc<str>, Val>) -> Eval<Flow> {
353        self.enter(block.span())?;
354        let out = self.block_inner(block, frame);
355        self.nesting.leave();
356        out
357    }
358
359    fn block_inner(&mut self, block: &Node, frame: &mut HashMap<Arc<str>, Val>) -> Eval<Flow> {
360        let stmts: &[Node] = if block.is_form(sym::DO) {
361            &block.args
362        } else {
363            std::slice::from_ref(block)
364        };
365        for stmt in stmts {
366            match self.stmt(stmt, frame)? {
367                Flow::Fell => {}
368                done => return Ok(done),
369            }
370        }
371        Ok(Flow::Fell)
372    }
373
374    fn stmt(&mut self, s: &Node, frame: &mut HashMap<Arc<str>, Val>) -> Eval<Flow> {
375        self.step(s.span())?;
376
377        if (s.is_form(sym::LET) || s.is_form(sym::VAR)) && s.args.len() == 2 {
378            let target = &s.args[0];
379            let name = if target.is_form(sym::ANNOT) {
380                target.args.first().and_then(Node::as_var)
381            } else {
382                target.as_var()
383            };
384            let Some(name) = name.map(|v| v.name.clone()) else {
385                return Err(self.wrong("a macro body binds a name, not a pattern", s.span()));
386            };
387            let v = self.eval(&s.args[1], frame)?;
388            frame.insert(name, v);
389            return Ok(Flow::Fell);
390        }
391
392        if s.is_form(sym::RETURN) {
393            let v = match s.args.first() {
394                Some(e) => self.eval(e, frame)?,
395                None => Val::Unit,
396            };
397            return Ok(Flow::Returned(v));
398        }
399
400        if s.is_form(sym::IF) && s.args.len() >= 2 {
401            let cond = self.eval(&s.args[0], frame)?;
402            return if self.truth(&cond, s.args[0].span())? {
403                self.block(&s.args[1], frame)
404            } else if let Some(alt) = s.args.get(2) {
405                self.block(alt, frame)
406            } else {
407                Ok(Flow::Fell)
408            };
409        }
410
411        if s.is_form(sym::FOR) && s.args.len() == 3 {
412            let Some(binder) = s.args[0].as_var().map(|v| v.name.clone()) else {
413                return Err(self.wrong("a `for` binds one name", s.args[0].span()));
414            };
415            let seq = self.eval(&s.args[1], frame)?;
416            let items = match &seq {
417                Val::List(xs) => xs.as_ref().clone(),
418                Val::Syntax(n) if n.is_form(sym::LIST) || n.is_form(sym::DO) => {
419                    n.args.iter().cloned().map(Val::Syntax).collect()
420                }
421                other => {
422                    let msg = format!("a `for` walks a list, not {}", other.type_name());
423                    return Err(self.wrong(msg, s.args[1].span()));
424                }
425            };
426            for item in items {
427                self.step(s.span())?;
428                frame.insert(binder.clone(), item);
429                match self.block(&s.args[2], frame)? {
430                    Flow::Fell => {}
431                    done => return Ok(done),
432                }
433            }
434            return Ok(Flow::Fell);
435        }
436
437        if s.is_form(sym::WHILE) && s.args.len() == 2 {
438            loop {
439                self.step(s.span())?;
440                let cond = self.eval(&s.args[0], frame)?;
441                if !self.truth(&cond, s.args[0].span())? {
442                    return Ok(Flow::Fell);
443                }
444                match self.block(&s.args[1], frame)? {
445                    Flow::Fell => {}
446                    done => return Ok(done),
447                }
448            }
449        }
450
451        if s.is_form(sym::DO) {
452            return self.block(s, frame);
453        }
454
455        // A statement whose value is discarded — a call written for its result and then ignored.
456        self.eval(s, frame)?;
457        Ok(Flow::Fell)
458    }
459
460    /// The forms that belong to the program rather than to the expander.
461    ///
462    /// Refused by name so the message is about the form: without this a `raise` in a macro body
463    /// would be an applied node whose head resolves to nothing, and the diagnostic would say
464    /// `raise` cannot be found — which is true of the environment and false about the language.
465    fn refuse_program_form(&mut self, n: &Node) -> Eval<()> {
466        let Some(head) = PROGRAM_ONLY.iter().find(|f| n.is_form(f)) else {
467            return Ok(());
468        };
469        self.diags.push(
470            Diagnostic::error(
471                "B0205",
472                format!("`{head}` is not available in a macro body"),
473                n.span(),
474            )
475            .with_primary_label("this belongs to the program the macro expands to")
476            .with_note(
477                "a macro body is pure compile-time computation: bindings, `if`, `for`, `while`, \
478                 lambdas, calls and `quote:`. Failure, declarations and pattern matching on \
479                 variants are the program's, not the expander's",
480            )
481            .with_fix("put it inside the `quote:` the macro returns"),
482        );
483        Err(Halt)
484    }
485
486    fn truth(&mut self, v: &Val, span: Span) -> Eval<bool> {
487        match v {
488            Val::Bool(b) => Ok(*b),
489            other => {
490                let msg = format!("a condition is a Bool, not {}", other.type_name());
491                Err(self.wrong(msg, span))
492            }
493        }
494    }
495
496    // ------------------------------------------------------------------------------ expressions
497
498    fn eval(&mut self, e: &Node, frame: &mut HashMap<Arc<str>, Val>) -> Eval<Val> {
499        self.step(e.span())?;
500        self.enter(e.span())?;
501        let out = self.eval_inner(e, frame);
502        self.nesting.leave();
503        out
504    }
505
506    fn eval_inner(&mut self, e: &Node, frame: &mut HashMap<Arc<str>, Val>) -> Eval<Val> {
507        self.refuse_program_form(e)?;
508
509        // A literal.
510        if let Some(l) = e.as_lit() {
511            return Ok(match l {
512                Lit::Int(n) => Val::Int(*n),
513                Lit::Float(f) => Val::Float(*f),
514                Lit::Str(s) => Val::Str(s.clone()),
515                Lit::Bool(b) => Val::Bool(*b),
516                Lit::Keyword(k) => Val::Keyword(k.clone()),
517            });
518        }
519
520        // A name.
521        if let Some(v) = e.as_var() {
522            if let Some(bound) = frame.get(&v.name) {
523                return Ok(bound.clone());
524            }
525            if v.name.as_ref() == "unit" {
526                return Ok(Val::Unit);
527            }
528            if let Some(def) = self.defs.get(&v.name) {
529                // A `def` used as a value is a function of its parameters.
530                return Ok(Val::Fun(Arc::new(Lambda {
531                    params: def.params.clone(),
532                    body: def.body.clone(),
533                    captured: HashMap::new(),
534                })));
535            }
536            return Err(self.unbound(&v.name, e.span()));
537        }
538
539        // `quote:` — the form whose value is syntax.
540        if e.is_form(sym::QUOTE) && e.args.len() == 1 {
541            let body = self.template(&e.args[0], frame)?;
542            return Ok(Val::Syntax(unwrap_block(body)));
543        }
544        if e.is_form(sym::UNQUOTE) || e.is_form(sym::SPLICE) {
545            let head = if e.is_form(sym::UNQUOTE) { "$" } else { "$*" };
546            let msg = format!("`{head}` is only meaningful inside a `quote:`");
547            return Err(self.wrong(msg, e.span()));
548        }
549
550        // Literal collections.
551        if e.is_form(sym::LIST) {
552            let mut out = Vec::with_capacity(e.args.len());
553            for a in &e.args {
554                out.push(self.eval(a, frame)?);
555            }
556            return Ok(Val::list(out));
557        }
558        if e.is_form(sym::RECORD) {
559            let mut fields: Vec<(Arc<str>, Val)> = Vec::new();
560            let mut i = 0;
561            while i + 1 < e.args.len() {
562                let Some(k) = e.args[i].as_keyword() else {
563                    return Err(self.wrong("a record field is a name", e.args[i].span()));
564                };
565                let v = self.eval(&e.args[i + 1], frame)?;
566                fields.push((Arc::from(k), v));
567                i += 2;
568            }
569            return Ok(Val::Record(Arc::new(fields)));
570        }
571
572        // The conditional expression, `a if c else b`.
573        if e.is_form(sym::IF) && e.args.len() == 3 {
574            let c = self.eval(&e.args[0], frame)?;
575            return if self.truth(&c, e.args[0].span())? {
576                self.eval(&e.args[1], frame)
577            } else {
578                self.eval(&e.args[2], frame)
579            };
580        }
581
582        // `and` and `or` short-circuit, which is why they are forms rather than builtins
583        // (`docs/53` §53.2 is where that was found out about the *program's* half).
584        if (e.is_form("and") || e.is_form("or")) && e.args.len() == 2 {
585            let left = self.eval(&e.args[0], frame)?;
586            let left = self.truth(&left, e.args[0].span())?;
587            if e.is_form("and") && !left {
588                return Ok(Val::Bool(false));
589            }
590            if e.is_form("or") && left {
591                return Ok(Val::Bool(true));
592            }
593            let right = self.eval(&e.args[1], frame)?;
594            return Ok(Val::Bool(self.truth(&right, e.args[1].span())?));
595        }
596
597        // A lambda closes over a copy of the frame: a macro body is a compile-time computation
598        // and nothing it builds outlives the expansion, so there is nothing for a shared cell to
599        // be observed by.
600        if e.is_form(sym::FN) && e.args.len() == 2 {
601            let params = e.args[0]
602                .args
603                .iter()
604                .filter_map(|p| {
605                    let target = if p.is_form(sym::ANNOT) { &p.args[0] } else { p };
606                    target.as_var().map(|s| s.name.clone())
607                })
608                .collect();
609            return Ok(Val::Fun(Arc::new(Lambda {
610                params,
611                body: e.args[1].clone(),
612                captured: frame.clone(),
613            })));
614        }
615
616        // `xs[i]`.
617        if e.is_form("index") && e.args.len() == 2 {
618            let subject = self.eval(&e.args[0], frame)?;
619            let idx = self.eval(&e.args[1], frame)?;
620            return self.index(&subject, &idx, e.span());
621        }
622
623        // `r.field`, and — the one method-shaped form — nothing else.
624        if e.is_form(sym::DOT) && e.args.len() == 2 {
625            let subject = self.eval(&e.args[0], frame)?;
626            let Some(field) = e.args[1].as_var().map(|s| s.name.clone()) else {
627                return Err(self.wrong("a field is a name", e.args[1].span()));
628            };
629            if let Val::Type(t) = &subject {
630                return self.type_field(t, &field, e.span());
631            }
632            let Val::Record(fields) = &subject else {
633                let msg = format!("{} has no fields", subject.type_name());
634                return Err(self.wrong(msg, e.span()));
635            };
636            return match fields.iter().find(|(k, _)| *k == field) {
637                Some((_, v)) => Ok(v.clone()),
638                None => {
639                    let msg = format!("this record has no field `{field}`");
640                    Err(self.wrong(msg, e.args[1].span()))
641                }
642            };
643        }
644        if e.is_form(sym::DOT) {
645            return Err(self.wrong(
646                "a macro body calls functions, not methods — the compile-time environment has no \
647                 traits",
648                e.span(),
649            ));
650        }
651
652        // A call: `(name args…)` or `(call callee args…)`.
653        if e.applied {
654            let (callee, args): (Option<Node>, &[Node]) = if e.is_form(sym::CALL) {
655                (e.args.first().cloned(), &e.args[1..])
656            } else {
657                (None, &e.args[..])
658            };
659
660            // The callee is checked *before* the arguments are evaluated: `http_fetch(url, req)`
661            // should say what is wrong with `http_fetch` rather than what is wrong with `req`.
662            if let Some(name) = e.head_sym().filter(|_| callee.is_none()) {
663                if !frame.contains_key(&name.name)
664                    && !self.defs.contains_key(&name.name)
665                    && RESTRICTED.iter().any(|(n, _)| *n == name.name.as_ref())
666                {
667                    return Err(self.unbound(&name.name.clone(), e.span()));
668                }
669                // Calling *syntax* is never right, and there is one way to write it by accident:
670                // `$n(x)` inside a `quote:`. `$` takes an expression, so that is the call `n(x)`
671                // evaluated in the body — where `x` is a name the *template* has and this
672                // environment does not. Said here, before the arguments are evaluated, because
673                // otherwise the report is about `x` and the reader is looking at the wrong word.
674                if matches!(frame.get(&name.name), Some(Val::Syntax(_))) {
675                    let msg = format!(
676                        "`{name}` is syntax and cannot be called at compile time — inside a \
677                         `quote:`, write `{name}(…)` rather than `${name}(…)`"
678                    );
679                    return Err(self.wrong(msg, e.span()));
680                }
681            }
682
683            let mut values = Vec::with_capacity(args.len());
684            for a in args {
685                // A keyword argument in a macro body is bound by name at the call, which the
686                // compile-time environment does not do: parameters are positional here.
687                if a.is_form(sym::KW_ARG) {
688                    return Err(self.wrong(
689                        "a compile-time call passes its arguments by position",
690                        a.span(),
691                    ));
692                }
693                values.push(self.eval(a, frame)?);
694            }
695
696            if let Some(callee) = callee {
697                let f = self.eval(&callee, frame)?;
698                return self.apply(&f, values, e.span());
699            }
700
701            let Some(name) = e.head_sym().map(|s| s.name.clone()) else {
702                return Err(self.wrong("a call needs a callee", e.span()));
703            };
704
705            // A local — a lambda in a binding — before a `def`, and a `def` before a builtin, so
706            // that a module can name a function of its own after one of the prelude's and have
707            // the compile-time environment agree with the program about which one it means.
708            if let Some(bound) = frame.get(&name).cloned() {
709                return self.apply(&bound, values, e.span());
710            }
711            if let Some(def) = self.defs.get(&name).cloned() {
712                return self.call_def(&name, &def, values, e.span());
713            }
714            if let Some(out) = self.builtin(&name, &values, e.span()) {
715                return out;
716            }
717            return Err(self.unbound(&name, e.span()));
718        }
719
720        let head = e.head_name().unwrap_or("this form").to_string();
721        Err(self.wrong(format!("`{head}` has no value at compile time"), e.span()))
722    }
723
724    fn unbound(&mut self, name: &str, span: Span) -> Halt {
725        if let Some((_, atom)) = RESTRICTED.iter().find(|(n, _)| *n == name) {
726            self.diags.push(
727                Diagnostic::error(
728                    "B0207",
729                    format!("`{name}` may not be called while expanding a macro"),
730                    span,
731                )
732                .with_primary_label(format!("performs `{atom}`"))
733                .with_note(
734                    "macro expansion is capability-restricted (`docs/02` §2.4): it is pure \
735                     computation over the module's own definitions, so that what a compile \
736                     produces depends on the source and on nothing else",
737                )
738                .with_fix("compute this in the program the macro expands to, not in the macro"),
739            );
740            return Halt;
741        }
742        self.diags.push(
743            Diagnostic::error(
744                "B0208",
745                format!("cannot find `{name}` at compile time"),
746                span,
747            )
748            .with_primary_label(
749                "not a local, a `def` in this module or one it imports, or a compile-time builtin",
750            )
751            .with_note(
752                "the macro interpreter's environment is deliberately small: the pure part of the \
753                 prelude, the definitions of this module and the ones it imports, and the \
754                 `node_*` reflection over syntax",
755            ),
756        );
757        Halt
758    }
759
760    fn index(&mut self, subject: &Val, idx: &Val, span: Span) -> Eval<Val> {
761        let Val::Int(i) = idx else {
762            let msg = format!("an index is an Int, not {}", idx.type_name());
763            return Err(self.wrong(msg, span));
764        };
765        let items: Vec<Val> = match subject {
766            Val::List(xs) => xs.as_ref().clone(),
767            Val::Syntax(n) if n.is_form(sym::LIST) || n.is_form(sym::DO) => {
768                n.args.iter().cloned().map(Val::Syntax).collect()
769            }
770            other => {
771                let msg = format!("{} is not indexable", other.type_name());
772                return Err(self.wrong(msg, span));
773            }
774        };
775        match usize::try_from(*i).ok().and_then(|i| items.get(i)) {
776            Some(v) => Ok(v.clone()),
777            None => {
778                let msg = format!("index {i} is outside a list of {}", items.len());
779                Err(self.wrong(msg, span))
780            }
781        }
782    }
783
784    fn apply(&mut self, f: &Val, args: Vec<Val>, span: Span) -> Eval<Val> {
785        let Val::Fun(lambda) = f else {
786            let msg = format!("{} is not a function", f.type_name());
787            return Err(self.wrong(msg, span));
788        };
789        if lambda.params.len() != args.len() {
790            let msg = format!(
791                "this function takes {} argument(s) and got {}",
792                lambda.params.len(),
793                args.len()
794            );
795            return Err(self.wrong(msg, span));
796        }
797        let mut frame = lambda.captured.clone();
798        for (p, a) in lambda.params.iter().zip(args) {
799            frame.insert(p.clone(), a);
800        }
801        self.enter(span)?;
802        let out = self.block(&lambda.body, &mut frame);
803        self.nesting.leave();
804        match out? {
805            Flow::Returned(v) => Ok(v),
806            // `lambda t: e` parses as a block holding one expression, so a body that falls off the
807            // end has one statement whose value is the answer.
808            Flow::Fell => self.last_value(&lambda.body.clone(), &mut frame),
809        }
810    }
811
812    fn last_value(&mut self, body: &Node, frame: &mut HashMap<Arc<str>, Val>) -> Eval<Val> {
813        let last = if body.is_form(sym::DO) {
814            body.args.last()
815        } else {
816            Some(body)
817        };
818        match last {
819            Some(e) => self.eval(e, frame),
820            None => Ok(Val::Unit),
821        }
822    }
823
824    fn call_def(&mut self, name: &str, def: &FnDef, args: Vec<Val>, span: Span) -> Eval<Val> {
825        if def.params.len() != args.len() {
826            let msg = format!(
827                "`{name}` takes {} argument(s) and got {}",
828                def.params.len(),
829                args.len()
830            );
831            return Err(self.wrong(msg, span));
832        }
833        let mut frame: HashMap<Arc<str>, Val> = HashMap::new();
834        for (p, a) in def.params.iter().zip(args) {
835            frame.insert(p.clone(), a);
836        }
837        self.enter(span)?;
838        let out = self.block(&def.body, &mut frame);
839        self.nesting.leave();
840        match out? {
841            Flow::Returned(v) => Ok(v),
842            Flow::Fell => Ok(Val::Unit),
843        }
844    }
845
846    // -------------------------------------------------------------------------------- templates
847
848    /// Walk a `quote`d template, replacing `$e` with the syntax of `e`'s value.
849    ///
850    /// Everything else is carried through unchanged — a template is data, so a `for` inside a
851    /// `quote:` is a loop in the *program being built*, not one the interpreter runs.
852    fn template(&mut self, t: &Node, frame: &mut HashMap<Arc<str>, Val>) -> Eval<Node> {
853        self.step(t.span())?;
854        self.enter(t.span())?;
855        let out = self.template_inner(t, frame);
856        self.nesting.leave();
857        out
858    }
859
860    fn template_inner(&mut self, t: &Node, frame: &mut HashMap<Arc<str>, Val>) -> Eval<Node> {
861        if t.is_form(sym::UNQUOTE) && t.args.len() == 1 {
862            // `$x` where `x` is bound nowhere is worth its own code: the mistake is almost always
863            // a parameter that was renamed, and the span to point at is the `$`.
864            if let Some(v) = t.args[0].as_var() {
865                if !frame.contains_key(&v.name) && !self.defs.contains_key(&v.name) {
866                    self.diags.push(
867                        Diagnostic::error(
868                            "B0206",
869                            format!("`${v}` is not bound in this macro"),
870                            t.span(),
871                        )
872                        .with_primary_label("unquoting an unbound name")
873                        .with_note("`$e` evaluates `e` in the macro body's own environment"),
874                    );
875                    return Err(Halt);
876                }
877            }
878            let v = self.eval(&t.args[0], frame)?;
879            return self.reflect(&v, t.span());
880        }
881
882        let mut args = Vec::with_capacity(t.args.len());
883        for a in &t.args {
884            if a.is_form(sym::SPLICE) && a.args.len() == 1 {
885                let v = self.eval(&a.args[0], frame)?;
886                for piece in self.spliced(&v, a.span())? {
887                    args.push(piece);
888                }
889                continue;
890            }
891            args.push(self.template(a, frame)?);
892        }
893
894        // A template head that names a bound piece of syntax is that syntax: `$f(x)` is written
895        // `f(x)` inside a quote, because a head is a symbol and `$` takes an expression.
896        let head = match &t.head {
897            Head::Sym(s) => match frame.get(&s.name) {
898                Some(Val::Syntax(bound)) if t.applied && !bound.applied => match &bound.head {
899                    Head::Sym(bs) => Head::Sym(bs.clone()),
900                    _ => Head::Sym(s.clone()),
901                },
902                _ => Head::Sym(s.clone()),
903            },
904            Head::Lit(l) => Head::Lit(l.clone()),
905        };
906
907        Ok(Node {
908            head,
909            args,
910            applied: t.applied,
911            meta: t.meta.clone(),
912        })
913    }
914
915    /// What `$*xs` puts into the surrounding form.
916    fn spliced(&mut self, v: &Val, span: Span) -> Eval<Vec<Node>> {
917        match v {
918            Val::List(xs) => {
919                let mut out = Vec::with_capacity(xs.len());
920                for x in xs.iter() {
921                    out.push(self.reflect(x, span)?);
922                }
923                Ok(out)
924            }
925            Val::Syntax(n) if n.is_form(sym::LIST) || n.is_form(sym::DO) => Ok(n.args.clone()),
926            other => Ok(vec![self.reflect(other, span)?]),
927        }
928    }
929
930    /// A value's syntax.
931    ///
932    /// Every value has one except a function: a closure is a thing the compile-time environment
933    /// holds, and there is no expression that denotes it in the program being built.
934    pub fn reflect(&mut self, v: &Val, span: Span) -> Eval<Node> {
935        Ok(match v {
936            Val::Unit => Node::sym("unit", span),
937            Val::Int(n) => Node::lit(Lit::Int(*n), span),
938            Val::Float(f) => Node::lit(Lit::Float(*f), span),
939            Val::Str(s) => Node::lit(Lit::Str(s.clone()), span),
940            Val::Bool(b) => Node::lit(Lit::Bool(*b), span),
941            Val::Keyword(k) => Node::lit(Lit::Keyword(k.clone()), span),
942            Val::Syntax(n) => n.clone(),
943            Val::List(xs) => {
944                let mut args = Vec::with_capacity(xs.len());
945                for x in xs.iter() {
946                    args.push(self.reflect(x, span)?);
947                }
948                Node::form(sym::LIST, args, span)
949            }
950            Val::Record(fields) => {
951                let mut args = Vec::with_capacity(fields.len() * 2);
952                for (k, val) in fields.iter() {
953                    args.push(Node::lit(Lit::Keyword(k.clone()), span));
954                    args.push(self.reflect(val, span)?);
955                }
956                Node::form(sym::RECORD, args, span)
957            }
958            Val::Fun(_) => {
959                return Err(self.wrong(
960                    "a function has no syntax — a macro returns the code that builds one",
961                    span,
962                ))
963            }
964            Val::Type(t) => {
965                let msg = format!(
966                    "`{t}` is a type and has no syntax — a macro reads a type and writes the code \
967                     that a value of it goes through"
968                );
969                return Err(self.wrong(msg, span));
970            }
971        })
972    }
973
974    /// What a type answers when asked about itself: `t.name`, `t.kind`, `t.args`, `t.result`,
975    /// `t.fields`, `t.variants`, `t.inner`.
976    ///
977    /// Read on access rather than carried in the value, because `model Tree: left: Tree` is a type
978    /// whose fields mention itself: a value holding its own fields would not be finite.
979    fn type_field(&mut self, t: &TyRepr, field: &str, span: Span) -> Eval<Val> {
980        let types = self.types;
981        let of = |r: &TyRepr| Val::Type(Arc::new(r.clone()));
982        let pairs = |fs: crate::typed::Fields| {
983            Val::list(
984                fs.into_iter()
985                    .map(|(n, ft)| {
986                        Val::Record(Arc::new(vec![
987                            (Arc::from("name"), Val::Str(n)),
988                            (Arc::from("ty"), Val::Type(Arc::new(ft))),
989                        ]))
990                    })
991                    .collect(),
992            )
993        };
994        match field {
995            "name" => Ok(Val::Str(t.head())),
996            "kind" => Ok(Val::str_(t.kind_name())),
997            "args" => Ok(Val::list(t.args().iter().map(of).collect())),
998            "result" => match t {
999                TyRepr::Fun { result, .. } => Ok(of(result)),
1000                _ => {
1001                    let msg = format!("`{t}` is not a function, so it has no result type");
1002                    Err(self.wrong(msg, span))
1003                }
1004            },
1005            "fields" => Ok(pairs(types.map(|e| e.fields(t)).unwrap_or_default())),
1006            "inner" => Ok(of(&types.map(|e| e.inner(t)).unwrap_or(TyRepr::Unknown))),
1007            "variants" => {
1008                let vs = types.map(|e| e.variants(t)).unwrap_or_default();
1009                Ok(Val::list(
1010                    vs.into_iter()
1011                        .map(|(n, fs)| {
1012                            Val::Record(Arc::new(vec![
1013                                (Arc::from("name"), Val::Str(n)),
1014                                (Arc::from("fields"), pairs(fs)),
1015                            ]))
1016                        })
1017                        .collect(),
1018                ))
1019            }
1020            other => {
1021                let msg = format!(
1022                    "a type answers `name`, `kind`, `args`, `result`, `fields`, `variants` and \
1023                     `inner` — not `{other}`"
1024                );
1025                Err(self.wrong(msg, span))
1026            }
1027        }
1028    }
1029
1030    // --------------------------------------------------------------------------------- builtins
1031
1032    /// The whitelist. `None` means the name is not a builtin at all.
1033    fn builtin(&mut self, name: &str, args: &[Val], span: Span) -> Option<Eval<Val>> {
1034        if !is_builtin(name) {
1035            return None;
1036        }
1037        Some(self.builtin_inner(name, args, span))
1038    }
1039
1040    fn builtin_inner(&mut self, name: &str, args: &[Val], span: Span) -> Eval<Val> {
1041        let arity = |want: usize, this: &mut Self| -> Eval<()> {
1042            if args.len() == want {
1043                Ok(())
1044            } else {
1045                let msg = format!("`{name}` takes {want} argument(s) and got {}", args.len());
1046                Err(this.wrong(msg, span))
1047            }
1048        };
1049
1050        macro_rules! s {
1051            ($i:expr) => {{
1052                match &args[$i] {
1053                    Val::Str(s) => s.clone(),
1054                    other => {
1055                        let msg = format!("`{name}` expects a Str, not {}", other.type_name());
1056                        return Err(self.wrong(msg, span));
1057                    }
1058                }
1059            }};
1060        }
1061        macro_rules! i {
1062            ($i:expr) => {{
1063                match &args[$i] {
1064                    Val::Int(n) => *n,
1065                    other => {
1066                        let msg = format!("`{name}` expects an Int, not {}", other.type_name());
1067                        return Err(self.wrong(msg, span));
1068                    }
1069                }
1070            }};
1071        }
1072        macro_rules! l {
1073            ($i:expr) => {{
1074                match &args[$i] {
1075                    Val::List(xs) => xs.as_ref().clone(),
1076                    other => {
1077                        let msg = format!("`{name}` expects a list, not {}", other.type_name());
1078                        return Err(self.wrong(msg, span));
1079                    }
1080                }
1081            }};
1082        }
1083        macro_rules! n {
1084            ($i:expr) => {{
1085                match &args[$i] {
1086                    Val::Syntax(n) => n.clone(),
1087                    other => {
1088                        let msg = format!("`{name}` expects syntax, not {}", other.type_name());
1089                        return Err(self.wrong(msg, span));
1090                    }
1091                }
1092            }};
1093        }
1094
1095        match name {
1096            // ---- text. The tables are `beck-prim`'s, so a macro folding a letter and a program
1097            // folding the same letter reach one implementation (`docs/93` §93.12).
1098            "str" => {
1099                arity(1, self)?;
1100                Ok(Val::str_(display(&args[0])))
1101            }
1102            "str_len" => {
1103                arity(1, self)?;
1104                Ok(Val::Int(s!(0).chars().count() as i64))
1105            }
1106            "str_is_empty" => {
1107                arity(1, self)?;
1108                Ok(Val::Bool(s!(0).is_empty()))
1109            }
1110            // The prelude's returns an `Option` and this has no unions, so — as with indexing —
1111            // the compile-time half is **total and refuses**. A macro parsing a typed literal
1112            // needs a number out of text and has no other way to get one.
1113            "str_to_int" => {
1114                arity(1, self)?;
1115                let text = s!(0);
1116                match text.trim().parse::<i64>() {
1117                    Ok(n) => Ok(Val::Int(n)),
1118                    Err(_) => {
1119                        let msg = format!("`{text}` is not an integer");
1120                        Err(self.wrong(msg, span))
1121                    }
1122                }
1123            }
1124            "str_trim" => {
1125                arity(1, self)?;
1126                Ok(Val::str_(s!(0).trim()))
1127            }
1128            "str_upper" => {
1129                arity(1, self)?;
1130                Ok(Val::str_(beck_prim::text::upper(&s!(0))))
1131            }
1132            "str_lower" => {
1133                arity(1, self)?;
1134                Ok(Val::str_(beck_prim::text::lower(&s!(0))))
1135            }
1136            "str_contains" => {
1137                arity(2, self)?;
1138                Ok(Val::Bool(s!(0).contains(s!(1).as_ref())))
1139            }
1140            "str_starts_with" => {
1141                arity(2, self)?;
1142                Ok(Val::Bool(s!(0).starts_with(s!(1).as_ref())))
1143            }
1144            "str_ends_with" => {
1145                arity(2, self)?;
1146                Ok(Val::Bool(s!(0).ends_with(s!(1).as_ref())))
1147            }
1148            "str_slice" => {
1149                arity(3, self)?;
1150                let (start, len) = (i!(1).max(0) as usize, i!(2).max(0) as usize);
1151                let out: String = s!(0).chars().skip(start).take(len).collect();
1152                Ok(Val::str_(out))
1153            }
1154            "str_replace" => {
1155                arity(3, self)?;
1156                Ok(Val::str_(beck_prim::text::replace(&s!(0), &s!(1), &s!(2))))
1157            }
1158            "str_repeat" => {
1159                arity(2, self)?;
1160                let n = i!(1).clamp(0, 1_000_000) as usize;
1161                Ok(Val::str_(s!(0).repeat(n)))
1162            }
1163            "str_chars" => {
1164                arity(1, self)?;
1165                Ok(Val::list(
1166                    s!(0).chars().map(|c| Val::str_(c.to_string())).collect(),
1167                ))
1168            }
1169            "str_split" => {
1170                arity(2, self)?;
1171                let (hay, sep) = (s!(0), s!(1));
1172                let parts: Vec<Val> = if sep.is_empty() {
1173                    hay.chars().map(|c| Val::str_(c.to_string())).collect()
1174                } else {
1175                    hay.split(sep.as_ref()).map(Val::str_).collect()
1176                };
1177                Ok(Val::list(parts))
1178            }
1179            "str_join" => {
1180                arity(2, self)?;
1181                let xs = l!(0);
1182                let sep = s!(1);
1183                let parts: Vec<String> = xs.iter().map(display).collect();
1184                Ok(Val::str_(parts.join(sep.as_ref())))
1185            }
1186
1187            // ---- lists
1188            "list_len" => {
1189                arity(1, self)?;
1190                Ok(Val::Int(l!(0).len() as i64))
1191            }
1192            "list_is_empty" => {
1193                arity(1, self)?;
1194                Ok(Val::Bool(l!(0).is_empty()))
1195            }
1196            "list_append" => {
1197                arity(2, self)?;
1198                let mut xs = l!(0);
1199                xs.push(args[1].clone());
1200                Ok(Val::list(xs))
1201            }
1202            "list_reverse" => {
1203                arity(1, self)?;
1204                let mut xs = l!(0);
1205                xs.reverse();
1206                Ok(Val::list(xs))
1207            }
1208            "list_contains" => {
1209                arity(2, self)?;
1210                Ok(Val::Bool(l!(0).iter().any(|x| val_eq(x, &args[1]))))
1211            }
1212            "list_take" => {
1213                arity(2, self)?;
1214                let n = i!(1).max(0) as usize;
1215                Ok(Val::list(l!(0).into_iter().take(n).collect()))
1216            }
1217            "list_drop" => {
1218                arity(2, self)?;
1219                let n = i!(1).max(0) as usize;
1220                Ok(Val::list(l!(0).into_iter().skip(n).collect()))
1221            }
1222            "list_slice" => {
1223                arity(3, self)?;
1224                let (start, len) = (i!(1).max(0) as usize, i!(2).max(0) as usize);
1225                Ok(Val::list(l!(0).into_iter().skip(start).take(len).collect()))
1226            }
1227            // One argument, a list *of* lists — the prelude's shape, not `a + b`.
1228            "concat_lists" => {
1229                arity(1, self)?;
1230                let mut out = Vec::new();
1231                for group in l!(0) {
1232                    match group {
1233                        Val::List(ys) => out.extend(ys.as_ref().clone()),
1234                        other => {
1235                            let msg = format!(
1236                                "`concat_lists` takes a list of lists, and found {}",
1237                                other.type_name()
1238                            );
1239                            return Err(self.wrong(msg, span));
1240                        }
1241                    }
1242                }
1243                Ok(Val::list(out))
1244            }
1245            "map_list" => {
1246                arity(2, self)?;
1247                let xs = l!(0);
1248                let f = args[1].clone();
1249                let mut out = Vec::with_capacity(xs.len());
1250                for x in xs {
1251                    out.push(self.apply(&f, vec![x], span)?);
1252                }
1253                Ok(Val::list(out))
1254            }
1255            "filter_list" => {
1256                arity(2, self)?;
1257                let xs = l!(0);
1258                let f = args[1].clone();
1259                let mut out = Vec::new();
1260                for x in xs {
1261                    let keep = self.apply(&f, vec![x.clone()], span)?;
1262                    if self.truth(&keep, span)? {
1263                        out.push(x);
1264                    }
1265                }
1266                Ok(Val::list(out))
1267            }
1268            "list_fold" => {
1269                arity(3, self)?;
1270                let xs = l!(0);
1271                let mut acc = args[1].clone();
1272                let f = args[2].clone();
1273                for x in xs {
1274                    acc = self.apply(&f, vec![acc, x], span)?;
1275                }
1276                Ok(acc)
1277            }
1278            "list_all" | "list_any" => {
1279                arity(2, self)?;
1280                let xs = l!(0);
1281                let f = args[1].clone();
1282                let all = name == "list_all";
1283                for x in xs {
1284                    let got = self.apply(&f, vec![x], span)?;
1285                    if self.truth(&got, span)? != all {
1286                        return Ok(Val::Bool(!all));
1287                    }
1288                }
1289                Ok(Val::Bool(all))
1290            }
1291            "list_flat_map" => {
1292                arity(2, self)?;
1293                let xs = l!(0);
1294                let f = args[1].clone();
1295                let mut out = Vec::new();
1296                for x in xs {
1297                    match self.apply(&f, vec![x], span)? {
1298                        Val::List(ys) => out.extend(ys.as_ref().clone()),
1299                        other => {
1300                            let msg =
1301                                format!("`list_flat_map` expects lists, not {}", other.type_name());
1302                            return Err(self.wrong(msg, span));
1303                        }
1304                    }
1305                }
1306                Ok(Val::list(out))
1307            }
1308
1309            // ---- numbers. No `sqrt`, `sin` or `cos`: they would make what the compiler produces
1310            // depend on the host's libm, which is F9's question and not one to prejudge here.
1311            "abs" => {
1312                arity(1, self)?;
1313                match &args[0] {
1314                    Val::Int(n) => Ok(Val::Int(n.abs())),
1315                    Val::Float(f) => Ok(Val::Float(f.abs())),
1316                    other => {
1317                        let msg = format!("`abs` expects a number, not {}", other.type_name());
1318                        Err(self.wrong(msg, span))
1319                    }
1320                }
1321            }
1322            "float" => {
1323                arity(1, self)?;
1324                Ok(Val::Float(i!(0) as f64))
1325            }
1326            "trunc" => {
1327                arity(1, self)?;
1328                match &args[0] {
1329                    Val::Float(f) => Ok(Val::Int(f.trunc() as i64)),
1330                    other => {
1331                        let msg = format!("`trunc` expects a Float, not {}", other.type_name());
1332                        Err(self.wrong(msg, span))
1333                    }
1334                }
1335            }
1336
1337            // ---- syntax. §2.2's `Node` is "an ordinary Beck value"; this is the part of that
1338            // sentence a macro can reach today — read a node, build a node.
1339            "node_head" => {
1340                arity(1, self)?;
1341                match &n!(0).head {
1342                    Head::Sym(s) => Ok(Val::str_(s.as_str())),
1343                    Head::Lit(_) => Err(self.wrong(
1344                        "this node is a literal and has no head symbol — ask `node_is_lit` first",
1345                        span,
1346                    )),
1347                }
1348            }
1349            "node_args" => {
1350                arity(1, self)?;
1351                Ok(Val::list(
1352                    n!(0).args.iter().cloned().map(Val::Syntax).collect(),
1353                ))
1354            }
1355            "node_is_call" => {
1356                arity(1, self)?;
1357                Ok(Val::Bool(n!(0).applied))
1358            }
1359            "node_is_lit" => {
1360                arity(1, self)?;
1361                Ok(Val::Bool(n!(0).as_lit().is_some()))
1362            }
1363            // The reader `node_head` is for a symbol; this is its other half. Without it a macro
1364            // could ask whether its argument was a literal and never find out *which* one, which
1365            // is what a typed literal's body is (§2.5) — the sigil desugaring hands the macro
1366            // `raw="…"` and the parse begins by reading that string.
1367            "node_lit" => {
1368                arity(1, self)?;
1369                let node = n!(0);
1370                match node.as_lit() {
1371                    Some(Lit::Int(n)) => Ok(Val::Int(*n)),
1372                    Some(Lit::Float(f)) => Ok(Val::Float(*f)),
1373                    Some(Lit::Str(s)) => Ok(Val::Str(s.clone())),
1374                    Some(Lit::Bool(b)) => Ok(Val::Bool(*b)),
1375                    Some(Lit::Keyword(k)) => Ok(Val::Keyword(k.clone())),
1376                    None => Err(self.wrong(
1377                        "this node is not a literal and has no value — ask `node_is_lit` first",
1378                        span,
1379                    )),
1380                }
1381            }
1382            "node_sym" => {
1383                arity(1, self)?;
1384                Ok(Val::Syntax(Node::sym(s!(0).as_ref(), span)))
1385            }
1386            "node_form" => {
1387                arity(2, self)?;
1388                let head = s!(0);
1389                let mut items = Vec::new();
1390                for a in l!(1) {
1391                    items.push(self.reflect(&a, span)?);
1392                }
1393                Ok(Val::Syntax(Node::form(head.as_ref(), items, span)))
1394            }
1395            "node_str" => {
1396                arity(1, self)?;
1397                Ok(Val::str_(print::to_sexpr(&n!(0))))
1398            }
1399            // The one name a `typed macro` body has that an ordinary one does not: what the
1400            // checker inferred this expression to be (`docs/02` §2.4).
1401            "node_ty" => {
1402                arity(1, self)?;
1403                let node = n!(0);
1404                let Some(types) = self.types else {
1405                    return Err(self.wrong(
1406                        "`node_ty` needs the checker's answers, which only a `typed macro` has — \
1407                         write `typed macro` rather than `macro`",
1408                        span,
1409                    ));
1410                };
1411                match types.of(node.span()) {
1412                    Some(t) => Ok(Val::Type(Arc::new(t.clone()))),
1413                    // Everything the call site *supplied* was inferred before this body ran, so
1414                    // what reaches here is syntax the body built itself. Saying which is what
1415                    // stops the answer being a plausible-looking `?`.
1416                    None => {
1417                        let msg = format!(
1418                            "`{}` has no inferred type: `node_ty` answers about the expressions \
1419                             this macro was called with, and this is syntax the body built",
1420                            print::to_sexpr(&node)
1421                        );
1422                        Err(self.wrong(msg, span))
1423                    }
1424                }
1425            }
1426            // A macro that reads a type and writes code for it meets types it has no rule for.
1427            // `refuse` is how it says so, at the call site rather than in its own body.
1428            "refuse" => {
1429                if args.len() != 1 && args.len() != 2 {
1430                    let msg = format!(
1431                        "`refuse` expects a message, and optionally somewhere to \
1432                                       point — {} arguments given",
1433                        args.len()
1434                    );
1435                    return Err(self.wrong(msg, span));
1436                }
1437                let msg = s!(0);
1438                // With a second argument the diagnostic lands on *that* node rather than on the
1439                // call. A macro that parses a typed literal has the body's own span (the sigil
1440                // desugaring gives `raw=` the span inside the quotes), so this is how §2.5's
1441                // "errors at the right offsets inside the literal" is reached.
1442                match args.len() {
1443                    2 => {
1444                        let at = n!(1).span();
1445                        Err(self.refusal_at(msg, at, span))
1446                    }
1447                    _ => Err(self.refusal(msg, span)),
1448                }
1449            }
1450            // `splice([a, b])` is several forms where one is expected — the shape §2.4's `derive`
1451            // returns, and the reason `expand_module` flattens a `do` at the top of a module.
1452            "splice" => {
1453                arity(1, self)?;
1454                let mut items = Vec::new();
1455                for a in l!(0) {
1456                    items.push(self.reflect(&a, span)?);
1457                }
1458                Ok(Val::Syntax(Node::form(sym::DO, items, span)))
1459            }
1460
1461            // ---- operators
1462            "+" | "-" | "*" | "/" | "%" => {
1463                arity(2, self)?;
1464                self.arith(name, &args[0], &args[1], span)
1465            }
1466            "negate" => {
1467                arity(1, self)?;
1468                match &args[0] {
1469                    Val::Int(n) => Ok(Val::Int(-n)),
1470                    Val::Float(f) => Ok(Val::Float(-f)),
1471                    other => {
1472                        let msg = format!("`-` expects a number, not {}", other.type_name());
1473                        Err(self.wrong(msg, span))
1474                    }
1475                }
1476            }
1477            "==" => {
1478                arity(2, self)?;
1479                Ok(Val::Bool(val_eq(&args[0], &args[1])))
1480            }
1481            "!=" => {
1482                arity(2, self)?;
1483                Ok(Val::Bool(!val_eq(&args[0], &args[1])))
1484            }
1485            "<" | "<=" | ">" | ">=" => {
1486                arity(2, self)?;
1487                self.compare(name, &args[0], &args[1], span)
1488            }
1489            "not" => {
1490                arity(1, self)?;
1491                let b = self.truth(&args[0], span)?;
1492                Ok(Val::Bool(!b))
1493            }
1494            other => unreachable!("`{other}` is listed as a builtin and not implemented"),
1495        }
1496    }
1497
1498    fn arith(&mut self, op: &str, a: &Val, b: &Val, span: Span) -> Eval<Val> {
1499        match (a, b) {
1500            (Val::Int(x), Val::Int(y)) => {
1501                if matches!(op, "/" | "%") && *y == 0 {
1502                    return Err(self.wrong("division by zero while expanding a macro", span));
1503                }
1504                Ok(Val::Int(match op {
1505                    "+" => x.wrapping_add(*y),
1506                    "-" => x.wrapping_sub(*y),
1507                    "*" => x.wrapping_mul(*y),
1508                    "/" => x.wrapping_div(*y),
1509                    _ => x.wrapping_rem(*y),
1510                }))
1511            }
1512            (Val::Float(x), Val::Float(y)) => Ok(Val::Float(match op {
1513                "+" => x + y,
1514                "-" => x - y,
1515                "*" => x * y,
1516                "/" => x / y,
1517                _ => x % y,
1518            })),
1519            // `+` concatenates strings, which is how the checker resolves it for a program too.
1520            (Val::Str(x), Val::Str(y)) if op == "+" => Ok(Val::str_(format!("{x}{y}"))),
1521            (Val::List(x), Val::List(y)) if op == "+" => {
1522                let mut out = x.as_ref().clone();
1523                out.extend(y.as_ref().clone());
1524                Ok(Val::list(out))
1525            }
1526            _ => {
1527                let msg = format!(
1528                    "`{op}` does not apply to {} and {}",
1529                    a.type_name(),
1530                    b.type_name()
1531                );
1532                Err(self.wrong(msg, span))
1533            }
1534        }
1535    }
1536
1537    fn compare(&mut self, op: &str, a: &Val, b: &Val, span: Span) -> Eval<Val> {
1538        let ord = match (a, b) {
1539            (Val::Int(x), Val::Int(y)) => x.cmp(y),
1540            (Val::Float(x), Val::Float(y)) => match x.partial_cmp(y) {
1541                Some(o) => o,
1542                None => return Err(self.wrong("a NaN has no order", span)),
1543            },
1544            (Val::Str(x), Val::Str(y)) => x.as_ref().cmp(y.as_ref()),
1545            _ => {
1546                let msg = format!(
1547                    "`{op}` does not compare {} with {}",
1548                    a.type_name(),
1549                    b.type_name()
1550                );
1551                return Err(self.wrong(msg, span));
1552            }
1553        };
1554        Ok(Val::Bool(match op {
1555            "<" => ord.is_lt(),
1556            "<=" => ord.is_le(),
1557            ">" => ord.is_gt(),
1558            _ => ord.is_ge(),
1559        }))
1560    }
1561}
1562
1563/// A one-statement `quote:` block is that statement, not a block.
1564fn unwrap_block(n: Node) -> Node {
1565    if n.is_form(sym::DO) && n.args.len() == 1 {
1566        return n.args[0].clone();
1567    }
1568    n
1569}
1570
1571/// Forms that are the *program's* and are refused with `B0205` rather than falling through to a
1572/// "cannot find" about their head.
1573const PROGRAM_ONLY: &[&str] = &[
1574    sym::MATCH,
1575    sym::TRY,
1576    sym::RAISE,
1577    sym::PARALLEL,
1578    sym::DEF,
1579    sym::MODEL,
1580    sym::UNION,
1581    sym::TRAIT,
1582    sym::IMPL,
1583    sym::TYPE,
1584    sym::NEWTYPE,
1585    sym::IMPORT,
1586    sym::TEST,
1587    sym::PROPERTY,
1588    sym::SERVICE,
1589    sym::UI,
1590];
1591
1592/// Every name the compile-time environment defines.
1593///
1594/// A single list so that "is this a builtin" and "what does it do" cannot disagree — the `match`
1595/// in `Interp::builtin_inner` has an `unreachable!` arm that fires if a name is listed here and
1596/// not implemented, and `every_builtin_is_implemented` runs it.
1597pub const BUILTINS: &[&str] = &[
1598    "+",
1599    "-",
1600    "*",
1601    "/",
1602    "%",
1603    "==",
1604    "!=",
1605    "<",
1606    "<=",
1607    ">",
1608    ">=",
1609    "abs",
1610    "concat_lists",
1611    "filter_list",
1612    "float",
1613    "list_all",
1614    "list_any",
1615    "list_append",
1616    "list_contains",
1617    "list_drop",
1618    "list_flat_map",
1619    "list_fold",
1620    "list_is_empty",
1621    "list_len",
1622    "list_reverse",
1623    "list_slice",
1624    "list_take",
1625    "map_list",
1626    "negate",
1627    "node_args",
1628    "node_form",
1629    "node_head",
1630    "node_is_call",
1631    "node_is_lit",
1632    "node_lit",
1633    "node_str",
1634    "node_sym",
1635    "node_ty",
1636    "not",
1637    "refuse",
1638    "splice",
1639    "str",
1640    "str_chars",
1641    "str_contains",
1642    "str_ends_with",
1643    "str_is_empty",
1644    "str_join",
1645    "str_len",
1646    "str_lower",
1647    "str_repeat",
1648    "str_replace",
1649    "str_slice",
1650    "str_split",
1651    "str_starts_with",
1652    "str_to_int",
1653    "str_trim",
1654    "str_upper",
1655    "trunc",
1656];
1657
1658pub fn is_builtin(name: &str) -> bool {
1659    BUILTINS.contains(&name)
1660}
1661
1662/// What `str(x)` renders, which is also what `str_join` uses for a non-string element.
1663fn display(v: &Val) -> String {
1664    match v {
1665        Val::Unit => "unit".to_string(),
1666        Val::Int(n) => n.to_string(),
1667        Val::Float(f) => {
1668            if f.fract() == 0.0 && f.is_finite() {
1669                format!("{f:.1}")
1670            } else {
1671                f.to_string()
1672            }
1673        }
1674        Val::Str(s) => s.to_string(),
1675        Val::Bool(b) => b.to_string(),
1676        Val::Keyword(k) => format!(":{k}"),
1677        Val::List(xs) => {
1678            let parts: Vec<String> = xs.iter().map(display).collect();
1679            format!("[{}]", parts.join(", "))
1680        }
1681        Val::Record(fields) => {
1682            let parts: Vec<String> = fields
1683                .iter()
1684                .map(|(k, v)| format!("{k}: {}", display(v)))
1685                .collect();
1686            format!("{{{}}}", parts.join(", "))
1687        }
1688        Val::Syntax(n) => print::to_sexpr(n),
1689        Val::Fun(_) => "<function>".to_string(),
1690        Val::Type(t) => t.to_string(),
1691    }
1692}
1693
1694fn val_eq(a: &Val, b: &Val) -> bool {
1695    match (a, b) {
1696        (Val::Unit, Val::Unit) => true,
1697        (Val::Int(x), Val::Int(y)) => x == y,
1698        (Val::Float(x), Val::Float(y)) => x == y,
1699        (Val::Str(x), Val::Str(y)) => x == y,
1700        (Val::Bool(x), Val::Bool(y)) => x == y,
1701        (Val::Keyword(x), Val::Keyword(y)) => x == y,
1702        (Val::List(x), Val::List(y)) => {
1703            x.len() == y.len() && x.iter().zip(y.iter()).all(|(a, b)| val_eq(a, b))
1704        }
1705        (Val::Record(x), Val::Record(y)) => {
1706            x.len() == y.len()
1707                && x.iter()
1708                    .zip(y.iter())
1709                    .all(|((ka, va), (kb, vb))| ka == kb && val_eq(va, vb))
1710        }
1711        (Val::Syntax(x), Val::Syntax(y)) => x.structurally_eq(y),
1712        // Two types are equal when they are the same type, which is what a macro asking
1713        // `node_ty(a) == node_ty(b)` means.
1714        (Val::Type(x), Val::Type(y)) => x == y,
1715        _ => false,
1716    }
1717}
1718
1719/// The parameter list of a `def` or a `macro`, as names.
1720pub fn param_names(params: &Node) -> Vec<Arc<str>> {
1721    params
1722        .args
1723        .iter()
1724        .filter_map(|p| {
1725            let target = if p.is_form(sym::ANNOT) { &p.args[0] } else { p };
1726            target.as_var().map(|s: &Symbol| s.name.clone())
1727        })
1728        .collect()
1729}
1730
1731#[cfg(test)]
1732mod tests {
1733    use super::*;
1734
1735    #[test]
1736    fn every_builtin_is_implemented() {
1737        // The `unreachable!` arm in `builtin_inner` is only unreachable if the two lists agree,
1738        // and "agree" is what this asserts: every listed name is dispatched, with an arity error
1739        // rather than a panic when it is called with nothing.
1740        let defs = HashMap::new();
1741        for name in BUILTINS {
1742            let mut diags = Diagnostics::new();
1743            let mut interp = Interp::new(&defs, &mut diags, MAX_STEPS, false);
1744            let _ = interp.builtin(name, &[], Span::NONE);
1745        }
1746    }
1747
1748    /// The interpreter's ceiling fits the stack the front end declares.
1749    ///
1750    /// `beck-syntax` and `beck-core` each have one of these and
1751    /// [`beck_diag::depth::STACK_BYTES`] names them, because a count is only a *bound* if the
1752    /// stack it implies is a stack that exists. This crate recurses now too, and its frames are
1753    /// its own: an interpreter's `eval` carries a value and an environment where a parser's
1754    /// carries a token cursor.
1755    ///
1756    /// Measured at the ceiling rather than extrapolated from a per-level cost: a compile-time
1757    /// recursion with no base case is refused at exactly [`Nesting`]'s limit, so what it spends
1758    /// *is* the worst case, and no arithmetic stands between the measurement and the conclusion.
1759    #[test]
1760    fn the_interpreters_ceiling_fits_the_declared_stack() {
1761        // Measured on a stack far larger than the one whose adequacy is being concluded, so the
1762        // measurement is never the thing that overflows.
1763        let spent = std::thread::Builder::new()
1764            .stack_size(256 * 1024 * 1024)
1765            .spawn(|| {
1766                let src = "\
1767def down(n: Int) -> Int:
1768    return down(n + 1)
1769
1770macro deep(x):
1771    y = down(1)
1772    return quote:
1773        $x
1774
1775def f() -> Int:
1776    return deep(1)
1777";
1778                beck_diag::depth::probe::stack_spent(|| {
1779                    let mut map = beck_diag::SourceMap::new();
1780                    let file = map.add("probe.beck", src);
1781                    let mut diags = Diagnostics::new();
1782                    let parsed = beck_syntax::parser::parse_module(file, "probe", src, &mut diags);
1783                    let out = crate::expand_module(&parsed, &mut diags);
1784                    assert!(
1785                        diags.iter().any(|d| d.code == "B0216"),
1786                        "the probe must reach the ceiling for this to be measuring it"
1787                    );
1788                    out
1789                })
1790            })
1791            .expect("a thread")
1792            .join()
1793            .expect("the probe expands");
1794
1795        println!(
1796            "the macro interpreter spends {spent} bytes reaching its ceiling of {} levels",
1797            beck_diag::depth::MAX_NESTING
1798        );
1799        // Twice over, as the parser's and the evaluator's are: whoever drives expansion has as
1800        // much stack again above the ceiling as the ceiling itself needs.
1801        assert!(
1802            spent * 2 < beck_diag::depth::STACK_BYTES,
1803            "reaching the ceiling costs {spent} bytes, and {} with the margin, against a declared \
1804             STACK_BYTES of {} — raise the declaration or lower the ceiling",
1805            spent * 2,
1806            beck_diag::depth::STACK_BYTES
1807        );
1808    }
1809
1810    #[test]
1811    fn the_restricted_list_is_sorted_and_unique() {
1812        let names: Vec<&str> = RESTRICTED.iter().map(|(n, _)| *n).collect();
1813        let mut sorted = names.clone();
1814        sorted.sort_unstable();
1815        sorted.dedup();
1816        assert_eq!(names, sorted, "keep `RESTRICTED` sorted and duplicate-free");
1817        let mut builtins = BUILTINS.to_vec();
1818        builtins.sort_unstable();
1819        for (name, _) in RESTRICTED {
1820            assert!(
1821                !builtins.contains(name),
1822                "`{name}` is both restricted and a builtin"
1823            );
1824        }
1825    }
1826}