1#![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::TypeHint;
13use crate::Value;
14
15#[cfg(all(feature = "no-gc", debug_assertions))]
30pub(crate) fn value_gcptr_is_static(value: &Value) -> bool {
31 use crate::value::MapValue;
32 use crate::value::SetValue;
33 match value {
34 Value::Nil
36 | Value::Bool(_)
37 | Value::Long(_)
38 | Value::Double(_)
39 | Value::Char(_)
40 | Value::Uuid(_) => true,
41 Value::Resource(_) | Value::SharedAtom(_) | Value::ByteBlob(_) => true,
43 Value::BigInt(p) => p.is_static_alloc(),
45 Value::BigDecimal(p) => p.is_static_alloc(),
46 Value::Ratio(p) => p.is_static_alloc(),
47 Value::Str(p) => p.is_static_alloc(),
48 Value::Pattern(p) => p.is_static_alloc(),
49 Value::Matcher(p) => p.is_static_alloc(),
50 Value::Symbol(p) => p.is_static_alloc(),
51 Value::Keyword(p) => p.is_static_alloc(),
52 Value::List(p) => p.is_static_alloc(),
53 Value::Vector(p) => p.is_static_alloc(),
54 Value::Queue(p) => p.is_static_alloc(),
55 Value::Map(m) => match m {
56 MapValue::Array(p) => p.is_static_alloc(),
57 MapValue::Hash(p) => p.is_static_alloc(),
58 MapValue::Sorted(p) => p.is_static_alloc(),
59 },
60 Value::Set(s) => match s {
61 SetValue::Hash(p) => p.is_static_alloc(),
62 SetValue::Sorted(p) => p.is_static_alloc(),
63 },
64 Value::NativeFunction(p) => p.is_static_alloc(),
65 Value::Fn(p) | Value::Macro(p) => p.is_static_alloc(),
66 Value::BoundFn(p) => p.is_static_alloc(),
67 Value::Var(p) => p.is_static_alloc(),
68 Value::Atom(p) => p.is_static_alloc(),
69 Value::Namespace(p) => p.is_static_alloc(),
70 Value::LazySeq(p) => p.is_static_alloc(),
71 Value::Cons(p) => p.is_static_alloc(),
72 Value::Protocol(p) => p.is_static_alloc(),
73 Value::ProtocolFn(p) => p.is_static_alloc(),
74 Value::MultiFn(p) => p.is_static_alloc(),
75 Value::Volatile(p) => p.is_static_alloc(),
76 Value::Delay(p) => p.is_static_alloc(),
77 Value::Promise(p) => p.is_static_alloc(),
78 Value::Future(p) => p.is_static_alloc(),
79 Value::Agent(p) => p.is_static_alloc(),
80 Value::TypeInstance(p) => p.is_static_alloc(),
81 Value::ObjectArray(p) => p.is_static_alloc(),
82 Value::NativeObject(p) => p.is_static_alloc(),
83 Value::Error(p) => p.is_static_alloc(),
84 Value::TransientMap(p) => p.is_static_alloc(),
85 Value::TransientVector(p) => p.is_static_alloc(),
86 Value::TransientSet(p) => p.is_static_alloc(),
87 Value::BooleanArray(_)
89 | Value::ByteArray(_)
90 | Value::ShortArray(_)
91 | Value::IntArray(_)
92 | Value::LongArray(_)
93 | Value::FloatArray(_)
94 | Value::DoubleArray(_)
95 | Value::CharArray(_) => true,
96 Value::Reduced(inner) | Value::WithMeta(inner, _) => value_gcptr_is_static(inner),
98 }
99}
100
101pub type MethodMap = HashMap<Arc<str>, Value>;
105
106#[derive(Debug)]
108pub struct Protocol {
109 pub name: Arc<str>,
110 pub ns: Arc<str>,
111 pub methods: Vec<ProtocolMethod>,
112 pub impls: Mutex<HashMap<Arc<str>, MethodMap>>,
114 pub extend_via_metadata: bool,
118}
119
120impl Protocol {
121 pub fn new(
122 name: Arc<str>,
123 ns: Arc<str>,
124 methods: Vec<ProtocolMethod>,
125 extend_via_metadata: bool,
126 ) -> Self {
127 Self {
128 name,
129 ns,
130 methods,
131 impls: Mutex::new(HashMap::new()),
132 extend_via_metadata,
133 }
134 }
135}
136
137static PROTOCOL_GENERATION: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
146
147pub fn protocol_generation() -> u64 {
149 PROTOCOL_GENERATION.load(std::sync::atomic::Ordering::Acquire)
150}
151
152pub fn bump_protocol_generation() {
155 PROTOCOL_GENERATION.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
156}
157
158impl cljrs_gc::Trace for Protocol {
159 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
160 {
161 let impls = self.impls.lock().unwrap();
162 for method_map in impls.values() {
163 for v in method_map.values() {
164 v.trace(visitor);
165 }
166 }
167 }
168 }
169}
170
171#[derive(Debug, Clone)]
173pub struct ProtocolMethod {
174 pub name: Arc<str>,
175 pub min_arity: usize,
176 pub variadic: bool,
177}
178
179impl cljrs_gc::Trace for ProtocolMethod {
180 fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
181}
182
183#[derive(Debug)]
187pub struct ProtocolFn {
188 pub protocol: GcPtr<Protocol>,
189 pub method_name: Arc<str>,
190 pub min_arity: usize,
191 pub variadic: bool,
192}
193
194impl cljrs_gc::Trace for ProtocolFn {
195 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
196 use cljrs_gc::GcVisitor as _;
197 visitor.visit(&self.protocol);
198 }
199}
200
201#[derive(Debug)]
205pub struct MultiFn {
206 pub name: Arc<str>,
207 pub dispatch_fn: Value,
208 pub methods: Mutex<HashMap<String, Value>>,
210 pub dispatch_vals: Mutex<HashMap<String, Value>>,
212 pub prefers: Mutex<HashMap<String, Vec<String>>>,
214 pub default_dispatch: String,
216}
217
218impl MultiFn {
219 pub fn new(name: Arc<str>, dispatch_fn: Value, default_dispatch: String) -> Self {
220 Self {
221 name,
222 dispatch_fn,
223 methods: Mutex::new(HashMap::new()),
224 dispatch_vals: Mutex::new(HashMap::new()),
225 prefers: Mutex::new(HashMap::new()),
226 default_dispatch,
227 }
228 }
229}
230
231impl cljrs_gc::Trace for MultiFn {
232 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
233 self.dispatch_fn.trace(visitor);
234 {
235 let methods = self.methods.lock().unwrap();
236 for v in methods.values() {
237 v.trace(visitor);
238 }
239 }
240 {
241 let dispatch_vals = self.dispatch_vals.lock().unwrap();
242 for v in dispatch_vals.values() {
243 v.trace(visitor);
244 }
245 }
246 }
247}
248
249#[derive(Debug)]
276pub struct Var {
277 pub namespace: Arc<str>,
278 pub name: Arc<str>,
279 pub value: Mutex<Option<Value>>,
280 pub shared_root: Arc<arc_swap::ArcSwap<Option<crate::shared::SharedValue>>>,
283 pub is_macro: bool,
284 pub meta: Mutex<Option<Value>>,
286 pub watches: Mutex<Vec<(Value, Value)>>,
287}
288
289impl Var {
290 pub fn new(namespace: impl Into<Arc<str>>, name: impl Into<Arc<str>>) -> Self {
291 Self {
292 namespace: namespace.into(),
293 name: name.into(),
294 value: Mutex::new(None),
295 shared_root: Arc::new(arc_swap::ArcSwap::new(Arc::new(None))),
296 is_macro: false,
297 meta: Mutex::new(None),
298 watches: Mutex::new(Vec::new()),
299 }
300 }
301
302 pub fn from_shared_root(
309 namespace: impl Into<Arc<str>>,
310 name: impl Into<Arc<str>>,
311 is_macro: bool,
312 shared_root: Arc<arc_swap::ArcSwap<Option<crate::shared::SharedValue>>>,
313 ) -> Self {
314 let local = shared_root
315 .load()
316 .as_ref()
317 .as_ref()
318 .map(crate::shared::demote);
319 Self {
320 namespace: namespace.into(),
321 name: name.into(),
322 value: Mutex::new(local),
323 shared_root,
324 is_macro,
325 meta: Mutex::new(None),
326 watches: Mutex::new(Vec::new()),
327 }
328 }
329
330 pub fn is_bound(&self) -> bool {
331 self.value.lock().unwrap().is_some()
332 }
333
334 pub fn deref(&self) -> Option<Value> {
335 self.value.lock().unwrap().clone()
336 }
337
338 pub fn deref_shared(&self) -> Option<Value> {
343 self.shared_root
344 .load()
345 .as_ref()
346 .as_ref()
347 .map(crate::shared::demote)
348 }
349
350 pub fn bind(&self, v: Value) {
351 #[cfg(all(feature = "no-gc", debug_assertions))]
355 debug_assert!(
356 value_gcptr_is_static(&v),
357 "no-gc: Var::bind({}/{}) received a region-local value — store violations \
358 indicate a missing StaticCtxGuard around the value expression",
359 self.namespace,
360 self.name
361 );
362 let v = crate::publish::publish_value(v);
367 let prev = {
373 let mut slot = self.value.lock().unwrap();
374 slot.replace(v.clone())
375 };
376 let shared = crate::shared::promote(&v).ok();
382 self.shared_root.store(Arc::new(shared));
383 if let Some(prev) = prev {
384 crate::jit_hooks::notify_var_rebind(&prev, &v);
385 }
386 }
387
388 pub fn get_meta(&self) -> Option<Value> {
389 self.meta.lock().unwrap().clone()
390 }
391
392 pub fn set_meta(&self, m: Value) {
393 *self.meta.lock().unwrap() = Some(m);
394 }
395
396 pub fn full_name(&self) -> String {
397 format!("{}/{}", self.namespace, self.name)
398 }
399}
400
401impl cljrs_gc::Trace for Var {
402 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
403 {
404 let value = self.value.lock().unwrap();
405 if let Some(v) = value.as_ref() {
406 v.trace(visitor);
407 }
408 }
409 {
410 let meta = self.meta.lock().unwrap();
411 if let Some(m) = meta.as_ref() {
412 m.trace(visitor);
413 }
414 }
415 {
416 let watches = self.watches.lock().unwrap();
417 for (key, f) in watches.iter() {
418 key.trace(visitor);
419 f.trace(visitor);
420 }
421 }
422 }
423}
424
425#[derive(Debug)]
429pub struct Atom {
430 pub value: Mutex<Value>,
431 pub meta: Mutex<Option<Value>>,
432 pub validator: Mutex<Option<Value>>,
433 pub watches: Mutex<Vec<(Value, Value)>>,
434}
435
436impl Atom {
437 pub fn new(v: Value) -> Self {
438 let v = crate::publish::publish_value(v);
441 Self {
442 value: Mutex::new(v),
443 meta: Mutex::new(None),
444 validator: Mutex::new(None),
445 watches: Mutex::new(Vec::new()),
446 }
447 }
448
449 pub fn deref(&self) -> Value {
450 self.value.lock().unwrap().clone()
451 }
452
453 pub fn reset(&self, v: Value) -> Value {
454 #[cfg(all(feature = "no-gc", debug_assertions))]
456 debug_assert!(
457 value_gcptr_is_static(&v),
458 "no-gc: Atom::reset() received a region-local value — the new-value \
459 expression must be computed inside a StaticCtxGuard (i.e. inside \
460 the swap! / reset! call) so it is allocated in the static arena"
461 );
462 let v = crate::publish::publish_value(v);
464 let mut guard = self.value.lock().unwrap();
465 *guard = v.clone();
466 v
467 }
468
469 pub fn get_meta(&self) -> Option<Value> {
470 self.meta.lock().unwrap().clone()
471 }
472
473 pub fn set_meta(&self, m: Option<Value>) {
474 *self.meta.lock().unwrap() = m;
475 }
476
477 pub fn get_validator(&self) -> Option<Value> {
478 self.validator.lock().unwrap().clone()
479 }
480
481 pub fn set_validator(&self, vf: Option<Value>) {
482 *self.validator.lock().unwrap() = vf;
483 }
484}
485
486impl cljrs_gc::Trace for Atom {
487 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
488 {
489 let value = self.value.lock().unwrap();
490 value.trace(visitor);
491 }
492 {
493 let meta = self.meta.lock().unwrap();
494 if let Some(m) = meta.as_ref() {
495 m.trace(visitor);
496 }
497 }
498 {
499 let validator = self.validator.lock().unwrap();
500 if let Some(vf) = validator.as_ref() {
501 vf.trace(visitor);
502 }
503 }
504 {
505 let watches = self.watches.lock().unwrap();
506 for (key, f) in watches.iter() {
507 key.trace(visitor);
508 f.trace(visitor);
509 }
510 }
511 }
512}
513
514#[derive(Debug, Clone, Default)]
520pub struct ReferClojureFilter {
521 pub only: Option<std::collections::HashSet<Arc<str>>>,
523 pub exclude: std::collections::HashSet<Arc<str>>,
525 pub rename: HashMap<Arc<str>, Arc<str>>,
529}
530
531impl ReferClojureFilter {
532 pub fn local_name(&self, name: &Arc<str>) -> Option<Arc<str>> {
535 if self.exclude.contains(name) {
536 return None;
537 }
538 if let Some(only) = &self.only
539 && !only.contains(name)
540 {
541 return None;
542 }
543 Some(
544 self.rename
545 .get(name)
546 .cloned()
547 .unwrap_or_else(|| name.clone()),
548 )
549 }
550}
551
552#[derive(Debug)]
554pub struct Namespace {
555 pub name: Arc<str>,
556 pub interns: Mutex<HashMap<Arc<str>, GcPtr<Var>>>,
558 pub refers: Mutex<HashMap<Arc<str>, GcPtr<Var>>>,
560 pub aliases: Mutex<HashMap<Arc<str>, Arc<str>>>,
562 pub source_file: Mutex<Option<Arc<str>>>,
566 pub git_repo_root: Mutex<Option<Arc<str>>>,
568 pub is_versioned: bool,
571 pub meta: Mutex<Option<Value>>,
573 pub refer_clojure_filter: Mutex<Option<ReferClojureFilter>>,
576}
577
578impl Namespace {
579 pub fn new(name: impl Into<Arc<str>>) -> Self {
580 Self {
581 name: name.into(),
582 interns: Mutex::new(HashMap::new()),
583 refers: Mutex::new(HashMap::new()),
584 aliases: Mutex::new(HashMap::new()),
585 source_file: Mutex::new(None),
586 git_repo_root: Mutex::new(None),
587 is_versioned: false,
588 meta: Mutex::new(None),
589 refer_clojure_filter: Mutex::new(None),
590 }
591 }
592
593 pub fn new_versioned(name: impl Into<Arc<str>>) -> Self {
595 Self {
596 is_versioned: true,
597 ..Self::new(name)
598 }
599 }
600
601 pub fn set_source_location(&self, file: &str, repo_root: Option<&str>) {
603 *self.source_file.lock().unwrap() = Some(Arc::from(file));
604 *self.git_repo_root.lock().unwrap() = repo_root.map(Arc::from);
605 }
606
607 pub fn get_meta(&self) -> Option<Value> {
608 self.meta.lock().unwrap().clone()
609 }
610
611 pub fn set_meta(&self, m: Value) {
612 *self.meta.lock().unwrap() = Some(m);
613 }
614}
615
616impl cljrs_gc::Trace for Namespace {
617 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
618 use cljrs_gc::GcVisitor as _;
619 {
620 let interns = self.interns.lock().unwrap();
621 for var in interns.values() {
622 visitor.visit(var);
623 }
624 }
625 {
626 let refers = self.refers.lock().unwrap();
627 for var in refers.values() {
628 visitor.visit(var);
629 }
630 }
631 {
632 let meta = self.meta.lock().unwrap();
633 if let Some(m) = meta.as_ref() {
634 m.trace(visitor);
635 }
636 }
637 }
638}
639
640pub type NativeFnPtr = fn(&[Value]) -> crate::error::ValueResult<Value>;
646
647pub type NativeFnFunc = Arc<dyn Fn(&[Value]) -> crate::error::ValueResult<Value>>;
650
651#[derive(Clone, Debug)]
652pub enum Arity {
653 Fixed(usize),
654 Variadic { min: usize },
655}
656
657pub struct NativeFn {
658 pub name: Arc<str>,
659 pub arity: Arity,
660 pub func: NativeFnFunc,
661}
662
663impl NativeFn {
664 pub fn new(name: impl Into<Arc<str>>, arity: Arity, func: NativeFnPtr) -> Self {
666 Self {
667 name: name.into(),
668 arity,
669 func: Arc::new(func),
670 }
671 }
672
673 pub fn with_closure(
675 name: impl Into<Arc<str>>,
676 arity: Arity,
677 func: impl Fn(&[Value]) -> crate::error::ValueResult<Value> + 'static,
678 ) -> Self {
679 Self {
680 name: name.into(),
681 arity,
682 func: Arc::new(func),
683 }
684 }
685}
686
687impl std::fmt::Debug for NativeFn {
688 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
689 f.debug_struct("NativeFn")
690 .field("name", &self.name)
691 .field("arity", &self.arity)
692 .field("func", &"<fn>")
693 .finish()
694 }
695}
696
697impl cljrs_gc::Trace for NativeFn {
698 fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
699}
700
701#[derive(Debug, Clone)]
705pub struct CljxFnArity {
706 pub params: Vec<Arc<str>>,
709 pub rest_param: Option<Arc<str>>,
711 pub body: Vec<Form>,
713 pub destructure_params: Vec<(usize, Form)>,
717 pub destructure_rest: Option<Form>,
719 pub ir_arity_id: u64,
721 pub param_hints: Vec<Option<TypeHint>>,
725 pub rest_hint: Option<TypeHint>,
728}
729
730impl CljxFnArity {
731 pub fn heap_size(&self) -> usize {
733 self.params.capacity() * mem::size_of::<Arc<str>>()
735 + self.body.capacity() * mem::size_of::<Form>()
737 + self.body.iter().map(|f| f.heap_size()).sum::<usize>()
738 + self.destructure_params.capacity() * mem::size_of::<(usize, Form)>()
740 + self.destructure_params.iter().map(|(_, f)| f.heap_size()).sum::<usize>()
741 + self.destructure_rest.as_ref()
743 .map_or(0, |f| mem::size_of::<Form>() + f.heap_size())
744 + self.param_hints.capacity() * mem::size_of::<Option<TypeHint>>()
746 }
747}
748
749#[derive(Debug, Clone)]
753pub struct CljxFn {
754 pub name: Option<Arc<str>>,
755 pub arities: Vec<CljxFnArity>,
756 pub closed_over_names: Vec<Arc<str>>,
758 pub closed_over_vals: Vec<Value>,
760 pub is_macro: bool,
762 pub is_async: bool,
767 pub defining_ns: Arc<str>,
769 pub self_ptr: Option<GcPtr<CljxFn>>,
774}
775
776impl CljxFn {
777 pub fn new(
778 name: Option<Arc<str>>,
779 arities: Vec<CljxFnArity>,
780 closed_over_names: Vec<Arc<str>>,
781 closed_over_vals: Vec<Value>,
782 is_macro: bool,
783 defining_ns: Arc<str>,
784 ) -> Self {
785 Self {
786 name,
787 arities,
788 closed_over_names,
789 closed_over_vals,
790 is_macro,
791 is_async: false,
792 defining_ns,
793 self_ptr: None,
794 }
795 }
796}
797
798impl cljrs_gc::Trace for CljxFn {
799 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
800 use cljrs_gc::GcVisitor as _;
801 for v in &self.closed_over_vals {
802 v.trace(visitor);
803 }
804 if let Some(ref p) = self.self_ptr {
805 visitor.visit(p);
806 }
807 }
808
809 fn gc_size_extra(&self) -> usize {
810 self.arities.capacity() * mem::size_of::<CljxFnArity>()
812 + self
813 .arities
814 .iter()
815 .map(CljxFnArity::heap_size)
816 .sum::<usize>()
817 }
818}
819
820#[derive(Debug)]
827pub struct BoundFn {
828 pub wrapped: Value,
830 pub captured_bindings: HashMap<usize, Value>,
832}
833
834impl cljrs_gc::Trace for BoundFn {
835 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
836 self.wrapped.trace(visitor);
837 for val in self.captured_bindings.values() {
838 val.trace(visitor);
839 }
840 }
841
842 fn gc_size_extra(&self) -> usize {
843 self.captured_bindings.capacity() * (1 + mem::size_of::<usize>() + mem::size_of::<Value>())
845 }
846}
847
848pub trait Thunk: std::fmt::Debug + cljrs_gc::Trace {
852 fn force(&self) -> Result<Value, String>;
853}
854
855pub enum LazySeqState {
857 Pending(Box<dyn Thunk>),
859 Forced(Value),
861 Error(String),
863}
864
865pub struct LazySeq {
867 pub state: Mutex<LazySeqState>,
868}
869
870impl LazySeq {
871 pub fn new(thunk: Box<dyn Thunk>) -> Self {
872 Self {
873 state: Mutex::new(LazySeqState::Pending(thunk)),
874 }
875 }
876
877 pub fn realize(&self) -> Value {
880 let thunk = {
881 let mut guard = self.state.lock().unwrap();
882 match &*guard {
883 LazySeqState::Forced(v) => return v.clone(),
884 LazySeqState::Error(_) => return Value::Nil,
885 LazySeqState::Pending(_) => {}
886 }
887 let prev = mem::replace(&mut *guard, LazySeqState::Forced(Value::Nil));
889 let LazySeqState::Pending(thunk) = prev else {
890 unreachable!("state was not Pending")
891 };
892 thunk
893 };
895 match thunk.force() {
898 Ok(result) => {
899 *self.state.lock().unwrap() = LazySeqState::Forced(result.clone());
900 result
901 }
902 Err(msg) => {
903 *self.state.lock().unwrap() = LazySeqState::Error(msg);
904 Value::Nil
905 }
906 }
907 }
908
909 pub fn error(&self) -> Option<String> {
911 let guard = self.state.lock().unwrap();
912 if let LazySeqState::Error(e) = &*guard {
913 Some(e.clone())
914 } else {
915 None
916 }
917 }
918}
919
920impl std::fmt::Debug for LazySeq {
921 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
922 write!(f, "LazySeq(...)")
923 }
924}
925
926impl cljrs_gc::Trace for LazySeq {
927 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
928 {
931 let state = self.state.lock().unwrap();
932 match &*state {
933 LazySeqState::Pending(thunk) => thunk.trace(visitor),
934 LazySeqState::Forced(v) => v.trace(visitor),
935 LazySeqState::Error(_) => {}
936 }
937 }
938 }
939}
940
941#[derive(Debug, Clone)]
948pub struct CljxCons {
949 pub head: Value,
950 pub tail: Value,
951}
952
953impl cljrs_gc::Trace for CljxCons {
954 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
955 self.head.trace(visitor);
956 self.tail.trace(visitor);
957 }
958}
959
960pub struct Volatile {
964 pub value: Mutex<Value>,
965}
966
967impl Volatile {
968 pub fn new(v: Value) -> Self {
969 let v = crate::publish::publish_value(v);
971 Self {
972 value: Mutex::new(v),
973 }
974 }
975
976 pub fn deref(&self) -> Value {
977 self.value.lock().unwrap().clone()
978 }
979
980 pub fn reset(&self, v: Value) -> Value {
981 #[cfg(all(feature = "no-gc", debug_assertions))]
983 debug_assert!(
984 value_gcptr_is_static(&v),
985 "no-gc: Volatile::reset() received a region-local value — ensure the \
986 new-value expression is inside a StaticCtxGuard (vreset! handles this)"
987 );
988 let v = crate::publish::publish_value(v);
990 *self.value.lock().unwrap() = v.clone();
991 v
992 }
993}
994
995impl std::fmt::Debug for Volatile {
996 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
997 write!(f, "Volatile")
998 }
999}
1000
1001impl cljrs_gc::Trace for Volatile {
1002 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1003 {
1004 let value = self.value.lock().unwrap();
1005 value.trace(visitor);
1006 }
1007 }
1008}
1009
1010pub enum DelayState {
1014 Pending(Box<dyn Thunk>),
1015 Forced(Value),
1016}
1017
1018pub struct Delay {
1020 pub state: Mutex<DelayState>,
1021}
1022
1023impl Delay {
1024 pub fn new(thunk: Box<dyn Thunk>) -> Self {
1025 Self {
1026 state: Mutex::new(DelayState::Pending(thunk)),
1027 }
1028 }
1029
1030 pub fn force(&self) -> Result<Value, String> {
1033 let thunk = {
1034 let mut guard = self.state.lock().unwrap();
1035 if let DelayState::Forced(v) = &*guard {
1036 return Ok(v.clone());
1037 }
1038 let prev = mem::replace(&mut *guard, DelayState::Forced(Value::Nil));
1039 let DelayState::Pending(thunk) = prev else {
1040 unreachable!("state was not Pending")
1041 };
1042 thunk
1043 };
1045 let result = thunk.force()?;
1048 *self.state.lock().unwrap() = DelayState::Forced(result.clone());
1049 Ok(result)
1050 }
1051
1052 pub fn is_realized(&self) -> bool {
1054 matches!(&*self.state.lock().unwrap(), DelayState::Forced(_))
1055 }
1056}
1057
1058impl std::fmt::Debug for Delay {
1059 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1060 write!(f, "Delay")
1061 }
1062}
1063
1064impl cljrs_gc::Trace for Delay {
1065 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1066 {
1069 let state = self.state.lock().unwrap();
1070 match &*state {
1071 DelayState::Pending(thunk) => thunk.trace(visitor),
1072 DelayState::Forced(v) => v.trace(visitor),
1073 }
1074 }
1075 }
1076}
1077
1078pub struct CljxPromise {
1082 pub value: Mutex<Option<Value>>,
1083 pub cond: Condvar,
1084}
1085
1086impl CljxPromise {
1087 pub fn new() -> Self {
1088 Self {
1089 value: Mutex::new(None),
1090 cond: Condvar::new(),
1091 }
1092 }
1093
1094 pub fn deliver(&self, v: Value) {
1096 let v = crate::publish::publish_value(v);
1099 let mut guard = self.value.lock().unwrap();
1100 if guard.is_none() {
1101 *guard = Some(v);
1102 self.cond.notify_all();
1103 }
1104 }
1105
1106 pub fn deref_blocking(&self) -> Value {
1108 let mut guard = self.value.lock().unwrap();
1109 while guard.is_none() {
1110 guard = self.cond.wait(guard).unwrap();
1111 }
1112 guard.as_ref().unwrap().clone()
1113 }
1114
1115 pub fn is_realized(&self) -> bool {
1117 self.value.lock().unwrap().is_some()
1118 }
1119}
1120
1121impl Default for CljxPromise {
1122 fn default() -> Self {
1123 Self::new()
1124 }
1125}
1126
1127impl std::fmt::Debug for CljxPromise {
1128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1129 write!(f, "Promise")
1130 }
1131}
1132
1133impl cljrs_gc::Trace for CljxPromise {
1134 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1135 {
1136 let value = self.value.lock().unwrap();
1137 if let Some(v) = value.as_ref() {
1138 v.trace(visitor);
1139 }
1140 }
1141 }
1142}
1143
1144pub enum FutureState {
1148 Running,
1149 Done(Value),
1150 Failed(Value),
1154 GasExhausted,
1157 Cancelled,
1158}
1159
1160pub const FUTURE_CANCELLED_MSG: &str = "future was cancelled";
1162
1163pub struct CljxFuture {
1165 pub state: Mutex<FutureState>,
1166 pub cond: Condvar,
1167 observed: std::sync::atomic::AtomicBool,
1171}
1172
1173impl CljxFuture {
1174 pub fn new() -> Self {
1175 Self {
1176 state: Mutex::new(FutureState::Running),
1177 cond: Condvar::new(),
1178 observed: std::sync::atomic::AtomicBool::new(false),
1179 }
1180 }
1181
1182 pub fn is_done(&self) -> bool {
1184 !matches!(&*self.state.lock().unwrap(), FutureState::Running)
1185 }
1186
1187 pub fn is_cancelled(&self) -> bool {
1189 matches!(&*self.state.lock().unwrap(), FutureState::Cancelled)
1190 }
1191
1192 pub fn cancel(&self) -> bool {
1197 let mut state = self.state.lock().unwrap();
1198 if matches!(&*state, FutureState::Running) {
1199 *state = FutureState::Cancelled;
1200 self.cond.notify_all();
1201 true
1202 } else {
1203 false
1204 }
1205 }
1206
1207 pub fn cancelled_error() -> Value {
1214 Value::Error(GcPtr::new(crate::ExceptionInfo::new(
1215 crate::ValueError::Other(FUTURE_CANCELLED_MSG.to_string()),
1216 FUTURE_CANCELLED_MSG.to_string(),
1217 None,
1218 None,
1219 )))
1220 }
1221
1222 pub fn mark_observed(&self) {
1226 self.observed
1227 .store(true, std::sync::atomic::Ordering::Relaxed);
1228 }
1229}
1230
1231impl Drop for CljxFuture {
1232 fn drop(&mut self) {
1233 if !self.observed.load(std::sync::atomic::Ordering::Relaxed)
1244 && let Ok(state) = self.state.lock()
1245 && matches!(&*state, FutureState::Failed(_))
1246 {
1247 eprintln!(
1248 "[clojurust warning] a failed future was discarded without its error \
1249 being observed (no await/deref); the thrown exception was lost"
1250 );
1251 }
1252 }
1253}
1254
1255impl Default for CljxFuture {
1256 fn default() -> Self {
1257 Self::new()
1258 }
1259}
1260
1261impl std::fmt::Debug for CljxFuture {
1262 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1263 write!(f, "Future")
1264 }
1265}
1266
1267impl cljrs_gc::Trace for CljxFuture {
1268 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1269 {
1270 let state = self.state.lock().unwrap();
1271 if let FutureState::Done(v) | FutureState::Failed(v) = &*state {
1274 v.trace(visitor);
1275 }
1276 }
1277 }
1278}
1279
1280pub struct Agent {
1284 pub state: Arc<Mutex<Value>>,
1286 pub error: Arc<Mutex<Option<Value>>>,
1288 pub watches: Mutex<Vec<(Value, Value)>>,
1289}
1290
1291impl Agent {
1292 pub fn get_state(&self) -> Value {
1293 self.state.lock().unwrap().clone()
1294 }
1295
1296 pub fn get_error(&self) -> Option<Value> {
1297 self.error.lock().unwrap().clone()
1298 }
1299
1300 pub fn clear_error(&self) {
1301 *self.error.lock().unwrap() = None;
1302 }
1303}
1304
1305impl std::fmt::Debug for Agent {
1306 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1307 write!(f, "Agent")
1308 }
1309}
1310
1311impl cljrs_gc::Trace for Agent {
1312 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1313 {
1314 let state = self.state.lock().unwrap();
1315 state.trace(visitor);
1316 }
1317 {
1318 let error = self.error.lock().unwrap();
1319 if let Some(e) = error.as_ref() {
1320 e.trace(visitor);
1321 }
1322 }
1323 {
1324 let watches = self.watches.lock().unwrap();
1325 for (key, f) in watches.iter() {
1326 key.trace(visitor);
1327 f.trace(visitor);
1328 }
1329 }
1330 }
1331}
1332
1333#[cfg(test)]
1336mod var_tests {
1337 use super::*;
1338 use crate::shared::SharedValue;
1339
1340 #[test]
1341 fn bind_promotable_mirrors_shared_root() {
1342 let var = Var::new("user", "x");
1343 assert!(var.shared_root.load().is_none());
1344 var.bind(Value::Long(7));
1345 assert!(matches!(
1346 var.shared_root.load().as_ref().as_ref(),
1347 Some(SharedValue::Long(7))
1348 ));
1349 assert_eq!(var.deref(), Some(Value::Long(7)));
1350 assert_eq!(var.deref_shared(), Some(Value::Long(7)));
1351 }
1352
1353 #[test]
1354 fn bind_nonpromotable_clears_shared_root() {
1355 let var = Var::new("user", "f");
1356 var.bind(Value::Long(1));
1357 assert!(var.shared_root.load().is_some());
1358 let f = Value::NativeFunction(GcPtr::new(NativeFn::new("f", Arity::Fixed(0), |_| {
1361 Ok(Value::Nil)
1362 })));
1363 var.bind(f);
1364 assert!(var.shared_root.load().is_none());
1365 assert!(var.is_bound());
1366 assert_eq!(var.deref_shared(), None);
1367 }
1368
1369 #[test]
1370 fn from_shared_root_seeds_local_slot() {
1371 let src = Var::new("user", "y");
1372 src.bind(Value::Long(99));
1373 let recv = Var::from_shared_root("user", "y", false, src.shared_root.clone());
1374 assert_eq!(recv.deref(), Some(Value::Long(99)));
1375 src.bind(Value::Long(100));
1377 assert_eq!(recv.deref_shared(), Some(Value::Long(100)));
1378 }
1379}