eclisp 0.2.0

Bindings for Embeddable Common Lisp
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),
        }
    }
}