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;
#[derive(Trace)]
pub struct Nil;
#[derive(Trace)]
pub struct NativeProcedure {
#[trace(skip)]
name: &'static str,
#[trace(skip)]
func: Box<dyn Fn(&Env, &[Expr]) -> Result<Value, Error>>,
}
#[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 {
pub fn new(
name: &'static str,
func: impl Fn(&Env, &[Expr]) -> Result<Value, Error> + 'static,
) -> Self {
Self {
name,
func: Box::new(func),
}
}
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 {
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;
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),*)))
})
}
}
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);