somni_expr/
function.rs

1use crate::{
2    for_all_tuples,
3    value::ValueType,
4    value::{Load, Store},
5    ExprContext, Type, TypeSet, TypedValue,
6};
7
8/// An error that occurs when calling a function.
9pub enum FunctionCallError {
10    /// The function was not found in the context.
11    FunctionNotFound,
12
13    /// The number of arguments passed to the function does not match the expected count.
14    IncorrectArgumentCount {
15        /// The expected number of arguments.
16        expected: usize,
17    },
18
19    /// The type of an argument does not match the expected type.
20    IncorrectArgumentType {
21        /// The 0-based index of the argument that has the incorrect type.
22        idx: usize,
23        /// The expected type of the argument.
24        expected: Type,
25    },
26
27    /// An error occurred while calling the function.
28    Other(&'static str),
29}
30
31#[doc(hidden)]
32pub trait DynFunction<A, T>
33where
34    T: TypeSet,
35    T::Integer: ValueType,
36    T::Float: ValueType,
37{
38    fn call(
39        &self,
40        ctx: &mut dyn ExprContext<T>,
41        args: &[TypedValue<T>],
42    ) -> Result<TypedValue<T>, FunctionCallError>;
43}
44
45macro_rules! ignore {
46    ($arg:tt) => {};
47}
48
49for_all_tuples! {
50    ($($arg:ident),*) => {
51        impl<$($arg,)* R, F, T> DynFunction<($($arg,)*), T> for F
52        where
53            $($arg: ValueType + Load<T>,)*
54            // This double bound on F ensures that we can work with reference types (&str), too:
55            // The first one ensures type-inference matches the Load implementation we want
56            // The second one ensures we don't run into "... is not generic enough" errors.
57            F: Fn($($arg,)*) -> R,
58            F: for<'t> Fn($($arg::Output<'t>,)*) -> R,
59            R: ValueType + Store<T>,
60            T: TypeSet,
61            T::Integer: ValueType,
62            T::Float: ValueType,
63        {
64            #[allow(non_snake_case, unused)]
65            fn call(&self, ctx: &mut dyn ExprContext<T>, args: &[TypedValue<T>]) -> Result<TypedValue<T>, FunctionCallError> {
66                let arg_count = 0;
67                $(
68                    ignore!($arg);
69                    let arg_count = arg_count + 1;
70                )*
71
72                let idx = 0;
73                let mut args = args.iter().cloned();
74                $(
75                    let Some(arg) = args.next() else {
76                        return Err(FunctionCallError::IncorrectArgumentCount { expected: arg_count });
77                    };
78                    let $arg = match <$arg>::load(ctx, arg) {
79                        Some(arg) => arg,
80                        None => return Err(FunctionCallError::IncorrectArgumentType { idx, expected: $arg::TYPE }),
81                    };
82                    let idx = idx + 1;
83                )*
84
85                if args.next().is_some() {
86                    return Err(FunctionCallError::IncorrectArgumentCount { expected: arg_count });
87                }
88
89                Ok(self($($arg),*).store(ctx))
90            }
91        }
92    };
93}
94
95pub(crate) struct ExprFn<'ctx, T>
96where
97    T: TypeSet,
98    T::Integer: ValueType,
99    T::Float: ValueType,
100{
101    #[allow(clippy::type_complexity)]
102    func: Box<
103        dyn Fn(
104                &mut dyn ExprContext<T>,
105                &[TypedValue<T>],
106            ) -> Result<TypedValue<T>, FunctionCallError>
107            + 'ctx,
108    >,
109}
110
111impl<'ctx, T> ExprFn<'ctx, T>
112where
113    T: TypeSet,
114    T::Integer: ValueType,
115    T::Float: ValueType,
116{
117    pub fn new<A, F>(func: F) -> Self
118    where
119        F: DynFunction<A, T> + 'ctx,
120    {
121        Self {
122            func: Box::new(move |ctx, args| func.call(ctx, args)),
123        }
124    }
125
126    pub fn call(
127        &self,
128        ctx: &mut dyn ExprContext<T>,
129        args: &[TypedValue<T>],
130    ) -> Result<TypedValue<T>, FunctionCallError> {
131        (self.func)(ctx, args)
132    }
133}