use crate::{TypeSet, TypedValue, for_all_tuples, value::LoadStore};
pub enum FunctionCallError {
FunctionNotFound,
IncorrectArgumentCount {
expected: usize,
},
IncorrectArgumentType {
idx: usize,
expected: &'static str,
},
Other(Box<str>),
}
pub trait DynFunction<A, T>
where
T: TypeSet,
{
fn call(&self, ctx: &mut T, args: &[TypedValue<T>])
-> Result<TypedValue<T>, FunctionCallError>;
}
macro_rules! substitute {
($arg:tt, $replacement:tt) => {
$replacement
};
}
for_all_tuples! {
($($arg:ident),*) => {
impl<$($arg,)* R, F, T> DynFunction<($($arg,)*), T> for F
where
$($arg: LoadStore<T>,)*
F: Fn($($arg,)*) -> R,
F: for<'t> Fn($($arg::Output<'t>,)*) -> R,
R: LoadStore<T>,
T: TypeSet,
{
#[allow(non_snake_case, unused)]
fn call(&self, ctx: &mut T, args: &[TypedValue<T>]) -> Result<TypedValue<T>, FunctionCallError> {
const ARG_COUNT: usize = 0 $( + substitute!($arg, 1) )* ;
if args.len() != ARG_COUNT {
return Err(FunctionCallError::IncorrectArgumentCount { expected: ARG_COUNT });
}
let idx = 0;
$(
let Some($arg) = <$arg>::load(ctx, &args[idx]) else {
return Err(FunctionCallError::IncorrectArgumentType { idx, expected: std::any::type_name::<$arg>() });
};
let idx = idx + 1;
)*
Ok(self($($arg),*).store(ctx))
}
}
};
}
pub(crate) struct ExprFn<'ctx, T: TypeSet> {
#[allow(clippy::type_complexity)]
func: Box<dyn Fn(&mut T, &[TypedValue<T>]) -> Result<TypedValue<T>, FunctionCallError> + 'ctx>,
}
impl<'ctx, T: TypeSet> ExprFn<'ctx, T> {
pub fn new<A, F>(func: F) -> Self
where
F: DynFunction<A, T> + 'ctx,
{
Self {
func: Box::new(move |ctx, args| func.call(ctx, args)),
}
}
pub fn call(
&self,
ctx: &mut T,
args: &[TypedValue<T>],
) -> Result<TypedValue<T>, FunctionCallError> {
(self.func)(ctx, args)
}
}