use crate::{FromLisp, IntoLisp, LispContext, LispError, ToLisp, Value, ValueRef, sys};
pub type PropertyGetter<T> =
Box<dyn for<'rt> Fn(&T, &LispContext<'rt>) -> Result<Value<'rt>, LispError>>;
pub type PropertySetter<T> = Box<dyn Fn(&mut T, ValueRef<'_>) -> Result<(), LispError>>;
pub type NativePropertyGetter = unsafe extern "C" fn(sys::cl_object) -> sys::cl_object;
pub type NativePropertySetter =
unsafe extern "C" fn(sys::cl_object, sys::cl_object) -> sys::cl_object;
pub struct Property<T> {
name: String,
lisp_type: Option<String>,
getter: Option<PropertyGetter<T>>,
setter: Option<PropertySetter<T>>,
native_getter: Option<NativePropertyGetter>,
native_setter: Option<NativePropertySetter>,
}
impl<T> Property<T> {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
lisp_type: None,
getter: None,
setter: None,
native_getter: None,
native_setter: None,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn lisp_type(mut self, ty: impl Into<String>) -> Self {
self.lisp_type = Some(ty.into());
self
}
pub fn type_name(&self) -> Option<&str> {
self.lisp_type.as_deref()
}
pub fn readable(&self) -> bool {
self.getter.is_some() || self.native_getter.is_some()
}
pub fn writable(&self) -> bool {
self.setter.is_some() || self.native_setter.is_some()
}
pub fn native_get(mut self, getter: NativePropertyGetter) -> Self {
self.native_getter = Some(getter);
self
}
pub fn native_set(mut self, setter: NativePropertySetter) -> Self {
self.native_setter = Some(setter);
self
}
pub fn native_getter(&self) -> Option<NativePropertyGetter> {
self.native_getter
}
pub fn native_setter(&self) -> Option<NativePropertySetter> {
self.native_setter
}
pub fn get<V, F>(mut self, getter: F) -> Self
where
F: for<'a> Fn(&'a T) -> &'a V + 'static,
V: ToLisp + 'static,
{
self.getter = Some(Box::new(move |obj, ctx| getter(obj).to_lisp(ctx)));
self
}
pub fn get_owned<V, F>(mut self, getter: F) -> Self
where
F: Fn(&T) -> V + 'static,
V: IntoLisp,
{
self.getter = Some(Box::new(move |obj, ctx| getter(obj).into_lisp(ctx)));
self
}
pub fn set<V, F>(mut self, setter: F) -> Self
where
V: FromLisp,
F: Fn(&mut T, V) + 'static,
{
self.setter = Some(Box::new(move |obj, value| {
setter(obj, V::from_lisp(value)?);
Ok(())
}));
self
}
pub fn read<'rt>(
&self,
obj: &T,
ctx: &LispContext<'rt>,
) -> Result<Option<Value<'rt>>, LispError> {
self.getter
.as_ref()
.map(|getter| getter(obj, ctx))
.transpose()
}
pub fn write(&self, obj: &mut T, value: ValueRef<'_>) -> Result<bool, LispError> {
match &self.setter {
Some(setter) => {
setter(obj, value)?;
Ok(true)
}
None => Ok(false),
}
}
}