Skip to main content

tatara_lisp_eval/
eval.rs

1//! Core evaluator.
2//!
3//! Threads a mutable `Env` and the immutable `FnRegistry<H>` through
4//! recursive eval. Special forms are dispatched by head symbol before
5//! function application. Closures capture a snapshot of the current env
6//! at lambda creation; native functions live in the registry and are
7//! referred to in values by name.
8
9use std::sync::Arc;
10
11use tatara_lisp::{Atom, MacroDef, MacroParams, Span, Spanned, SpannedExpander, SpannedForm};
12
13use crate::code::{spanned_to_value, value_to_spanned};
14use crate::env::{Env, Seal};
15use crate::error::{EvalError, Result};
16use crate::ffi::{
17    Arity, Caller, FnEntry, FnImpl, FnRegistry, FromValue, HigherOrderCallable, IntoValue,
18    NativeCallable,
19};
20use crate::module::{Loader, Module, ModuleError, ModuleRegistry, NoLoader};
21use crate::special::SpecialForm;
22use crate::value::{Closure, ErrorObj, NativeFn, Value};
23
24/// An embedded tatara-lisp evaluator, parameterized over the host context
25/// `H` that registered functions read/write.
26/// Default macro rewrite-chain ceiling.
27///
28/// Chosen to be far above any hand-written macro chain and far below the
29/// ~4–5 k Rust frames where the old unbounded expander aborted, so the typed
30/// error is what an author sees rather than a SIGABRT. Raise it with
31/// [`Interpreter::set_macro_expansion_limit`] for a generator that genuinely
32/// chains further.
33pub const DEFAULT_MACRO_EXPANSION_LIMIT: usize = 256;
34
35/// Which of the three arbiters claims a head symbol. See
36/// [`Interpreter::resolve_head`] — they are consulted in this order, and the
37/// first match wins.
38#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
39pub enum HeadBinding {
40    /// A `match` arm in the evaluator. Appears in no environment, so an
41    /// environment lookup cannot see it.
42    SpecialForm,
43    /// Registered on the expander; rewrites the form before evaluation, which
44    /// is why a macro beats a same-named primitive.
45    Macro,
46    /// An ordinary binding — a native fn, a closure, or a value.
47    Value,
48}
49
50pub struct Interpreter<H> {
51    pub(crate) registry: FnRegistry<H>,
52    pub(crate) globals: Env,
53    /// Span-preserving macro expander. Top-level `defmacro`,
54    /// `defpoint-template`, and `defcheck` forms register here; macro calls
55    /// in subsequent forms are rewritten before evaluation. Persisted across
56    /// `eval_program` calls so REPL sessions accumulate macros naturally.
57    pub(crate) expander: SpannedExpander,
58    /// Module table — populated as `(require ...)` loads files. Shared
59    /// across all `Interpreter`s that share a registry (cloning an
60    /// `Interpreter` for sub-eval reuses the same registry).
61    pub(crate) modules: ModuleRegistry,
62    /// Source loader for `(require ...)`. Embedders inject filesystem
63    /// access here; the default `NoLoader` rejects every require.
64    pub(crate) loader: Arc<dyn Loader>,
65    /// Maximum macro REWRITE steps in one expansion chain.
66    ///
67    /// See `expand_at_depth`. Configurable because the honest ceiling is
68    /// domain-dependent — a code generator legitimately chains further than a
69    /// hand-written program — but never absent: an unbounded expander aborts
70    /// the process instead of failing the compilation.
71    pub(crate) macro_expansion_limit: usize,
72    /// Path of the module currently being evaluated. `(provide ...)`
73    /// adds names to whichever module owns this path. Top-level eval
74    /// (not inside any `(require)`) uses an empty path which means
75    /// "no current module" — `provide` errors there.
76    pub(crate) current_module: Option<Arc<str>>,
77}
78
79impl<H: 'static> Interpreter<H> {
80    pub fn new() -> Self {
81        Self {
82            registry: FnRegistry::new(),
83            globals: Env::new(),
84            expander: SpannedExpander::new(),
85            modules: ModuleRegistry::new(),
86            loader: Arc::new(NoLoader),
87            macro_expansion_limit: DEFAULT_MACRO_EXPANSION_LIMIT,
88            current_module: None,
89        }
90    }
91
92    /// Derive a child interpreter that **shares** this one's already-evaluated
93    /// globals instead of rebuilding them.
94    ///
95    /// Building an interpreter is dominated by *evaluating* the Lisp standard
96    /// library, not by registering the Rust primitives: measured at 945µs per
97    /// `install_full_stdlib_with`, which is essentially the entire cost of
98    /// running one short program. Any embedder that needs many isolated
99    /// interpreters — a test runner giving each test a clean slate, a
100    /// supervisor giving each process private state — pays that once per child
101    /// for a result that is identical every time.
102    ///
103    /// `fork` pays it once. The child gets:
104    ///
105    /// - **every global, readable.** Frames are `Arc`-shared, so the stdlib is
106    ///   not copied at all.
107    /// - **a private frame for its own `define`s.** They land above the shared
108    ///   frames, so the parent and every sibling are blind to them.
109    /// - **no write path to the shared frames.** They sit below the child's
110    ///   write floor, so a `set!` reaching for a parent binding raises
111    ///   `SetSealed` instead of mutating state another child can observe.
112    ///
113    /// That last property is why this is a fork and not a clone: `Env::set`
114    /// takes `&self` and writes *through* the `Arc`, so a plain clone would
115    /// share globals in the mutable direction too, and one child could edit
116    /// another's stdlib.
117    ///
118    /// # What is deliberately still shared
119    ///
120    /// The module registry, so a `(require ...)` resolved by one child is not
121    /// re-read by the next. Modules are immutable once loaded; this is a cache,
122    /// not shared program state.
123    ///
124    /// # What a fork is not
125    ///
126    /// It is not a snapshot. The shared frames stay live: a `define` the
127    /// *parent* runs after forking is visible to children already forked,
128    /// because they hold the same frame. Fork children, then stop defining in
129    /// the parent.
130    pub fn fork(&self) -> Self {
131        Self {
132            registry: self.registry.clone(),
133            globals: self.globals.sealed_below_top(Seal::Fork),
134            expander: self.expander.clone(),
135            modules: self.modules.clone(),
136            loader: Arc::clone(&self.loader),
137            macro_expansion_limit: self.macro_expansion_limit,
138            current_module: self.current_module.clone(),
139        }
140    }
141
142    /// Replace the source loader. Required for `(require ...)` to do
143    /// anything useful — the default `NoLoader` rejects every require.
144    pub fn set_loader(&mut self, loader: Arc<dyn Loader>) {
145        self.loader = loader;
146    }
147
148    /// Borrow the module registry. Useful for tests + inspection.
149    pub fn modules(&self) -> &ModuleRegistry {
150        &self.modules
151    }
152
153    /// Register a native Rust function, exposing it to Lisp code under
154    /// `name`. Re-registering the same name overwrites the prior entry
155    /// (last-write-wins) and leaves the global binding intact.
156    pub fn register_fn<F>(&mut self, name: impl Into<Arc<str>>, arity: Arity, callable: F)
157    where
158        F: NativeCallable<H>,
159    {
160        let name = name.into();
161        self.registry.insert(FnEntry {
162            name: name.clone(),
163            arity,
164            callable: FnImpl::Native(Arc::new(callable)),
165        });
166        self.globals.define(
167            name.clone(),
168            Value::NativeFn(Arc::new(NativeFn { name, arity })),
169        );
170    }
171
172    /// Register a higher-order Rust primitive — receives a `Caller` so it
173    /// can invoke `Value::Closure` / `Value::NativeFn` arguments back into
174    /// the eval loop. Used for `map`, `filter`, `fold`, `apply`,
175    /// `for-each`, etc. Same overwrite semantics as `register_fn`.
176    pub fn register_higher_order_fn<F>(
177        &mut self,
178        name: impl Into<Arc<str>>,
179        arity: Arity,
180        callable: F,
181    ) where
182        F: HigherOrderCallable<H>,
183    {
184        let name = name.into();
185        self.registry.insert(FnEntry {
186            name: name.clone(),
187            arity,
188            callable: FnImpl::Higher(Arc::new(callable)),
189        });
190        self.globals.define(
191            name.clone(),
192            Value::NativeFn(Arc::new(NativeFn { name, arity })),
193        );
194    }
195
196    /// Register a primitive that may have to **wait**.
197    ///
198    /// Two phases: `ready(&[Value], &H) -> bool` decides against an
199    /// immutable host, and `call(&[Value], &mut H, Span)` does the work only
200    /// once ready said yes. The immutable borrow in `ready` is the point —
201    /// see [`crate::ffi::AwaitableCallable`]: it makes consume-then-wait a
202    /// compile error rather than a documented hazard.
203    ///
204    /// Under `Vm::step`/`resume` a not-ready call parks the process. Under
205    /// `Vm::run`, which has no scheduler, it is `VmError::Deadlocked`.
206    ///
207    /// # Consume-then-wait does not typecheck
208    ///
209    /// This is the reason the form is split, so it is *asserted*, not
210    /// described. The body below is the natural shape of a selective
211    /// `receive` — take a message, find it does not match, wait — which is
212    /// precisely the message-losing bug a one-phase parking contract cannot
213    /// prevent. `ready` holds `&H`, so it is rejected at compile time:
214    ///
215    /// ```compile_fail
216    /// use tatara_lisp_eval::{ffi::Arity, Interpreter, Value};
217    /// #[derive(Default)]
218    /// struct Mail { queue: std::collections::VecDeque<i64> }
219    /// let mut interp: Interpreter<Mail> = Interpreter::new();
220    /// interp.register_awaitable_fn(
221    ///     "selective-take",
222    ///     Arity::Exact(0),
223    ///     // E0596: cannot borrow `mail.queue` as mutable.
224    ///     |_args: &[Value], mail: &Mail| mail.queue.pop_front().is_some(),
225    ///     |_args: &[Value], _mail: &mut Mail, _span| Ok(Value::Nil),
226    /// );
227    /// ```
228    ///
229    /// The permitted shape reads the queue and leaves it alone:
230    ///
231    /// ```
232    /// use tatara_lisp_eval::{ffi::Arity, Interpreter, Value};
233    /// #[derive(Default)]
234    /// struct Mail { queue: std::collections::VecDeque<i64> }
235    /// let mut interp: Interpreter<Mail> = Interpreter::new();
236    /// interp.register_awaitable_fn(
237    ///     "take",
238    ///     Arity::Exact(0),
239    ///     |_args: &[Value], mail: &Mail| !mail.queue.is_empty(),
240    ///     |_args: &[Value], mail: &mut Mail, _span| {
241    ///         Ok(Value::Int(mail.queue.pop_front().expect("ready said yes")))
242    ///     },
243    /// );
244    /// ```
245    pub fn register_awaitable_fn<R, C>(
246        &mut self,
247        name: impl Into<Arc<str>>,
248        arity: Arity,
249        ready: R,
250        call: C,
251    ) where
252        R: Fn(&[Value], &H) -> bool + Send + Sync + 'static,
253        C: Fn(&[Value], &mut H, Span) -> Result<Value> + Send + Sync + 'static,
254    {
255        let name = name.into();
256        self.registry.insert(FnEntry {
257            name: name.clone(),
258            arity,
259            callable: FnImpl::Awaitable(Arc::new(crate::ffi::Awaitable { ready, call })),
260        });
261        self.globals.define(
262            name.clone(),
263            Value::NativeFn(Arc::new(NativeFn { name, arity })),
264        );
265    }
266
267    /// Raise or lower the macro rewrite-chain ceiling.
268    ///
269    /// There is no way to remove it. An unbounded expander does not fail the
270    /// compilation, it aborts the process.
271    pub fn set_macro_expansion_limit(&mut self, limit: usize) {
272        self.macro_expansion_limit = limit;
273    }
274
275    /// Evaluate a single already-read spanned form in this interpreter's
276    /// global environment. Macro expansion runs first if any macros are
277    /// registered. Bare `eval_spanned` does NOT register top-level
278    /// `defmacro` — `eval_top_form` is the entry point for that.
279    pub fn eval_spanned(&mut self, form: &Spanned, host: &mut H) -> Result<Value> {
280        let expanded = self.fully_expand(form, host)?;
281        eval_in(
282            &mut self.globals,
283            &self.registry,
284            &self.expander,
285            &expanded,
286            host,
287        )
288    }
289
290    /// Evaluate a slice of forms in order, returning the last result.
291    ///
292    /// Top-level `defmacro` / `defpoint-template` / `defcheck` forms register
293    /// into the persistent expander and yield `Value::Nil`. All other forms
294    /// are fully expanded (recursively rewriting macro calls anywhere
295    /// in the form tree, with each macro body run through the live
296    /// evaluator at expansion time) before being evaluated. This is the
297    /// canonical entry point for running a tatara-lisp program — REPL,
298    /// embedded host, batch script.
299    ///
300    /// Empty input returns `Value::Nil`.
301    pub fn eval_program(&mut self, forms: &[Spanned], host: &mut H) -> Result<Value> {
302        let mut last = Value::Nil;
303        for form in forms {
304            last = self.eval_top_form(form, host)?;
305        }
306        Ok(last)
307    }
308
309    /// Evaluate one top-level form: register macros, handle module-
310    /// system forms (`provide` / `require`), expand, then eval.
311    /// Public so embedders that drive the read-eval loop themselves
312    /// (REPL, hot-reload watchers) can preserve top-level semantics
313    /// without re-implementing the registration handshake.
314    pub fn eval_top_form(&mut self, form: &Spanned, host: &mut H) -> Result<Value> {
315        if self.expander.try_register_macro(form)? {
316            return Ok(Value::Nil);
317        }
318        // Handle module-system forms BEFORE general expansion. They
319        // need `&mut self` access (loader, module registry, current
320        // module) which the generic eval dispatch can't carry.
321        if let Some(head) = head_symbol(form) {
322            match head {
323                "provide" => return self.eval_provide(form, host),
324                "require" => return self.eval_require(form, host),
325                _ => {}
326            }
327        }
328        let expanded = self.fully_expand(form, host)?;
329        eval_in(
330            &mut self.globals,
331            &self.registry,
332            &self.expander,
333            &expanded,
334            host,
335        )
336    }
337
338    /// Top-level `(provide name1 name2 ...)`. Adds each name to the
339    /// current module's export set. Errors if not currently inside a
340    /// module load (i.e. running at the embedder's top level).
341    fn eval_provide(&mut self, form: &Spanned, _host: &mut H) -> Result<Value> {
342        let items = form.as_list().unwrap_or(&[]);
343        let span = form.span;
344        let Some(current) = self.current_module.clone() else {
345            return Err(EvalError::bad_form(
346                "provide",
347                "`provide` only valid at module top level — embedder evaluating top-level code has no current module",
348                span,
349            ));
350        };
351        // Collect names to export.
352        let mut names: Vec<Arc<str>> = Vec::with_capacity(items.len().saturating_sub(1));
353        for item in &items[1..] {
354            let name = item.as_symbol().ok_or_else(|| {
355                EvalError::bad_form(
356                    "provide",
357                    "expected symbol — every arg must name a binding to export",
358                    item.span,
359                )
360            })?;
361            names.push(Arc::<str>::from(name));
362        }
363        // Append to the partially-loaded module's export set. The
364        // module is ALWAYS in the registry's "loading" stack at this
365        // point — loaded into the table on finish_load. We append
366        // exports via a dedicated registry method.
367        {
368            let mut g = self.modules.inner_lock();
369            // The currently-loading module's exports are tracked in a
370            // side staging map keyed by path; finalize_load merges
371            // the staging into the Module before promoting.
372            g.exports_staging
373                .entry(current.to_string())
374                .or_default()
375                .extend(names.iter().cloned());
376        }
377        Ok(Value::Nil)
378    }
379
380    /// Top-level `(require "path" ...)`. Loads the file via the
381    /// configured loader, evaluates its contents in a fresh module
382    /// context, then imports its exports into the calling env.
383    ///
384    /// Forms supported:
385    ///   (require "path")              ; alias = path; binds path/name
386    ///   (require "path" :as alias)    ; binds alias/name
387    ///   (require "path" :refer (...)) ; binds bare names; alias also bound
388    fn eval_require(&mut self, form: &Spanned, host: &mut H) -> Result<Value> {
389        let items = form.as_list().unwrap_or(&[]);
390        let span = form.span;
391        if items.len() < 2 {
392            return Err(EvalError::bad_form(
393                "require",
394                "expected (require \"path\" [:as alias] [:refer (...)])",
395                span,
396            ));
397        }
398        let path: Arc<str> = match items[1].as_string() {
399            Some(s) => Arc::from(s),
400            None => {
401                return Err(EvalError::bad_form(
402                    "require",
403                    "first arg must be a string path",
404                    items[1].span,
405                ))
406            }
407        };
408
409        // Parse optional :as alias / :refer (names) trailing kwargs.
410        let mut alias: Option<Arc<str>> = None;
411        let mut refer: Option<Vec<Arc<str>>> = None;
412        let mut i = 2usize;
413        while i < items.len() {
414            let kw = items[i].as_keyword().ok_or_else(|| {
415                EvalError::bad_form(
416                    "require",
417                    "expected keyword (:as / :refer) after path",
418                    items[i].span,
419                )
420            })?;
421            let val = items.get(i + 1).ok_or_else(|| {
422                EvalError::bad_form("require", "keyword without value", items[i].span)
423            })?;
424            match kw {
425                "as" => {
426                    alias = Some(Arc::from(val.as_symbol().ok_or_else(|| {
427                        EvalError::bad_form("require", ":as needs a symbol alias", val.span)
428                    })?));
429                }
430                "refer" => {
431                    let names_list = val.as_list().ok_or_else(|| {
432                        EvalError::bad_form(
433                            "require",
434                            ":refer needs a parenthesized list of symbols",
435                            val.span,
436                        )
437                    })?;
438                    let mut names = Vec::with_capacity(names_list.len());
439                    for n in names_list {
440                        names.push(Arc::<str>::from(n.as_symbol().ok_or_else(|| {
441                            EvalError::bad_form(
442                                "require",
443                                ":refer list must contain symbols only",
444                                n.span,
445                            )
446                        })?));
447                    }
448                    refer = Some(names);
449                }
450                other => {
451                    return Err(EvalError::bad_form(
452                        "require",
453                        format!("unknown require option :{other}"),
454                        items[i].span,
455                    ));
456                }
457            }
458            i += 2;
459        }
460
461        // Load + evaluate the module if it's not already cached.
462        if !self.modules.has(&path) {
463            self.load_module(&path, span, host)?;
464        }
465        let module = self.modules.get(&path).ok_or_else(|| {
466            EvalError::native_fn("require", "module disappeared after load", span)
467        })?;
468
469        // Import bindings into the calling env.
470        let chosen_alias = alias.unwrap_or_else(|| path.clone());
471        for name in &module.exports {
472            let value = module.bindings.get(name).cloned().unwrap_or(Value::Nil);
473            let qualified: Arc<str> = Arc::from(format!("{chosen_alias}/{name}"));
474            self.globals.define(qualified, value);
475        }
476        if let Some(names) = refer {
477            for name in names {
478                if let Some(value) = module.bindings.get(&name) {
479                    if module.exports.contains(&name) {
480                        self.globals.define(name.clone(), value.clone());
481                    } else {
482                        return Err(EvalError::User {
483                            value: error_value(
484                                "not-exported",
485                                &format!("{path} does not export {name}"),
486                            ),
487                            at: span,
488                        });
489                    }
490                } else {
491                    return Err(EvalError::User {
492                        value: error_value(
493                            "not-defined",
494                            &format!("{path} does not define {name}"),
495                        ),
496                        at: span,
497                    });
498                }
499            }
500        }
501        Ok(Value::Nil)
502    }
503
504    /// Drive the load of a single module: read source via loader,
505    /// register on the load stack (cycle detect), evaluate every form
506    /// against a fresh global env owned by THIS interpreter (so the
507    /// module sees the same primitives + macros), capture the bindings
508    /// that ended up in `globals` after eval, and finalize.
509    fn load_module(&mut self, path: &str, span: Span, host: &mut H) -> Result<()> {
510        // Cycle detect.
511        self.modules
512            .begin_load(path)
513            .map_err(|e| module_error_to_eval(e, span))?;
514
515        // Read source.
516        let source = match self.loader.load(path) {
517            Ok(s) => s,
518            Err(e) => {
519                self.modules.abort_load(path);
520                return Err(module_error_to_eval(e, span));
521            }
522        };
523
524        // Parse.
525        let forms = match tatara_lisp::read_spanned(&source) {
526            Ok(f) => f,
527            Err(e) => {
528                self.modules.abort_load(path);
529                return Err(EvalError::Reader(e));
530            }
531        };
532
533        // Save + swap module-context state. We isolate the module's
534        // bindings by snapshotting the globals env, evaluating into a
535        // FRESH env that inherits the host primitives, then restoring.
536        let saved_globals = std::mem::replace(&mut self.globals, Env::new());
537        // Re-install primitives into the fresh env: every NativeFn
538        // binding from the saved env is copied (the registry behind
539        // them is unchanged).
540        for (name, value) in saved_globals.iter_top_level() {
541            // Only carry NativeFn / Closure bindings forward — these
542            // are the primitive surface. The module's user-defined
543            // values get isolated.
544            if matches!(value, Value::NativeFn(_) | Value::Closure(_)) {
545                self.globals.define(name.clone(), value.clone());
546            }
547        }
548        let saved_current = self.current_module.replace(Arc::from(path));
549
550        // Evaluate every form. On error, restore + propagate.
551        let mut eval_err: Option<EvalError> = None;
552        for f in &forms {
553            // Re-enter eval_top_form so nested defmacro / require
554            // works recursively. (defmacro inside a module is fine;
555            // require chains are how libraries depend on each other.)
556            if let Err(e) = self.eval_top_form(f, host) {
557                eval_err = Some(e);
558                break;
559            }
560        }
561
562        // Snapshot module's bindings + exports BEFORE restoring globals.
563        let module_globals = std::mem::replace(&mut self.globals, saved_globals);
564        self.current_module = saved_current;
565
566        if let Some(e) = eval_err {
567            self.modules.abort_load(path);
568            return Err(e);
569        }
570
571        // Build the Module from the captured env's top-level bindings
572        // + the staged export set.
573        let mut module = Module::new(path);
574        for (name, value) in module_globals.iter_top_level() {
575            // Skip primitives that we re-inherited. We want only the
576            // module's OWN definitions.
577            if !matches!(value, Value::NativeFn(_)) {
578                module.define(name.clone(), value.clone());
579            }
580        }
581        // Apply staged exports.
582        let staged = {
583            let mut g = self.modules.inner_lock();
584            g.exports_staging.remove(path).unwrap_or_default()
585        };
586        for n in staged {
587            module.add_export(n);
588        }
589        self.modules.finish_load(module);
590        Ok(())
591    }
592
593    /// Fully expand a form: walk the tree; whenever the head of a list
594    /// is a registered macro, evaluate the macro body (a regular Lisp
595    /// program) at expansion time, convert the resulting Value back to
596    /// a Spanned tree, and recurse — the expansion may itself contain
597    /// further macro calls.
598    ///
599    /// This is the CL/Racket macro model: the macro body has full access
600    /// to every primitive and library function, can compute over its
601    /// argument source forms (which arrive as Lisp data structures —
602    /// lists of symbols, etc.), and produces code as data.
603    pub fn fully_expand(&mut self, form: &Spanned, host: &mut H) -> Result<Spanned> {
604        // Fast path: no macros registered — nothing to expand.
605        if self.expander.is_empty() {
606            return Ok(form.clone());
607        }
608        self.expand_recursive(form, host)
609    }
610
611    fn expand_recursive(&mut self, form: &Spanned, host: &mut H) -> Result<Spanned> {
612        self.expand_at_depth(form, host, 0)
613    }
614
615    /// Expand with a bounded number of *rewrite* steps.
616    ///
617    /// ## Why this is bounded, and why the bound counts rewrites
618    ///
619    /// Expansion used to recurse without a limit, so a macro whose expansion
620    /// mentions itself — `(defmacro forever (x) `(forever ,x))` — recursed
621    /// until the **Rust** stack gave out: `fatal runtime error: stack
622    /// overflow, aborting`. Uncatchable, and at *build* time, so a runaway
623    /// macro took the compiler down rather than failing the compilation.
624    ///
625    /// The counter increments on a macro REWRITE, not on structural descent.
626    /// Descending into a deeply-nested but finite form is legitimate work and
627    /// terminates on its own; a rewrite chain is the only part that can be
628    /// unbounded, because each rewrite can produce another macro call. Bounding
629    /// descent instead would reject large honest programs while still allowing
630    /// a two-macro cycle to run forever.
631    fn expand_at_depth(&mut self, form: &Spanned, host: &mut H, depth: usize) -> Result<Spanned> {
632        match &form.form {
633            SpannedForm::List(items) if !items.is_empty() => {
634                if let Some(head) = items[0].as_symbol() {
635                    if self.expander.has(head) {
636                        if depth >= self.macro_expansion_limit {
637                            // Naming the macro is the point. "expansion limit
638                            // exceeded" alone leaves the author searching;
639                            // the macro that was rewriting is the one to look
640                            // at.
641                            return Err(EvalError::MacroExpansionLimit {
642                                macro_name: head.into(),
643                                limit: self.macro_expansion_limit,
644                                at: form.span,
645                            });
646                        }
647                        // Macro call. Expand by running the body, then
648                        // recurse on the result (it may itself be a
649                        // macro call or contain nested macro calls).
650                        let expanded =
651                            self.expand_macro_call(head, &items[1..], form.span, host)?;
652                        return self.expand_at_depth(&expanded, host, depth + 1);
653                    }
654                }
655                // Not a macro call — recurse into children to catch
656                // nested macros. Structural descent does NOT charge the
657                // budget; see the doc comment.
658                let mut out = Vec::with_capacity(items.len());
659                for child in items {
660                    out.push(self.expand_at_depth(child, host, depth)?);
661                }
662                Ok(Spanned::new(form.span, SpannedForm::List(out)))
663            }
664            SpannedForm::Quote(_) => {
665                // Inside a `'expr`, expr is data — don't expand inside.
666                Ok(form.clone())
667            }
668            SpannedForm::Quasiquote(inner) => {
669                // Inside a `\`expr`, only unquoted subforms get expanded.
670                Ok(Spanned::new(
671                    form.span,
672                    SpannedForm::Quasiquote(Box::new(self.expand_inside_quasiquote(inner, host)?)),
673                ))
674            }
675            // Atoms, Nil, bare Unquote/UnquoteSplice — pass through.
676            _ => Ok(form.clone()),
677        }
678    }
679
680    fn expand_inside_quasiquote(&mut self, form: &Spanned, host: &mut H) -> Result<Spanned> {
681        match &form.form {
682            SpannedForm::Unquote(inner) => Ok(Spanned::new(
683                form.span,
684                SpannedForm::Unquote(Box::new(self.expand_recursive(inner, host)?)),
685            )),
686            SpannedForm::UnquoteSplice(inner) => Ok(Spanned::new(
687                form.span,
688                SpannedForm::UnquoteSplice(Box::new(self.expand_recursive(inner, host)?)),
689            )),
690            SpannedForm::List(items) => {
691                let mut out = Vec::with_capacity(items.len());
692                for item in items {
693                    out.push(self.expand_inside_quasiquote(item, host)?);
694                }
695                Ok(Spanned::new(form.span, SpannedForm::List(out)))
696            }
697            _ => Ok(form.clone()),
698        }
699    }
700
701    /// Expand a single macro call: bind macro params to lowered Value
702    /// representations of the source-form args, evaluate the body in
703    /// the live interpreter, and lift the result Value back to Spanned.
704    fn expand_macro_call(
705        &mut self,
706        macro_name: &str,
707        args: &[Spanned],
708        call_span: Span,
709        host: &mut H,
710    ) -> Result<Spanned> {
711        // Take a clone of the def — we'll use it without holding the
712        // expander borrow across an eval call.
713        let def: MacroDef = self
714            .expander
715            .get_macro(macro_name)
716            .cloned()
717            .ok_or_else(|| {
718                EvalError::native_fn(
719                    Arc::<str>::from(macro_name),
720                    "macro disappeared during expansion",
721                    call_span,
722                )
723            })?;
724
725        // Lift the body Sexp (which has no spans) to a Spanned tree
726        // stamped with the call site. Errors inside the body will
727        // appear at the macro call site — the right behavior for
728        // user-facing diagnostics.
729        let body_spanned = Spanned::from_sexp_at(&def.body, call_span);
730
731        // Expand any macros INSIDE the body before evaluation. This is
732        // what lets a macro use other macros (`dolist`, `when-let`,
733        // helper macros from stdlib) in its expansion logic. Without
734        // this pass, the body's eval would hit those forms as plain
735        // function calls and fail.
736        let body_expanded = self.fully_expand(&body_spanned, host)?;
737
738        // Build the macro-time environment: capture globals, push a
739        // frame for the macro params.
740        // SEALED: the macro body reads every global and stdlib function,
741        // and `define`s freely in its own frame — but a `set!` walking
742        // outward into the interpreter's globals is refused rather than
743        // silently mutating compile-time state. Without this, expansion is
744        // not deterministic and no expansion memo is sound.
745        let mut macro_env = self.globals.sealed_below_top(Seal::MacroExpansion);
746        bind_macro_args(&mut macro_env, &def.name, &def.params, args, call_span)?;
747
748        // Evaluate the body in the macro env using the live interpreter
749        // — every primitive, every library fn is in scope.
750        let result = eval_in(
751            &mut macro_env,
752            &self.registry,
753            &self.expander,
754            &body_expanded,
755            host,
756        )?;
757
758        // Convert the resulting Value back to a Spanned form. Anything
759        // that can't be lifted (closure, native fn, foreign) is a user
760        // error in the macro.
761        value_to_spanned(&result, call_span).map_err(|reason| {
762            EvalError::native_fn(
763                Arc::<str>::from(format!("macro {macro_name}")),
764                reason,
765                call_span,
766            )
767        })
768    }
769
770    /// Borrow the macro expander. Embedders may register macros directly
771    /// (e.g. preloaded standard library) without reading them from source.
772    pub fn expander(&self) -> &SpannedExpander {
773        &self.expander
774    }
775
776    /// Mutable access to the expander — for preloading macros via
777    /// `try_register_macro` from a separately-read form list, or clearing
778    /// the registry.
779    pub fn expander_mut(&mut self) -> &mut SpannedExpander {
780        &mut self.expander
781    }
782
783    /// Look up a symbol in the global env.
784    pub fn lookup_global(&self, name: &str) -> Option<Value> {
785        self.globals.lookup(name)
786    }
787
788    /// Bind a value in the global env.
789    pub fn define_global(&mut self, name: impl Into<Arc<str>>, value: Value) {
790        self.globals.define(name, value);
791    }
792
793    /// Borrow the globals env. Used by the VM to snapshot at closure
794    /// creation time.
795    pub fn globals_snapshot(&self) -> &Env {
796        &self.globals
797    }
798
799    /// What the head symbol of a form resolves to, **in the order the
800    /// evaluator actually consults**.
801    ///
802    /// Exposed because that order is not obvious and getting it wrong is
803    /// silent. There are three arbiters, not one:
804    ///
805    /// 1. a [`SpecialForm`] — a `match` arm, so it appears in no environment
806    ///    and `globals_snapshot().lookup()` cannot see it;
807    /// 2. a macro registered on the expander, which rewrites before evaluation;
808    /// 3. a binding in the environment.
809    ///
810    /// A consumer checking "is this name already taken" by looking only at the
811    /// environment misses the first two entirely. That is not hypothetical:
812    /// blue lowered `assert e` to a name the stdlib bound *as a macro*, a
813    /// macro beat the primitive, and every assertion in its test suite silently
814    /// passed. Its gate then still could not see special forms, so `not`,
815    /// `and`, `or`, `if` and `when` all read as unbound.
816    ///
817    /// Answering from a live interpreter rather than from a published list is
818    /// deliberate: a list is a second copy of the truth and goes stale on the
819    /// next installer that lands.
820    #[must_use]
821    pub fn resolve_head(&self, name: &str) -> Option<HeadBinding> {
822        if SpecialForm::from_symbol(name).is_some() {
823            Some(HeadBinding::SpecialForm)
824        } else if self.expander.has(name) {
825            Some(HeadBinding::Macro)
826        } else if self.globals.lookup(name).is_some() {
827            Some(HeadBinding::Value)
828        } else {
829            None
830        }
831    }
832
833    /// Every head symbol a form in this interpreter could resolve to.
834    ///
835    /// The union across all three arbiters, read off this interpreter rather
836    /// than declared, so it is exactly as current as the interpreter is. Build
837    /// one with [`crate::install_full_stdlib_with`] and this is the full
838    /// reserved surface.
839    #[must_use]
840    pub fn reserved_head_names(&self) -> std::collections::BTreeSet<Arc<str>> {
841        let mut out: std::collections::BTreeSet<Arc<str>> = SpecialForm::ALL
842            .iter()
843            .map(|sf| Arc::from(sf.symbol()))
844            .collect();
845        out.extend(
846            self.expander
847                .macro_names()
848                .map(Arc::from)
849                .collect::<Vec<Arc<str>>>(),
850        );
851        out.extend(self.globals.iter_top_level().into_iter().map(|(n, _)| n));
852        out
853    }
854
855    /// External entry point: apply a callable `Value` (closure or
856    /// native fn) with `args`. Wraps the internal `apply_external` so
857    /// the VM can dispatch to the tree-walker for non-VM callables.
858    pub fn apply_external_value(
859        &mut self,
860        callee: &Value,
861        args: Vec<Value>,
862        host: &mut H,
863        call_span: Span,
864    ) -> Result<Value> {
865        apply_external(
866            callee,
867            args,
868            call_span,
869            &self.registry,
870            &self.expander,
871            host,
872        )
873    }
874
875    /// The register-then-expand handshake, once.
876    ///
877    /// A top-level `(defmacro …)` is a *registration*, not a form to run:
878    /// `try_register_macro` stores it and the caller must not also compile it.
879    /// Everything else is fully expanded. Getting that pair wrong is silent —
880    /// forget the registration and every use of the macro compiles as a
881    /// function call to an unbound name; forget to skip the registered form and
882    /// the compiler sees a `defmacro` it has no opcode for.
883    ///
884    /// Spelling it out per call site is how it drifts, so the one spelling
885    /// lives here. `eval_program_vm` is the first caller.
886    ///
887    /// **Strict and interleaved**, matching runtime semantics: a macro must be
888    /// defined *before* the form that uses it, and the first expansion failure
889    /// aborts. A *static* analyser wants neither — it registers every macro in
890    /// the file up front and degrades to the unexpanded form on failure rather
891    /// than refusing to analyse the file. That is
892    /// [`crate::build_check::BuildExpander`], deliberately a separate policy.
893    pub fn expand_program(&mut self, forms: &[Spanned], host: &mut H) -> Result<Vec<Spanned>> {
894        let mut expanded: Vec<Spanned> = Vec::with_capacity(forms.len());
895        for form in forms {
896            if self.expander.try_register_macro(form)? {
897                continue;
898            }
899            expanded.push(self.fully_expand(form, host)?);
900        }
901        Ok(expanded)
902    }
903
904    /// Compile + execute a parsed program through the bytecode VM.
905    /// Top-level `defmacro` forms register into the persistent
906    /// expander (same as `eval_program`); every other form is
907    /// macro-expanded in place, then a fresh `Chunk` is compiled and
908    /// run. This is the opt-in fast path; `eval_program` remains the
909    /// authoritative tree-walker. Returns the value of the last form.
910    pub fn eval_program_vm(&mut self, forms: &[Spanned], host: &mut H) -> Result<Value> {
911        let expanded = self.expand_program(forms, host)?;
912        let chunk = crate::vm::compile_program(&expanded).map_err(|e| match e {
913            crate::vm::CompileError::Bad { at, message } => {
914                EvalError::bad_form(Arc::<str>::from("vm:compile"), message, at)
915            }
916        })?;
917        let mut vm = crate::vm::Vm::new();
918        vm.run(&chunk, self, host).map_err(|e| match e {
919            crate::vm::VmError::Eval(inner) => inner,
920            other => EvalError::native_fn(
921                Arc::<str>::from("vm"),
922                format!("{other}"),
923                Span::synthetic(),
924            ),
925        })
926    }
927
928    // ── Typed registration helpers ──────────────────────────────────
929
930    /// Register a 0-arity native fn with typed return value.
931    pub fn register_typed0<R, F>(&mut self, name: impl Into<Arc<str>>, f: F)
932    where
933        R: IntoValue + 'static,
934        F: Fn(&mut H) -> Result<R> + Send + Sync + 'static,
935    {
936        self.register_fn(
937            name,
938            Arity::Exact(0),
939            move |_args: &[Value], host: &mut H, _sp| f(host).map(IntoValue::into_value),
940        );
941    }
942
943    /// Register a 1-arity native fn with typed arg + return.
944    pub fn register_typed1<A, R, F>(&mut self, name: impl Into<Arc<str>>, f: F)
945    where
946        A: FromValue + 'static,
947        R: IntoValue + 'static,
948        F: Fn(&mut H, A) -> Result<R> + Send + Sync + 'static,
949    {
950        self.register_fn(
951            name,
952            Arity::Exact(1),
953            move |args: &[Value], host: &mut H, sp| {
954                let a = A::from_value(&args[0], sp)?;
955                f(host, a).map(IntoValue::into_value)
956            },
957        );
958    }
959
960    /// Register a 2-arity native fn with typed args + return.
961    pub fn register_typed2<A, B, R, F>(&mut self, name: impl Into<Arc<str>>, f: F)
962    where
963        A: FromValue + 'static,
964        B: FromValue + 'static,
965        R: IntoValue + 'static,
966        F: Fn(&mut H, A, B) -> Result<R> + Send + Sync + 'static,
967    {
968        self.register_fn(
969            name,
970            Arity::Exact(2),
971            move |args: &[Value], host: &mut H, sp| {
972                let a = A::from_value(&args[0], sp)?;
973                let b = B::from_value(&args[1], sp)?;
974                f(host, a, b).map(IntoValue::into_value)
975            },
976        );
977    }
978
979    /// Register a 3-arity native fn with typed args + return.
980    pub fn register_typed3<A, B, C, R, F>(&mut self, name: impl Into<Arc<str>>, f: F)
981    where
982        A: FromValue + 'static,
983        B: FromValue + 'static,
984        C: FromValue + 'static,
985        R: IntoValue + 'static,
986        F: Fn(&mut H, A, B, C) -> Result<R> + Send + Sync + 'static,
987    {
988        self.register_fn(
989            name,
990            Arity::Exact(3),
991            move |args: &[Value], host: &mut H, sp| {
992                let a = A::from_value(&args[0], sp)?;
993                let b = B::from_value(&args[1], sp)?;
994                let c = C::from_value(&args[2], sp)?;
995                f(host, a, b, c).map(IntoValue::into_value)
996            },
997        );
998    }
999
1000    /// Register a 4-arity native fn with typed args + return.
1001    pub fn register_typed4<A, B, C, D, R, F>(&mut self, name: impl Into<Arc<str>>, f: F)
1002    where
1003        A: FromValue + 'static,
1004        B: FromValue + 'static,
1005        C: FromValue + 'static,
1006        D: FromValue + 'static,
1007        R: IntoValue + 'static,
1008        F: Fn(&mut H, A, B, C, D) -> Result<R> + Send + Sync + 'static,
1009    {
1010        self.register_fn(
1011            name,
1012            Arity::Exact(4),
1013            move |args: &[Value], host: &mut H, sp| {
1014                let a = A::from_value(&args[0], sp)?;
1015                let b = B::from_value(&args[1], sp)?;
1016                let c = C::from_value(&args[2], sp)?;
1017                let d = D::from_value(&args[3], sp)?;
1018                f(host, a, b, c, d).map(IntoValue::into_value)
1019            },
1020        );
1021    }
1022}
1023
1024impl<H: 'static> Default for Interpreter<H> {
1025    fn default() -> Self {
1026        Self::new()
1027    }
1028}
1029
1030// ── Core recursive evaluator ──────────────────────────────────────────
1031
1032/// Evaluate `form` against `env`, resolving native fns via `registry`.
1033/// Mutates `env` for `define` / `set!` / body frame push+pop.
1034pub(crate) fn eval_in<H: 'static>(
1035    env: &mut Env,
1036    registry: &FnRegistry<H>,
1037    expander: &SpannedExpander,
1038    form: &Spanned,
1039    host: &mut H,
1040) -> Result<Value> {
1041    match &form.form {
1042        SpannedForm::Nil => Ok(Value::Nil),
1043        SpannedForm::Atom(a) => eval_atom(a, form.span, env),
1044        SpannedForm::Quote(inner) => Ok(quoted_value(inner)),
1045        SpannedForm::Quasiquote(inner) => quasiquote_eval(inner, env, registry, expander, host),
1046        SpannedForm::Unquote(_) | SpannedForm::UnquoteSplice(_) => Err(EvalError::bad_form(
1047            "unquote",
1048            "unquote outside of quasiquote",
1049            form.span,
1050        )),
1051        SpannedForm::List(items) => {
1052            if items.is_empty() {
1053                return Ok(Value::Nil);
1054            }
1055            // Head may be a special-form keyword, a symbol that resolves
1056            // to a callable, or an arbitrary expression that evaluates
1057            // to a callable.
1058            if let Some(head_sym) = items[0].as_symbol() {
1059                if let Some(sf) = SpecialForm::from_symbol(head_sym) {
1060                    return eval_special(sf, items, form.span, env, registry, expander, host);
1061                }
1062            }
1063            eval_application(items, form.span, env, registry, expander, host)
1064        }
1065    }
1066}
1067
1068fn eval_atom(a: &Atom, span: Span, env: &Env) -> Result<Value> {
1069    match a {
1070        Atom::Symbol(name) => env
1071            .lookup(name)
1072            .ok_or_else(|| EvalError::unbound(name.as_str(), span)),
1073        Atom::Keyword(s) => Ok(Value::Keyword(crate::interner::intern(s.as_str()))),
1074        Atom::Str(s) => Ok(Value::Str(Arc::from(s.as_str()))),
1075        Atom::Int(n) => Ok(Value::Int(*n)),
1076        Atom::Float(n) => Ok(Value::Float(*n)),
1077        Atom::Bool(b) => Ok(Value::Bool(*b)),
1078    }
1079}
1080
1081/// `'x` (Quote node from the reader) — yields the runtime value of x
1082/// without evaluation. Symbol → Value::Symbol; list → Value::List of
1083/// lowered children. Same semantics as the explicit `(quote x)`.
1084fn quoted_value(inner: &Spanned) -> Value {
1085    crate::code::spanned_to_value(inner)
1086}
1087
1088/// Evaluate a quasiquoted form — unlike `quote`, `,expr` inside the form
1089/// is evaluated and substituted, and `,@expr` splices the evaluated list
1090/// into the enclosing list. Atoms lower to their runtime `Value`
1091/// equivalents (Symbol → Value::Symbol, etc.). Nested quasiquote is not
1092/// supported in v1 — it is returned as an opaque `Value::Sexp` literal.
1093fn quasiquote_eval<H: 'static>(
1094    form: &Spanned,
1095    env: &mut Env,
1096    registry: &FnRegistry<H>,
1097    expander: &SpannedExpander,
1098    host: &mut H,
1099) -> Result<Value> {
1100    match &form.form {
1101        SpannedForm::Unquote(inner) => eval_in(env, registry, expander, inner, host),
1102        SpannedForm::UnquoteSplice(_) => Err(EvalError::bad_form(
1103            "unquote-splice",
1104            "`,@` only valid directly inside a list",
1105            form.span,
1106        )),
1107        SpannedForm::List(items) => {
1108            let mut out: Vec<Value> = Vec::with_capacity(items.len());
1109            for item in items {
1110                if let SpannedForm::UnquoteSplice(inner) = &item.form {
1111                    let v = eval_in(env, registry, expander, inner, host)?;
1112                    match v {
1113                        Value::List(xs) => out.extend(xs.iter().cloned()),
1114                        Value::Nil => {}
1115                        other => {
1116                            return Err(EvalError::type_mismatch(
1117                                "list",
1118                                other.type_name(),
1119                                item.span,
1120                            ))
1121                        }
1122                    }
1123                } else {
1124                    out.push(quasiquote_eval(item, env, registry, expander, host)?);
1125                }
1126            }
1127            if out.is_empty() {
1128                Ok(Value::Nil)
1129            } else {
1130                Ok(Value::list(out))
1131            }
1132        }
1133        SpannedForm::Nil => Ok(Value::Nil),
1134        SpannedForm::Atom(a) => Ok(match a {
1135            Atom::Symbol(s) => Value::Symbol(crate::interner::intern(s.as_str())),
1136            Atom::Keyword(s) => Value::Keyword(crate::interner::intern(s.as_str())),
1137            Atom::Str(s) => Value::Str(Arc::from(s.as_str())),
1138            Atom::Int(n) => Value::Int(*n),
1139            Atom::Float(n) => Value::Float(*n),
1140            Atom::Bool(b) => Value::Bool(*b),
1141        }),
1142        // Inside quasiquote, an inner `quote` is preserved structurally —
1143        // we treat it as an opaque literal subtree so downstream consumers
1144        // can see it as a source form if they care.
1145        SpannedForm::Quote(_) | SpannedForm::Quasiquote(_) => {
1146            Ok(Value::Sexp(form.to_sexp(), form.span))
1147        }
1148    }
1149}
1150
1151// ── Function application ──────────────────────────────────────────────
1152
1153fn eval_application<H: 'static>(
1154    items: &[Spanned],
1155    call_span: Span,
1156    env: &mut Env,
1157    registry: &FnRegistry<H>,
1158    expander: &SpannedExpander,
1159    host: &mut H,
1160) -> Result<Value> {
1161    let head_val = eval_in(env, registry, expander, &items[0], host)?;
1162    let mut args: Vec<Value> = Vec::with_capacity(items.len().saturating_sub(1));
1163    for arg_form in &items[1..] {
1164        args.push(eval_in(env, registry, expander, arg_form, host)?);
1165    }
1166    apply(&head_val, args, call_span, registry, expander, host)
1167}
1168
1169fn apply<H: 'static>(
1170    callee: &Value,
1171    args: Vec<Value>,
1172    call_span: Span,
1173    registry: &FnRegistry<H>,
1174    expander: &SpannedExpander,
1175    host: &mut H,
1176) -> Result<Value> {
1177    match callee {
1178        Value::NativeFn(nfn) => {
1179            if nfn.arity.check(args.len()).is_err() {
1180                return Err(EvalError::ArityMismatch {
1181                    fn_name: nfn.name.clone(),
1182                    expected: nfn.arity,
1183                    got: args.len(),
1184                    at: call_span,
1185                });
1186            }
1187            let entry = registry.lookup(&nfn.name).ok_or_else(|| {
1188                EvalError::native_fn(
1189                    nfn.name.clone(),
1190                    format!("native fn {} is not registered", nfn.name),
1191                    call_span,
1192                )
1193            })?;
1194            match &entry.callable {
1195                FnImpl::Native(f) => f.call(&args, host, call_span),
1196                FnImpl::Higher(f) => {
1197                    let caller = Caller { registry, expander };
1198                    f.call(&args, host, &caller, call_span)
1199                }
1200                // The readiness check happens HERE, with `host` reborrowed
1201                // immutably, which is what makes the no-consume guarantee
1202                // structural: `f.ready` cannot touch the host mutably even
1203                // if its author wanted to.
1204                FnImpl::Awaitable(f) => {
1205                    if f.ready(&args, host) {
1206                        f.call(&args, host, call_span)
1207                    } else {
1208                        Ok(crate::vm::Vm::park())
1209                    }
1210                }
1211            }
1212        }
1213        Value::Closure(c) => call_closure(c.clone(), args, call_span, registry, expander, host),
1214        // VM-compiled closure flowing into a tree-walker apply path
1215        // (typically because a native HoF captured the closure as an
1216        // arg). Lift to a tree-walker-shaped Closure and dispatch.
1217        // See `CompiledClosure::lift_to_closure` for trade-offs.
1218        Value::Foreign(any) => {
1219            if let Some(cc) = any
1220                .clone()
1221                .downcast::<crate::vm::run::CompiledClosure>()
1222                .ok()
1223            {
1224                let lifted = cc.lift_to_closure();
1225                return call_closure(lifted, args, call_span, registry, expander, host);
1226            }
1227            Err(EvalError::NotCallable {
1228                value_kind: callee.type_name(),
1229                at: call_span,
1230            })
1231        }
1232        other => Err(EvalError::NotCallable {
1233            value_kind: other.type_name(),
1234            at: call_span,
1235        }),
1236    }
1237}
1238
1239// ── Tail-call optimization ────────────────────────────────────────
1240//
1241// Tatara-lisp guarantees TCO in the sense Scheme R7RS requires: a
1242// procedure call in tail position never grows the stack. This is
1243// implemented as a trampoline driven from `call_closure`.
1244//
1245// "Tail position" is the structural notion: the form whose value
1246// becomes the value of the surrounding form. The tail positions
1247// supported here:
1248//
1249//   * `if` — both branches
1250//   * `cond` / `when` / `unless` — last form of the matching body
1251//   * `begin` / `let` / `let*` / `letrec` — last form of the body
1252//   * `and` / `or` — last form when prior forms didn't short-circuit
1253//   * Lambda body — last form
1254//
1255// `eval_in_tail` mirrors `eval_in` but, for closure-application forms
1256// in tail position, returns `TailResult::Resume(closure, args)` rather
1257// than calling `apply`. The outer trampoline in `call_closure` then
1258// rebinds and loops without consuming a stack frame.
1259
1260/// Result of tail-position evaluation.
1261enum TailResult {
1262    /// Evaluation completed; here is the value.
1263    Done(Value),
1264    /// A tail call to a closure that the trampoline should re-enter
1265    /// rather than recursing into. Carries the closure to invoke,
1266    /// the already-evaluated arguments, and the call site span for
1267    /// arity-error attribution.
1268    Resume(Arc<Closure>, Vec<Value>, Span),
1269}
1270
1271/// Tail-position evaluation. Same semantics as `eval_in` for forms
1272/// that don't yield a closure tail call, but defers closure tail calls
1273/// to the trampoline.
1274fn eval_in_tail<H: 'static>(
1275    env: &mut Env,
1276    registry: &FnRegistry<H>,
1277    expander: &SpannedExpander,
1278    form: &Spanned,
1279    host: &mut H,
1280) -> Result<TailResult> {
1281    match &form.form {
1282        SpannedForm::List(items) if !items.is_empty() => {
1283            // Special-form check first.
1284            if let Some(head_sym) = items[0].as_symbol() {
1285                if let Some(sf) = SpecialForm::from_symbol(head_sym) {
1286                    return eval_special_tail(sf, items, form.span, env, registry, expander, host);
1287                }
1288            }
1289            // Function application: evaluate head + args, then either
1290            // resume (closure) or apply (everything else).
1291            let head_val = eval_in(env, registry, expander, &items[0], host)?;
1292            let mut args: Vec<Value> = Vec::with_capacity(items.len().saturating_sub(1));
1293            for arg_form in &items[1..] {
1294                args.push(eval_in(env, registry, expander, arg_form, host)?);
1295            }
1296            match head_val {
1297                Value::Closure(c) => Ok(TailResult::Resume(c, args, form.span)),
1298                _ => apply(&head_val, args, form.span, registry, expander, host)
1299                    .map(TailResult::Done),
1300            }
1301        }
1302        // Atoms, Quote, Nil — no tail context to exploit; just compute.
1303        _ => eval_in(env, registry, expander, form, host).map(TailResult::Done),
1304    }
1305}
1306
1307fn eval_special_tail<H: 'static>(
1308    sf: SpecialForm,
1309    items: &[Spanned],
1310    call_span: Span,
1311    env: &mut Env,
1312    registry: &FnRegistry<H>,
1313    expander: &SpannedExpander,
1314    host: &mut H,
1315) -> Result<TailResult> {
1316    match sf {
1317        SpecialForm::If => {
1318            if items.len() < 3 || items.len() > 4 {
1319                return eval_special(sf, items, call_span, env, registry, expander, host)
1320                    .map(TailResult::Done);
1321            }
1322            let c = eval_in(env, registry, expander, &items[1], host)?;
1323            if c.is_truthy() {
1324                eval_in_tail(env, registry, expander, &items[2], host)
1325            } else if items.len() == 4 {
1326                eval_in_tail(env, registry, expander, &items[3], host)
1327            } else {
1328                Ok(TailResult::Done(Value::Nil))
1329            }
1330        }
1331        SpecialForm::Begin => {
1332            let body = &items[1..];
1333            if body.is_empty() {
1334                return Ok(TailResult::Done(Value::Nil));
1335            }
1336            for form in &body[..body.len() - 1] {
1337                eval_in(env, registry, expander, form, host)?;
1338            }
1339            eval_in_tail(env, registry, expander, body.last().unwrap(), host)
1340        }
1341        SpecialForm::When | SpecialForm::Unless => {
1342            if items.len() < 2 {
1343                return eval_special(sf, items, call_span, env, registry, expander, host)
1344                    .map(TailResult::Done);
1345            }
1346            let invert = matches!(sf, SpecialForm::Unless);
1347            let cond = eval_in(env, registry, expander, &items[1], host)?;
1348            let run = cond.is_truthy() ^ invert;
1349            if !run {
1350                return Ok(TailResult::Done(Value::Nil));
1351            }
1352            let body = &items[2..];
1353            if body.is_empty() {
1354                return Ok(TailResult::Done(Value::Nil));
1355            }
1356            for form in &body[..body.len() - 1] {
1357                eval_in(env, registry, expander, form, host)?;
1358            }
1359            eval_in_tail(env, registry, expander, body.last().unwrap(), host)
1360        }
1361        SpecialForm::Cond => {
1362            for clause in &items[1..] {
1363                let Some(clause_list) = clause.as_list() else {
1364                    return eval_special(sf, items, call_span, env, registry, expander, host)
1365                        .map(TailResult::Done);
1366                };
1367                if clause_list.is_empty() {
1368                    return eval_special(sf, items, call_span, env, registry, expander, host)
1369                        .map(TailResult::Done);
1370                }
1371                let is_else = clause_list[0].as_symbol() == Some("else");
1372                let cond_matches = if is_else {
1373                    true
1374                } else {
1375                    eval_in(env, registry, expander, &clause_list[0], host)?.is_truthy()
1376                };
1377                if cond_matches {
1378                    let body = &clause_list[1..];
1379                    if body.is_empty() {
1380                        return Ok(TailResult::Done(Value::Nil));
1381                    }
1382                    for form in &body[..body.len() - 1] {
1383                        eval_in(env, registry, expander, form, host)?;
1384                    }
1385                    return eval_in_tail(env, registry, expander, body.last().unwrap(), host);
1386                }
1387            }
1388            Ok(TailResult::Done(Value::Nil))
1389        }
1390        SpecialForm::Let | SpecialForm::LetStar | SpecialForm::LetRec => {
1391            eval_let_family_tail(sf, items, call_span, env, registry, expander, host)
1392        }
1393        SpecialForm::And => {
1394            let exprs = &items[1..];
1395            if exprs.is_empty() {
1396                return Ok(TailResult::Done(Value::Bool(true)));
1397            }
1398            // All but last: short-circuit.
1399            for e in &exprs[..exprs.len() - 1] {
1400                let v = eval_in(env, registry, expander, e, host)?;
1401                if !v.is_truthy() {
1402                    return Ok(TailResult::Done(v));
1403                }
1404            }
1405            // Last in tail position.
1406            eval_in_tail(env, registry, expander, exprs.last().unwrap(), host)
1407        }
1408        SpecialForm::Or => {
1409            let exprs = &items[1..];
1410            if exprs.is_empty() {
1411                return Ok(TailResult::Done(Value::Bool(false)));
1412            }
1413            for e in &exprs[..exprs.len() - 1] {
1414                let v = eval_in(env, registry, expander, e, host)?;
1415                if v.is_truthy() {
1416                    return Ok(TailResult::Done(v));
1417                }
1418            }
1419            eval_in_tail(env, registry, expander, exprs.last().unwrap(), host)
1420        }
1421        SpecialForm::Try => {
1422            // try/catch is delicate to TCO — preserving the catch
1423            // handler context across a tail call would require unwinding
1424            // through Resume. Punt: always run try in non-tail position.
1425            // Tail position inside the catch handler is fine; the body
1426            // simply doesn't trampoline a tail call past the try frame.
1427            sf_try(items, call_span, env, registry, expander, host).map(TailResult::Done)
1428        }
1429        SpecialForm::MacroexpandOne => {
1430            sf_macroexpand(items, call_span, env, registry, expander, host, false)
1431                .map(TailResult::Done)
1432        }
1433        SpecialForm::MacroexpandAll => {
1434            sf_macroexpand(items, call_span, env, registry, expander, host, true)
1435                .map(TailResult::Done)
1436        }
1437        SpecialForm::Delay => sf_delay(items, call_span, env).map(TailResult::Done),
1438        SpecialForm::Eval => {
1439            sf_eval(items, call_span, env, registry, expander, host).map(TailResult::Done)
1440        }
1441        // Non-tail forms: just evaluate normally.
1442        _ => {
1443            eval_special(sf, items, call_span, env, registry, expander, host).map(TailResult::Done)
1444        }
1445    }
1446}
1447
1448/// Tail-aware evaluator for `let` / `let*` / `letrec`. Mirrors the
1449/// non-tail versions in `sf_let` / `sf_let_star` / `sf_letrec` but uses
1450/// `eval_in_tail` for the body's last form.
1451fn eval_let_family_tail<H: 'static>(
1452    sf: SpecialForm,
1453    items: &[Spanned],
1454    call_span: Span,
1455    env: &mut Env,
1456    registry: &FnRegistry<H>,
1457    expander: &SpannedExpander,
1458    host: &mut H,
1459) -> Result<TailResult> {
1460    if items.len() < 3 {
1461        return Err(EvalError::bad_form(
1462            match sf {
1463                SpecialForm::Let => "let",
1464                SpecialForm::LetStar => "let*",
1465                SpecialForm::LetRec => "letrec",
1466                _ => "let-family",
1467            },
1468            "expected ((name expr)...) body...",
1469            call_span,
1470        ));
1471    }
1472    let bindings = parse_binding_list(
1473        &items[1],
1474        match sf {
1475            SpecialForm::Let => "let",
1476            SpecialForm::LetStar => "let*",
1477            SpecialForm::LetRec => "letrec",
1478            _ => "let-family",
1479        },
1480    )?;
1481
1482    match sf {
1483        SpecialForm::Let => {
1484            let mut values = Vec::with_capacity(bindings.len());
1485            for (_, expr) in &bindings {
1486                values.push(eval_in(env, registry, expander, expr, host)?);
1487            }
1488            env.push();
1489            for ((name, _), val) in bindings.into_iter().zip(values) {
1490                env.define(name, val);
1491            }
1492        }
1493        SpecialForm::LetStar => {
1494            env.push();
1495            for (name, expr) in bindings {
1496                let v = eval_in(env, registry, expander, expr, host)?;
1497                env.define(name, v);
1498            }
1499        }
1500        SpecialForm::LetRec => {
1501            env.push();
1502            for (name, _) in &bindings {
1503                env.define(name.clone(), Value::Nil);
1504            }
1505            for (name, expr) in &bindings {
1506                let v = eval_in(env, registry, expander, expr, host)?;
1507                env.define(name.clone(), v);
1508            }
1509        }
1510        _ => unreachable!(),
1511    }
1512
1513    let body = &items[2..];
1514    let result = if body.is_empty() {
1515        Ok(TailResult::Done(Value::Nil))
1516    } else {
1517        for form in &body[..body.len() - 1] {
1518            if let Err(e) = eval_in(env, registry, expander, form, host) {
1519                env.pop();
1520                return Err(e);
1521            }
1522        }
1523        eval_in_tail(env, registry, expander, body.last().unwrap(), host)
1524    };
1525    env.pop();
1526    result
1527}
1528
1529/// External entry point for `Caller::apply_value` — the higher-order
1530/// primitive needs to invoke a callable Value back into the eval loop.
1531/// This is the same `apply` function above; it is exposed `pub(crate)`
1532/// at function visibility so the FFI module can reach it without
1533/// publishing the rest of the eval internals.
1534pub(crate) fn apply_external<H: 'static>(
1535    callee: &Value,
1536    args: Vec<Value>,
1537    call_span: Span,
1538    registry: &FnRegistry<H>,
1539    expander: &SpannedExpander,
1540    host: &mut H,
1541) -> Result<Value> {
1542    apply(callee, args, call_span, registry, expander, host)
1543}
1544
1545/// Bind macro parameters onto the macro-time env.
1546///
1547/// The positional binding itself is NOT restated here: it runs the one
1548/// shared `MacroParams::bind_carrier` over the `Spanned` carrier — the same
1549/// loop the plain and span-preserving expanders use — and this function only
1550/// lowers the resulting per-index values Spanned→Value and defines them.
1551/// Before that lift this was a third copy of the loop, and the only one of
1552/// the three that knew nothing about `&optional`.
1553fn bind_macro_args(
1554    env: &mut Env,
1555    macro_name: &str,
1556    params: &MacroParams,
1557    args: &[Spanned],
1558    call_span: Span,
1559) -> Result<()> {
1560    let bound = params
1561        .bind_carrier(macro_name, args, call_span)
1562        .map_err(|e| {
1563            EvalError::native_fn(
1564                Arc::<str>::from(format!("macro {macro_name}")),
1565                e.to_string(),
1566                call_span,
1567            )
1568        })?;
1569    for (name, value) in params.names().into_iter().zip(bound.iter()) {
1570        env.define(Arc::<str>::from(name), spanned_to_value(value));
1571    }
1572    Ok(())
1573}
1574
1575/// Apply a closure to arguments. Implements TCO: if the body's last
1576/// form is a tail call to another closure, the trampoline reuses the
1577/// stack frame instead of recursing. Self-recursion and mutual
1578/// recursion both bottom out into a loop.
1579fn call_closure<H: 'static>(
1580    closure: Arc<Closure>,
1581    args: Vec<Value>,
1582    call_span: Span,
1583    registry: &FnRegistry<H>,
1584    expander: &SpannedExpander,
1585    host: &mut H,
1586) -> Result<Value> {
1587    let mut current = closure;
1588    let mut current_args = args;
1589    let mut current_span = call_span;
1590    loop {
1591        // Arity check.
1592        let required = current.params.len();
1593        let has_rest = current.rest.is_some();
1594        if !has_rest && current_args.len() != required {
1595            return Err(EvalError::ArityMismatch {
1596                fn_name: Arc::from("<closure>"),
1597                expected: Arity::Exact(required),
1598                got: current_args.len(),
1599                at: current_span,
1600            });
1601        }
1602        if has_rest && current_args.len() < required {
1603            return Err(EvalError::ArityMismatch {
1604                fn_name: Arc::from("<closure>"),
1605                expected: Arity::AtLeast(required),
1606                got: current_args.len(),
1607                at: current_span,
1608            });
1609        }
1610
1611        // Build the body env: capture closure's lexical scope, push frame,
1612        // bind params + rest.
1613        let mut env = current.captured_env.clone();
1614        env.push();
1615        for (param, arg) in current.params.iter().zip(current_args.iter()) {
1616            env.define(param.clone(), arg.clone());
1617        }
1618        if let Some(rest_name) = &current.rest {
1619            let rest_args: Vec<Value> = current_args.iter().skip(required).cloned().collect();
1620            env.define(rest_name.clone(), Value::list(rest_args));
1621        }
1622
1623        // Body: evaluate all but the last normally, then the last in
1624        // tail position so a tail call can be trampolined.
1625        let body = &current.body;
1626        if body.is_empty() {
1627            return Ok(Value::Nil);
1628        }
1629        for body_form in &body[..body.len() - 1] {
1630            eval_in(&mut env, registry, expander, body_form, host)?;
1631        }
1632        match eval_in_tail(&mut env, registry, expander, body.last().unwrap(), host)? {
1633            TailResult::Done(v) => return Ok(v),
1634            TailResult::Resume(next, next_args, next_span) => {
1635                // Tail call: replace state and loop. Drop env (frame
1636                // popped on next iteration's fresh env).
1637                current = next;
1638                current_args = next_args;
1639                current_span = next_span;
1640            }
1641        }
1642    }
1643}
1644
1645// ── Special forms ─────────────────────────────────────────────────────
1646
1647fn eval_special<H: 'static>(
1648    sf: SpecialForm,
1649    items: &[Spanned],
1650    call_span: Span,
1651    env: &mut Env,
1652    registry: &FnRegistry<H>,
1653    expander: &SpannedExpander,
1654    host: &mut H,
1655) -> Result<Value> {
1656    match sf {
1657        SpecialForm::Quote => sf_quote(items, call_span),
1658        SpecialForm::Quasiquote => {
1659            if items.len() != 2 {
1660                return Err(EvalError::bad_form(
1661                    "quasiquote",
1662                    format!("expected 1 arg, got {}", items.len() - 1),
1663                    call_span,
1664                ));
1665            }
1666            quasiquote_eval(&items[1], env, registry, expander, host)
1667        }
1668        SpecialForm::If => sf_if(items, call_span, env, registry, expander, host),
1669        SpecialForm::Cond => sf_cond(items, call_span, env, registry, expander, host),
1670        SpecialForm::When => sf_when_unless(items, call_span, env, registry, expander, host, false),
1671        SpecialForm::Unless => {
1672            sf_when_unless(items, call_span, env, registry, expander, host, true)
1673        }
1674        SpecialForm::Let => sf_let(items, call_span, env, registry, expander, host),
1675        SpecialForm::LetStar => sf_let_star(items, call_span, env, registry, expander, host),
1676        SpecialForm::LetRec => sf_letrec(items, call_span, env, registry, expander, host),
1677        SpecialForm::Lambda => sf_lambda(items, call_span, env),
1678        SpecialForm::Define => sf_define(items, call_span, env, registry, expander, host),
1679        SpecialForm::Set => sf_set(items, call_span, env, registry, expander, host),
1680        SpecialForm::Begin => sf_begin(&items[1..], env, registry, expander, host),
1681        SpecialForm::And => sf_and(&items[1..], env, registry, expander, host),
1682        SpecialForm::Or => sf_or(&items[1..], env, registry, expander, host),
1683        SpecialForm::Not => sf_not(items, call_span, env, registry, expander, host),
1684        SpecialForm::Try => sf_try(items, call_span, env, registry, expander, host),
1685        SpecialForm::MacroexpandOne => {
1686            sf_macroexpand(items, call_span, env, registry, expander, host, false)
1687        }
1688        SpecialForm::MacroexpandAll => {
1689            sf_macroexpand(items, call_span, env, registry, expander, host, true)
1690        }
1691        SpecialForm::Delay => sf_delay(items, call_span, env),
1692        SpecialForm::Eval => sf_eval(items, call_span, env, registry, expander, host),
1693        SpecialForm::Provide | SpecialForm::Require => Err(EvalError::bad_form(
1694            if matches!(sf, SpecialForm::Provide) { "provide" } else { "require" },
1695            "module-system forms are only valid at top level — wrap your call in (eval (quote ...)) if you really need it dynamic",
1696            call_span,
1697        )),
1698    }
1699}
1700
1701/// Extract the head-symbol of a list form, or `None` if `form` isn't a
1702/// list whose head is a symbol. Used by the top-level dispatcher to
1703/// recognize module-system forms before macroexpansion.
1704fn head_symbol(form: &Spanned) -> Option<&str> {
1705    let SpannedForm::List(items) = &form.form else {
1706        return None;
1707    };
1708    items.first().and_then(Spanned::as_symbol)
1709}
1710
1711/// Build a `Value::Error` with the given tag + message.
1712fn error_value(tag: &str, message: &str) -> Value {
1713    Value::Error(Arc::new(ErrorObj {
1714        tag: Arc::from(tag),
1715        message: Arc::from(message),
1716        data: Vec::new(),
1717    }))
1718}
1719
1720/// Convert a `ModuleError` to the `EvalError::User` carrying a
1721/// `Value::Error`. This way module-system failures can be `(catch ...)`-ed
1722/// like any other thrown error.
1723fn module_error_to_eval(e: ModuleError, span: Span) -> EvalError {
1724    let (tag, message) = match &e {
1725        ModuleError::NotFound(_) => ("module-not-found", e.to_string()),
1726        ModuleError::Circular { .. } => ("circular-require", e.to_string()),
1727        ModuleError::NotExported(_, _) => ("not-exported", e.to_string()),
1728        // Its own tag, not `module-not-found`: a `(catch ...)` handler must be
1729        // able to tell "this program asked for something that isn't there"
1730        // from "this program asked for something the gate refuses to give it".
1731        ModuleError::Denied { .. } => ("module-denied", e.to_string()),
1732    };
1733    EvalError::User {
1734        value: error_value(tag, &message),
1735        at: span,
1736    }
1737}
1738
1739fn sf_quote(items: &[Spanned], span: Span) -> Result<Value> {
1740    if items.len() != 2 {
1741        return Err(EvalError::bad_form(
1742            "quote",
1743            format!("expected 1 arg, got {}", items.len() - 1),
1744            span,
1745        ));
1746    }
1747    // Scheme / Clojure semantics: (quote x) returns the runtime
1748    // structural value of x. A bare symbol becomes Value::Symbol; a
1749    // list becomes Value::List of recursively-lowered items; etc.
1750    // This is what makes (car '(a b c)) return the symbol `a` —
1751    // exactly what users expect from a Lisp.
1752    Ok(crate::code::spanned_to_value(&items[1]))
1753}
1754
1755fn sf_if<H: 'static>(
1756    items: &[Spanned],
1757    span: Span,
1758    env: &mut Env,
1759    registry: &FnRegistry<H>,
1760    expander: &SpannedExpander,
1761    host: &mut H,
1762) -> Result<Value> {
1763    if items.len() < 3 || items.len() > 4 {
1764        return Err(EvalError::bad_form(
1765            "if",
1766            format!("expected (if c t [e]), got {} subforms", items.len()),
1767            span,
1768        ));
1769    }
1770    let c = eval_in(env, registry, expander, &items[1], host)?;
1771    if c.is_truthy() {
1772        eval_in(env, registry, expander, &items[2], host)
1773    } else if items.len() == 4 {
1774        eval_in(env, registry, expander, &items[3], host)
1775    } else {
1776        Ok(Value::Nil)
1777    }
1778}
1779
1780fn sf_cond<H: 'static>(
1781    items: &[Spanned],
1782    span: Span,
1783    env: &mut Env,
1784    registry: &FnRegistry<H>,
1785    expander: &SpannedExpander,
1786    host: &mut H,
1787) -> Result<Value> {
1788    for clause in &items[1..] {
1789        let Some(clause_list) = clause.as_list() else {
1790            return Err(EvalError::bad_form(
1791                "cond",
1792                "clause must be a list",
1793                clause.span,
1794            ));
1795        };
1796        if clause_list.is_empty() {
1797            return Err(EvalError::bad_form("cond", "empty clause", clause.span));
1798        }
1799        let is_else = clause_list[0].as_symbol() == Some("else");
1800        let cond_matches = if is_else {
1801            true
1802        } else {
1803            let v = eval_in(env, registry, expander, &clause_list[0], host)?;
1804            v.is_truthy()
1805        };
1806        if cond_matches {
1807            let mut last = Value::Nil;
1808            for expr in &clause_list[1..] {
1809                last = eval_in(env, registry, expander, expr, host)?;
1810            }
1811            return Ok(last);
1812        }
1813    }
1814    // No clause matched.
1815    let _ = span;
1816    Ok(Value::Nil)
1817}
1818
1819fn sf_when_unless<H: 'static>(
1820    items: &[Spanned],
1821    span: Span,
1822    env: &mut Env,
1823    registry: &FnRegistry<H>,
1824    expander: &SpannedExpander,
1825    host: &mut H,
1826    invert: bool,
1827) -> Result<Value> {
1828    if items.len() < 2 {
1829        return Err(EvalError::bad_form(
1830            if invert { "unless" } else { "when" },
1831            "need a test",
1832            span,
1833        ));
1834    }
1835    let cond = eval_in(env, registry, expander, &items[1], host)?;
1836    let run = cond.is_truthy() ^ invert;
1837    if run {
1838        let mut last = Value::Nil;
1839        for expr in &items[2..] {
1840            last = eval_in(env, registry, expander, expr, host)?;
1841        }
1842        Ok(last)
1843    } else {
1844        Ok(Value::Nil)
1845    }
1846}
1847
1848/// Parse a `((name expr) ...)` binding list into `[(name, &expr_spanned)]`.
1849fn parse_binding_list<'a>(
1850    list: &'a Spanned,
1851    form_name: &'static str,
1852) -> Result<Vec<(Arc<str>, &'a Spanned)>> {
1853    let bindings = list
1854        .as_list()
1855        .ok_or_else(|| EvalError::bad_form(form_name, "bindings must be a list", list.span))?;
1856    let mut out = Vec::with_capacity(bindings.len());
1857    for binding in bindings {
1858        let pair = binding.as_list().ok_or_else(|| {
1859            EvalError::bad_form(form_name, "each binding must be (name expr)", binding.span)
1860        })?;
1861        if pair.len() != 2 {
1862            return Err(EvalError::bad_form(
1863                form_name,
1864                "binding must be exactly (name expr)",
1865                binding.span,
1866            ));
1867        }
1868        let name = pair[0].as_symbol().ok_or_else(|| {
1869            EvalError::bad_form(form_name, "binding name must be a symbol", pair[0].span)
1870        })?;
1871        out.push((Arc::<str>::from(name), &pair[1]));
1872    }
1873    Ok(out)
1874}
1875
1876fn sf_let<H: 'static>(
1877    items: &[Spanned],
1878    span: Span,
1879    env: &mut Env,
1880    registry: &FnRegistry<H>,
1881    expander: &SpannedExpander,
1882    host: &mut H,
1883) -> Result<Value> {
1884    if items.len() < 3 {
1885        return Err(EvalError::bad_form(
1886            "let",
1887            "expected (let ((name expr)...) body...)",
1888            span,
1889        ));
1890    }
1891    let bindings = parse_binding_list(&items[1], "let")?;
1892    // Parallel semantics: evaluate all RHS in the *outer* env, then
1893    // extend with new frame.
1894    let mut values = Vec::with_capacity(bindings.len());
1895    for (_, expr) in &bindings {
1896        values.push(eval_in(env, registry, expander, expr, host)?);
1897    }
1898    env.push();
1899    for ((name, _), val) in bindings.into_iter().zip(values) {
1900        env.define(name, val);
1901    }
1902    let result = eval_body(&items[2..], env, registry, expander, host);
1903    env.pop();
1904    result
1905}
1906
1907fn sf_let_star<H: 'static>(
1908    items: &[Spanned],
1909    span: Span,
1910    env: &mut Env,
1911    registry: &FnRegistry<H>,
1912    expander: &SpannedExpander,
1913    host: &mut H,
1914) -> Result<Value> {
1915    if items.len() < 3 {
1916        return Err(EvalError::bad_form(
1917            "let*",
1918            "expected (let* ((name expr)...) body...)",
1919            span,
1920        ));
1921    }
1922    let bindings = parse_binding_list(&items[1], "let*")?;
1923    env.push();
1924    for (name, expr) in bindings {
1925        let v = eval_in(env, registry, expander, expr, host)?;
1926        env.define(name, v);
1927    }
1928    let result = eval_body(&items[2..], env, registry, expander, host);
1929    env.pop();
1930    result
1931}
1932
1933fn sf_letrec<H: 'static>(
1934    items: &[Spanned],
1935    span: Span,
1936    env: &mut Env,
1937    registry: &FnRegistry<H>,
1938    expander: &SpannedExpander,
1939    host: &mut H,
1940) -> Result<Value> {
1941    if items.len() < 3 {
1942        return Err(EvalError::bad_form(
1943            "letrec",
1944            "expected (letrec ((name expr)...) body...)",
1945            span,
1946        ));
1947    }
1948    let bindings = parse_binding_list(&items[1], "letrec")?;
1949    env.push();
1950    // Pre-bind each name to Nil so RHS can self-reference (and cross-
1951    // reference). Then eval each RHS in order and rebind.
1952    for (name, _) in &bindings {
1953        env.define(name.clone(), Value::Nil);
1954    }
1955    for (name, expr) in &bindings {
1956        let v = eval_in(env, registry, expander, expr, host)?;
1957        env.define(name.clone(), v);
1958    }
1959    let result = eval_body(&items[2..], env, registry, expander, host);
1960    env.pop();
1961    result
1962}
1963
1964fn eval_body<H: 'static>(
1965    body: &[Spanned],
1966    env: &mut Env,
1967    registry: &FnRegistry<H>,
1968    expander: &SpannedExpander,
1969    host: &mut H,
1970) -> Result<Value> {
1971    let mut last = Value::Nil;
1972    for form in body {
1973        last = eval_in(env, registry, expander, form, host)?;
1974    }
1975    Ok(last)
1976}
1977
1978fn sf_lambda(items: &[Spanned], span: Span, env: &Env) -> Result<Value> {
1979    if items.len() < 3 {
1980        return Err(EvalError::bad_form(
1981            "lambda",
1982            "expected (lambda (params...) body...)",
1983            span,
1984        ));
1985    }
1986    // Empty `()` source parses as Nil, not List([]); accept both as
1987    // "no parameters". Anything else must be a List.
1988    let param_list: &[Spanned] = match &items[1].form {
1989        SpannedForm::Nil => &[],
1990        SpannedForm::List(xs) => xs.as_slice(),
1991        _ => {
1992            return Err(EvalError::bad_form(
1993                "lambda",
1994                "params must be a list",
1995                items[1].span,
1996            ))
1997        }
1998    };
1999    let (params, rest) = parse_lambda_params(param_list, items[1].span)?;
2000    let body = items[2..].to_vec();
2001    Ok(Value::Closure(Arc::new(Closure {
2002        params,
2003        rest,
2004        body,
2005        captured_env: env.clone(),
2006        source: span,
2007    })))
2008}
2009
2010fn parse_lambda_params(list: &[Spanned], span: Span) -> Result<(Vec<Arc<str>>, Option<Arc<str>>)> {
2011    let mut params = Vec::new();
2012    let mut rest = None;
2013    let mut i = 0;
2014    while i < list.len() {
2015        let s = list[i]
2016            .as_symbol()
2017            .ok_or_else(|| EvalError::bad_form("lambda", "param must be a symbol", list[i].span))?;
2018        if s == "&rest" {
2019            let name = list
2020                .get(i + 1)
2021                .and_then(Spanned::as_symbol)
2022                .ok_or_else(|| EvalError::bad_form("lambda", "&rest needs a name", span))?;
2023            rest = Some(Arc::<str>::from(name));
2024            if i + 2 != list.len() {
2025                return Err(EvalError::bad_form(
2026                    "lambda",
2027                    "&rest must be the last param",
2028                    span,
2029                ));
2030            }
2031            break;
2032        }
2033        params.push(Arc::<str>::from(s));
2034        i += 1;
2035    }
2036    Ok((params, rest))
2037}
2038
2039/// `(define name expr)` or `(define (name params...) body...)`
2040fn sf_define<H: 'static>(
2041    items: &[Spanned],
2042    span: Span,
2043    env: &mut Env,
2044    registry: &FnRegistry<H>,
2045    expander: &SpannedExpander,
2046    host: &mut H,
2047) -> Result<Value> {
2048    if items.len() < 3 {
2049        return Err(EvalError::bad_form(
2050            "define",
2051            "expected (define name expr) or (define (name args) body)",
2052            span,
2053        ));
2054    }
2055    match &items[1].form {
2056        SpannedForm::Atom(Atom::Symbol(name)) => {
2057            let v = eval_in(env, registry, expander, &items[2], host)?;
2058            env.define(Arc::<str>::from(name.as_str()), v);
2059            Ok(Value::Nil)
2060        }
2061        SpannedForm::List(head_list) => {
2062            if head_list.is_empty() {
2063                return Err(EvalError::bad_form(
2064                    "define",
2065                    "empty (name args) list",
2066                    items[1].span,
2067                ));
2068            }
2069            let name = head_list[0].as_symbol().ok_or_else(|| {
2070                EvalError::bad_form(
2071                    "define",
2072                    "first item in (name args) must be a symbol",
2073                    head_list[0].span,
2074                )
2075            })?;
2076            let (params, rest) = parse_lambda_params(&head_list[1..], items[1].span)?;
2077            let body = items[2..].to_vec();
2078            let closure = Arc::new(Closure {
2079                params,
2080                rest,
2081                body,
2082                captured_env: env.clone(),
2083                source: span,
2084            });
2085            env.define(Arc::<str>::from(name), Value::Closure(closure));
2086            Ok(Value::Nil)
2087        }
2088        _ => Err(EvalError::bad_form(
2089            "define",
2090            "second form must be a symbol or (name args) list",
2091            items[1].span,
2092        )),
2093    }
2094}
2095
2096fn sf_set<H: 'static>(
2097    items: &[Spanned],
2098    span: Span,
2099    env: &mut Env,
2100    registry: &FnRegistry<H>,
2101    expander: &SpannedExpander,
2102    host: &mut H,
2103) -> Result<Value> {
2104    if items.len() != 3 {
2105        return Err(EvalError::bad_form(
2106            "set!",
2107            "expected (set! name expr)",
2108            span,
2109        ));
2110    }
2111    let name = items[1]
2112        .as_symbol()
2113        .ok_or_else(|| EvalError::bad_form("set!", "first arg must be a symbol", items[1].span))?;
2114    let v = eval_in(env, registry, expander, &items[2], host)?;
2115    if env.set(name, v) {
2116        Ok(Value::Nil)
2117    } else if let (true, Some(seal)) = (env.is_sealed_binding(name), env.seal()) {
2118        // Distinguishes a sealed write from an unbound name. The seal carries
2119        // its own reason, so this raise site does not assume which boundary
2120        // was crossed — it used to, and the assumption was wrong the moment a
2121        // second caller started sealing.
2122        Err(EvalError::SetSealed {
2123            name: name.into(),
2124            seal,
2125            at: items[1].span,
2126        })
2127    } else {
2128        Err(EvalError::unbound(name, items[1].span))
2129    }
2130}
2131
2132fn sf_begin<H: 'static>(
2133    body: &[Spanned],
2134    env: &mut Env,
2135    registry: &FnRegistry<H>,
2136    expander: &SpannedExpander,
2137    host: &mut H,
2138) -> Result<Value> {
2139    eval_body(body, env, registry, expander, host)
2140}
2141
2142fn sf_and<H: 'static>(
2143    exprs: &[Spanned],
2144    env: &mut Env,
2145    registry: &FnRegistry<H>,
2146    expander: &SpannedExpander,
2147    host: &mut H,
2148) -> Result<Value> {
2149    let mut last = Value::Bool(true);
2150    for e in exprs {
2151        last = eval_in(env, registry, expander, e, host)?;
2152        if !last.is_truthy() {
2153            return Ok(last);
2154        }
2155    }
2156    Ok(last)
2157}
2158
2159fn sf_or<H: 'static>(
2160    exprs: &[Spanned],
2161    env: &mut Env,
2162    registry: &FnRegistry<H>,
2163    expander: &SpannedExpander,
2164    host: &mut H,
2165) -> Result<Value> {
2166    let mut last = Value::Bool(false);
2167    for e in exprs {
2168        last = eval_in(env, registry, expander, e, host)?;
2169        if last.is_truthy() {
2170            return Ok(last);
2171        }
2172    }
2173    Ok(last)
2174}
2175
2176fn sf_not<H: 'static>(
2177    items: &[Spanned],
2178    span: Span,
2179    env: &mut Env,
2180    registry: &FnRegistry<H>,
2181    expander: &SpannedExpander,
2182    host: &mut H,
2183) -> Result<Value> {
2184    if items.len() != 2 {
2185        return Err(EvalError::bad_form("not", "expected (not x)", span));
2186    }
2187    let v = eval_in(env, registry, expander, &items[1], host)?;
2188    Ok(Value::Bool(!v.is_truthy()))
2189}
2190
2191/// `(try body... (catch (binding) handler...))` — evaluate body
2192/// sequentially. If any form raises an `EvalError::User` (Lisp
2193/// `(throw ...)`), bind the thrown Value to `binding` and run handler.
2194/// Other Rust-side errors (type mismatch, arity, etc.) are converted
2195/// to a `Value::Error` with tag `:runtime` so handlers can also
2196/// recover from them.
2197///
2198/// Form layout:
2199/// ```text
2200///   (try
2201///     body-expr
2202///     ...
2203///     (catch (e) handler-body...))
2204/// ```
2205/// The catch clause MUST be the last form. There can only be one
2206/// catch clause. Body forms before it are evaluated in order; the
2207/// last body form's value (or the handler's value, if caught) is
2208/// returned.
2209fn sf_try<H: 'static>(
2210    items: &[Spanned],
2211    span: Span,
2212    env: &mut Env,
2213    registry: &FnRegistry<H>,
2214    expander: &SpannedExpander,
2215    host: &mut H,
2216) -> Result<Value> {
2217    if items.len() < 3 {
2218        return Err(EvalError::bad_form(
2219            "try",
2220            "expected (try body... (catch (e) handler...))",
2221            span,
2222        ));
2223    }
2224    // The last form must be a catch clause.
2225    let catch_form = items.last().unwrap();
2226    let catch_list = catch_form.as_list().ok_or_else(|| {
2227        EvalError::bad_form(
2228            "try",
2229            "last form must be (catch (binding) handler...)",
2230            catch_form.span,
2231        )
2232    })?;
2233    if catch_list.is_empty() || catch_list[0].as_symbol() != Some("catch") {
2234        return Err(EvalError::bad_form(
2235            "try",
2236            "last form must be a (catch ...) clause",
2237            catch_form.span,
2238        ));
2239    }
2240    if catch_list.len() < 3 {
2241        return Err(EvalError::bad_form(
2242            "catch",
2243            "expected (catch (binding) handler...)",
2244            catch_form.span,
2245        ));
2246    }
2247    let binding_list = catch_list[1].as_list().ok_or_else(|| {
2248        EvalError::bad_form(
2249            "catch",
2250            "binding must be a 1-element list (e)",
2251            catch_list[1].span,
2252        )
2253    })?;
2254    if binding_list.len() != 1 {
2255        return Err(EvalError::bad_form(
2256            "catch",
2257            "binding must bind exactly one symbol",
2258            catch_list[1].span,
2259        ));
2260    }
2261    let binding_name = binding_list[0].as_symbol().ok_or_else(|| {
2262        EvalError::bad_form("catch", "binding must be a symbol", binding_list[0].span)
2263    })?;
2264
2265    let body = &items[1..items.len() - 1];
2266    let mut last = Value::Nil;
2267    for form in body {
2268        match eval_in(env, registry, expander, form, host) {
2269            Ok(v) => {
2270                last = v;
2271            }
2272            Err(EvalError::User { value, .. }) => {
2273                return run_catch_handler(
2274                    binding_name,
2275                    value,
2276                    &catch_list[2..],
2277                    env,
2278                    registry,
2279                    expander,
2280                    host,
2281                );
2282            }
2283            Err(other) => {
2284                // Convert any other runtime error into a Value::Error
2285                // so catch can still observe it. Tag :runtime
2286                // distinguishes from user-thrown errors.
2287                let value = rust_err_to_value_error(&other);
2288                return run_catch_handler(
2289                    binding_name,
2290                    value,
2291                    &catch_list[2..],
2292                    env,
2293                    registry,
2294                    expander,
2295                    host,
2296                );
2297            }
2298        }
2299    }
2300    Ok(last)
2301}
2302
2303fn run_catch_handler<H: 'static>(
2304    binding_name: &str,
2305    error_value: Value,
2306    handler_body: &[Spanned],
2307    env: &mut Env,
2308    registry: &FnRegistry<H>,
2309    expander: &SpannedExpander,
2310    host: &mut H,
2311) -> Result<Value> {
2312    env.push();
2313    env.define(Arc::<str>::from(binding_name), error_value);
2314    let mut last = Value::Nil;
2315    for form in handler_body {
2316        match eval_in(env, registry, expander, form, host) {
2317            Ok(v) => last = v,
2318            Err(e) => {
2319                env.pop();
2320                return Err(e);
2321            }
2322        }
2323    }
2324    env.pop();
2325    Ok(last)
2326}
2327
2328/// `(eval form)` — evaluate the runtime Value `form` as code. The
2329/// argument is itself evaluated first to obtain the form (typically
2330/// a quoted list). The form is then lifted to Spanned, fully expanded
2331/// (in case it contains macro calls), and evaluated in the current
2332/// env. Returns the result.
2333///
2334/// Unlocks runtime metaprogramming: `(eval (read-string source))` is
2335/// the canonical "compile + run from string" pattern.
2336fn sf_eval<H: 'static>(
2337    items: &[Spanned],
2338    call_span: Span,
2339    env: &mut Env,
2340    registry: &FnRegistry<H>,
2341    expander: &SpannedExpander,
2342    host: &mut H,
2343) -> Result<Value> {
2344    if items.len() != 2 {
2345        return Err(EvalError::bad_form(
2346            "eval",
2347            "expected (eval form)",
2348            call_span,
2349        ));
2350    }
2351    let form_value = eval_in(env, registry, expander, &items[1], host)?;
2352    let form_spanned = crate::code::value_to_spanned(&form_value, call_span)
2353        .map_err(|reason| EvalError::native_fn(Arc::<str>::from("eval"), reason, call_span))?;
2354    let expanded = fully_expand_with(&form_spanned, registry, expander, env, host)?;
2355    eval_in(env, registry, expander, &expanded, host)
2356}
2357
2358/// `(delay expr)` — wrap `expr` in a `Value::Promise` whose first
2359/// `force` evaluates the body once and caches. The body becomes the
2360/// closure body of a 0-arity lambda capturing the current env, then
2361/// stored as the promise's pending state.
2362fn sf_delay(items: &[Spanned], call_span: Span, env: &Env) -> Result<Value> {
2363    if items.len() != 2 {
2364        return Err(EvalError::bad_form(
2365            "delay",
2366            "expected (delay expr)",
2367            call_span,
2368        ));
2369    }
2370    let body = vec![items[1].clone()];
2371    let thunk = Arc::new(Closure {
2372        params: Vec::new(),
2373        rest: None,
2374        body,
2375        captured_env: env.clone(),
2376        source: call_span,
2377    });
2378    Ok(Value::Promise(Arc::new(std::sync::Mutex::new(
2379        crate::value::PromiseState::Pending(thunk),
2380    ))))
2381}
2382
2383/// `(macroexpand-1 form)` and `(macroexpand form)` — return the
2384/// expansion of `form` as a Value. `form` is evaluated to obtain a
2385/// source-form Value (typically a quoted list); we lift it back to a
2386/// Spanned, run one (macroexpand-1) or full (macroexpand) expansion,
2387/// then convert the result Value back.
2388///
2389/// Useful for debugging macros — see exactly what the expander
2390/// produces given a sample input.
2391fn sf_macroexpand<H: 'static>(
2392    items: &[Spanned],
2393    call_span: Span,
2394    env: &mut Env,
2395    registry: &FnRegistry<H>,
2396    expander: &SpannedExpander,
2397    host: &mut H,
2398    fully: bool,
2399) -> Result<Value> {
2400    if items.len() != 2 {
2401        return Err(EvalError::bad_form(
2402            if fully {
2403                "macroexpand"
2404            } else {
2405                "macroexpand-1"
2406            },
2407            "expected (macroexpand[-1] form)",
2408            call_span,
2409        ));
2410    }
2411    // Evaluate the argument to obtain a source-form Value.
2412    let form_value = eval_in(env, registry, expander, &items[1], host)?;
2413    // Lift to Spanned so the expander can walk it.
2414    let form_spanned = crate::code::value_to_spanned(&form_value, call_span).map_err(|reason| {
2415        EvalError::native_fn(
2416            Arc::<str>::from(if fully {
2417                "macroexpand"
2418            } else {
2419                "macroexpand-1"
2420            }),
2421            reason,
2422            call_span,
2423        )
2424    })?;
2425
2426    // Build a fresh interpreter-style call into the same expander/registry.
2427    // We can't recursively call self.fully_expand or self.expand_macro_call
2428    // here because we don't have &mut Interpreter. Instead, we do the
2429    // single-step or recursive expansion ourselves via the same
2430    // primitives that the Interpreter uses.
2431    let expanded = if fully {
2432        fully_expand_with(&form_spanned, registry, expander, env, host)?
2433    } else {
2434        macroexpand_one(&form_spanned, registry, expander, env, host)?
2435    };
2436
2437    Ok(crate::code::spanned_to_value(&expanded))
2438}
2439
2440/// Free-function variant of `Interpreter::expand_macro_call`. Takes the
2441/// state pieces explicitly so it can be called from a special form
2442/// (where we don't have `&mut Interpreter` available).
2443fn expand_one_macro_call<H: 'static>(
2444    macro_name: &str,
2445    args: &[Spanned],
2446    call_span: Span,
2447    registry: &FnRegistry<H>,
2448    expander: &SpannedExpander,
2449    parent_env: &Env,
2450    host: &mut H,
2451) -> Result<Spanned> {
2452    let def: MacroDef = expander.get_macro(macro_name).cloned().ok_or_else(|| {
2453        EvalError::native_fn(
2454            Arc::<str>::from(macro_name),
2455            "macro disappeared during expansion",
2456            call_span,
2457        )
2458    })?;
2459    let body_spanned = Spanned::from_sexp_at(&def.body, call_span);
2460    // First expand any macros inside the body itself.
2461    let body_expanded = fully_expand_with(&body_spanned, registry, expander, parent_env, host)?;
2462
2463    let mut macro_env = parent_env.clone();
2464    macro_env.push();
2465    bind_macro_args(&mut macro_env, &def.name, &def.params, args, call_span)?;
2466    let result = eval_in(&mut macro_env, registry, expander, &body_expanded, host)?;
2467
2468    crate::code::value_to_spanned(&result, call_span).map_err(|reason| {
2469        EvalError::native_fn(
2470            Arc::<str>::from(format!("macro {macro_name}")),
2471            reason,
2472            call_span,
2473        )
2474    })
2475}
2476
2477/// Free-function variant of `Interpreter::fully_expand`. Recursively
2478/// expands every macro call in the form tree, terminating at fixed
2479/// point.
2480fn fully_expand_with<H: 'static>(
2481    form: &Spanned,
2482    registry: &FnRegistry<H>,
2483    expander: &SpannedExpander,
2484    parent_env: &Env,
2485    host: &mut H,
2486) -> Result<Spanned> {
2487    if expander.is_empty() {
2488        return Ok(form.clone());
2489    }
2490    expand_recursive_with(form, registry, expander, parent_env, host)
2491}
2492
2493fn expand_recursive_with<H: 'static>(
2494    form: &Spanned,
2495    registry: &FnRegistry<H>,
2496    expander: &SpannedExpander,
2497    parent_env: &Env,
2498    host: &mut H,
2499) -> Result<Spanned> {
2500    match &form.form {
2501        SpannedForm::List(items) if !items.is_empty() => {
2502            if let Some(head) = items[0].as_symbol() {
2503                if expander.has(head) {
2504                    let expanded = expand_one_macro_call(
2505                        head,
2506                        &items[1..],
2507                        form.span,
2508                        registry,
2509                        expander,
2510                        parent_env,
2511                        host,
2512                    )?;
2513                    return expand_recursive_with(&expanded, registry, expander, parent_env, host);
2514                }
2515            }
2516            let mut out = Vec::with_capacity(items.len());
2517            for child in items {
2518                out.push(expand_recursive_with(
2519                    child, registry, expander, parent_env, host,
2520                )?);
2521            }
2522            Ok(Spanned::new(form.span, SpannedForm::List(out)))
2523        }
2524        SpannedForm::Quote(_) => Ok(form.clone()),
2525        SpannedForm::Quasiquote(inner) => Ok(Spanned::new(
2526            form.span,
2527            SpannedForm::Quasiquote(Box::new(expand_inside_quasiquote_with(
2528                inner, registry, expander, parent_env, host,
2529            )?)),
2530        )),
2531        _ => Ok(form.clone()),
2532    }
2533}
2534
2535fn expand_inside_quasiquote_with<H: 'static>(
2536    form: &Spanned,
2537    registry: &FnRegistry<H>,
2538    expander: &SpannedExpander,
2539    parent_env: &Env,
2540    host: &mut H,
2541) -> Result<Spanned> {
2542    match &form.form {
2543        SpannedForm::Unquote(inner) => Ok(Spanned::new(
2544            form.span,
2545            SpannedForm::Unquote(Box::new(expand_recursive_with(
2546                inner, registry, expander, parent_env, host,
2547            )?)),
2548        )),
2549        SpannedForm::UnquoteSplice(inner) => Ok(Spanned::new(
2550            form.span,
2551            SpannedForm::UnquoteSplice(Box::new(expand_recursive_with(
2552                inner, registry, expander, parent_env, host,
2553            )?)),
2554        )),
2555        SpannedForm::List(items) => {
2556            let mut out = Vec::with_capacity(items.len());
2557            for item in items {
2558                out.push(expand_inside_quasiquote_with(
2559                    item, registry, expander, parent_env, host,
2560                )?);
2561            }
2562            Ok(Spanned::new(form.span, SpannedForm::List(out)))
2563        }
2564        _ => Ok(form.clone()),
2565    }
2566}
2567
2568/// One-step macroexpansion: expand ONLY the head call if it's a macro;
2569/// otherwise return form unchanged. Children are NOT expanded.
2570fn macroexpand_one<H: 'static>(
2571    form: &Spanned,
2572    registry: &FnRegistry<H>,
2573    expander: &SpannedExpander,
2574    parent_env: &Env,
2575    host: &mut H,
2576) -> Result<Spanned> {
2577    if let SpannedForm::List(items) = &form.form {
2578        if let Some(head) = items.first().and_then(Spanned::as_symbol) {
2579            if expander.has(head) {
2580                return expand_one_macro_call(
2581                    head,
2582                    &items[1..],
2583                    form.span,
2584                    registry,
2585                    expander,
2586                    parent_env,
2587                    host,
2588                );
2589            }
2590        }
2591    }
2592    Ok(form.clone())
2593}
2594
2595/// Convert a Rust-side `EvalError` into a `Value::Error` so a `(catch)`
2596/// handler can observe runtime errors uniformly with user-thrown ones.
2597fn rust_err_to_value_error(err: &EvalError) -> Value {
2598    use crate::value::ErrorObj;
2599    let tag: Arc<str> = Arc::from(err.tag());
2600    let message: Arc<str> = Arc::from(err.short_message());
2601    Value::Error(Arc::new(ErrorObj {
2602        tag,
2603        message,
2604        data: Vec::new(),
2605    }))
2606}
2607
2608#[cfg(test)]
2609mod tests {
2610    use super::*;
2611    use crate::primitive::install_primitives;
2612    use tatara_lisp::read_spanned;
2613
2614    struct NoHost;
2615
2616    fn eval_ok(src: &str) -> Value {
2617        let forms = read_spanned(src).unwrap();
2618        let mut i: Interpreter<NoHost> = Interpreter::new();
2619        install_primitives(&mut i);
2620        let mut host = NoHost;
2621        i.eval_program(&forms, &mut host).unwrap()
2622    }
2623
2624    fn eval_err(src: &str) -> EvalError {
2625        let forms = read_spanned(src).unwrap();
2626        let mut i: Interpreter<NoHost> = Interpreter::new();
2627        install_primitives(&mut i);
2628        let mut host = NoHost;
2629        i.eval_program(&forms, &mut host).unwrap_err()
2630    }
2631
2632    // ── Literals + symbol lookup ──────────────────────────────────
2633
2634    #[test]
2635    fn literal_int() {
2636        assert!(matches!(eval_ok("42"), Value::Int(42)));
2637    }
2638
2639    #[test]
2640    fn unbound_symbol_errors() {
2641        let e = eval_err("no-such-var");
2642        assert!(matches!(e, EvalError::UnboundSymbol { .. }));
2643    }
2644
2645    #[test]
2646    fn quote_returns_runtime_list_of_symbols() {
2647        // Scheme/Clojure semantics: '(a b c) yields a runtime list of
2648        // three symbols, not a wrapped source-form Sexp.
2649        let v = eval_ok("'(a b c)");
2650        match v {
2651            Value::List(xs) => {
2652                assert_eq!(xs.len(), 3);
2653                assert!(matches!(&xs[0], Value::Symbol(s) if s.as_ref() == "a"));
2654                assert!(matches!(&xs[1], Value::Symbol(s) if s.as_ref() == "b"));
2655                assert!(matches!(&xs[2], Value::Symbol(s) if s.as_ref() == "c"));
2656            }
2657            other => panic!("{other:?}"),
2658        }
2659    }
2660
2661    // ── Arithmetic via primitives ─────────────────────────────────
2662
2663    #[test]
2664    fn add_ints() {
2665        assert!(matches!(eval_ok("(+ 1 2 3)"), Value::Int(6)));
2666    }
2667
2668    #[test]
2669    fn sub_divides_float() {
2670        match eval_ok("(- 10 3)") {
2671            Value::Int(7) => {}
2672            other => panic!("{other:?}"),
2673        }
2674    }
2675
2676    #[test]
2677    fn division_by_zero_errors() {
2678        assert!(matches!(
2679            eval_err("(/ 1 0)"),
2680            EvalError::DivisionByZero { .. }
2681        ));
2682    }
2683
2684    // ── Conditionals ──────────────────────────────────────────────
2685
2686    #[test]
2687    fn if_truthy_branch() {
2688        assert!(matches!(eval_ok("(if #t 1 2)"), Value::Int(1)));
2689    }
2690
2691    #[test]
2692    fn if_falsy_branch() {
2693        assert!(matches!(eval_ok("(if #f 1 2)"), Value::Int(2)));
2694    }
2695
2696    #[test]
2697    fn if_no_else_returns_nil() {
2698        assert!(matches!(eval_ok("(if #f 1)"), Value::Nil));
2699    }
2700
2701    #[test]
2702    fn cond_picks_first_match() {
2703        assert!(matches!(
2704            eval_ok("(cond (#f 1) (#t 2) (else 3))"),
2705            Value::Int(2)
2706        ));
2707    }
2708
2709    #[test]
2710    fn cond_falls_through_to_else() {
2711        assert!(matches!(
2712            eval_ok("(cond (#f 1) (#f 2) (else 3))"),
2713            Value::Int(3)
2714        ));
2715    }
2716
2717    #[test]
2718    fn when_runs_body_if_true() {
2719        assert!(matches!(eval_ok("(when #t 99)"), Value::Int(99)));
2720        assert!(matches!(eval_ok("(when #f 99)"), Value::Nil));
2721    }
2722
2723    // ── Let forms ─────────────────────────────────────────────────
2724
2725    #[test]
2726    fn let_binds_and_evaluates_body() {
2727        assert!(matches!(
2728            eval_ok("(let ((x 10) (y 20)) (+ x y))"),
2729            Value::Int(30)
2730        ));
2731    }
2732
2733    #[test]
2734    fn let_star_sequential_bindings() {
2735        assert!(matches!(
2736            eval_ok("(let* ((x 5) (y (+ x 1))) (+ x y))"),
2737            Value::Int(11)
2738        ));
2739    }
2740
2741    #[test]
2742    fn letrec_mutual_recursion() {
2743        let v = eval_ok(
2744            "(letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1)))))
2745                      (odd?  (lambda (n) (if (= n 0) #f (even? (- n 1))))))
2746               (even? 10))",
2747        );
2748        assert!(matches!(v, Value::Bool(true)));
2749    }
2750
2751    // ── Lambda + closure ──────────────────────────────────────────
2752
2753    #[test]
2754    fn lambda_applies() {
2755        assert!(matches!(
2756            eval_ok("((lambda (x y) (+ x y)) 3 4)"),
2757            Value::Int(7)
2758        ));
2759    }
2760
2761    #[test]
2762    fn lambda_closes_over_env() {
2763        assert!(matches!(
2764            eval_ok("(let ((n 10)) ((lambda (x) (+ x n)) 5))"),
2765            Value::Int(15)
2766        ));
2767    }
2768
2769    #[test]
2770    fn closure_captures_by_value_at_creation() {
2771        // make-adder style — the returned closure should capture n=5 even
2772        // though the outer let scope has exited.
2773        let v = eval_ok(
2774            "(define make-adder (lambda (n) (lambda (x) (+ x n))))
2775             (define add5 (make-adder 5))
2776             (add5 10)",
2777        );
2778        assert!(matches!(v, Value::Int(15)));
2779    }
2780
2781    #[test]
2782    fn rest_args_collect_into_list() {
2783        let v = eval_ok("((lambda (x &rest rs) (length rs)) 1 2 3 4 5)");
2784        assert!(matches!(v, Value::Int(4)));
2785    }
2786
2787    #[test]
2788    fn closure_arity_mismatch() {
2789        let e = eval_err("((lambda (x y) (+ x y)) 1)");
2790        assert!(matches!(e, EvalError::ArityMismatch { .. }));
2791    }
2792
2793    // ── Define + set! ─────────────────────────────────────────────
2794
2795    #[test]
2796    fn define_then_use() {
2797        assert!(matches!(eval_ok("(define x 42) x"), Value::Int(42)));
2798    }
2799
2800    #[test]
2801    fn define_function_shorthand() {
2802        assert!(matches!(
2803            eval_ok("(define (sq x) (* x x)) (sq 6)"),
2804            Value::Int(36)
2805        ));
2806    }
2807
2808    #[test]
2809    fn set_mutates_existing() {
2810        assert!(matches!(
2811            eval_ok("(define x 1) (set! x 99) x"),
2812            Value::Int(99)
2813        ));
2814    }
2815
2816    #[test]
2817    fn set_unbound_errors() {
2818        let e = eval_err("(set! nope 1)");
2819        assert!(matches!(e, EvalError::UnboundSymbol { .. }));
2820    }
2821
2822    // ── begin / and / or / not ────────────────────────────────────
2823
2824    #[test]
2825    fn begin_returns_last() {
2826        assert!(matches!(eval_ok("(begin 1 2 3)"), Value::Int(3)));
2827    }
2828
2829    #[test]
2830    fn and_short_circuits() {
2831        assert!(matches!(eval_ok("(and 1 #f 2)"), Value::Bool(false)));
2832        assert!(matches!(eval_ok("(and 1 2 3)"), Value::Int(3)));
2833        assert!(matches!(eval_ok("(and)"), Value::Bool(true)));
2834    }
2835
2836    #[test]
2837    fn or_short_circuits() {
2838        assert!(matches!(eval_ok("(or #f #f 7)"), Value::Int(7)));
2839        assert!(matches!(eval_ok("(or #f #f)"), Value::Bool(false)));
2840        assert!(matches!(eval_ok("(or)"), Value::Bool(false)));
2841    }
2842
2843    #[test]
2844    fn not_inverts() {
2845        assert!(matches!(eval_ok("(not #t)"), Value::Bool(false)));
2846        assert!(matches!(eval_ok("(not #f)"), Value::Bool(true)));
2847        assert!(matches!(eval_ok("(not 42)"), Value::Bool(false)));
2848    }
2849
2850    // ── Recursion ─────────────────────────────────────────────────
2851
2852    #[test]
2853    fn recursive_factorial() {
2854        let v = eval_ok(
2855            "(define (fact n)
2856               (if (= n 0) 1 (* n (fact (- n 1)))))
2857             (fact 6)",
2858        );
2859        assert!(matches!(v, Value::Int(720)));
2860    }
2861
2862    #[test]
2863    fn recursive_length() {
2864        let v = eval_ok(
2865            "(define (len xs)
2866               (if (null? xs) 0 (+ 1 (len (cdr xs)))))
2867             (len (list 1 2 3 4 5))",
2868        );
2869        assert!(matches!(v, Value::Int(5)));
2870    }
2871
2872    // ── Host context reachable via register_fn ────────────────────
2873
2874    // ── Quasiquote ────────────────────────────────────────────────
2875
2876    #[test]
2877    fn quasiquote_plain_list_is_runtime_list() {
2878        let v = eval_ok("`(a b c)");
2879        match v {
2880            Value::List(xs) => {
2881                assert_eq!(xs.len(), 3);
2882                assert!(matches!(&xs[0], Value::Symbol(s) if s.as_ref() == "a"));
2883                assert!(matches!(&xs[1], Value::Symbol(s) if s.as_ref() == "b"));
2884                assert!(matches!(&xs[2], Value::Symbol(s) if s.as_ref() == "c"));
2885            }
2886            other => panic!("{other:?}"),
2887        }
2888    }
2889
2890    #[test]
2891    fn quasiquote_unquote_substitutes_evaluated_value() {
2892        let v = eval_ok("(let ((x 42)) `(a ,x c))");
2893        match v {
2894            Value::List(xs) => {
2895                assert_eq!(xs.len(), 3);
2896                assert!(matches!(&xs[1], Value::Int(42)));
2897            }
2898            other => panic!("{other:?}"),
2899        }
2900    }
2901
2902    #[test]
2903    fn quasiquote_unquote_arbitrary_expr() {
2904        let v = eval_ok("`(x ,(+ 1 2 3) y)");
2905        match v {
2906            Value::List(xs) => {
2907                assert!(matches!(&xs[1], Value::Int(6)));
2908            }
2909            other => panic!("{other:?}"),
2910        }
2911    }
2912
2913    #[test]
2914    fn quasiquote_splice_inlines_list() {
2915        let v = eval_ok("`(a ,@(list 1 2 3) b)");
2916        match v {
2917            Value::List(xs) => {
2918                assert_eq!(xs.len(), 5);
2919                assert!(matches!(&xs[0], Value::Symbol(s) if s.as_ref() == "a"));
2920                assert!(matches!(&xs[1], Value::Int(1)));
2921                assert!(matches!(&xs[2], Value::Int(2)));
2922                assert!(matches!(&xs[3], Value::Int(3)));
2923                assert!(matches!(&xs[4], Value::Symbol(s) if s.as_ref() == "b"));
2924            }
2925            other => panic!("{other:?}"),
2926        }
2927    }
2928
2929    #[test]
2930    fn quasiquote_splice_empty_list_splices_nothing() {
2931        let v = eval_ok("`(a ,@(list) b)");
2932        match v {
2933            Value::List(xs) => {
2934                assert_eq!(xs.len(), 2);
2935                assert!(matches!(&xs[0], Value::Symbol(s) if s.as_ref() == "a"));
2936                assert!(matches!(&xs[1], Value::Symbol(s) if s.as_ref() == "b"));
2937            }
2938            other => panic!("{other:?}"),
2939        }
2940    }
2941
2942    #[test]
2943    fn quasiquote_splice_non_list_errors() {
2944        let e = eval_err("`(a ,@42)");
2945        assert!(matches!(e, EvalError::TypeMismatch { .. }));
2946    }
2947
2948    #[test]
2949    fn quasiquote_atom_yields_atom_value() {
2950        assert!(matches!(eval_ok("`foo"), Value::Symbol(s) if s.as_ref() == "foo"));
2951        assert!(matches!(eval_ok("`42"), Value::Int(42)));
2952    }
2953
2954    #[test]
2955    fn quasiquote_with_nested_list_and_unquote() {
2956        // `(foo (bar ,x) baz) where x=99 → (foo (bar 99) baz)
2957        let v = eval_ok("(let ((x 99)) `(foo (bar ,x) baz))");
2958        match v {
2959            Value::List(xs) => {
2960                assert_eq!(xs.len(), 3);
2961                match &xs[1] {
2962                    Value::List(inner) => {
2963                        assert!(matches!(&inner[1], Value::Int(99)));
2964                    }
2965                    other => panic!("{other:?}"),
2966                }
2967            }
2968            other => panic!("{other:?}"),
2969        }
2970    }
2971
2972    #[test]
2973    fn quasiquote_symbol_keyword_distinction_preserved() {
2974        let v = eval_ok("`(:key val)");
2975        match v {
2976            Value::List(xs) => {
2977                assert!(matches!(&xs[0], Value::Keyword(s) if s.as_ref() == "key"));
2978                assert!(matches!(&xs[1], Value::Symbol(s) if s.as_ref() == "val"));
2979            }
2980            other => panic!("{other:?}"),
2981        }
2982    }
2983
2984    #[test]
2985    fn bare_unquote_outside_quasiquote_errors() {
2986        let e = eval_err(",x");
2987        assert!(matches!(e, EvalError::BadSpecialForm { .. }));
2988    }
2989
2990    // ── Host context reachable via register_fn ────────────────────
2991
2992    #[test]
2993    fn native_fn_reads_host_state() {
2994        struct Counter {
2995            n: i64,
2996        }
2997        let forms = read_spanned("(bump) (bump) (bump) (cur)").unwrap();
2998        let mut i: Interpreter<Counter> = Interpreter::new();
2999        install_primitives(&mut i);
3000        i.register_fn(
3001            "bump",
3002            Arity::Exact(0),
3003            |_args: &[Value], host: &mut Counter, _span| {
3004                host.n += 1;
3005                Ok(Value::Int(host.n))
3006            },
3007        );
3008        i.register_fn(
3009            "cur",
3010            Arity::Exact(0),
3011            |_args: &[Value], host: &mut Counter, _span| Ok(Value::Int(host.n)),
3012        );
3013        let mut host = Counter { n: 0 };
3014        let v = i.eval_program(&forms, &mut host).unwrap();
3015        assert!(matches!(v, Value::Int(3)));
3016    }
3017
3018    // ── Typed FFI registration ────────────────────────────────────
3019
3020    struct Ctx {
3021        records: Vec<(String, i64)>,
3022    }
3023
3024    #[test]
3025    fn register_typed1_marshals_string_arg() {
3026        let mut i: Interpreter<Ctx> = Interpreter::new();
3027        install_primitives(&mut i);
3028        i.register_typed1("greet", |_h: &mut Ctx, name: String| -> Result<String> {
3029            Ok(format!("hello {name}"))
3030        });
3031        let forms = read_spanned(r#"(greet "luis")"#).unwrap();
3032        let mut h = Ctx { records: vec![] };
3033        let v = i.eval_program(&forms, &mut h).unwrap();
3034        match v {
3035            Value::Str(s) => assert_eq!(&*s, "hello luis"),
3036            other => panic!("{other:?}"),
3037        }
3038    }
3039
3040    #[test]
3041    fn register_typed2_marshals_host_state_mutation() {
3042        let mut i: Interpreter<Ctx> = Interpreter::new();
3043        install_primitives(&mut i);
3044        i.register_typed2(
3045            "record",
3046            |h: &mut Ctx, name: String, n: i64| -> Result<()> {
3047                h.records.push((name, n));
3048                Ok(())
3049            },
3050        );
3051        let forms = read_spanned(r#"(record "a" 1) (record "b" 2)"#).unwrap();
3052        let mut h = Ctx { records: vec![] };
3053        let _ = i.eval_program(&forms, &mut h).unwrap();
3054        assert_eq!(h.records.len(), 2);
3055        assert_eq!(h.records[0], ("a".to_string(), 1));
3056        assert_eq!(h.records[1], ("b".to_string(), 2));
3057    }
3058
3059    #[test]
3060    fn register_typed_arg_type_mismatch_surfaces_at_call_site() {
3061        let mut i: Interpreter<Ctx> = Interpreter::new();
3062        install_primitives(&mut i);
3063        i.register_typed1("needs-int", |_h: &mut Ctx, n: i64| -> Result<i64> {
3064            Ok(n + 1)
3065        });
3066        let forms = read_spanned(r#"(needs-int "not-a-number")"#).unwrap();
3067        let mut h = Ctx { records: vec![] };
3068        let err = i.eval_program(&forms, &mut h).unwrap_err();
3069        assert!(matches!(
3070            err,
3071            EvalError::TypeMismatch {
3072                expected: "integer",
3073                ..
3074            }
3075        ));
3076    }
3077
3078    #[test]
3079    fn register_typed3_three_args() {
3080        let mut i: Interpreter<Ctx> = Interpreter::new();
3081        install_primitives(&mut i);
3082        i.register_typed3(
3083            "triple-sum",
3084            |_h: &mut Ctx, a: i64, b: i64, c: i64| -> Result<i64> { Ok(a + b + c) },
3085        );
3086        let forms = read_spanned("(triple-sum 10 20 30)").unwrap();
3087        let mut h = Ctx { records: vec![] };
3088        let v = i.eval_program(&forms, &mut h).unwrap();
3089        assert!(matches!(v, Value::Int(60)));
3090    }
3091
3092    // ── User macros via defmacro ──────────────────────────────────
3093
3094    // ── expansion is BOUNDED ───────────────────────────────────────
3095    //
3096    // Measured before the bound existed: `(defmacro forever (x) `(forever
3097    // ,x))` followed by `(forever 1)` produced
3098    //
3099    //     thread 'main' has overflowed its stack
3100    //     fatal runtime error: stack overflow, aborting
3101    //
3102    // Uncatchable, and at BUILD time — a runaway macro took the compiler down
3103    // instead of failing the compilation. For a language whose stated aim is
3104    // safe metaprogramming, that is the worst place to have this hole.
3105
3106    /// **A self-referential macro is a typed error, and it names the macro.**
3107    #[test]
3108    fn a_runaway_macro_is_a_typed_error_that_names_the_macro() {
3109        let err = eval_err("(defmacro forever (x) `(forever ,x))\n(forever 1)");
3110        match err {
3111            EvalError::MacroExpansionLimit {
3112                ref macro_name,
3113                limit,
3114                ..
3115            } => {
3116                assert_eq!(&**macro_name, "forever", "the error must name the culprit");
3117                assert_eq!(limit, DEFAULT_MACRO_EXPANSION_LIMIT);
3118            }
3119            other => panic!("expected MacroExpansionLimit, got {other:?}"),
3120        }
3121    }
3122
3123    /// A mutually-recursive PAIR must also be caught. A guard that only
3124    /// noticed direct self-reference would miss the two-macro cycle, which is
3125    /// the form a real codebase actually produces.
3126    #[test]
3127    fn a_mutually_recursive_macro_pair_is_caught_too() {
3128        let err =
3129            eval_err("(defmacro ping (x) `(pong ,x))\n(defmacro pong (x) `(ping ,x))\n(ping 1)");
3130        assert!(
3131            matches!(err, EvalError::MacroExpansionLimit { .. }),
3132            "a two-macro cycle must be bounded as well: {err:?}"
3133        );
3134    }
3135
3136    /// **Anti-vacuity, and the reason the counter charges REWRITES rather
3137    /// than structural descent.** A deeply-nested but finite form is
3138    /// legitimate work: it terminates on its own, and bounding descent would
3139    /// reject honest programs while still letting a cycle run forever.
3140    ///
3141    /// 400 nesting levels is well past the 256 rewrite ceiling, so this fails
3142    /// if the budget is charged for descent.
3143    #[test]
3144    fn deep_but_finite_nesting_is_not_charged_to_the_expansion_budget() {
3145        let mut src = String::from("(defmacro id1 (x) x)\n");
3146        src.push_str(&"(+ 1 ".repeat(400));
3147        src.push_str("(id1 7)");
3148        src.push_str(&")".repeat(400));
3149        let v = eval_ok(&src);
3150        assert!(matches!(v, Value::Int(407)), "got {v:?}");
3151    }
3152
3153    /// A long but TERMINATING rewrite chain under the ceiling still works, so
3154    /// the bound rejects only what does not terminate.
3155    #[test]
3156    fn a_terminating_chain_under_the_ceiling_still_expands() {
3157        // step -> step2 -> plain code: three rewrites, far under 256.
3158        let v =
3159            eval_ok("(defmacro step (x) `(step2 ,x))\n(defmacro step2 (x) `(* ,x 3))\n(step 5)");
3160        assert!(matches!(v, Value::Int(15)), "got {v:?}");
3161    }
3162
3163    /// The ceiling is adjustable — a generator may legitimately chain further
3164    /// — but there is no way to remove it.
3165    #[test]
3166    fn the_expansion_ceiling_is_configurable() {
3167        let forms = read_spanned("(defmacro forever (x) `(forever ,x))\n(forever 1)").unwrap();
3168        let mut i: Interpreter<NoHost> = Interpreter::new();
3169        install_primitives(&mut i);
3170        i.set_macro_expansion_limit(4);
3171        match i.eval_program(&forms, &mut NoHost).unwrap_err() {
3172            EvalError::MacroExpansionLimit { limit, .. } => assert_eq!(limit, 4),
3173            other => panic!("expected MacroExpansionLimit, got {other:?}"),
3174        }
3175    }
3176
3177    #[test]
3178    fn user_macro_expands_and_evaluates() {
3179        let v = eval_ok(
3180            "(defmacro twice (x) `(* ,x 2))
3181             (twice 21)",
3182        );
3183        assert!(matches!(v, Value::Int(42)));
3184    }
3185
3186    #[test]
3187    fn user_macro_definition_returns_nil() {
3188        let v = eval_ok("(defmacro inc (x) `(+ ,x 1))");
3189        assert!(matches!(v, Value::Nil));
3190    }
3191
3192    #[test]
3193    fn user_macro_inside_define_body_expands() {
3194        // (define (f n) (inc n)) — the (inc n) call is rewritten to (+ n 1)
3195        // before define captures the body.
3196        let v = eval_ok(
3197            "(defmacro inc (x) `(+ ,x 1))
3198             (define (f n) (inc n))
3199             (f 41)",
3200        );
3201        assert!(matches!(v, Value::Int(42)));
3202    }
3203
3204    #[test]
3205    fn user_macro_with_rest_args_splices() {
3206        let v = eval_ok(
3207            "(defmacro sum-all (&rest xs) `(+ ,@xs))
3208             (sum-all 1 2 3 4 5)",
3209        );
3210        assert!(matches!(v, Value::Int(15)));
3211    }
3212
3213    #[test]
3214    fn nested_user_macros_compose() {
3215        let v = eval_ok(
3216            "(defmacro twice (x) `(* ,x 2))
3217             (defmacro quad (x) `(twice (twice ,x)))
3218             (quad 5)",
3219        );
3220        assert!(matches!(v, Value::Int(20)));
3221    }
3222
3223    #[test]
3224    fn user_macro_can_expand_to_special_form() {
3225        // Macros can expand into special forms — `if`, `let`, `lambda`,
3226        // `define` are all reachable as expansion targets.
3227        let v = eval_ok(
3228            "(defmacro guard (test then) `(if ,test ,then 0))
3229             (guard #t 99)",
3230        );
3231        assert!(matches!(v, Value::Int(99)));
3232    }
3233
3234    #[test]
3235    fn user_macro_redefined_replaces_prior_template() {
3236        let v = eval_ok(
3237            "(defmacro k () `1)
3238             (defmacro k () `2)
3239             (k)",
3240        );
3241        assert!(matches!(v, Value::Int(2)));
3242    }
3243
3244    #[test]
3245    fn user_macro_unbound_template_var_errors() {
3246        // ,y refers to a name not bound in the macro's parameter list
3247        // and not defined in the surrounding scope. Under the
3248        // full-eval expander this surfaces as a proper unbound-symbol
3249        // error at expansion time, with the offending symbol in the
3250        // payload — strictly better than the legacy "compile" error.
3251        let mut i: Interpreter<NoHost> = Interpreter::new();
3252        install_primitives(&mut i);
3253        let forms = read_spanned("(defmacro bad (x) `(list ,y)) (bad 1)").unwrap();
3254        let err = i.eval_program(&forms, &mut NoHost).unwrap_err();
3255        match err {
3256            EvalError::UnboundSymbol { name, .. } => assert_eq!(&*name, "y"),
3257            other => panic!("expected UnboundSymbol, got {other:?}"),
3258        }
3259    }
3260
3261    #[test]
3262    fn defpoint_template_keyword_registers_as_macro() {
3263        // `defpoint-template` is the typed-DSL spelling of `defmacro` —
3264        // the runtime should accept both.
3265        let v = eval_ok(
3266            "(defpoint-template double (x) `(* ,x 2))
3267             (double 7)",
3268        );
3269        assert!(matches!(v, Value::Int(14)));
3270    }
3271
3272    #[test]
3273    fn defcheck_keyword_registers_as_macro() {
3274        let v = eval_ok(
3275            "(defcheck always-7 () `7)
3276             (always-7)",
3277        );
3278        assert!(matches!(v, Value::Int(7)));
3279    }
3280
3281    #[test]
3282    fn macro_call_evaluated_with_runtime_arg() {
3283        // Macro arg is itself an expression — the substituted expression
3284        // is evaluated *after* expansion, so the arg's runtime value is
3285        // what reaches the expanded form.
3286        let v = eval_ok(
3287            "(defmacro double (x) `(+ ,x ,x))
3288             (define n 13)
3289             (double n)",
3290        );
3291        assert!(matches!(v, Value::Int(26)));
3292    }
3293
3294    #[test]
3295    fn macro_persists_across_eval_program_calls() {
3296        // The expander state outlives a single eval_program call — REPL
3297        // semantics rely on this.
3298        let mut i: Interpreter<NoHost> = Interpreter::new();
3299        install_primitives(&mut i);
3300        let mut host = NoHost;
3301        let defs = read_spanned("(defmacro inc (x) `(+ ,x 1))").unwrap();
3302        i.eval_program(&defs, &mut host).unwrap();
3303        assert_eq!(i.expander().len(), 1);
3304
3305        let call = read_spanned("(inc 41)").unwrap();
3306        let v = i.eval_program(&call, &mut host).unwrap();
3307        assert!(matches!(v, Value::Int(42)));
3308    }
3309
3310    #[test]
3311    fn macro_expansion_inside_lambda_body() {
3312        let v = eval_ok(
3313            "(defmacro sq (x) `(* ,x ,x))
3314             ((lambda (n) (sq n)) 9)",
3315        );
3316        assert!(matches!(v, Value::Int(81)));
3317    }
3318
3319    #[test]
3320    fn no_macros_registered_keeps_eval_program_a_passthrough() {
3321        // Sanity: with no macros registered, eval_program should still run
3322        // every existing test path correctly. Touching the same code as
3323        // the rest of the suite — this just asserts the optimization
3324        // we baked in (skip expand when expander is empty) didn't
3325        // accidentally drop forms.
3326        let v = eval_ok("(+ 1 2 3)");
3327        assert!(matches!(v, Value::Int(6)));
3328    }
3329
3330    #[test]
3331    fn eval_top_form_drives_one_form_at_a_time() {
3332        let mut i: Interpreter<NoHost> = Interpreter::new();
3333        install_primitives(&mut i);
3334        let mut host = NoHost;
3335        let forms = read_spanned("(defmacro id (x) `,x) (id 42)").unwrap();
3336
3337        // First form: registers, returns Nil.
3338        let r0 = i.eval_top_form(&forms[0], &mut host).unwrap();
3339        assert!(matches!(r0, Value::Nil));
3340
3341        // Second form: macro expanded → 42.
3342        let r1 = i.eval_top_form(&forms[1], &mut host).unwrap();
3343        assert!(matches!(r1, Value::Int(42)));
3344    }
3345
3346    // ── Full-eval macroexpansion power tests ──────────────────────
3347    //
3348    // These exercise the Racket/CL/Clojure-grade macro model: the
3349    // macro body is a regular Lisp program evaluated at expansion time
3350    // with full access to every primitive and library fn.
3351
3352    use crate::install_full_stdlib_with;
3353
3354    fn run_full(src: &str) -> Value {
3355        let mut i: Interpreter<NoHost> = Interpreter::new();
3356        install_full_stdlib_with(&mut i, &mut NoHost);
3357        let forms = read_spanned(src).unwrap();
3358        i.eval_program(&forms, &mut NoHost).unwrap()
3359    }
3360
3361    #[test]
3362    fn macro_can_use_map_at_expansion_time() {
3363        // The macro body uses (map ...) at expansion time to transform
3364        // each arg into a different form. Result: a `(list ...)` whose
3365        // children are the squared symbols' representations.
3366        let v = run_full(
3367            "(defmacro double-each (&rest xs)
3368               `(list ,@(map (lambda (x) (* x 2)) xs)))
3369             (double-each 1 2 3 4 5)",
3370        );
3371        assert_eq!(format!("{v}"), "(2 4 6 8 10)");
3372    }
3373
3374    #[test]
3375    fn macro_can_use_foldl_at_expansion_time() {
3376        // The expansion ITSELF is built by folding — the macro returns
3377        // a sum-of-args expression, but only after expansion-time
3378        // computation chooses the additive form.
3379        let v = run_full(
3380            "(defmacro static-sum (&rest xs)
3381               (foldl + 0 xs))
3382             (static-sum 1 2 3 4 5)",
3383        );
3384        assert!(matches!(v, Value::Int(15)));
3385    }
3386
3387    #[test]
3388    fn macro_can_use_filter_at_expansion_time() {
3389        // Macro args arrive as source-form Values: literals stay
3390        // literals, but `(- 4)` is a List not a negative number.
3391        // Use direct negative literals so the filter sees integers.
3392        let v = run_full(
3393            "(defmacro sum-positives (&rest xs)
3394               `(+ ,@(filter positive? xs)))
3395             (sum-positives 1 -2 3 -4 5)",
3396        );
3397        // Filter to (1 3 5) at expansion → emit (+ 1 3 5) → 9.
3398        assert!(matches!(v, Value::Int(9)));
3399    }
3400
3401    #[test]
3402    fn macro_can_recursively_emit_let_chain() {
3403        // (chain-let (a 1) (b 2) (c 3) body) →
3404        //   (let ((a 1)) (let ((b 2)) (let ((c 3)) body))).
3405        let v = run_full(
3406            "(defmacro chain-let (binding &rest more)
3407               (if (null? more)
3408                   `(let (,binding) #t)
3409                   `(let (,binding) (chain-let ,@more))))
3410             (chain-let (a 1) (b 2) (c 3))",
3411        );
3412        assert!(matches!(v, Value::Bool(true)));
3413    }
3414
3415    #[test]
3416    fn macro_can_use_gensym_for_hygiene() {
3417        // The macro introduces a fresh local binding via gensym, so
3418        // no name collision risk.
3419        let v = run_full(
3420            "(defmacro swap-bind (init body)
3421               (let ((tmp (gensym \"tmp\")))
3422                 `(let ((,tmp ,init))
3423                    (+ ,tmp ,tmp))))
3424             (swap-bind 21 #t)",
3425        );
3426        assert!(matches!(v, Value::Int(42)));
3427    }
3428
3429    #[test]
3430    fn macro_can_inspect_arg_shape() {
3431        // Detect whether the arg is a list and emit different code.
3432        let v = run_full(
3433            "(defmacro shape-aware (x)
3434               (if (list? x)
3435                   `(+ ,@x)         ;; sum the children
3436                   `,x))            ;; pass through scalars
3437             (+ (shape-aware (1 2 3)) (shape-aware 100))",
3438        );
3439        // (1 2 3) → 6; 100 → 100; total → 106.
3440        assert!(matches!(v, Value::Int(106)));
3441    }
3442
3443    #[test]
3444    fn macro_can_call_user_helper_fn() {
3445        // Define a helper at top level; macro body calls it at expand.
3446        let v = run_full(
3447            "(define (square x) (* x x))
3448             (defmacro static-square (n) (square n))
3449             (static-square 7)",
3450        );
3451        assert!(matches!(v, Value::Int(49)));
3452    }
3453
3454    #[test]
3455    fn macro_emitting_quoted_form_round_trips() {
3456        // A macro that produces a quoted constant — the (quote x)
3457        // representation must round-trip cleanly.
3458        let v = run_full(
3459            "(defmacro literal-list (&rest xs)
3460               `(quote ,xs))
3461             (literal-list a b c)",
3462        );
3463        let s = format!("{v}");
3464        assert!(s.contains('a') && s.contains('b') && s.contains('c'));
3465    }
3466
3467    #[test]
3468    fn quasiquote_inside_quasiquote_in_macro_output_is_preserved() {
3469        // A macro that emits a quasiquote at runtime — the runtime
3470        // should see a quasiquote and evaluate it.
3471        let v = run_full(
3472            "(defmacro emit-qq (x) `(quasiquote (a (unquote ,x) c)))
3473             (let ((q (emit-qq 99))) q)",
3474        );
3475        // Result is the runtime-value (a 99 c).
3476        assert_eq!(format!("{v}"), "(a 99 c)");
3477    }
3478
3479    #[test]
3480    fn macro_body_can_define_locals_and_dispatch() {
3481        // Macro body uses let + cond + map — full programmability.
3482        let v = run_full(
3483            "(defmacro classify-args (&rest xs)
3484               (let ((evens (filter even? xs))
3485                     (odds  (filter odd?  xs)))
3486                 `(list (list :evens ,@evens)
3487                        (list :odds  ,@odds))))
3488             (classify-args 1 2 3 4 5 6)",
3489        );
3490        let s = format!("{v}");
3491        assert!(s.contains(":evens 2 4 6"));
3492        assert!(s.contains(":odds 1 3 5"));
3493    }
3494
3495    // ── Tail-call optimization tests ──────────────────────────────
3496    //
3497    // These prove the trampoline catches the standard tail positions:
3498    // direct self-recursion through `if`, mutual recursion, deep
3499    // recursion through `cond`, `let`-body, and `begin`. Without TCO,
3500    // each would stack-overflow at ~10k frames; with TCO they run in
3501    // bounded space.
3502
3503    #[test]
3504    fn tco_self_recursion_via_if() {
3505        // Sum integers 1..n via accumulator. Tail call inside `if` else
3506        // branch. n=100_000 would overflow the default Rust stack
3507        // without TCO.
3508        let v = run_full(
3509            "(define (sum n acc)
3510               (if (= n 0)
3511                   acc
3512                   (sum (- n 1) (+ acc n))))
3513             (sum 100000 0)",
3514        );
3515        // n*(n+1)/2 = 5_000_050_000
3516        assert!(matches!(v, Value::Int(5_000_050_000)));
3517    }
3518
3519    #[test]
3520    fn tco_mutual_recursion() {
3521        // Two closures call each other in tail position. Trampoline
3522        // must support the closure swap.
3523        let v = run_full(
3524            "(define (even-r? n) (if (= n 0) #t (odd-r? (- n 1))))
3525             (define (odd-r?  n) (if (= n 0) #f (even-r? (- n 1))))
3526             (even-r? 50000)",
3527        );
3528        assert!(matches!(v, Value::Bool(true)));
3529    }
3530
3531    #[test]
3532    fn tco_via_cond_branch() {
3533        let v = run_full(
3534            "(define (countdown n)
3535               (cond
3536                 ((<= n 0) :done)
3537                 (else (countdown (- n 1)))))
3538             (countdown 50000)",
3539        );
3540        assert!(matches!(v, Value::Keyword(s) if &*s == "done"));
3541    }
3542
3543    #[test]
3544    fn tco_via_let_body() {
3545        // Tail call inside the BODY of a `let`. Trampoline must respect
3546        // that the let frame is on env when entering the call.
3547        let v = run_full(
3548            "(define (loop-let n)
3549               (let ((m (- n 1)))
3550                 (if (<= n 0) :done (loop-let m))))
3551             (loop-let 50000)",
3552        );
3553        assert!(matches!(v, Value::Keyword(s) if &*s == "done"));
3554    }
3555
3556    #[test]
3557    fn tco_via_begin_last_form() {
3558        let v = run_full(
3559            "(define (counter n)
3560               (begin
3561                 (+ 1 1)
3562                 (+ 2 2)
3563                 (if (<= n 0) :done (counter (- n 1)))))
3564             (counter 50000)",
3565        );
3566        assert!(matches!(v, Value::Keyword(s) if &*s == "done"));
3567    }
3568
3569    #[test]
3570    fn tco_via_when_unless() {
3571        let v = run_full(
3572            "(define (drain n)
3573               (when (> n 0)
3574                 (drain (- n 1))))
3575             (drain 50000)",
3576        );
3577        // when's else branch returns nil; here recurses inside.
3578        assert!(matches!(v, Value::Nil));
3579    }
3580
3581    #[test]
3582    fn tco_through_and_or_short_circuit_last() {
3583        // `and` returns the last value if all are truthy. The last form
3584        // is in tail position.
3585        let v = run_full(
3586            "(define (loop-and n)
3587               (and #t #t (if (<= n 0) :done (loop-and (- n 1)))))
3588             (loop-and 30000)",
3589        );
3590        assert!(matches!(v, Value::Keyword(s) if &*s == "done"));
3591    }
3592
3593    #[test]
3594    fn non_tail_recursion_still_works_for_small_n() {
3595        // Non-tail recursion: (* n (fact (- n 1))) — the multiply
3596        // happens AFTER the recursive call returns, so it's not a tail
3597        // call. Should still work for moderate n via the regular stack.
3598        let v = run_full(
3599            "(define (fact n)
3600               (if (= n 0) 1 (* n (fact (- n 1)))))
3601             (fact 12)",
3602        );
3603        // 12! = 479_001_600
3604        assert!(matches!(v, Value::Int(479_001_600)));
3605    }
3606
3607    // ── Structured errors / try / catch ────────────────────────────
3608
3609    #[test]
3610    fn error_constructor_returns_error_value() {
3611        let v = run_full("(error :validation \"bad input\")");
3612        match v {
3613            Value::Error(e) => {
3614                assert_eq!(&*e.tag, "validation");
3615                assert_eq!(&*e.message, "bad input");
3616                assert!(e.data.is_empty());
3617            }
3618            other => panic!("{other:?}"),
3619        }
3620    }
3621
3622    #[test]
3623    fn ex_info_uses_default_tag() {
3624        let v = run_full("(ex-info \"validation failed\" (list :field \"email\" :code 42))");
3625        match v {
3626            Value::Error(e) => {
3627                assert_eq!(&*e.tag, "ex-info");
3628                assert_eq!(&*e.message, "validation failed");
3629                assert_eq!(e.data.len(), 2);
3630            }
3631            other => panic!("{other:?}"),
3632        }
3633    }
3634
3635    #[test]
3636    fn error_predicate() {
3637        let v = run_full("(error? (error :x \"y\"))");
3638        assert!(matches!(v, Value::Bool(true)));
3639        let v = run_full("(error? 42)");
3640        assert!(matches!(v, Value::Bool(false)));
3641    }
3642
3643    #[test]
3644    fn error_accessors() {
3645        let v = run_full(
3646            "(let ((e (ex-info \"oops\" (list :user-id 42))))
3647               (list (error-tag e) (error-message e) (error-data-get e :user-id)))",
3648        );
3649        assert_eq!(format!("{v}"), "(:ex-info \"oops\" 42)");
3650    }
3651
3652    #[test]
3653    fn try_catches_thrown_error() {
3654        let v = run_full(
3655            "(try
3656               (throw (ex-info \"boom\" (list :code 500)))
3657               (catch (e)
3658                 (error-message e)))",
3659        );
3660        assert_eq!(format!("{v}"), "\"boom\"");
3661    }
3662
3663    #[test]
3664    fn try_returns_body_value_when_no_throw() {
3665        let v = run_full(
3666            "(try
3667               (+ 1 2 3)
3668               (catch (e) :unreachable))",
3669        );
3670        assert!(matches!(v, Value::Int(6)));
3671    }
3672
3673    #[test]
3674    fn try_catches_runtime_errors_too() {
3675        // Division by zero is a Rust-side EvalError, not a user throw.
3676        // The catch handler should still observe it (wrapped to
3677        // Value::Error with tag :division-by-zero).
3678        let v = run_full(
3679            "(try
3680               (/ 1 0)
3681               (catch (e) (error-tag e)))",
3682        );
3683        assert!(matches!(v, Value::Keyword(s) if &*s == "division-by-zero"));
3684    }
3685
3686    #[test]
3687    fn try_catches_unbound_symbol_error() {
3688        let v = run_full(
3689            "(try
3690               undefined-var
3691               (catch (e) (error-tag e)))",
3692        );
3693        assert!(matches!(v, Value::Keyword(s) if &*s == "unbound-symbol"));
3694    }
3695
3696    #[test]
3697    fn try_catches_arity_mismatch() {
3698        let v = run_full(
3699            "(try
3700               ((lambda (x y) (+ x y)) 1)
3701               (catch (e) (error-tag e)))",
3702        );
3703        assert!(matches!(v, Value::Keyword(s) if &*s == "arity-mismatch"));
3704    }
3705
3706    #[test]
3707    fn nested_try_inner_handler_takes_precedence() {
3708        let v = run_full(
3709            "(try
3710               (try
3711                 (throw (ex-info \"inner\" ()))
3712                 (catch (e) :inner-caught))
3713               (catch (e) :outer-caught))",
3714        );
3715        assert!(matches!(v, Value::Keyword(s) if &*s == "inner-caught"));
3716    }
3717
3718    #[test]
3719    fn outer_try_catches_when_handler_rethrows() {
3720        let v = run_full(
3721            "(try
3722               (try
3723                 (throw (ex-info \"first\" ()))
3724                 (catch (e) (throw (ex-info \"rethrown\" ()))))
3725               (catch (e) (error-message e)))",
3726        );
3727        assert_eq!(format!("{v}"), "\"rethrown\"");
3728    }
3729
3730    #[test]
3731    fn throw_propagates_when_no_try() {
3732        // Without try, throw bubbles up as EvalError::User.
3733        let mut i: Interpreter<NoHost> = Interpreter::new();
3734        install_full_stdlib_with(&mut i, &mut NoHost);
3735        let forms = read_spanned("(throw (ex-info \"unhandled\" (list :code 99)))").unwrap();
3736        let err = i.eval_program(&forms, &mut NoHost).unwrap_err();
3737        match err {
3738            EvalError::User { value, .. } => match value {
3739                Value::Error(e) => {
3740                    assert_eq!(&*e.message, "unhandled");
3741                }
3742                other => panic!("{other:?}"),
3743            },
3744            other => panic!("{other:?}"),
3745        }
3746    }
3747
3748    // ── macroexpand-1 / macroexpand introspection ─────────────────
3749
3750    #[test]
3751    fn macroexpand_one_step() {
3752        let v = run_full(
3753            "(defmacro twice (x) `(* ,x 2))
3754             (macroexpand-1 '(twice 7))",
3755        );
3756        // Single step: (twice 7) → (* 7 2)
3757        assert_eq!(format!("{v}"), "(* 7 2)");
3758    }
3759
3760    #[test]
3761    fn macroexpand_full_until_fixed_point() {
3762        let v = run_full(
3763            "(defmacro twice (x) `(* ,x 2))
3764             (defmacro quad (x) `(twice (twice ,x)))
3765             (macroexpand '(quad 5))",
3766        );
3767        // (quad 5) → (twice (twice 5)) → (twice (* 5 2)) → (* (* 5 2) 2)
3768        assert_eq!(format!("{v}"), "(* (* 5 2) 2)");
3769    }
3770
3771    #[test]
3772    fn macroexpand_returns_unchanged_for_non_macro() {
3773        let v = run_full("(macroexpand-1 '(+ 1 2 3))");
3774        // + isn't a macro — passes through.
3775        assert_eq!(format!("{v}"), "(+ 1 2 3)");
3776    }
3777
3778    #[test]
3779    fn macroexpand_one_does_not_recurse_into_children() {
3780        // Only the head is expanded one level. Inner macro calls remain.
3781        let v = run_full(
3782            "(defmacro twice (x) `(* ,x 2))
3783             (defmacro outer (x) `(list ,x))
3784             (macroexpand-1 '(outer (twice 3)))",
3785        );
3786        // (outer (twice 3)) → (list (twice 3))   — inner macro NOT expanded.
3787        assert_eq!(format!("{v}"), "(list (twice 3))");
3788    }
3789
3790    #[test]
3791    fn macroexpand_recurses_into_children() {
3792        let v = run_full(
3793            "(defmacro twice (x) `(* ,x 2))
3794             (defmacro outer (x) `(list ,x))
3795             (macroexpand '(outer (twice 3)))",
3796        );
3797        // Full expansion expands inner: (list (* 3 2))
3798        assert_eq!(format!("{v}"), "(list (* 3 2))");
3799    }
3800
3801    // ── Module system: provide / require / qualified names ────────
3802
3803    fn run_with_modules(modules: &[(&str, &str)], src: &str) -> Value {
3804        use crate::module::MapLoader;
3805        let mut i: Interpreter<NoHost> = Interpreter::new();
3806        install_full_stdlib_with(&mut i, &mut NoHost);
3807        let mut loader = MapLoader::new();
3808        for (path, source) in modules {
3809            loader.insert(*path, *source);
3810        }
3811        i.set_loader(Arc::new(loader));
3812        let forms = read_spanned(src).unwrap();
3813        i.eval_program(&forms, &mut NoHost).unwrap()
3814    }
3815
3816    fn run_with_modules_err(modules: &[(&str, &str)], src: &str) -> EvalError {
3817        use crate::module::MapLoader;
3818        let mut i: Interpreter<NoHost> = Interpreter::new();
3819        install_full_stdlib_with(&mut i, &mut NoHost);
3820        let mut loader = MapLoader::new();
3821        for (path, source) in modules {
3822            loader.insert(*path, *source);
3823        }
3824        i.set_loader(Arc::new(loader));
3825        let forms = read_spanned(src).unwrap();
3826        i.eval_program(&forms, &mut NoHost).unwrap_err()
3827    }
3828
3829    #[test]
3830    fn require_with_explicit_alias_imports_qualified_names() {
3831        let v = run_with_modules(
3832            &[(
3833                "lib/math",
3834                "(define square (lambda (x) (* x x)))
3835                 (define cube (lambda (x) (* x x x)))
3836                 (provide square cube)",
3837            )],
3838            "(require \"lib/math\" :as math)
3839             (math/square 7)",
3840        );
3841        assert!(matches!(v, Value::Int(49)));
3842    }
3843
3844    #[test]
3845    fn require_uses_path_as_default_alias() {
3846        let v = run_with_modules(
3847            &[(
3848                "lib/math",
3849                "(define double (lambda (x) (* x 2))) (provide double)",
3850            )],
3851            "(require \"lib/math\")
3852             (lib/math/double 21)",
3853        );
3854        // No explicit :as alias → bound under the path itself, so
3855        // `lib/math/double` is the qualified name.
3856        assert!(matches!(v, Value::Int(42)));
3857    }
3858
3859    #[test]
3860    fn require_refer_imports_unqualified_names() {
3861        let v = run_with_modules(
3862            &[(
3863                "lib/math",
3864                "(define square (lambda (x) (* x x)))
3865                 (define cube (lambda (x) (* x x x)))
3866                 (provide square cube)",
3867            )],
3868            "(require \"lib/math\" :refer (square))
3869             (square 6)",
3870        );
3871        assert!(matches!(v, Value::Int(36)));
3872    }
3873
3874    #[test]
3875    fn require_does_not_import_non_provided() {
3876        // `private` is defined but NOT provided — should not be
3877        // accessible from the importing module.
3878        let err = run_with_modules_err(
3879            &[(
3880                "lib/secret",
3881                "(define public 1)
3882                 (define private 2)
3883                 (provide public)",
3884            )],
3885            "(require \"lib/secret\" :as s)
3886             s/private",
3887        );
3888        match err {
3889            EvalError::UnboundSymbol { name, .. } => assert_eq!(&*name, "s/private"),
3890            other => panic!("{other:?}"),
3891        }
3892    }
3893
3894    #[test]
3895    fn require_chain_a_imports_b() {
3896        let v = run_with_modules(
3897            &[
3898                (
3899                    "lib/util",
3900                    "(define inc1 (lambda (n) (+ n 1)))
3901                     (provide inc1)",
3902                ),
3903                (
3904                    "lib/wrapper",
3905                    "(require \"lib/util\" :as u)
3906                     (define inc2 (lambda (n) (u/inc1 (u/inc1 n))))
3907                     (provide inc2)",
3908                ),
3909            ],
3910            "(require \"lib/wrapper\" :as w)
3911             (w/inc2 10)",
3912        );
3913        assert!(matches!(v, Value::Int(12)));
3914    }
3915
3916    #[test]
3917    fn require_module_not_found() {
3918        let err = run_with_modules_err(&[], "(require \"missing/module\")");
3919        // Surfaces as a Value::Error inside EvalError::User.
3920        match err {
3921            EvalError::User { value, .. } => match value {
3922                Value::Error(e) => {
3923                    assert_eq!(&*e.tag, "module-not-found");
3924                    assert!(e.message.contains("missing/module"));
3925                }
3926                other => panic!("{other:?}"),
3927            },
3928            other => panic!("{other:?}"),
3929        }
3930    }
3931
3932    #[test]
3933    fn circular_require_detected() {
3934        let err = run_with_modules_err(
3935            &[
3936                ("a", "(require \"b\") (provide x) (define x 1)"),
3937                ("b", "(require \"a\") (provide y) (define y 2)"),
3938            ],
3939            "(require \"a\")",
3940        );
3941        match err {
3942            EvalError::User { value, .. } => match value {
3943                Value::Error(e) => assert_eq!(&*e.tag, "circular-require"),
3944                other => panic!("{other:?}"),
3945            },
3946            other => panic!("{other:?}"),
3947        }
3948    }
3949
3950    #[test]
3951    fn provide_at_top_level_errors() {
3952        // Without being inside a require, (provide ...) is meaningless.
3953        let mut i: Interpreter<NoHost> = Interpreter::new();
3954        install_full_stdlib_with(&mut i, &mut NoHost);
3955        let forms = read_spanned("(provide x)").unwrap();
3956        let err = i.eval_program(&forms, &mut NoHost).unwrap_err();
3957        assert!(matches!(err, EvalError::BadSpecialForm { form, .. } if &*form == "provide"));
3958    }
3959
3960    #[test]
3961    fn require_refer_unknown_name_errors() {
3962        let err = run_with_modules_err(
3963            &[(
3964                "lib/math",
3965                "(define square (lambda (x) (* x x))) (provide square)",
3966            )],
3967            "(require \"lib/math\" :refer (square cube))",
3968        );
3969        match err {
3970            EvalError::User { value, .. } => match value {
3971                Value::Error(e) => {
3972                    assert!(matches!(&*e.tag, "not-defined" | "not-exported"));
3973                }
3974                other => panic!("{other:?}"),
3975            },
3976            other => panic!("{other:?}"),
3977        }
3978    }
3979
3980    #[test]
3981    fn require_caches_module_load_once() {
3982        let v = run_with_modules(
3983            &[("lib/foo", "(define x 42) (provide x)")],
3984            "(require \"lib/foo\" :as a)
3985             (require \"lib/foo\" :as b)
3986             (+ a/x b/x)",
3987        );
3988        // Both alias to the same cached module.
3989        assert!(matches!(v, Value::Int(84)));
3990    }
3991
3992    // ---- macro-phase seal ------------------------------------------
3993    //
3994    // Measured before this landed: `(define *g* 0)` `(defmacro leak ()
3995    // (set! *g* 99))` `(leak)` `*g*` returned Int(99) — a macro body wrote
3996    // the interpreter's globals, and the runtime program could read it.
3997    // The mechanism was that `Env::set` takes `&self` and mutates through
3998    // `Arc`-shared frames, so cloning the env shared them.
3999
4000    #[test]
4001    fn macro_body_cannot_set_a_global() {
4002        let mut interp = Interpreter::new();
4003        install_primitives(&mut interp);
4004        let src = "(define *g* 0) (defmacro leak () (set! *g* 99)) (leak)";
4005        let forms = tatara_lisp::read_spanned(src).expect("parse");
4006        let err = interp
4007            .eval_program(&forms, &mut ())
4008            .expect_err("a macro must not be able to set! a global");
4009        let msg = format!("{err}");
4010        assert!(
4011            msg.contains("sealed") || msg.contains("cannot `set!`"),
4012            "expected a sealed-write diagnostic, got: {msg}"
4013        );
4014    }
4015
4016    #[test]
4017    fn the_global_is_actually_unchanged_after_a_refused_macro_set() {
4018        let mut interp = Interpreter::new();
4019        install_primitives(&mut interp);
4020        let forms = tatara_lisp::read_spanned("(define *g* 0) (defmacro leak () (set! *g* 99))")
4021            .expect("parse");
4022        interp.eval_program(&forms, &mut ()).expect("setup");
4023        // The expansion fails; the global must still read 0.
4024        let call = tatara_lisp::read_spanned("(leak)").expect("parse");
4025        let _ = interp.eval_program(&call, &mut ());
4026        let read = tatara_lisp::read_spanned("*g*").expect("parse");
4027        let v = interp.eval_program(&read, &mut ()).expect("read *g*");
4028        assert!(
4029            matches!(v, Value::Int(0)),
4030            "global was mutated by a macro body despite the seal: {v:?}"
4031        );
4032    }
4033
4034    /// Anti-vacuity: the seal must not break ORDINARY `set!`. If it did,
4035    /// the tests above would pass for the wrong reason.
4036    #[test]
4037    fn ordinary_set_still_works_at_runtime() {
4038        let mut interp = Interpreter::new();
4039        install_primitives(&mut interp);
4040        let forms = tatara_lisp::read_spanned("(define x 1) (set! x 42) x").expect("parse");
4041        let v = interp.eval_program(&forms, &mut ()).expect("runtime set!");
4042        assert!(matches!(v, Value::Int(42)), "got {v:?}");
4043    }
4044
4045    /// A macro body may still `define` and `set!` its OWN locals — the
4046    /// seal only blocks reaching outward.
4047    #[test]
4048    fn macro_body_can_mutate_its_own_locals() {
4049        let mut interp = Interpreter::new();
4050        install_primitives(&mut interp);
4051        let src = "(defmacro m () (begin (define n 1) (set! n 2) n)) (m)";
4052        let forms = tatara_lisp::read_spanned(src).expect("parse");
4053        let v = interp
4054            .eval_program(&forms, &mut ())
4055            .expect("a macro must be able to mutate its own locals");
4056        assert!(matches!(v, Value::Int(2)), "got {v:?}");
4057    }
4058}