Add new register_fn_raw API.

This commit is contained in:
Stephen Chung 2020-07-05 23:08:44 +08:00
parent 4052ad3df1
commit a27f89b524
5 changed files with 135 additions and 14 deletions

View File

@ -14,6 +14,7 @@ Breaking changes
---------------- ----------------
* `EvalAltResult::ErrorMismatchOutputType` has an extra argument containing the name of the requested type. * `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 New features
------------ ------------
@ -22,6 +23,7 @@ New features
This is particularly useful when converting a Rust `struct` to a `Dynamic` _object map_ and back. 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::disable_symbol` to surgically disable keywords and/or operators.
* `Engine::register_custom_operator` to define a custom operator. * `Engine::register_custom_operator` to define a custom operator.
* New low-level API `Engine::register_raw_fn`.
Version 0.16.1 Version 0.16.1

View File

@ -55,10 +55,35 @@ let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?;
let result: () = engine.call_fn(&mut scope, &ast, "hidden", ())?; 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 For more control, construct all arguments as `Dynamic` values and use `Engine::call_fn_dynamic`, passing it
anything that implements `IntoIterator<Item = Dynamic>` (such as a simple `Vec<Dynamic>`): anything that implements `IntoIterator<Item = Dynamic>` (such as a simple `Vec<Dynamic>`):
```rust ```rust
let result: Dynamic = engine.call_fn_dynamic(&mut scope, &ast, "hello", let result: Dynamic = engine.call_fn_dynamic(
vec![ String::from("abc").into(), 123_i64.into() ])?; &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);
``` ```

View File

@ -32,6 +32,38 @@ use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf};
/// Engine public API /// Engine public API
impl Engine { 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::<T>()`
///
/// To get access to a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// 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<Dynamic, Box<EvalAltResult>>
+ 'static,
#[cfg(feature = "sync")] func: impl Fn(&Engine, &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>>
+ Send
+ Sync
+ 'static,
) {
self.global_module.set_fn_var_args(name, args, func);
}
/// Register a custom type for use with the `Engine`. /// Register a custom type for use with the `Engine`.
/// The type must implement `Clone`. /// The type must implement `Clone`.
/// ///
@ -1127,7 +1159,7 @@ impl Engine {
args: A, args: A,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = args.into_vec(); 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()); 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 /// # Example
/// ///
@ -1148,7 +1188,7 @@ impl Engine {
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> { /// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_function"))] /// # #[cfg(not(feature = "no_function"))]
/// # { /// # {
/// use rhai::{Engine, Scope}; /// use rhai::{Engine, Scope, Dynamic};
/// ///
/// let engine = Engine::new(); /// let engine = Engine::new();
/// ///
@ -1156,20 +1196,27 @@ impl Engine {
/// fn add(x, y) { len(x) + y + foo } /// fn add(x, y) { len(x) + y + foo }
/// fn add1(x) { len(x) + 1 + foo } /// fn add1(x) { len(x) + 1 + foo }
/// fn bar() { foo/2 } /// fn bar() { foo/2 }
/// fn action(x) { this += x; } // function using 'this' pointer
/// ")?; /// ")?;
/// ///
/// let mut scope = Scope::new(); /// let mut scope = Scope::new();
/// scope.push("foo", 42_i64); /// scope.push("foo", 42_i64);
/// ///
/// // Call the script-defined function /// // 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::<i64>(), 168); /// assert_eq!(result.cast::<i64>(), 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::<i64>(), 46); /// assert_eq!(result.cast::<i64>(), 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::<i64>(), 21); /// assert_eq!(result.cast::<i64>(), 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(()) /// # Ok(())
/// # } /// # }
@ -1180,10 +1227,10 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
name: &str, name: &str,
arg_values: impl IntoIterator<Item = Dynamic>, mut this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let mut arg_values: StaticVec<_> = arg_values.into_iter().collect(); self.call_fn_dynamic_raw(scope, ast, name, &mut this_ptr, arg_values.as_mut())
self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut())
} }
/// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments. /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments.
@ -1200,6 +1247,7 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
name: &str, name: &str,
this_ptr: &mut Option<&mut Dynamic>,
arg_values: &mut [Dynamic], arg_values: &mut [Dynamic],
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let mut args: StaticVec<_> = arg_values.iter_mut().collect(); let mut args: StaticVec<_> = arg_values.iter_mut().collect();
@ -1220,7 +1268,7 @@ impl Engine {
&mut mods, &mut mods,
&mut state, &mut state,
ast.lib(), ast.lib(),
&mut None, this_ptr,
name, name,
fn_def, fn_def,
args, args,

View File

@ -12,7 +12,7 @@ use crate::stdlib::{
borrow::Cow, borrow::Cow,
boxed::Box, boxed::Box,
char, char,
collections::{HashMap, HashSet}, collections::HashMap,
fmt, fmt,
iter::Peekable, iter::Peekable,
str::{Chars, FromStr}, str::{Chars, FromStr},

View File

@ -1,5 +1,7 @@
#![cfg(not(feature = "no_function"))] #![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] #[test]
fn test_fn() -> Result<(), Box<EvalAltResult>> { fn test_fn() -> Result<(), Box<EvalAltResult>> {
@ -110,3 +112,47 @@ fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
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::<INT>(),
std::any::TypeId::of::<ImmutableString>(),
std::any::TypeId::of::<INT>(),
],
move |engine: &Engine, args: &mut [&mut Dynamic]| {
let callback = args[1].clone().cast::<ImmutableString>();
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::<INT>(&ast)?, 42);
Ok(())
}