use std::sync::{Arc, Mutex};
use arc_swap::ArcSwap;
use cljrs_gc::{GcPtr, StaticGcPtr};
use crate::intern::{intern_keyword, intern_symbol};
use crate::keyword::Keyword;
use crate::symbol::Symbol;
use crate::value::Value;
#[derive(Debug, Clone)]
pub struct PromoteError {
pub type_name: &'static str,
}
impl PromoteError {
pub fn not_promotable(type_name: &'static str) -> Self {
Self { type_name }
}
}
impl std::fmt::Display for PromoteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"value of type '{}' cannot be promoted to a shared-atom: \
only scalars, strings, keywords, symbols, and byte-blobs are supported",
self.type_name
)
}
}
impl std::error::Error for PromoteError {}
#[derive(Debug, Clone)]
pub enum SharedValue {
Nil,
Bool(bool),
Long(i64),
Double(f64),
Char(char),
Uuid(u128),
Str(Arc<str>),
Keyword(StaticGcPtr<Keyword>),
Symbol(StaticGcPtr<Symbol>),
ByteBlob(Arc<[u8]>),
}
unsafe impl Send for SharedValue {}
unsafe impl Sync for SharedValue {}
impl SharedValue {
pub fn type_name(&self) -> &'static str {
match self {
SharedValue::Nil => "nil",
SharedValue::Bool(_) => "boolean",
SharedValue::Long(_) => "long",
SharedValue::Double(_) => "double",
SharedValue::Char(_) => "char",
SharedValue::Uuid(_) => "uuid",
SharedValue::Str(_) => "string",
SharedValue::Keyword(_) => "keyword",
SharedValue::Symbol(_) => "symbol",
SharedValue::ByteBlob(_) => "byte-blob",
}
}
}
pub fn promote(value: &Value) -> Result<SharedValue, PromoteError> {
match value {
Value::Nil => Ok(SharedValue::Nil),
Value::Bool(b) => Ok(SharedValue::Bool(*b)),
Value::Long(n) => Ok(SharedValue::Long(*n)),
Value::Double(d) => Ok(SharedValue::Double(*d)),
Value::Char(c) => Ok(SharedValue::Char(*c)),
Value::Uuid(u) => Ok(SharedValue::Uuid(*u)),
Value::Str(s) => Ok(SharedValue::Str(Arc::from(s.get().as_str()))),
Value::Keyword(kw) => {
let kw = kw.get();
let ptr = intern_keyword(kw.namespace.as_deref(), &kw.name);
Ok(SharedValue::Keyword(ptr))
}
Value::Symbol(sym) => {
let sym = sym.get();
let ptr = intern_symbol(sym.namespace.as_deref(), &sym.name, sym.version.as_deref());
Ok(SharedValue::Symbol(ptr))
}
Value::ByteArray(arr) => {
let bytes = arr.get().lock().unwrap();
let blob: Arc<[u8]> = bytes.iter().map(|&b| b as u8).collect::<Vec<_>>().into();
Ok(SharedValue::ByteBlob(blob))
}
Value::ByteBlob(blob) => Ok(SharedValue::ByteBlob(blob.clone())),
other => Err(PromoteError::not_promotable(other.type_name())),
}
}
pub fn demote(sv: &SharedValue) -> Value {
match sv {
SharedValue::Nil => Value::Nil,
SharedValue::Bool(b) => Value::Bool(*b),
SharedValue::Long(n) => Value::Long(*n),
SharedValue::Double(d) => Value::Double(*d),
SharedValue::Char(c) => Value::Char(*c),
SharedValue::Uuid(u) => Value::Uuid(*u),
SharedValue::Str(s) => Value::Str(GcPtr::new(s.as_ref().to_owned())),
SharedValue::Keyword(kw) => Value::Keyword(GcPtr::new(kw.get().clone())),
SharedValue::Symbol(sym) => Value::Symbol(GcPtr::new(sym.get().clone())),
SharedValue::ByteBlob(blob) => Value::ByteBlob(blob.clone()),
}
}
#[derive(Debug)]
pub struct SharedAtom {
pub cell: Arc<ArcSwap<SharedValue>>,
pub meta: Mutex<Option<SharedValue>>,
}
impl SharedAtom {
pub fn new(val: SharedValue) -> Self {
Self {
cell: Arc::new(ArcSwap::new(Arc::new(val))),
meta: Mutex::new(None),
}
}
pub fn deref_val(&self) -> Arc<SharedValue> {
self.cell.load_full()
}
pub fn reset(&self, val: SharedValue) -> Arc<SharedValue> {
let arc = Arc::new(val);
self.cell.store(arc.clone());
arc
}
pub fn swap<F>(&self, mut f: F) -> Arc<SharedValue>
where
F: FnMut(&SharedValue) -> SharedValue,
{
self.cell.rcu(|old| Arc::new(f(old)))
}
pub fn compare_and_set(&self, current: &Arc<SharedValue>, new: SharedValue) -> bool {
let prev = self.cell.compare_and_swap(current, Arc::new(new));
std::ptr::eq(Arc::as_ptr(current), Arc::as_ptr(&prev))
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::keyword::Keyword;
use crate::value::Value;
use cljrs_gc::GcPtr;
use super::*;
fn kw(name: &str) -> Value {
Value::Keyword(GcPtr::new(Keyword::simple(name)))
}
#[test]
fn promote_scalars() {
assert!(matches!(promote(&Value::Nil), Ok(SharedValue::Nil)));
assert!(matches!(
promote(&Value::Bool(true)),
Ok(SharedValue::Bool(true))
));
assert!(matches!(
promote(&Value::Long(42)),
Ok(SharedValue::Long(42))
));
}
#[test]
fn promote_keyword_interns() {
let v = kw("foo");
let sv = promote(&v).unwrap();
let sv2 = promote(&kw("foo")).unwrap();
if let (SharedValue::Keyword(a), SharedValue::Keyword(b)) = (sv, sv2) {
assert!(
cljrs_gc::StaticGcPtr::ptr_eq(&a, &b),
"same keyword should intern to same StaticGcPtr"
);
} else {
panic!("expected SharedValue::Keyword");
}
}
#[test]
fn demote_roundtrip_long() {
let sv = SharedValue::Long(99);
assert!(matches!(demote(&sv), Value::Long(99)));
}
#[test]
fn demote_roundtrip_keyword() {
let sv = promote(&kw("test")).unwrap();
let v = demote(&sv);
if let Value::Keyword(kw_ptr) = v {
assert_eq!(kw_ptr.get().name.as_ref(), "test");
} else {
panic!("expected Value::Keyword");
}
}
#[test]
fn promote_non_promotable_returns_err() {
let atom = Value::Atom(GcPtr::new(crate::types::Atom::new(Value::Nil)));
assert!(promote(&atom).is_err());
}
#[test]
fn promote_byte_blob() {
let arr: Vec<i8> = vec![1, 2, 3];
let v = Value::ByteArray(GcPtr::new(std::sync::Mutex::new(arr)));
let sv = promote(&v).unwrap();
assert!(matches!(sv, SharedValue::ByteBlob(_)));
}
#[test]
fn shared_atom_reset_and_deref() {
let atom = SharedAtom::new(SharedValue::Long(0));
atom.reset(SharedValue::Long(42));
let val = atom.deref_val();
assert!(matches!(val.as_ref(), SharedValue::Long(42)));
}
#[test]
fn shared_atom_swap() {
let atom = SharedAtom::new(SharedValue::Long(1));
atom.swap(|old| {
if let SharedValue::Long(n) = old {
SharedValue::Long(n + 1)
} else {
SharedValue::Long(0)
}
});
let val = atom.deref_val();
assert!(matches!(val.as_ref(), SharedValue::Long(2)));
}
#[test]
fn shared_atom_compare_and_set() {
let atom = SharedAtom::new(SharedValue::Long(1));
let cur = atom.deref_val();
assert!(atom.compare_and_set(&cur, SharedValue::Long(2)));
assert!(matches!(atom.deref_val().as_ref(), SharedValue::Long(2)));
assert!(!atom.compare_and_set(&cur, SharedValue::Long(99)));
assert!(matches!(atom.deref_val().as_ref(), SharedValue::Long(2)));
}
#[test]
fn shared_atom_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<SharedAtom>();
assert_send_sync::<Arc<SharedAtom>>();
}
#[test]
fn byte_blob_shared_across_clone() {
let blob: Arc<[u8]> = vec![10u8, 20, 30].into();
let v1 = Value::ByteBlob(blob.clone());
let v2 = Value::ByteBlob(blob.clone());
if let (Value::ByteBlob(a), Value::ByteBlob(b)) = (&v1, &v2) {
assert!(Arc::ptr_eq(a, b));
}
}
}