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