eclisp 0.2.0

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

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)
    }
}

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);

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);

impl<T: LispClass> IntoLisp for &mut T {
    fn into_lisp<'rt>(self, ctx: &LispContext<'rt>) -> Result<Value<'rt>, LispError> {
        clos::scoped_child(ctx, self, T::NAME)
    }
}