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    /// Namespace in which this function was defined (for macro hygiene).
544    pub defining_ns: Arc<str>,
545}
546
547impl CljxFn {
548    pub fn new(
549        name: Option<Arc<str>>,
550        arities: Vec<CljxFnArity>,
551        closed_over_names: Vec<Arc<str>>,
552        closed_over_vals: Vec<Value>,
553        is_macro: bool,
554        defining_ns: Arc<str>,
555    ) -> Self {
556        Self {
557            name,
558            arities,
559            closed_over_names,
560            closed_over_vals,
561            is_macro,
562            defining_ns,
563        }
564    }
565}
566
567impl cljrs_gc::Trace for CljxFn {
568    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
569        for v in &self.closed_over_vals {
570            v.trace(visitor);
571        }
572    }
573}
574
575// ── BoundFn ──────────────────────────────────────────────────────────────────
576
577/// A function wrapped with captured dynamic bindings.
578/// When called, the captured bindings are pushed as a frame before delegating
579/// to the wrapped function. This means captured bindings override the caller's
580/// for the same var, but vars not in the capture fall through normally.
581#[derive(Debug)]
582pub struct BoundFn {
583    /// The wrapped callable.
584    pub wrapped: Value,
585    /// Captured dynamic bindings (merged flat frame; opaque to cljrs-value).
586    pub captured_bindings: HashMap<usize, Value>,
587}
588
589impl cljrs_gc::Trace for BoundFn {
590    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
591        self.wrapped.trace(visitor);
592        for val in self.captured_bindings.values() {
593            val.trace(visitor);
594        }
595    }
596}
597
598// ── Thunk / LazySeq ───────────────────────────────────────────────────────────
599
600/// A deferred computation that produces a `Value` when forced.
601pub trait Thunk: Send + Sync + std::fmt::Debug + cljrs_gc::Trace {
602    fn force(&self) -> Result<Value, String>;
603}
604
605/// Internal state of a lazy sequence cell.
606pub enum LazySeqState {
607    /// Thunk not yet evaluated.
608    Pending(Box<dyn Thunk>),
609    /// Result cached after first force.
610    Forced(Value),
611    /// Thunk evaluation failed; error message is cached.
612    Error(String),
613}
614
615/// A lazy sequence that forces its thunk exactly once and caches the result.
616pub struct LazySeq {
617    pub state: Mutex<LazySeqState>,
618}
619
620impl LazySeq {
621    pub fn new(thunk: Box<dyn Thunk>) -> Self {
622        Self {
623            state: Mutex::new(LazySeqState::Pending(thunk)),
624        }
625    }
626
627    /// Realize the sequence: force the thunk on first call, return cached value on subsequent calls.
628    /// On error, returns `Value::Nil` and caches the error (retrievable via `error()`).
629    pub fn realize(&self) -> Value {
630        let thunk = {
631            let mut guard = self.state.lock().unwrap();
632            match &*guard {
633                LazySeqState::Forced(v) => return v.clone(),
634                LazySeqState::Error(_) => return Value::Nil,
635                LazySeqState::Pending(_) => {}
636            }
637            // Replace the pending state with a temporary Forced(Nil), extract the thunk.
638            let prev = mem::replace(&mut *guard, LazySeqState::Forced(Value::Nil));
639            let LazySeqState::Pending(thunk) = prev else {
640                unreachable!("state was not Pending")
641            };
642            thunk
643            // guard dropped here — lock released before forcing
644        };
645        // Force the thunk WITHOUT holding the lock. This ensures GC's
646        // lock().unwrap() in LazySeq::trace() will not deadlock.
647        match thunk.force() {
648            Ok(result) => {
649                *self.state.lock().unwrap() = LazySeqState::Forced(result.clone());
650                result
651            }
652            Err(msg) => {
653                *self.state.lock().unwrap() = LazySeqState::Error(msg);
654                Value::Nil
655            }
656        }
657    }
658
659    /// Return the cached error message, if the thunk failed.
660    pub fn error(&self) -> Option<String> {
661        let guard = self.state.lock().unwrap();
662        if let LazySeqState::Error(e) = &*guard {
663            Some(e.clone())
664        } else {
665            None
666        }
667    }
668}
669
670impl std::fmt::Debug for LazySeq {
671    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
672        write!(f, "LazySeq(...)")
673    }
674}
675
676impl cljrs_gc::Trace for LazySeq {
677    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
678        // Safe to lock unconditionally: realize() drops the lock before entering
679        // eval (thunk.force()), so the lock is never held across a GC safepoint.
680        {
681            let state = self.state.lock().unwrap();
682            match &*state {
683                LazySeqState::Pending(thunk) => thunk.trace(visitor),
684                LazySeqState::Forced(v) => v.trace(visitor),
685                LazySeqState::Error(_) => {}
686            }
687        }
688    }
689}
690
691// ── CljxCons ──────────────────────────────────────────────────────────────────
692
693/// A lazy cons cell: head element + tail (may be a `LazySeq`, `List`, or `Nil`).
694///
695/// Used when `cons` is called with a `LazySeq` or `Cons` tail, enabling lazy
696/// sequences without eagerly realizing them.
697#[derive(Debug, Clone)]
698pub struct CljxCons {
699    pub head: Value,
700    pub tail: Value,
701}
702
703impl cljrs_gc::Trace for CljxCons {
704    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
705        self.head.trace(visitor);
706        self.tail.trace(visitor);
707    }
708}
709
710// ── Volatile ──────────────────────────────────────────────────────────────────
711
712/// Non-atomic mutable cell (single-thread performance, no CAS).
713pub struct Volatile {
714    pub value: Mutex<Value>,
715}
716
717impl Volatile {
718    pub fn new(v: Value) -> Self {
719        Self {
720            value: Mutex::new(v),
721        }
722    }
723
724    pub fn deref(&self) -> Value {
725        self.value.lock().unwrap().clone()
726    }
727
728    pub fn reset(&self, v: Value) -> Value {
729        // In no-gc debug builds: assert the new value came from the StaticArena.
730        #[cfg(all(feature = "no-gc", debug_assertions))]
731        debug_assert!(
732            value_gcptr_is_static(&v),
733            "no-gc: Volatile::reset() received a region-local value — ensure the \
734             new-value expression is inside a StaticCtxGuard (vreset! handles this)"
735        );
736        *self.value.lock().unwrap() = v.clone();
737        v
738    }
739}
740
741impl std::fmt::Debug for Volatile {
742    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
743        write!(f, "Volatile")
744    }
745}
746
747impl cljrs_gc::Trace for Volatile {
748    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
749        {
750            let value = self.value.lock().unwrap();
751            value.trace(visitor);
752        }
753    }
754}
755
756// ── Delay ─────────────────────────────────────────────────────────────────────
757
758/// Internal state of a delay cell.
759pub enum DelayState {
760    Pending(Box<dyn Thunk>),
761    Forced(Value),
762}
763
764/// A lazy one-time computation (forced at most once, result cached).
765pub struct Delay {
766    pub state: Mutex<DelayState>,
767}
768
769impl Delay {
770    pub fn new(thunk: Box<dyn Thunk>) -> Self {
771        Self {
772            state: Mutex::new(DelayState::Pending(thunk)),
773        }
774    }
775
776    /// Force the delay and cache the result.
777    /// Returns the value on success, or an error message on failure.
778    pub fn force(&self) -> Result<Value, String> {
779        let thunk = {
780            let mut guard = self.state.lock().unwrap();
781            if let DelayState::Forced(v) = &*guard {
782                return Ok(v.clone());
783            }
784            let prev = mem::replace(&mut *guard, DelayState::Forced(Value::Nil));
785            let DelayState::Pending(thunk) = prev else {
786                unreachable!("state was not Pending")
787            };
788            thunk
789            // guard dropped here — lock released before forcing
790        };
791        // Force the thunk WITHOUT holding the lock so GC's lock().unwrap() in
792        // Delay::trace() will not deadlock.
793        let result = thunk.force()?;
794        *self.state.lock().unwrap() = DelayState::Forced(result.clone());
795        Ok(result)
796    }
797
798    /// True if the delay has already been forced.
799    pub fn is_realized(&self) -> bool {
800        matches!(&*self.state.lock().unwrap(), DelayState::Forced(_))
801    }
802}
803
804impl std::fmt::Debug for Delay {
805    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
806        write!(f, "Delay")
807    }
808}
809
810impl cljrs_gc::Trace for Delay {
811    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
812        // Safe to lock unconditionally: force() drops the lock before entering
813        // eval (thunk.force()), so the lock is never held across a GC safepoint.
814        {
815            let state = self.state.lock().unwrap();
816            match &*state {
817                DelayState::Pending(thunk) => thunk.trace(visitor),
818                DelayState::Forced(v) => v.trace(visitor),
819            }
820        }
821    }
822}
823
824// ── CljxPromise ───────────────────────────────────────────────────────────────
825
826/// A one-shot rendezvous (promise).
827pub struct CljxPromise {
828    pub value: Mutex<Option<Value>>,
829    pub cond: Condvar,
830}
831
832impl CljxPromise {
833    pub fn new() -> Self {
834        Self {
835            value: Mutex::new(None),
836            cond: Condvar::new(),
837        }
838    }
839
840    /// Deliver a value (no-op if already delivered).
841    pub fn deliver(&self, v: Value) {
842        let mut guard = self.value.lock().unwrap();
843        if guard.is_none() {
844            *guard = Some(v);
845            self.cond.notify_all();
846        }
847    }
848
849    /// Block until a value is available, then return it.
850    pub fn deref_blocking(&self) -> Value {
851        let mut guard = self.value.lock().unwrap();
852        while guard.is_none() {
853            guard = self.cond.wait(guard).unwrap();
854        }
855        guard.as_ref().unwrap().clone()
856    }
857
858    /// True if already delivered.
859    pub fn is_realized(&self) -> bool {
860        self.value.lock().unwrap().is_some()
861    }
862}
863
864impl Default for CljxPromise {
865    fn default() -> Self {
866        Self::new()
867    }
868}
869
870impl std::fmt::Debug for CljxPromise {
871    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
872        write!(f, "Promise")
873    }
874}
875
876impl cljrs_gc::Trace for CljxPromise {
877    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
878        {
879            let value = self.value.lock().unwrap();
880            if let Some(v) = value.as_ref() {
881                v.trace(visitor);
882            }
883        }
884    }
885}
886
887// ── CljxFuture ────────────────────────────────────────────────────────────────
888
889/// Thread-pool future state.
890pub enum FutureState {
891    Running,
892    Done(Value),
893    Failed(String),
894    Cancelled,
895}
896
897/// A future value computed asynchronously on another thread.
898pub struct CljxFuture {
899    pub state: Mutex<FutureState>,
900    pub cond: Condvar,
901}
902
903impl CljxFuture {
904    pub fn new() -> Self {
905        Self {
906            state: Mutex::new(FutureState::Running),
907            cond: Condvar::new(),
908        }
909    }
910
911    /// True if done, failed, or cancelled (not still running).
912    pub fn is_done(&self) -> bool {
913        !matches!(&*self.state.lock().unwrap(), FutureState::Running)
914    }
915
916    /// True if explicitly cancelled.
917    pub fn is_cancelled(&self) -> bool {
918        matches!(&*self.state.lock().unwrap(), FutureState::Cancelled)
919    }
920}
921
922impl Default for CljxFuture {
923    fn default() -> Self {
924        Self::new()
925    }
926}
927
928impl std::fmt::Debug for CljxFuture {
929    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
930        write!(f, "Future")
931    }
932}
933
934impl cljrs_gc::Trace for CljxFuture {
935    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
936        {
937            let state = self.state.lock().unwrap();
938            if let FutureState::Done(v) = &*state {
939                v.trace(visitor);
940            }
941        }
942    }
943}
944
945// ── Agent ─────────────────────────────────────────────────────────────────────
946
947/// A Clojure agent action: takes the current state, returns the new state.
948pub type AgentFn = Box<dyn FnOnce(Value) -> Result<Value, Value> + Send>;
949
950/// Messages sent to an agent's worker thread.
951pub enum AgentMsg {
952    Update(AgentFn),
953    Shutdown,
954}
955
956/// A Clojure agent — asynchronous state update queue.
957pub struct Agent {
958    /// Current state, shared between the Value::Agent handle and the worker thread.
959    pub state: Arc<Mutex<Value>>,
960    /// Last error, shared similarly.
961    pub error: Arc<Mutex<Option<Value>>>,
962    /// Channel to send actions to the worker thread.
963    pub sender: Mutex<std::sync::mpsc::SyncSender<AgentMsg>>,
964    pub watches: Mutex<Vec<(Value, Value)>>,
965}
966
967impl Agent {
968    pub fn get_state(&self) -> Value {
969        self.state.lock().unwrap().clone()
970    }
971
972    pub fn get_error(&self) -> Option<Value> {
973        self.error.lock().unwrap().clone()
974    }
975
976    pub fn clear_error(&self) {
977        *self.error.lock().unwrap() = None;
978    }
979}
980
981impl std::fmt::Debug for Agent {
982    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
983        write!(f, "Agent")
984    }
985}
986
987impl cljrs_gc::Trace for Agent {
988    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
989        {
990            let state = self.state.lock().unwrap();
991            state.trace(visitor);
992        }
993        {
994            let error = self.error.lock().unwrap();
995            if let Some(e) = error.as_ref() {
996                e.trace(visitor);
997            }
998        }
999        {
1000            let watches = self.watches.lock().unwrap();
1001            for (key, f) in watches.iter() {
1002                key.trace(visitor);
1003                f.trace(visitor);
1004            }
1005        }
1006    }
1007}