diff --git a/RELEASES.md b/RELEASES.md index 7b9d8230..72bbd0be 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -14,6 +14,7 @@ Breaking changes ---------------- * `EvalAltResult::ErrorMismatchOutputType` has an extra argument containing the name of the requested type. +* `Engine::call_fn_dynamic` take an extra argument, allowing a `Dynamic` value to be bound to the `this` pointer. New features ------------ @@ -22,6 +23,7 @@ New features This is particularly useful when converting a Rust `struct` to a `Dynamic` _object map_ and back. * `Engine::disable_symbol` to surgically disable keywords and/or operators. * `Engine::register_custom_operator` to define a custom operator. +* New low-level API `Engine::register_raw_fn`. Version 0.16.1 diff --git a/doc/src/engine/call-fn.md b/doc/src/engine/call-fn.md index d881d7bf..fd9255d0 100644 --- a/doc/src/engine/call-fn.md +++ b/doc/src/engine/call-fn.md @@ -55,10 +55,35 @@ let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?; let result: () = engine.call_fn(&mut scope, &ast, "hidden", ())?; ``` + +`Engine::call_fn_dynamic` +------------------------ + For more control, construct all arguments as `Dynamic` values and use `Engine::call_fn_dynamic`, passing it anything that implements `IntoIterator` (such as a simple `Vec`): ```rust -let result: Dynamic = engine.call_fn_dynamic(&mut scope, &ast, "hello", - vec![ String::from("abc").into(), 123_i64.into() ])?; +let result: Dynamic = engine.call_fn_dynamic( + &mut scope, // scope to use + &ast, // AST to use + "hello", // function entry-point + None, // 'this' pointer, if any + [ String::from("abc").into(), 123_i64.into() ])?; // arguments +``` + + +Binding the `this` Pointer +------------------------- + +`Engine::call_fn_dynamic` can also bind a value to the `this` pointer of a script-defined function. + +```rust +let ast = engine.compile("fn action(x) { this += x; }")?; + +let mut value: Dynamic = 1_i64.into(); + +let result = engine.call_fn_dynamic(&mut scope, &ast, "action", Some(&mut value), [ 41_i64.into() ])?; +// ^^^^^^^^^^^^^^^^ binding the 'this' pointer + +assert_eq!(value.as_int().unwrap(), 42); ``` diff --git a/src/api.rs b/src/api.rs index 6f49ad73..74b78b84 100644 --- a/src/api.rs +++ b/src/api.rs @@ -32,6 +32,38 @@ use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; /// Engine public API impl Engine { + /// Register a function with the `Engine`. + /// + /// ## WARNING - Low Level API + /// + /// This function is very low level. It takes a list of `TypeId`'s indicating the actual types of the parameters. + /// + /// Arguments are simply passed in as a mutable array of `&mut Dynamic`. + /// The arguments are guaranteed to be of the correct types matching the `TypeId`'s. + /// + /// To get access to a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// + /// To get access to a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// Notice that this will _consume_ the argument, replacing it with `()`. + /// + /// To get access to the first mutable parameter, use `args.get_mut(0).unwrap()` + #[deprecated(note = "this function is volatile and may change")] + pub fn register_raw_fn( + &mut self, + name: &str, + args: &[TypeId], + + #[cfg(not(feature = "sync"))] func: impl Fn(&Engine, &mut [&mut Dynamic]) -> Result> + + 'static, + + #[cfg(feature = "sync")] func: impl Fn(&Engine, &mut [&mut Dynamic]) -> Result> + + Send + + Sync + + 'static, + ) { + self.global_module.set_fn_var_args(name, args, func); + } + /// Register a custom type for use with the `Engine`. /// The type must implement `Clone`. /// @@ -1127,7 +1159,7 @@ impl Engine { args: A, ) -> Result> { let mut arg_values = args.into_vec(); - let result = self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut())?; + let result = self.call_fn_dynamic_raw(scope, ast, name, &mut None, arg_values.as_mut())?; let typ = self.map_type_name(result.type_name()); @@ -1140,7 +1172,15 @@ impl Engine { }); } - /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments. + /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments + /// and optionally a value for binding to the 'this' pointer. + /// + /// ## WARNING + /// + /// 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. /// /// # Example /// @@ -1148,7 +1188,7 @@ impl Engine { /// # fn main() -> Result<(), Box> { /// # #[cfg(not(feature = "no_function"))] /// # { - /// use rhai::{Engine, Scope}; + /// use rhai::{Engine, Scope, Dynamic}; /// /// let engine = Engine::new(); /// @@ -1156,20 +1196,27 @@ impl Engine { /// fn add(x, y) { len(x) + y + foo } /// fn add1(x) { len(x) + 1 + foo } /// fn bar() { foo/2 } + /// fn action(x) { this += x; } // function using 'this' pointer /// ")?; /// /// let mut scope = Scope::new(); /// scope.push("foo", 42_i64); /// /// // Call the script-defined function - /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", vec![ String::from("abc").into(), 123_i64.into() ])?; + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", None, [ String::from("abc").into(), 123_i64.into() ])?; + /// // ^^^^ no 'this' pointer /// assert_eq!(result.cast::(), 168); /// - /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", vec![ String::from("abc").into() ])?; + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", None, [ String::from("abc").into() ])?; /// assert_eq!(result.cast::(), 46); /// - /// let result= engine.call_fn_dynamic(&mut scope, &ast, "bar", vec![])?; + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "bar", None, [])?; /// assert_eq!(result.cast::(), 21); + /// + /// let mut value: Dynamic = 1_i64.into(); + /// let result = engine.call_fn_dynamic(&mut scope, &ast, "action", Some(&mut value), [ 41_i64.into() ])?; + /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer + /// assert_eq!(value.as_int().unwrap(), 42); /// # } /// # Ok(()) /// # } @@ -1180,10 +1227,10 @@ impl Engine { scope: &mut Scope, ast: &AST, name: &str, - arg_values: impl IntoIterator, + mut this_ptr: Option<&mut Dynamic>, + mut arg_values: impl AsMut<[Dynamic]>, ) -> Result> { - let mut arg_values: StaticVec<_> = arg_values.into_iter().collect(); - self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut()) + self.call_fn_dynamic_raw(scope, ast, name, &mut this_ptr, arg_values.as_mut()) } /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments. @@ -1200,6 +1247,7 @@ impl Engine { scope: &mut Scope, ast: &AST, name: &str, + this_ptr: &mut Option<&mut Dynamic>, arg_values: &mut [Dynamic], ) -> Result> { let mut args: StaticVec<_> = arg_values.iter_mut().collect(); @@ -1220,7 +1268,7 @@ impl Engine { &mut mods, &mut state, ast.lib(), - &mut None, + this_ptr, name, fn_def, args, diff --git a/src/token.rs b/src/token.rs index 056f9714..673eb11f 100644 --- a/src/token.rs +++ b/src/token.rs @@ -12,7 +12,7 @@ use crate::stdlib::{ borrow::Cow, boxed::Box, char, - collections::{HashMap, HashSet}, + collections::HashMap, fmt, iter::Peekable, str::{Chars, FromStr}, diff --git a/tests/call_fn.rs b/tests/call_fn.rs index dd46450d..f217073e 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,5 +1,7 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, Func, ParseError, ParseErrorType, Scope, INT}; +use rhai::{ + Dynamic, Engine, EvalAltResult, Func, ImmutableString, ParseError, ParseErrorType, Scope, INT, +}; #[test] fn test_fn() -> Result<(), Box> { @@ -110,3 +112,47 @@ fn test_anonymous_fn() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_fn_ptr() -> Result<(), Box> { + let mut engine = Engine::new(); + + let ast = engine.compile( + r#" + fn foo(x) { this += x; } + + let x = 41; + x.bar("foo", 1); + x + "#, + )?; + let ast_clone = ast.clone(); + + engine.register_raw_fn( + "bar", + &[ + std::any::TypeId::of::(), + std::any::TypeId::of::(), + std::any::TypeId::of::(), + ], + move |engine: &Engine, args: &mut [&mut Dynamic]| { + let callback = args[1].clone().cast::(); + let value = args[2].clone(); + let this_ptr = args.get_mut(0).unwrap(); + + engine.call_fn_dynamic( + &mut Scope::new(), + &ast_clone, + &callback, + Some(this_ptr), + [value], + )?; + + Ok(().into()) + }, + ); + + assert_eq!(engine.eval_ast::(&ast)?, 42); + + Ok(()) +}