litto 0.1.0

Building blocks for DSL scripting language interpreters that interact with native Rust code.
Documentation
//! Primitive types used by `tinylang`.

use super::TinyLang;
use crate::value::AbstractValue;
use crate::Interpreter;
use gcmodule::Trace;
use std::borrow::Cow;
use std::fmt;

type Value = <TinyLang as Interpreter>::Value;
type Error = <TinyLang as Interpreter>::Error;
type Expr = <TinyLang as Interpreter>::Expr;
type Env = <TinyLang as Interpreter>::Env;

/// The `nil` type. The return value of `(begin)`.
#[derive(Trace)]
pub struct Nil;

/// A native function.
#[derive(Trace)]
pub struct NativeProcedure {
    #[trace(skip)]
    name: &'static str,
    #[trace(skip)]
    func: Box<dyn Fn(&Env, &[Expr]) -> Result<Value, Error>>,
}

/// A pure lambda.
#[derive(Trace)]
pub struct Procedure {
    env: Env,
    #[trace(skip)]
    arg_names: Vec<Cow<'static, str>>,
    #[trace(skip)]
    body: Expr,
}

impl fmt::Debug for Nil {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "nil")
    }
}

impl fmt::Display for Nil {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "nil")
    }
}

impl AbstractValue<TinyLang> for Nil {}

impl NativeProcedure {
    /// Create a `NativeProcedure`.
    pub fn new(
        name: &'static str,
        func: impl Fn(&Env, &[Expr]) -> Result<Value, Error> + 'static,
    ) -> Self {
        Self {
            name,
            func: Box::new(func),
        }
    }

    /// Register the function to an environment.
    pub fn register(self, env: &Env) {
        env.set_local(self.name, Value::new(self));
    }
}

impl fmt::Debug for NativeProcedure {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.name)
    }
}

impl fmt::Display for NativeProcedure {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.name)
    }
}

impl AbstractValue<TinyLang> for NativeProcedure {
    fn apply(&self, env: &Env, args: &[Expr]) -> Result<Value, Error> {
        (self.func)(env, args)
    }
}

impl fmt::Debug for Procedure {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.body)
    }
}

impl fmt::Display for Procedure {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self.body)
    }
}

impl Procedure {
    /// Creates a `Procedure` in a given environment.
    /// `args` are plain symbol names.
    pub fn new(env: Env, args: &[Expr], body: Expr) -> Result<Self, Error> {
        let arg_names: Vec<_> = args
            .iter()
            .map(|exp| match exp {
                Expr::Symbol(name) => Ok(name.clone()),
                _ => return Err(format!("invalid procedure arg name: {:?}", exp)),
            })
            .collect::<Result<Vec<_>, _>>()?;
        Ok(Self {
            env,
            arg_names,
            body,
        })
    }
}

impl AbstractValue<TinyLang> for Procedure {
    fn apply(&self, arg_env: &Env, args: &[Expr]) -> Result<Value, Error> {
        if args.len() != self.arg_names.len() {
            return Err(format!(
                "expect {} arguments, got {}",
                self.arg_names.len(),
                args.len()
            ));
        }
        let args = args
            .iter()
            .map(|exp| TinyLang::evaluate(&arg_env, &exp))
            .collect::<Result<Vec<_>, _>>()?;
        let env = self.env.nested();
        for (name, arg) in self.arg_names.iter().zip(args.into_iter()) {
            env.set_local(name, arg);
        }
        TinyLang::evaluate(&env, &self.body)
    }
}

#[allow(unused)]
macro_rules! impl_func {
    ( $( $arg:ident : $ty:ident ),* ) => {
        const _: () = {
            use $crate::value::AbstractValue;
            use $crate::lang::tinylang::TinyLang;
            use $crate::lang::tinylang::types::NativeProcedure;
            type Error = <TinyLang as Interpreter>::Error;

            // ( $($arg),* ) -> R
            impl<R, $($ty),*> From<(&'static str, fn($(&$ty),*) -> R)> for NativeProcedure
            where
                R: AbstractValue<TinyLang>,
                $($ty: AbstractValue<TinyLang>),*
            {
                #[allow(unused_assignments)]
                fn from(value: (&'static str, fn($(&$ty),*) -> R)) -> Self {
                    let (name, func): (&'static str, _) = value;
                    NativeProcedure::new(name, move |env, args| -> Result<Value, Error> {
                        let expected_len = 0 $( + { let _: Option<$ty> = None; 1 } )*;
                        if args.len() != expected_len {
                            return Err(format!(
                                "{} requires {} arguments, {} provided",
                                name,
                                expected_len,
                                args.len()
                            ));
                        }
                        let mut i = 0;
                        $(
                        let arg = TinyLang::evaluate(env, &args[i])?;
                        let $arg: &$ty = arg
                            .downcast_ref()
                            .ok_or_else(|| format!("{}: invalid argument type", name))?;
                        i += 1;
                        )*
                        Ok(Value::new(func($($arg),*)))
                    })
                }
            }

            // ( $($arg),* ) -> Result
            impl<R, $($ty),*> From<(&'static str, fn($(&$ty),*) -> Result<R, Error>)> for NativeProcedure
            where
                R: AbstractValue<TinyLang>,
                $($ty: AbstractValue<TinyLang>),*
            {
                #[allow(unused_assignments)]
                fn from(value: (&'static str, fn($(&$ty),*) -> Result<R, Error>)) -> Self {
                    let (name, func): (&'static str, _) = value;
                    NativeProcedure::new(name, move |env, args| -> Result<Value, Error> {
                        let expected_len = 0 $( + { let _: Option<$ty> = None; 1 } )*;
                        if args.len() != expected_len {
                            return Err(format!(
                                "{} requires {} arguments, {} provided",
                                name,
                                expected_len,
                                args.len()
                            ));
                        }
                        let mut i = 0;
                        $(
                        let arg = TinyLang::evaluate(env, &args[i])?;
                        let $arg: &$ty = arg
                            .downcast_ref()
                            .ok_or_else(|| format!("{}: invalid argument type", name))?;
                        i += 1;
                        )*
                        Ok(Value::new(func($($arg),*)?))
                    })
                }
            }
        };
    }
}

impl_func!(a: A);
impl_func!(a: A, b: B);
impl_func!(a: A, b: B, c: C);
impl_func!(a: A, b: B, c: C, d: D);
impl_func!(a: A, b: B, c: C, d: D, e: E);