1use std::sync::{Arc, Mutex};
31
32use arc_swap::ArcSwap;
33use cljrs_gc::{GcPtr, StaticGcPtr};
34
35use crate::intern::{intern_keyword, intern_symbol};
36use crate::keyword::Keyword;
37use crate::symbol::Symbol;
38use crate::value::Value;
39
40#[derive(Debug, Clone)]
44pub struct PromoteError {
45 pub type_name: &'static str,
46}
47
48impl PromoteError {
49 pub fn not_promotable(type_name: &'static str) -> Self {
50 Self { type_name }
51 }
52}
53
54impl std::fmt::Display for PromoteError {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 write!(
57 f,
58 "value of type '{}' cannot be promoted to a shared-atom: \
59 only scalars, strings, keywords, symbols, and byte-blobs are supported",
60 self.type_name
61 )
62 }
63}
64
65impl std::error::Error for PromoteError {}
66
67#[derive(Debug, Clone)]
79pub enum SharedValue {
80 Nil,
81 Bool(bool),
82 Long(i64),
83 Double(f64),
84 Char(char),
85 Uuid(u128),
86 Str(Arc<str>),
88 Keyword(StaticGcPtr<Keyword>),
90 Symbol(StaticGcPtr<Symbol>),
92 ByteBlob(Arc<[u8]>),
96}
97
98unsafe impl Send for SharedValue {}
101unsafe impl Sync for SharedValue {}
102
103impl SharedValue {
104 pub fn type_name(&self) -> &'static str {
105 match self {
106 SharedValue::Nil => "nil",
107 SharedValue::Bool(_) => "boolean",
108 SharedValue::Long(_) => "long",
109 SharedValue::Double(_) => "double",
110 SharedValue::Char(_) => "char",
111 SharedValue::Uuid(_) => "uuid",
112 SharedValue::Str(_) => "string",
113 SharedValue::Keyword(_) => "keyword",
114 SharedValue::Symbol(_) => "symbol",
115 SharedValue::ByteBlob(_) => "byte-blob",
116 }
117 }
118}
119
120pub fn promote(value: &Value) -> Result<SharedValue, PromoteError> {
129 match value {
130 Value::Nil => Ok(SharedValue::Nil),
131 Value::Bool(b) => Ok(SharedValue::Bool(*b)),
132 Value::Long(n) => Ok(SharedValue::Long(*n)),
133 Value::Double(d) => Ok(SharedValue::Double(*d)),
134 Value::Char(c) => Ok(SharedValue::Char(*c)),
135 Value::Uuid(u) => Ok(SharedValue::Uuid(*u)),
136 Value::Str(s) => Ok(SharedValue::Str(Arc::from(s.get().as_str()))),
137 Value::Keyword(kw) => {
138 let kw = kw.get();
139 let ptr = intern_keyword(kw.namespace.as_deref(), &kw.name);
140 Ok(SharedValue::Keyword(ptr))
141 }
142 Value::Symbol(sym) => {
143 let sym = sym.get();
144 let ptr = intern_symbol(sym.namespace.as_deref(), &sym.name, sym.version.as_deref());
145 Ok(SharedValue::Symbol(ptr))
146 }
147 Value::ByteArray(arr) => {
148 let bytes = arr.get().lock().unwrap();
149 let blob: Arc<[u8]> = bytes.iter().map(|&b| b as u8).collect::<Vec<_>>().into();
150 Ok(SharedValue::ByteBlob(blob))
151 }
152 Value::ByteBlob(blob) => Ok(SharedValue::ByteBlob(blob.clone())),
153 other => Err(PromoteError::not_promotable(other.type_name())),
154 }
155}
156
157pub fn demote(sv: &SharedValue) -> Value {
164 match sv {
165 SharedValue::Nil => Value::Nil,
166 SharedValue::Bool(b) => Value::Bool(*b),
167 SharedValue::Long(n) => Value::Long(*n),
168 SharedValue::Double(d) => Value::Double(*d),
169 SharedValue::Char(c) => Value::Char(*c),
170 SharedValue::Uuid(u) => Value::Uuid(*u),
171 SharedValue::Str(s) => Value::Str(GcPtr::new(s.as_ref().to_owned())),
172 SharedValue::Keyword(kw) => Value::Keyword(GcPtr::new(kw.get().clone())),
173 SharedValue::Symbol(sym) => Value::Symbol(GcPtr::new(sym.get().clone())),
174 SharedValue::ByteBlob(blob) => Value::ByteBlob(blob.clone()),
175 }
176}
177
178#[derive(Debug)]
191pub struct SharedAtom {
192 pub cell: Arc<ArcSwap<SharedValue>>,
193 pub meta: Mutex<Option<SharedValue>>,
194}
195
196impl SharedAtom {
197 pub fn new(val: SharedValue) -> Self {
198 Self {
199 cell: Arc::new(ArcSwap::new(Arc::new(val))),
200 meta: Mutex::new(None),
201 }
202 }
203
204 pub fn deref_val(&self) -> Arc<SharedValue> {
206 self.cell.load_full()
207 }
208
209 pub fn reset(&self, val: SharedValue) -> Arc<SharedValue> {
211 let arc = Arc::new(val);
212 self.cell.store(arc.clone());
213 arc
214 }
215
216 pub fn swap<F>(&self, mut f: F) -> Arc<SharedValue>
219 where
220 F: FnMut(&SharedValue) -> SharedValue,
221 {
222 self.cell.rcu(|old| Arc::new(f(old)))
223 }
224}
225
226#[cfg(test)]
229mod tests {
230 use std::sync::Arc;
231
232 use crate::keyword::Keyword;
233 use crate::value::Value;
234 use cljrs_gc::GcPtr;
235
236 use super::*;
237
238 fn kw(name: &str) -> Value {
239 Value::Keyword(GcPtr::new(Keyword::simple(name)))
240 }
241
242 #[test]
243 fn promote_scalars() {
244 assert!(matches!(promote(&Value::Nil), Ok(SharedValue::Nil)));
245 assert!(matches!(
246 promote(&Value::Bool(true)),
247 Ok(SharedValue::Bool(true))
248 ));
249 assert!(matches!(
250 promote(&Value::Long(42)),
251 Ok(SharedValue::Long(42))
252 ));
253 }
254
255 #[test]
256 fn promote_keyword_interns() {
257 let v = kw("foo");
258 let sv = promote(&v).unwrap();
259 let sv2 = promote(&kw("foo")).unwrap();
260 if let (SharedValue::Keyword(a), SharedValue::Keyword(b)) = (sv, sv2) {
261 assert!(
262 cljrs_gc::StaticGcPtr::ptr_eq(&a, &b),
263 "same keyword should intern to same StaticGcPtr"
264 );
265 } else {
266 panic!("expected SharedValue::Keyword");
267 }
268 }
269
270 #[test]
271 fn demote_roundtrip_long() {
272 let sv = SharedValue::Long(99);
273 assert!(matches!(demote(&sv), Value::Long(99)));
274 }
275
276 #[test]
277 fn demote_roundtrip_keyword() {
278 let sv = promote(&kw("test")).unwrap();
279 let v = demote(&sv);
280 if let Value::Keyword(kw_ptr) = v {
281 assert_eq!(kw_ptr.get().name.as_ref(), "test");
282 } else {
283 panic!("expected Value::Keyword");
284 }
285 }
286
287 #[test]
288 fn promote_non_promotable_returns_err() {
289 let atom = Value::Atom(GcPtr::new(crate::types::Atom::new(Value::Nil)));
290 assert!(promote(&atom).is_err());
291 }
292
293 #[test]
294 fn promote_byte_blob() {
295 let arr: Vec<i8> = vec![1, 2, 3];
296 let v = Value::ByteArray(GcPtr::new(std::sync::Mutex::new(arr)));
297 let sv = promote(&v).unwrap();
298 assert!(matches!(sv, SharedValue::ByteBlob(_)));
299 }
300
301 #[test]
302 fn shared_atom_reset_and_deref() {
303 let atom = SharedAtom::new(SharedValue::Long(0));
304 atom.reset(SharedValue::Long(42));
305 let val = atom.deref_val();
306 assert!(matches!(val.as_ref(), SharedValue::Long(42)));
307 }
308
309 #[test]
310 fn shared_atom_swap() {
311 let atom = SharedAtom::new(SharedValue::Long(1));
312 atom.swap(|old| {
313 if let SharedValue::Long(n) = old {
314 SharedValue::Long(n + 1)
315 } else {
316 SharedValue::Long(0)
317 }
318 });
319 let val = atom.deref_val();
320 assert!(matches!(val.as_ref(), SharedValue::Long(2)));
321 }
322
323 #[test]
324 fn shared_atom_is_send_sync() {
325 fn assert_send_sync<T: Send + Sync>() {}
326 assert_send_sync::<SharedAtom>();
327 assert_send_sync::<Arc<SharedAtom>>();
328 }
329
330 #[test]
331 fn byte_blob_shared_across_clone() {
332 let blob: Arc<[u8]> = vec![10u8, 20, 30].into();
333 let v1 = Value::ByteBlob(blob.clone());
334 let v2 = Value::ByteBlob(blob.clone());
335 if let (Value::ByteBlob(a), Value::ByteBlob(b)) = (&v1, &v2) {
337 assert!(Arc::ptr_eq(a, b));
338 }
339 }
340}