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