somni_expr/
function.rs

1//! Support for native Rust functions.
2
3use crate::{for_all_tuples, value::LoadStore, TypeSet, TypedValue};
4
5/// An error that occurs when calling a function.
6pub enum FunctionCallError {
7    /// The function was not found in the context.
8    FunctionNotFound,
9
10    /// The number of arguments passed to the function does not match the expected count.
11    IncorrectArgumentCount {
12        /// The expected number of arguments.
13        expected: usize,
14    },
15
16    /// The type of an argument does not match the expected type.
17    IncorrectArgumentType {
18        /// The 0-based index of the argument that has the incorrect type.
19        idx: usize,
20        /// The expected type of the argument.
21        expected: &'static str,
22    },
23
24    /// An error occurred while calling the function.
25    Other(Box<str>),
26}
27
28/// Functions and closures that implement this trait can be registered as functions to be called by expressions.
29pub trait DynFunction<A, T>
30where
31    T: TypeSet,
32{
33    /// Call the function with the specified arguments.
34    ///
35    /// # Errors
36    ///
37    /// This functions returns an error if:
38    /// - The number of arguments is incorrect
39    /// - The types of arguments are incorrect
40    fn call(&self, ctx: &mut T, args: &[TypedValue<T>])
41        -> Result<TypedValue<T>, FunctionCallError>;
42}
43
44macro_rules! substitute {
45    ($arg:tt, $replacement:tt) => {
46        $replacement
47    };
48}
49
50for_all_tuples! {
51    ($($arg:ident),*) => {
52        impl<$($arg,)* R, F, T> DynFunction<($($arg,)*), T> for F
53        where
54            $($arg: LoadStore<T>,)*
55            // This double bound on F ensures that we can work with reference types (&str), too:
56            // The first one ensures type-inference matches the Load implementation we want
57            // The second one ensures we don't run into "... is not generic enough" errors.
58            F: Fn($($arg,)*) -> R,
59            F: for<'t> Fn($($arg::Output<'t>,)*) -> R,
60            R: LoadStore<T>,
61            T: TypeSet,
62        {
63
64            #[allow(non_snake_case, unused)]
65            fn call(&self, ctx: &mut T, args: &[TypedValue<T>]) -> Result<TypedValue<T>, FunctionCallError> {
66                const ARG_COUNT: usize = 0 $( + substitute!($arg, 1) )* ;
67
68                if args.len() != ARG_COUNT {
69                    return Err(FunctionCallError::IncorrectArgumentCount { expected: ARG_COUNT });
70                }
71
72                let idx = 0;
73                $(
74                    let Some($arg) = <$arg>::load(ctx, &args[idx]) else {
75                        return Err(FunctionCallError::IncorrectArgumentType { idx, expected: std::any::type_name::<$arg>() });
76                    };
77                    let idx = idx + 1;
78                )*
79
80
81                Ok(self($($arg),*).store(ctx))
82            }
83        }
84    };
85}
86
87pub(crate) struct ExprFn<'ctx, T: TypeSet> {
88    #[allow(clippy::type_complexity)]
89    func: Box<dyn Fn(&mut T, &[TypedValue<T>]) -> Result<TypedValue<T>, FunctionCallError> + 'ctx>,
90}
91
92impl<'ctx, T: TypeSet> ExprFn<'ctx, T> {
93    pub fn new<A, F>(func: F) -> Self
94    where
95        F: DynFunction<A, T> + 'ctx,
96    {
97        Self {
98            func: Box::new(move |ctx, args| func.call(ctx, args)),
99        }
100    }
101
102    pub fn call(
103        &self,
104        ctx: &mut T,
105        args: &[TypedValue<T>],
106    ) -> Result<TypedValue<T>, FunctionCallError> {
107        (self.func)(ctx, args)
108    }
109}