1#![allow(unused)]
4
5use std::collections::HashMap;
6use std::mem;
7use std::sync::{Arc, Condvar, Mutex};
8
9use cljrs_gc::GcPtr;
10use cljrs_gc::mutator::TrackedMutex;
11use cljrs_reader::Form;
12
13use crate::TypeHint;
14use crate::Value;
15
16#[cfg(all(feature = "no-gc", debug_assertions))]
31pub(crate) fn value_gcptr_is_static(value: &Value) -> bool {
32 use crate::value::MapValue;
33 use crate::value::SetValue;
34 match value {
35 Value::Nil
37 | Value::Bool(_)
38 | Value::Long(_)
39 | Value::Double(_)
40 | Value::Char(_)
41 | Value::Uuid(_) => true,
42 Value::Resource(_) | Value::SharedAtom(_) | Value::ByteBlob(_) => true,
44 Value::BigInt(p) => p.is_static_alloc(),
46 Value::BigDecimal(p) => p.is_static_alloc(),
47 Value::Ratio(p) => p.is_static_alloc(),
48 Value::Str(p) => p.is_static_alloc(),
49 Value::Pattern(p) => p.is_static_alloc(),
50 Value::Matcher(p) => p.is_static_alloc(),
51 Value::Symbol(p) => p.is_static_alloc(),
52 Value::Keyword(p) => p.is_static_alloc(),
53 Value::List(p) => p.is_static_alloc(),
54 Value::Vector(p) => p.is_static_alloc(),
55 Value::Queue(p) => p.is_static_alloc(),
56 Value::Map(m) => match m {
57 MapValue::Array(p) => p.is_static_alloc(),
58 MapValue::Hash(p) => p.is_static_alloc(),
59 MapValue::Sorted(p) => p.is_static_alloc(),
60 },
61 Value::Set(s) => match s {
62 SetValue::Hash(p) => p.is_static_alloc(),
63 SetValue::Sorted(p) => p.is_static_alloc(),
64 },
65 Value::NativeFunction(p) => p.is_static_alloc(),
66 Value::Fn(p) | Value::Macro(p) => p.is_static_alloc(),
67 Value::BoundFn(p) => p.is_static_alloc(),
68 Value::Var(p) => p.is_static_alloc(),
69 Value::Atom(p) => p.is_static_alloc(),
70 Value::Namespace(p) => p.is_static_alloc(),
71 Value::LazySeq(p) => p.is_static_alloc(),
72 Value::Cons(p) => p.is_static_alloc(),
73 Value::Protocol(p) => p.is_static_alloc(),
74 Value::ProtocolFn(p) => p.is_static_alloc(),
75 Value::MultiFn(p) => p.is_static_alloc(),
76 Value::Volatile(p) => p.is_static_alloc(),
77 Value::Delay(p) => p.is_static_alloc(),
78 Value::Promise(p) => p.is_static_alloc(),
79 Value::Future(p) => p.is_static_alloc(),
80 Value::Agent(p) => p.is_static_alloc(),
81 Value::TypeInstance(p) => p.is_static_alloc(),
82 Value::ObjectArray(p) => p.is_static_alloc(),
83 Value::NativeObject(p) => p.is_static_alloc(),
84 Value::Error(p) => p.is_static_alloc(),
85 Value::TransientMap(p) => p.is_static_alloc(),
86 Value::TransientVector(p) => p.is_static_alloc(),
87 Value::TransientSet(p) => p.is_static_alloc(),
88 Value::BooleanArray(_)
90 | Value::ByteArray(_)
91 | Value::ShortArray(_)
92 | Value::IntArray(_)
93 | Value::LongArray(_)
94 | Value::FloatArray(_)
95 | Value::DoubleArray(_)
96 | Value::CharArray(_) => true,
97 Value::Reduced(inner) | Value::WithMeta(inner, _) => value_gcptr_is_static(inner),
99 }
100}
101
102pub type MethodMap = HashMap<Arc<str>, Value>;
106
107#[derive(Debug)]
109pub struct Protocol {
110 pub name: Arc<str>,
111 pub ns: Arc<str>,
112 pub methods: Vec<ProtocolMethod>,
113 pub impls: TrackedMutex<HashMap<Arc<str>, MethodMap>>,
115 pub extend_via_metadata: bool,
119}
120
121impl Protocol {
122 pub fn new(
123 name: Arc<str>,
124 ns: Arc<str>,
125 methods: Vec<ProtocolMethod>,
126 extend_via_metadata: bool,
127 ) -> Self {
128 Self {
129 name,
130 ns,
131 methods,
132 impls: TrackedMutex::new(HashMap::new()),
133 extend_via_metadata,
134 }
135 }
136}
137
138static PROTOCOL_GENERATION: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
147
148pub fn protocol_generation() -> u64 {
150 PROTOCOL_GENERATION.load(std::sync::atomic::Ordering::Acquire)
151}
152
153pub fn bump_protocol_generation() {
156 PROTOCOL_GENERATION.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
157}
158
159impl cljrs_gc::Trace for Protocol {
160 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
161 {
162 let impls = self.impls.lock().unwrap();
163 for method_map in impls.values() {
164 for v in method_map.values() {
165 v.trace(visitor);
166 }
167 }
168 }
169 }
170}
171
172#[derive(Debug, Clone)]
174pub struct ProtocolMethod {
175 pub name: Arc<str>,
176 pub min_arity: usize,
177 pub variadic: bool,
178}
179
180impl cljrs_gc::Trace for ProtocolMethod {
181 fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
182}
183
184#[derive(Debug)]
188pub struct ProtocolFn {
189 pub protocol: GcPtr<Protocol>,
190 pub method_name: Arc<str>,
191 pub min_arity: usize,
192 pub variadic: bool,
193}
194
195impl cljrs_gc::Trace for ProtocolFn {
196 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
197 use cljrs_gc::GcVisitor as _;
198 visitor.visit(&self.protocol);
199 }
200}
201
202#[derive(Debug)]
206pub struct MultiFn {
207 pub name: Arc<str>,
208 pub dispatch_fn: Value,
209 pub methods: TrackedMutex<HashMap<String, Value>>,
211 pub dispatch_vals: TrackedMutex<HashMap<String, Value>>,
213 pub prefers: TrackedMutex<HashMap<String, Vec<String>>>,
215 pub preference_vals: TrackedMutex<HashMap<String, Value>>,
218 pub default_dispatch: String,
220 method_generation: std::sync::atomic::AtomicU64,
221 method_cache: TrackedMutex<MultiFnMethodCache>,
222}
223
224#[derive(Debug, Default)]
225struct MultiFnMethodCache {
226 hierarchy_generation: u64,
227 method_generation: u64,
228 methods: HashMap<String, String>,
229}
230
231impl MultiFn {
232 pub fn new(name: Arc<str>, dispatch_fn: Value, default_dispatch: String) -> Self {
233 Self {
234 name,
235 dispatch_fn,
236 methods: TrackedMutex::new(HashMap::new()),
237 dispatch_vals: TrackedMutex::new(HashMap::new()),
238 prefers: TrackedMutex::new(HashMap::new()),
239 preference_vals: TrackedMutex::new(HashMap::new()),
240 default_dispatch,
241 method_generation: std::sync::atomic::AtomicU64::new(0),
242 method_cache: TrackedMutex::new(MultiFnMethodCache::default()),
243 }
244 }
245
246 pub fn method_generation(&self) -> u64 {
248 self.method_generation
249 .load(std::sync::atomic::Ordering::Acquire)
250 }
251
252 pub fn bump_method_generation(&self) {
254 self.method_generation
255 .fetch_add(1, std::sync::atomic::Ordering::AcqRel);
256 }
257
258 pub fn cached_method(
261 &self,
262 dispatch_key: &str,
263 hierarchy_generation: u64,
264 method_generation: u64,
265 ) -> Option<String> {
266 let mut cache = self.method_cache.lock().unwrap();
267 if cache.hierarchy_generation != hierarchy_generation
268 || cache.method_generation != method_generation
269 {
270 cache.hierarchy_generation = hierarchy_generation;
271 cache.method_generation = method_generation;
272 cache.methods.clear();
273 }
274 cache.methods.get(dispatch_key).cloned()
275 }
276
277 pub fn cache_method(
281 &self,
282 dispatch_key: String,
283 method_key: String,
284 hierarchy_generation: u64,
285 method_generation: u64,
286 ) {
287 let mut cache = self.method_cache.lock().unwrap();
288 if cache.hierarchy_generation != hierarchy_generation
289 || cache.method_generation != method_generation
290 {
291 cache.hierarchy_generation = hierarchy_generation;
292 cache.method_generation = method_generation;
293 cache.methods.clear();
294 }
295 cache.methods.insert(dispatch_key, method_key);
296 }
297
298 pub fn cached_method_count(&self) -> usize {
300 self.method_cache.lock().unwrap().methods.len()
301 }
302}
303
304impl cljrs_gc::Trace for MultiFn {
305 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
306 self.dispatch_fn.trace(visitor);
307 {
308 let methods = self.methods.lock().unwrap();
309 for v in methods.values() {
310 v.trace(visitor);
311 }
312 }
313 {
314 let dispatch_vals = self.dispatch_vals.lock().unwrap();
315 for v in dispatch_vals.values() {
316 v.trace(visitor);
317 }
318 }
319 {
320 let preference_vals = self.preference_vals.lock().unwrap();
321 for v in preference_vals.values() {
322 v.trace(visitor);
323 }
324 }
325 }
326}
327
328#[derive(Debug)]
355pub struct Var {
356 pub namespace: Arc<str>,
357 pub name: Arc<str>,
358 pub value: TrackedMutex<Option<Value>>,
359 pub shared_root: Arc<arc_swap::ArcSwap<Option<crate::shared::SharedValue>>>,
362 pub is_macro: bool,
363 pub meta: TrackedMutex<Option<Value>>,
365 pub watches: TrackedMutex<Vec<(Value, Value)>>,
366 binding_generation: std::sync::atomic::AtomicU64,
367}
368
369impl Var {
370 pub fn new(namespace: impl Into<Arc<str>>, name: impl Into<Arc<str>>) -> Self {
371 Self {
372 namespace: namespace.into(),
373 name: name.into(),
374 value: TrackedMutex::new(None),
375 shared_root: Arc::new(arc_swap::ArcSwap::new(Arc::new(None))),
376 is_macro: false,
377 meta: TrackedMutex::new(None),
378 watches: TrackedMutex::new(Vec::new()),
379 binding_generation: std::sync::atomic::AtomicU64::new(0),
380 }
381 }
382
383 pub fn from_shared_root(
390 namespace: impl Into<Arc<str>>,
391 name: impl Into<Arc<str>>,
392 is_macro: bool,
393 shared_root: Arc<arc_swap::ArcSwap<Option<crate::shared::SharedValue>>>,
394 ) -> Self {
395 let local = shared_root
396 .load()
397 .as_ref()
398 .as_ref()
399 .map(crate::shared::demote);
400 Self {
401 namespace: namespace.into(),
402 name: name.into(),
403 value: TrackedMutex::new(local),
404 shared_root,
405 is_macro,
406 meta: TrackedMutex::new(None),
407 watches: TrackedMutex::new(Vec::new()),
408 binding_generation: std::sync::atomic::AtomicU64::new(0),
409 }
410 }
411
412 pub fn is_bound(&self) -> bool {
413 self.value.lock().unwrap().is_some()
414 }
415
416 pub fn deref(&self) -> Option<Value> {
417 self.value.lock().unwrap().clone()
418 }
419
420 pub fn binding_generation(&self) -> u64 {
423 self.binding_generation
424 .load(std::sync::atomic::Ordering::Acquire)
425 }
426
427 pub fn deref_shared(&self) -> Option<Value> {
432 self.shared_root
433 .load()
434 .as_ref()
435 .as_ref()
436 .map(crate::shared::demote)
437 }
438
439 pub fn bind(&self, v: Value) {
440 #[cfg(all(feature = "no-gc", debug_assertions))]
444 debug_assert!(
445 value_gcptr_is_static(&v),
446 "no-gc: Var::bind({}/{}) received a region-local value — store violations \
447 indicate a missing StaticCtxGuard around the value expression",
448 self.namespace,
449 self.name
450 );
451 let v = crate::publish::publish_value(v);
456 let prev = {
462 let mut slot = self.value.lock().unwrap();
463 slot.replace(v.clone())
464 };
465 let shared = crate::shared::promote(&v).ok();
471 self.shared_root.store(Arc::new(shared));
472 self.binding_generation
473 .fetch_add(1, std::sync::atomic::Ordering::AcqRel);
474 if let Some(prev) = prev {
475 crate::jit_hooks::notify_var_rebind(&prev, &v);
476 }
477 }
478
479 pub fn get_meta(&self) -> Option<Value> {
480 self.meta.lock().unwrap().clone()
481 }
482
483 pub fn set_meta(&self, m: Value) {
484 *self.meta.lock().unwrap() = Some(m);
485 }
486
487 pub fn full_name(&self) -> String {
488 format!("{}/{}", self.namespace, self.name)
489 }
490}
491
492impl cljrs_gc::Trace for Var {
493 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
494 {
495 let value = self.value.lock().unwrap();
496 if let Some(v) = value.as_ref() {
497 v.trace(visitor);
498 }
499 }
500 {
501 let meta = self.meta.lock().unwrap();
502 if let Some(m) = meta.as_ref() {
503 m.trace(visitor);
504 }
505 }
506 {
507 let watches = self.watches.lock().unwrap();
508 for (key, f) in watches.iter() {
509 key.trace(visitor);
510 f.trace(visitor);
511 }
512 }
513 }
514}
515
516#[derive(Debug)]
520pub struct Atom {
521 pub value: TrackedMutex<Value>,
522 pub meta: TrackedMutex<Option<Value>>,
523 pub validator: TrackedMutex<Option<Value>>,
524 pub watches: TrackedMutex<Vec<(Value, Value)>>,
525}
526
527impl Atom {
528 pub fn new(v: Value) -> Self {
529 let v = crate::publish::publish_value(v);
532 Self {
533 value: TrackedMutex::new(v),
534 meta: TrackedMutex::new(None),
535 validator: TrackedMutex::new(None),
536 watches: TrackedMutex::new(Vec::new()),
537 }
538 }
539
540 pub fn deref(&self) -> Value {
541 self.value.lock().unwrap().clone()
542 }
543
544 pub fn reset(&self, v: Value) -> Value {
545 #[cfg(all(feature = "no-gc", debug_assertions))]
547 debug_assert!(
548 value_gcptr_is_static(&v),
549 "no-gc: Atom::reset() received a region-local value — the new-value \
550 expression must be computed inside a StaticCtxGuard (i.e. inside \
551 the swap! / reset! call) so it is allocated in the static arena"
552 );
553 let v = crate::publish::publish_value(v);
555 let mut guard = self.value.lock().unwrap();
556 *guard = v.clone();
557 v
558 }
559
560 pub fn get_meta(&self) -> Option<Value> {
561 self.meta.lock().unwrap().clone()
562 }
563
564 pub fn set_meta(&self, m: Option<Value>) {
565 *self.meta.lock().unwrap() = m;
566 }
567
568 pub fn get_validator(&self) -> Option<Value> {
569 self.validator.lock().unwrap().clone()
570 }
571
572 pub fn set_validator(&self, vf: Option<Value>) {
573 *self.validator.lock().unwrap() = vf;
574 }
575}
576
577impl cljrs_gc::Trace for Atom {
578 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
579 {
580 let value = self.value.lock().unwrap();
581 value.trace(visitor);
582 }
583 {
584 let meta = self.meta.lock().unwrap();
585 if let Some(m) = meta.as_ref() {
586 m.trace(visitor);
587 }
588 }
589 {
590 let validator = self.validator.lock().unwrap();
591 if let Some(vf) = validator.as_ref() {
592 vf.trace(visitor);
593 }
594 }
595 {
596 let watches = self.watches.lock().unwrap();
597 for (key, f) in watches.iter() {
598 key.trace(visitor);
599 f.trace(visitor);
600 }
601 }
602 }
603}
604
605#[derive(Debug, Clone, Default)]
611pub struct ReferClojureFilter {
612 pub only: Option<std::collections::HashSet<Arc<str>>>,
614 pub exclude: std::collections::HashSet<Arc<str>>,
616 pub rename: HashMap<Arc<str>, Arc<str>>,
620}
621
622impl ReferClojureFilter {
623 pub fn local_name(&self, name: &Arc<str>) -> Option<Arc<str>> {
626 if self.exclude.contains(name) {
627 return None;
628 }
629 if let Some(only) = &self.only
630 && !only.contains(name)
631 {
632 return None;
633 }
634 Some(
635 self.rename
636 .get(name)
637 .cloned()
638 .unwrap_or_else(|| name.clone()),
639 )
640 }
641}
642
643#[derive(Debug)]
645pub struct Namespace {
646 pub name: Arc<str>,
647 pub interns: TrackedMutex<HashMap<Arc<str>, GcPtr<Var>>>,
649 pub refers: TrackedMutex<HashMap<Arc<str>, GcPtr<Var>>>,
651 pub aliases: TrackedMutex<HashMap<Arc<str>, Arc<str>>>,
653 pub source_file: TrackedMutex<Option<Arc<str>>>,
657 pub git_repo_root: TrackedMutex<Option<Arc<str>>>,
659 pub is_versioned: bool,
662 pub meta: TrackedMutex<Option<Value>>,
664 pub refer_clojure_filter: TrackedMutex<Option<ReferClojureFilter>>,
667}
668
669impl Namespace {
670 pub fn new(name: impl Into<Arc<str>>) -> Self {
671 Self {
672 name: name.into(),
673 interns: TrackedMutex::new(HashMap::new()),
674 refers: TrackedMutex::new(HashMap::new()),
675 aliases: TrackedMutex::new(HashMap::new()),
676 source_file: TrackedMutex::new(None),
677 git_repo_root: TrackedMutex::new(None),
678 is_versioned: false,
679 meta: TrackedMutex::new(None),
680 refer_clojure_filter: TrackedMutex::new(None),
681 }
682 }
683
684 pub fn new_versioned(name: impl Into<Arc<str>>) -> Self {
686 Self {
687 is_versioned: true,
688 ..Self::new(name)
689 }
690 }
691
692 pub fn set_source_location(&self, file: &str, repo_root: Option<&str>) {
694 *self.source_file.lock().unwrap() = Some(Arc::from(file));
695 *self.git_repo_root.lock().unwrap() = repo_root.map(Arc::from);
696 }
697
698 pub fn get_meta(&self) -> Option<Value> {
699 self.meta.lock().unwrap().clone()
700 }
701
702 pub fn set_meta(&self, m: Value) {
703 *self.meta.lock().unwrap() = Some(m);
704 }
705}
706
707impl cljrs_gc::Trace for Namespace {
708 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
709 use cljrs_gc::GcVisitor as _;
710 {
711 let interns = self.interns.lock().unwrap();
712 for var in interns.values() {
713 visitor.visit(var);
714 }
715 }
716 {
717 let refers = self.refers.lock().unwrap();
718 for var in refers.values() {
719 visitor.visit(var);
720 }
721 }
722 {
723 let meta = self.meta.lock().unwrap();
724 if let Some(m) = meta.as_ref() {
725 m.trace(visitor);
726 }
727 }
728 }
729}
730
731pub type NativeFnPtr = fn(&[Value]) -> crate::error::ValueResult<Value>;
737
738pub type NativeFnFunc = Arc<dyn Fn(&[Value]) -> crate::error::ValueResult<Value>>;
741
742#[derive(Clone, Debug)]
743pub enum Arity {
744 Fixed(usize),
745 Variadic { min: usize },
746}
747
748pub struct NativeFn {
749 pub name: Arc<str>,
750 pub arity: Arity,
751 pub func: NativeFnFunc,
752}
753
754impl NativeFn {
755 pub fn new(name: impl Into<Arc<str>>, arity: Arity, func: NativeFnPtr) -> Self {
757 Self {
758 name: name.into(),
759 arity,
760 func: Arc::new(func),
761 }
762 }
763
764 pub fn with_closure(
766 name: impl Into<Arc<str>>,
767 arity: Arity,
768 func: impl Fn(&[Value]) -> crate::error::ValueResult<Value> + 'static,
769 ) -> Self {
770 Self {
771 name: name.into(),
772 arity,
773 func: Arc::new(func),
774 }
775 }
776}
777
778impl std::fmt::Debug for NativeFn {
779 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
780 f.debug_struct("NativeFn")
781 .field("name", &self.name)
782 .field("arity", &self.arity)
783 .field("func", &"<fn>")
784 .finish()
785 }
786}
787
788impl cljrs_gc::Trace for NativeFn {
789 fn trace(&self, _: &mut cljrs_gc::MarkVisitor) {}
790}
791
792#[derive(Debug, Clone)]
796pub struct CljxFnArity {
797 pub params: Vec<Arc<str>>,
800 pub rest_param: Option<Arc<str>>,
802 pub body: Vec<Form>,
804 pub destructure_params: Vec<(usize, Form)>,
808 pub destructure_rest: Option<Form>,
810 pub ir_arity_id: u64,
812 pub param_hints: Vec<Option<TypeHint>>,
816 pub rest_hint: Option<TypeHint>,
819}
820
821impl CljxFnArity {
822 pub fn heap_size(&self) -> usize {
824 self.params.capacity() * mem::size_of::<Arc<str>>()
826 + self.body.capacity() * mem::size_of::<Form>()
828 + self.body.iter().map(|f| f.heap_size()).sum::<usize>()
829 + self.destructure_params.capacity() * mem::size_of::<(usize, Form)>()
831 + self.destructure_params.iter().map(|(_, f)| f.heap_size()).sum::<usize>()
832 + self.destructure_rest.as_ref()
834 .map_or(0, |f| mem::size_of::<Form>() + f.heap_size())
835 + self.param_hints.capacity() * mem::size_of::<Option<TypeHint>>()
837 }
838}
839
840#[derive(Debug, Clone)]
844pub struct CljxFn {
845 pub name: Option<Arc<str>>,
846 pub arities: Vec<CljxFnArity>,
847 pub closed_over_names: Vec<Arc<str>>,
849 pub closed_over_vals: Vec<Value>,
851 pub is_macro: bool,
853 pub is_async: bool,
858 pub defining_ns: Arc<str>,
860 pub self_ptr: Option<GcPtr<CljxFn>>,
865}
866
867impl CljxFn {
868 pub fn new(
869 name: Option<Arc<str>>,
870 arities: Vec<CljxFnArity>,
871 closed_over_names: Vec<Arc<str>>,
872 closed_over_vals: Vec<Value>,
873 is_macro: bool,
874 defining_ns: Arc<str>,
875 ) -> Self {
876 Self {
877 name,
878 arities,
879 closed_over_names,
880 closed_over_vals,
881 is_macro,
882 is_async: false,
883 defining_ns,
884 self_ptr: None,
885 }
886 }
887}
888
889impl cljrs_gc::Trace for CljxFn {
890 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
891 use cljrs_gc::GcVisitor as _;
892 for v in &self.closed_over_vals {
893 v.trace(visitor);
894 }
895 if let Some(ref p) = self.self_ptr {
896 visitor.visit(p);
897 }
898 }
899
900 fn gc_size_extra(&self) -> usize {
901 self.arities.capacity() * mem::size_of::<CljxFnArity>()
903 + self
904 .arities
905 .iter()
906 .map(CljxFnArity::heap_size)
907 .sum::<usize>()
908 }
909}
910
911#[derive(Debug)]
918pub struct BoundFn {
919 pub wrapped: Value,
921 pub captured_bindings: HashMap<usize, Value>,
923}
924
925impl cljrs_gc::Trace for BoundFn {
926 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
927 self.wrapped.trace(visitor);
928 for val in self.captured_bindings.values() {
929 val.trace(visitor);
930 }
931 }
932
933 fn gc_size_extra(&self) -> usize {
934 self.captured_bindings.capacity() * (1 + mem::size_of::<usize>() + mem::size_of::<Value>())
936 }
937}
938
939pub trait Thunk: std::fmt::Debug + cljrs_gc::Trace {
943 fn force(&self) -> Result<Value, String>;
944}
945
946pub enum LazySeqState {
948 Pending(Box<dyn Thunk>),
950 Forced(Value),
952 Error(String),
954}
955
956pub struct LazySeq {
958 pub state: TrackedMutex<LazySeqState>,
959}
960
961impl LazySeq {
962 pub fn new(thunk: Box<dyn Thunk>) -> Self {
963 Self {
964 state: TrackedMutex::new(LazySeqState::Pending(thunk)),
965 }
966 }
967
968 pub fn realize(&self) -> Value {
971 let thunk = {
972 let mut guard = self.state.lock().unwrap();
973 match &*guard {
974 LazySeqState::Forced(v) => return v.clone(),
975 LazySeqState::Error(_) => return Value::Nil,
976 LazySeqState::Pending(_) => {}
977 }
978 let prev = mem::replace(&mut *guard, LazySeqState::Forced(Value::Nil));
980 let LazySeqState::Pending(thunk) = prev else {
981 unreachable!("state was not Pending")
982 };
983 thunk
984 };
986 match thunk.force() {
989 Ok(result) => {
990 *self.state.lock().unwrap() = LazySeqState::Forced(result.clone());
991 result
992 }
993 Err(msg) => {
994 *self.state.lock().unwrap() = LazySeqState::Error(msg);
995 Value::Nil
996 }
997 }
998 }
999
1000 pub fn error(&self) -> Option<String> {
1002 let guard = self.state.lock().unwrap();
1003 if let LazySeqState::Error(e) = &*guard {
1004 Some(e.clone())
1005 } else {
1006 None
1007 }
1008 }
1009}
1010
1011impl std::fmt::Debug for LazySeq {
1012 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1013 write!(f, "LazySeq(...)")
1014 }
1015}
1016
1017impl cljrs_gc::Trace for LazySeq {
1018 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1019 {
1022 let state = self.state.lock().unwrap();
1023 match &*state {
1024 LazySeqState::Pending(thunk) => thunk.trace(visitor),
1025 LazySeqState::Forced(v) => v.trace(visitor),
1026 LazySeqState::Error(_) => {}
1027 }
1028 }
1029 }
1030}
1031
1032#[derive(Debug, Clone)]
1039pub struct CljxCons {
1040 pub head: Value,
1041 pub tail: Value,
1042}
1043
1044impl cljrs_gc::Trace for CljxCons {
1045 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1046 self.head.trace(visitor);
1047 self.tail.trace(visitor);
1048 }
1049}
1050
1051pub struct Volatile {
1055 pub value: TrackedMutex<Value>,
1056}
1057
1058impl Volatile {
1059 pub fn new(v: Value) -> Self {
1060 let v = crate::publish::publish_value(v);
1062 Self {
1063 value: TrackedMutex::new(v),
1064 }
1065 }
1066
1067 pub fn deref(&self) -> Value {
1068 self.value.lock().unwrap().clone()
1069 }
1070
1071 pub fn reset(&self, v: Value) -> Value {
1072 #[cfg(all(feature = "no-gc", debug_assertions))]
1074 debug_assert!(
1075 value_gcptr_is_static(&v),
1076 "no-gc: Volatile::reset() received a region-local value — ensure the \
1077 new-value expression is inside a StaticCtxGuard (vreset! handles this)"
1078 );
1079 let v = crate::publish::publish_value(v);
1081 *self.value.lock().unwrap() = v.clone();
1082 v
1083 }
1084}
1085
1086impl std::fmt::Debug for Volatile {
1087 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1088 write!(f, "Volatile")
1089 }
1090}
1091
1092impl cljrs_gc::Trace for Volatile {
1093 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1094 {
1095 let value = self.value.lock().unwrap();
1096 value.trace(visitor);
1097 }
1098 }
1099}
1100
1101pub enum DelayState {
1105 Pending(Box<dyn Thunk>),
1106 Forced(Value),
1107}
1108
1109pub struct Delay {
1111 pub state: TrackedMutex<DelayState>,
1112}
1113
1114impl Delay {
1115 pub fn new(thunk: Box<dyn Thunk>) -> Self {
1116 Self {
1117 state: TrackedMutex::new(DelayState::Pending(thunk)),
1118 }
1119 }
1120
1121 pub fn force(&self) -> Result<Value, String> {
1124 let thunk = {
1125 let mut guard = self.state.lock().unwrap();
1126 if let DelayState::Forced(v) = &*guard {
1127 return Ok(v.clone());
1128 }
1129 let prev = mem::replace(&mut *guard, DelayState::Forced(Value::Nil));
1130 let DelayState::Pending(thunk) = prev else {
1131 unreachable!("state was not Pending")
1132 };
1133 thunk
1134 };
1136 let result = thunk.force()?;
1139 *self.state.lock().unwrap() = DelayState::Forced(result.clone());
1140 Ok(result)
1141 }
1142
1143 pub fn is_realized(&self) -> bool {
1145 matches!(&*self.state.lock().unwrap(), DelayState::Forced(_))
1146 }
1147}
1148
1149impl std::fmt::Debug for Delay {
1150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1151 write!(f, "Delay")
1152 }
1153}
1154
1155impl cljrs_gc::Trace for Delay {
1156 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1157 {
1160 let state = self.state.lock().unwrap();
1161 match &*state {
1162 DelayState::Pending(thunk) => thunk.trace(visitor),
1163 DelayState::Forced(v) => v.trace(visitor),
1164 }
1165 }
1166 }
1167}
1168
1169pub struct CljxPromise {
1173 pub value: Mutex<Option<Value>>,
1174 pub cond: Condvar,
1175}
1176
1177impl CljxPromise {
1178 pub fn new() -> Self {
1179 Self {
1180 value: Mutex::new(None),
1181 cond: Condvar::new(),
1182 }
1183 }
1184
1185 pub fn deliver(&self, v: Value) {
1187 let v = crate::publish::publish_value(v);
1190 let mut guard = self.value.lock().unwrap();
1191 if guard.is_none() {
1192 *guard = Some(v);
1193 self.cond.notify_all();
1194 }
1195 }
1196
1197 pub fn deref_blocking(&self) -> Value {
1199 let mut guard = self.value.lock().unwrap();
1200 while guard.is_none() {
1201 guard = self.cond.wait(guard).unwrap();
1202 }
1203 guard.as_ref().unwrap().clone()
1204 }
1205
1206 pub fn is_realized(&self) -> bool {
1208 self.value.lock().unwrap().is_some()
1209 }
1210}
1211
1212impl Default for CljxPromise {
1213 fn default() -> Self {
1214 Self::new()
1215 }
1216}
1217
1218impl std::fmt::Debug for CljxPromise {
1219 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1220 write!(f, "Promise")
1221 }
1222}
1223
1224impl cljrs_gc::Trace for CljxPromise {
1225 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1226 {
1227 let value = self.value.lock().unwrap();
1228 if let Some(v) = value.as_ref() {
1229 v.trace(visitor);
1230 }
1231 }
1232 }
1233}
1234
1235pub enum FutureState {
1239 Running,
1240 Done(Value),
1241 Failed(Value),
1245 GasExhausted,
1248 MemoryLimitExceeded(cljrs_gc::MemoryLimitExceeded),
1251 Cancelled,
1252}
1253
1254pub const FUTURE_CANCELLED_MSG: &str = "future was cancelled";
1256
1257pub struct CljxFuture {
1259 pub state: Mutex<FutureState>,
1260 pub cond: Condvar,
1261 observed: std::sync::atomic::AtomicBool,
1265}
1266
1267impl CljxFuture {
1268 pub fn new() -> Self {
1269 Self {
1270 state: Mutex::new(FutureState::Running),
1271 cond: Condvar::new(),
1272 observed: std::sync::atomic::AtomicBool::new(false),
1273 }
1274 }
1275
1276 pub fn is_done(&self) -> bool {
1278 !matches!(&*self.state.lock().unwrap(), FutureState::Running)
1279 }
1280
1281 pub fn is_cancelled(&self) -> bool {
1283 matches!(&*self.state.lock().unwrap(), FutureState::Cancelled)
1284 }
1285
1286 pub fn cancel(&self) -> bool {
1291 let mut state = self.state.lock().unwrap();
1292 if matches!(&*state, FutureState::Running) {
1293 *state = FutureState::Cancelled;
1294 self.cond.notify_all();
1295 true
1296 } else {
1297 false
1298 }
1299 }
1300
1301 pub fn cancelled_error() -> Value {
1308 Value::Error(GcPtr::new(crate::ExceptionInfo::new(
1309 crate::ValueError::Other(FUTURE_CANCELLED_MSG.to_string()),
1310 FUTURE_CANCELLED_MSG.to_string(),
1311 None,
1312 None,
1313 )))
1314 }
1315
1316 pub fn mark_observed(&self) {
1320 self.observed
1321 .store(true, std::sync::atomic::Ordering::Relaxed);
1322 }
1323}
1324
1325impl Drop for CljxFuture {
1326 fn drop(&mut self) {
1327 if !self.observed.load(std::sync::atomic::Ordering::Relaxed)
1338 && let Ok(state) = self.state.lock()
1339 && matches!(&*state, FutureState::Failed(_))
1340 {
1341 eprintln!(
1342 "[clojurust warning] a failed future was discarded without its error \
1343 being observed (no await/deref); the thrown exception was lost"
1344 );
1345 }
1346 }
1347}
1348
1349impl Default for CljxFuture {
1350 fn default() -> Self {
1351 Self::new()
1352 }
1353}
1354
1355impl std::fmt::Debug for CljxFuture {
1356 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1357 write!(f, "Future")
1358 }
1359}
1360
1361impl cljrs_gc::Trace for CljxFuture {
1362 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1363 {
1364 let state = self.state.lock().unwrap();
1365 if let FutureState::Done(v) | FutureState::Failed(v) = &*state {
1368 v.trace(visitor);
1369 }
1370 }
1371 }
1372}
1373
1374pub struct Agent {
1378 pub state: Arc<Mutex<Value>>,
1380 pub error: Arc<Mutex<Option<Value>>>,
1382 pub watches: TrackedMutex<Vec<(Value, Value)>>,
1383}
1384
1385impl Agent {
1386 pub fn get_state(&self) -> Value {
1387 self.state.lock().unwrap().clone()
1388 }
1389
1390 pub fn get_error(&self) -> Option<Value> {
1391 self.error.lock().unwrap().clone()
1392 }
1393
1394 pub fn clear_error(&self) {
1395 *self.error.lock().unwrap() = None;
1396 }
1397}
1398
1399impl std::fmt::Debug for Agent {
1400 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1401 write!(f, "Agent")
1402 }
1403}
1404
1405impl cljrs_gc::Trace for Agent {
1406 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
1407 {
1408 let state = self.state.lock().unwrap();
1409 state.trace(visitor);
1410 }
1411 {
1412 let error = self.error.lock().unwrap();
1413 if let Some(e) = error.as_ref() {
1414 e.trace(visitor);
1415 }
1416 }
1417 {
1418 let watches = self.watches.lock().unwrap();
1419 for (key, f) in watches.iter() {
1420 key.trace(visitor);
1421 f.trace(visitor);
1422 }
1423 }
1424 }
1425}
1426
1427#[cfg(test)]
1430mod var_tests {
1431 use super::*;
1432 use crate::shared::SharedValue;
1433
1434 #[test]
1435 fn bind_promotable_mirrors_shared_root() {
1436 let var = Var::new("user", "x");
1437 assert!(var.shared_root.load().is_none());
1438 var.bind(Value::Long(7));
1439 assert!(matches!(
1440 var.shared_root.load().as_ref().as_ref(),
1441 Some(SharedValue::Long(7))
1442 ));
1443 assert_eq!(var.deref(), Some(Value::Long(7)));
1444 assert_eq!(var.deref_shared(), Some(Value::Long(7)));
1445 }
1446
1447 #[test]
1448 fn bind_nonpromotable_clears_shared_root() {
1449 let var = Var::new("user", "f");
1450 var.bind(Value::Long(1));
1451 assert!(var.shared_root.load().is_some());
1452 let f = Value::NativeFunction(GcPtr::new(NativeFn::new("f", Arity::Fixed(0), |_| {
1455 Ok(Value::Nil)
1456 })));
1457 var.bind(f);
1458 assert!(var.shared_root.load().is_none());
1459 assert!(var.is_bound());
1460 assert_eq!(var.deref_shared(), None);
1461 }
1462
1463 #[test]
1464 fn from_shared_root_seeds_local_slot() {
1465 let src = Var::new("user", "y");
1466 src.bind(Value::Long(99));
1467 let recv = Var::from_shared_root("user", "y", false, src.shared_root.clone());
1468 assert_eq!(recv.deref(), Some(Value::Long(99)));
1469 src.bind(Value::Long(100));
1471 assert_eq!(recv.deref_shared(), Some(Value::Long(100)));
1472 }
1473}