From 30bfdd841aad0cbd536d9cca42a52acb1b4942a3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 27 Nov 2021 16:28:34 +0800 Subject: [PATCH] Add FnPtr::call. --- CHANGELOG.md | 1 + src/api/call_fn.rs | 1 - src/types/fn_ptr.rs | 71 +++++++++++++++++++++++++++++++++++++++++++-- tests/closures.rs | 2 +- tests/fn_ptr.rs | 42 ++++++++++++++++++++++++++- 5 files changed, 111 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9842d2d9..4ccb9ef9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 ---------------- diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index 6ebb35df..28b43e1d 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -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`. /// diff --git a/src/types/fn_ptr.rs b/src/types/fn_ptr.rs index 8710010c..3f4bfe4a 100644 --- a/src/types/fn_ptr.rs +++ b/src/types/fn_ptr.rs @@ -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> { + /// # #[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( + &self, + engine: &Engine, + ast: &AST, + args: impl FuncArgs, + ) -> Result> { + 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::()).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; diff --git a/tests/closures.rs b/tests/closures.rs index c68038f5..a53eeb77 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -340,7 +340,7 @@ fn test_closures_external() -> Result<(), Box> { 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())); diff --git a/tests/fn_ptr.rs b/tests/fn_ptr.rs index 04d89b29..3dbc6e4c 100644 --- a/tests/fn_ptr.rs +++ b/tests/fn_ptr.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult, FnPtr, INT}; #[test] fn test_fn_ptr() -> Result<(), Box> { @@ -111,3 +111,43 @@ fn test_fn_ptr_curry() -> Result<(), Box> { Ok(()) } + +#[test] +#[cfg(not(feature = "no_function"))] +fn test_fn_ptr_call() -> Result<(), Box> { + 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> { + 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::(&ast)?; + + move |x: INT| -> Result> { fn_ptr.call(&engine, &ast, (x,)) } + }; + + // 'f' captures: the Engine, the AST, and the closure + assert_eq!(f(42)?, "hello42"); + + Ok(()) +}