//! Module that defines the `call_fn` API of [`Engine`]. #![cfg(not(feature = "no_function"))] use crate::eval::{Caches, GlobalRuntimeState}; use crate::types::dynamic::Variant; use crate::types::RestoreOnDrop; use crate::{ reify, Dynamic, Engine, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, AST, ERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::{type_name, TypeId}, mem, }; /// Options for calling a script-defined function via [`Engine::call_fn_with_options`]. #[derive(Debug, Hash)] #[non_exhaustive] pub struct CallFnOptions<'t> { /// A value for binding to the `this` pointer (if any). pub this_ptr: Option<&'t mut Dynamic>, /// The custom state of this evaluation run (if any), overrides [`Engine::default_tag`]. pub tag: Option, /// Evaluate the [`AST`] to load necessary modules before calling the function? Default `true`. pub eval_ast: bool, /// Rewind the [`Scope`] after the function call? Default `true`. pub rewind_scope: bool, } impl Default for CallFnOptions<'_> { #[inline(always)] fn default() -> Self { Self::new() } } impl<'a> CallFnOptions<'a> { /// Create a default [`CallFnOptions`]. #[inline(always)] pub fn new() -> Self { Self { this_ptr: None, tag: None, eval_ast: true, rewind_scope: true, } } /// Bind to the `this` pointer. #[inline(always)] pub fn bind_this_ptr(mut self, value: &'a mut Dynamic) -> Self { self.this_ptr = Some(value); self } /// Set the custom state of this evaluation run (if any). #[inline(always)] pub fn with_tag(mut self, value: impl Variant + Clone) -> Self { self.tag = Some(Dynamic::from(value)); self } /// Set whether to evaluate the [`AST`] to load necessary modules before calling the function. #[inline(always)] pub const fn eval_ast(mut self, value: bool) -> Self { self.eval_ast = value; self } /// Set whether to rewind the [`Scope`] after the function call. #[inline(always)] pub const fn rewind_scope(mut self, value: bool) -> Self { self.rewind_scope = value; self } } impl Engine { /// Call a script function defined in an [`AST`] with multiple arguments. /// /// Not available under `no_function`. /// /// The [`AST`] is evaluated before calling the function. /// This allows a script to load the necessary modules. /// This is usually desired. If not, use [`call_fn_with_options`] instead. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Scope}; /// /// let engine = Engine::new(); /// /// let ast = engine.compile(" /// fn add(x, y) { len(x) + y + foo } /// fn add1(x) { len(x) + 1 + foo } /// fn bar() { foo/2 } /// ")?; /// /// let mut scope = Scope::new(); /// scope.push("foo", 42_i64); /// /// // Call the script-defined function /// let result = engine.call_fn::(&mut scope, &ast, "add", ( "abc", 123_i64 ) )?; /// assert_eq!(result, 168); /// /// let result = engine.call_fn::(&mut scope, &ast, "add1", ( "abc", ) )?; /// // ^^^^^^^^^^ tuple of one /// assert_eq!(result, 46); /// /// let result = engine.call_fn::(&mut scope, &ast, "bar", () )?; /// assert_eq!(result, 21); /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn call_fn( &self, scope: &mut Scope, ast: &AST, name: impl AsRef, args: impl FuncArgs, ) -> RhaiResultOf { self.call_fn_with_options(Default::default(), scope, ast, name, args) } /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. /// /// Options are provided via the [`CallFnOptions`] type. /// This is an advanced API. /// /// Not available under `no_function`. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { /// use rhai::{Engine, Scope, Dynamic, CallFnOptions}; /// /// let engine = Engine::new(); /// /// let ast = engine.compile(" /// fn action(x) { this += x; } // function using 'this' pointer /// fn decl(x) { let hello = x; } // declaring variables /// ")?; /// /// let mut scope = Scope::new(); /// scope.push("foo", 42_i64); /// /// // Binding the 'this' pointer /// let mut value = 1_i64.into(); /// let options = CallFnOptions::new().bind_this_ptr(&mut value); /// /// engine.call_fn_with_options(options, &mut scope, &ast, "action", ( 41_i64, ))?; /// assert_eq!(value.as_int().unwrap(), 42); /// /// // Do not rewind scope /// let options = CallFnOptions::default().rewind_scope(false); /// /// engine.call_fn_with_options(options, &mut scope, &ast, "decl", ( 42_i64, ))?; /// assert_eq!(scope.get_value::("hello").unwrap(), 42); /// # Ok(()) /// # } /// ``` #[inline(always)] pub fn call_fn_with_options( &self, options: CallFnOptions, scope: &mut Scope, ast: &AST, name: impl AsRef, args: impl FuncArgs, ) -> RhaiResultOf { let mut arg_values = StaticVec::new_const(); args.parse(&mut arg_values); let result = self._call_fn( options, scope, &mut GlobalRuntimeState::new(self), &mut Caches::new(), ast, name.as_ref(), arg_values.as_mut(), )?; // Bail out early if the return type needs no cast if TypeId::of::() == TypeId::of::() { return Ok(reify!(result => T)); } // Cast return type let typ = self.map_type_name(result.type_name()); result.try_cast().ok_or_else(|| { let t = self.map_type_name(type_name::()).into(); ERR::ErrorMismatchOutputType(t, typ.into(), Position::NONE).into() }) } /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. /// /// # Arguments /// /// All the arguments are _consumed_, meaning that they're replaced by `()`. This is to avoid /// unnecessarily cloning the arguments. /// /// Do not use the arguments after this call. If they are needed afterwards, clone them _before_ /// calling this function. #[inline(always)] pub(crate) fn _call_fn( &self, options: CallFnOptions, scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, ast: &AST, name: &str, arg_values: &mut [Dynamic], ) -> RhaiResult { let statements = ast.statements(); let orig_lib_len = global.lib.len(); let mut orig_tag = None; if let Some(value) = options.tag { orig_tag = Some(mem::replace(&mut global.tag, value)); } global.lib.push(ast.shared_lib().clone()); let mut no_this_ptr = Dynamic::NULL; let this_ptr = options.this_ptr.unwrap_or(&mut no_this_ptr); #[cfg(not(feature = "no_module"))] let orig_embedded_module_resolver = std::mem::replace( &mut global.embedded_module_resolver, ast.resolver().cloned(), ); let rewind_scope = options.rewind_scope; let result = if options.eval_ast && !statements.is_empty() { let orig_scope_len = scope.len(); let scope = &mut *RestoreOnDrop::lock_if(rewind_scope, scope, move |s| { s.rewind(orig_scope_len); }); self.eval_global_statements(global, caches, scope, statements) } else { Ok(Dynamic::UNIT) } .and_then(|_| { let args = &mut arg_values.iter_mut().collect::>(); // Check for data race. #[cfg(not(feature = "no_closure"))] crate::func::ensure_no_data_race(name, args, false).map(|_| Dynamic::UNIT)?; if let Some(fn_def) = ast.shared_lib().get_script_fn(name, args.len()) { self.call_script_fn( global, caches, scope, this_ptr, fn_def, args, rewind_scope, Position::NONE, ) } else { Err(ERR::ErrorFunctionNotFound(name.into(), Position::NONE).into()) } }); #[cfg(feature = "debugging")] if self.debugger.is_some() { global.debugger.status = crate::eval::DebuggerStatus::Terminate; let node = &crate::ast::Stmt::Noop(Position::NONE); self.run_debugger(global, caches, scope, this_ptr, node)?; } #[cfg(not(feature = "no_module"))] { global.embedded_module_resolver = orig_embedded_module_resolver; } if let Some(value) = orig_tag { global.tag = value; } global.lib.truncate(orig_lib_len); result } }