From 46b92c9d1f22970a84c574c3200f9b3d94e5e5a2 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 18 Oct 2020 21:47:34 +0800 Subject: [PATCH] Allow NativeCallContext in function arguments. --- Cargo.toml | 2 +- RELEASES.md | 6 + codegen/Cargo.toml | 2 +- codegen/src/function.rs | 55 ++- codegen/src/rhai_module.rs | 2 +- codegen/src/test/function.rs | 71 ++- codegen/src/test/module.rs | 80 +--- doc/src/language/fn-ptr.md | 10 +- doc/src/plugins/function.md | 73 +++ doc/src/plugins/module.md | 79 ++++ doc/src/rust/register-raw.md | 2 +- src/engine.rs | 2 +- src/fn_call.rs | 6 +- src/module/mod.rs | 3 +- src/packages/array_basic.rs | 838 +++++++++++++++++------------------ src/packages/iter_basic.rs | 5 +- src/plugin.rs | 8 +- 17 files changed, 710 insertions(+), 534 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e6a70183..58ca03db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ categories = [ "no-std", "embedded", "wasm", "parser-implementations" ] [dependencies] smallvec = { version = "1.4.2", default-features = false } -rhai_codegen = { version = "0.1", path = "codegen" } +rhai_codegen = { version = "0.2", path = "codegen" } [features] default = [] diff --git a/RELEASES.md b/RELEASES.md index 1dc6c67d..8a4cd93b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -12,7 +12,13 @@ Breaking changes * `EvalAltResult::ErrorLoopBreak` is renamed to `EvalAltResult::LoopBreak`. * `Engine::register_raw_fn` function signature has changed. +New features +------------ + +* The plugins system is enhanced to support functions taking a `NativeCallContext` as the first parameter. + Enhancements +------------ * Calling `eval` or `Fn` in method-call style, which is an error, is now caught during parsing. diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index c1abf4bf..377b8f91 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai_codegen" -version = "0.1.1" +version = "0.2.0" edition = "2018" authors = ["jhwgh1968"] description = "Procedural macro support package for Rhai, a scripting language for Rust" diff --git a/codegen/src/function.rs b/codegen/src/function.rs index a992e858..d0f56f0b 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -222,6 +222,7 @@ pub(crate) struct ExportedFn { entire_span: proc_macro2::Span, signature: syn::Signature, is_public: bool, + pass_context: bool, return_dynamic: bool, mut_receiver: bool, params: ExportedFnParams, @@ -237,15 +238,36 @@ impl Parse for ExportedFn { let dynamic_type_path2 = syn::parse2::(quote! { rhai::Dynamic }).unwrap(); let mut return_dynamic = false; + let context_type_path1 = syn::parse2::(quote! { NativeCallContext }).unwrap(); + let context_type_path2 = + syn::parse2::(quote! { rhai::NativeCallContext }).unwrap(); + let mut pass_context = false; + // #[cfg] attributes are not allowed on functions due to what is generated for them crate::attrs::deny_cfg_attr(&fn_all.attrs)?; // Determine if the function is public. let is_public = matches!(fn_all.vis, syn::Visibility::Public(_)); - // Determine whether function generates a special calling convention for a mutable - // reciever. + + // Determine if the function requires a call context + if let Some(first_arg) = fn_all.sig.inputs.first() { + if let syn::FnArg::Typed(syn::PatType { ref ty, .. }) = first_arg { + match flatten_type_groups(ty.as_ref()) { + syn::Type::Path(p) + if p.path == context_type_path1 || p.path == context_type_path2 => + { + pass_context = true; + } + _ => (), + } + } + } + + let skip_slots = if pass_context { 1 } else { 0 }; + + // Determine whether function generates a special calling convention for a mutable receiver. let mut_receiver = { - if let Some(first_arg) = fn_all.sig.inputs.first() { + if let Some(first_arg) = fn_all.sig.inputs.iter().skip(skip_slots).next() { match first_arg { syn::FnArg::Receiver(syn::Receiver { reference: Some(_), .. @@ -265,8 +287,7 @@ impl Parse for ExportedFn { _ => { return Err(syn::Error::new( ty.span(), - "references from Rhai in this position \ - must be mutable", + "references from Rhai in this position must be mutable", )) } }, @@ -281,7 +302,7 @@ impl Parse for ExportedFn { }; // All arguments after the first must be moved except for &str. - for arg in fn_all.sig.inputs.iter().skip(1) { + for arg in fn_all.sig.inputs.iter().skip(skip_slots + 1) { let ty = match arg { syn::FnArg::Typed(syn::PatType { ref ty, .. }) => ty, _ => panic!("internal error: receiver argument outside of first position!?"), @@ -304,8 +325,7 @@ impl Parse for ExportedFn { if !is_ok { return Err(syn::Error::new( ty.span(), - "this type in this position passes from \ - Rhai by value", + "this type in this position passes from Rhai by value", )); } } @@ -337,6 +357,7 @@ impl Parse for ExportedFn { entire_span, signature: fn_all.sig, is_public, + pass_context, return_dynamic, mut_receiver, params: ExportedFnParams::default(), @@ -363,6 +384,10 @@ impl ExportedFn { self.params.skip } + pub(crate) fn pass_context(&self) -> bool { + self.pass_context + } + pub(crate) fn signature(&self) -> &syn::Signature { &self.signature } @@ -418,11 +443,13 @@ impl ExportedFn { } pub(crate) fn arg_list(&self) -> impl Iterator { - self.signature.inputs.iter() + let skip = if self.pass_context { 1 } else { 0 }; + self.signature.inputs.iter().skip(skip) } pub(crate) fn arg_count(&self) -> usize { - self.signature.inputs.len() + let skip = if self.pass_context { 1 } else { 0 }; + self.signature.inputs.len() - skip } pub(crate) fn return_type(&self) -> Option<&syn::Type> { @@ -625,6 +652,10 @@ impl ExportedFn { let mut input_type_exprs: Vec = Vec::new(); let skip_first_arg; + if self.pass_context { + unpack_exprs.push(syn::parse2::(quote! { context }).unwrap()); + } + // Handle the first argument separately if the function has a "method like" receiver if is_method_call { skip_first_arg = true; @@ -764,9 +795,7 @@ impl ExportedFn { let type_name = syn::Ident::new(on_type_name, proc_macro2::Span::call_site()); quote! { impl PluginFunction for #type_name { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), #arg_count, "wrong arg count: {} != {}", args.len(), #arg_count); diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index fec67d1c..b7d160bc 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -68,7 +68,7 @@ pub(crate) fn generate_body( ); } - // NB: these are token streams, because reparsing messes up "> >" vs ">>" + // NB: these are token streams, because re-parsing messes up "> >" vs ">>" let mut gen_fn_tokens: Vec = Vec::new(); for function in fns { function.update_scope(&parent_scope); diff --git a/codegen/src/test/function.rs b/codegen/src/test/function.rs index ea790f29..ee5616e7 100644 --- a/codegen/src/test/function.rs +++ b/codegen/src/test/function.rs @@ -277,9 +277,7 @@ mod generate_tests { use super::*; struct Token(); impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 0usize, "wrong arg count: {} != {}", args.len(), 0usize); Ok(Dynamic::from(do_nothing())) @@ -320,9 +318,7 @@ mod generate_tests { use super::*; struct Token(); impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = mem::take(args[0usize]).cast::(); @@ -352,6 +348,49 @@ mod generate_tests { assert_streams_eq(item_fn.generate(), expected_tokens); } + #[test] + fn one_arg_fn_with_context() { + let input_tokens: TokenStream = quote! { + pub fn do_something(context: NativeCallContext, x: usize) {} + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_do_something { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).cast::(); + Ok(Dynamic::from(do_something(context, arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_variadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + Token().into() + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + pub fn dynamic_result_fn(context: NativeCallContext, x: usize) -> Result > { + Ok(Dynamic::from(super::do_something(context, x))) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert!(item_fn.pass_context()); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + #[test] fn return_dynamic() { let input_tokens: TokenStream = quote! { @@ -366,9 +405,7 @@ mod generate_tests { use super::*; struct Token(); impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 0usize, "wrong arg count: {} != {}", args.len(), 0usize); Ok(return_dynamic()) @@ -405,9 +442,7 @@ mod generate_tests { let expected_tokens = quote! { impl PluginFunction for MyType { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = mem::take(args[0usize]).cast::(); @@ -439,9 +474,7 @@ mod generate_tests { use super::*; struct Token(); impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); let arg0 = mem::take(args[0usize]).cast::(); @@ -485,9 +518,7 @@ mod generate_tests { use super::*; struct Token(); impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); let arg1 = mem::take(args[1usize]).cast::(); @@ -532,9 +563,7 @@ mod generate_tests { use super::*; struct Token(); impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = mem::take(args[0usize]).take_immutable_string().unwrap(); diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs index fac3becd..554d1901 100644 --- a/codegen/src/test/module.rs +++ b/codegen/src/test/module.rs @@ -302,9 +302,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct get_mystic_number_token(); impl PluginFunction for get_mystic_number_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 0usize, "wrong arg count: {} != {}", args.len(), 0usize); Ok(Dynamic::from(get_mystic_number())) @@ -364,9 +362,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct add_one_to_token(); impl PluginFunction for add_one_to_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = mem::take(args[0usize]).cast::(); @@ -441,9 +437,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct add_one_to_token(); impl PluginFunction for add_one_to_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = mem::take(args[0usize]).cast::(); @@ -469,9 +463,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct add_n_to_token(); impl PluginFunction for add_n_to_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); let arg0 = mem::take(args[0usize]).cast::(); @@ -535,9 +527,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct add_together_token(); impl PluginFunction for add_together_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); let arg0 = mem::take(args[0usize]).cast::(); @@ -608,9 +598,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct add_together_token(); impl PluginFunction for add_together_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); let arg0 = mem::take(args[0usize]).cast::(); @@ -850,9 +838,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct get_mystic_number_token(); impl PluginFunction for get_mystic_number_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 0usize, "wrong arg count: {} != {}", args.len(), 0usize); Ok(Dynamic::from(get_mystic_number())) @@ -943,9 +929,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct print_out_to_token(); impl PluginFunction for print_out_to_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = mem::take(args[0usize]).take_immutable_string().unwrap(); @@ -1007,9 +991,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct print_out_to_token(); impl PluginFunction for print_out_to_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = mem::take(args[0usize]).take_string().unwrap(); @@ -1071,9 +1053,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct increment_token(); impl PluginFunction for increment_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = &mut args[0usize].write_lock::().unwrap(); @@ -1138,9 +1118,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct increment_token(); impl PluginFunction for increment_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = &mut args[0usize].write_lock::().unwrap(); @@ -1225,9 +1203,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct increment_token(); impl PluginFunction for increment_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = &mut args[0usize].write_lock::().unwrap(); @@ -1310,9 +1286,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct int_foo_token(); impl PluginFunction for int_foo_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = &mut args[0usize].write_lock::().unwrap(); @@ -1376,9 +1350,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct int_foo_token(); impl PluginFunction for int_foo_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); let arg0 = &mut args[0usize].write_lock::().unwrap(); @@ -1442,9 +1414,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct int_foo_token(); impl PluginFunction for int_foo_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); let arg1 = mem::take(args[1usize]).cast::(); @@ -1513,9 +1483,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct int_foo_token(); impl PluginFunction for int_foo_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); let arg1 = mem::take(args[1usize]).cast::(); @@ -1580,9 +1548,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct get_by_index_token(); impl PluginFunction for get_by_index_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); let arg1 = mem::take(args[1usize]).cast::(); @@ -1652,9 +1618,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct get_by_index_token(); impl PluginFunction for get_by_index_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); let arg1 = mem::take(args[1usize]).cast::(); @@ -1721,9 +1685,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct set_by_index_token(); impl PluginFunction for set_by_index_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 3usize, "wrong arg count: {} != {}", args.len(), 3usize); let arg1 = mem::take(args[1usize]).cast::(); @@ -1797,9 +1759,7 @@ mod generate_tests { #[allow(non_camel_case_types)] struct set_by_index_token(); impl PluginFunction for set_by_index_token { - fn call(&self, - args: &mut [&mut Dynamic] - ) -> Result> { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 3usize, "wrong arg count: {} != {}", args.len(), 3usize); let arg1 = mem::take(args[1usize]).cast::(); diff --git a/doc/src/language/fn-ptr.md b/doc/src/language/fn-ptr.md index 5901c23b..acd301eb 100644 --- a/doc/src/language/fn-ptr.md +++ b/doc/src/language/fn-ptr.md @@ -17,11 +17,11 @@ Built-in methods The following standard methods (mostly defined in the [`BasicFnPackage`][packages] but excluded if using a [raw `Engine`]) operate on function pointers: -| Function | Parameter(s) | Description | -| ---------------------------------- | ------------ | ---------------------------------------------------------------------------- | -| `name` method and property | _none_ | returns the name of the function encapsulated by the function pointer | -| `is_anonymous` method and property | _none_ | does the function pointer refer to an [anonymous function]? | -| `call` | _arguments_ | calls the function matching the function pointer's name with the _arguments_ | +| Function | Parameter(s) | Description | +| ---------------------------------- | ------------ | ------------------------------------------------------------------------------------------------ | +| `name` method and property | _none_ | returns the name of the function encapsulated by the function pointer | +| `is_anonymous` method and property | _none_ | does the function pointer refer to an [anonymous function]? Not available under [`no_function`]. | +| `call` | _arguments_ | calls the function matching the function pointer's name with the _arguments_ | Examples diff --git a/doc/src/plugins/function.md b/doc/src/plugins/function.md index 396f53e1..904d56be 100644 --- a/doc/src/plugins/function.md +++ b/doc/src/plugins/function.md @@ -75,3 +75,76 @@ fn main() { register_exported_fn!(engine, "+", double_and_divide); } ``` + + +`NativeCallContext` Parameter +---------------------------- + +If the _first_ parameter of a function is of type `rhai::NativeCallContext`, then it is treated +specially by the plugins system. + +`NativeCallContext` is a type that encapsulates the current _native call context_ and exposes the following: + +* `NativeCallContext::engine(): &Engine` - the current [`Engine`], with all configurations and settings. + This is sometimes useful for calling a script-defined function within the same evaluation context + using [`Engine::call_fn`][`call_fn`]. + +* `NativeCallContext::namespace(): &Module` - the global namespace of script-defined functions, as a [`Module`]. + +This first parameter, if exists, will be stripped before all other processing. It is _virtual_. +Most importantly, it does _not_ count as a parameter to the function and there is no need to provide +this argument when calling the function in Rhai. + +The native call context can be used to call a [function pointer] or [closure] that has been passed +as a parameter to the function, thereby implementing a _callback_: + +```rust +use rhai::{Dynamic, FnPtr, NativeCallContext, EvalAltResult}; +use rhai::plugin::*; // a "prelude" import for macros + +#[export_fn(return_raw)] +pub fn greet(context: NativeCallContext, callback: FnPtr) + -> Result> +{ + // Call the callback closure with the current context + // to obtain the name to greet! + let name = callback.call_dynamic(context, None, [])?; + Ok(format!("hello, {}!", name).into()) +} +``` + +The native call context is also useful in another scenario: protecting a function from malicious scripts. + +```rust +use rhai::{Dynamic, INT, Array, NativeCallContext, EvalAltResult, Position}; +use rhai::plugin::*; // a "prelude" import for macros + +// This function builds an array of arbitrary size, but is protected +// against attacks by first checking with the allowed limit set +// into the 'Engine'. +#[export_fn(return_raw)] +pub fn grow(context: NativeCallContext, size: INT) + -> Result> +{ + // Make sure the function does not generate a + // data structure larger than the allowed limit + // for the Engine! + if size as usize > context.engine().max_array_size() + { + return EvalAltResult::ErrorDataTooLarge( + "Size to grow".to_string(), + context.engine().max_array_size(), + size as usize, + Position::none(), + ).into(); + } + + let array = Array::new(); + + for x in 0..size { + array.push(x.into()); + } + + OK(array.into()) +} +``` diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md index 87a11087..aeaeeb58 100644 --- a/doc/src/plugins/module.md +++ b/doc/src/plugins/module.md @@ -334,6 +334,85 @@ mod my_module { ``` +`NativeCallContext` Parameter +---------------------------- + +If the _first_ parameter of a function is of type `rhai::NativeCallContext`, then it is treated +specially by the plugins system. + +`NativeCallContext` is a type that encapsulates the current _native call context_ and exposes the following: + +* `NativeCallContext::engine(): &Engine` - the current [`Engine`], with all configurations and settings. + This is sometimes useful for calling a script-defined function within the same evaluation context + using [`Engine::call_fn`][`call_fn`]. + +* `NativeCallContext::namespace(): &Module` - the global namespace of script-defined functions, as a [`Module`]. + +This first parameter, if exists, will be stripped before all other processing. It is _virtual_. +Most importantly, it does _not_ count as a parameter to the function and there is no need to provide +this argument when calling the function in Rhai. + +The native call context can be used to call a [function pointer] or [closure] that has been passed +as a parameter to the function, thereby implementing a _callback_: + +```rust +use rhai::{Dynamic, FnPtr, NativeCallContext, EvalAltResult}; +use rhai::plugin::*; // a "prelude" import for macros + +#[export_module] +mod my_module { + #[rhai_fn(return_raw)] + pub fn greet(context: NativeCallContext, callback: FnPtr) + -> Result> + { + // Call the callback closure with the current context + // to obtain the name to greet! + let name = callback.call_dynamic(context, None, [])?; + Ok(format!("hello, {}!", name).into()) + } +} +``` + +The native call context is also useful in another scenario: protecting a function from malicious scripts. + +```rust +use rhai::{Dynamic, INT, Array, NativeCallContext, EvalAltResult, Position}; +use rhai::plugin::*; // a "prelude" import for macros + +#[export_module] +mod my_module { + // This function builds an array of arbitrary size, but is protected + // against attacks by first checking with the allowed limit set + // into the 'Engine'. + #[rhai_fn(return_raw)] + pub fn grow(context: NativeCallContext, size: INT) + -> Result> + { + // Make sure the function does not generate a + // data structure larger than the allowed limit + // for the Engine! + if size as usize > context.engine().max_array_size() + { + return EvalAltResult::ErrorDataTooLarge( + "Size to grow".to_string(), + context.engine().max_array_size(), + size as usize, + Position::none(), + ).into(); + } + + let array = Array::new(); + + for x in 0..size { + array.push(x.into()); + } + + OK(array.into()) + } +} +``` + + `#[export_module]` Parameters ---------------------------- diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index 8a351c2a..78990e1c 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -70,7 +70,7 @@ where: * `context.engine(): &Engine` - the current [`Engine`], with all configurations and settings. This is sometimes useful for calling a script-defined function within the same evaluation context - using [`Engine::call_fn`][`call_fn`]. + using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. * `context.namespace(): &Module` - the global namespace of script-defined functions, as a [`Module`]. diff --git a/src/engine.rs b/src/engine.rs index c570e0c6..46af0581 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1561,7 +1561,7 @@ impl Engine { // Overriding exact implementation if func.is_plugin_fn() { - func.get_plugin_fn().call(args)?; + func.get_plugin_fn().call((self, lib).into(), args)?; } else { func.get_native_fn()((self, lib).into(), args)?; } diff --git a/src/fn_call.rs b/src/fn_call.rs index b16d35db..8260c0e1 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -206,7 +206,7 @@ impl Engine { // Run external function let result = if func.is_plugin_fn() { - func.get_plugin_fn().call(args) + func.get_plugin_fn().call((self, lib).into(), args) } else { func.get_native_fn()((self, lib).into(), args) }; @@ -1185,7 +1185,9 @@ impl Engine { self.call_script_fn(new_scope, mods, state, lib, &mut None, fn_def, args, level) } - Some(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut()), + Some(f) if f.is_plugin_fn() => { + f.get_plugin_fn().call((self, lib).into(), args.as_mut()) + } Some(f) if f.is_native() => { if !f.is_method() { // Clone first argument diff --git a/src/module/mod.rs b/src/module/mod.rs index 759bf1aa..66c08875 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1,7 +1,6 @@ //! Module defining external-loaded modules for Rhai. use crate::any::{Dynamic, Variant}; -use crate::engine::Engine; use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, NativeCallContext, SendSync}; use crate::fn_register::by_value as cast_arg; use crate::parser::FnAccess; @@ -15,7 +14,7 @@ use crate::{fn_native::Shared, parser::ScriptFnDef}; #[cfg(not(feature = "no_module"))] use crate::{ - engine::Imports, + engine::{Engine, Imports}, parser::AST, scope::{Entry as ScopeEntry, Scope}, }; diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index b42277e7..77ef56e7 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -1,7 +1,7 @@ #![cfg(not(feature = "no_index"))] #![allow(non_snake_case)] -use crate::any::{Dynamic, Variant}; +use crate::any::Dynamic; use crate::def_package; use crate::engine::Array; use crate::fn_native::{FnPtr, NativeCallContext}; @@ -38,6 +38,23 @@ macro_rules! gen_array_functions { list.insert(position as usize, Dynamic::from(item)); } } + + #[rhai_fn(return_raw)] + pub fn pad(context: NativeCallContext, list: &mut Array, len: INT, item: $arg_type) -> Result> { + // Check if array will be over max size limit + #[cfg(not(feature = "unchecked"))] + if context.engine().max_array_size() > 0 && len > 0 && (len as usize) > context.engine().max_array_size() { + return EvalAltResult::ErrorDataTooLarge( + "Size of array".to_string(), context.engine().max_array_size(), len as usize, Position::none(), + ).into(); + } + + if len > 0 && len as usize > list.len() { + list.resize(len as usize, Dynamic::from(item)); + } + + Ok(().into()) + } } })* } } @@ -46,10 +63,6 @@ macro_rules! gen_array_functions { macro_rules! reg_functions { ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( combine_with_exported_module!($mod_name, "array_functions", $root::$arg_type::functions); - - $mod_name.set_raw_fn("pad", - &[TypeId::of::(), TypeId::of::(), TypeId::of::<$arg_type>()], - pad::<$arg_type>); )* } } @@ -71,18 +84,6 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { #[cfg(not(feature = "no_object"))] reg_functions!(lib += map; Map); - lib.set_raw_fn("map", &[TypeId::of::(), TypeId::of::()], map); - lib.set_raw_fn("filter", &[TypeId::of::(), TypeId::of::()], filter); - lib.set_raw_fn("drain", &[TypeId::of::(), TypeId::of::()], drain); - lib.set_raw_fn("retain", &[TypeId::of::(), TypeId::of::()], retain); - lib.set_raw_fn("reduce", &[TypeId::of::(), TypeId::of::()], reduce); - lib.set_raw_fn("reduce", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], reduce_with_initial); - lib.set_raw_fn("reduce_rev", &[TypeId::of::(), TypeId::of::()], reduce_rev); - lib.set_raw_fn("reduce_rev", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], reduce_rev_with_initial); - lib.set_raw_fn("some", &[TypeId::of::(), TypeId::of::()], some); - lib.set_raw_fn("all", &[TypeId::of::(), TypeId::of::()], all); - lib.set_raw_fn("sort", &[TypeId::of::(), TypeId::of::()], sort); - // Merge in the module at the end to override `+=` for arrays combine_with_exported_module!(lib, "array", array_functions); @@ -193,6 +194,364 @@ mod array_functions { list[start..].iter().cloned().collect() } + #[rhai_fn(return_raw)] + pub fn map( + context: NativeCallContext, + list: &mut Array, + mapper: FnPtr, + ) -> Result> { + let mut array = Array::with_capacity(list.len()); + + for (i, item) in list.iter().enumerate() { + array.push( + mapper + .call_dynamic(context, None, [item.clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(mapper.fn_name()) => + { + mapper.call_dynamic(context, None, [item.clone(), (i as INT).into()]) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "map".to_string(), + err, + Position::none(), + )) + })?, + ); + } + + Ok(array.into()) + } + #[rhai_fn(return_raw)] + pub fn filter( + context: NativeCallContext, + list: &mut Array, + filter: FnPtr, + ) -> Result> { + let mut array = Array::with_capacity(list.len()); + + for (i, item) in list.iter().enumerate() { + if filter + .call_dynamic(context, None, [item.clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(filter.fn_name()) => + { + filter.call_dynamic(context, None, [item.clone(), (i as INT).into()]) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "filter".to_string(), + err, + Position::none(), + )) + })? + .as_bool() + .unwrap_or(false) + { + array.push(item.clone()); + } + } + + Ok(array.into()) + } + #[rhai_fn(return_raw)] + pub fn some( + context: NativeCallContext, + list: &mut Array, + filter: FnPtr, + ) -> Result> { + for (i, item) in list.iter().enumerate() { + if filter + .call_dynamic(context, None, [item.clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(filter.fn_name()) => + { + filter.call_dynamic(context, None, [item.clone(), (i as INT).into()]) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "filter".to_string(), + err, + Position::none(), + )) + })? + .as_bool() + .unwrap_or(false) + { + return Ok(true.into()); + } + } + + Ok(false.into()) + } + #[rhai_fn(return_raw)] + pub fn all( + context: NativeCallContext, + list: &mut Array, + filter: FnPtr, + ) -> Result> { + for (i, item) in list.iter().enumerate() { + if !filter + .call_dynamic(context, None, [item.clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(filter.fn_name()) => + { + filter.call_dynamic(context, None, [item.clone(), (i as INT).into()]) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "filter".to_string(), + err, + Position::none(), + )) + })? + .as_bool() + .unwrap_or(false) + { + return Ok(false.into()); + } + } + + Ok(true.into()) + } + #[rhai_fn(return_raw)] + pub fn reduce( + context: NativeCallContext, + list: &mut Array, + reducer: FnPtr, + ) -> Result> { + let mut result: Dynamic = ().into(); + + for (i, item) in list.iter().enumerate() { + result = reducer + .call_dynamic(context, None, [result.clone(), item.clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(reducer.fn_name()) => + { + reducer.call_dynamic( + context, + None, + [result, item.clone(), (i as INT).into()], + ) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "reduce".to_string(), + err, + Position::none(), + )) + })?; + } + + Ok(result) + } + #[rhai_fn(name = "reduce", return_raw)] + pub fn reduce_with_initial( + context: NativeCallContext, + list: &mut Array, + reducer: FnPtr, + initial: FnPtr, + ) -> Result> { + let mut result = initial.call_dynamic(context, None, []).map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "reduce".to_string(), + err, + Position::none(), + )) + })?; + + for (i, item) in list.iter().enumerate() { + result = reducer + .call_dynamic(context, None, [result.clone(), item.clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(reducer.fn_name()) => + { + reducer.call_dynamic( + context, + None, + [result, item.clone(), (i as INT).into()], + ) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "reduce".to_string(), + err, + Position::none(), + )) + })?; + } + + Ok(result) + } + #[rhai_fn(return_raw)] + pub fn reduce_rev( + context: NativeCallContext, + list: &mut Array, + reducer: FnPtr, + ) -> Result> { + let mut result: Dynamic = ().into(); + + for (i, item) in list.iter().enumerate().rev() { + result = reducer + .call_dynamic(context, None, [result.clone(), item.clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(reducer.fn_name()) => + { + reducer.call_dynamic( + context, + None, + [result, item.clone(), (i as INT).into()], + ) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "reduce".to_string(), + err, + Position::none(), + )) + })?; + } + + Ok(result) + } + #[rhai_fn(name = "reduce_rev", return_raw)] + pub fn reduce_rev_with_initial( + context: NativeCallContext, + list: &mut Array, + reducer: FnPtr, + initial: FnPtr, + ) -> Result> { + let mut result = initial.call_dynamic(context, None, []).map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "reduce".to_string(), + err, + Position::none(), + )) + })?; + + for (i, item) in list.iter().enumerate().rev() { + result = reducer + .call_dynamic(context, None, [result.clone(), item.clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(reducer.fn_name()) => + { + reducer.call_dynamic( + context, + None, + [result, item.clone(), (i as INT).into()], + ) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "reduce".to_string(), + err, + Position::none(), + )) + })?; + } + + Ok(result) + } + #[rhai_fn(return_raw)] + pub fn sort( + context: NativeCallContext, + list: &mut Array, + comparer: FnPtr, + ) -> Result> { + list.sort_by(|x, y| { + comparer + .call_dynamic(context, None, [x.clone(), y.clone()]) + .ok() + .and_then(|v| v.as_int().ok()) + .map(|v| { + if v > 0 { + Ordering::Greater + } else if v < 0 { + Ordering::Less + } else { + Ordering::Equal + } + }) + .unwrap_or_else(|| { + let x_type_id = x.type_id(); + let y_type_id = y.type_id(); + + if x_type_id > y_type_id { + Ordering::Greater + } else if x_type_id < y_type_id { + Ordering::Less + } else { + Ordering::Equal + } + }) + }); + + Ok(().into()) + } + #[rhai_fn(return_raw)] + pub fn drain( + context: NativeCallContext, + list: &mut Array, + filter: FnPtr, + ) -> Result> { + let mut drained = Array::with_capacity(list.len()); + + let mut i = list.len(); + + while i > 0 { + i -= 1; + + if filter + .call_dynamic(context, None, [list[i].clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(filter.fn_name()) => + { + filter.call_dynamic(context, None, [list[i].clone(), (i as INT).into()]) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "filter".to_string(), + err, + Position::none(), + )) + })? + .as_bool() + .unwrap_or(false) + { + drained.push(list.remove(i)); + } + } + + Ok(drained.into()) + } #[rhai_fn(name = "drain")] pub fn drain_range(list: &mut Array, start: INT, len: INT) -> Array { let start = if start < 0 { @@ -213,6 +572,45 @@ mod array_functions { list.drain(start..start + len - 1).collect() } + #[rhai_fn(return_raw)] + pub fn retain( + context: NativeCallContext, + list: &mut Array, + filter: FnPtr, + ) -> Result> { + let mut drained = Array::with_capacity(list.len()); + + let mut i = list.len(); + + while i > 0 { + i -= 1; + + if !filter + .call_dynamic(context, None, [list[i].clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(fn_sig, _) + if fn_sig.starts_with(filter.fn_name()) => + { + filter.call_dynamic(context, None, [list[i].clone(), (i as INT).into()]) + } + _ => Err(err), + }) + .map_err(|err| { + Box::new(EvalAltResult::ErrorInFunctionCall( + "filter".to_string(), + err, + Position::none(), + )) + })? + .as_bool() + .unwrap_or(false) + { + drained.push(list.remove(i)); + } + } + + Ok(drained.into()) + } #[rhai_fn(name = "retain")] pub fn retain_range(list: &mut Array, start: INT, len: INT) -> Array { let start = if start < 0 { @@ -238,412 +636,6 @@ mod array_functions { } } -fn pad( - _context: NativeCallContext, - args: &mut [&mut Dynamic], -) -> Result<(), Box> { - let len = *args[1].read_lock::().unwrap(); - - // Check if array will be over max size limit - #[cfg(not(feature = "unchecked"))] - if _context.engine().max_array_size() > 0 - && len > 0 - && (len as usize) > _context.engine().max_array_size() - { - return EvalAltResult::ErrorDataTooLarge( - "Size of array".to_string(), - _context.engine().max_array_size(), - len as usize, - Position::none(), - ) - .into(); - } - - if len > 0 { - let item = args[2].clone(); - let mut list = args[0].write_lock::().unwrap(); - - if len as usize > list.len() { - list.resize(len as usize, item); - } - } - Ok(()) -} - -fn map(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { - let list = args[0].read_lock::().unwrap(); - let mapper = args[1].read_lock::().unwrap(); - - let mut array = Array::with_capacity(list.len()); - - for (i, item) in list.iter().enumerate() { - array.push( - mapper - .call_dynamic(context, None, [item.clone()]) - .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => { - mapper.call_dynamic(context, None, [item.clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "map".to_string(), - err, - Position::none(), - )) - })?, - ); - } - - Ok(array) -} - -fn filter( - context: NativeCallContext, - args: &mut [&mut Dynamic], -) -> Result> { - let list = args[0].read_lock::().unwrap(); - let filter = args[1].read_lock::().unwrap(); - - let mut array = Array::with_capacity(list.len()); - - for (i, item) in list.iter().enumerate() { - if filter - .call_dynamic(context, None, [item.clone()]) - .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => { - filter.call_dynamic(context, None, [item.clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "filter".to_string(), - err, - Position::none(), - )) - })? - .as_bool() - .unwrap_or(false) - { - array.push(item.clone()); - } - } - - Ok(array) -} - -fn some(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { - let list = args[0].read_lock::().unwrap(); - let filter = args[1].read_lock::().unwrap(); - - for (i, item) in list.iter().enumerate() { - if filter - .call_dynamic(context, None, [item.clone()]) - .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => { - filter.call_dynamic(context, None, [item.clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "filter".to_string(), - err, - Position::none(), - )) - })? - .as_bool() - .unwrap_or(false) - { - return Ok(true.into()); - } - } - - Ok(false.into()) -} - -fn all(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { - let list = args[0].read_lock::().unwrap(); - let filter = args[1].read_lock::().unwrap(); - - for (i, item) in list.iter().enumerate() { - if !filter - .call_dynamic(context, None, [item.clone()]) - .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => { - filter.call_dynamic(context, None, [item.clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "filter".to_string(), - err, - Position::none(), - )) - })? - .as_bool() - .unwrap_or(false) - { - return Ok(false.into()); - } - } - - Ok(true.into()) -} - -fn reduce( - context: NativeCallContext, - args: &mut [&mut Dynamic], -) -> Result> { - let list = args[0].read_lock::().unwrap(); - let reducer = args[1].read_lock::().unwrap(); - - let mut result: Dynamic = ().into(); - - for (i, item) in list.iter().enumerate() { - result = reducer - .call_dynamic(context, None, [result.clone(), item.clone()]) - .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => { - reducer.call_dynamic(context, None, [result, item.clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "reduce".to_string(), - err, - Position::none(), - )) - })?; - } - - Ok(result) -} - -fn reduce_with_initial( - context: NativeCallContext, - args: &mut [&mut Dynamic], -) -> Result> { - let list = args[0].read_lock::().unwrap(); - let reducer = args[1].read_lock::().unwrap(); - let initial = args[2].read_lock::().unwrap(); - - let mut result = initial.call_dynamic(context, None, []).map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "reduce".to_string(), - err, - Position::none(), - )) - })?; - - for (i, item) in list.iter().enumerate() { - result = reducer - .call_dynamic(context, None, [result.clone(), item.clone()]) - .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => { - reducer.call_dynamic(context, None, [result, item.clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "reduce".to_string(), - err, - Position::none(), - )) - })?; - } - - Ok(result) -} - -fn reduce_rev( - context: NativeCallContext, - args: &mut [&mut Dynamic], -) -> Result> { - let list = args[0].read_lock::().unwrap(); - let reducer = args[1].read_lock::().unwrap(); - - let mut result: Dynamic = ().into(); - - for (i, item) in list.iter().enumerate().rev() { - result = reducer - .call_dynamic(context, None, [result.clone(), item.clone()]) - .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => { - reducer.call_dynamic(context, None, [result, item.clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "reduce".to_string(), - err, - Position::none(), - )) - })?; - } - - Ok(result) -} - -fn reduce_rev_with_initial( - context: NativeCallContext, - args: &mut [&mut Dynamic], -) -> Result> { - let list = args[0].read_lock::().unwrap(); - let reducer = args[1].read_lock::().unwrap(); - let initial = args[2].read_lock::().unwrap(); - - let mut result = initial.call_dynamic(context, None, []).map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "reduce".to_string(), - err, - Position::none(), - )) - })?; - - for (i, item) in list.iter().enumerate().rev() { - result = reducer - .call_dynamic(context, None, [result.clone(), item.clone()]) - .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => { - reducer.call_dynamic(context, None, [result, item.clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "reduce".to_string(), - err, - Position::none(), - )) - })?; - } - - Ok(result) -} - -fn sort( - context: NativeCallContext, - args: &mut [&mut Dynamic], -) -> Result> { - let comparer = args[1].read_lock::().unwrap().clone(); - let mut list = args[0].write_lock::().unwrap(); - - list.sort_by(|x, y| { - comparer - .call_dynamic(context, None, [x.clone(), y.clone()]) - .ok() - .and_then(|v| v.as_int().ok()) - .map(|v| { - if v > 0 { - Ordering::Greater - } else if v < 0 { - Ordering::Less - } else { - Ordering::Equal - } - }) - .unwrap_or_else(|| { - let x_type_id = x.type_id(); - let y_type_id = y.type_id(); - - if x_type_id > y_type_id { - Ordering::Greater - } else if x_type_id < y_type_id { - Ordering::Less - } else { - Ordering::Equal - } - }) - }); - - Ok(().into()) -} - -fn drain( - context: NativeCallContext, - args: &mut [&mut Dynamic], -) -> Result> { - let filter = args[1].read_lock::().unwrap().clone(); - let mut list = args[0].write_lock::().unwrap(); - - let mut drained = Array::with_capacity(list.len()); - - let mut i = list.len(); - - while i > 0 { - i -= 1; - - if filter - .call_dynamic(context, None, [list[i].clone()]) - .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => { - filter.call_dynamic(context, None, [list[i].clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "filter".to_string(), - err, - Position::none(), - )) - })? - .as_bool() - .unwrap_or(false) - { - drained.push(list.remove(i)); - } - } - - Ok(drained) -} - -fn retain( - context: NativeCallContext, - args: &mut [&mut Dynamic], -) -> Result> { - let filter = args[1].read_lock::().unwrap().clone(); - let mut list = args[0].write_lock::().unwrap(); - - let mut drained = Array::with_capacity(list.len()); - - let mut i = list.len(); - - while i > 0 { - i -= 1; - - if !filter - .call_dynamic(context, None, [list[i].clone()]) - .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => { - filter.call_dynamic(context, None, [list[i].clone(), (i as INT).into()]) - } - _ => Err(err), - }) - .map_err(|err| { - Box::new(EvalAltResult::ErrorInFunctionCall( - "filter".to_string(), - err, - Position::none(), - )) - })? - .as_bool() - .unwrap_or(false) - { - drained.push(list.remove(i)); - } - } - - Ok(drained) -} - gen_array_functions!(basic => INT, bool, char, ImmutableString, FnPtr, Array, Unit); #[cfg(not(feature = "only_i32"))] diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 0b3dead7..21dc8e86 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -3,7 +3,10 @@ use crate::def_package; use crate::parser::INT; use crate::result::EvalAltResult; -use crate::stdlib::ops::{Add, Range}; +use crate::stdlib::{ + boxed::Box, + ops::{Add, Range}, +}; fn get_range(from: T, to: T) -> Result, Box> { Ok(from..to) diff --git a/src/plugin.rs b/src/plugin.rs index d00ef28a..30edcc25 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -2,7 +2,7 @@ pub use crate::any::Dynamic; pub use crate::engine::Engine; -pub use crate::fn_native::CallableFunction; +pub use crate::fn_native::{CallableFunction, NativeCallContext}; pub use crate::fn_register::{RegisterFn, RegisterResultFn}; pub use crate::module::Module; pub use crate::parser::FnAccess; @@ -22,7 +22,11 @@ pub use rhai_codegen::{export_fn, register_exported_fn}; /// Use the `#[export_module]` and `#[export_fn]` procedural attributes instead. pub trait PluginFunction { /// Call the plugin function with the arguments provided. - fn call(&self, args: &mut [&mut Dynamic]) -> Result>; + fn call( + &self, + context: NativeCallContext, + args: &mut [&mut Dynamic], + ) -> Result>; /// Is this plugin function a method? fn is_method_call(&self) -> bool;