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}