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