piccolo 0.3.3

Stackless Lua VM implemented in pure Rust
Documentation
use std::{error::Error as StdError, fmt, string::String as StdString, sync::Arc};

use gc_arena::{Collect, Rootable};
use thiserror::Error;

use crate::{Callback, CallbackReturn, Context, MetaMethod, Singleton, Table, UserData, Value};

#[derive(Debug, Clone, Copy, Error)]
#[error("type error, expected {expected}, found {found}")]
pub struct TypeError {
    pub expected: &'static str,
    pub found: &'static str,
}

#[derive(Debug, Copy, Clone, Collect)]
#[collect(no_drop)]
pub struct LuaError<'gc>(pub Value<'gc>);

impl<'gc> fmt::Display for LuaError<'gc> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl<'gc> From<Value<'gc>> for LuaError<'gc> {
    fn from(error: Value<'gc>) -> Self {
        LuaError(error)
    }
}

impl<'gc> LuaError<'gc> {
    pub fn to_static(self) -> StaticLuaError {
        self.into()
    }
}

#[derive(Debug, Clone, Error)]
#[error("{0}")]
pub struct StaticLuaError(StdString);

impl<'gc> From<LuaError<'gc>> for StaticLuaError {
    fn from(error: LuaError<'gc>) -> Self {
        Self(error.to_string())
    }
}

#[derive(Debug, Clone, Collect)]
#[collect(require_static)]
pub struct RuntimeError(pub Arc<anyhow::Error>);

impl fmt::Display for RuntimeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl<E: Into<anyhow::Error>> From<E> for RuntimeError {
    fn from(err: E) -> Self {
        Self(Arc::new(err.into()))
    }
}

impl RuntimeError {
    pub fn root_cause(&self) -> &(dyn StdError + 'static) {
        self.0.root_cause()
    }

    pub fn is<E>(&self) -> bool
    where
        E: fmt::Display + fmt::Debug + Send + Sync + 'static,
    {
        self.0.is::<E>()
    }

    pub fn downcast<E>(&self) -> Option<&E>
    where
        E: fmt::Display + fmt::Debug + Send + Sync + 'static,
    {
        self.0.downcast_ref::<E>()
    }
}

impl AsRef<dyn StdError + 'static> for RuntimeError {
    fn as_ref(&self) -> &(dyn StdError + 'static) {
        (*self.0).as_ref()
    }
}

#[derive(Debug, Clone, Collect)]
#[collect(no_drop)]
pub enum Error<'gc> {
    Lua(LuaError<'gc>),
    Runtime(RuntimeError),
}

impl<'gc> fmt::Display for Error<'gc> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::Lua(err) => write!(f, "lua error: {}", err),
            Error::Runtime(err) => write!(f, "runtime error: {}", err),
        }
    }
}

impl<'gc> From<Value<'gc>> for Error<'gc> {
    fn from(value: Value<'gc>) -> Self {
        Self::from_value(value)
    }
}

impl<'gc> From<LuaError<'gc>> for Error<'gc> {
    fn from(error: LuaError<'gc>) -> Self {
        Self::Lua(error)
    }
}

impl<'gc> From<RuntimeError> for Error<'gc> {
    fn from(error: RuntimeError) -> Self {
        Self::Runtime(error)
    }
}

impl<'gc, E> From<E> for Error<'gc>
where
    E: Into<anyhow::Error>,
{
    fn from(error: E) -> Self {
        Self::Runtime(RuntimeError::from(error))
    }
}

impl<'gc> Error<'gc> {
    pub fn from_value(value: Value<'gc>) -> Self {
        if let Value::UserData(ud) = value {
            if let Ok(err) = ud.downcast_static::<RuntimeError>() {
                return Error::Runtime(err.clone());
            }
        }

        Error::Lua(value.into())
    }

    pub fn to_value(&self, ctx: Context<'gc>) -> Value<'gc> {
        match self {
            Error::Lua(err) => err.0,
            Error::Runtime(err) => {
                #[derive(Copy, Clone, Collect)]
                #[collect(no_drop)]
                struct UDMeta<'gc>(Table<'gc>);

                impl<'gc> Singleton<'gc> for UDMeta<'gc> {
                    fn create(ctx: Context<'gc>) -> Self {
                        let table = Table::new(&ctx);
                        table
                            .set(
                                ctx,
                                MetaMethod::ToString,
                                Callback::from_fn(&ctx, |ctx, _, mut stack| {
                                    let ud = stack.consume::<UserData>(ctx)?;
                                    let error = ud.downcast_static::<RuntimeError>()?;
                                    stack.replace(ctx, error.to_string());
                                    Ok(CallbackReturn::Return)
                                }),
                            )
                            .unwrap();
                        Self(table)
                    }
                }

                let ud = UserData::new_static(&ctx, err.clone());
                ud.set_metatable(&ctx, Some(ctx.singleton::<Rootable![UDMeta<'_>]>().0));
                ud.into()
            }
        }
    }

    pub fn to_static(&self) -> StaticError {
        self.clone().into_static()
    }

    pub fn into_static(self) -> StaticError {
        self.into()
    }
}

#[derive(Debug, Clone)]
pub enum StaticError {
    Lua(StaticLuaError),
    Runtime(RuntimeError),
}

impl fmt::Display for StaticError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            StaticError::Lua(err) => write!(f, "lua error: {err}"),
            StaticError::Runtime(err) => write!(f, "runtime error: {err}"),
        }
    }
}

impl StdError for StaticError {
    fn source(&self) -> Option<&(dyn StdError + 'static)> {
        match self {
            StaticError::Lua(err) => Some(err),
            StaticError::Runtime(err) => Some(err.as_ref()),
        }
    }
}

impl From<StaticLuaError> for StaticError {
    fn from(error: StaticLuaError) -> Self {
        Self::Lua(error)
    }
}

impl From<RuntimeError> for StaticError {
    fn from(error: RuntimeError) -> Self {
        Self::Runtime(error)
    }
}

impl<'gc> From<Error<'gc>> for StaticError {
    fn from(err: Error<'gc>) -> Self {
        match err {
            Error::Lua(err) => err.to_static().into(),
            Error::Runtime(e) => e.into(),
        }
    }
}