diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 25c2d37c..5697cdf0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,3 +13,6 @@ jobs: - uses: actions/checkout@v3 - name: Run tests run: cargo test + + - name: Run sync tests + run: cargo test --features sync diff --git a/Cargo.toml b/Cargo.toml index 29e993dc..ccdc20f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,5 @@ name = "tulisp" path = "src/lib.rs" [features] +sync = [] big_functions = [] diff --git a/src/builtin/functions/hash_table.rs b/src/builtin/functions/hash_table.rs index ba966ed4..10d2d4f1 100644 --- a/src/builtin/functions/hash_table.rs +++ b/src/builtin/functions/hash_table.rs @@ -1,5 +1,8 @@ -use crate::{Error, TulispAny, TulispContext, TulispObject, destruct_eval_bind}; -use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use crate::{ + Error, TulispContext, TulispObject, destruct_eval_bind, + object::wrappers::generic::{Shared, SharedMut}, +}; +use std::collections::HashMap; struct TulispObjectEql(TulispObject); @@ -29,7 +32,7 @@ impl From for TulispObjectEql { } pub(crate) struct HashTable { - inner: RefCell>, + inner: SharedMut>, } impl std::fmt::Display for HashTable { @@ -46,8 +49,8 @@ pub(crate) fn add(ctx: &mut TulispContext) { ) .with_trace(args.clone())); } - let table: Rc = Rc::new(HashTable { - inner: RefCell::new(HashMap::new()), + let table = Shared::new_any(HashTable { + inner: SharedMut::new(HashMap::new()), }); Ok(table.into()) }); diff --git a/src/context.rs b/src/context.rs index e83c5856..fc1d463c 100644 --- a/src/context.rs +++ b/src/context.rs @@ -3,7 +3,7 @@ mod add_function; mod rest; pub use rest::Rest; -use std::{collections::HashMap, fs, rc::Rc}; +use std::{collections::HashMap, fs}; use crate::{ TulispObject, TulispValue, builtin, @@ -11,6 +11,7 @@ use crate::{ error::Error, eval::{DummyEval, eval, eval_and_then, eval_basic, funcall}, list, + object::wrappers::generic::{Shared, TulispFn}, parse::parse, }; @@ -86,13 +87,9 @@ impl TulispContext { } #[inline(always)] - pub fn add_special_form( - &mut self, - name: &str, - func: impl Fn(&mut TulispContext, &TulispObject) -> Result + 'static, - ) { + pub fn add_special_form(&mut self, name: &str, func: impl TulispFn + std::any::Any) { self.intern(name) - .set_global(TulispValue::Func(Rc::new(func)).into_ref(None)) + .set_global(TulispValue::Func(Shared::new_tulisp_fn(func)).into_ref(None)) .unwrap(); } @@ -125,13 +122,9 @@ impl TulispContext { } #[inline(always)] - pub fn add_macro( - &mut self, - name: &str, - func: impl Fn(&mut TulispContext, &TulispObject) -> Result + 'static, - ) { + pub fn add_macro(&mut self, name: &str, func: impl TulispFn) { self.intern(name) - .set_global(TulispValue::Macro(Rc::new(func)).into_ref(None)) + .set_global(TulispValue::Macro(Shared::new_tulisp_fn(func)).into_ref(None)) .unwrap(); } diff --git a/src/context/add_function.rs b/src/context/add_function.rs index e87cc183..127f7b9c 100644 --- a/src/context/add_function.rs +++ b/src/context/add_function.rs @@ -1,3 +1,4 @@ +use crate::object::wrappers::generic::SyncSend; use crate::{Error, Rest, TulispContext, TulispObject, destruct_bind, destruct_eval_bind}; pub trait TulispCallable< @@ -25,7 +26,7 @@ macro_rules! impl_tulisp_callable { impl TulispCallable<($($arg,)* $($opt,)*) , OutT, false, $args_count, $opts_count, false, true, false> for FnT where - FnT: Fn($($arg,)* $(Option<$opt>),*) -> OutT + 'static, + FnT: Fn($($arg,)* $(Option<$opt>),*) -> OutT + 'static + SyncSend, $($arg: TryFrom + 'static,)* $($opt: TryFrom + 'static,)* OutT: Into + 'static, @@ -51,7 +52,7 @@ macro_rules! impl_tulisp_callable { impl TulispCallable<($($arg,)* $($opt,)*) , (), false, $args_count, $opts_count, false, false, false> for FnT where - FnT: Fn($($arg,)* $(Option<$opt>),*) + 'static, + FnT: Fn($($arg,)* $(Option<$opt>),*) + 'static + SyncSend, $($arg: TryFrom + 'static,)* $($opt: TryFrom + 'static,)* { @@ -76,7 +77,7 @@ macro_rules! impl_tulisp_callable { impl TulispCallable<($($arg,)* $($opt,)*) , OutT, true, $args_count, $opts_count, false, true,false> for FnT where - FnT: Fn(&mut TulispContext, $($arg,)* $(Option<$opt>),*) -> OutT + 'static, + FnT: Fn(&mut TulispContext, $($arg,)* $(Option<$opt>),*) -> OutT + 'static + SyncSend, $($arg: TryFrom + 'static,)* $($opt: TryFrom + 'static,)* OutT: Into + 'static, @@ -102,7 +103,7 @@ macro_rules! impl_tulisp_callable { impl TulispCallable<($($arg,)* $($opt,)*), (), true, $args_count, $opts_count, false, false,false> for FnT where - FnT: Fn(&mut TulispContext, $($arg,)* $(Option<$opt>),*) + 'static, + FnT: Fn(&mut TulispContext, $($arg,)* $(Option<$opt>),*) + 'static + SyncSend, $($arg: TryFrom + 'static,)* $($opt: TryFrom + 'static,)* { @@ -127,7 +128,7 @@ macro_rules! impl_tulisp_callable { impl TulispCallable<($($arg,)* $($opt,)*) , OutT, false, $args_count, $opts_count, false, true, true> for FnT where - FnT: Fn($($arg,)* $(Option<$opt>),*) -> Result + 'static, + FnT: Fn($($arg,)* $(Option<$opt>),*) -> Result + 'static + SyncSend, $($arg: TryFrom + 'static,)* $($opt: TryFrom + 'static,)* OutT: Into + 'static, @@ -153,7 +154,7 @@ macro_rules! impl_tulisp_callable { impl TulispCallable<($($arg,)* $($opt,)*) , OutT, true, $args_count, $opts_count, false, true, true> for FnT where - FnT: Fn(&mut TulispContext, $($arg,)* $(Option<$opt>),*) -> Result + 'static, + FnT: Fn(&mut TulispContext, $($arg,)* $(Option<$opt>),*) -> Result + 'static + SyncSend, $($arg: TryFrom + 'static,)* $($opt: TryFrom + 'static,)* OutT: Into + 'static, @@ -179,7 +180,7 @@ macro_rules! impl_tulisp_callable { impl TulispCallable<($($arg,)* $($opt,)* RestT,), OutT, false, $args_count, $opts_count, true, true, false> for FnT where - FnT: Fn($($arg,)* $(Option<$opt>,)* Rest) -> OutT + 'static, + FnT: Fn($($arg,)* $(Option<$opt>,)* Rest) -> OutT + 'static + SyncSend, $($arg: TryFrom + 'static,)* $($opt: TryFrom + 'static,)* RestT: TryFrom + Into + 'static, @@ -216,7 +217,7 @@ macro_rules! impl_tulisp_callable { impl TulispCallable<($($arg,)* $($opt,)* RestT,), (), false, $args_count, $opts_count, true, false, false> for FnT where - FnT: Fn($($arg,)* $(Option<$opt>,)* Rest) + 'static, + FnT: Fn($($arg,)* $(Option<$opt>,)* Rest) + 'static + SyncSend, $($arg: TryFrom + 'static,)* $($opt: TryFrom + 'static,)* RestT: TryFrom + Into + 'static, @@ -252,7 +253,7 @@ macro_rules! impl_tulisp_callable { impl TulispCallable<($($arg,)* $($opt,)* RestT,), OutT, true, $args_count, $opts_count, true, true, false> for FnT where - FnT: Fn(&mut TulispContext, $($arg,)* $(Option<$opt>,)* Rest) -> OutT + 'static, + FnT: Fn(&mut TulispContext, $($arg,)* $(Option<$opt>,)* Rest) -> OutT + 'static + SyncSend, $($arg: TryFrom + 'static,)* $($opt: TryFrom + 'static,)* RestT: TryFrom + Into + 'static, @@ -290,7 +291,7 @@ macro_rules! impl_tulisp_callable { impl TulispCallable<($($arg,)* $($opt,)* RestT,), (), true, $args_count, $opts_count, true, false, false> for FnT where - FnT: Fn(&mut TulispContext, $($arg,)* $(Option<$opt>,)* Rest) + 'static, + FnT: Fn(&mut TulispContext, $($arg,)* $(Option<$opt>,)* Rest) + 'static + SyncSend, $($arg: TryFrom + 'static,)* $($opt: TryFrom + 'static,)* RestT: TryFrom + Into + 'static, @@ -327,7 +328,7 @@ macro_rules! impl_tulisp_callable { impl TulispCallable<($($arg,)* $($opt,)* RestT,), OutT, false, $args_count, $opts_count, true, true, true> for FnT where - FnT: Fn($($arg,)* $(Option<$opt>,)* Rest) -> Result + 'static, + FnT: Fn($($arg,)* $(Option<$opt>,)* Rest) -> Result + 'static + SyncSend, $($arg: TryFrom + 'static,)* $($opt: TryFrom + 'static,)* RestT: TryFrom + Into + 'static, @@ -364,7 +365,7 @@ macro_rules! impl_tulisp_callable { impl TulispCallable<($($arg,)* $($opt,)* RestT,), OutT, true, $args_count, $opts_count, true, true, true> for FnT where - FnT: Fn(&mut TulispContext, $($arg,)* $(Option<$opt>,)* Rest) -> Result + 'static, + FnT: Fn(&mut TulispContext, $($arg,)* $(Option<$opt>,)* Rest) -> Result + 'static + SyncSend, $($arg: TryFrom + 'static,)* $($opt: TryFrom + 'static,)* RestT: TryFrom + Into + 'static, diff --git a/src/lib.rs b/src/lib.rs index 003d5083..207a1ac6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,7 +99,7 @@ pub use value::TulispAny; pub use value::TulispValue; mod object; -pub use object::TulispObject; +pub use {object::TulispObject, object::wrappers::generic::Shared}; #[cfg(test)] mod test_utils { diff --git a/src/object.rs b/src/object.rs index 55632037..af1e86b7 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1,14 +1,12 @@ +pub mod wrappers; + use crate::{ TulispValue, cons::{self, Cons}, error::Error, + object::wrappers::generic::{Shared, SharedMut, SharedRef}, value::TulispAny, }; -use std::{ - any::Any, - cell::{Cell, Ref, RefCell}, - rc::Rc, -}; #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub struct Span { @@ -30,8 +28,8 @@ impl Span { /// A type for representing tulisp objects. #[derive(Debug, Clone)] pub struct TulispObject { - rc: Rc>, - span: Rc>>, + rc: SharedMut, + span: SharedMut>, } impl Default for TulispObject { @@ -281,7 +279,7 @@ impl TulispObject { "Returns a string if `self` contains a string, and an Error otherwise." ); extractor_fn_with_err!( - Rc, + Shared, as_any, r#"Returns a boxed value if `self` contains a boxed value, and an Error otherwise. @@ -290,8 +288,7 @@ with `as_any`, and downcast to desired types. ## Example ```rust -# use tulisp::{TulispContext, destruct_bind, Error, TulispAny}; -# use std::rc::Rc; +# use tulisp::{TulispContext, destruct_bind, Error, TulispAny, Shared}; # # fn main() -> Result<(), Error> { let mut ctx = TulispContext::new(); @@ -310,7 +307,7 @@ ctx.add_special_form("make_any", |_ctx, args| { destruct_bind!((inp) = args); let inp: i64 = inp.try_into()?; - let any_obj: Rc = Rc::new(TestStruct { value: inp }); + let any_obj = Shared::new_any(TestStruct { value: inp }); Ok(any_obj.into()) }); @@ -372,8 +369,8 @@ impl TulispObject { pub(crate) fn new(vv: TulispValue, span: Option) -> TulispObject { Self { - rc: Rc::new(RefCell::new(vv)), - span: Rc::new(Cell::new(span)), + rc: SharedMut::new(vv), + span: SharedMut::new(span), } } @@ -396,7 +393,7 @@ impl TulispObject { #[inline(always)] pub(crate) fn eq_ptr(&self, other: &TulispObject) -> bool { - Rc::ptr_eq(&self.rc, &other.rc) + self.rc.ptr_eq(&other.rc) } #[inline(always)] @@ -405,19 +402,19 @@ impl TulispObject { } pub(crate) fn addr_as_usize(&self) -> usize { - self.rc.as_ptr() as usize + self.rc.addr_as_usize() } pub(crate) fn clone_without_span(&self) -> Self { Self { - rc: Rc::clone(&self.rc), - span: Rc::new(Cell::new(None)), + rc: self.rc.clone(), + span: SharedMut::new(None), } } #[inline(always)] pub(crate) fn strong_count(&self) -> usize { - Rc::strong_count(&self.rc) + self.rc.strong_count() } #[inline(always)] @@ -430,7 +427,7 @@ impl TulispObject { } #[inline(always)] - pub(crate) fn inner_ref(&self) -> Ref<'_, TulispValue> { + pub(crate) fn inner_ref(&self) -> SharedRef<'_, TulispValue> { self.rc.borrow() } @@ -448,7 +445,7 @@ impl TulispObject { } pub(crate) fn with_span(&self, in_span: Option) -> Self { - self.span.set(in_span); + *self.span.borrow_mut() = in_span; self.clone() } pub(crate) fn take(&self) -> TulispValue { @@ -458,7 +455,7 @@ impl TulispObject { #[doc(hidden)] #[inline(always)] pub fn span(&self) -> Option { - self.span.get() + self.span.borrow().clone() } #[doc(hidden)] @@ -552,7 +549,7 @@ impl From for bool { } } -impl TryFrom for Rc { +impl TryFrom for Shared { type Error = Error; fn try_from(value: TulispObject) -> Result { @@ -575,7 +572,7 @@ tulisp_object_from!(f64); tulisp_object_from!(&str); tulisp_object_from!(String); tulisp_object_from!(bool); -tulisp_object_from!(Rc); +tulisp_object_from!(Shared); impl FromIterator for TulispObject { fn from_iter>(iter: T) -> Self { diff --git a/src/object/wrappers.rs b/src/object/wrappers.rs new file mode 100644 index 00000000..b9ce14b4 --- /dev/null +++ b/src/object/wrappers.rs @@ -0,0 +1,231 @@ +use crate::{Error, TulispContext, TulispObject}; + +#[cfg(not(feature = "sync"))] +pub mod generic { + use std::ops::Deref; + + use crate::TulispAny; + + use super::*; + + pub trait SyncSend {} + impl SyncSend for T {} + + #[repr(transparent)] + #[derive(Debug)] + pub struct Shared(std::rc::Rc); + + pub type SharedRef<'a, T> = std::cell::Ref<'a, T>; + + impl std::fmt::Display for Shared { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Shared({})", self.0) + } + } + + impl Clone for Shared { + fn clone(&self) -> Self { + Self(self.0.clone()) + } + } + + impl Shared { + pub fn new_tulisp_fn(val: impl TulispFn) -> Shared { + Shared(std::rc::Rc::new(val)) + } + + pub fn new_any(val: impl TulispAny) -> Shared { + Shared(std::rc::Rc::new(val)) + } + + pub fn downcast_ref(&self) -> Option<&U> { + let a: &dyn std::any::Any = &*self.0; + a.downcast_ref::() + } + + pub fn downcast(self) -> Result, Shared> { + match std::rc::Rc::downcast::(self.0.clone()) { + Ok(v) => Ok(Shared(v)), + Err(_) => Err(Shared(self.0)), + } + } + } + + impl Shared { + pub fn new(val: T) -> Self { + Shared(std::rc::Rc::new(val)) + } + + pub fn ptr_eq(&self, other: &Self) -> bool { + std::rc::Rc::ptr_eq(&self.0, &other.0) + } + } + + impl Deref for Shared { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl std::ops::DerefMut for Shared { + fn deref_mut(&mut self) -> &mut Self::Target { + std::rc::Rc::get_mut(&mut self.0).expect("Multiple references exist") + } + } + + #[repr(transparent)] + #[derive(Clone, Debug)] + pub struct SharedMut(std::rc::Rc>); + + impl SharedMut { + pub fn new(val: T) -> Self { + SharedMut(std::rc::Rc::new(std::cell::RefCell::new(val))) + } + pub fn ptr_eq(&self, other: &Self) -> bool { + std::rc::Rc::ptr_eq(&self.0, &other.0) + } + + pub fn borrow(&self) -> std::cell::Ref<'_, T> { + self.0.borrow() + } + + pub fn borrow_mut(&self) -> std::cell::RefMut<'_, T> { + self.0.borrow_mut() + } + + pub fn addr_as_usize(&self) -> usize { + self.0.as_ptr() as usize + } + + pub fn strong_count(&self) -> usize { + std::rc::Rc::strong_count(&self.0) + } + } + + impl Deref for SharedMut { + type Target = T; + + fn deref(&self) -> &Self::Target { + todo!() + } + } + + pub trait TulispFn: + Fn(&mut TulispContext, &TulispObject) -> Result + 'static + { + } + impl TulispFn for T where + T: Fn(&mut TulispContext, &TulispObject) -> Result + 'static + { + } +} + +#[cfg(feature = "sync")] +pub mod generic { + use std::ops::Deref; + + use crate::TulispAny; + + use super::*; + + pub trait SyncSend: Sync + Send {} + impl SyncSend for T where T: Send + Sync {} + + #[repr(transparent)] + #[derive(Debug)] + pub struct Shared(std::sync::Arc); + + pub type SharedRef<'a, T> = std::sync::RwLockReadGuard<'a, T>; + + impl std::fmt::Display for Shared { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Shared({})", self.0) + } + } + + impl Clone for Shared { + fn clone(&self) -> Self { + Self(self.0.clone()) + } + } + + impl Shared { + pub fn new_tulisp_fn(val: impl TulispFn) -> Shared { + Shared(std::sync::Arc::new(val)) + } + + pub fn new_any(val: impl TulispAny) -> Shared { + Shared(std::sync::Arc::new(val)) + } + + pub fn downcast_ref(&self) -> Option<&U> { + let a: &dyn std::any::Any = &*self.0; + a.downcast_ref::() + } + + pub fn downcast(self) -> Result, Shared> { + match std::sync::Arc::downcast::(self.0.clone()) { + Ok(v) => Ok(Shared(v)), + Err(_) => Err(Shared(self.0)), + } + } + } + + impl Shared { + pub fn new(val: T) -> Self { + Shared(std::sync::Arc::new(val)) + } + + pub fn ptr_eq(&self, other: &Self) -> bool { + std::sync::Arc::ptr_eq(&self.0, &other.0) + } + } + + impl Deref for Shared { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + #[repr(transparent)] + #[derive(Clone, Debug)] + pub struct SharedMut(std::sync::Arc>); + impl SharedMut { + pub fn new(val: T) -> Self { + SharedMut(std::sync::Arc::new(std::sync::RwLock::new(val))) + } + pub fn ptr_eq(&self, other: &Self) -> bool { + std::sync::Arc::ptr_eq(&self.0, &other.0) + } + + pub fn borrow(&self) -> std::sync::RwLockReadGuard<'_, T> { + self.0.read().unwrap() + } + + pub fn borrow_mut(&self) -> std::sync::RwLockWriteGuard<'_, T> { + self.0.write().unwrap() + } + + pub fn addr_as_usize(&self) -> usize { + std::sync::Arc::as_ptr(&self.0) as usize + } + + pub fn strong_count(&self) -> usize { + std::sync::Arc::strong_count(&self.0) + } + } + pub trait TulispFn: + Fn(&mut TulispContext, &TulispObject) -> Result + SyncSend + 'static + { + } + impl TulispFn for T where + T: Fn(&mut TulispContext, &TulispObject) -> Result + + SyncSend + + 'static + { + } +} diff --git a/src/value.rs b/src/value.rs index c36d6f80..ae63d83f 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,15 +1,17 @@ use crate::{ - TulispContext, TulispObject, + TulispObject, cons::{self, Cons}, context::Scope, error::Error, - object::Span, + object::{ + Span, + wrappers::generic::{Shared, SyncSend, TulispFn}, + }, }; use std::{ any::Any, convert::TryInto, fmt::{Display, Write}, - rc::Rc, }; #[doc(hidden)] @@ -98,8 +100,6 @@ impl DefunParams { } } -type TulispFn = dyn Fn(&mut TulispContext, &TulispObject) -> Result; - #[derive(Default, Clone, Debug)] pub struct SymbolBindings { name: String, @@ -199,8 +199,15 @@ impl SymbolBindings { } } -pub trait TulispAny: Any + Display {} -impl TulispAny for T {} +pub trait TulispAny: Any + Display + SyncSend {} + +impl std::fmt::Debug for dyn TulispAny { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "TulispAny({})", self) + } +} + +impl TulispAny for T {} #[doc(hidden)] #[derive(Clone)] @@ -243,9 +250,9 @@ pub enum TulispValue { Splice { value: TulispObject, }, - Any(Rc), - Func(Rc), - Macro(Rc), + Any(Shared), + Func(Shared), + Macro(Shared), Defmacro { params: DefunParams, body: TulispObject, @@ -722,11 +729,11 @@ impl TulispValue { } #[inline(always)] - pub(crate) fn as_any(&self) -> Result, Error> { + pub(crate) fn as_any(&self) -> Result, Error> { match self { TulispValue::Any(value) => Ok(value.clone()), _ => Err(Error::type_mismatch(format!( - "Expected Any(Rc), got: {}", + "Expected Any(Shared), got: {}", self ))), } @@ -821,8 +828,8 @@ impl From for TulispValue { } } -impl From> for TulispValue { - fn from(value: Rc) -> Self { +impl From> for TulispValue { + fn from(value: Shared) -> Self { TulispValue::Any(value) } } diff --git a/tests/tests.rs b/tests/tests.rs index 8a8c29b8..45242fa2 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,5 +1,5 @@ -use std::{fmt::Display, rc::Rc}; -use tulisp::{Error, Iter, TulispAny, TulispContext, TulispObject, destruct_eval_bind}; +use std::fmt::Display; +use tulisp::{Error, Iter, Shared, TulispAny, TulispContext, TulispObject, destruct_eval_bind}; macro_rules! tulisp_assert { (@impl $ctx: expr, program:$input:expr, result:$result:expr $(,)?) => { @@ -1165,7 +1165,7 @@ fn test_any() -> Result<(), Error> { ctx.add_special_form("make_any", |ctx, args| { destruct_eval_bind!(ctx, (inp) = args); - let res: Rc = Rc::new(TestStruct { + let res: Shared = Shared::new_any(TestStruct { value: inp.try_into()?, }); @@ -1190,7 +1190,7 @@ fn test_any() -> Result<(), Error> { tulisp_assert! { ctx: ctx, program: "(get_int 55)", - error: r#"ERR TypeMismatch: Expected Any(Rc), got: 55 + error: r#"ERR TypeMismatch: Expected Any(Shared), got: 55 :1.10-1.11: at 55 :1.1-1.12: at (get_int 55) "#