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