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