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