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`. * Added `into_array` and `into_typed_array` for `Dynamic`.
* New `FnPtr::call` to simplify calling a function pointer.
Deprecated API's Deprecated API's
---------------- ----------------

View File

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

View File

@ -1,12 +1,15 @@
//! The `FnPtr` type. //! The `FnPtr` type.
use crate::tokenizer::is_valid_identifier; use crate::tokenizer::is_valid_identifier;
use crate::types::dynamic::Variant;
use crate::{ use crate::{
Dynamic, EvalAltResult, Identifier, NativeCallContext, Position, RhaiResult, StaticVec, Dynamic, Engine, EvalAltResult, FuncArgs, Identifier, NativeCallContext, Position, RhaiResult,
StaticVec, AST,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{ use std::{
any::type_name,
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
fmt, mem, fmt, mem,
}; };
@ -96,8 +99,69 @@ impl FnPtr {
self.0.starts_with(crate::engine::FN_ANONYMOUS) self.0.starts_with(crate::engine::FN_ANONYMOUS)
} }
/// Call the function pointer with curried arguments (if any). /// 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 /// # WARNING
/// ///
@ -110,8 +174,9 @@ impl FnPtr {
&self, &self,
ctx: &NativeCallContext, ctx: &NativeCallContext,
this_ptr: Option<&mut Dynamic>, this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>, arg_values: impl AsMut<[Dynamic]>,
) -> RhaiResult { ) -> RhaiResult {
let mut arg_values = arg_values;
let mut arg_values = arg_values.as_mut(); let mut arg_values = arg_values.as_mut();
let mut args_data; let mut args_data;

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, FnPtr, INT};
#[test] #[test]
fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> { fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
@ -111,3 +111,43 @@ fn test_fn_ptr_curry() -> Result<(), Box<EvalAltResult>> {
Ok(()) 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(())
}