rhai/api/
call_fn.rs

1//! Module that defines the `call_fn` API of [`Engine`].
2#![cfg(not(feature = "no_function"))]
3
4use crate::eval::{Caches, GlobalRuntimeState};
5use crate::types::dynamic::Variant;
6use crate::{
7    Dynamic, Engine, FnArgsVec, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR,
8};
9#[cfg(feature = "no_std")]
10use std::prelude::v1::*;
11use std::{any::type_name, mem};
12
13/// Options for calling a script-defined function via [`Engine::call_fn_with_options`].
14#[derive(Debug, Hash)]
15#[non_exhaustive]
16pub struct CallFnOptions<'t> {
17    /// A value for binding to the `this` pointer (if any). Default [`None`].
18    pub this_ptr: Option<&'t mut Dynamic>,
19    /// The custom state of this evaluation run (if any), overrides [`Engine::default_tag`]. Default [`None`].
20    pub tag: Option<Dynamic>,
21    /// Evaluate the [`AST`] to load necessary modules before calling the function? Default `true`.
22    pub eval_ast: bool,
23    /// Rewind the [`Scope`] after the function call? Default `true`.
24    pub rewind_scope: bool,
25    /// Call only scripted functions from the [`AST`] instead of functions in all namespaces.
26    pub in_all_namespaces: bool,
27}
28
29impl Default for CallFnOptions<'_> {
30    #[inline(always)]
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl<'a> CallFnOptions<'a> {
37    /// Create a default [`CallFnOptions`].
38    #[inline(always)]
39    #[must_use]
40    pub fn new() -> Self {
41        Self {
42            this_ptr: None,
43            tag: None,
44            eval_ast: true,
45            rewind_scope: true,
46            in_all_namespaces: false,
47        }
48    }
49    /// Bind to the `this` pointer.
50    #[inline(always)]
51    #[must_use]
52    pub fn bind_this_ptr(mut self, value: &'a mut Dynamic) -> Self {
53        self.this_ptr = Some(value);
54        self
55    }
56    /// Set the custom state of this evaluation run (if any).
57    #[inline(always)]
58    #[must_use]
59    pub fn with_tag(mut self, value: impl Variant + Clone) -> Self {
60        self.tag = Some(Dynamic::from(value));
61        self
62    }
63    /// Set whether to evaluate the [`AST`] to load necessary modules before calling the function.
64    #[inline(always)]
65    #[must_use]
66    pub const fn eval_ast(mut self, value: bool) -> Self {
67        self.eval_ast = value;
68        self
69    }
70    /// Set whether to rewind the [`Scope`] after the function call.
71    #[inline(always)]
72    #[must_use]
73    pub const fn rewind_scope(mut self, value: bool) -> Self {
74        self.rewind_scope = value;
75        self
76    }
77    /// Call functions in all namespaces instead of only scripted functions within the [`AST`].
78    #[inline(always)]
79    #[must_use]
80    pub fn in_all_namespaces(mut self, value: bool) -> Self {
81        self.in_all_namespaces = value;
82        self
83    }
84}
85
86impl Engine {
87    /// Call a script function defined in an [`AST`] with multiple arguments.
88    ///
89    /// Not available under `no_function`.
90    ///
91    /// The [`AST`] is evaluated before calling the function.
92    /// This allows a script to load the necessary modules.
93    /// This is usually desired. If not, use [`call_fn_with_options`][Engine::call_fn_with_options] instead.
94    ///
95    /// # Example
96    ///
97    /// ```
98    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
99    /// use rhai::{Engine, Scope};
100    ///
101    /// let engine = Engine::new();
102    ///
103    /// let ast = engine.compile("
104    ///     fn add(x, y) { len(x) + y + foo }
105    ///     fn add1(x)   { len(x) + 1 + foo }
106    ///     fn bar()     { foo/2 }
107    /// ")?;
108    ///
109    /// let mut scope = Scope::new();
110    /// scope.push("foo", 42_i64);
111    ///
112    /// // Call the script-defined function
113    /// let result = engine.call_fn::<i64>(&mut scope, &ast, "add", ( "abc", 123_i64 ) )?;
114    /// assert_eq!(result, 168);
115    ///
116    /// let result = engine.call_fn::<i64>(&mut scope, &ast, "add1", ( "abc", ) )?;
117    /// //                                                           ^^^^^^^^^^ tuple of one
118    /// assert_eq!(result, 46);
119    ///
120    /// let result = engine.call_fn::<i64>(&mut scope, &ast, "bar", () )?;
121    /// assert_eq!(result, 21);
122    /// # Ok(())
123    /// # }
124    /// ```
125    #[inline(always)]
126    pub fn call_fn<T: Variant + Clone>(
127        &self,
128        scope: &mut Scope,
129        ast: &AST,
130        fn_name: impl AsRef<str>,
131        args: impl FuncArgs,
132    ) -> RhaiResultOf<T> {
133        self.call_fn_with_options(<_>::default(), scope, ast, fn_name, args)
134    }
135    /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments.
136    /// Options are provided via the [`CallFnOptions`] type.
137    ///
138    /// If [`in_all_namespaces`](CallFnOptions::in_all_namespaces) is specified, the function will
139    /// be searched in all namespaces instead, so registered native Rust functions etc. are also found.
140    ///
141    /// This is an advanced API.
142    ///
143    /// Not available under `no_function`.
144    ///
145    /// # Example
146    ///
147    /// ```
148    /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
149    /// use rhai::{Engine, Scope, Dynamic, CallFnOptions};
150    ///
151    /// let engine = Engine::new();
152    ///
153    /// let ast = engine.compile("
154    ///     fn action(x) { this += x; }         // function using 'this' pointer
155    ///     fn decl(x)   { let hello = x; }     // declaring variables
156    /// ")?;
157    ///
158    /// let mut scope = Scope::new();
159    /// scope.push("foo", 42_i64);
160    ///
161    /// // Binding the 'this' pointer
162    /// let mut value = 1_i64.into();
163    /// let options = CallFnOptions::new().bind_this_ptr(&mut value);
164    ///
165    /// engine.call_fn_with_options(options, &mut scope, &ast, "action", ( 41_i64, ))?;
166    /// assert_eq!(value.as_int().unwrap(), 42);
167    ///
168    /// // Do not rewind scope
169    /// let options = CallFnOptions::default().rewind_scope(false);
170    ///
171    /// engine.call_fn_with_options(options, &mut scope, &ast, "decl", ( 42_i64, ))?;
172    /// assert_eq!(scope.get_value::<i64>("hello").unwrap(), 42);
173    /// # Ok(())
174    /// # }
175    /// ```
176    #[inline(always)]
177    pub fn call_fn_with_options<T: Variant + Clone>(
178        &self,
179        options: CallFnOptions,
180        scope: &mut Scope,
181        ast: &AST,
182        fn_name: impl AsRef<str>,
183        args: impl FuncArgs,
184    ) -> RhaiResultOf<T> {
185        let mut arg_values = FnArgsVec::new_const();
186        args.parse(&mut arg_values);
187
188        self._call_fn(
189            options,
190            scope,
191            ast,
192            fn_name.as_ref(),
193            arg_values.as_mut(),
194            &mut self.new_global_runtime_state(),
195            &mut Caches::new(),
196        )
197        .and_then(|result| {
198            result.try_cast_result().map_err(|r| {
199                let result_type = self.map_type_name(r.type_name());
200                let cast_type = match type_name::<T>() {
201                    typ if typ.contains("::") => self.map_type_name(typ),
202                    typ => typ,
203                };
204                ERR::ErrorMismatchOutputType(cast_type.into(), result_type.into(), Position::NONE)
205                    .into()
206            })
207        })
208    }
209    /// Make a function call with multiple [`Dynamic`] arguments.
210    ///
211    /// # Arguments
212    ///
213    /// All the arguments are _consumed_, meaning that they're replaced by `()`. This is to avoid
214    /// unnecessarily cloning the arguments.
215    ///
216    /// Do not use the arguments after this call. If they are needed afterwards, clone them _before_
217    /// calling this function.
218    #[inline(always)]
219    pub(crate) fn _call_fn(
220        &self,
221        options: CallFnOptions,
222        scope: &mut Scope,
223        ast: &AST,
224        name: &str,
225        arg_values: &mut [Dynamic],
226        global: &mut GlobalRuntimeState,
227        caches: &mut Caches,
228    ) -> RhaiResult {
229        let statements = ast.statements();
230
231        let orig_source = mem::replace(&mut global.source, ast.source_raw().cloned());
232
233        let orig_lib_len = global.lib.len();
234        global.lib.push(ast.shared_lib().clone());
235
236        let orig_tag = options.tag.map(|v| mem::replace(&mut global.tag, v));
237
238        let mut this_ptr = options.this_ptr;
239
240        #[cfg(not(feature = "no_module"))]
241        let orig_embedded_module_resolver =
242            std::mem::replace(&mut global.embedded_module_resolver, ast.resolver.clone());
243
244        let rewind_scope = options.rewind_scope;
245        let in_all_namespaces = options.in_all_namespaces;
246
247        defer! { global => move |g| {
248            #[cfg(not(feature = "no_module"))]
249            {
250                g.embedded_module_resolver = orig_embedded_module_resolver;
251            }
252            if let Some(orig_tag) = orig_tag { g.tag = orig_tag; }
253            g.lib.truncate(orig_lib_len);
254            g.source = orig_source;
255        }}
256
257        let global_result = if options.eval_ast && !statements.is_empty() {
258            defer! {
259                scope if rewind_scope => rewind;
260                let orig_scope_len = scope.len();
261            }
262
263            self.eval_global_statements(global, caches, scope, statements, true)
264        } else {
265            Ok(Dynamic::UNIT)
266        };
267
268        let result = global_result.and_then(|_| {
269            let args = &mut arg_values.iter_mut().collect::<FnArgsVec<_>>();
270
271            // Check for data race.
272            #[cfg(not(feature = "no_closure"))]
273            crate::func::ensure_no_data_race(name, args, false)?;
274
275            if let Some(fn_def) = ast.shared_lib().get_script_fn(name, args.len()) {
276                self.call_script_fn(
277                    global,
278                    caches,
279                    scope,
280                    this_ptr.as_deref_mut(),
281                    None,
282                    fn_def,
283                    args,
284                    rewind_scope,
285                    Position::NONE,
286                )
287                .or_else(|err| match *err {
288                    ERR::Exit(out, ..) => Ok(out),
289                    _ => Err(err),
290                })
291            } else if !in_all_namespaces {
292                Err(ERR::ErrorFunctionNotFound(name.into(), Position::NONE).into())
293            } else {
294                let has_this = if let Some(this_ptr) = this_ptr.as_deref_mut() {
295                    args.insert(0, this_ptr);
296                    true
297                } else {
298                    false
299                };
300
301                self.eval_fn_call_with_arguments::<Dynamic>(name, args, has_this, has_this)
302            }
303        });
304
305        #[cfg(feature = "debugging")]
306        if self.is_debugger_registered() {
307            global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate;
308            let node = &crate::ast::Stmt::Noop(Position::NONE);
309            self.dbg(global, caches, scope, this_ptr, node)?;
310        }
311
312        result.map_err(|err| match *err {
313            ERR::ErrorInFunctionCall(fn_name, _, inner_err, _) if fn_name == name => inner_err,
314            _ => err,
315        })
316    }
317}