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.
* `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

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", ())?;
```
`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<Item = Dynamic>` (such as a simple `Vec<Dynamic>`):
```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);
```

View File

@ -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::<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`.
/// The type must implement `Clone`.
///
@ -1127,7 +1159,7 @@ impl Engine {
args: A,
) -> Result<T, Box<EvalAltResult>> {
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<rhai::EvalAltResult>> {
/// # #[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::<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);
///
/// 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);
///
/// 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<Item = Dynamic>,
mut this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>,
) -> Result<Dynamic, Box<EvalAltResult>> {
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<Dynamic, Box<EvalAltResult>> {
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,

View File

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

View File

@ -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<EvalAltResult>> {
@ -110,3 +112,47 @@ fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
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(())
}