Skip to main content

cljrs_runtime/interp/
special.rs

1//! Special form evaluators.
2
3use std::collections::{HashMap, HashSet};
4use std::sync::Arc;
5
6use crate::builtins::form::{
7    expand_pairs, expand_reader_conds, expand_reader_conds_cow, form_to_value, resolve_auto_forms,
8    select_reader_cond,
9};
10use crate::env::env::{Env, RequireRefer, RequireSpec};
11use crate::env::error::{EvalError, EvalResult};
12use crate::env::loader::load_ns;
13use crate::interp::destructure::bind_pattern;
14use crate::interp::eval::{eval, eval_body, is_special_form};
15use cljrs_gc::GcPtr;
16use cljrs_reader::Form;
17use cljrs_reader::form::FormKind;
18use cljrs_value::error::ExceptionInfo;
19use cljrs_value::{
20    CljxFn, CljxFnArity, CljxFuture, FutureState, Keyword, MapValue, Protocol, ProtocolMethod,
21    ReferClojureFilter, TypeHint, Value, ValueError,
22};
23
24/// Dispatch to the right special-form handler.
25pub fn eval_special(head: &str, args: &[Form], env: &mut Env) -> EvalResult {
26    crate::env::policy::check_special(head)?;
27    match head {
28        "def" => eval_def(args, env),
29        "fn*" | "fn" => eval_fn(args, env),
30        "if" => eval_if(args, env),
31        "do" => eval_body(args, env),
32        "let*" | "let" => eval_let(args, env),
33        "loop*" | "loop" => eval_loop(args, env),
34        "recur" => eval_recur(args, env),
35        "quote" => eval_quote(args, env),
36        "var" => eval_var(args, env),
37        "set!" => eval_set_bang(args, env),
38        "throw" => eval_throw(args, env),
39        "try" => eval_try(args, env),
40        "defn" => eval_defn(args, env, false),
41        "defn-" => eval_defn(args, env, true),
42        "defmacro" => eval_defmacro(args, env),
43        "defonce" => eval_defonce(args, env),
44        "and" => eval_and(args, env),
45        "or" => eval_or(args, env),
46        "." => Err(EvalError::Runtime("interop not yet implemented".into())),
47        "ns" => eval_ns(args, env),
48        "require" => eval_require(args, env),
49        "letfn" => eval_letfn(args, env),
50        "in-ns" => eval_in_ns(args, env),
51        "alias" => eval_alias(args, env),
52        "protocol*" => eval_protocol_star(args, env),
53        "deftype*" => eval_deftype_star(args, env),
54        "load-file" => eval_load_file(args, env),
55        "binding" => eval_binding(args, env),
56        "with-out-str" => eval_with_out_str(args, env),
57        "await" => eval_await(args, env),
58        _ => unreachable!("unknown special form: {head}"),
59    }
60}
61
62// ── def ───────────────────────────────────────────────────────────────────────
63
64fn eval_def(args: &[Form], env: &mut Env) -> EvalResult {
65    let target = parse_def(args, env)?;
66    let val = match target.value_form {
67        Some(form) => {
68            // Under no-gc: def value expressions go to the StaticArena since the
69            // Var must outlive all scratch regions.
70            #[cfg(feature = "no-gc")]
71            let _static_ctx = cljrs_gc::alloc_ctx::StaticCtxGuard::new();
72            eval(form, env)?
73        }
74        None => Value::Nil,
75    };
76    intern_def(target, val, env)
77}
78
79/// A parsed `(def name "doc"? value?)`: everything but the value itself.
80///
81/// Public, together with [`parse_def`] and [`intern_def`], so the async
82/// evaluator (`cljrs-async`) can evaluate `value_form` with a yielding
83/// evaluator and still share the rest of `def`.
84pub struct DefTarget<'a> {
85    pub name: String,
86    /// Metadata from `^meta` on the name and the docstring, merged.
87    pub meta: Option<Value>,
88    /// The value expression; `None` for `(def name)`.
89    pub value_form: Option<&'a Form>,
90}
91
92/// Parse the name, metadata and docstring of a `def` without evaluating its
93/// value expression. `^{...}` metadata on the name *is* evaluated here.
94pub fn parse_def<'a>(args: &'a [Form], env: &mut Env) -> EvalResult<DefTarget<'a>> {
95    if args.is_empty() {
96        return Err(EvalError::Runtime("def requires a name".into()));
97    }
98    let (name, meta_opt) = extract_def_name(&args[0], env)?;
99    // Optional docstring: (def name "docstring" value)
100    let (docstring, val_idx) = if args.len() > 2
101        && let FormKind::Str(s) = &args[1].kind
102    {
103        (Some(s.clone()), 2)
104    } else {
105        (None, 1)
106    };
107    Ok(DefTarget {
108        name,
109        meta: merge_meta(meta_opt, docstring.as_deref().map(doc_meta)),
110        value_form: args.get(val_idx),
111    })
112}
113
114/// Intern `val` under a parsed `def` target in the current namespace.
115pub fn intern_def(target: DefTarget<'_>, val: Value, env: &mut Env) -> EvalResult {
116    let var = env
117        .globals
118        .intern(&env.current_ns, Arc::from(target.name.as_str()), val);
119    if let Some(meta_val) = target.meta {
120        var.get().set_meta(meta_val);
121    }
122    Ok(Value::Var(var))
123}
124
125/// The already-bound var a `defonce` named `name` would leave untouched, if
126/// any.
127pub fn defonce_existing(name: &str, env: &Env) -> Option<Value> {
128    env.globals
129        .lookup_var(&env.current_ns, name)
130        .filter(|var| var.get().is_bound())
131        .map(Value::Var)
132}
133
134/// Build a `{:doc "..."}` metadata map fragment for a docstring.
135fn doc_meta(doc: &str) -> Value {
136    Value::Map(MapValue::empty().assoc(
137        Value::keyword(Keyword::parse("doc")),
138        Value::string(doc.to_string()),
139    ))
140}
141
142fn private_meta() -> Value {
143    Value::Map(
144        MapValue::empty().assoc(Value::keyword(Keyword::parse("private")), Value::Bool(true)),
145    )
146}
147
148/// Build a `{:arglists ([x] [x y & more])}` metadata fragment from a
149/// `Value::Fn`/`Value::Macro`'s parsed arities. `skip` elides leading fixed
150/// params that aren't part of the public signature (defmacro's implicit
151/// `&form`/`&env`).
152fn arglists_meta(fn_val: &Value, skip: usize) -> Option<Value> {
153    let arities = match fn_val {
154        Value::Fn(f) => &f.get().arities,
155        Value::Macro(f) => &f.get().arities,
156        _ => return None,
157    };
158    let lists: Vec<Value> = arities
159        .iter()
160        .map(|a| {
161            let mut syms: Vec<Value> = a
162                .params
163                .iter()
164                .skip(skip)
165                .map(|p| Value::symbol(cljrs_value::Symbol::simple(p.as_ref())))
166                .collect();
167            if let Some(rest) = &a.rest_param {
168                syms.push(Value::symbol(cljrs_value::Symbol::simple("&")));
169                syms.push(Value::symbol(cljrs_value::Symbol::simple(rest.as_ref())));
170            }
171            Value::Vector(GcPtr::new(cljrs_value::PersistentVector::from_iter(syms)))
172        })
173        .collect();
174    Some(Value::Map(MapValue::empty().assoc(
175        Value::keyword(Keyword::parse("arglists")),
176        Value::Vector(GcPtr::new(cljrs_value::PersistentVector::from_iter(lists))),
177    )))
178}
179
180/// Extract the def name and optional metadata from the name form.
181fn extract_def_name(form: &Form, env: &mut Env) -> EvalResult<(String, Option<Value>)> {
182    match &form.kind {
183        FormKind::Symbol(s) => Ok((s.clone(), None)),
184        // `^:a ^:b x` nests `Meta` forms; unwrap all, outer mark winning.
185        FormKind::Meta(meta_form, inner) => {
186            let meta_val = compile_meta_form(meta_form, env)?;
187            let (name, inner_meta) = extract_def_name(inner, env)?;
188            Ok((name, merge_meta(inner_meta, Some(meta_val))))
189        }
190        _ => Err(EvalError::Runtime("def name must be a symbol".into())),
191    }
192}
193
194/// Expand a metadata shorthand form into a map value, evaluating the general
195/// case (`^{:x (+ 1 2)}` → `{:x 3}`).
196///
197/// The shorthand table itself lives in [`crate::builtins::form`] and is shared
198/// with the quoted position, which differs only in resolving that general case
199/// as data instead.
200pub fn compile_meta_form(meta: &Form, env: &mut Env) -> EvalResult<Value> {
201    crate::builtins::form::expand_meta_annotation(meta, &mut |f| eval(f, env))
202}
203
204// ── fn* ───────────────────────────────────────────────────────────────────────
205
206fn eval_fn(args: &[Form], env: &mut Env) -> EvalResult {
207    // Peel any leading `^meta` wrappers, e.g. `(fn ^:async [..] ..)` or
208    // `(fn ^:async name [..] ..)`, recording whether `:async` was requested.
209    let mut is_async = false;
210    let peeled: Vec<Form>;
211    let args: &[Form] = if matches!(args.first().map(|f| &f.kind), Some(FormKind::Meta(..))) {
212        let (metas, head) = args[0].peel_meta();
213        is_async |= metas.iter().any(|m| m.requests_async());
214        peeled = std::iter::once(head.clone())
215            .chain(args[1..].iter().cloned())
216            .collect();
217        &peeled
218    } else {
219        args
220    };
221
222    let mut idx = 0;
223    let mut name: Option<Arc<str>> = None;
224
225    // Optional name.
226    if let Some(FormKind::Symbol(s)) = args.first().map(|f| &f.kind)
227        && !is_special_form(s)
228    {
229        name = Some(Arc::from(s.as_str()));
230        idx = 1;
231    }
232
233    let rest = &args[idx..];
234    if rest.is_empty() {
235        return Err(EvalError::Runtime("fn* requires params and body".into()));
236    }
237
238    // A `^hint` on the params vector or on an arity clause is advisory — the
239    // structural shape is the form underneath it.
240    let arities = match &rest[0].unmeta().kind {
241        FormKind::Vector(_) => {
242            // Single arity: (fn* [params] body...)
243            vec![parse_arity(&rest[0], &rest[1..])?]
244        }
245        FormKind::List(_) => {
246            // Multi-arity: (fn* ([params] body...) ...)
247            rest.iter()
248                .map(|arity_form| {
249                    if let Some(forms) = arity_form.as_list() {
250                        if forms.is_empty() {
251                            return Err(EvalError::Runtime("arity clause requires params".into()));
252                        }
253                        parse_arity(&forms[0], &forms[1..])
254                    } else {
255                        Err(EvalError::Runtime("expected arity clause (list)".into()))
256                    }
257                })
258                .collect::<EvalResult<Vec<_>>>()?
259        }
260        _ => {
261            return Err(EvalError::Runtime(
262                "fn* expects vector or arity clauses".into(),
263            ));
264        }
265    };
266
267    // Capture closed-over locals.
268    let (closed_over_names, closed_over_vals) = env.all_local_bindings();
269
270    let mut cljrs_fn = CljxFn::new(
271        name.clone(),
272        arities,
273        closed_over_names,
274        closed_over_vals,
275        false,
276        Arc::clone(&env.current_ns),
277    );
278    cljrs_fn.is_async = is_async;
279
280    env.on_fn_defined(&cljrs_fn);
281
282    // Eagerly lower each arity to IR if the compiler is ready.
283    //eager_lower_fn(&cljrs_fn, env);
284
285    let mut ptr = GcPtr::new(cljrs_fn);
286    // For named anonymous functions (fn g ...), store a back-pointer so that
287    // the self-reference returned from the body is pointer-equal to the outer
288    // binding — required for `(= f (f))` to be `true`.
289    if ptr.get().name.is_some() {
290        let self_clone = ptr.clone();
291        ptr.get_mut().self_ptr = Some(self_clone);
292    }
293    Ok(Value::Fn(ptr))
294}
295
296/// Parse one arity: params-form and body forms.
297pub fn parse_arity(params_form: &Form, body: &[Form]) -> EvalResult<CljxFnArity> {
298    let Some(param_forms) = params_form.as_vector() else {
299        return Err(EvalError::Runtime(
300            "fn arity params must be a vector".into(),
301        ));
302    };
303
304    let mut params: Vec<Arc<str>> = Vec::new();
305    let mut param_hints: Vec<Option<TypeHint>> = Vec::new();
306    let mut rest_param: Option<Arc<str>> = None;
307    let mut rest_hint: Option<TypeHint> = None;
308    let mut destructure_params: Vec<(usize, Form)> = Vec::new();
309    let mut destructure_rest: Option<Form> = None;
310    let mut saw_amp = false;
311
312    for p in param_forms {
313        // Peel a leading `^hint` (e.g. `^long x`, `^doubles a`) from the param,
314        // resolving the `:tag` to a primitive `TypeHint`.  Unknown / non-primitive
315        // tags resolve to `None` and are simply ignored (Clojure treats them as
316        // advisory).  The unwrapped form keeps the existing symbol/destructure
317        // handling unchanged.
318        let (hint, p) = peel_param_hint(p);
319        match &p.kind {
320            FormKind::Symbol(s) if s == "&" => {
321                saw_amp = true;
322            }
323            FormKind::Symbol(s) => {
324                if saw_amp {
325                    rest_param = Some(Arc::from(s.as_str()));
326                    rest_hint = hint;
327                    break;
328                } else {
329                    params.push(Arc::from(s.as_str()));
330                    param_hints.push(hint);
331                }
332            }
333            // Destructuring patterns: vectors and maps
334            FormKind::Vector(_) | FormKind::Map(_) => {
335                if saw_amp {
336                    let gensym = format!("__destructure_rest_{}", params.len());
337                    rest_param = Some(Arc::from(gensym.as_str()));
338                    destructure_rest = Some(p.clone());
339                    break;
340                } else {
341                    let idx = params.len();
342                    let gensym = format!("__destructure_{idx}");
343                    params.push(Arc::from(gensym.as_str()));
344                    // Destructured params can't be primitive-tagged.
345                    param_hints.push(None);
346                    destructure_params.push((idx, p.clone()));
347                }
348            }
349            _ => {
350                return Err(EvalError::Runtime(
351                    "fn params must be symbols, vectors, or maps".into(),
352                ));
353            }
354        }
355    }
356
357    let body = desugar_pre_post_conditions(body);
358
359    Ok(CljxFnArity {
360        params,
361        rest_param,
362        body,
363        destructure_params,
364        destructure_rest,
365        ir_arity_id: crate::interp::arity::fresh_arity_id(),
366        param_hints,
367        rest_hint,
368    })
369}
370
371/// Desugar a `:pre`/`:post` conditions map at the head of a function body.
372///
373/// Clojure binds `%` to the return value inside `:post` conditions. This
374/// transforms the raw body forms into equivalent assertion code so the
375/// interpreter does not need to handle conditions separately at call time.
376///
377/// Input body (simplified):
378/// ```text
379/// [{:pre [(pos? x)] :post [(pos? %)]} (inc x)]
380/// ```
381/// Output body:
382/// ```text
383/// [(assert (pos? x))
384///  (let* [% (inc x)]
385///    (assert (pos? %))
386///    %)]
387/// ```
388fn desugar_pre_post_conditions(body: &[Form]) -> Vec<Form> {
389    let first = match body.first() {
390        Some(f) => f,
391        None => return body.to_vec(),
392    };
393
394    let entries = match first.as_map() {
395        Some(entries) => entries,
396        None => return body.to_vec(),
397    };
398
399    let mut pre_conds: Vec<Form> = Vec::new();
400    let mut post_conds: Vec<Form> = Vec::new();
401    let mut has_conditions = false;
402
403    for chunk in entries.chunks(2) {
404        if chunk.len() < 2 {
405            break;
406        }
407        let (key, val) = (&chunk[0], &chunk[1]);
408        match &key.kind {
409            FormKind::Keyword(k) if k == "pre" => {
410                if let FormKind::Vector(conds) = &val.kind {
411                    pre_conds.extend_from_slice(conds);
412                    has_conditions = true;
413                }
414            }
415            FormKind::Keyword(k) if k == "post" => {
416                if let FormKind::Vector(conds) = &val.kind {
417                    post_conds.extend_from_slice(conds);
418                    has_conditions = true;
419                }
420            }
421            _ => {}
422        }
423    }
424
425    if !has_conditions {
426        return body.to_vec();
427    }
428
429    let real_body = &body[1..]; // strip the conditions map
430    let span = first.span.clone();
431    let mut new_body: Vec<Form> = Vec::new();
432
433    // Emit (assert cond) for each :pre condition.
434    for cond in &pre_conds {
435        new_body.push(make_assert_call(cond, &span));
436    }
437
438    if post_conds.is_empty() {
439        new_body.extend_from_slice(real_body);
440    } else {
441        // Wrap real body in (let* [% <body>] (assert post-cond)... %)
442        // so that % refers to the return value inside :post conditions.
443        let percent = Form::new(FormKind::Symbol("%".to_string()), span.clone());
444
445        let body_expr = if real_body.len() == 1 {
446            real_body[0].clone()
447        } else {
448            let mut do_forms = vec![Form::new(FormKind::Symbol("do".to_string()), span.clone())];
449            do_forms.extend_from_slice(real_body);
450            Form::new(FormKind::List(do_forms), span.clone())
451        };
452
453        let binding_vec = Form::new(
454            FormKind::Vector(vec![percent.clone(), body_expr]),
455            span.clone(),
456        );
457
458        let mut let_forms = vec![
459            Form::new(FormKind::Symbol("let*".to_string()), span.clone()),
460            binding_vec,
461        ];
462        for cond in &post_conds {
463            let_forms.push(make_assert_call(cond, &span));
464        }
465        let_forms.push(percent);
466
467        new_body.push(Form::new(FormKind::List(let_forms), span));
468    }
469
470    new_body
471}
472
473/// Build `(assert <cond>)` as a Form node.
474fn make_assert_call(cond: &Form, span: &cljrs_types::span::Span) -> Form {
475    Form::new(
476        FormKind::List(vec![
477            Form::new(FormKind::Symbol("assert".to_string()), span.clone()),
478            cond.clone(),
479        ]),
480        span.clone(),
481    )
482}
483
484/// Peel a `^hint` wrapper off a parameter form, returning the resolved
485/// primitive [`TypeHint`] (if the tag is a recognized primitive) together with
486/// the unwrapped inner form.  A param with no metadata returns `(None, form)`.
487fn peel_param_hint(p: &Form) -> (Option<TypeHint>, &Form) {
488    if let FormKind::Meta(meta, inner) = &p.kind {
489        let hint = tag_name_of_meta(meta).and_then(|n| TypeHint::from_tag(&n));
490        // Recurse in case of stacked metadata; the innermost form is the param.
491        let (inner_hint, inner_form) = peel_param_hint(inner);
492        (hint.or(inner_hint), inner_form)
493    } else {
494        (None, p)
495    }
496}
497
498/// Extract the bare tag name from a `^meta` form: `^long` (symbol shorthand) or
499/// `^{:tag long}` (explicit map).  Strips any namespace from the tag symbol.
500fn tag_name_of_meta(meta: &Form) -> Option<Arc<str>> {
501    match &meta.kind {
502        // `^long x` — the metadata is a bare symbol naming the tag.
503        FormKind::Symbol(s) => Some(strip_tag_ns(s)),
504        // `^{:tag long} x` — pull the `:tag` entry out of the map literal.
505        FormKind::Map(entries) => entries.chunks(2).find_map(|kv| {
506            let is_tag = matches!(&kv[0].kind, FormKind::Keyword(k) if k == "tag");
507            if !is_tag {
508                return None;
509            }
510            match kv.get(1).map(|f| &f.kind) {
511                Some(FormKind::Symbol(s)) => Some(strip_tag_ns(s)),
512                Some(FormKind::Str(s)) => Some(strip_tag_ns(s)),
513                _ => None,
514            }
515        }),
516        _ => None,
517    }
518}
519
520/// Strip a namespace prefix from a tag name (`clojure.core/long` → `long`).
521fn strip_tag_ns(s: &str) -> Arc<str> {
522    match s.rfind('/') {
523        Some(pos) if pos + 1 < s.len() => Arc::from(&s[pos + 1..]),
524        _ => Arc::from(s),
525    }
526}
527
528// ── if ────────────────────────────────────────────────────────────────────────
529
530fn eval_if(args: &[Form], env: &mut Env) -> EvalResult {
531    if args.is_empty() {
532        return Err(EvalError::Runtime("if requires a test".into()));
533    }
534    let test = eval(&args[0], env)?;
535    let truthy = !matches!(test, Value::Nil | Value::Bool(false));
536    if truthy {
537        if args.len() > 1 {
538            eval(&args[1], env)
539        } else {
540            Ok(Value::Nil)
541        }
542    } else if args.len() > 2 {
543        eval(&args[2], env)
544    } else {
545        Ok(Value::Nil)
546    }
547}
548
549// ── let* ──────────────────────────────────────────────────────────────────────
550
551fn eval_let(args: &[Form], env: &mut Env) -> EvalResult {
552    let bindings = match args.first().and_then(|f| f.as_vector()) {
553        Some(v) => expand_pairs(v)
554            .map_err(|_| EvalError::Runtime("let* binding vector must have even length".into()))?
555            .into_owned(),
556        None => return Err(EvalError::Runtime("let* requires a binding vector".into())),
557    };
558
559    let body = &args[1..];
560
561    // Detect assoc/conj chains that can be virtualized.
562    let chains = crate::interp::virtualize::detect_let_chains(&bindings);
563    let virtualizable_chains = find_virtualizable_chains(&chains, &bindings, body);
564
565    env.push_frame();
566
567    let pairs: Vec<_> = bindings.chunks(2).collect();
568    let mut i = 0;
569    while i < pairs.len() {
570        // Check if this binding starts a virtualizable chain.
571        if let Some(chain) = virtualizable_chains.iter().find(|c| c.start == i) {
572            match eval_virtualized_chain(chain, &pairs, env) {
573                Ok(()) => {
574                    i += chain.len;
575                    continue;
576                }
577                Err(e) => {
578                    env.pop_frame();
579                    return Err(e);
580                }
581            }
582        }
583
584        // Normal evaluation.
585        let pair = pairs[i];
586        let val = match eval(&pair[1], env) {
587            Ok(v) => v,
588            Err(e) => {
589                env.pop_frame();
590                return Err(e);
591            }
592        };
593        if let Err(e) = bind_pattern(&pair[0], val, env) {
594            env.pop_frame();
595            return Err(e);
596        }
597        i += 1;
598    }
599
600    let result = eval_body(body, env);
601    env.pop_frame();
602    result
603}
604
605/// Filter chains to only those that are safe to virtualize.
606///
607/// A chain is safe if no intermediate binding is used outside the chain
608/// (i.e., it's only used as the collection argument of the next step).
609fn find_virtualizable_chains<'a>(
610    chains: &'a [crate::interp::virtualize::LetChain],
611    bindings: &[Form],
612    body: &[Form],
613) -> Vec<&'a crate::interp::virtualize::LetChain> {
614    chains
615        .iter()
616        .filter(|chain| {
617            // Check that no intermediate (all except the last) is used in body
618            // or in other bindings outside the chain.
619            for j in chain.start..(chain.start + chain.len - 1) {
620                let name = match &bindings[j * 2].kind {
621                    FormKind::Symbol(s) => s.as_str(),
622                    _ => return false,
623                };
624                if crate::interp::virtualize::binding_used_in_body(name, body) {
625                    return false;
626                }
627                if crate::interp::virtualize::binding_used_in_other_bindings(
628                    name,
629                    bindings,
630                    chain.start,
631                    chain.len,
632                ) {
633                    return false;
634                }
635            }
636            true
637        })
638        .collect()
639}
640
641/// Evaluate an assoc/conj chain using transient operations.
642///
643/// Instead of creating N intermediate persistent collections, we:
644/// 1. Evaluate the root collection (first arg of the first assoc/conj)
645/// 2. Convert to transient
646/// 3. Apply each assoc!/conj! mutably
647/// 4. Convert back to persistent
648/// 5. Bind only the final name (and intermediate names point to intermediate
649///    transient values for correctness, though they shouldn't be used).
650fn eval_virtualized_chain(
651    chain: &crate::interp::virtualize::LetChain,
652    pairs: &[&[Form]],
653    env: &mut Env,
654) -> Result<(), EvalError> {
655    use crate::builtins::transients::{
656        builtin_assoc_bang, builtin_conj_bang, builtin_persistent_bang, builtin_transient,
657    };
658
659    // Step 1: Evaluate the root collection (first arg of the first call).
660    let first_pair = pairs[chain.start];
661    let first_expr_forms = match &first_pair[1].kind {
662        FormKind::List(forms) => forms,
663        _ => unreachable!("chain detection ensures this is a list"),
664    };
665    let root_collection = eval(&first_expr_forms[1], env)?;
666
667    // Step 2: Convert to transient.
668    let mut transient = match builtin_transient(std::slice::from_ref(&root_collection)) {
669        Ok(t) => t,
670        Err(_) => {
671            // Collection doesn't support transients (e.g., sorted map).
672            // Fall back to normal evaluation for the whole chain.
673            return eval_chain_normally(chain, pairs, env);
674        }
675    };
676
677    // Step 3: Apply each chain operation using transient mutation.
678    for j in 0..chain.len {
679        let pair_idx = chain.start + j;
680        let pair = pairs[pair_idx];
681        let expr_forms = match &pair[1].kind {
682            FormKind::List(forms) => forms,
683            _ => unreachable!(),
684        };
685
686        // Evaluate the non-collection arguments.
687        let mut args = vec![transient.clone()];
688        for arg_form in expr_forms.iter().skip(2) {
689            args.push(eval(arg_form, env)?);
690        }
691
692        // Apply the transient operation.
693        transient = match chain.ops[j] {
694            crate::interp::virtualize::ChainOpKind::Assoc => {
695                builtin_assoc_bang(&args).map_err(|e| EvalError::Runtime(e.to_string()))?
696            }
697            crate::interp::virtualize::ChainOpKind::Conj => {
698                builtin_conj_bang(&args).map_err(|e| EvalError::Runtime(e.to_string()))?
699            }
700        };
701
702        // Bind intermediate names to a placeholder (they shouldn't be used,
703        // but we need them in the env for structural correctness).
704        // Only the last binding gets the persistent result.
705        if j < chain.len - 1 {
706            let name = match &pair[0].kind {
707                FormKind::Symbol(s) => s.clone(),
708                _ => unreachable!(),
709            };
710            // Bind to nil as placeholder — intermediates are verified as unused.
711            env.bind(Arc::from(name.as_str()), Value::Nil);
712        }
713    }
714
715    // Step 4: Convert back to persistent.
716    let persistent =
717        builtin_persistent_bang(&[transient]).map_err(|e| EvalError::Runtime(e.to_string()))?;
718
719    // Step 5: Bind the final name.
720    let last_pair = pairs[chain.start + chain.len - 1];
721    bind_pattern(&last_pair[0], persistent, env)?;
722
723    Ok(())
724}
725
726/// Fallback: evaluate a chain using normal persistent operations.
727fn eval_chain_normally(
728    chain: &crate::interp::virtualize::LetChain,
729    pairs: &[&[Form]],
730    env: &mut Env,
731) -> Result<(), EvalError> {
732    for j in 0..chain.len {
733        let pair_idx = chain.start + j;
734        let pair = pairs[pair_idx];
735        let val = eval(&pair[1], env)?;
736        bind_pattern(&pair[0], val, env)?;
737    }
738    Ok(())
739}
740
741// ── loop* / recur ─────────────────────────────────────────────────────────────
742
743pub fn eval_loop(args: &[Form], env: &mut Env) -> EvalResult {
744    let bindings = match args.first().and_then(|f| f.as_vector()) {
745        Some(v) => expand_pairs(v)
746            .map_err(|_| EvalError::Runtime("loop* binding vector must have even length".into()))?
747            .into_owned(),
748        None => return Err(EvalError::Runtime("loop* requires a binding vector".into())),
749    };
750
751    let body = &args[1..];
752
753    // Separate pattern forms and initial values.
754    let patterns: Vec<Form> = bindings.iter().step_by(2).cloned().collect();
755    let mut current_vals: Vec<Value> = Vec::new();
756
757    // Evaluate initial values.
758    for i in (1..bindings.len()).step_by(2) {
759        current_vals.push(eval(&bindings[i], env)?);
760    }
761
762    loop {
763        // Root current_vals so they survive GC — they're not yet bound in env.
764        let _vals_root = crate::env::gc_roots::root_values(&current_vals);
765
766        // GC safepoint on every loop iteration so tight recur loops
767        // don't starve the collector.
768        crate::env::gc_roots::gc_safepoint(env);
769
770        // Under no-gc: push a fresh scratch region for this iteration.
771        // Intermediates allocated in the body land here and are freed
772        // after the iteration ends.
773        #[cfg(feature = "no-gc")]
774        let mut scratch = cljrs_gc::alloc_ctx::ScratchGuard::new();
775
776        // Under GC: scope this iteration's heap allocations in a fresh alloc
777        // frame.  Every value the body allocates is rooted (via ALLOC_ROOTS)
778        // only until this frame drops at the end of the iteration; the
779        // intermediates — and the previous iteration's now-dead recur values —
780        // then become collectable, instead of being pinned for the lifetime of
781        // the enclosing top-level form.  `result` (the return value or recur
782        // values) is moved out before the frame drops and is re-rooted at the
783        // top of the next iteration (`root_values`) or by the caller during
784        // return unwinding; no GC safepoint runs in the interval, exactly as
785        // the IR/JIT dispatch seam relies on (see `crate::tiered::apply`).
786        #[cfg(not(feature = "no-gc"))]
787        let _iter_frame = cljrs_gc::push_alloc_frame();
788
789        env.push_frame();
790        for (pat, val) in patterns.iter().zip(current_vals.iter()) {
791            if let Err(e) = bind_pattern(pat, val.clone(), env) {
792                env.pop_frame();
793                return Err(e);
794            }
795        }
796
797        // Under no-gc: pop scratch before tail expression so the return value
798        // or recur args are allocated in the enclosing scope's context.
799        #[cfg(not(feature = "no-gc"))]
800        let result = eval_body_recur(body, env);
801        #[cfg(feature = "no-gc")]
802        let result = eval_body_with_scratch_loop(body, &mut scratch, env);
803
804        env.pop_frame();
805        // scratch / _iter_frame drop at the end of the iteration (after the
806        // match below), freeing this iteration's intermediates.
807
808        match result {
809            Ok(v) => return Ok(v),
810            Err(EvalError::Recur(new_vals)) => {
811                if new_vals.len() != patterns.len() {
812                    return Err(EvalError::Arity {
813                        name: "recur".into(),
814                        expected: patterns.len().to_string(),
815                        got: new_vals.len(),
816                    });
817                }
818                // new_vals were evaluated in the caller's context (scratch was
819                // popped before the tail form), so they survive the reset.
820                current_vals = new_vals;
821            }
822            Err(e) => return Err(e),
823        }
824    }
825}
826
827fn eval_recur(args: &[Form], env: &mut Env) -> EvalResult {
828    let vals: Vec<Value> = args
829        .iter()
830        .map(|f| eval(f, env))
831        .collect::<EvalResult<_>>()?;
832    Err(EvalError::Recur(vals))
833}
834
835/// Eval body forms, propagating Recur without catching it.
836pub fn eval_body_recur(body: &[Form], env: &mut Env) -> EvalResult {
837    let mut result = Value::Nil;
838    for form in body {
839        result = eval(form, env)?;
840    }
841    Ok(result)
842}
843
844/// Under `no-gc`: eval loop body with scratch-region semantics.
845///
846/// Evaluates all non-tail forms inside the scratch region, then pops the
847/// scratch before the tail (return/recur) expression so it lands in the
848/// caller's context.
849#[cfg(feature = "no-gc")]
850fn eval_body_with_scratch_loop(
851    body: &[Form],
852    scratch: &mut cljrs_gc::alloc_ctx::ScratchGuard,
853    env: &mut Env,
854) -> EvalResult {
855    if body.is_empty() {
856        scratch.pop_for_return();
857        return Ok(Value::Nil);
858    }
859    for form in &body[..body.len() - 1] {
860        eval(form, env)?;
861    }
862    scratch.pop_for_return();
863    eval(&body[body.len() - 1], env)
864}
865
866// ── quote ─────────────────────────────────────────────────────────────────────
867
868fn eval_quote(args: &[Form], env: &Env) -> EvalResult {
869    match args.first() {
870        // `::kw` and an auto-resolved map's symbol keys resolve against the
871        // reading namespace even under quote - the JVM resolves them at read
872        // time, before quote can see them.
873        Some(f) => form_to_value(&resolve_auto_forms(f, env)?),
874        None => Err(EvalError::Runtime("quote requires an argument".into())),
875    }
876}
877
878// ── var ───────────────────────────────────────────────────────────────────────
879
880fn eval_var(args: &[Form], env: &mut Env) -> EvalResult {
881    let sym = match args.first().map(|f| &f.kind) {
882        Some(FormKind::Symbol(s)) => s.clone(),
883        _ => return Err(EvalError::Runtime("var requires a symbol".into())),
884    };
885    let parsed = cljrs_value::Symbol::parse(&sym);
886    let ns: Arc<str> = env.resolve_ns_or_current(parsed.namespace.as_deref());
887    let name = parsed.name.as_ref();
888    env.globals
889        .lookup_var_in_ns(&ns, name)
890        .map(Value::Var)
891        .ok_or_else(|| EvalError::UnboundSymbol(sym))
892}
893
894// ── set! ──────────────────────────────────────────────────────────────────────
895
896fn eval_set_bang(args: &[Form], env: &mut Env) -> EvalResult {
897    let target = args
898        .first()
899        .ok_or_else(|| EvalError::Runtime("set! requires a target".into()))?;
900    let val = if args.len() > 1 {
901        eval(&args[1], env)?
902    } else {
903        Value::Nil
904    };
905    match &target.kind {
906        FormKind::Symbol(sym) => set_bang_symbol(sym, val, env),
907        _ => match set_bang_field_target(target) {
908            Some((field, inst_form)) => {
909                let inst = eval(inst_form, env)?;
910                set_type_instance_field(&inst, field, val)
911            }
912            None => Err(set_bang_target_error()),
913        },
914    }
915}
916
917/// `(set! sym val)` for an already-evaluated `val`: a mutable deftype field in
918/// scope, else the thread-local binding of the var `sym` names, else its root.
919pub fn set_bang_symbol(sym: &str, val: Value, env: &mut Env) -> EvalResult {
920    // A bare unqualified name inside a deftype method may be one of its
921    // mutable fields; only if not does it fall through to var logic.
922    if !sym.contains('/')
923        && let Some(v) = try_set_mutable_field(env, sym, &val)?
924    {
925        return Ok(v);
926    }
927    let parsed = cljrs_value::Symbol::parse(sym);
928    let ns = parsed.namespace.as_deref().unwrap_or(&env.current_ns);
929    let var = env
930        .globals
931        .lookup_var_in_ns(ns, &parsed.name)
932        .ok_or_else(|| EvalError::UnboundSymbol(sym.to_string()))?;
933    // Prefer updating the thread-local binding if one exists.
934    if !crate::env::dynamics::set_thread_local(&var, val.clone()) {
935        var.get().bind(val.clone());
936    }
937    Ok(val)
938}
939
940/// For a `(set! (.-field inst) v)` target, the field name and the `inst`
941/// form; `None` for any other non-symbol target.
942pub fn set_bang_field_target(target: &Form) -> Option<(&str, &Form)> {
943    match &target.kind {
944        FormKind::List(parts) if parts.len() == 2 => match &parts[0].kind {
945            FormKind::Symbol(op) if op.starts_with(".-") => Some((&op[2..], &parts[1])),
946            _ => None,
947        },
948        _ => None,
949    }
950}
951
952/// The error for a `set!` target that is neither a symbol nor `(.-field inst)`.
953pub fn set_bang_target_error() -> EvalError {
954    EvalError::Runtime("set! requires a symbol or (.-field inst) target".into())
955}
956
957/// If `sym` names a mutable field of the `__deftype_self__` instance in scope,
958/// update its cell AND refresh the in-scope local snapshot, returning the new
959/// value. `None` when `sym` is not such a field, so `set!` falls through to var
960/// logic.
961fn try_set_mutable_field(env: &mut Env, sym: &str, val: &Value) -> EvalResult<Option<Value>> {
962    let Some(Value::TypeInstance(ti)) = env.lookup_local_frames(DEFTYPE_SELF) else {
963        return Ok(None);
964    };
965    let Some(atom) = ti.get().mutable.clone() else {
966        return Ok(None);
967    };
968    let key = Value::keyword(cljrs_value::Keyword::simple(sym));
969    let Value::Map(map) = atom.get().deref() else {
970        return Ok(None);
971    };
972    if map.get(&key).is_none() {
973        return Ok(None);
974    }
975    atom.get().reset(Value::Map(map.assoc(key, val.clone())));
976    // Keep the method's local snapshot consistent for later reads.
977    env.bind(Arc::from(sym), val.clone());
978    Ok(Some(val.clone()))
979}
980
981/// Set a mutable field on an explicit instance: `(set! (.-field inst) v)`.
982pub fn set_type_instance_field(inst: &Value, field: &str, val: Value) -> EvalResult {
983    let Value::TypeInstance(ti) = inst else {
984        return Err(EvalError::Runtime(format!(
985            "set! (.-{field} …): target is not a type instance"
986        )));
987    };
988    let atom = ti.get().mutable.clone().ok_or_else(|| {
989        EvalError::Runtime(format!("set! (.-{field} …): type has no mutable fields"))
990    })?;
991    let key = Value::keyword(cljrs_value::Keyword::simple(field));
992    let map = match atom.get().deref() {
993        Value::Map(m) => m,
994        _ => MapValue::empty(),
995    };
996    if map.get(&key).is_none() {
997        return Err(EvalError::Runtime(format!(
998            "set!: {field} is not a mutable field"
999        )));
1000    }
1001    atom.get().reset(Value::Map(map.assoc(key, val.clone())));
1002    Ok(val)
1003}
1004
1005// ── throw ─────────────────────────────────────────────────────────────────────
1006
1007fn eval_throw(args: &[Form], env: &mut Env) -> EvalResult {
1008    let val = match args.first() {
1009        Some(f) => eval(f, env)?,
1010        None => Value::Nil,
1011    };
1012    Err(throw_value(val))
1013}
1014
1015/// The error `(throw val)` raises for an already-evaluated `val`.
1016///
1017/// Wraps non-error values in an ExceptionInfo so try/catch always sees a
1018/// Value::Error and ex-message / ex-data work uniformly inside the handler.
1019pub fn throw_value(val: Value) -> EvalError {
1020    let val = match val {
1021        Value::Error(_) => val,
1022        other => {
1023            let msg = format!("{}", other);
1024            Value::Error(GcPtr::new(ExceptionInfo::new(
1025                ValueError::Other(msg.clone()),
1026                msg,
1027                None,
1028                None,
1029            )))
1030        }
1031    };
1032    EvalError::Thrown(val)
1033}
1034
1035// ── try ───────────────────────────────────────────────────────────────────────
1036
1037/// A catch clause: `(catch Type binding body...)`.
1038///
1039/// Public so the async evaluator (`cljrs-async`) can reuse `parse_try_args` to
1040/// build a yielding `try`/`catch`.
1041pub struct CatchClause<'a> {
1042    pub type_sym: &'a str,
1043    pub binding: &'a str,
1044    pub body: &'a [Form],
1045}
1046
1047/// Convert a non-Thrown EvalError into a `Value::Error` so it can be bound
1048/// inside a catch clause and inspected with `ex-message` / `ex-data`.
1049///
1050/// Public so the async evaluator can convert errors the same way `try` does.
1051pub fn eval_error_to_value(err: &EvalError) -> Value {
1052    let msg = err.to_string();
1053    Value::Error(GcPtr::new(ExceptionInfo::new(
1054        ValueError::Other(msg.clone()),
1055        msg,
1056        None,
1057        None,
1058    )))
1059}
1060
1061/// Test whether the type symbol on a `(catch <Type> e ...)` clause matches a
1062/// thrown value. Type names are matched by their last `.`-separated segment so
1063/// fully-qualified names like `java.lang.Exception` work as well as bare ones.
1064pub fn catch_type_matches(type_name: &str, val: &Value) -> bool {
1065    let short = type_name.rsplit('.').next().unwrap_or(type_name);
1066    match short {
1067        // Catch-all (matches any value, error or not — back-compat).
1068        // `:default` is the ClojureScript universal catch keyword; it arrives
1069        // here as the keyword's name ("default") via `parse_try_args`.
1070        "Object" | "Exception" | "Throwable" | "Error" | "default" => true,
1071        // ExceptionInfo only matches actual ex-info / Exception values.
1072        "ExceptionInfo" => matches!(val, Value::Error(_)),
1073        _ => false,
1074    }
1075}
1076
1077fn eval_try(args: &[Form], env: &mut Env) -> EvalResult {
1078    let (body, catches, fin_body) = parse_try_args(args);
1079
1080    let mut result = eval_body(body, env);
1081
1082    // Handle catch: never intercept Recur (loop trampoline signal).
1083    let err_opt = match std::mem::replace(&mut result, Ok(Value::Nil)) {
1084        Ok(v) => {
1085            result = Ok(v);
1086            None
1087        }
1088        Err(EvalError::Recur(args)) => {
1089            result = Err(EvalError::Recur(args));
1090            None
1091        }
1092        Err(EvalError::GasExhausted) => {
1093            result = Err(EvalError::GasExhausted);
1094            None
1095        }
1096        Err(other) => Some(other),
1097    };
1098
1099    if let Some(err) = err_opt {
1100        let thrown_val = match err {
1101            EvalError::Thrown(v) => v,
1102            ref other => eval_error_to_value(other),
1103        };
1104        let mut handled = false;
1105        for c in &catches {
1106            if catch_type_matches(c.type_sym, &thrown_val) {
1107                env.push_frame();
1108                env.bind(Arc::from(c.binding), thrown_val.clone());
1109                result = eval_body(c.body, env);
1110                env.pop_frame();
1111                handled = true;
1112                break;
1113            }
1114        }
1115        if !handled {
1116            // No matching catch — re-throw.
1117            result = Err(EvalError::Thrown(thrown_val));
1118        }
1119    }
1120
1121    // Always run finally.  Its normal (Ok) value is discarded — `try` returns
1122    // the body/catch result — but an exception thrown from `finally` supersedes
1123    // the pending result or exception (matching JVM/Clojure semantics).
1124    if !fin_body.is_empty() {
1125        eval_body(fin_body, env)?;
1126    }
1127
1128    result
1129}
1130
1131/// The class name or `:default` keyword naming what a `catch` clause catches.
1132fn catch_target(f: &Form) -> Option<&str> {
1133    f.as_symbol().or_else(|| f.as_keyword())
1134}
1135
1136/// Split try args into (body, catch clauses, finally body).
1137///
1138/// Public so the async evaluator can parse `try` forms identically.
1139pub fn parse_try_args(args: &[Form]) -> (&[Form], Vec<CatchClause<'_>>, &[Form]) {
1140    let mut body_end = args.len();
1141    let mut catches: Vec<CatchClause<'_>> = Vec::new();
1142    let mut fin_body: &[Form] = &[];
1143
1144    for (i, form) in args.iter().enumerate() {
1145        if let Some(parts) = form.as_list()
1146            && let Some(s) = parts.first().and_then(|f| f.as_symbol())
1147        {
1148            if s == "catch" {
1149                if i < body_end {
1150                    body_end = i;
1151                }
1152                // The catch type may be a symbol (`Throwable`, `java.lang.Exception`),
1153                // the ClojureScript `:default` catch-all keyword, or a reader
1154                // conditional `#?(:rust Exception :clj ...)` whose selected branch
1155                // resolves to one of those.
1156                let type_sym = match parts.get(1) {
1157                    Some(f) => match catch_target(f) {
1158                        Some(name) => name,
1159                        None => match &f.unmeta().kind {
1160                            FormKind::ReaderCond {
1161                                splicing: false,
1162                                clauses,
1163                            } => match select_reader_cond(clauses).and_then(catch_target) {
1164                                Some(name) => name,
1165                                None => continue,
1166                            },
1167                            _ => continue,
1168                        },
1169                    },
1170                    None => continue,
1171                };
1172                let binding = match parts.get(2).and_then(|f| f.as_symbol()) {
1173                    Some(s) => s,
1174                    None => continue,
1175                };
1176                catches.push(CatchClause {
1177                    type_sym,
1178                    binding,
1179                    body: &parts[3..],
1180                });
1181                continue;
1182            }
1183            if s == "finally" {
1184                if i < body_end {
1185                    body_end = i;
1186                }
1187                fin_body = &parts[1..];
1188                continue;
1189            }
1190        }
1191    }
1192
1193    (&args[..body_end], catches, fin_body)
1194}
1195
1196// ── defn ──────────────────────────────────────────────────────────────────────
1197
1198pub fn eval_defn(args: &[Form], env: &mut Env, private: bool) -> EvalResult {
1199    // The name may carry reader metadata, e.g. `(defn ^:async fetch ...)`.
1200    let name_form = args
1201        .first()
1202        .ok_or_else(|| EvalError::Runtime("defn requires a symbol name".into()))?;
1203    let (name_metas, name_sym) = name_form.peel_meta();
1204    let (name, mut is_async) = match &name_sym.kind {
1205        FormKind::Symbol(s) => (s.clone(), name_metas.iter().any(|m| m.requests_async())),
1206        _ => return Err(EvalError::Runtime("defn name must be a symbol".into())),
1207    };
1208    // Optional docstring and/or metadata map after the name.
1209    // Valid orderings: (defn name body...), (defn name "doc" body...),
1210    // (defn name {:meta ...} body...), (defn name "doc" {:meta ...} body...).
1211    let mut rest_start = 1;
1212    let mut docstring: Option<String> = None;
1213    if rest_start < args.len()
1214        && let Some(s) = args[rest_start].as_string()
1215    {
1216        docstring = Some(s.to_string());
1217        rest_start += 1;
1218    }
1219    let mut attr_meta: Option<Value> = None;
1220    if rest_start < args.len() && args[rest_start].as_map().is_some() {
1221        // An attr-map such as `{:async true}` can also request async dispatch.
1222        is_async |= args[rest_start].requests_async();
1223        attr_meta = Some(eval(&args[rest_start], env)?);
1224        rest_start += 1;
1225    }
1226    // Build (fn* name ...)
1227    let mut fn_args = vec![Form::new(
1228        FormKind::Symbol(name.to_string()),
1229        args[0].span.clone(),
1230    )];
1231    fn_args.extend_from_slice(&args[rest_start..]);
1232    // Under no-gc: the Fn object must live in the StaticArena since the Var
1233    // intern outlives all scratch regions.
1234    #[cfg(feature = "no-gc")]
1235    let _static_ctx = cljrs_gc::alloc_ctx::StaticCtxGuard::new();
1236    let mut fn_val = eval_fn(&fn_args, env)?;
1237    if is_async && let Value::Fn(ref mut f) = fn_val {
1238        f.get_mut().is_async = true;
1239    }
1240    let var = env
1241        .globals
1242        .intern(&env.current_ns, Arc::from(name.as_str()), fn_val.clone());
1243    let mut meta = attr_meta;
1244    if let Some(doc) = &docstring {
1245        meta = merge_meta(meta, Some(doc_meta(doc)));
1246    }
1247    meta = merge_meta(meta, arglists_meta(&fn_val, 0));
1248    if private {
1249        meta = merge_meta(meta, Some(private_meta()));
1250    }
1251    if let Some(meta_val) = meta {
1252        var.get().set_meta(meta_val);
1253    }
1254    Ok(Value::Var(var))
1255}
1256
1257// ── defmacro ──────────────────────────────────────────────────────────────────
1258
1259/// Prepend `&form` and `&env` Form symbols to an arity form's parameter vector.
1260///
1261/// Handles:
1262/// - Single-arity vector `[params...]` → `[&form &env params...]`
1263/// - Multi-arity clause list `([params...] body...)` → `([&form &env params...] body...)`
1264fn prepend_macro_params(form: &Form) -> Form {
1265    let span = form.span.clone();
1266    match &form.unmeta().kind {
1267        FormKind::Vector(params) => {
1268            let mut new_params = vec![
1269                Form::new(FormKind::Symbol("&form".to_string()), span.clone()),
1270                Form::new(FormKind::Symbol("&env".to_string()), span.clone()),
1271            ];
1272            new_params.extend_from_slice(params);
1273            Form::new(FormKind::Vector(new_params), span)
1274        }
1275        FormKind::List(forms) => {
1276            // Arity clause: ([params...] body...) — prepend to first element (params vector).
1277            if let Some(first) = forms.first() {
1278                let new_params_form = prepend_macro_params(first);
1279                let mut new_forms = vec![new_params_form];
1280                new_forms.extend_from_slice(&forms[1..]);
1281                Form::new(FormKind::List(new_forms), span)
1282            } else {
1283                form.clone()
1284            }
1285        }
1286        _ => form.clone(),
1287    }
1288}
1289
1290fn eval_defmacro(args: &[Form], env: &mut Env) -> EvalResult {
1291    // The name may carry reader metadata, e.g. `(defmacro ^:private foo ...)`,
1292    // possibly stacked (`^:a ^:b foo`).
1293    let mut current = args
1294        .first()
1295        .ok_or_else(|| EvalError::Runtime("defmacro requires a symbol at position 0".into()))?
1296        .clone();
1297    let mut name_meta: Option<Value> = None;
1298    while let FormKind::Meta(meta_form, inner) = current.kind {
1299        let meta_val = compile_meta_form(&meta_form, env)?;
1300        name_meta = merge_meta(name_meta, Some(meta_val));
1301        current = *inner;
1302    }
1303    let name = match &current.kind {
1304        FormKind::Symbol(s) => s.clone(),
1305        _ => {
1306            return Err(EvalError::Runtime(
1307                "defmacro requires a symbol at position 0".into(),
1308            ));
1309        }
1310    };
1311
1312    let mut rest_start = 1;
1313    let mut docstring: Option<String> = None;
1314    if rest_start < args.len()
1315        && let FormKind::Str(s) = &args[rest_start].kind
1316    {
1317        docstring = Some(s.clone());
1318        rest_start += 1;
1319    }
1320    let mut attr_meta = None;
1321    if rest_start < args.len() && matches!(args[rest_start].kind, FormKind::Map(_)) {
1322        attr_meta = Some(eval(&args[rest_start], env)?);
1323        rest_start += 1;
1324    }
1325    // Prepend implicit &form and &env params to each arity.
1326    let mut fn_args = vec![Form::new(
1327        FormKind::Symbol(name.to_string()),
1328        args[0].span.clone(),
1329    )];
1330    for form in &args[rest_start..] {
1331        fn_args.push(prepend_macro_params(form));
1332    }
1333    // Under no-gc: the Macro object must live in the StaticArena since the Var
1334    // intern outlives all scratch regions.
1335    #[cfg(feature = "no-gc")]
1336    let _static_ctx = cljrs_gc::alloc_ctx::StaticCtxGuard::new();
1337    let fn_val = eval_fn(&fn_args, env)?;
1338
1339    // Convert Fn → Macro by setting is_macro = true.
1340    let macro_val = match fn_val {
1341        Value::Fn(f) => {
1342            let mut mfn = f.get().clone();
1343            mfn.is_macro = true;
1344            Value::Macro(GcPtr::new(mfn))
1345        }
1346        other => other,
1347    };
1348
1349    let var = env
1350        .globals
1351        .intern(&env.current_ns, Arc::from(name.as_str()), macro_val.clone());
1352    let mut meta = merge_meta(name_meta, attr_meta);
1353    if let Some(doc) = &docstring {
1354        meta = merge_meta(meta, Some(doc_meta(doc)));
1355    }
1356    // Skip the implicit &form/&env params when showing the macro's signature.
1357    meta = merge_meta(meta, arglists_meta(&macro_val, 2));
1358    if let Some(m) = meta {
1359        var.get().set_meta(m);
1360    }
1361    Ok(Value::Var(var))
1362}
1363
1364// ── defonce ───────────────────────────────────────────────────────────────────
1365
1366fn eval_defonce(args: &[Form], env: &mut Env) -> EvalResult {
1367    if args.is_empty() {
1368        return Err(EvalError::Runtime("defonce requires a name".into()));
1369    }
1370    // Share `def`'s name extraction so a metadata-carrying symbol such as
1371    // `(defonce ^:private registry (atom {}))` is accepted here too.
1372    let (name, _meta) = extract_def_name(&args[0], env)?;
1373    // If already bound, return immediately.
1374    if let Some(var) = defonce_existing(&name, env) {
1375        return Ok(var);
1376    }
1377    eval_def(args, env)
1378}
1379
1380// ── and / or ──────────────────────────────────────────────────────────────────
1381
1382fn eval_and(args: &[Form], env: &mut Env) -> EvalResult {
1383    let mut result = Value::Bool(true);
1384    for form in args {
1385        result = eval(form, env)?;
1386        if matches!(result, Value::Nil | Value::Bool(false)) {
1387            return Ok(result);
1388        }
1389    }
1390    Ok(result)
1391}
1392
1393fn eval_or(args: &[Form], env: &mut Env) -> EvalResult {
1394    let mut last = Value::Nil;
1395    for form in args {
1396        last = eval(form, env)?;
1397        if !matches!(last, Value::Nil | Value::Bool(false)) {
1398            return Ok(last);
1399        }
1400    }
1401    Ok(last)
1402}
1403
1404// ── require ───────────────────────────────────────────────────────────────────
1405
1406fn eval_require(args: &[Form], env: &mut Env) -> EvalResult {
1407    for arg in args {
1408        let val = eval(arg, env)?;
1409        let spec = parse_require_spec_val(val).map_err(EvalError::Runtime)?;
1410        load_ns(env.globals.clone(), &spec, &env.current_ns)?;
1411    }
1412    Ok(Value::Nil)
1413}
1414
1415/// Parse a `RequireSpec` from an already-evaluated `Value`.
1416/// Accepts: `'some.ns`, `['some.ns :as alias]`, `['some.ns :refer [syms]]`,
1417/// `['some.ns :refer :all]`, and versioned forms like `'some.ns@abc1234` or
1418/// `['some.ns@abc1234 :as alias]`.
1419fn parse_require_spec_val(val: Value) -> Result<RequireSpec, String> {
1420    match val {
1421        Value::Symbol(s) => {
1422            let sym = s.get();
1423            Ok(RequireSpec {
1424                ns: sym.name.clone(),
1425                version: sym.version.clone(),
1426                alias: None,
1427                refer: RequireRefer::None,
1428            })
1429        }
1430        Value::Vector(v) => {
1431            let items: Vec<Value> = v.get().iter().cloned().collect();
1432            if items.is_empty() {
1433                return Err("require spec vector must not be empty".into());
1434            }
1435            let (ns, version) = match &items[0] {
1436                Value::Symbol(s) => {
1437                    let sym = s.get();
1438                    (sym.name.clone(), sym.version.clone())
1439                }
1440                other => {
1441                    return Err(format!(
1442                        "require spec: first element must be a symbol, got {}",
1443                        other.type_name()
1444                    ));
1445                }
1446            };
1447            let mut alias = None;
1448            let mut refer = RequireRefer::None;
1449            let mut i = 1;
1450            while i < items.len() {
1451                match &items[i] {
1452                    Value::Keyword(k) if k.get().name.as_ref() == "as" => {
1453                        i += 1;
1454                        alias = Some(match items.get(i) {
1455                            Some(Value::Symbol(s)) => s.get().name.clone(),
1456                            _ => return Err("require :as expects a symbol".into()),
1457                        });
1458                    }
1459                    Value::Keyword(k) if k.get().name.as_ref() == "refer" => {
1460                        i += 1;
1461                        refer = match items.get(i) {
1462                            Some(Value::Keyword(k2)) if k2.get().name.as_ref() == "all" => {
1463                                RequireRefer::All
1464                            }
1465                            Some(Value::Vector(rv)) => {
1466                                let names: Vec<Arc<str>> = rv
1467                                    .get()
1468                                    .iter()
1469                                    .map(|v| match v {
1470                                        Value::Symbol(s) => Ok(s.get().name.clone()),
1471                                        other => Err(format!(
1472                                            "require :refer expects symbols, got {}",
1473                                            other.type_name()
1474                                        )),
1475                                    })
1476                                    .collect::<Result<_, _>>()?;
1477                                RequireRefer::Named(names)
1478                            }
1479                            _ => {
1480                                return Err(
1481                                    "require :refer expects :all or a vector of symbols".into()
1482                                );
1483                            }
1484                        };
1485                    }
1486                    other => {
1487                        return Err(format!(
1488                            "require spec: unexpected option {}",
1489                            other.type_name()
1490                        ));
1491                    }
1492                }
1493                i += 1;
1494            }
1495            Ok(RequireSpec {
1496                ns,
1497                version,
1498                alias,
1499                refer,
1500            })
1501        }
1502        other => Err(format!(
1503            "require expects a symbol or vector, got {}",
1504            other.type_name()
1505        )),
1506    }
1507}
1508
1509/// The form a require-spec element denotes: a reader conditional resolves to
1510/// its selected branch, `None` when no branch matches this platform. Any other
1511/// form is itself.
1512fn spec_element(form: &Form) -> Option<&Form> {
1513    match &form.kind {
1514        FormKind::ReaderCond { clauses, .. } => select_reader_cond(clauses),
1515        _ => Some(form),
1516    }
1517}
1518
1519/// Parse a `RequireSpec` from a raw `Form` (unevaluated, used in `ns` macro).
1520/// Also handles versioned namespace symbols such as `my.ns@abc1234`.
1521///
1522/// Public so the AOT compiler can establish an entry namespace from the same
1523/// parse `eval_ns` uses. A second implementation there would drift: it already
1524/// did, dropping `:refer` and `@version` from structurally emitted requires.
1525pub fn parse_require_spec_form(form: &Form) -> Result<RequireSpec, String> {
1526    match &form.kind {
1527        FormKind::Symbol(s) => {
1528            let sym = cljrs_value::Symbol::parse(s);
1529            Ok(RequireSpec {
1530                ns: sym.name.clone(),
1531                version: sym.version.clone(),
1532                alias: None,
1533                refer: RequireRefer::None,
1534            })
1535        }
1536        FormKind::Vector(items) => {
1537            if items.is_empty() {
1538                return Err("require spec vector must not be empty".into());
1539            }
1540            let head = spec_element(&items[0]).ok_or_else(|| {
1541                "require spec: no reader-conditional branch matched for the namespace".to_string()
1542            })?;
1543            let (ns, version) = match &head.kind {
1544                FormKind::Symbol(s) => {
1545                    let sym = cljrs_value::Symbol::parse(s);
1546                    (sym.name.clone(), sym.version.clone())
1547                }
1548                _ => return Err("require spec: first element must be a symbol".into()),
1549            };
1550            let mut alias = None;
1551            let mut refer = RequireRefer::None;
1552            let mut i = 1;
1553            while i < items.len() {
1554                // An option whose conditional selects nothing is dropped; the
1555                // namespace slot above cannot be, so it errors instead.
1556                let Some(item) = spec_element(&items[i]) else {
1557                    i += 1;
1558                    continue;
1559                };
1560                match &item.kind {
1561                    FormKind::Keyword(k) if k == "as" => {
1562                        i += 1;
1563                        alias = Some(match items.get(i).map(|f| &f.kind) {
1564                            Some(FormKind::Symbol(s)) => Arc::from(s.as_str()),
1565                            _ => return Err("require :as expects a symbol".into()),
1566                        });
1567                    }
1568                    FormKind::Keyword(k) if k == "refer" => {
1569                        i += 1;
1570                        refer = match items.get(i).map(|f| &f.kind) {
1571                            Some(FormKind::Keyword(k2)) if k2 == "all" => RequireRefer::All,
1572                            Some(FormKind::Vector(rv)) => {
1573                                let names: Vec<Arc<str>> = rv
1574                                    .iter()
1575                                    .map(|f| match &f.kind {
1576                                        FormKind::Symbol(s) => Ok(Arc::from(s.as_str())),
1577                                        _ => Err("require :refer expects symbols".to_string()),
1578                                    })
1579                                    .collect::<Result<_, _>>()?;
1580                                RequireRefer::Named(names)
1581                            }
1582                            _ => return Err("require :refer expects :all or a vector".into()),
1583                        };
1584                    }
1585                    _ => return Err(format!("require spec: unexpected form at position {i}")),
1586                }
1587                i += 1;
1588            }
1589            Ok(RequireSpec {
1590                ns,
1591                version,
1592                alias,
1593                refer,
1594            })
1595        }
1596        _ => Err("require spec must be a symbol or vector".into()),
1597    }
1598}
1599
1600// ── ns ────────────────────────────────────────────────────────────────────────
1601
1602/// Extract the ns name and optional metadata from the `ns` macro's name form.
1603/// Handles plain symbols and `^meta` shorthand / map forms, e.g.
1604/// `(ns ^{:doc "..."} my.ns ...)` or `(ns ^:no-doc my.ns ...)`.
1605fn extract_ns_name_form(form: Option<&Form>, env: &mut Env) -> EvalResult<(String, Option<Value>)> {
1606    let mut current = form
1607        .ok_or_else(|| EvalError::Runtime("ns requires a symbol at position 0".into()))?
1608        .clone();
1609    let mut meta_acc: Option<Value> = None;
1610    while let FormKind::Meta(meta_form, inner) = current.kind {
1611        let meta_val = compile_meta_form(&meta_form, env)?;
1612        meta_acc = merge_meta(meta_acc, Some(meta_val));
1613        current = *inner;
1614    }
1615    match &current.kind {
1616        FormKind::Symbol(s) => Ok((s.clone(), meta_acc)),
1617        _ => Err(EvalError::Runtime(
1618            "ns requires a symbol at position 0".into(),
1619        )),
1620    }
1621}
1622
1623/// Merge two optional metadata maps, with `overlay` entries taking
1624/// precedence over `base` entries (matching Clojure's `(merge base overlay)`).
1625fn merge_meta(base: Option<Value>, overlay: Option<Value>) -> Option<Value> {
1626    match (base, overlay) {
1627        (None, None) => None,
1628        (Some(b), None) => Some(b),
1629        (None, Some(o)) => Some(o),
1630        (Some(Value::Map(b)), Some(Value::Map(o))) => {
1631            let mut merged = b;
1632            for (k, v) in o.iter() {
1633                merged = merged.assoc(k.clone(), v.clone());
1634            }
1635            Some(Value::Map(merged))
1636        }
1637        (_, Some(o)) => Some(o),
1638    }
1639}
1640
1641/// Parse the body of a `(:refer-clojure ...)` clause into the filter applied
1642/// to the automatic `clojure.core` refer.  Accepts `:exclude`, `:only` and
1643/// `:rename`, each once, in any order.
1644fn parse_refer_clojure_clause(items: &[Form]) -> Result<ReferClojureFilter, String> {
1645    let mut filter = ReferClojureFilter::default();
1646    let items = expand_reader_conds(items);
1647    let mut i = 0;
1648    while i < items.len() {
1649        let FormKind::Keyword(k) = &items[i].kind else {
1650            return Err(":refer-clojure expects keyword options (:exclude, :only, :rename)".into());
1651        };
1652        let arg = items
1653            .get(i + 1)
1654            .ok_or_else(|| format!(":refer-clojure :{k} expects a value"))?;
1655        match k.as_str() {
1656            "exclude" => filter.exclude = symbol_name_set(arg, "exclude")?,
1657            "only" => filter.only = Some(symbol_name_set(arg, "only")?),
1658            "rename" => {
1659                let FormKind::Map(entries) = &arg.kind else {
1660                    return Err(":refer-clojure :rename expects a map".into());
1661                };
1662                if entries.len() % 2 != 0 {
1663                    return Err(":refer-clojure :rename expects an even-sized map".into());
1664                }
1665                for pair in entries.chunks(2) {
1666                    match (&pair[0].kind, &pair[1].kind) {
1667                        (FormKind::Symbol(from), FormKind::Symbol(to)) => {
1668                            filter
1669                                .rename
1670                                .insert(Arc::from(from.as_str()), Arc::from(to.as_str()));
1671                        }
1672                        _ => {
1673                            return Err(
1674                                ":refer-clojure :rename expects symbol keys and values".into()
1675                            );
1676                        }
1677                    }
1678                }
1679            }
1680            other => return Err(format!(":refer-clojure does not support :{other}")),
1681        }
1682        i += 2;
1683    }
1684    Ok(filter)
1685}
1686
1687/// Read a vector of symbols (an `:exclude`/`:only` list) as a set of names.
1688fn symbol_name_set(form: &Form, opt: &str) -> Result<HashSet<Arc<str>>, String> {
1689    let FormKind::Vector(items) = &form.kind else {
1690        return Err(format!(":refer-clojure :{opt} expects a vector of symbols"));
1691    };
1692    expand_reader_conds(items)
1693        .iter()
1694        .map(|f| match &f.kind {
1695            FormKind::Symbol(sym) => Ok(Arc::from(sym.as_str())),
1696            _ => Err(format!(":refer-clojure :{opt} expects symbols")),
1697        })
1698        .collect()
1699}
1700
1701fn eval_ns(args: &[Form], env: &mut Env) -> EvalResult {
1702    let (name, name_meta) = extract_ns_name_form(args.first(), env)?;
1703    env.globals.get_or_create_ns(&name);
1704    env.current_ns = Arc::from(name.as_str());
1705    // Auto-refer clojure.core (Clojure default behaviour).
1706    if name != "clojure.core" {
1707        env.globals.refer_core(&name);
1708    }
1709    sync_star_ns(env);
1710
1711    // Optional docstring, then optional attr-map, before reference clauses.
1712    let mut rest = &args[1..];
1713    if matches!(rest.first().map(|f| &f.kind), Some(FormKind::Str(_))) {
1714        rest = &rest[1..];
1715    }
1716    let mut attr_meta = None;
1717    if matches!(rest.first().map(|f| &f.kind), Some(FormKind::Map(_))) {
1718        attr_meta = Some(eval(&rest[0], env)?);
1719        rest = &rest[1..];
1720    }
1721
1722    if let Some(m) = merge_meta(name_meta, attr_meta) {
1723        let ns_ptr = env.globals.get_or_create_ns(&env.current_ns);
1724        ns_ptr.get().set_meta(m);
1725    }
1726
1727    // `:refer-clojure` first: it narrows the automatic core refer done above,
1728    // and an explicit `:require ... :refer` below must be able to override it.
1729    // Re-evaluating an `ns` form without the clause clears a filter left by an
1730    // earlier evaluation, restoring the full core refer.
1731    //
1732    // More than one `:refer-clojure` clause is not meaningful, so the last one
1733    // wins rather than accumulating.  (Clojure runs each clause as a separate
1734    // `refer` call, where a second clause silently *re-adds* what the first
1735    // excluded; there is no reading of that under which both clauses do what
1736    // they say.)
1737    let mut refer_clojure = None;
1738    for clause in rest {
1739        if let FormKind::List(items) = &clause.kind
1740            && matches!(items.first().map(|f| &f.kind), Some(FormKind::Keyword(k)) if k == "refer-clojure")
1741        {
1742            refer_clojure =
1743                Some(parse_refer_clojure_clause(&items[1..]).map_err(EvalError::Runtime)?);
1744        }
1745    }
1746    if name != "clojure.core" {
1747        env.globals
1748            .set_refer_clojure_filter(&name, refer_clojure)
1749            .map_err(EvalError::Runtime)?;
1750    }
1751
1752    for clause in rest {
1753        if let FormKind::List(items) = &clause.kind {
1754            match items.first().map(|f| &f.kind) {
1755                Some(FormKind::Keyword(k)) if k == "require" => {
1756                    // Expand reader conditionals among require specs
1757                    let expanded = expand_reader_conds(&items[1..]);
1758                    for spec_form in &expanded {
1759                        let spec =
1760                            parse_require_spec_form(spec_form).map_err(EvalError::Runtime)?;
1761                        load_ns(env.globals.clone(), &spec, &name)?;
1762                    }
1763                }
1764                Some(FormKind::Keyword(k)) if k == "import" => {
1765                    for spec in &expand_reader_conds(&items[1..]) {
1766                        import_types(env, &name, spec);
1767                    }
1768                }
1769                // `:refer-clojure` was handled in the pass above; other clauses
1770                // (`:use`) — skip for now.
1771                _ => {}
1772            }
1773        }
1774    }
1775
1776    let ns_ptr = env.globals.get_or_create_ns(&env.current_ns);
1777    Ok(Value::Namespace(ns_ptr))
1778}
1779
1780/// Apply one `(:import ...)` spec (`[pkg Name ...]`, `(pkg Name ...)` or
1781/// `pkg.Name`) by referring each named record or deftype into `dst_ns`.
1782///
1783/// A type's dispatch tag is qualified by its defining namespace, so an
1784/// unqualified `Name` only reaches that tag through a var `dst_ns` can
1785/// resolve; without the refer, `(extend-type Name ...)` in the importing
1786/// namespace registers its methods under a tag no instance carries.
1787fn import_types(env: &Env, dst_ns: &str, spec: &Form) {
1788    match &spec.unmeta().kind {
1789        FormKind::Vector(items) | FormKind::List(items) => {
1790            let mut syms = items.iter().filter_map(Form::as_symbol);
1791            if let Some(pkg) = syms.next() {
1792                for name in syms {
1793                    import_type(env, dst_ns, pkg, name);
1794                }
1795            }
1796        }
1797        FormKind::Symbol(s) => {
1798            if let Some((pkg, name)) = s.rsplit_once('.') {
1799                import_type(env, dst_ns, pkg, name);
1800            }
1801        }
1802        _ => {}
1803    }
1804}
1805
1806/// Refer the type `name` defined in namespace `pkg` into `dst_ns`. `pkg` is
1807/// also tried with `_` read as `-`, the JVM's package spelling of a namespace.
1808/// Anything that is not a loaded record or deftype (a host class in a `.cljc`
1809/// file, say) is left alone, as `:import` was before it referred anything.
1810fn import_type(env: &Env, dst_ns: &str, pkg: &str, name: &str) {
1811    let demunged = pkg.replace('_', "-");
1812    for ns in [pkg, demunged.as_str()] {
1813        let tag = format!("{ns}.{name}");
1814        let names_type = matches!(
1815            env.globals.lookup_in_ns(ns, name),
1816            Some(Value::Symbol(s)) if s.get().to_string() == tag
1817        );
1818        if names_type {
1819            env.globals.refer_named(dst_ns, ns, &[Arc::from(name)]);
1820            return;
1821        }
1822    }
1823}
1824
1825// ── load-file ─────────────────────────────────────────────────────────────────
1826
1827fn eval_load_file(args: &[Form], env: &mut Env) -> EvalResult {
1828    if args.is_empty() {
1829        return Err(EvalError::Runtime(
1830            "load-file requires a path argument".into(),
1831        ));
1832    }
1833    let path_val = eval(&args[0], env)?;
1834    let path = match &path_val {
1835        Value::Str(s) => s.get().clone(),
1836        v => {
1837            return Err(EvalError::Runtime(format!(
1838                "load-file: expected string, got {}",
1839                v.type_name()
1840            )));
1841        }
1842    };
1843    let src = std::fs::read_to_string(&path)
1844        .map_err(|e| EvalError::Runtime(format!("load-file: {e}")))?;
1845    let mut parser = cljrs_reader::Parser::new(src, path.clone());
1846    let forms = parser
1847        .parse_all()
1848        .map_err(|e| EvalError::Runtime(format!("load-file parse error: {e}")))?;
1849    let mut result = Value::Nil;
1850    for form in forms {
1851        let _alloc_frame = cljrs_gc::push_alloc_frame();
1852        result = eval(&form, env)?;
1853    }
1854    Ok(result)
1855}
1856
1857// ── letfn ─────────────────────────────────────────────────────────────────────
1858
1859fn eval_letfn(args: &[Form], env: &mut Env) -> EvalResult {
1860    push_letfn_frame(args, env)?;
1861    let result = eval_body(&args[1..], env);
1862    env.pop_frame();
1863    result
1864}
1865
1866/// Push a local frame binding every fn of `(letfn [fns…] body…)`, mutually
1867/// visible. On success the caller evaluates the body and pops the frame; on
1868/// error the frame has already been popped.
1869///
1870/// Public so the async evaluator can run the body with a yielding evaluator.
1871pub fn push_letfn_frame(args: &[Form], env: &mut Env) -> EvalResult<()> {
1872    // (letfn [(f [params] body...) ...] body...)
1873    //
1874    // Three passes, because a closure here captures VALUES, not cells:
1875    // `eval_fn` snapshots `env.all_local_bindings()`, so a name bound after the
1876    // closure was built is invisible to it forever. Binding each fn as it was
1877    // built therefore gave letfn `let`-like sequential scope — a backward
1878    // reference resolved, a forward one or a mutual pair raised "unbound
1879    // symbol", which defeats the only reason letfn exists.
1880    let bindings = match args.first().and_then(|f| f.as_vector()) {
1881        Some(v) => expand_reader_conds_cow(v).into_owned(),
1882        None => return Err(EvalError::Runtime("letfn requires a binding vector".into())),
1883    };
1884
1885    env.push_frame();
1886
1887    // Pass 1: bind every name to nil, so the pass-2 snapshot CONTAINS all of
1888    // them. A capture list cannot grow after the fact; it can only be corrected.
1889    for binding in &bindings {
1890        if let Some(parts) = binding.as_list() {
1891            if parts.is_empty() {
1892                continue;
1893            }
1894            let Some(name) = parts[0].as_symbol() else {
1895                env.pop_frame();
1896                return Err(EvalError::Runtime(
1897                    "letfn binding name must be a symbol".into(),
1898                ));
1899            };
1900            env.bind(Arc::from(name), Value::Nil);
1901        }
1902    }
1903
1904    // Pass 2: build the closures. Each captures the real value of any sibling
1905    // already built and nil for the rest.
1906    let mut built: Vec<(Arc<str>, Value)> = Vec::new();
1907    for binding in &bindings {
1908        if let Some(parts) = binding.as_list() {
1909            if parts.is_empty() {
1910                continue;
1911            }
1912            // parts[0] is the name, so eval_fn sees this as a NAMED fn and wires
1913            // up its self-reference; pass 3 handles every other direction.
1914            let fn_val = match eval_fn(parts, env) {
1915                Ok(v) => v,
1916                Err(e) => {
1917                    env.pop_frame();
1918                    return Err(e);
1919                }
1920            };
1921            let name: Arc<str> = match parts[0].as_symbol() {
1922                Some(s) => Arc::from(s),
1923                None => unreachable!("pass 1 rejected every non-symbol name"),
1924            };
1925            env.bind(Arc::clone(&name), fn_val.clone());
1926            built.push((name, fn_val));
1927        }
1928    }
1929
1930    // Pass 3: replace the nil placeholders each closure captured with the
1931    // sibling it actually names. This is what makes the scope MUTUAL rather
1932    // than sequential, and it necessarily builds a reference cycle between
1933    // co-recursive fns — which is inherent to letfn, not an artifact here.
1934    for (_, fn_val) in &built {
1935        if let Value::Fn(ptr) = fn_val {
1936            let mut ptr = ptr.clone();
1937            let f = ptr.get_mut();
1938            for i in 0..f.closed_over_names.len() {
1939                let captured = f.closed_over_names[i].clone();
1940                if let Some((_, real)) = built.iter().find(|(n, _)| *n == captured) {
1941                    f.closed_over_vals[i] = real.clone();
1942                }
1943            }
1944        }
1945    }
1946
1947    Ok(())
1948}
1949
1950// ── in-ns ─────────────────────────────────────────────────────────────────────
1951
1952fn eval_in_ns(args: &[Form], env: &mut Env) -> EvalResult {
1953    // (in-ns 'foo.bar)
1954    if args.is_empty() {
1955        return Err(EvalError::Runtime("in-ns requires a namespace name".into()));
1956    }
1957    let ns_val = eval(&args[0], env)?;
1958    let ns_name = extract_ns_name(&ns_val)?;
1959    env.globals.get_or_create_ns(&ns_name);
1960    env.globals.refer_core(&ns_name);
1961    env.current_ns = Arc::from(ns_name.as_str());
1962    sync_star_ns(env);
1963    let ns_ptr = env.globals.get_or_create_ns(&env.current_ns);
1964    Ok(Value::Namespace(ns_ptr))
1965}
1966
1967// ── alias ─────────────────────────────────────────────────────────────────────
1968
1969fn eval_alias(args: &[Form], env: &mut Env) -> EvalResult {
1970    // (alias 'short 'some.long.ns)
1971    if args.len() < 2 {
1972        return Err(EvalError::Runtime(
1973            "alias requires alias-sym and namespace-sym".into(),
1974        ));
1975    }
1976    let alias_val = eval(&args[0], env)?;
1977    let ns_val = eval(&args[1], env)?;
1978
1979    let alias_name = extract_ns_name(&alias_val)?;
1980    let full_ns = extract_ns_name(&ns_val)?;
1981
1982    let ns_ptr = env.globals.get_or_create_ns(&env.current_ns);
1983    let mut aliases = ns_ptr.get().aliases.lock().unwrap();
1984    aliases.insert(Arc::from(alias_name.as_str()), Arc::from(full_ns.as_str()));
1985    Ok(Value::Nil)
1986}
1987
1988/// Extract a namespace-name string from a Value::Symbol, Value::Str, or Value::Keyword.
1989fn extract_ns_name(v: &Value) -> EvalResult<String> {
1990    match v {
1991        Value::Symbol(s) => {
1992            // Use the full name (e.g. "clojure.core").
1993            Ok(s.get().name.as_ref().to_string())
1994        }
1995        Value::Str(s) => Ok(s.get().clone()),
1996        Value::Keyword(k) => Ok(k.get().name.as_ref().to_string()),
1997        other => Err(EvalError::Runtime(format!(
1998            "expected a symbol or string for namespace name, got {}",
1999            other.type_name()
2000        ))),
2001    }
2002}
2003
2004// ── protocol* ─────────────────────────────────────────────────────────────────
2005
2006/// Read a `{:name "m" :min-arity n :variadic b}` map into a `ProtocolMethod`.
2007fn protocol_method_of(spec: &Value) -> EvalResult<ProtocolMethod> {
2008    let Value::Map(m) = spec.unwrap_meta() else {
2009        return Err(EvalError::Runtime(format!(
2010            "protocol* method spec must be a map, got {}",
2011            spec.type_name()
2012        )));
2013    };
2014    let field = |k: &str| m.get(&Value::keyword(Keyword::simple(k)));
2015    let name: Arc<str> = match field("name") {
2016        Some(Value::Str(s)) => Arc::from(s.get().as_str()),
2017        Some(Value::Symbol(s)) => Arc::from(s.get().name.as_ref()),
2018        Some(Value::Keyword(k)) => Arc::from(k.get().name.as_ref()),
2019        _ => {
2020            return Err(EvalError::Runtime(
2021                "protocol* method spec needs a :name".into(),
2022            ));
2023        }
2024    };
2025    let min_arity = match field("min-arity") {
2026        Some(Value::Long(n)) if n >= 0 => n as usize,
2027        _ => 1,
2028    };
2029    let variadic = !matches!(
2030        field("variadic"),
2031        None | Some(Value::Nil) | Some(Value::Bool(false))
2032    );
2033    Ok(ProtocolMethod {
2034        name,
2035        min_arity,
2036        variadic,
2037    })
2038}
2039
2040/// `(protocol* Name method-specs extend-via-metadata?)` — mint a protocol.
2041///
2042/// `method-specs` evaluates to a sequence of `{:name :min-arity :variadic}`
2043/// maps. The protocol is created in the CURRENT namespace, which is the whole
2044/// reason this stays a special form: `Protocol.ns` is what qualifies a method
2045/// name for extend-via-metadata dispatch, and a builtin fn cannot see the
2046/// environment. The protocol is returned, not interned — `defprotocol` is the
2047/// Clojure macro that `def`s it along with a dispatch fn per method.
2048fn eval_protocol_star(args: &[Form], env: &mut Env) -> EvalResult {
2049    if args.len() < 2 {
2050        return Err(EvalError::Runtime(
2051            "protocol* requires a name and method specs".into(),
2052        ));
2053    }
2054    // Name metadata belongs on the var `defprotocol` binds, not here.
2055    let (name, _) = require_sym_meta(args, 0, "protocol*", env)?;
2056    let specs = crate::interp::eval::eval(&args[1], env)?;
2057    let specs: Vec<Value> = match specs.unwrap_meta() {
2058        Value::Vector(v) => v.get().iter().cloned().collect(),
2059        Value::Nil => Vec::new(),
2060        v => {
2061            return Err(EvalError::Runtime(format!(
2062                "protocol* method specs must be a vector, got {}",
2063                v.type_name()
2064            )));
2065        }
2066    };
2067    let methods = specs
2068        .iter()
2069        .map(protocol_method_of)
2070        .collect::<EvalResult<Vec<_>>>()?;
2071    let extend_via_metadata = match args.get(2) {
2072        Some(f) => !matches!(
2073            crate::interp::eval::eval(f, env)?,
2074            Value::Nil | Value::Bool(false)
2075        ),
2076        None => false,
2077    };
2078    let proto = Protocol::new(
2079        Arc::from(name.as_str()),
2080        env.current_ns.clone(),
2081        methods,
2082        extend_via_metadata,
2083    );
2084    Ok(Value::Protocol(GcPtr::new(proto)))
2085}
2086
2087/// Build a `CljxFn` from the tail of a method-impl list: `(name [params] body...)`.
2088/// `parts[0]` is the method name symbol (ignored here — caller handles it).
2089/// `parts[1]` is the params vector.
2090/// `parts[2..]` is the body.
2091/// Bring a defrecord's FIELDS into scope in a method body, as
2092/// `(let* [f (:f this) ...] body...)`.
2093///
2094/// The bare field symbol is the idiomatic form — `(mutable? [_] (valid-sha? sha))`
2095/// — and it read as an unbound symbol, because a method impl is built as an
2096/// ordinary fn whose only bindings are its own params. Clojure compiles the
2097/// fields as instance fields of the generated class, so they are simply in
2098/// scope.
2099///
2100/// A field whose name a PARAM already takes is skipped: the param shadows the
2101/// field in Clojure, and binding it here would shadow the param instead.
2102/// Returns None when there is nothing to bind, or when the first param is not
2103/// a plain symbol (a destructured `this` has no name to read the fields from).
2104fn synth_field_scope(
2105    params_form: &Form,
2106    fields: &[Arc<str>],
2107    mutable_fields: &[Arc<str>],
2108    body: &[Form],
2109) -> Option<Vec<Form>> {
2110    let param_forms = match &params_form.kind {
2111        FormKind::Vector(v) => v,
2112        _ => return None,
2113    };
2114    let this_name = match param_forms.first().map(|f| &f.kind) {
2115        Some(FormKind::Symbol(s)) => s.clone(),
2116        _ => return None,
2117    };
2118    let param_names: Vec<&str> = param_forms
2119        .iter()
2120        .filter_map(|f| match &f.kind {
2121            FormKind::Symbol(s) => Some(s.as_str()),
2122            _ => None,
2123        })
2124        .collect();
2125
2126    let span = params_form.span.clone();
2127    let is_mut = |name: &str| mutable_fields.iter().any(|m| m.as_ref() == name);
2128    let mut bindings: Vec<Form> = Vec::new();
2129    for field in fields {
2130        if param_names.contains(&field.as_ref()) {
2131            continue;
2132        }
2133        bindings.push(Form::new(FormKind::Symbol(field.to_string()), span.clone()));
2134        // A mutable field reads through `(.-field this)` — the live cell; an
2135        // immutable one through `(:field this)` — the field map. Both snapshot
2136        // at method entry; `set!` refreshes the local for later reads.
2137        let accessor = if is_mut(field) {
2138            FormKind::List(vec![
2139                Form::new(FormKind::Symbol(format!(".-{field}")), span.clone()),
2140                Form::new(FormKind::Symbol(this_name.clone()), span.clone()),
2141            ])
2142        } else {
2143            FormKind::List(vec![
2144                Form::new(FormKind::Keyword(field.to_string()), span.clone()),
2145                Form::new(FormKind::Symbol(this_name.clone()), span.clone()),
2146            ])
2147        };
2148        bindings.push(Form::new(accessor, span.clone()));
2149    }
2150    // With mutable fields present, bind a hidden handle to `this` so `set!` can
2151    // find the instance whose cell to update.
2152    if !mutable_fields.is_empty() {
2153        bindings.push(Form::new(
2154            FormKind::Symbol(DEFTYPE_SELF.to_string()),
2155            span.clone(),
2156        ));
2157        bindings.push(Form::new(FormKind::Symbol(this_name.clone()), span.clone()));
2158    }
2159    if bindings.is_empty() {
2160        return None;
2161    }
2162
2163    let mut let_forms = vec![
2164        Form::new(FormKind::Symbol("let*".to_string()), span.clone()),
2165        Form::new(FormKind::Vector(bindings), span.clone()),
2166    ];
2167    let_forms.extend_from_slice(body);
2168    Some(vec![Form::new(FormKind::List(let_forms), span)])
2169}
2170
2171fn build_impl_fn(
2172    parts: &[Form],
2173    fields: &[Arc<str>],
2174    mutable_fields: &[Arc<str>],
2175    env: &mut Env,
2176) -> EvalResult<Value> {
2177    if parts.len() < 2 {
2178        return Err(EvalError::Runtime(
2179            "protocol method impl requires params and body".into(),
2180        ));
2181    }
2182    // parts[1] should be the params vector.
2183    let params_form = &parts[1];
2184    let body = &parts[2..];
2185    let scoped;
2186    let body: &[Form] = if fields.is_empty() {
2187        body
2188    } else {
2189        match synth_field_scope(params_form, fields, mutable_fields, body) {
2190            Some(v) => {
2191                scoped = v;
2192                &scoped
2193            }
2194            None => body,
2195        }
2196    };
2197    let arity = parse_arity(params_form, body)?;
2198    let (closed_over_names, closed_over_vals) = env.all_local_bindings();
2199    let fn_name: Option<Arc<str>> = parts[0].as_symbol().map(Arc::from);
2200    let cljrs_fn = CljxFn::new(
2201        fn_name,
2202        vec![arity],
2203        closed_over_names,
2204        closed_over_vals,
2205        false,
2206        Arc::clone(&env.current_ns),
2207    );
2208    Ok(Value::Fn(GcPtr::new(cljrs_fn)))
2209}
2210
2211// ── binding ───────────────────────────────────────────────────────────────────
2212
2213fn eval_binding(args: &[Form], env: &mut Env) -> EvalResult {
2214    let pairs = match args.first().and_then(|f| f.as_vector()) {
2215        Some(v) => expand_pairs(v)
2216            .map_err(|_| EvalError::Runtime("binding vector must have even count".into()))?
2217            .into_owned(),
2218        None => return Err(EvalError::Runtime("binding requires a vector".into())),
2219    };
2220
2221    let mut frame: HashMap<usize, Value> = HashMap::new();
2222    for pair in pairs.chunks(2) {
2223        let Some(sym_str) = pair[0].as_symbol() else {
2224            return Err(EvalError::Runtime("binding targets must be symbols".into()));
2225        };
2226        let parsed = cljrs_value::Symbol::parse(sym_str);
2227        let ns_part: Arc<str> = env.resolve_ns_or_current(parsed.namespace.as_deref());
2228        let var_ptr = env
2229            .globals
2230            .lookup_var_in_ns(&ns_part, &parsed.name)
2231            .ok_or_else(|| EvalError::UnboundSymbol(sym_str.to_string()))?;
2232        let val = eval(&pair[1], env)?;
2233        frame.insert(crate::env::dynamics::var_key_of(&var_ptr), val);
2234    }
2235
2236    let _guard = crate::env::dynamics::push_frame(frame);
2237    eval_body(&args[1..], env)
2238    // _guard drops here → pop_frame()
2239}
2240
2241// ── deftype / defrecord shared construction ─────────────────────────────────────
2242
2243/// Hidden `let*` binding a `deftype` method body carries when the type has
2244/// mutable fields: a handle to `this`, so `set!` can locate the instance whose
2245/// interior-mutable cell to update.
2246const DEFTYPE_SELF: &str = "__deftype_self__";
2247
2248/// Does a field's `^meta` mark it `^:unsynchronized-mutable` or
2249/// `^:volatile-mutable`? Instances are single-threaded here, so the two are
2250/// treated identically — only whether the field is mutable at all matters.
2251fn meta_form_is_mutable(meta: &Form) -> bool {
2252    let is_mut_kw = |k: &str| k == "unsynchronized-mutable" || k == "volatile-mutable";
2253    match &meta.kind {
2254        FormKind::Keyword(k) => is_mut_kw(k),
2255        FormKind::Map(entries) => entries.chunks(2).any(|kv| {
2256            matches!(&kv[0].kind, FormKind::Keyword(k) if is_mut_kw(k))
2257                && !matches!(
2258                    kv.get(1).map(|f| &f.kind),
2259                    None | Some(FormKind::Bool(false)) | Some(FormKind::Nil)
2260                )
2261        }),
2262        // Metadata that reached this form through a macro is QUOTED: it is
2263        // already a value, and re-analysing it would resolve its contents as
2264        // code (see `value_to_form`). A field vector written in source arrives
2265        // unquoted; one a macro emitted arrives quoted. Both describe the same
2266        // field, so both have to be read.
2267        FormKind::Quote(inner) => meta_form_is_mutable(inner),
2268        _ => false,
2269    }
2270}
2271
2272/// A single field spec: its name and whether it is mutable.
2273fn field_spec_of(form: &Form) -> Option<(Arc<str>, bool)> {
2274    match &form.kind {
2275        FormKind::Symbol(s) => Some((Arc::from(s.as_str()), false)),
2276        FormKind::Meta(meta, inner) => {
2277            let here = meta_form_is_mutable(meta);
2278            field_spec_of(inner).map(|(name, inner_mut)| (name, inner_mut || here))
2279        }
2280        _ => None,
2281    }
2282}
2283
2284/// Parse a `deftype` `[field ...]` vector into `(name, mutable?)` specs.
2285fn parse_field_specs(form: &Form, ctx: &str) -> EvalResult<Vec<(Arc<str>, bool)>> {
2286    // `as_vector` reports the shape under any `^meta`, so a marker on the
2287    // vector itself — `(defrecord R ^:marker [x])` — stays transparent.
2288    let Some(fields) = form.as_vector() else {
2289        return Err(EvalError::Runtime(format!(
2290            "{ctx} requires a field vector as second arg"
2291        )));
2292    };
2293    fields
2294        .iter()
2295        .map(|f| {
2296            field_spec_of(f)
2297                .ok_or_else(|| EvalError::Runtime(format!("{ctx} field names must be symbols")))
2298        })
2299        .collect()
2300}
2301
2302// ── deftype* (defrecord / deftype are bootstrap macros over it) ─────────────────
2303
2304/// The irreducible datatype primitive: mint a type tag and register method impls
2305/// against it, with the fields in scope in each body and mutable fields backed by
2306/// live cells. It does NOT synthesise constructors or intern the type symbol —
2307/// that sugar lives in the `deftype` bootstrap macro (`->T`, `(def T 'T)`), which
2308/// is what makes deftype a particular case of a Clojure macro over this form.
2309fn eval_deftype_star(args: &[Form], env: &mut Env) -> EvalResult {
2310    // (deftype* TypeName [field ...] Proto (method [this] body) ...)
2311    if args.len() < 2 {
2312        return Err(EvalError::Runtime(
2313            "deftype* requires a name and field vector".into(),
2314        ));
2315    }
2316    // Type metadata (e.g. ^:private) has no var to hold it; unwrapped so the
2317    // name reads, and deliberately not applied anywhere it would not belong.
2318    let (type_name, _) = require_sym_meta(args, 0, "deftype*", env)?;
2319    let type_tag = qualified_type_tag(&type_name, env);
2320
2321    let specs = parse_field_specs(&args[1], "deftype*")?;
2322    let field_names: Vec<Arc<str>> = specs.iter().map(|(n, _)| n.clone()).collect();
2323    let mutable_names: Vec<Arc<str>> = specs
2324        .iter()
2325        .filter(|(_, m)| *m)
2326        .map(|(n, _)| n.clone())
2327        .collect();
2328
2329    // Register protocol/interface method impls, with the fields in scope in
2330    // each body — same machinery as defrecord/reify. Mutable fields read
2331    // through the live cell and are writable with `set!`.
2332    register_impls_for_tag(&type_tag, &args[2..], &field_names, &mutable_names, env)?;
2333    // Return the minted tag so a caller (e.g. the `reify` macro) can feed it
2334    // straight to `make-type-instance` — a single dataflow source for the tag,
2335    // rather than a second textual reference that a gensym could desync.
2336    Ok(Value::string(type_tag.to_string()))
2337}
2338
2339/// The dispatch tag of a record or deftype: its name qualified by the defining
2340/// namespace, `my.ns.Point`, as the JVM names the generated class. Two
2341/// namespaces may each define a `Point`; with a bare-name tag the second one's
2342/// protocol impls replaced the first one's, and `instance?` could not tell
2343/// their instances apart.
2344fn qualified_type_tag(type_name: &str, env: &Env) -> Arc<str> {
2345    Arc::from(format!("{}.{}", env.current_ns, type_name))
2346}
2347
2348// ── register_impls_for_tag ────────────────────────────────────────────────────
2349
2350/// Resolve a protocol NAME symbol in an impl position (extend-type, extend-protocol,
2351/// reify/defrecord), honouring the current namespace's `:require :as` aliases and
2352/// fully-qualified names — not just an unqualified lookup in the current ns.
2353///
2354/// `(defrecord R [] mp/IThing (-do [_] ...))` failed with "mp/IThing is not a
2355/// protocol" even though the protocol was loaded and (resolve 'mini.proto/IThing)
2356/// was truthy: the old code looked up the whole string "mp/IThing" as an intern of
2357/// the CURRENT ns, where it is neither interned nor referred. A qualified protocol
2358/// symbol must resolve through its own namespace, exactly as `eval` resolves any
2359/// other qualified symbol.
2360fn resolve_protocol_sym(env: &Env, s: &str) -> Option<GcPtr<Protocol>> {
2361    let parsed = cljrs_value::Symbol::parse(s);
2362    let val = match parsed.namespace.as_deref() {
2363        Some(ns_part) => {
2364            let ns = env.resolve_ns_part(ns_part);
2365            env.globals.lookup_in_ns(&ns, &parsed.name)
2366        }
2367        None => env.globals.lookup_in_ns(&env.current_ns, s),
2368    };
2369    match val {
2370        Some(Value::Protocol(p)) => Some(p),
2371        _ => None,
2372    }
2373}
2374
2375/// Parse `Proto (method [params] body) ...` segments and register them under `type_tag`.
2376/// Shared by `defrecord` and `reify`.
2377fn register_impls_for_tag(
2378    type_tag: &Arc<str>,
2379    forms: &[Form],
2380    fields: &[Arc<str>],
2381    mutable_fields: &[Arc<str>],
2382    env: &mut Env,
2383) -> EvalResult<()> {
2384    let mut current_proto: Option<GcPtr<cljrs_value::Protocol>> = None;
2385
2386    for form in forms {
2387        match &form.unmeta().kind {
2388            FormKind::Symbol(s) => match resolve_protocol_sym(env, s) {
2389                Some(p) => current_proto = Some(p),
2390                None => {
2391                    return Err(EvalError::Runtime(format!(
2392                        "reify/defrecord: {} is not a protocol",
2393                        s
2394                    )));
2395                }
2396            },
2397            FormKind::List(parts) => {
2398                let proto = current_proto.as_ref().ok_or_else(|| {
2399                    EvalError::Runtime("reify/defrecord: method impl before protocol name".into())
2400                })?;
2401                if parts.is_empty() {
2402                    continue;
2403                }
2404                let method_name: Arc<str> = match parts[0].as_symbol() {
2405                    Some(s) => Arc::from(s),
2406                    None => continue,
2407                };
2408                let fn_val = build_impl_fn(parts, fields, mutable_fields, env)?;
2409                let mut impls = proto.get().impls.lock().unwrap();
2410                impls
2411                    .entry(type_tag.clone())
2412                    .or_default()
2413                    .insert(method_name, fn_val);
2414                drop(impls);
2415                cljrs_value::bump_protocol_generation();
2416            }
2417            _ => {}
2418        }
2419    }
2420    Ok(())
2421}
2422
2423// ── helpers ───────────────────────────────────────────────────────────────────
2424
2425/// Update the root binding of `*ns*` in `clojure.core` to the current namespace.
2426/// Called whenever `env.current_ns` changes (ns, in-ns, standard_env setup).
2427pub fn sync_star_ns(env: &mut Env) {
2428    if let Some(star_ns_var) = env.globals.lookup_var("clojure.core", "*ns*") {
2429        let ns_ptr = env.globals.get_or_create_ns(&env.current_ns);
2430        star_ns_var.get().bind(Value::Namespace(ns_ptr));
2431    }
2432}
2433
2434/// The symbol at `idx`, unwrapping any `^meta` wrapper, together with the
2435/// metadata it carried.
2436///
2437/// `(defprotocol ^:private Driver ...)` reads as `Meta(:private, Symbol("Driver"))`,
2438/// so matching `FormKind::Symbol` alone rejects a form Clojure accepts. malli's
2439/// `malli.impl.regex` opens with five such protocols, which made all of malli
2440/// unloadable.
2441///
2442/// The metadata is RETURNED rather than discarded: dropping it would trade a
2443/// loud error for a silent loss of `^:private`, and callers that intern a var
2444/// attach it there.
2445fn require_sym_meta(
2446    args: &[Form],
2447    idx: usize,
2448    form_name: &str,
2449    env: &mut Env,
2450) -> EvalResult<(String, Option<Value>)> {
2451    fn peel(form: &Form, env: &mut Env) -> EvalResult<Option<(String, Option<Value>)>> {
2452        match &form.kind {
2453            FormKind::Symbol(s) => Ok(Some((s.clone(), None))),
2454            // `^:a ^:b x` nests Meta forms; unwrap all, outer mark winning.
2455            FormKind::Meta(meta_form, inner) => {
2456                let meta_val = compile_meta_form(meta_form, env)?;
2457                match peel(inner, env)? {
2458                    Some((name, inner_meta)) => {
2459                        Ok(Some((name, merge_meta(inner_meta, Some(meta_val)))))
2460                    }
2461                    None => Ok(None),
2462                }
2463            }
2464            _ => Ok(None),
2465        }
2466    }
2467    let err = || EvalError::Runtime(format!("{form_name} requires a symbol at position {idx}"));
2468    match args.get(idx) {
2469        Some(form) => peel(form, env)?.ok_or_else(err),
2470        None => Err(err()),
2471    }
2472}
2473
2474// ── with-out-str ──────────────────────────────────────────────────────────────
2475
2476fn eval_with_out_str(body: &[Form], env: &mut Env) -> EvalResult {
2477    crate::builtins::builtins::push_output_capture();
2478    let result = eval_body(body, env);
2479    let captured = crate::builtins::builtins::pop_output_capture().unwrap_or_default();
2480    // Propagate errors but still pop the capture buffer
2481    result?;
2482    Ok(Value::string(captured))
2483}
2484
2485// ── await ─────────────────────────────────────────────────────────────────────
2486
2487/// Blocking deref in sync context; yielding deref in async context.
2488///
2489/// When `cljrs-async` is loaded, `eval_async` intercepts `await` forms before
2490/// the sync evaluator reaches this handler, so this path is only taken in
2491/// non-async (sync) code. It blocks the OS thread until the future/promise
2492/// resolves — equivalent to `(deref val)`.
2493fn eval_await(args: &[Form], env: &mut Env) -> EvalResult {
2494    if args.is_empty() {
2495        return Err(EvalError::Runtime("await requires one argument".into()));
2496    }
2497    let val = eval(&args[0], env)?;
2498    match val {
2499        Value::Future(f) => {
2500            let mut guard = f.get().state.lock().unwrap();
2501            loop {
2502                match &*guard {
2503                    FutureState::Done(v) => {
2504                        f.get().mark_observed();
2505                        return Ok(v.clone());
2506                    }
2507                    FutureState::Failed(v) => {
2508                        f.get().mark_observed();
2509                        return Err(EvalError::Thrown(v.clone()));
2510                    }
2511                    FutureState::GasExhausted => {
2512                        f.get().mark_observed();
2513                        return Err(EvalError::GasExhausted);
2514                    }
2515                    FutureState::Cancelled => {
2516                        return Err(EvalError::Thrown(CljxFuture::cancelled_error()));
2517                    }
2518                    FutureState::Running => {
2519                        guard = f.get().cond.wait(guard).unwrap();
2520                    }
2521                }
2522            }
2523        }
2524        Value::Promise(p) => Ok(p.get().deref_blocking()),
2525        other => Ok(other),
2526    }
2527}