Add FnPtr::call.

This commit is contained in:
Stephen Chung 2021-11-27 16:28:34 +08:00
parent d56585c877
commit 30bfdd841a
5 changed files with 111 additions and 6 deletions

View File

@ -13,6 +13,7 @@ Enhancements
------------
* Added `into_array` and `into_typed_array` for `Dynamic`.
* New `FnPtr::call` to simplify calling a function pointer.
Deprecated API's
----------------

View File

@ -9,7 +9,6 @@ use std::prelude::v1::*;
impl Engine {
/// Call a script function defined in an [`AST`] with multiple arguments.
/// Arguments are passed as a tuple.
///
/// Not available under `no_function`.
///

View File

@ -1,12 +1,15 @@
//! The `FnPtr` type.
use crate::tokenizer::is_valid_identifier;
use crate::types::dynamic::Variant;
use crate::{
Dynamic, EvalAltResult, Identifier, NativeCallContext, Position, RhaiResult, StaticVec,
Dynamic, Engine, EvalAltResult, FuncArgs, Identifier, NativeCallContext, Position, RhaiResult,
StaticVec, AST,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
any::type_name,
convert::{TryFrom, TryInto},
fmt, mem,
};
@ -96,8 +99,69 @@ impl FnPtr {
self.0.starts_with(crate::engine::FN_ANONYMOUS)
}
/// Call the function pointer with curried arguments (if any).
/// The function may be script-defined (not available under `no_function`) or native Rust.
///
/// If this function is a script-defined function, it must not be marked private.
/// This method is intended for calling a function pointer that is passed into a native Rust
/// function as an argument. Therefore, the [`AST`] is _NOT_ evaluated before calling the
/// function.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_function"))]
/// # {
/// use rhai::{Engine, FnPtr};
///
/// let engine = Engine::new();
///
/// let ast = engine.compile("fn foo(x, y) { len(x) + y }")?;
///
/// let mut fn_ptr = FnPtr::new("foo")?;
///
/// // Curry values into the function pointer
/// fn_ptr.set_curry(vec!["abc".into()]);
///
/// // Values are only needed for non-curried parameters
/// let result: i64 = fn_ptr.call(&engine, &ast, ( 39_i64, ) )?;
///
/// assert_eq!(result, 42);
/// # }
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn call<T: Variant + Clone>(
&self,
engine: &Engine,
ast: &AST,
args: impl FuncArgs,
) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = crate::StaticVec::new_const();
args.parse(&mut arg_values);
let lib = [ast.as_ref()];
let ctx = NativeCallContext::new(engine, self.fn_name(), &lib);
let result = self.call_dynamic(&ctx, None, arg_values)?;
let typ = engine.map_type_name(result.type_name());
result.try_cast().ok_or_else(|| {
EvalAltResult::ErrorMismatchOutputType(
engine.map_type_name(type_name::<T>()).into(),
typ.into(),
Position::NONE,
)
.into()
})
}
/// Call the function pointer with curried arguments (if any).
/// The function may be script-defined (not available under `no_function`) or native Rust.
///
/// This method is intended for calling a function pointer that is passed into a native Rust
/// function as an argument. Therefore, the [`AST`] is _NOT_ evaluated before calling the
/// function.
///
/// # WARNING
///
@ -110,8 +174,9 @@ impl FnPtr {
&self,
ctx: &NativeCallContext,
this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>,
arg_values: impl AsMut<[Dynamic]>,
) -> RhaiResult {
let mut arg_values = arg_values;
let mut arg_values = arg_values.as_mut();
let mut args_data;

View File

@ -340,7 +340,7 @@ fn test_closures_external() -> Result<(), Box<EvalAltResult>> {
let fn_name = fn_ptr.fn_name().to_string();
let context = NativeCallContext::new(&engine, &fn_name, &lib);
// Closure 'f' captures: the engine, the AST, and the curried function pointer
// Closure 'f' captures: the engine, the AST, and the curried function pointer
let f = move |x: INT| fn_ptr.call_dynamic(&context, None, [x.into()]);
assert_eq!(f(42)?.into_string(), Ok("hello42".to_string()));

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, INT};
use rhai::{Engine, EvalAltResult, FnPtr, INT};
#[test]
fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
@ -111,3 +111,43 @@ fn test_fn_ptr_curry() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
#[cfg(not(feature = "no_function"))]
fn test_fn_ptr_call() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let ast = engine.compile("private fn foo(x, y) { len(x) + y }")?;
let mut fn_ptr = FnPtr::new("foo")?;
fn_ptr.set_curry(vec!["abc".into()]);
let result: INT = fn_ptr.call(&engine, &ast, (39 as INT,))?;
assert_eq!(result, 42);
Ok(())
}
#[test]
#[cfg(not(feature = "no_function"))]
fn test_fn_ptr_make_closure() -> Result<(), Box<EvalAltResult>> {
let f = {
let engine = Engine::new();
let ast = engine.compile(
r#"
let test = "hello";
|x| test + x // this creates a closure
"#,
)?;
let fn_ptr = engine.eval_ast::<FnPtr>(&ast)?;
move |x: INT| -> Result<String, Box<EvalAltResult>> { fn_ptr.call(&engine, &ast, (x,)) }
};
// 'f' captures: the Engine, the AST, and the closure
assert_eq!(f(42)?, "hello42");
Ok(())
}