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