Skip to main content

cljrs_value/
types.rs

1//! Stub types for Phase 4/7 that are referenced by the Value enum.
2
3#![allow(unused)]
4
5use std::collections::HashMap;
6use std::mem;
7use std::sync::{Arc, Condvar, Mutex};
8
9use cljrs_gc::GcPtr;
10use cljrs_reader::Form;
11
12use crate::Value;
13
14// ── No-GC debug provenance helper ────────────────────────────────────────────
15
16/// In `no-gc` debug builds: return `true` if the top-level `GcPtr` inside
17/// `value` (if any) was allocated by the global `StaticArena`.
18///
19/// Primitives (`Nil`, `Bool`, `Long`, `Double`, `Char`) contain no `GcPtr`
20/// and always return `true`.  `Resource` is Arc-managed and also returns
21/// `true`.  All other variants have a `GcPtr` that is checked against the
22/// static arena's chunk range.
23///
24/// This check is intentionally **shallow** (top-level pointer only).  If the
25/// value was produced inside a `StaticCtxGuard`, ALL allocations during its
26/// evaluation go to the static arena — so a static top-level pointer implies
27/// static contents.
28#[cfg(all(feature = "no-gc", debug_assertions))]
29pub(crate) fn value_gcptr_is_static(value: &Value) -> bool {
30    use crate::value::MapValue;
31    use crate::value::SetValue;
32    match value {
33        // Inline scalars — no GcPtr.
34        Value::Nil
35        | Value::Bool(_)
36        | Value::Long(_)
37        | Value::Double(_)
38        | Value::Char(_)
39        | Value::Uuid(_) => true,
40        // Arc-managed — not GcPtr.
41        Value::Resource(_) => true,
42        // GcPtr variants.
43        Value::BigInt(p) => p.is_static_alloc(),
44        Value::BigDecimal(p) => p.is_static_alloc(),
45        Value::Ratio(p) => p.is_static_alloc(),
46        Value::Str(p) => p.is_static_alloc(),
47        Value::Pattern(p) => p.is_static_alloc(),
48        Value::Matcher(p) => p.is_static_alloc(),
49        Value::Symbol(p) => p.is_static_alloc(),
50        Value::Keyword(p) => p.is_static_alloc(),
51        Value::List(p) => p.is_static_alloc(),
52        Value::Vector(p) => p.is_static_alloc(),
53        Value::Queue(p) => p.is_static_alloc(),
54        Value::Map(m) => match m {
55            MapValue::Array(p) => p.is_static_alloc(),
56            MapValue::Hash(p) => p.is_static_alloc(),
57            MapValue::Sorted(p) => p.is_static_alloc(),
58        },
59        Value::Set(s) => match s {
60            SetValue::Hash(p) => p.is_static_alloc(),
61            SetValue::Sorted(p) => p.is_static_alloc(),
62        },
63        Value::NativeFunction(p) => p.is_static_alloc(),
64        Value::Fn(p) | Value::Macro(p) => p.is_static_alloc(),
65        Value::BoundFn(p) => p.is_static_alloc(),
66        Value::Var(p) => p.is_static_alloc(),
67        Value::Atom(p) => p.is_static_alloc(),
68        Value::Namespace(p) => p.is_static_alloc(),
69        Value::LazySeq(p) => p.is_static_alloc(),
70        Value::Cons(p) => p.is_static_alloc(),
71        Value::Protocol(p) => p.is_static_alloc(),
72        Value::ProtocolFn(p) => p.is_static_alloc(),
73        Value::MultiFn(p) => p.is_static_alloc(),
74        Value::Volatile(p) => p.is_static_alloc(),
75        Value::Delay(p) => p.is_static_alloc(),
76        Value::Promise(p) => p.is_static_alloc(),
77        Value::Future(p) => p.is_static_alloc(),
78        Value::Agent(p) => p.is_static_alloc(),
79        Value::TypeInstance(p) => p.is_static_alloc(),
80        Value::ObjectArray(p) => p.is_static_alloc(),
81        Value::NativeObject(p) => p.is_static_alloc(),
82        Value::Error(p) => p.is_static_alloc(),
83        Value::TransientMap(p) => p.is_static_alloc(),
84        Value::TransientVector(p) => p.is_static_alloc(),
85        Value::TransientSet(p) => p.is_static_alloc(),
86        // Primitive arrays — no meaningful pointer check needed.
87        Value::BooleanArray(_)
88        | Value::ByteArray(_)
89        | Value::ShortArray(_)
90        | Value::IntArray(_)
91        | Value::LongArray(_)
92        | Value::FloatArray(_)
93        | Value::DoubleArray(_)
94        | Value::CharArray(_) => true,
95        // Wrapper variants.
96        Value::Reduced(inner) | Value::WithMeta(inner, _) => value_gcptr_is_static(inner),
97    }
98}
99
100// ── Protocol ──────────────────────────────────────────────────────────────────
101
102/// Inner map type for protocol implementations: method_name → impl fn.
103pub type MethodMap = HashMap<Arc<str>, Value>;
104
105/// A Clojure protocol — an interface-like construct with named methods.
106#[derive(Debug)]
107pub struct Protocol {
108    pub name: Arc<str>,
109    pub ns: Arc<str>,
110    pub methods: Vec<ProtocolMethod>,
111    /// type_tag → { method_name → impl fn }
112    pub impls: Mutex<HashMap<Arc<str>, MethodMap>>,
113}
114
115impl Protocol {
116    pub fn new(name: Arc<str>, ns: Arc<str>, methods: Vec<ProtocolMethod>) -> Self {
117        Self {
118            name,
119            ns,
120            methods,
121            impls: Mutex::new(HashMap::new()),
122        }
123    }
124}
125
126impl cljrs_gc::Trace for Protocol {
127    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
128        {
129            let impls = self.impls.lock().unwrap();
130            for method_map in impls.values() {
131                for v in method_map.values() {
132                    v.trace(visitor);
133                }
134            }
135        }
136    }
137}
138
139/// One method signature declared in a `defprotocol`.
140#[derive(Debug, Clone)]
141pub struct ProtocolMethod {
142    pub name: Arc<str>,
143    pub min_arity: usize,
144    pub variadic: bool,
145}
146
147impl cljrs_gc::Trace for ProtocolMethod {
148    fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
149}
150
151// ── ProtocolFn ────────────────────────────────────────────────────────────────
152
153/// Callable that dispatches a single protocol method on the type of `args[0]`.
154#[derive(Debug)]
155pub struct ProtocolFn {
156    pub protocol: GcPtr<Protocol>,
157    pub method_name: Arc<str>,
158    pub min_arity: usize,
159    pub variadic: bool,
160}
161
162impl cljrs_gc::Trace for ProtocolFn {
163    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
164        use cljrs_gc::GcVisitor as _;
165        visitor.visit(&self.protocol);
166    }
167}
168
169// ── MultiFn ───────────────────────────────────────────────────────────────────
170
171/// A Clojure multimethod — arbitrary dispatch via a user-supplied function.
172#[derive(Debug)]
173pub struct MultiFn {
174    pub name: Arc<str>,
175    pub dispatch_fn: Value,
176    /// pr_str(dispatch-val) → implementation fn
177    pub methods: Mutex<HashMap<String, Value>>,
178    /// recorded preferences (for future derive/hierarchy)
179    pub prefers: Mutex<HashMap<String, Vec<String>>>,
180    /// normally ":default"
181    pub default_dispatch: String,
182}
183
184impl MultiFn {
185    pub fn new(name: Arc<str>, dispatch_fn: Value, default_dispatch: String) -> Self {
186        Self {
187            name,
188            dispatch_fn,
189            methods: Mutex::new(HashMap::new()),
190            prefers: Mutex::new(HashMap::new()),
191            default_dispatch,
192        }
193    }
194}
195
196impl cljrs_gc::Trace for MultiFn {
197    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
198        self.dispatch_fn.trace(visitor);
199        {
200            let methods = self.methods.lock().unwrap();
201            for v in methods.values() {
202                v.trace(visitor);
203            }
204        }
205    }
206}
207
208// ── Var ───────────────────────────────────────────────────────────────────────
209
210/// A Clojure var — a namespace-interned mutable root binding.
211#[derive(Debug)]
212pub struct Var {
213    pub namespace: Arc<str>,
214    pub name: Arc<str>,
215    pub value: Mutex<Option<Value>>,
216    pub is_macro: bool,
217    /// Metadata map (e.g. `{:dynamic true}`).
218    pub meta: Mutex<Option<Value>>,
219    pub watches: Mutex<Vec<(Value, Value)>>,
220}
221
222impl Var {
223    pub fn new(namespace: impl Into<Arc<str>>, name: impl Into<Arc<str>>) -> Self {
224        Self {
225            namespace: namespace.into(),
226            name: name.into(),
227            value: Mutex::new(None),
228            is_macro: false,
229            meta: Mutex::new(None),
230            watches: Mutex::new(Vec::new()),
231        }
232    }
233
234    pub fn is_bound(&self) -> bool {
235        self.value.lock().unwrap().is_some()
236    }
237
238    pub fn deref(&self) -> Option<Value> {
239        self.value.lock().unwrap().clone()
240    }
241
242    pub fn bind(&self, v: Value) {
243        // In no-gc debug builds: assert the value being stored in this
244        // program-lifetime Var came from the StaticArena, not a scratch region.
245        // A region-local pointer would dangle after the function returns.
246        #[cfg(all(feature = "no-gc", debug_assertions))]
247        debug_assert!(
248            value_gcptr_is_static(&v),
249            "no-gc: Var::bind({}/{}) received a region-local value — store violations \
250             indicate a missing StaticCtxGuard around the value expression",
251            self.namespace,
252            self.name
253        );
254        *self.value.lock().unwrap() = Some(v);
255    }
256
257    pub fn get_meta(&self) -> Option<Value> {
258        self.meta.lock().unwrap().clone()
259    }
260
261    pub fn set_meta(&self, m: Value) {
262        *self.meta.lock().unwrap() = Some(m);
263    }
264
265    pub fn full_name(&self) -> String {
266        format!("{}/{}", self.namespace, self.name)
267    }
268}
269
270impl cljrs_gc::Trace for Var {
271    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
272        {
273            let value = self.value.lock().unwrap();
274            if let Some(v) = value.as_ref() {
275                v.trace(visitor);
276            }
277        }
278        {
279            let meta = self.meta.lock().unwrap();
280            if let Some(m) = meta.as_ref() {
281                m.trace(visitor);
282            }
283        }
284        {
285            let watches = self.watches.lock().unwrap();
286            for (key, f) in watches.iter() {
287                key.trace(visitor);
288                f.trace(visitor);
289            }
290        }
291    }
292}
293
294// ── Atom ──────────────────────────────────────────────────────────────────────
295
296/// A Clojure atom — a thread-safe mutable reference.
297#[derive(Debug)]
298pub struct Atom {
299    pub value: Mutex<Value>,
300    pub meta: Mutex<Option<Value>>,
301    pub validator: Mutex<Option<Value>>,
302    pub watches: Mutex<Vec<(Value, Value)>>,
303}
304
305impl Atom {
306    pub fn new(v: Value) -> Self {
307        Self {
308            value: Mutex::new(v),
309            meta: Mutex::new(None),
310            validator: Mutex::new(None),
311            watches: Mutex::new(Vec::new()),
312        }
313    }
314
315    pub fn deref(&self) -> Value {
316        self.value.lock().unwrap().clone()
317    }
318
319    pub fn reset(&self, v: Value) -> Value {
320        // In no-gc debug builds: assert the new value came from the StaticArena.
321        #[cfg(all(feature = "no-gc", debug_assertions))]
322        debug_assert!(
323            value_gcptr_is_static(&v),
324            "no-gc: Atom::reset() received a region-local value — the new-value \
325             expression must be computed inside a StaticCtxGuard (i.e. inside \
326             the swap! / reset! call) so it is allocated in the static arena"
327        );
328        let mut guard = self.value.lock().unwrap();
329        *guard = v.clone();
330        v
331    }
332
333    pub fn get_meta(&self) -> Option<Value> {
334        self.meta.lock().unwrap().clone()
335    }
336
337    pub fn set_meta(&self, m: Option<Value>) {
338        *self.meta.lock().unwrap() = m;
339    }
340
341    pub fn get_validator(&self) -> Option<Value> {
342        self.validator.lock().unwrap().clone()
343    }
344
345    pub fn set_validator(&self, vf: Option<Value>) {
346        *self.validator.lock().unwrap() = vf;
347    }
348}
349
350impl cljrs_gc::Trace for Atom {
351    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
352        {
353            let value = self.value.lock().unwrap();
354            value.trace(visitor);
355        }
356        {
357            let meta = self.meta.lock().unwrap();
358            if let Some(m) = meta.as_ref() {
359                m.trace(visitor);
360            }
361        }
362        {
363            let validator = self.validator.lock().unwrap();
364            if let Some(vf) = validator.as_ref() {
365                vf.trace(visitor);
366            }
367        }
368        {
369            let watches = self.watches.lock().unwrap();
370            for (key, f) in watches.iter() {
371                key.trace(visitor);
372                f.trace(visitor);
373            }
374        }
375    }
376}
377
378// ── Namespace ─────────────────────────────────────────────────────────────────
379
380/// A Clojure namespace with intern table, refers, and aliases.
381#[derive(Debug)]
382pub struct Namespace {
383    pub name: Arc<str>,
384    /// Vars interned directly in this namespace.
385    pub interns: Mutex<HashMap<Arc<str>, GcPtr<Var>>>,
386    /// Vars referred from other namespaces (e.g. clojure.core).
387    pub refers: Mutex<HashMap<Arc<str>, GcPtr<Var>>>,
388    /// Namespace aliases: short-name → full namespace name.
389    pub aliases: Mutex<HashMap<Arc<str>, Arc<str>>>,
390    /// Absolute path of the source file this namespace was loaded from,
391    /// populated by the loader.  Used by the versioned resolver to locate the
392    /// file for `git show`.
393    pub source_file: Mutex<Option<Arc<str>>>,
394    /// Absolute path of the git repository root that contains `source_file`.
395    pub git_repo_root: Mutex<Option<Arc<str>>>,
396    /// `true` for namespaces loaded from a specific commit (`name@hash`).
397    /// Versioned namespaces are immutable: `intern()` will refuse new bindings.
398    pub is_versioned: bool,
399}
400
401impl Namespace {
402    pub fn new(name: impl Into<Arc<str>>) -> Self {
403        Self {
404            name: name.into(),
405            interns: Mutex::new(HashMap::new()),
406            refers: Mutex::new(HashMap::new()),
407            aliases: Mutex::new(HashMap::new()),
408            source_file: Mutex::new(None),
409            git_repo_root: Mutex::new(None),
410            is_versioned: false,
411        }
412    }
413
414    /// Create a versioned (immutable) namespace for `name@commit`.
415    pub fn new_versioned(name: impl Into<Arc<str>>) -> Self {
416        Self {
417            is_versioned: true,
418            ..Self::new(name)
419        }
420    }
421
422    /// Record the source file path and its git repo root (if in a repo).
423    pub fn set_source_location(&self, file: &str, repo_root: Option<&str>) {
424        *self.source_file.lock().unwrap() = Some(Arc::from(file));
425        *self.git_repo_root.lock().unwrap() = repo_root.map(Arc::from);
426    }
427}
428
429impl cljrs_gc::Trace for Namespace {
430    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
431        use cljrs_gc::GcVisitor as _;
432        {
433            let interns = self.interns.lock().unwrap();
434            for var in interns.values() {
435                visitor.visit(var);
436            }
437        }
438        {
439            let refers = self.refers.lock().unwrap();
440            for var in refers.values() {
441                visitor.visit(var);
442            }
443        }
444    }
445}
446
447// ── NativeFn ──────────────────────────────────────────────────────────────────
448
449/// A Rust function callable from Clojure.
450/// Legacy type alias kept for source compatibility. Bare `fn` pointers
451/// implement `Fn` and can be passed anywhere a `NativeFnFunc` is expected.
452pub type NativeFnPtr = fn(&[Value]) -> crate::error::ValueResult<Value>;
453
454/// The callable stored inside a `NativeFn`. Supports both bare function
455/// pointers and closures that capture state.
456pub type NativeFnFunc = Arc<dyn Fn(&[Value]) -> crate::error::ValueResult<Value> + Send + Sync>;
457
458#[derive(Clone, Debug)]
459pub enum Arity {
460    Fixed(usize),
461    Variadic { min: usize },
462}
463
464pub struct NativeFn {
465    pub name: Arc<str>,
466    pub arity: Arity,
467    pub func: NativeFnFunc,
468}
469
470impl NativeFn {
471    /// Create from a bare function pointer (backwards-compatible).
472    pub fn new(name: impl Into<Arc<str>>, arity: Arity, func: NativeFnPtr) -> Self {
473        Self {
474            name: name.into(),
475            arity,
476            func: Arc::new(func),
477        }
478    }
479
480    /// Create from a closure or any `Fn(&[Value]) -> ValueResult<Value>`.
481    pub fn with_closure(
482        name: impl Into<Arc<str>>,
483        arity: Arity,
484        func: impl Fn(&[Value]) -> crate::error::ValueResult<Value> + Send + Sync + 'static,
485    ) -> Self {
486        Self {
487            name: name.into(),
488            arity,
489            func: Arc::new(func),
490        }
491    }
492}
493
494impl std::fmt::Debug for NativeFn {
495    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496        f.debug_struct("NativeFn")
497            .field("name", &self.name)
498            .field("arity", &self.arity)
499            .field("func", &"<fn>")
500            .finish()
501    }
502}
503
504impl cljrs_gc::Trace for NativeFn {
505    fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
506}
507
508// ── CljxFnArity ───────────────────────────────────────────────────────────────
509
510/// One arity branch of a Clojure function.
511#[derive(Debug, Clone)]
512pub struct CljxFnArity {
513    /// Simple parameter names (no `&`).
514    /// For destructured params, these are gensym'd names.
515    pub params: Vec<Arc<str>>,
516    /// The name after `&`, if any.
517    pub rest_param: Option<Arc<str>>,
518    /// The body forms for this arity.
519    pub body: Vec<Form>,
520    /// Destructuring patterns: (param_index, original_form).
521    /// After binding the gensym'd param, these patterns are applied
522    /// via `bind_pattern` to destructure the value.
523    pub destructure_params: Vec<(usize, Form)>,
524    /// If the rest param is destructured, the original form.
525    pub destructure_rest: Option<Form>,
526    /// Unique ID for IR cache lookup (assigned by the evaluator).
527    pub ir_arity_id: u64,
528}
529
530// ── CljxFn ────────────────────────────────────────────────────────────────────
531
532/// An interpreted Clojure closure with captured environment.
533#[derive(Debug, Clone)]
534pub struct CljxFn {
535    pub name: Option<Arc<str>>,
536    pub arities: Vec<CljxFnArity>,
537    /// Names of closed-over bindings (parallel to `closed_over_vals`).
538    pub closed_over_names: Vec<Arc<str>>,
539    /// Values of closed-over bindings (parallel to `closed_over_names`).
540    pub closed_over_vals: Vec<Value>,
541    /// True if this function was defined with `defmacro`.
542    pub is_macro: bool,
543    /// True if this function carries `^:async` metadata. When an async runtime
544    /// (`cljrs-async`) is registered, calling such a function spawns its body as
545    /// a task and returns a `Value::Future` immediately instead of running it
546    /// synchronously. Without a runtime it runs synchronously like any other fn.
547    pub is_async: bool,
548    /// Namespace in which this function was defined (for macro hygiene).
549    pub defining_ns: Arc<str>,
550}
551
552impl CljxFn {
553    pub fn new(
554        name: Option<Arc<str>>,
555        arities: Vec<CljxFnArity>,
556        closed_over_names: Vec<Arc<str>>,
557        closed_over_vals: Vec<Value>,
558        is_macro: bool,
559        defining_ns: Arc<str>,
560    ) -> Self {
561        Self {
562            name,
563            arities,
564            closed_over_names,
565            closed_over_vals,
566            is_macro,
567            is_async: false,
568            defining_ns,
569        }
570    }
571}
572
573impl cljrs_gc::Trace for CljxFn {
574    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
575        for v in &self.closed_over_vals {
576            v.trace(visitor);
577        }
578    }
579}
580
581// ── BoundFn ──────────────────────────────────────────────────────────────────
582
583/// A function wrapped with captured dynamic bindings.
584/// When called, the captured bindings are pushed as a frame before delegating
585/// to the wrapped function. This means captured bindings override the caller's
586/// for the same var, but vars not in the capture fall through normally.
587#[derive(Debug)]
588pub struct BoundFn {
589    /// The wrapped callable.
590    pub wrapped: Value,
591    /// Captured dynamic bindings (merged flat frame; opaque to cljrs-value).
592    pub captured_bindings: HashMap<usize, Value>,
593}
594
595impl cljrs_gc::Trace for BoundFn {
596    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
597        self.wrapped.trace(visitor);
598        for val in self.captured_bindings.values() {
599            val.trace(visitor);
600        }
601    }
602}
603
604// ── Thunk / LazySeq ───────────────────────────────────────────────────────────
605
606/// A deferred computation that produces a `Value` when forced.
607pub trait Thunk: Send + Sync + std::fmt::Debug + cljrs_gc::Trace {
608    fn force(&self) -> Result<Value, String>;
609}
610
611/// Internal state of a lazy sequence cell.
612pub enum LazySeqState {
613    /// Thunk not yet evaluated.
614    Pending(Box<dyn Thunk>),
615    /// Result cached after first force.
616    Forced(Value),
617    /// Thunk evaluation failed; error message is cached.
618    Error(String),
619}
620
621/// A lazy sequence that forces its thunk exactly once and caches the result.
622pub struct LazySeq {
623    pub state: Mutex<LazySeqState>,
624}
625
626impl LazySeq {
627    pub fn new(thunk: Box<dyn Thunk>) -> Self {
628        Self {
629            state: Mutex::new(LazySeqState::Pending(thunk)),
630        }
631    }
632
633    /// Realize the sequence: force the thunk on first call, return cached value on subsequent calls.
634    /// On error, returns `Value::Nil` and caches the error (retrievable via `error()`).
635    pub fn realize(&self) -> Value {
636        let thunk = {
637            let mut guard = self.state.lock().unwrap();
638            match &*guard {
639                LazySeqState::Forced(v) => return v.clone(),
640                LazySeqState::Error(_) => return Value::Nil,
641                LazySeqState::Pending(_) => {}
642            }
643            // Replace the pending state with a temporary Forced(Nil), extract the thunk.
644            let prev = mem::replace(&mut *guard, LazySeqState::Forced(Value::Nil));
645            let LazySeqState::Pending(thunk) = prev else {
646                unreachable!("state was not Pending")
647            };
648            thunk
649            // guard dropped here — lock released before forcing
650        };
651        // Force the thunk WITHOUT holding the lock. This ensures GC's
652        // lock().unwrap() in LazySeq::trace() will not deadlock.
653        match thunk.force() {
654            Ok(result) => {
655                *self.state.lock().unwrap() = LazySeqState::Forced(result.clone());
656                result
657            }
658            Err(msg) => {
659                *self.state.lock().unwrap() = LazySeqState::Error(msg);
660                Value::Nil
661            }
662        }
663    }
664
665    /// Return the cached error message, if the thunk failed.
666    pub fn error(&self) -> Option<String> {
667        let guard = self.state.lock().unwrap();
668        if let LazySeqState::Error(e) = &*guard {
669            Some(e.clone())
670        } else {
671            None
672        }
673    }
674}
675
676impl std::fmt::Debug for LazySeq {
677    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
678        write!(f, "LazySeq(...)")
679    }
680}
681
682impl cljrs_gc::Trace for LazySeq {
683    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
684        // Safe to lock unconditionally: realize() drops the lock before entering
685        // eval (thunk.force()), so the lock is never held across a GC safepoint.
686        {
687            let state = self.state.lock().unwrap();
688            match &*state {
689                LazySeqState::Pending(thunk) => thunk.trace(visitor),
690                LazySeqState::Forced(v) => v.trace(visitor),
691                LazySeqState::Error(_) => {}
692            }
693        }
694    }
695}
696
697// ── CljxCons ──────────────────────────────────────────────────────────────────
698
699/// A lazy cons cell: head element + tail (may be a `LazySeq`, `List`, or `Nil`).
700///
701/// Used when `cons` is called with a `LazySeq` or `Cons` tail, enabling lazy
702/// sequences without eagerly realizing them.
703#[derive(Debug, Clone)]
704pub struct CljxCons {
705    pub head: Value,
706    pub tail: Value,
707}
708
709impl cljrs_gc::Trace for CljxCons {
710    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
711        self.head.trace(visitor);
712        self.tail.trace(visitor);
713    }
714}
715
716// ── Volatile ──────────────────────────────────────────────────────────────────
717
718/// Non-atomic mutable cell (single-thread performance, no CAS).
719pub struct Volatile {
720    pub value: Mutex<Value>,
721}
722
723impl Volatile {
724    pub fn new(v: Value) -> Self {
725        Self {
726            value: Mutex::new(v),
727        }
728    }
729
730    pub fn deref(&self) -> Value {
731        self.value.lock().unwrap().clone()
732    }
733
734    pub fn reset(&self, v: Value) -> Value {
735        // In no-gc debug builds: assert the new value came from the StaticArena.
736        #[cfg(all(feature = "no-gc", debug_assertions))]
737        debug_assert!(
738            value_gcptr_is_static(&v),
739            "no-gc: Volatile::reset() received a region-local value — ensure the \
740             new-value expression is inside a StaticCtxGuard (vreset! handles this)"
741        );
742        *self.value.lock().unwrap() = v.clone();
743        v
744    }
745}
746
747impl std::fmt::Debug for Volatile {
748    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
749        write!(f, "Volatile")
750    }
751}
752
753impl cljrs_gc::Trace for Volatile {
754    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
755        {
756            let value = self.value.lock().unwrap();
757            value.trace(visitor);
758        }
759    }
760}
761
762// ── Delay ─────────────────────────────────────────────────────────────────────
763
764/// Internal state of a delay cell.
765pub enum DelayState {
766    Pending(Box<dyn Thunk>),
767    Forced(Value),
768}
769
770/// A lazy one-time computation (forced at most once, result cached).
771pub struct Delay {
772    pub state: Mutex<DelayState>,
773}
774
775impl Delay {
776    pub fn new(thunk: Box<dyn Thunk>) -> Self {
777        Self {
778            state: Mutex::new(DelayState::Pending(thunk)),
779        }
780    }
781
782    /// Force the delay and cache the result.
783    /// Returns the value on success, or an error message on failure.
784    pub fn force(&self) -> Result<Value, String> {
785        let thunk = {
786            let mut guard = self.state.lock().unwrap();
787            if let DelayState::Forced(v) = &*guard {
788                return Ok(v.clone());
789            }
790            let prev = mem::replace(&mut *guard, DelayState::Forced(Value::Nil));
791            let DelayState::Pending(thunk) = prev else {
792                unreachable!("state was not Pending")
793            };
794            thunk
795            // guard dropped here — lock released before forcing
796        };
797        // Force the thunk WITHOUT holding the lock so GC's lock().unwrap() in
798        // Delay::trace() will not deadlock.
799        let result = thunk.force()?;
800        *self.state.lock().unwrap() = DelayState::Forced(result.clone());
801        Ok(result)
802    }
803
804    /// True if the delay has already been forced.
805    pub fn is_realized(&self) -> bool {
806        matches!(&*self.state.lock().unwrap(), DelayState::Forced(_))
807    }
808}
809
810impl std::fmt::Debug for Delay {
811    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
812        write!(f, "Delay")
813    }
814}
815
816impl cljrs_gc::Trace for Delay {
817    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
818        // Safe to lock unconditionally: force() drops the lock before entering
819        // eval (thunk.force()), so the lock is never held across a GC safepoint.
820        {
821            let state = self.state.lock().unwrap();
822            match &*state {
823                DelayState::Pending(thunk) => thunk.trace(visitor),
824                DelayState::Forced(v) => v.trace(visitor),
825            }
826        }
827    }
828}
829
830// ── CljxPromise ───────────────────────────────────────────────────────────────
831
832/// A one-shot rendezvous (promise).
833pub struct CljxPromise {
834    pub value: Mutex<Option<Value>>,
835    pub cond: Condvar,
836}
837
838impl CljxPromise {
839    pub fn new() -> Self {
840        Self {
841            value: Mutex::new(None),
842            cond: Condvar::new(),
843        }
844    }
845
846    /// Deliver a value (no-op if already delivered).
847    pub fn deliver(&self, v: Value) {
848        let mut guard = self.value.lock().unwrap();
849        if guard.is_none() {
850            *guard = Some(v);
851            self.cond.notify_all();
852        }
853    }
854
855    /// Block until a value is available, then return it.
856    pub fn deref_blocking(&self) -> Value {
857        let mut guard = self.value.lock().unwrap();
858        while guard.is_none() {
859            guard = self.cond.wait(guard).unwrap();
860        }
861        guard.as_ref().unwrap().clone()
862    }
863
864    /// True if already delivered.
865    pub fn is_realized(&self) -> bool {
866        self.value.lock().unwrap().is_some()
867    }
868}
869
870impl Default for CljxPromise {
871    fn default() -> Self {
872        Self::new()
873    }
874}
875
876impl std::fmt::Debug for CljxPromise {
877    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
878        write!(f, "Promise")
879    }
880}
881
882impl cljrs_gc::Trace for CljxPromise {
883    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
884        {
885            let value = self.value.lock().unwrap();
886            if let Some(v) = value.as_ref() {
887                v.trace(visitor);
888            }
889        }
890    }
891}
892
893// ── CljxFuture ────────────────────────────────────────────────────────────────
894
895/// Thread-pool future state.
896pub enum FutureState {
897    Running,
898    Done(Value),
899    Failed(String),
900    Cancelled,
901}
902
903/// A future value computed asynchronously on another thread.
904pub struct CljxFuture {
905    pub state: Mutex<FutureState>,
906    pub cond: Condvar,
907}
908
909impl CljxFuture {
910    pub fn new() -> Self {
911        Self {
912            state: Mutex::new(FutureState::Running),
913            cond: Condvar::new(),
914        }
915    }
916
917    /// True if done, failed, or cancelled (not still running).
918    pub fn is_done(&self) -> bool {
919        !matches!(&*self.state.lock().unwrap(), FutureState::Running)
920    }
921
922    /// True if explicitly cancelled.
923    pub fn is_cancelled(&self) -> bool {
924        matches!(&*self.state.lock().unwrap(), FutureState::Cancelled)
925    }
926}
927
928impl Default for CljxFuture {
929    fn default() -> Self {
930        Self::new()
931    }
932}
933
934impl std::fmt::Debug for CljxFuture {
935    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
936        write!(f, "Future")
937    }
938}
939
940impl cljrs_gc::Trace for CljxFuture {
941    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
942        {
943            let state = self.state.lock().unwrap();
944            if let FutureState::Done(v) = &*state {
945                v.trace(visitor);
946            }
947        }
948    }
949}
950
951// ── Agent ─────────────────────────────────────────────────────────────────────
952
953/// A Clojure agent action: takes the current state, returns the new state.
954pub type AgentFn = Box<dyn FnOnce(Value) -> Result<Value, Value> + Send>;
955
956/// Messages sent to an agent's worker thread.
957pub enum AgentMsg {
958    Update(AgentFn),
959    Shutdown,
960}
961
962/// A Clojure agent — asynchronous state update queue.
963pub struct Agent {
964    /// Current state, shared between the Value::Agent handle and the worker thread.
965    pub state: Arc<Mutex<Value>>,
966    /// Last error, shared similarly.
967    pub error: Arc<Mutex<Option<Value>>>,
968    /// Channel to send actions to the worker thread.
969    pub sender: Mutex<std::sync::mpsc::SyncSender<AgentMsg>>,
970    pub watches: Mutex<Vec<(Value, Value)>>,
971}
972
973impl Agent {
974    pub fn get_state(&self) -> Value {
975        self.state.lock().unwrap().clone()
976    }
977
978    pub fn get_error(&self) -> Option<Value> {
979        self.error.lock().unwrap().clone()
980    }
981
982    pub fn clear_error(&self) {
983        *self.error.lock().unwrap() = None;
984    }
985}
986
987impl std::fmt::Debug for Agent {
988    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
989        write!(f, "Agent")
990    }
991}
992
993impl cljrs_gc::Trace for Agent {
994    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
995        {
996            let state = self.state.lock().unwrap();
997            state.trace(visitor);
998        }
999        {
1000            let error = self.error.lock().unwrap();
1001            if let Some(e) = error.as_ref() {
1002                e.trace(visitor);
1003            }
1004        }
1005        {
1006            let watches = self.watches.lock().unwrap();
1007            for (key, f) in watches.iter() {
1008                key.trace(visitor);
1009                f.trace(visitor);
1010            }
1011        }
1012    }
1013}