eclisp 0.2.0-beta.0

Bindings for Embeddable Common Lisp
use crate::{LispContext, LispError, Value, ValueRef, sys};

pub trait IntoLisp {
    fn into_lisp<'rt>(self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError>;
}

pub trait ToLisp {
    fn to_lisp<'rt>(&self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError>;
}

pub trait FromLisp: Sized {
    fn from_lisp(value: ValueRef<'_>) -> Result<Self, LispError>;
}

impl<T: ToLisp + ?Sized> IntoLisp for &T {
    fn into_lisp<'rt>(self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        self.to_lisp(ctx)
    }
}

impl<T: ToLisp + ?Sized> ToLisp for &T {
    fn to_lisp<'rt>(&self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        (*self).to_lisp(ctx)
    }
}

pub trait RawLisp: Sized {
    unsafe fn from_raw(value: sys::cl_object) -> Option<Self>;

    unsafe fn to_raw(&self) -> sys::cl_object;
}

macro_rules! impl_primitive {
    ($($ty:ty => $method:ident $(as $cast:ty)? ; $reader:ident),* $(,)?) => {
        $(
            impl IntoLisp for $ty {
                fn into_lisp<'rt>(
                    self,
                    ctx: &LispContext<'rt>,
                ) -> Result<Value<'rt>, LispError> {
                    ctx.$method(self $(as $cast)?)
                }
            }

            impl ToLisp for $ty {
                fn to_lisp<'rt>(
                    &self,
                    ctx: &LispContext<'rt>,
                ) -> Result<Value<'rt>, LispError> {
                    (*self).into_lisp(ctx)
                }
            }

            impl FromLisp for $ty {
                fn from_lisp(value: ValueRef<'_>) -> Result<Self, LispError> {
                    let value = value.$reader()?;

                    Self::try_from(value).map_err(|_| {
                        LispError::RuntimeError(format!(
                            "Lisp value is out of range for {}",
                            stringify!($ty)
                        ))
                    })
                }
            }
        )*
    };
}

impl_primitive! {
    i8  => int as i64; try_as_i64,
    i16 => int as i64; try_as_i64,
    i32 => int as i64; try_as_i64,
    i64 => int;        try_as_i64,
    u8  => int as i64; try_as_i64,
    u16 => int as i64; try_as_i64,
    u32 => int as i64; try_as_i64,
}

macro_rules! impl_float {
    ($($ty:ty),* $(,)?) => {
        $(
            impl IntoLisp for $ty {
                fn into_lisp<'rt>(
                    self,
                    ctx: &LispContext<'rt>,
                ) -> Result<Value<'rt>, LispError> {
                    ctx.float(self as f64)
                }
            }

            impl ToLisp for $ty {
                fn to_lisp<'rt>(
                    &self,
                    ctx: &LispContext<'rt>,
                ) -> Result<Value<'rt>, LispError> {
                    (*self).into_lisp(ctx)
                }
            }

            impl FromLisp for $ty {
                fn from_lisp(value: ValueRef<'_>) -> Result<Self, LispError> {
                    Ok(value.try_as_f64()? as Self)
                }
            }
        )*
    };
}

impl_float!(f32, f64);

macro_rules! impl_raw_int {
    ($($ty:ty),* $(,)?) => {
        $(
            impl RawLisp for $ty {
                unsafe fn from_raw(
                    value: sys::cl_object,
                ) -> Option<Self> {
                    if unsafe { sys::eclisp_is_int(value) } == 0 {
                        return None;
                    }

                    let mut result = 0i64;

                    if unsafe {
                        sys::eclisp_as_int(value, &mut result)
                    } == 0 {
                        return None;
                    }

                    <$ty>::try_from(result).ok()
                }

                unsafe fn to_raw(&self) -> sys::cl_object {
                    unsafe { sys::eclisp_int_raw(*self as i64) }
                }
            }
        )*
    };
}

impl_raw_int!(i8, i16, i32, i64, u8, u16, u32);

impl RawLisp for f32 {
    unsafe fn from_raw(value: sys::cl_object) -> Option<Self> {
        if unsafe { sys::eclisp_can_be_float(value) } == 0 {
            return None;
        }

        let mut result = 0.0f64;

        if unsafe { sys::eclisp_as_float(value, &mut result) } == 0 {
            return None;
        }

        Some(result as f32)
    }

    unsafe fn to_raw(&self) -> sys::cl_object {
        unsafe { sys::ecl_make_single_float(*self) }
    }
}

impl RawLisp for bool {
    unsafe fn from_raw(value: sys::cl_object) -> Option<Self> {
        if unsafe { sys::eclisp_is_nil(value) } != 0 {
            Some(false)
        } else if unsafe { sys::eclisp_is_t(value) } != 0 {
            Some(true)
        } else {
            None
        }
    }

    unsafe fn to_raw(&self) -> sys::cl_object {
        unsafe {
            if *self {
                sys::eclisp_t_raw()
            } else {
                sys::eclisp_nil_raw()
            }
        }
    }
}

impl IntoLisp for bool {
    fn into_lisp<'rt>(self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        ctx.bool(self)
    }
}

impl ToLisp for bool {
    fn to_lisp<'rt>(&self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        (*self).into_lisp(ctx)
    }
}

impl FromLisp for bool {
    fn from_lisp(value: ValueRef<'_>) -> Result<Self, LispError> {
        if value.is_nil() {
            Ok(false)
        } else if value.is_t() {
            Ok(true)
        } else {
            Err(LispError::RuntimeError(
                "Expected Lisp boolean (T or NIL)".into(),
            ))
        }
    }
}

impl IntoLisp for String {
    fn into_lisp<'rt>(self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        ctx.string(self)
    }
}

impl ToLisp for String {
    fn to_lisp<'rt>(&self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        ctx.string(self)
    }
}

impl FromLisp for String {
    fn from_lisp(value: ValueRef<'_>) -> Result<Self, LispError> {
        Ok(value.try_as_string()?)
    }
}

impl ToLisp for str {
    fn to_lisp<'rt>(&self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        ctx.string(self)
    }
}

impl IntoLisp for () {
    fn into_lisp<'rt>(self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        ctx.nil()
    }
}

impl ToLisp for () {
    fn to_lisp<'rt>(&self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        ctx.nil()
    }
}

impl<T: IntoLisp> IntoLisp for Option<T> {
    fn into_lisp<'rt>(self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        match self {
            Some(value) => value.into_lisp(ctx),
            None => ctx.nil(),
        }
    }
}

impl<T: ToLisp> ToLisp for Option<T> {
    fn to_lisp<'rt>(&self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        match self {
            Some(value) => value.to_lisp(ctx),
            None => ctx.nil(),
        }
    }
}

fn into_list<'rt>(
    ctx: &LispContext<'rt>,
    values: Vec<Value<'rt>>,
) -> Result<Value<'rt>, LispError> {
    let refs: Vec<_> = values.iter().collect();
    ctx.list(&refs)
}

impl<T: IntoLisp> IntoLisp for Vec<T> {
    fn into_lisp<'rt>(self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        let values = self
            .into_iter()
            .map(|value| value.into_lisp(ctx))
            .collect::<Result<Vec<_>, _>>()?;

        into_list(ctx, values)
    }
}

impl<T: ToLisp> ToLisp for [T] {
    fn to_lisp<'rt>(&self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        let values = self
            .iter()
            .map(|value| value.to_lisp(ctx))
            .collect::<Result<Vec<_>, _>>()?;

        into_list(ctx, values)
    }
}

impl<T: ToLisp> ToLisp for Vec<T> {
    fn to_lisp<'rt>(&self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        self.as_slice().to_lisp(ctx)
    }
}

macro_rules! impl_tuple {
    ($($ty:ident : $var:ident),+ $(,)?) => {
        impl<$($ty: IntoLisp),+> IntoLisp for ($($ty,)+) {
            fn into_lisp<'rt>(
                self,
                ctx: &LispContext<'rt>,
            ) -> Result<Value<'rt>, LispError> {
                let ($($var,)+) = self;

                into_list(ctx, vec![
                    $($var.into_lisp(ctx)?,)+
                ])
            }
        }

        impl<$($ty: ToLisp),+> ToLisp for ($($ty,)+) {
            fn to_lisp<'rt>(
                &self,
                ctx: &LispContext<'rt>,
            ) -> Result<Value<'rt>, LispError> {
                let ($($var,)+) = self;

                into_list(ctx, vec![
                    $($var.to_lisp(ctx)?,)+
                ])
            }
        }
    };
}

impl_tuple!(A: a);
impl_tuple!(A: a, B: b);
impl_tuple!(A: a, B: b, C: c);
impl_tuple!(A: a, B: b, C: c, D: d);
impl_tuple!(A: a, B: b, C: c, D: d, E: e);
impl_tuple!(A: a, B: b, C: c, D: d, E: e, F: f);
impl_tuple!(A: a, B: b, C: c, D: d, E: e, F: f, G: g);
impl_tuple!(A: a, B: b, C: c, D: d, E: e, F: f, G: g, H: h);