From a16fc71935dea3aa72d0defc0427c20493f0e461 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 16 Oct 2020 23:41:56 +0800 Subject: [PATCH 01/16] Encapsulate RwLock and RefCell into Locked. --- src/any.rs | 45 +++++++++++++++--------------------- src/fn_native.rs | 17 ++++++++++---- src/module/resolvers/file.rs | 20 +++++++--------- 3 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/any.rs b/src/any.rs index 407a25ca..615637c8 100644 --- a/src/any.rs +++ b/src/any.rs @@ -5,7 +5,7 @@ use crate::parser::{ImmutableString, INT}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; #[cfg(not(feature = "no_closure"))] -use crate::fn_native::{shared_try_take, Shared}; +use crate::fn_native::{shared_try_take, Locked, Shared}; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; @@ -26,14 +26,11 @@ use crate::stdlib::{ #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] -use crate::stdlib::{ - cell::{Ref, RefCell, RefMut}, - rc::Rc, -}; +use crate::stdlib::cell::{Ref, RefMut}; #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] -use crate::stdlib::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use crate::stdlib::sync::{RwLockReadGuard, RwLockWriteGuard}; #[cfg(not(feature = "no_object"))] use crate::stdlib::collections::HashMap; @@ -165,11 +162,7 @@ pub enum Union { Variant(Box>), #[cfg(not(feature = "no_closure"))] - #[cfg(not(feature = "sync"))] - Shared(Shared>), - #[cfg(not(feature = "no_closure"))] - #[cfg(feature = "sync")] - Shared(Shared>), + Shared(Shared>), } /// Underlying `Variant` read guard for `Dynamic`. @@ -644,10 +637,7 @@ impl Dynamic { #[cfg(not(feature = "no_closure"))] return match self.0 { Union::Shared(..) => self, - #[cfg(not(feature = "sync"))] - _ => Self(Union::Shared(Rc::new(RefCell::new(self)))), - #[cfg(feature = "sync")] - _ => Self(Union::Shared(Arc::new(RwLock::new(self)))), + _ => Self(Union::Shared(Locked::new(self).into())), }; #[cfg(feature = "no_closure")] @@ -859,15 +849,20 @@ impl Dynamic { pub fn flatten(self) -> Self { match self.0 { #[cfg(not(feature = "no_closure"))] - Union::Shared(cell) => { - #[cfg(not(feature = "sync"))] - return shared_try_take(cell) - .map_or_else(|c| c.borrow().clone(), RefCell::into_inner); - - #[cfg(feature = "sync")] - return shared_try_take(cell) - .map_or_else(|c| c.read().unwrap().clone(), |v| v.into_inner().unwrap()); - } + Union::Shared(cell) => shared_try_take(cell).map_or_else( + |cell| { + #[cfg(not(feature = "sync"))] + return cell.borrow().clone(); + #[cfg(feature = "sync")] + return cell.read().unwrap().clone(); + }, + |value| { + #[cfg(not(feature = "sync"))] + return value.into_inner(); + #[cfg(feature = "sync")] + return value.into_inner().unwrap(); + }, + ), _ => self, } } @@ -910,7 +905,6 @@ impl Dynamic { Union::Shared(ref cell) => { #[cfg(not(feature = "sync"))] let data = cell.borrow(); - #[cfg(feature = "sync")] let data = cell.read().unwrap(); @@ -944,7 +938,6 @@ impl Dynamic { Union::Shared(ref cell) => { #[cfg(not(feature = "sync"))] let data = cell.borrow_mut(); - #[cfg(feature = "sync")] let data = cell.write().unwrap(); diff --git a/src/fn_native.rs b/src/fn_native.rs index 48df3aee..eabc19e3 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -15,10 +15,10 @@ use crate::stdlib::{ boxed::Box, convert::TryFrom, fmt, iter::empty, mem, string::String, vec::Vec, }; -#[cfg(not(feature = "sync"))] -use crate::stdlib::rc::Rc; #[cfg(feature = "sync")] -use crate::stdlib::sync::Arc; +use crate::stdlib::sync::{Arc, RwLock}; +#[cfg(not(feature = "sync"))] +use crate::stdlib::{cell::RefCell, rc::Rc}; /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] @@ -34,13 +34,20 @@ pub trait SendSync {} #[cfg(not(feature = "sync"))] impl SendSync for T {} -/// Immutable reference-counted container +/// Immutable reference-counted container. #[cfg(not(feature = "sync"))] pub type Shared = Rc; -/// Immutable reference-counted container +/// Immutable reference-counted container. #[cfg(feature = "sync")] pub type Shared = Arc; +/// Synchronized shared object. +#[cfg(not(feature = "sync"))] +pub type Locked = RefCell; +/// Synchronized shared object. +#[cfg(feature = "sync")] +pub type Locked = RwLock; + /// Consume a `Shared` resource and return a mutable reference to the wrapped value. /// If the resource is shared (i.e. has other outstanding references), a cloned copy is used. pub fn shared_make_mut(value: &mut Shared) -> &mut T { diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 33204dd8..154b76d4 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -1,4 +1,5 @@ use crate::engine::Engine; +use crate::fn_native::Locked; use crate::module::{Module, ModuleResolver}; use crate::parser::AST; use crate::result::EvalAltResult; @@ -6,12 +7,6 @@ use crate::token::Position; use crate::stdlib::{boxed::Box, collections::HashMap, path::PathBuf, string::String}; -#[cfg(not(feature = "sync"))] -use crate::stdlib::cell::RefCell; - -#[cfg(feature = "sync")] -use crate::stdlib::sync::RwLock; - /// Module resolution service that loads module script files from the file system. /// /// Script files are cached so they are are not reloaded and recompiled in subsequent requests. @@ -21,6 +16,12 @@ use crate::stdlib::sync::RwLock; /// to the base directory. The script file is then forced to be in a specified extension /// (default `.rhai`). /// +/// # Function Namespace +/// +/// When a function within a script file module is loaded, all functions in the _global_ namespace +/// plus all those defined within the same module are _merged_ into a _unified_ namespace before +/// the call. Therefore, functions in a module script can cross-call each other. +/// /// # Examples /// /// ``` @@ -39,12 +40,7 @@ use crate::stdlib::sync::RwLock; pub struct FileModuleResolver { path: PathBuf, extension: String, - - #[cfg(not(feature = "sync"))] - cache: RefCell>, - - #[cfg(feature = "sync")] - cache: RwLock>, + cache: Locked>, } impl Default for FileModuleResolver { From 39474d642001f9438c3239fd542c6f28329f94f8 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 17 Oct 2020 13:49:16 +0800 Subject: [PATCH 02/16] Streamline function pointers and currying. --- RELEASES.md | 11 ++ doc/src/language/fn-ptr.md | 2 + src/engine.rs | 2 +- src/fn_call.rs | 217 ++++++++++++++++++------------------- src/fn_native.rs | 27 ++--- src/module/mod.rs | 24 ++++ src/packages/mod.rs | 7 ++ src/parser.rs | 8 ++ src/syntax.rs | 5 +- 9 files changed, 171 insertions(+), 132 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 19a0e65d..c06a0242 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,17 @@ Rhai Release Notes ================== + +Version 0.19.3 +============== + + +Version 0.19.2 +============== + +Bug fix on call module functions. + + Version 0.19.1 ============== diff --git a/doc/src/language/fn-ptr.md b/doc/src/language/fn-ptr.md index b8412138..9e22eb70 100644 --- a/doc/src/language/fn-ptr.md +++ b/doc/src/language/fn-ptr.md @@ -6,6 +6,8 @@ Function Pointers It is possible to store a _function pointer_ in a variable just like a normal value. In fact, internally a function pointer simply stores the _name_ of the function as a string. +A function pointer is created via the `Fn` function, which takes a [string] parameter. + Call a function pointer using the `call` method. diff --git a/src/engine.rs b/src/engine.rs index b8bbbc1e..aab58fbc 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1479,7 +1479,7 @@ impl Engine { Expr::Stmt(x) => self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level), // var op= rhs - Expr::Assignment(x) if matches!(x.0, Expr::Variable(_)) => { + Expr::Assignment(x) if x.0.get_variable_access(false).is_some() => { let (lhs_expr, op, rhs_expr, op_pos) = x.as_ref(); let mut rhs_val = self .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? diff --git a/src/fn_call.rs b/src/fn_call.rs index e0cf13e0..5785f1d2 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -711,16 +711,17 @@ impl Engine { let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { // FnPtr call let fn_ptr = obj.read_lock::().unwrap(); - let mut curry = fn_ptr.curry().iter().cloned().collect::>(); // Redirect function name let fn_name = fn_ptr.fn_name(); + let args_len = call_args.len() + fn_ptr.curry().len(); // Recalculate hash let hash = if native { 0 } else { - calc_fn_hash(empty(), fn_name, curry.len() + call_args.len(), empty()) + calc_fn_hash(empty(), fn_name, args_len, empty()) }; // Arguments are passed as-is, adding the curried arguments + let mut curry = fn_ptr.curry().iter().cloned().collect::>(); let mut arg_values = curry .iter_mut() .chain(call_args.iter_mut()) @@ -737,16 +738,17 @@ impl Engine { { // FnPtr call on object let fn_ptr = call_args.remove(0).cast::(); - let mut curry = fn_ptr.curry().iter().cloned().collect::>(); // Redirect function name - let fn_name = fn_ptr.get_fn_name().clone(); + let fn_name = fn_ptr.fn_name(); + let args_len = call_args.len() + fn_ptr.curry().len(); // Recalculate hash let hash = if native { 0 } else { - calc_fn_hash(empty(), &fn_name, curry.len() + call_args.len(), empty()) + calc_fn_hash(empty(), fn_name, args_len, empty()) }; // Replace the first argument with the object pointer, adding the curried arguments + let mut curry = fn_ptr.curry().iter().cloned().collect::>(); let mut arg_values = once(obj) .chain(curry.iter_mut()) .chain(call_args.iter_mut()) @@ -755,7 +757,7 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - state, lib, &fn_name, hash, args, is_ref, true, pub_only, None, def_val, level, + state, lib, fn_name, hash, args, is_ref, true, pub_only, None, def_val, level, ) } else if _fn_name == KEYWORD_FN_PTR_CURRY && obj.is::() { // Curry call @@ -796,14 +798,12 @@ impl Engine { _redirected = fn_ptr.get_fn_name().clone(); _fn_name = &_redirected; // Add curried arguments - if !fn_ptr.curry().is_empty() { - fn_ptr - .curry() - .iter() - .cloned() - .enumerate() - .for_each(|(i, v)| call_args.insert(i, v)); - } + fn_ptr + .curry() + .iter() + .cloned() + .enumerate() + .for_each(|(i, v)| call_args.insert(i, v)); // Recalculate the hash based on the new function name and new arguments hash = if native { 0 @@ -861,35 +861,33 @@ impl Engine { if !self.has_override(lib, hash_fn, hash_script, pub_only) { // Fn - only in function call style - let expr = args_expr.get(0).unwrap(); - let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - - return arg_value + return self + .eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)? .take_immutable_string() .map_err(|typ| { - self.make_type_mismatch_err::(typ, expr.position()) + self.make_type_mismatch_err::(typ, args_expr[0].position()) }) .and_then(|s| FnPtr::try_from(s)) .map(Into::::into) - .map_err(|err| err.fill_position(expr.position())); + .map_err(|err| err.fill_position(args_expr[0].position())); } } // Handle curry() if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 { - let expr = args_expr.get(0).unwrap(); - let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; if !fn_ptr.is::() { return Err(self.make_type_mismatch_err::( self.map_type_name(fn_ptr.type_name()), - expr.position(), + args_expr[0].position(), )); } let (fn_name, mut fn_curry) = fn_ptr.cast::().take_data(); // Append the new curried arguments to the existing list. + args_expr .iter() .skip(1) @@ -904,8 +902,7 @@ impl Engine { // Handle is_shared() #[cfg(not(feature = "no_closure"))] if name == KEYWORD_IS_SHARED && args_expr.len() == 1 { - let expr = args_expr.get(0).unwrap(); - let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let value = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; return Ok(value.is_shared().into()); } @@ -913,25 +910,24 @@ impl Engine { // Handle call() - Redirect function call let redirected; let mut args_expr = args_expr.as_ref(); - let mut curry: StaticVec<_> = Default::default(); + let mut curry = StaticVec::new(); let mut name = name; if name == KEYWORD_FN_PTR_CALL && args_expr.len() >= 1 && !self.has_override(lib, 0, hash_script, pub_only) { - let expr = args_expr.get(0).unwrap(); - let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; if !fn_ptr.is::() { return Err(self.make_type_mismatch_err::( self.map_type_name(fn_ptr.type_name()), - expr.position(), + args_expr[0].position(), )); } let fn_ptr = fn_ptr.cast::(); - curry = fn_ptr.curry().iter().cloned().collect(); + curry.extend(fn_ptr.curry().iter().cloned()); // Redirect function name redirected = fn_ptr.take_data().0; @@ -941,7 +937,8 @@ impl Engine { args_expr = &args_expr.as_ref()[1..]; // Recalculate hash - hash_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); + let args_len = args_expr.len() + curry.len(); + hash_script = calc_fn_hash(empty(), name, args_len, empty()); } // Handle is_def_var() @@ -949,10 +946,10 @@ impl Engine { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); if !self.has_override(lib, hash_fn, hash_script, pub_only) { - let expr = args_expr.get(0).unwrap(); - let var_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let var_name = + self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; let var_name = var_name.as_str().map_err(|err| { - self.make_type_mismatch_err::(err, expr.position()) + self.make_type_mismatch_err::(err, args_expr[0].position()) })?; if var_name.is_empty() { return Ok(false.into()); @@ -974,18 +971,17 @@ impl Engine { ); if !self.has_override(lib, hash_fn, hash_script, pub_only) { - let expr0 = args_expr.get(0).unwrap(); - let expr1 = args_expr.get(1).unwrap(); - - let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr0, level)?; - let num_params = self.eval_expr(scope, mods, state, lib, this_ptr, expr1, level)?; + let fn_name = + self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; + let num_params = + self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[1], level)?; let fn_name = fn_name.as_str().map_err(|err| { - self.make_type_mismatch_err::(err, expr0.position()) + self.make_type_mismatch_err::(err, args_expr[0].position()) + })?; + let num_params = num_params.as_int().map_err(|err| { + self.make_type_mismatch_err::(err, args_expr[1].position()) })?; - let num_params = num_params - .as_int() - .map_err(|err| self.make_type_mismatch_err::(err, expr1.position()))?; if fn_name.is_empty() || num_params < 0 { return Ok(false.into()); @@ -1003,14 +999,14 @@ impl Engine { if !self.has_override(lib, hash_fn, hash_script, pub_only) { // eval - only in function call style let prev_len = scope.len(); - let expr = args_expr.get(0).unwrap(); - let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let script = + self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; let script = script.as_str().map_err(|typ| { - self.make_type_mismatch_err::(typ, expr.position()) + self.make_type_mismatch_err::(typ, args_expr[0].position()) })?; let result = if !script.is_empty() { self.eval_script_expr(scope, mods, state, lib, script, level + 1) - .map_err(|err| err.fill_position(expr.position())) + .map_err(|err| err.fill_position(args_expr[0].position())) } else { Ok(().into()) }; @@ -1041,41 +1037,38 @@ impl Engine { } else { // If the first argument is a variable, and there is no curried arguments, convert to method-call style // in order to leverage potential &mut first argument and avoid cloning the value - match args_expr.get(0).unwrap() { + if args_expr[0].get_variable_access(false).is_some() && curry.is_empty() { // func(x, ...) -> x.func(...) - lhs @ Expr::Variable(_) if curry.is_empty() => { - arg_values = args_expr - .iter() - .skip(1) - .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) - .collect::>()?; + arg_values = args_expr + .iter() + .skip(1) + .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) + .collect::>()?; - let (target, _, _, pos) = - self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?; + let (target, _, _, pos) = + self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?; - self.inc_operations(state) - .map_err(|err| err.fill_position(pos))?; + self.inc_operations(state) + .map_err(|err| err.fill_position(pos))?; - args = if target.is_shared() || target.is_value() { - arg_values.insert(0, target.take_or_clone().flatten()); - arg_values.iter_mut().collect() - } else { - // Turn it into a method call only if the object is not shared and not a simple value - is_ref = true; - once(target.take_ref().unwrap()) - .chain(arg_values.iter_mut()) - .collect() - }; - } + args = if target.is_shared() || target.is_value() { + arg_values.insert(0, target.take_or_clone().flatten()); + arg_values.iter_mut().collect() + } else { + // Turn it into a method call only if the object is not shared and not a simple value + is_ref = true; + once(target.take_ref().unwrap()) + .chain(arg_values.iter_mut()) + .collect() + }; + } else { // func(..., ...) - _ => { - arg_values = args_expr - .iter() - .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) - .collect::>()?; + arg_values = args_expr + .iter() + .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) + .collect::>()?; - args = curry.iter_mut().chain(arg_values.iter_mut()).collect(); - } + args = curry.iter_mut().chain(arg_values.iter_mut()).collect(); } } @@ -1116,50 +1109,46 @@ impl Engine { // See if the first argument is a variable (not module-qualified). // If so, convert to method-call style in order to leverage potential // &mut first argument and avoid cloning the value - match args_expr.get(0).unwrap() { + if args_expr[0].get_variable_access(true).is_some() { // func(x, ...) -> x.func(...) - Expr::Variable(x) if x.1.is_none() => { - arg_values = args_expr - .iter() - .enumerate() - .map(|(i, expr)| { - // Skip the first argument - if i == 0 { - Ok(Default::default()) - } else { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - } - }) - .collect::>()?; + arg_values = args_expr + .iter() + .enumerate() + .map(|(i, expr)| { + // Skip the first argument + if i == 0 { + Ok(Default::default()) + } else { + self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) + } + }) + .collect::>()?; - // Get target reference to first argument - let var_expr = args_expr.get(0).unwrap(); - let (target, _, _, pos) = - self.search_scope_only(scope, mods, state, lib, this_ptr, var_expr)?; + // Get target reference to first argument + let (target, _, _, pos) = + self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?; - self.inc_operations(state) - .map_err(|err| err.fill_position(pos))?; - - if target.is_shared() || target.is_value() { - arg_values[0] = target.take_or_clone().flatten(); - args = arg_values.iter_mut().collect(); - } else { - let (first, rest) = arg_values.split_first_mut().unwrap(); - first_arg_value = Some(first); - args = once(target.take_ref().unwrap()) - .chain(rest.iter_mut()) - .collect(); - } - } - // func(..., ...) or func(mod::x, ...) - _ => { - arg_values = args_expr - .iter() - .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) - .collect::>()?; + self.inc_operations(state) + .map_err(|err| err.fill_position(pos))?; + if target.is_shared() || target.is_value() { + arg_values[0] = target.take_or_clone().flatten(); args = arg_values.iter_mut().collect(); + } else { + let (first, rest) = arg_values.split_first_mut().unwrap(); + first_arg_value = Some(first); + args = once(target.take_ref().unwrap()) + .chain(rest.iter_mut()) + .collect(); } + } else { + // func(..., ...) or func(mod::x, ...) + arg_values = args_expr + .iter() + .map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)) + .collect::>()?; + + args = arg_values.iter_mut().collect(); } } diff --git a/src/fn_native.rs b/src/fn_native.rs index eabc19e3..7ced25a9 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -11,9 +11,7 @@ use crate::token::{is_valid_identifier, Position}; use crate::utils::ImmutableString; use crate::{calc_fn_hash, StaticVec}; -use crate::stdlib::{ - boxed::Box, convert::TryFrom, fmt, iter::empty, mem, string::String, vec::Vec, -}; +use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, mem, string::String}; #[cfg(feature = "sync")] use crate::stdlib::sync::{Arc, RwLock}; @@ -79,12 +77,15 @@ pub type FnCallArgs<'a> = [&'a mut Dynamic]; /// A general function pointer, which may carry additional (i.e. curried) argument values /// to be passed onto a function during a call. #[derive(Debug, Clone, Default)] -pub struct FnPtr(ImmutableString, Vec); +pub struct FnPtr(ImmutableString, StaticVec); impl FnPtr { /// Create a new function pointer. #[inline(always)] - pub(crate) fn new_unchecked>(name: S, curry: Vec) -> Self { + pub(crate) fn new_unchecked>( + name: S, + curry: StaticVec, + ) -> Self { Self(name.into(), curry) } /// Get the name of the function. @@ -99,13 +100,13 @@ impl FnPtr { } /// Get the underlying data of the function pointer. #[inline(always)] - pub(crate) fn take_data(self) -> (ImmutableString, Vec) { + pub(crate) fn take_data(self) -> (ImmutableString, StaticVec) { (self.0, self.1) } /// Get the curried arguments. #[inline(always)] pub fn curry(&self) -> &[Dynamic] { - &self.1 + self.1.as_ref() } /// Call the function pointer with curried arguments (if any). @@ -125,24 +126,24 @@ impl FnPtr { this_ptr: Option<&mut Dynamic>, mut arg_values: impl AsMut<[Dynamic]>, ) -> Result> { + let arg_values = arg_values.as_mut(); + let fn_name = self.fn_name(); + let mut args_data = self - .1 + .curry() .iter() .cloned() - .chain(arg_values.as_mut().iter_mut().map(|v| mem::take(v))) + .chain(arg_values.iter_mut().map(mem::take)) .collect::>(); let has_this = this_ptr.is_some(); - let args_len = args_data.len(); let mut args = args_data.iter_mut().collect::>(); + let hash_script = calc_fn_hash(empty(), fn_name, args.len(), empty()); if let Some(obj) = this_ptr { args.insert(0, obj); } - let fn_name = self.0.as_str(); - let hash_script = calc_fn_hash(empty(), fn_name, args_len, empty()); - engine .exec_fn_call( &mut Default::default(), diff --git a/src/module/mod.rs b/src/module/mod.rs index d7cc40d9..5ad7c3a5 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -425,6 +425,30 @@ impl Module { } } + /// Does the particular Rust function exist in the module? + /// + /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. + /// It is also returned by the `set_fn_XXX` calls. + /// + /// # Examples + /// + /// ``` + /// use rhai::Module; + /// + /// let mut module = Module::new(); + /// module.set_fn_0("calc", || Ok(42_i64)); + /// assert!(module.contains_fn_with_name("calc", true)); + /// ``` + #[inline] + #[allow(dead_code)] + pub(crate) fn contains_fn_with_name(&self, fn_name: &str, public_only: bool) -> bool { + self.functions + .values() + .filter(|(_, access, _, _, _)| !public_only || access.is_public()) + .find(|(name, _, _, _, _)| name == fn_name) + .is_some() + } + /// Set a Rust function into the module, returning a hash key. /// /// If there is an existing Rust function of the same hash, it is replaced. diff --git a/src/packages/mod.rs b/src/packages/mod.rs index d7c7d023..1bc33737 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -65,6 +65,13 @@ impl PackagesCollection { pub fn contains_fn(&self, hash: u64, public_only: bool) -> bool { self.0.iter().any(|p| p.contains_fn(hash, public_only)) } + /// Does the specified function name exist in the `PackagesCollection`? + #[allow(dead_code)] + pub fn contains_fn_with_name(&self, fn_name: &str, public_only: bool) -> bool { + self.0 + .iter() + .any(|p| p.contains_fn_with_name(fn_name, public_only)) + } /// Get specified function via its hash key. pub fn get_fn(&self, hash: u64, public_only: bool) -> Option<&CallableFunction> { self.0 diff --git a/src/parser.rs b/src/parser.rs index 77b460f7..55d97082 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1079,6 +1079,14 @@ impl Expr { }) } + /// Is the expression a simple variable access? + pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> { + match self { + Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.0).0.as_str()), + _ => None, + } + } + /// Get the `Position` of the expression. pub fn position(&self) -> Position { match self { diff --git a/src/syntax.rs b/src/syntax.rs index f0372649..5e827105 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -41,10 +41,7 @@ impl Expression<'_> { /// If this expression is a variable name, return it. Otherwise `None`. #[inline(always)] pub fn get_variable_name(&self) -> Option<&str> { - match self.0 { - Expr::Variable(x) => Some((x.0).0.as_str()), - _ => None, - } + self.0.get_variable_access(true) } /// Get the expression. #[inline(always)] From 8eb6c821d4687ed40fda31cf9125ebaa02e9ce91 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 17 Oct 2020 14:08:59 +0800 Subject: [PATCH 03/16] Fix tests. --- src/module/mod.rs | 24 ------------------------ src/packages/mod.rs | 7 ------- 2 files changed, 31 deletions(-) diff --git a/src/module/mod.rs b/src/module/mod.rs index 5ad7c3a5..d7cc40d9 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -425,30 +425,6 @@ impl Module { } } - /// Does the particular Rust function exist in the module? - /// - /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. - /// It is also returned by the `set_fn_XXX` calls. - /// - /// # Examples - /// - /// ``` - /// use rhai::Module; - /// - /// let mut module = Module::new(); - /// module.set_fn_0("calc", || Ok(42_i64)); - /// assert!(module.contains_fn_with_name("calc", true)); - /// ``` - #[inline] - #[allow(dead_code)] - pub(crate) fn contains_fn_with_name(&self, fn_name: &str, public_only: bool) -> bool { - self.functions - .values() - .filter(|(_, access, _, _, _)| !public_only || access.is_public()) - .find(|(name, _, _, _, _)| name == fn_name) - .is_some() - } - /// Set a Rust function into the module, returning a hash key. /// /// If there is an existing Rust function of the same hash, it is replaced. diff --git a/src/packages/mod.rs b/src/packages/mod.rs index 1bc33737..d7c7d023 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -65,13 +65,6 @@ impl PackagesCollection { pub fn contains_fn(&self, hash: u64, public_only: bool) -> bool { self.0.iter().any(|p| p.contains_fn(hash, public_only)) } - /// Does the specified function name exist in the `PackagesCollection`? - #[allow(dead_code)] - pub fn contains_fn_with_name(&self, fn_name: &str, public_only: bool) -> bool { - self.0 - .iter() - .any(|p| p.contains_fn_with_name(fn_name, public_only)) - } /// Get specified function via its hash key. pub fn get_fn(&self, hash: u64, public_only: bool) -> Option<&CallableFunction> { self.0 From 7a4905209c06f922f0e90fe85e4cb4b719a09ac5 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 17 Oct 2020 16:34:07 +0800 Subject: [PATCH 04/16] Introduce EvalAltResult::ErrorSystem. --- RELEASES.md | 6 ++++++ src/api.rs | 10 ++++++++-- src/engine.rs | 18 ++++++++--------- src/result.rs | 54 +++++++++++++++++++++------------------------------ 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index c06a0242..715003ed 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -5,6 +5,12 @@ Rhai Release Notes Version 0.19.3 ============== +Breaking changes +---------------- + +* `EvalAltResult::ErrorReadingScriptFile` is removed in favor of the new `EvalAltResult::ErrorSystem`. +* `EvalAltResult::ErrorLoopBreak` is renamed to `EvalAltResult::LoopBreak`. + Version 0.19.2 ============== diff --git a/src/api.rs b/src/api.rs index 573730e5..87699672 100644 --- a/src/api.rs +++ b/src/api.rs @@ -924,13 +924,19 @@ impl Engine { #[inline] fn read_file(path: PathBuf) -> Result> { let mut f = File::open(path.clone()).map_err(|err| { - EvalAltResult::ErrorReadingScriptFile(path.clone(), Position::none(), err) + EvalAltResult::ErrorSystem( + format!("Cannot open script file '{}'", path.to_string_lossy()), + err.into(), + ) })?; let mut contents = String::new(); f.read_to_string(&mut contents).map_err(|err| { - EvalAltResult::ErrorReadingScriptFile(path.clone(), Position::none(), err) + EvalAltResult::ErrorSystem( + format!("Cannot read script file '{}'", path.to_string_lossy()), + err.into(), + ) })?; Ok(contents) diff --git a/src/engine.rs b/src/engine.rs index aab58fbc..3cb249d6 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1814,10 +1814,8 @@ impl Engine { match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) { Ok(_) => (), Err(err) => match *err { - EvalAltResult::ErrorLoopBreak(false, _) => (), - EvalAltResult::ErrorLoopBreak(true, _) => { - return Ok(Default::default()) - } + EvalAltResult::LoopBreak(false, _) => (), + EvalAltResult::LoopBreak(true, _) => return Ok(Default::default()), _ => return Err(err), }, } @@ -1834,8 +1832,8 @@ impl Engine { match self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level) { Ok(_) => (), Err(err) => match *err { - EvalAltResult::ErrorLoopBreak(false, _) => (), - EvalAltResult::ErrorLoopBreak(true, _) => return Ok(Default::default()), + EvalAltResult::LoopBreak(false, _) => (), + EvalAltResult::LoopBreak(true, _) => return Ok(Default::default()), _ => return Err(err), }, } @@ -1875,8 +1873,8 @@ impl Engine { match self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) { Ok(_) => (), Err(err) => match *err { - EvalAltResult::ErrorLoopBreak(false, _) => (), - EvalAltResult::ErrorLoopBreak(true, _) => break, + EvalAltResult::LoopBreak(false, _) => (), + EvalAltResult::LoopBreak(true, _) => break, _ => return Err(err), }, } @@ -1891,10 +1889,10 @@ impl Engine { } // Continue statement - Stmt::Continue(pos) => EvalAltResult::ErrorLoopBreak(false, *pos).into(), + Stmt::Continue(pos) => EvalAltResult::LoopBreak(false, *pos).into(), // Break statement - Stmt::Break(pos) => EvalAltResult::ErrorLoopBreak(true, *pos).into(), + Stmt::Break(pos) => EvalAltResult::LoopBreak(true, *pos).into(), // Return value Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => { diff --git a/src/result.rs b/src/result.rs index ad73bae2..650b0a0a 100644 --- a/src/result.rs +++ b/src/result.rs @@ -15,10 +15,6 @@ use crate::stdlib::{ string::{String, ToString}, }; -#[cfg(not(feature = "no_std"))] -#[cfg(not(target_arch = "wasm32"))] -use crate::stdlib::path::PathBuf; - /// Evaluation result. /// /// All wrapped `Position` values represent the location in the script where the error occurs. @@ -27,16 +23,12 @@ use crate::stdlib::path::PathBuf; #[derive(Debug)] #[non_exhaustive] pub enum EvalAltResult { + /// System error. Wrapped values are the error message and the internal error. + ErrorSystem(String, Box), + /// Syntax error. ErrorParsing(ParseErrorType, Position), - /// Error reading from a script file. Wrapped value is the path of the script file. - /// - /// Never appears under the `no_std` feature. - #[cfg(not(feature = "no_std"))] - #[cfg(not(target_arch = "wasm32"))] - ErrorReadingScriptFile(PathBuf, Position, std::io::Error), - /// Usage of an unknown variable. Wrapped value is the variable name. ErrorVariableNotFound(String, Position), /// Call to an unknown function. Wrapped value is the function signature. @@ -96,7 +88,7 @@ pub enum EvalAltResult { /// Breaking out of loops - not an error if within a loop. /// The wrapped value, if true, means breaking clean out of the loop (i.e. a `break` statement). /// The wrapped value, if false, means breaking the current context (i.e. a `continue` statement). - ErrorLoopBreak(bool, Position), + LoopBreak(bool, Position), /// Not an error: Value returned from a script via the `return` keyword. /// Wrapped value is the result value. Return(Dynamic, Position), @@ -105,10 +97,8 @@ pub enum EvalAltResult { impl EvalAltResult { pub(crate) fn desc(&self) -> &str { match self { - #[cfg(not(feature = "no_std"))] - #[cfg(not(target_arch = "wasm32"))] - Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file", - + #[allow(deprecated)] + Self::ErrorSystem(_, s) => s.description(), Self::ErrorParsing(p, _) => p.desc(), Self::ErrorInFunctionCall(_, _, _) => "Error in called function", Self::ErrorInModule(_, _, _) => "Error in module", @@ -146,8 +136,8 @@ impl EvalAltResult { Self::ErrorDataTooLarge(_, _, _, _) => "Data size exceeds maximum limit", Self::ErrorTerminated(_) => "Script terminated.", Self::ErrorRuntime(_, _) => "Runtime error", - Self::ErrorLoopBreak(true, _) => "Break statement not inside a loop", - Self::ErrorLoopBreak(false, _) => "Continue statement not inside a loop", + Self::LoopBreak(true, _) => "Break statement not inside a loop", + Self::LoopBreak(false, _) => "Continue statement not inside a loop", Self::Return(_, _) => "[Not Error] Function returns value", } } @@ -161,11 +151,8 @@ impl fmt::Display for EvalAltResult { let pos = self.position(); match self { - #[cfg(not(feature = "no_std"))] - #[cfg(not(target_arch = "wasm32"))] - Self::ErrorReadingScriptFile(path, _, err) => { - write!(f, "{} '{}': {}", desc, path.display(), err)? - } + Self::ErrorSystem(s, _) if s.is_empty() => f.write_str(desc)?, + Self::ErrorSystem(s, _) => write!(f, "{}: {}", s, desc)?, Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?, @@ -213,7 +200,7 @@ impl fmt::Display for EvalAltResult { } Self::ErrorArithmetic(s, _) => f.write_str(s)?, - Self::ErrorLoopBreak(_, _) => f.write_str(desc)?, + Self::LoopBreak(_, _) => f.write_str(desc)?, Self::Return(_, _) => f.write_str(desc)?, Self::ErrorArrayBounds(_, index, _) if *index < 0 => { @@ -258,6 +245,13 @@ impl fmt::Display for EvalAltResult { } } +impl> From for EvalAltResult { + #[inline(always)] + fn from(err: T) -> Self { + Self::ErrorRuntime(err.as_ref().to_string(), Position::none()) + } +} + impl> From for Box { #[inline(always)] fn from(err: T) -> Self { @@ -272,9 +266,7 @@ impl EvalAltResult { /// Get the `Position` of this error. pub fn position(&self) -> Position { match self { - #[cfg(not(feature = "no_std"))] - #[cfg(not(target_arch = "wasm32"))] - Self::ErrorReadingScriptFile(_, pos, _) => *pos, + Self::ErrorSystem(_, _) => Position::none(), Self::ErrorParsing(_, pos) | Self::ErrorFunctionNotFound(_, pos) @@ -301,7 +293,7 @@ impl EvalAltResult { | Self::ErrorDataTooLarge(_, _, _, pos) | Self::ErrorTerminated(pos) | Self::ErrorRuntime(_, pos) - | Self::ErrorLoopBreak(_, pos) + | Self::LoopBreak(_, pos) | Self::Return(_, pos) => *pos, } } @@ -309,9 +301,7 @@ impl EvalAltResult { /// Override the `Position` of this error. pub fn set_position(&mut self, new_position: Position) { match self { - #[cfg(not(feature = "no_std"))] - #[cfg(not(target_arch = "wasm32"))] - Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position, + Self::ErrorSystem(_, _) => (), Self::ErrorParsing(_, pos) | Self::ErrorFunctionNotFound(_, pos) @@ -338,7 +328,7 @@ impl EvalAltResult { | Self::ErrorDataTooLarge(_, _, _, pos) | Self::ErrorTerminated(pos) | Self::ErrorRuntime(_, pos) - | Self::ErrorLoopBreak(_, pos) + | Self::LoopBreak(_, pos) | Self::Return(_, pos) => *pos = new_position, } } From f903eda8ab3ef6e3dfbaa0c6b274bad2fb35596f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 17 Oct 2020 18:18:29 +0800 Subject: [PATCH 05/16] Catch Fn and eval in method call at parse time. --- RELEASES.md | 4 ++++ src/fn_call.rs | 31 ++++++++++--------------------- src/parser.rs | 15 ++++++++++++++- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 715003ed..9a7f9d08 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -11,6 +11,10 @@ Breaking changes * `EvalAltResult::ErrorReadingScriptFile` is removed in favor of the new `EvalAltResult::ErrorSystem`. * `EvalAltResult::ErrorLoopBreak` is renamed to `EvalAltResult::LoopBreak`. +Enhancements + +* Calling `eval` or `Fn` in method-call style, which is an error, is now caught during parsing. + Version 0.19.2 ============== diff --git a/src/fn_call.rs b/src/fn_call.rs index 5785f1d2..43cbb9bb 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -524,27 +524,16 @@ impl Engine { )) } - // Fn - KEYWORD_FN_PTR - if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => - { - EvalAltResult::ErrorRuntime( - "'Fn' should not be called in method style. Try Fn(...);".into(), - Position::none(), - ) - .into() - } - - // eval - reaching this point it must be a method-style call - KEYWORD_EVAL - if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => - { - EvalAltResult::ErrorRuntime( - "'eval' should not be called in method style. Try eval(...);".into(), - Position::none(), - ) - .into() - } + // Fn/eval - reaching this point it must be a method-style call, mostly like redirected + // by a function pointer so it isn't caught at parse time. + KEYWORD_FN_PTR | KEYWORD_EVAL if args.len() == 1 => EvalAltResult::ErrorRuntime( + format!( + "'{}' should not be called in method style. Try {}(...);", + fn_name, fn_name + ), + Position::none(), + ) + .into(), // Script-like function found #[cfg(not(feature = "no_function"))] diff --git a/src/parser.rs b/src/parser.rs index 55d97082..a26ec543 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,9 @@ //! Main module defining the lexer and parser. use crate::any::{Dynamic, Union}; -use crate::engine::{Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; +use crate::engine::{ + Engine, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT, +}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::fn_native::{FnPtr, Shared}; use crate::module::{Module, ModuleRef}; @@ -2318,6 +2320,17 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result + { + return Err(PERR::BadInput(format!( + "'{}' should not be called in method style. Try {}(...);", + (x.0).0, + (x.0).0 + )) + .into_err((x.0).3)); + } // lhs.func!(...) (_, Expr::FnCall(x)) if (x.0).2 => { return Err(PERR::MalformedCapture( From aa6d00f25370b07029b5af5368532264d8343391 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 17 Oct 2020 20:01:31 +0800 Subject: [PATCH 06/16] Fix bug. --- src/fn_call.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/fn_call.rs b/src/fn_call.rs index 43cbb9bb..39fbbd7a 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -526,14 +526,18 @@ impl Engine { // Fn/eval - reaching this point it must be a method-style call, mostly like redirected // by a function pointer so it isn't caught at parse time. - KEYWORD_FN_PTR | KEYWORD_EVAL if args.len() == 1 => EvalAltResult::ErrorRuntime( - format!( - "'{}' should not be called in method style. Try {}(...);", - fn_name, fn_name - ), - Position::none(), - ) - .into(), + KEYWORD_FN_PTR | KEYWORD_EVAL + if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => + { + EvalAltResult::ErrorRuntime( + format!( + "'{}' should not be called in method style. Try {}(...);", + fn_name, fn_name + ), + Position::none(), + ) + .into() + } // Script-like function found #[cfg(not(feature = "no_function"))] From 58c820785b77d44d47ca011c06586ec65fc14d2f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 18 Oct 2020 13:18:12 +0800 Subject: [PATCH 07/16] Add drain and retain for arrays. --- doc/src/language/arrays.md | 14 +++- src/packages/array_basic.rs | 125 ++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) diff --git a/doc/src/language/arrays.md b/doc/src/language/arrays.md index d687e002..56706d86 100644 --- a/doc/src/language/arrays.md +++ b/doc/src/language/arrays.md @@ -48,8 +48,12 @@ The following methods (mostly defined in the [`BasicArrayPackage`][packages] but | `clear` | _none_ | empties the array | | `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | | `chop` | target length | cuts off the head of the array, leaving the tail at exactly a specified length | +| `drain` | 1) [function pointer] to predicate (usually a [closure]),
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | removes all items (returning them) that return `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | +| `drain` | 1) start position, beginning if < 0, end if > length,
2) number of items to remove, none if < 0 | removes a portion of the array, returning the removed items (not in original order) | +| `retain` | 1) [function pointer] to predicate (usually a [closure]),
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | removes all items (returning them) that do not return `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | +| `retain` | 1) start position, beginning if < 0, end if > length,
2) number of items to retain, none if < 0 | retains a portion of the array, removes all other items and returning them (not in original order) | | `splice` | 1) start position, beginning if < 0, end if > length,
2) number of items to remove, none if < 0,
3) array to insert | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) | -| `filter` | [function pointer] to predicate (usually a [closure]) | constructs a new array with all items that returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | +| `filter` | [function pointer] to predicate (usually a [closure]) | constructs a new array with all items that return `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | | `map` | [function pointer] to conversion function (usually a [closure]) | constructs a new array with all items mapped to the result of applying the conversion function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | | `reduce` | 1) [function pointer] to accumulator function (usually a [closure]),
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | reduces the array into a single value via the accumulator function:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
3rd parameter: _(optional)_ offset index | | `reduce_rev` | 1) [function pointer] to accumulator function (usually a [closure]),
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | reduces the array (in reverse order) into a single value via the accumulator function:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
3rd parameter: _(optional)_ offset index | @@ -199,4 +203,12 @@ a.splice(1, 1, [1, 3, 2]); // a == [42, 1, 3, 2, 99] a.extract(1, 3); // returns [1, 3, 2] a.sort(|x, y| x - y); // a == [1, 2, 3, 42, 99] + +a.drain(|v| v <= 1); // a == [2, 3, 42, 99] + +a.drain(|v, i| i >= 3); // a == [2, 3, 42] + +a.retain(|v| v > 10); // a == [42] + +a.retain(|v, i| i > 0); // a == [] ``` diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 826a2196..5fa5b103 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -73,6 +73,8 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { 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); @@ -191,6 +193,49 @@ mod array_functions { list[start..].iter().cloned().collect() } + #[rhai_fn(name = "drain")] + pub fn drain_range(list: &mut Array, start: INT, len: INT) -> Array { + let start = if start < 0 { + 0 + } else if start as usize >= list.len() { + list.len() - 1 + } else { + start as usize + }; + + let len = if len < 0 { + 0 + } else if len as usize > list.len() - start { + list.len() - start + } else { + len as usize + }; + + list.drain(start..start + len - 1).collect() + } + #[rhai_fn(name = "retain")] + pub fn retain_range(list: &mut Array, start: INT, len: INT) -> Array { + let start = if start < 0 { + 0 + } else if start as usize >= list.len() { + list.len() - 1 + } else { + start as usize + }; + + let len = if len < 0 { + 0 + } else if len as usize > list.len() - start { + list.len() - start + } else { + len as usize + }; + + let mut drained = list.drain(start + len..).collect::(); + drained.extend(list.drain(..start)); + + drained + } } fn pad( @@ -552,6 +597,86 @@ fn sort( Ok(().into()) } +fn drain( + engine: &Engine, + lib: &Module, + 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(engine, lib, None, [list[i].clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(_, _) => { + filter.call_dynamic(engine, lib, 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( + engine: &Engine, + lib: &Module, + 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(engine, lib, None, [list[i].clone()]) + .or_else(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(_, _) => { + filter.call_dynamic(engine, lib, 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"))] From 82e6dd446ad30bf470af99a281632a175bddc8d1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 18 Oct 2020 17:02:17 +0800 Subject: [PATCH 08/16] Encapsulate register_fn_raw parameters into NativeCallContext. --- RELEASES.md | 1 + doc/src/language/fn-ptr.md | 58 ++++++++++++--- doc/src/rust/register-raw.md | 25 ++++--- src/api.rs | 13 ++-- src/engine.rs | 140 ++++++++++++++++++++++------------- src/fn_call.rs | 8 +- src/fn_native.rs | 48 ++++++++++-- src/fn_register.rs | 5 +- src/lib.rs | 2 +- src/module/mod.rs | 75 ++++++++++--------- src/packages/array_basic.rs | 129 ++++++++++++-------------------- src/packages/fn_basic.rs | 4 + src/packages/iter_basic.rs | 6 +- src/packages/string_more.rs | 13 ++-- src/parser.rs | 35 +++++---- src/settings.rs | 46 ++++++------ src/syntax.rs | 6 +- src/token.rs | 2 +- tests/call_fn.rs | 4 +- tests/closures.rs | 13 ++-- 20 files changed, 368 insertions(+), 265 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 9a7f9d08..1dc6c67d 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -10,6 +10,7 @@ Breaking changes * `EvalAltResult::ErrorReadingScriptFile` is removed in favor of the new `EvalAltResult::ErrorSystem`. * `EvalAltResult::ErrorLoopBreak` is renamed to `EvalAltResult::LoopBreak`. +* `Engine::register_raw_fn` function signature has changed. Enhancements diff --git a/doc/src/language/fn-ptr.md b/doc/src/language/fn-ptr.md index 9e22eb70..5901c23b 100644 --- a/doc/src/language/fn-ptr.md +++ b/doc/src/language/fn-ptr.md @@ -17,10 +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 | -| `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]? | +| `call` | _arguments_ | calls the function matching the function pointer's name with the _arguments_ | Examples @@ -186,16 +187,15 @@ must be used to register the function. Essentially, use the low-level `Engine::register_raw_fn` method to register the function. `FnPtr::call_dynamic` is used to actually call the function pointer, passing to it the -current scripting [`Engine`], collection of script-defined functions, the `this` pointer, -and other necessary arguments. +current _native call context_, the `this` pointer, and other necessary arguments. ```rust -use rhai::{Engine, Module, Dynamic, FnPtr}; +use rhai::{Engine, Module, Dynamic, FnPtr, NativeCallContext}; let mut engine = Engine::new(); // Define Rust function in required low-level API signature -fn call_fn_ptr_with_value(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) +fn call_fn_ptr_with_value(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { // 'args' is guaranteed to contain enough arguments of the correct types @@ -205,7 +205,7 @@ fn call_fn_ptr_with_value(engine: &Engine, lib: &Module, args: &mut [&mut Dynami // Use 'FnPtr::call_dynamic' to call the function pointer. // Beware, private script-defined functions will not be found. - fp.call_dynamic(engine, lib, Some(this_ptr), [value]) + fp.call_dynamic(context, Some(this_ptr), [value]) } // Register a Rust function using the low-level API @@ -218,3 +218,43 @@ engine.register_raw_fn("super_call", call_fn_ptr_with_value ); ``` + + +`NativeCallContext` +------------------ + +`FnPtr::call_dynamic` takes a parameter of type `NativeCallContext` which holds the _native call context_ +of the particular call to a registered Rust function. + +This type is normally provided by the [`Engine`] (e.g. when using `Engine::register_fn_raw`(../rust/register-raw.md)). +However, it may also be manually constructed from a tuple: + +```rust +use rhai::{Engine, FnPtr}; + +let engine = Engine::new(); + +// Compile script to AST +let mut ast = engine.compile( + r#" + let test = "hello"; + |x| test + x // this creates an closure + "#, +)?; + +// Save the closure together with captured variables +let fn_ptr = engine.eval_ast::(&ast)?; + +// Get rid of the script, retaining only functions +ast.retain_functions(|_, _, _| true); + +// Create native call context via a tuple containing the Engine and the +// set of script-defined functions (within the AST) +let context = (&engine, ast.as_ref()).into(); + +// 'f' captures: the engine, the AST, and the closure +let f = move |x: i64| fn_ptr.call_dynamic(context, None, [x.into()]); + +// 'f' can be called like a normal function +let result = f(42)?; +``` diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index 460df172..8a351c2a 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -18,8 +18,9 @@ The `Engine::register_raw_fn` method is marked _volatile_, meaning that it may b If this is acceptable, then using this method to register a Rust function opens up more opportunities. -In particular, a reference to the current `Engine` instance is passed as an argument so the Rust function -can also use `Engine` facilities (like evaluating a script). +In particular, a the current _native call context_ (in form of the `NativeCallContext` type) is passed as an argument. +`NativeCallContext` exposes the current [`Engine`], among others, so the Rust function can also use [`Engine`] facilities +(such as evaluating a script). ```rust engine.register_raw_fn( @@ -28,7 +29,7 @@ engine.register_raw_fn( std::any::TypeId::of::(), // type of first parameter std::any::TypeId::of::() // type of second parameter ], - |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { // fixed function signature + |context, args| { // fixed function signature // Arguments are guaranteed to be correct in number and of the correct types. // But remember this is Rust, so you can keep only one mutable reference at any one time! @@ -59,17 +60,19 @@ Function Signature The function signature passed to `Engine::register_raw_fn` takes the following form: -> `Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic])` -> `-> Result> + 'static` +> `Fn(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> + 'static` where: * `T: Variant + Clone` - return type of the function. -* `engine: &Engine` - the current [`Engine`], with all configurations and settings. +* `context: NativeCallContext` - the current _native call context_, which exposes the following: -* `lib: &Module` - the current global library of script-defined functions, as a [`Module`]. - This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`]. + * `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`]. + + * `context.namespace(): &Module` - the global namespace of script-defined functions, as a [`Module`]. * `args: &mut [&mut Dynamic]` - a slice containing `&mut` references to [`Dynamic`] values. The slice is guaranteed to contain enough arguments _of the correct types_. @@ -106,7 +109,7 @@ then calls it within the same [`Engine`]. This way, a _callback_ function can b to a native Rust function. ```rust -use rhai::{Engine, Module, Dynamic, FnPtr}; +use rhai::{Engine, FnPtr}; let mut engine = Engine::new(); @@ -118,7 +121,7 @@ engine.register_raw_fn( std::any::TypeId::of::(), std::any::TypeId::of::(), ], - |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + |context, args| { // 'args' is guaranteed to contain enough arguments of the correct types let fp = std::mem::take(args[1]).cast::(); // 2nd argument - function pointer @@ -127,7 +130,7 @@ engine.register_raw_fn( // Use 'FnPtr::call_dynamic' to call the function pointer. // Beware, private script-defined functions will not be found. - fp.call_dynamic(engine, lib, Some(this_ptr), [value]) + fp.call_dynamic(context, Some(this_ptr), [value]) }, ); diff --git a/src/api.rs b/src/api.rs index 87699672..031e06b7 100644 --- a/src/api.rs +++ b/src/api.rs @@ -3,8 +3,7 @@ use crate::any::{Dynamic, Variant}; use crate::engine::{Engine, EvalContext, Imports, State}; use crate::error::ParseError; -use crate::fn_native::SendSync; -use crate::module::{FuncReturn, Module}; +use crate::fn_native::{NativeCallContext, SendSync}; use crate::optimize::OptimizationLevel; use crate::parser::AST; use crate::result::EvalAltResult; @@ -28,7 +27,7 @@ use crate::{ use crate::fn_register::{RegisterFn, RegisterResultFn}; #[cfg(not(feature = "no_function"))] -use crate::{fn_args::FuncArgs, fn_call::ensure_no_data_race, StaticVec}; +use crate::{fn_args::FuncArgs, fn_call::ensure_no_data_race, module::Module, StaticVec}; #[cfg(not(feature = "no_optimize"))] use crate::optimize::optimize_into_ast; @@ -69,7 +68,9 @@ impl Engine { &mut self, name: &str, arg_types: &[TypeId], - func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, + func: impl Fn(NativeCallContext, &mut [&mut Dynamic]) -> Result> + + SendSync + + 'static, ) -> &mut Self { self.global_module.set_raw_fn(name, arg_types, func); self @@ -1622,7 +1623,7 @@ impl Engine { name: &str, mut this_ptr: Option<&mut Dynamic>, mut arg_values: impl AsMut<[Dynamic]>, - ) -> FuncReturn { + ) -> Result> { let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect(); self.call_fn_dynamic_raw(scope, lib.as_ref(), name, &mut this_ptr, args.as_mut()) @@ -1645,7 +1646,7 @@ impl Engine { name: &str, this_ptr: &mut Option<&mut Dynamic>, args: &mut [&mut Dynamic], - ) -> FuncReturn { + ) -> Result> { let fn_def = lib .get_script_fn(name, args.len(), true) .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::none()))?; diff --git a/src/engine.rs b/src/engine.rs index 3cb249d6..c570e0c6 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,12 +1,12 @@ //! Main module defining the script evaluation `Engine`. -use crate::any::{map_std_type_name, Dynamic, Union}; +use crate::any::{map_std_type_name, Dynamic, Union, Variant}; use crate::fn_call::run_builtin_op_assignment; use crate::fn_native::{Callback, FnPtr, OnVarCallback}; use crate::module::{Module, ModuleRef}; use crate::optimize::OptimizationLevel; use crate::packages::{Package, PackagesCollection, StandardPackage}; -use crate::parser::{Expr, ReturnType, Stmt, INT}; +use crate::parser::{Expr, ReturnType, Stmt}; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -14,8 +14,8 @@ use crate::syntax::CustomSyntax; use crate::token::Position; use crate::{calc_fn_hash, StaticVec}; -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] -use crate::any::Variant; +#[cfg(not(feature = "no_index"))] +use crate::parser::INT; #[cfg(not(feature = "no_module"))] use crate::module::ModuleResolver; @@ -80,6 +80,7 @@ pub const MAX_CALL_STACK_DEPTH: usize = 12; #[cfg(debug_assertions)] pub const MAX_EXPR_DEPTH: usize = 32; #[cfg(not(feature = "unchecked"))] +#[cfg(not(feature = "no_function"))] #[cfg(debug_assertions)] pub const MAX_FUNCTION_EXPR_DEPTH: usize = 16; @@ -90,6 +91,7 @@ pub const MAX_CALL_STACK_DEPTH: usize = 128; #[cfg(not(debug_assertions))] pub const MAX_EXPR_DEPTH: usize = 128; #[cfg(not(feature = "unchecked"))] +#[cfg(not(feature = "no_function"))] #[cfg(not(debug_assertions))] pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32; @@ -144,6 +146,7 @@ impl IndexChainValue { /// # Panics /// /// Panics if not `IndexChainValue::Value`. + #[cfg(not(feature = "no_index"))] pub fn as_value(self) -> Dynamic { match self { Self::None | Self::FnCallArgs(_) => panic!("expecting IndexChainValue::Value"), @@ -155,6 +158,7 @@ impl IndexChainValue { /// # Panics /// /// Panics if not `IndexChainValue::FnCallArgs`. + #[cfg(not(feature = "no_object"))] pub fn as_fn_call_args(self) -> StaticVec { match self { Self::None | Self::Value(_) => panic!("expecting IndexChainValue::FnCallArgs"), @@ -178,7 +182,6 @@ impl From for IndexChainValue { } /// A type that encapsulates a mutation target for an expression with side effects. -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[derive(Debug)] pub enum Target<'a> { /// The target is a mutable reference to a `Dynamic` value somewhere. @@ -196,7 +199,6 @@ pub enum Target<'a> { StringChar(&'a mut Dynamic, usize, Dynamic), } -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] impl<'a> Target<'a> { /// Is the `Target` a reference pointing to other data? #[allow(dead_code)] @@ -306,6 +308,7 @@ impl<'a> Target<'a> { } } /// Update the value of the `Target`. + #[cfg(any(not(feature = "no_object"), not(feature = "no_index")))] pub fn set_value( &mut self, new_val: (Dynamic, Position), @@ -348,7 +351,6 @@ impl<'a> Target<'a> { } } -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] impl<'a> From<&'a mut Dynamic> for Target<'a> { #[inline(always)] fn from(value: &'a mut Dynamic) -> Self { @@ -364,7 +366,6 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> { } } -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] impl> From for Target<'_> { #[inline(always)] fn from(value: T) -> Self { @@ -414,35 +415,44 @@ pub struct Limits { /// /// Defaults to 16 for debug builds and 128 for non-debug builds. pub max_call_stack_depth: usize, - /// Maximum depth of statements/expressions at global level. + /// Maximum depth of statements/expressions at global level (0 = unlimited). pub max_expr_depth: usize, - /// Maximum depth of statements/expressions in functions. + /// Maximum depth of statements/expressions in functions (0 = unlimited). + /// Not available under `no_function`. + #[cfg(not(feature = "no_function"))] pub max_function_expr_depth: usize, - /// Maximum number of operations allowed to run. + /// Maximum number of operations allowed to run (0 = unlimited). pub max_operations: u64, /// Maximum number of modules allowed to load. + /// Not available under `no_module`. + #[cfg(not(feature = "no_modules"))] pub max_modules: usize, - /// Maximum length of a string. + /// Maximum length of a string (0 = unlimited). pub max_string_size: usize, - /// Maximum length of an array. + /// Maximum length of an array (0 = unlimited). + /// Not available under `no_index`. + #[cfg(not(feature = "no_index"))] pub max_array_size: usize, - /// Maximum number of properties in a map. + /// Maximum number of properties in a map (0 = unlimited). + /// Not available under `no_object`. + #[cfg(not(feature = "no_object"))] pub max_map_size: usize, } /// Context of a script evaluation process. #[derive(Debug)] pub struct EvalContext<'e, 'a, 's, 'm, 't, 'd: 't> { - pub(crate) engine: &'e Engine, + engine: &'e Engine, pub(crate) mods: &'a mut Imports, pub(crate) state: &'s mut State, - pub(crate) lib: &'m Module, + lib: &'m Module, pub(crate) this_ptr: &'t mut Option<&'d mut Dynamic>, - pub(crate) level: usize, + level: usize, } impl<'e, 'a, 's, 'm, 't, 'd> EvalContext<'e, 'a, 's, 'm, 't, 'd> { /// The current `Engine`. + #[inline(always)] pub fn engine(&self) -> &'e Engine { self.engine } @@ -450,14 +460,17 @@ impl<'e, 'a, 's, 'm, 't, 'd> EvalContext<'e, 'a, 's, 'm, 't, 'd> { /// Available under the `internals` feature only. #[cfg(feature = "internals")] #[cfg(not(feature = "no_modules"))] + #[inline(always)] pub fn imports(&self) -> &'a Imports { self.mods } /// The global namespace containing definition of all script-defined functions. + #[inline(always)] pub fn namespace(&self) -> &'m Module { self.lib } /// The current nesting level of function calls. + #[inline(always)] pub fn call_level(&self) -> usize { self.level } @@ -516,7 +529,7 @@ pub struct Engine { /// Max limits. #[cfg(not(feature = "unchecked"))] - pub(crate) limits: Limits, + pub(crate) limits_set: Limits, } impl fmt::Debug for Engine { @@ -662,14 +675,18 @@ impl Engine { }, #[cfg(not(feature = "unchecked"))] - limits: Limits { + limits_set: Limits { max_call_stack_depth: MAX_CALL_STACK_DEPTH, max_expr_depth: MAX_EXPR_DEPTH, + #[cfg(not(feature = "no_function"))] max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH, max_operations: 0, + #[cfg(not(feature = "no_module"))] max_modules: usize::MAX, max_string_size: 0, + #[cfg(not(feature = "no_index"))] max_array_size: 0, + #[cfg(not(feature = "no_object"))] max_map_size: 0, }, }; @@ -710,14 +727,18 @@ impl Engine { }, #[cfg(not(feature = "unchecked"))] - limits: Limits { + limits_set: Limits { max_call_stack_depth: MAX_CALL_STACK_DEPTH, max_expr_depth: MAX_EXPR_DEPTH, + #[cfg(not(feature = "no_function"))] max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH, max_operations: 0, + #[cfg(not(feature = "no_module"))] max_modules: usize::MAX, max_string_size: 0, + #[cfg(not(feature = "no_index"))] max_array_size: 0, + #[cfg(not(feature = "no_object"))] max_map_size: 0, }, } @@ -1285,7 +1306,7 @@ impl Engine { ) -> Result, Box> { self.inc_operations(state)?; - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + #[cfg(not(feature = "no_index"))] let is_ref = target.is_ref(); let val = target.as_mut(); @@ -1542,7 +1563,7 @@ impl Engine { if func.is_plugin_fn() { func.get_plugin_fn().call(args)?; } else { - func.get_native_fn()(self, lib, args)?; + func.get_native_fn()((self, lib).into(), args)?; } } // Built-in op-assignment function @@ -1954,7 +1975,7 @@ impl Engine { // Guard against too many modules #[cfg(not(feature = "unchecked"))] - if state.modules >= self.limits.max_modules { + if state.modules >= self.max_modules() { return EvalAltResult::ErrorTooManyModules(*_pos).into(); } @@ -2042,8 +2063,19 @@ impl Engine { result: Result>, ) -> Result> { // If no data size limits, just return - if self.limits.max_string_size + self.limits.max_array_size + self.limits.max_map_size == 0 + let mut total = 0; + + total += self.max_string_size(); + #[cfg(not(feature = "no_index"))] { + total += self.max_array_size(); + } + #[cfg(not(feature = "no_object"))] + { + total += self.max_map_size(); + } + + if total == 0 { return result; } @@ -2103,46 +2135,52 @@ impl Engine { // Simply return all errors Err(_) => return result, // String with limit - Ok(Dynamic(Union::Str(_))) if self.limits.max_string_size > 0 => (), + Ok(Dynamic(Union::Str(_))) if self.max_string_size() > 0 => (), // Array with limit #[cfg(not(feature = "no_index"))] - Ok(Dynamic(Union::Array(_))) if self.limits.max_array_size > 0 => (), + Ok(Dynamic(Union::Array(_))) if self.max_array_size() > 0 => (), // Map with limit #[cfg(not(feature = "no_object"))] - Ok(Dynamic(Union::Map(_))) if self.limits.max_map_size > 0 => (), + Ok(Dynamic(Union::Map(_))) if self.max_map_size() > 0 => (), // Everything else is simply returned Ok(_) => return result, }; - let (arr, map, s) = calc_size(result.as_ref().unwrap()); + let (_arr, _map, s) = calc_size(result.as_ref().unwrap()); - if s > self.limits.max_string_size { - EvalAltResult::ErrorDataTooLarge( + if s > self.max_string_size() { + return EvalAltResult::ErrorDataTooLarge( "Length of string".to_string(), - self.limits.max_string_size, + self.max_string_size(), s, Position::none(), ) - .into() - } else if arr > self.limits.max_array_size { - EvalAltResult::ErrorDataTooLarge( - "Size of array".to_string(), - self.limits.max_array_size, - arr, - Position::none(), - ) - .into() - } else if map > self.limits.max_map_size { - EvalAltResult::ErrorDataTooLarge( - "Number of properties in object map".to_string(), - self.limits.max_map_size, - map, - Position::none(), - ) - .into() - } else { - result + .into(); } + + #[cfg(not(feature = "no_index"))] + if _arr > self.max_array_size() { + return EvalAltResult::ErrorDataTooLarge( + "Size of array".to_string(), + self.max_array_size(), + _arr, + Position::none(), + ) + .into(); + } + + #[cfg(not(feature = "no_object"))] + if _map > self.max_map_size() { + return EvalAltResult::ErrorDataTooLarge( + "Number of properties in object map".to_string(), + self.max_map_size(), + _map, + Position::none(), + ) + .into(); + } + + result } /// Check if the number of operations stay within limit. @@ -2152,7 +2190,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] // Guard against too many operations - if self.limits.max_operations > 0 && state.operations > self.limits.max_operations { + if self.max_operations() > 0 && state.operations > self.max_operations() { return EvalAltResult::ErrorTooManyOperations(Position::none()).into(); } diff --git a/src/fn_call.rs b/src/fn_call.rs index 39fbbd7a..b16d35db 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -208,7 +208,7 @@ impl Engine { let result = if func.is_plugin_fn() { func.get_plugin_fn().call(args) } else { - func.get_native_fn()(self, lib, args) + func.get_native_fn()((self, lib).into(), args) }; // Restore the original reference @@ -356,7 +356,7 @@ impl Engine { // Check for stack overflow #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "unchecked"))] - if level > self.limits.max_call_stack_depth { + if level > self.max_call_levels() { return Err(Box::new( EvalAltResult::ErrorStackOverflow(Position::none()), )); @@ -648,7 +648,7 @@ impl Engine { // Check for stack overflow #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "unchecked"))] - if _level > self.limits.max_call_stack_depth { + if _level > self.max_call_levels() { return Err(Box::new( EvalAltResult::ErrorStackOverflow(Position::none()), )); @@ -1196,7 +1196,7 @@ impl Engine { } } - f.get_native_fn()(self, lib, args.as_mut()) + f.get_native_fn()((self, lib).into(), args.as_mut()) } Some(_) => unreachable!(), None if def_val.is_some() => Ok(def_val.unwrap().into()), diff --git a/src/fn_native.rs b/src/fn_native.rs index 7ced25a9..69790b8d 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,7 +1,7 @@ //! Module defining interfaces to native-Rust functions. use crate::any::Dynamic; -use crate::engine::{Engine, EvalContext}; +use crate::engine::{Engine, EvalContext, FN_ANONYMOUS}; use crate::module::Module; use crate::parser::{FnAccess, ScriptFnDef}; use crate::plugin::PluginFunction; @@ -46,6 +46,35 @@ pub type Locked = RefCell; #[cfg(feature = "sync")] pub type Locked = RwLock; +/// Context of a script evaluation process. +#[derive(Debug, Copy, Clone)] +pub struct NativeCallContext<'e, 'm> { + engine: &'e Engine, + lib: &'m Module, +} + +impl<'e, 'm> From<(&'e Engine, &'m Module)> for NativeCallContext<'e, 'm> { + fn from(value: (&'e Engine, &'m Module)) -> Self { + Self { + engine: value.0, + lib: value.1, + } + } +} + +impl<'e, 'm> NativeCallContext<'e, 'm> { + /// The current `Engine`. + #[inline(always)] + pub fn engine(&self) -> &'e Engine { + self.engine + } + /// The global namespace containing definition of all script-defined functions. + #[inline(always)] + pub fn namespace(&self) -> &'m Module { + self.lib + } +} + /// Consume a `Shared` resource and return a mutable reference to the wrapped value. /// If the resource is shared (i.e. has other outstanding references), a cloned copy is used. pub fn shared_make_mut(value: &mut Shared) -> &mut T { @@ -108,6 +137,11 @@ impl FnPtr { pub fn curry(&self) -> &[Dynamic] { self.1.as_ref() } + /// Does this function pointer refer to an anonymous function? + #[inline(always)] + pub fn is_anonymous(&self) -> bool { + self.0.starts_with(FN_ANONYMOUS) + } /// Call the function pointer with curried arguments (if any). /// @@ -121,8 +155,7 @@ impl FnPtr { /// clone them _before_ calling this function. pub fn call_dynamic( &self, - engine: &Engine, - lib: impl AsRef, + context: NativeCallContext, this_ptr: Option<&mut Dynamic>, mut arg_values: impl AsMut<[Dynamic]>, ) -> Result> { @@ -144,10 +177,11 @@ impl FnPtr { args.insert(0, obj); } - engine + context + .engine() .exec_fn_call( &mut Default::default(), - lib.as_ref(), + context.namespace(), fn_name, hash_script, args.as_mut(), @@ -204,11 +238,11 @@ impl TryFrom<&str> for FnPtr { /// A general function trail object. #[cfg(not(feature = "sync"))] -pub type FnAny = dyn Fn(&Engine, &Module, &mut FnCallArgs) -> Result>; +pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> Result>; /// A general function trail object. #[cfg(feature = "sync")] pub type FnAny = - dyn Fn(&Engine, &Module, &mut FnCallArgs) -> Result> + Send + Sync; + dyn Fn(NativeCallContext, &mut FnCallArgs) -> Result> + Send + Sync; /// A standard function that gets an iterator from a type. pub type IteratorFn = fn(Dynamic) -> Box>; diff --git a/src/fn_register.rs b/src/fn_register.rs index d8bbb09a..f394b348 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -4,8 +4,7 @@ use crate::any::{Dynamic, DynamicWriteLock, Variant}; use crate::engine::Engine; -use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; -use crate::module::Module; +use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, NativeCallContext, SendSync}; use crate::parser::FnAccess; use crate::r#unsafe::unsafe_cast_box; use crate::result::EvalAltResult; @@ -127,7 +126,7 @@ macro_rules! make_func { // ^ dereferencing function // ^ argument reference expression(like A, *B, &mut C etc) - Box::new(move |_: &Engine, _: &Module, args: &mut FnCallArgs| { + Box::new(move |_: NativeCallContext, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); diff --git a/src/lib.rs b/src/lib.rs index dd277a73..e2973ae0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,7 +85,7 @@ mod utils; pub use any::Dynamic; pub use engine::{Engine, EvalContext}; pub use error::{ParseError, ParseErrorType}; -pub use fn_native::FnPtr; +pub use fn_native::{FnPtr, NativeCallContext}; pub use fn_register::{RegisterFn, RegisterResultFn}; pub use module::Module; pub use parser::{ImmutableString, AST, INT}; diff --git a/src/module/mod.rs b/src/module/mod.rs index d7cc40d9..dcdfe756 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -2,7 +2,7 @@ use crate::any::{Dynamic, Variant}; use crate::engine::Engine; -use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync}; +use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, NativeCallContext, SendSync}; use crate::fn_register::by_value as cast_arg; use crate::parser::FnAccess; use crate::result::EvalAltResult; @@ -24,7 +24,11 @@ use crate::{ use crate::engine::{Array, FN_IDX_GET, FN_IDX_SET}; #[cfg(not(feature = "no_object"))] -use crate::engine::{make_getter, make_setter, Map}; +use crate::engine::{make_getter, make_setter}; + +#[cfg(not(feature = "no_index"))] +#[cfg(not(feature = "no_object"))] +use crate::engine::Map; use crate::stdlib::{ any::TypeId, @@ -38,9 +42,6 @@ use crate::stdlib::{ vec::Vec, }; -/// Return type of module-level Rust function. -pub type FuncReturn = Result>; - pub type FuncInfo = ( String, FnAccess, @@ -536,10 +537,12 @@ impl Module { &mut self, name: impl Into, arg_types: &[TypeId], - func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, + func: impl Fn(NativeCallContext, &mut [&mut Dynamic]) -> Result> + + SendSync + + 'static, ) -> u64 { - let f = move |engine: &Engine, lib: &Module, args: &mut FnCallArgs| { - func(engine, lib, args).map(Dynamic::from) + let f = move |context: NativeCallContext, args: &mut [&mut Dynamic]| { + func(context, args).map(Dynamic::from) }; self.set_fn( name, @@ -566,9 +569,9 @@ impl Module { pub fn set_fn_0( &mut self, name: impl Into, - func: impl Fn() -> FuncReturn + SendSync + 'static, + func: impl Fn() -> Result> + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, _: &Module, _: &mut FnCallArgs| func().map(Dynamic::from); + let f = move |_: NativeCallContext, _: &mut FnCallArgs| func().map(Dynamic::from); let arg_types = []; self.set_fn( name, @@ -595,9 +598,9 @@ impl Module { pub fn set_fn_1( &mut self, name: impl Into, - func: impl Fn(A) -> FuncReturn + SendSync + 'static, + func: impl Fn(A) -> Result> + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { + let f = move |_: NativeCallContext, args: &mut FnCallArgs| { func(cast_arg::(&mut args[0])).map(Dynamic::from) }; let arg_types = [TypeId::of::()]; @@ -626,9 +629,9 @@ impl Module { pub fn set_fn_1_mut( &mut self, name: impl Into, - func: impl Fn(&mut A) -> FuncReturn + SendSync + 'static, + func: impl Fn(&mut A) -> Result> + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { + let f = move |_: NativeCallContext, args: &mut FnCallArgs| { func(&mut args[0].write_lock::().unwrap()).map(Dynamic::from) }; let arg_types = [TypeId::of::()]; @@ -658,7 +661,7 @@ impl Module { pub fn set_getter_fn( &mut self, name: impl Into, - func: impl Fn(&mut A) -> FuncReturn + SendSync + 'static, + func: impl Fn(&mut A) -> Result> + SendSync + 'static, ) -> u64 { self.set_fn_1_mut(make_getter(&name.into()), func) } @@ -682,9 +685,9 @@ impl Module { pub fn set_fn_2( &mut self, name: impl Into, - func: impl Fn(A, B) -> FuncReturn + SendSync + 'static, + func: impl Fn(A, B) -> Result> + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { + let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let a = cast_arg::(&mut args[0]); let b = cast_arg::(&mut args[1]); @@ -719,9 +722,9 @@ impl Module { pub fn set_fn_2_mut( &mut self, name: impl Into, - func: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static, + func: impl Fn(&mut A, B) -> Result> + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { + let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let b = cast_arg::(&mut args[1]); let a = &mut args[0].write_lock::().unwrap(); @@ -758,7 +761,7 @@ impl Module { pub fn set_setter_fn( &mut self, name: impl Into, - func: impl Fn(&mut A, B) -> FuncReturn<()> + SendSync + 'static, + func: impl Fn(&mut A, B) -> Result<(), Box> + SendSync + 'static, ) -> u64 { self.set_fn_2_mut(make_setter(&name.into()), func) } @@ -788,7 +791,7 @@ impl Module { #[inline] pub fn set_indexer_get_fn( &mut self, - func: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static, + func: impl Fn(&mut A, B) -> Result> + SendSync + 'static, ) -> u64 { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -831,9 +834,9 @@ impl Module { >( &mut self, name: impl Into, - func: impl Fn(A, B, C) -> FuncReturn + SendSync + 'static, + func: impl Fn(A, B, C) -> Result> + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { + let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let a = cast_arg::(&mut args[0]); let b = cast_arg::(&mut args[1]); let c = cast_arg::(&mut args[2]); @@ -874,9 +877,9 @@ impl Module { >( &mut self, name: impl Into, - func: impl Fn(&mut A, B, C) -> FuncReturn + SendSync + 'static, + func: impl Fn(&mut A, B, C) -> Result> + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { + let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let b = cast_arg::(&mut args[2]); let c = cast_arg::(&mut args[3]); let a = &mut args[0].write_lock::().unwrap(); @@ -918,7 +921,7 @@ impl Module { #[inline] pub fn set_indexer_set_fn( &mut self, - func: impl Fn(&mut A, B, C) -> FuncReturn<()> + SendSync + 'static, + func: impl Fn(&mut A, B, C) -> Result<(), Box> + SendSync + 'static, ) -> u64 { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -934,7 +937,7 @@ impl Module { panic!("Cannot register indexer for strings."); } - let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { + let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let b = cast_arg::(&mut args[1]); let c = cast_arg::(&mut args[2]); let a = &mut args[0].write_lock::().unwrap(); @@ -982,8 +985,8 @@ impl Module { #[inline] pub fn set_indexer_get_set_fn( &mut self, - getter: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static, - setter: impl Fn(&mut A, B, T) -> FuncReturn<()> + SendSync + 'static, + getter: impl Fn(&mut A, B) -> Result> + SendSync + 'static, + setter: impl Fn(&mut A, B, T) -> Result<(), Box> + SendSync + 'static, ) -> (u64, u64) { ( self.set_indexer_get_fn(getter), @@ -1016,9 +1019,9 @@ impl Module { >( &mut self, name: impl Into, - func: impl Fn(A, B, C, D) -> FuncReturn + SendSync + 'static, + func: impl Fn(A, B, C, D) -> Result> + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { + let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let a = cast_arg::(&mut args[0]); let b = cast_arg::(&mut args[1]); let c = cast_arg::(&mut args[2]); @@ -1066,9 +1069,9 @@ impl Module { >( &mut self, name: impl Into, - func: impl Fn(&mut A, B, C, D) -> FuncReturn + SendSync + 'static, + func: impl Fn(&mut A, B, C, D) -> Result> + SendSync + 'static, ) -> u64 { - let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { + let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let b = cast_arg::(&mut args[1]); let c = cast_arg::(&mut args[2]); let d = cast_arg::(&mut args[3]); @@ -1340,7 +1343,11 @@ impl Module { /// # } /// ``` #[cfg(not(feature = "no_module"))] - pub fn eval_ast_as_new(mut scope: Scope, ast: &AST, engine: &Engine) -> FuncReturn { + pub fn eval_ast_as_new( + mut scope: Scope, + ast: &AST, + engine: &Engine, + ) -> Result> { let mut mods = Imports::new(); // Run the script diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 5fa5b103..b42277e7 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -3,8 +3,8 @@ use crate::any::{Dynamic, Variant}; use crate::def_package; -use crate::engine::{Array, Engine}; -use crate::fn_native::FnPtr; +use crate::engine::Array; +use crate::fn_native::{FnPtr, NativeCallContext}; use crate::parser::{ImmutableString, INT}; use crate::plugin::*; use crate::result::EvalAltResult; @@ -239,21 +239,20 @@ mod array_functions { } fn pad( - _engine: &Engine, - _: &Module, + _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 _engine.limits.max_array_size > 0 + if _context.engine().max_array_size() > 0 && len > 0 - && (len as usize) > _engine.limits.max_array_size + && (len as usize) > _context.engine().max_array_size() { return EvalAltResult::ErrorDataTooLarge( "Size of array".to_string(), - _engine.limits.max_array_size, + _context.engine().max_array_size(), len as usize, Position::none(), ) @@ -271,11 +270,7 @@ fn pad( Ok(()) } -fn map( - engine: &Engine, - lib: &Module, - args: &mut [&mut Dynamic], -) -> Result> { +fn map(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { let list = args[0].read_lock::().unwrap(); let mapper = args[1].read_lock::().unwrap(); @@ -284,10 +279,10 @@ fn map( for (i, item) in list.iter().enumerate() { array.push( mapper - .call_dynamic(engine, lib, None, [item.clone()]) + .call_dynamic(context, None, [item.clone()]) .or_else(|err| match *err { EvalAltResult::ErrorFunctionNotFound(_, _) => { - mapper.call_dynamic(engine, lib, None, [item.clone(), (i as INT).into()]) + mapper.call_dynamic(context, None, [item.clone(), (i as INT).into()]) } _ => Err(err), }) @@ -305,8 +300,7 @@ fn map( } fn filter( - engine: &Engine, - lib: &Module, + context: NativeCallContext, args: &mut [&mut Dynamic], ) -> Result> { let list = args[0].read_lock::().unwrap(); @@ -316,10 +310,10 @@ fn filter( for (i, item) in list.iter().enumerate() { if filter - .call_dynamic(engine, lib, None, [item.clone()]) + .call_dynamic(context, None, [item.clone()]) .or_else(|err| match *err { EvalAltResult::ErrorFunctionNotFound(_, _) => { - filter.call_dynamic(engine, lib, None, [item.clone(), (i as INT).into()]) + filter.call_dynamic(context, None, [item.clone(), (i as INT).into()]) } _ => Err(err), }) @@ -340,20 +334,16 @@ fn filter( Ok(array) } -fn some( - engine: &Engine, - lib: &Module, - args: &mut [&mut Dynamic], -) -> Result> { +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(engine, lib, None, [item.clone()]) + .call_dynamic(context, None, [item.clone()]) .or_else(|err| match *err { EvalAltResult::ErrorFunctionNotFound(_, _) => { - filter.call_dynamic(engine, lib, None, [item.clone(), (i as INT).into()]) + filter.call_dynamic(context, None, [item.clone(), (i as INT).into()]) } _ => Err(err), }) @@ -374,20 +364,16 @@ fn some( Ok(false.into()) } -fn all( - engine: &Engine, - lib: &Module, - args: &mut [&mut Dynamic], -) -> Result> { +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(engine, lib, None, [item.clone()]) + .call_dynamic(context, None, [item.clone()]) .or_else(|err| match *err { EvalAltResult::ErrorFunctionNotFound(_, _) => { - filter.call_dynamic(engine, lib, None, [item.clone(), (i as INT).into()]) + filter.call_dynamic(context, None, [item.clone(), (i as INT).into()]) } _ => Err(err), }) @@ -409,8 +395,7 @@ fn all( } fn reduce( - engine: &Engine, - lib: &Module, + context: NativeCallContext, args: &mut [&mut Dynamic], ) -> Result> { let list = args[0].read_lock::().unwrap(); @@ -420,14 +405,11 @@ fn reduce( for (i, item) in list.iter().enumerate() { result = reducer - .call_dynamic(engine, lib, None, [result.clone(), item.clone()]) + .call_dynamic(context, None, [result.clone(), item.clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => reducer.call_dynamic( - engine, - lib, - None, - [result, item.clone(), (i as INT).into()], - ), + EvalAltResult::ErrorFunctionNotFound(_, _) => { + reducer.call_dynamic(context, None, [result, item.clone(), (i as INT).into()]) + } _ => Err(err), }) .map_err(|err| { @@ -443,15 +425,14 @@ fn reduce( } fn reduce_with_initial( - engine: &Engine, - lib: &Module, + 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(engine, lib, None, []).map_err(|err| { + let mut result = initial.call_dynamic(context, None, []).map_err(|err| { Box::new(EvalAltResult::ErrorInFunctionCall( "reduce".to_string(), err, @@ -461,14 +442,11 @@ fn reduce_with_initial( for (i, item) in list.iter().enumerate() { result = reducer - .call_dynamic(engine, lib, None, [result.clone(), item.clone()]) + .call_dynamic(context, None, [result.clone(), item.clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => reducer.call_dynamic( - engine, - lib, - None, - [result, item.clone(), (i as INT).into()], - ), + EvalAltResult::ErrorFunctionNotFound(_, _) => { + reducer.call_dynamic(context, None, [result, item.clone(), (i as INT).into()]) + } _ => Err(err), }) .map_err(|err| { @@ -484,8 +462,7 @@ fn reduce_with_initial( } fn reduce_rev( - engine: &Engine, - lib: &Module, + context: NativeCallContext, args: &mut [&mut Dynamic], ) -> Result> { let list = args[0].read_lock::().unwrap(); @@ -495,14 +472,11 @@ fn reduce_rev( for (i, item) in list.iter().enumerate().rev() { result = reducer - .call_dynamic(engine, lib, None, [result.clone(), item.clone()]) + .call_dynamic(context, None, [result.clone(), item.clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => reducer.call_dynamic( - engine, - lib, - None, - [result, item.clone(), (i as INT).into()], - ), + EvalAltResult::ErrorFunctionNotFound(_, _) => { + reducer.call_dynamic(context, None, [result, item.clone(), (i as INT).into()]) + } _ => Err(err), }) .map_err(|err| { @@ -518,15 +492,14 @@ fn reduce_rev( } fn reduce_rev_with_initial( - engine: &Engine, - lib: &Module, + 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(engine, lib, None, []).map_err(|err| { + let mut result = initial.call_dynamic(context, None, []).map_err(|err| { Box::new(EvalAltResult::ErrorInFunctionCall( "reduce".to_string(), err, @@ -536,14 +509,11 @@ fn reduce_rev_with_initial( for (i, item) in list.iter().enumerate().rev() { result = reducer - .call_dynamic(engine, lib, None, [result.clone(), item.clone()]) + .call_dynamic(context, None, [result.clone(), item.clone()]) .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) => reducer.call_dynamic( - engine, - lib, - None, - [result, item.clone(), (i as INT).into()], - ), + EvalAltResult::ErrorFunctionNotFound(_, _) => { + reducer.call_dynamic(context, None, [result, item.clone(), (i as INT).into()]) + } _ => Err(err), }) .map_err(|err| { @@ -559,8 +529,7 @@ fn reduce_rev_with_initial( } fn sort( - engine: &Engine, - lib: &Module, + context: NativeCallContext, args: &mut [&mut Dynamic], ) -> Result> { let comparer = args[1].read_lock::().unwrap().clone(); @@ -568,7 +537,7 @@ fn sort( list.sort_by(|x, y| { comparer - .call_dynamic(engine, lib, None, [x.clone(), y.clone()]) + .call_dynamic(context, None, [x.clone(), y.clone()]) .ok() .and_then(|v| v.as_int().ok()) .map(|v| { @@ -598,8 +567,7 @@ fn sort( } fn drain( - engine: &Engine, - lib: &Module, + context: NativeCallContext, args: &mut [&mut Dynamic], ) -> Result> { let filter = args[1].read_lock::().unwrap().clone(); @@ -613,10 +581,10 @@ fn drain( i -= 1; if filter - .call_dynamic(engine, lib, None, [list[i].clone()]) + .call_dynamic(context, None, [list[i].clone()]) .or_else(|err| match *err { EvalAltResult::ErrorFunctionNotFound(_, _) => { - filter.call_dynamic(engine, lib, None, [list[i].clone(), (i as INT).into()]) + filter.call_dynamic(context, None, [list[i].clone(), (i as INT).into()]) } _ => Err(err), }) @@ -638,8 +606,7 @@ fn drain( } fn retain( - engine: &Engine, - lib: &Module, + context: NativeCallContext, args: &mut [&mut Dynamic], ) -> Result> { let filter = args[1].read_lock::().unwrap().clone(); @@ -653,10 +620,10 @@ fn retain( i -= 1; if !filter - .call_dynamic(engine, lib, None, [list[i].clone()]) + .call_dynamic(context, None, [list[i].clone()]) .or_else(|err| match *err { EvalAltResult::ErrorFunctionNotFound(_, _) => { - filter.call_dynamic(engine, lib, None, [list[i].clone(), (i as INT).into()]) + filter.call_dynamic(context, None, [list[i].clone(), (i as INT).into()]) } _ => Err(err), }) diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 495a08c3..6816407d 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -12,4 +12,8 @@ mod fn_ptr_functions { pub fn name(f: &mut FnPtr) -> ImmutableString { f.get_fn_name().clone() } + #[rhai_fn(name = "is_anonymous", get = "is_anonymous")] + pub fn is_anonymous(f: &mut FnPtr) -> bool { + f.is_anonymous() + } } diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index c4832c95..0b3dead7 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -1,11 +1,11 @@ use crate::any::Variant; use crate::def_package; -use crate::module::FuncReturn; use crate::parser::INT; +use crate::result::EvalAltResult; use crate::stdlib::ops::{Add, Range}; -fn get_range(from: T, to: T) -> FuncReturn> { +fn get_range(from: T, to: T) -> Result, Box> { Ok(from..to) } @@ -34,7 +34,7 @@ where } } -fn get_step_range(from: T, to: T, step: T) -> FuncReturn> +fn get_step_range(from: T, to: T, step: T) -> Result, Box> where for<'a> &'a T: Add<&'a T, Output = T>, T: Variant + Clone + PartialOrd, diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index e3b78292..1a311b5d 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -2,7 +2,6 @@ use crate::any::Dynamic; use crate::def_package; -use crate::engine::Engine; use crate::fn_native::FnPtr; use crate::parser::{ImmutableString, INT}; use crate::plugin::*; @@ -62,15 +61,17 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str lib.set_raw_fn( "pad", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], - |_engine: &Engine, _: &Module, args: &mut [&mut Dynamic]| { + |_context, args| { let len = *args[1].read_lock::().unwrap(); // Check if string will be over max size limit #[cfg(not(feature = "unchecked"))] - if _engine.limits.max_string_size > 0 && len > 0 && (len as usize) > _engine.limits.max_string_size { + if _context.engine().max_string_size() > 0 && len > 0 + && (len as usize) > _context.engine().max_string_size() + { return EvalAltResult::ErrorDataTooLarge( "Length of string".to_string(), - _engine.limits.max_string_size, + _context.engine().max_string_size(), len as usize, Position::none(), ).into(); @@ -90,10 +91,10 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str } #[cfg(not(feature = "unchecked"))] - if _engine.limits.max_string_size > 0 && s.len() > _engine.limits.max_string_size { + if _context.engine().max_string_size() > 0 && s.len() > _context.engine().max_string_size() { return EvalAltResult::ErrorDataTooLarge( "Length of string".to_string(), - _engine.limits.max_string_size, + _context.engine().max_string_size(), s.len(), Position::none(), ).into(); diff --git a/src/parser.rs b/src/parser.rs index a26ec543..404d1ffd 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,9 +1,7 @@ //! Main module defining the lexer and parser. use crate::any::{Dynamic, Union}; -use crate::engine::{ - Engine, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT, -}; +use crate::engine::{Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::fn_native::{FnPtr, Shared}; use crate::module::{Module, ModuleRef}; @@ -18,7 +16,7 @@ use crate::{calc_fn_hash, StaticVec}; use crate::engine::Array; #[cfg(not(feature = "no_object"))] -use crate::engine::{make_getter, make_setter, Map}; +use crate::engine::{make_getter, make_setter, Map, KEYWORD_EVAL, KEYWORD_FN_PTR}; #[cfg(not(feature = "no_function"))] use crate::engine::{FN_ANONYMOUS, KEYWORD_FN_PTR_CURRY}; @@ -603,13 +601,15 @@ struct ParseState<'e> { /// All consequent calls to `access_var` will not be affected #[cfg(not(feature = "no_closure"))] allow_capture: bool, - /// Encapsulates a local stack with variable names to simulate an actual runtime scope. + /// Encapsulates a local stack with imported module names. + #[cfg(not(feature = "no_module"))] modules: Vec, /// Maximum levels of expression nesting. #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, /// Maximum levels of expression nesting in functions. #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_function"))] max_function_expr_depth: usize, } @@ -619,19 +619,23 @@ impl<'e> ParseState<'e> { pub fn new( engine: &'e Engine, #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, - #[cfg(not(feature = "unchecked"))] max_function_expr_depth: usize, + #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_function"))] + max_function_expr_depth: usize, ) -> Self { Self { engine, #[cfg(not(feature = "unchecked"))] max_expr_depth, #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_function"))] max_function_expr_depth, #[cfg(not(feature = "no_closure"))] externals: Default::default(), #[cfg(not(feature = "no_closure"))] allow_capture: true, stack: Default::default(), + #[cfg(not(feature = "no_module"))] modules: Default::default(), } } @@ -1696,11 +1700,10 @@ fn parse_array_literal( while !input.peek().unwrap().0.is_eof() { #[cfg(not(feature = "unchecked"))] - if state.engine.limits.max_array_size > 0 && arr.len() >= state.engine.limits.max_array_size - { + if state.engine.max_array_size() > 0 && arr.len() >= state.engine.max_array_size() { return Err(PERR::LiteralTooLarge( "Size of array literal".to_string(), - state.engine.limits.max_array_size, + state.engine.max_array_size(), ) .into_err(input.peek().unwrap().1)); } @@ -1804,10 +1807,10 @@ fn parse_map_literal( }; #[cfg(not(feature = "unchecked"))] - if state.engine.limits.max_map_size > 0 && map.len() >= state.engine.limits.max_map_size { + if state.engine.max_map_size() > 0 && map.len() >= state.engine.max_map_size() { return Err(PERR::LiteralTooLarge( "Number of properties in object map literal".to_string(), - state.engine.limits.max_map_size, + state.engine.max_map_size(), ) .into_err(input.peek().unwrap().1)); } @@ -3599,9 +3602,10 @@ impl Engine { let mut state = ParseState::new( self, #[cfg(not(feature = "unchecked"))] - self.limits.max_expr_depth, + self.max_expr_depth(), #[cfg(not(feature = "unchecked"))] - self.limits.max_function_expr_depth, + #[cfg(not(feature = "no_function"))] + self.max_function_expr_depth(), ); let settings = ParseSettings { @@ -3646,9 +3650,10 @@ impl Engine { let mut state = ParseState::new( self, #[cfg(not(feature = "unchecked"))] - self.limits.max_expr_depth, + self.max_expr_depth(), #[cfg(not(feature = "unchecked"))] - self.limits.max_function_expr_depth, + #[cfg(not(feature = "no_function"))] + self.max_function_expr_depth(), ); while !input.peek().unwrap().0.is_eof() { diff --git a/src/settings.rs b/src/settings.rs index c10f5aa7..c72fe2d8 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -53,7 +53,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn set_max_call_levels(&mut self, levels: usize) -> &mut Self { - self.limits.max_call_stack_depth = levels; + self.limits_set.max_call_stack_depth = levels; self } @@ -61,7 +61,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn max_call_levels(&self) -> usize { - self.limits.max_call_stack_depth + self.limits_set.max_call_stack_depth } /// Set the maximum number of operations allowed for a script to run to avoid @@ -69,7 +69,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn set_max_operations(&mut self, operations: u64) -> &mut Self { - self.limits.max_operations = if operations == u64::MAX { + self.limits_set.max_operations = if operations == u64::MAX { 0 } else { operations @@ -81,14 +81,14 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn max_operations(&self) -> u64 { - self.limits.max_operations + self.limits_set.max_operations } /// Set the maximum number of imported modules allowed for a script. #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn set_max_modules(&mut self, modules: usize) -> &mut Self { - self.limits.max_modules = modules; + self.limits_set.max_modules = modules; self } @@ -96,7 +96,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn max_modules(&self) -> usize { - self.limits.max_modules + self.limits_set.max_modules } /// Set the depth limits for expressions (0 for unlimited). @@ -105,18 +105,21 @@ impl Engine { pub fn set_max_expr_depths( &mut self, max_expr_depth: usize, - max_function_expr_depth: usize, + #[cfg(not(feature = "no_function"))] max_function_expr_depth: usize, ) -> &mut Self { - self.limits.max_expr_depth = if max_expr_depth == usize::MAX { + self.limits_set.max_expr_depth = if max_expr_depth == usize::MAX { 0 } else { max_expr_depth }; - self.limits.max_function_expr_depth = if max_function_expr_depth == usize::MAX { - 0 - } else { - max_function_expr_depth - }; + #[cfg(not(feature = "no_function"))] + { + self.limits_set.max_function_expr_depth = if max_function_expr_depth == usize::MAX { + 0 + } else { + max_function_expr_depth + }; + } self } @@ -124,21 +127,22 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn max_expr_depth(&self) -> usize { - self.limits.max_expr_depth + self.limits_set.max_expr_depth } /// The depth limit for expressions in functions (0 for unlimited). #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn max_function_expr_depth(&self) -> usize { - self.limits.max_function_expr_depth + self.limits_set.max_function_expr_depth } /// Set the maximum length of strings (0 for unlimited). #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn set_max_string_size(&mut self, max_size: usize) -> &mut Self { - self.limits.max_string_size = if max_size == usize::MAX { 0 } else { max_size }; + self.limits_set.max_string_size = if max_size == usize::MAX { 0 } else { max_size }; self } @@ -146,7 +150,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn max_string_size(&self) -> usize { - self.limits.max_string_size + self.limits_set.max_string_size } /// Set the maximum length of arrays (0 for unlimited). @@ -154,7 +158,7 @@ impl Engine { #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn set_max_array_size(&mut self, max_size: usize) -> &mut Self { - self.limits.max_array_size = if max_size == usize::MAX { 0 } else { max_size }; + self.limits_set.max_array_size = if max_size == usize::MAX { 0 } else { max_size }; self } @@ -163,7 +167,7 @@ impl Engine { #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn max_array_size(&self) -> usize { - self.limits.max_array_size + self.limits_set.max_array_size } /// Set the maximum length of object maps (0 for unlimited). @@ -171,7 +175,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn set_max_map_size(&mut self, max_size: usize) -> &mut Self { - self.limits.max_map_size = if max_size == usize::MAX { 0 } else { max_size }; + self.limits_set.max_map_size = if max_size == usize::MAX { 0 } else { max_size }; self } @@ -180,7 +184,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn max_map_size(&self) -> usize { - self.limits.max_map_size + self.limits_set.max_map_size } /// Set the module resolution service used by the `Engine`. diff --git a/src/syntax.rs b/src/syntax.rs index 5e827105..08b102b7 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -67,14 +67,14 @@ impl EvalContext<'_, '_, '_, '_, '_, '_> { scope: &mut Scope, expr: &Expression, ) -> Result> { - self.engine.eval_expr( + self.engine().eval_expr( scope, self.mods, self.state, - self.lib, + self.namespace(), self.this_ptr, expr.expr(), - self.level, + self.call_level(), ) } } diff --git a/src/token.rs b/src/token.rs index 92f10da8..418765d6 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1747,7 +1747,7 @@ pub fn lex<'a, 'e>( engine, state: TokenizeState { #[cfg(not(feature = "unchecked"))] - max_string_size: engine.limits.max_string_size, + max_string_size: engine.limits_set.max_string_size, #[cfg(feature = "unchecked")] max_string_size: 0, non_unary: false, diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 5d22abd1..4d4e3552 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -98,12 +98,12 @@ fn test_fn_ptr_raw() -> Result<(), Box> { TypeId::of::(), TypeId::of::(), ], - move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + move |context, args| { let fp = std::mem::take(args[1]).cast::(); let value = args[2].clone(); let this_ptr = args.get_mut(0).unwrap(); - fp.call_dynamic(engine, lib, Some(this_ptr), [value]) + fp.call_dynamic(context, Some(this_ptr), [value]) }, ); diff --git a/tests/closures.rs b/tests/closures.rs index 6f5de9e7..4f5f1b17 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, ParseErrorType, RegisterFn, Scope, INT}; +use rhai::{Engine, EvalAltResult, FnPtr, ParseErrorType, RegisterFn, Scope, INT}; use std::any::TypeId; use std::cell::RefCell; use std::mem::take; @@ -16,9 +16,9 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { engine.register_raw_fn( "call_with_arg", &[TypeId::of::(), TypeId::of::()], - |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + |context, args| { let fn_ptr = std::mem::take(args[0]).cast::(); - fn_ptr.call_dynamic(engine, lib, None, [std::mem::take(args[1])]) + fn_ptr.call_dynamic(context, None, [std::mem::take(args[1])]) }, ); @@ -135,10 +135,10 @@ fn test_closures() -> Result<(), Box> { engine.register_raw_fn( "custom_call", &[TypeId::of::(), TypeId::of::()], - |engine: &Engine, module: &Module, args: &mut [&mut Dynamic]| { + |context, args| { let func = take(args[1]).cast::(); - func.call_dynamic(engine, module, None, []) + func.call_dynamic(context, None, []) }, ); @@ -259,7 +259,6 @@ fn test_closures_external() -> Result<(), Box> { let mut ast = engine.compile( r#" let test = "hello"; - |x| test + x "#, )?; @@ -271,7 +270,7 @@ fn test_closures_external() -> Result<(), Box> { ast.retain_functions(|_, _, _| true); // Closure 'f' captures: the engine, the AST, and the curried function pointer - let f = move |x: INT| fn_ptr.call_dynamic(&engine, ast, None, [x.into()]); + let f = move |x: INT| fn_ptr.call_dynamic((&engine, ast.as_ref()).into(), None, [x.into()]); assert_eq!(f(42)?.as_str(), Ok("hello42")); From 232ff91957bcfaea97db07a3f0e4d600ee1b29d3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 18 Oct 2020 17:08:57 +0800 Subject: [PATCH 09/16] Fix doc test. --- src/module/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module/mod.rs b/src/module/mod.rs index dcdfe756..759bf1aa 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -511,7 +511,7 @@ impl Module { /// // Pass parameter types via a slice with TypeId's /// &[std::any::TypeId::of::(), std::any::TypeId::of::()], /// // Fixed closure signature - /// |engine, lib, args| { + /// |context, args| { /// // 'args' is guaranteed to be the right length and of the correct types /// /// // Get the second parameter by 'consuming' it From dc4c47e008d7fd04b68edbc62e93e416448bf2af Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 18 Oct 2020 17:29:11 +0800 Subject: [PATCH 10/16] Fix no_function and no_module builds. --- src/fn_native.rs | 6 +++++- src/parser.rs | 19 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/fn_native.rs b/src/fn_native.rs index 69790b8d..113f19a1 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,7 +1,7 @@ //! Module defining interfaces to native-Rust functions. use crate::any::Dynamic; -use crate::engine::{Engine, EvalContext, FN_ANONYMOUS}; +use crate::engine::{Engine, EvalContext}; use crate::module::Module; use crate::parser::{FnAccess, ScriptFnDef}; use crate::plugin::PluginFunction; @@ -11,6 +11,9 @@ use crate::token::{is_valid_identifier, Position}; use crate::utils::ImmutableString; use crate::{calc_fn_hash, StaticVec}; +#[cfg(not(feature = "no_function"))] +use crate::engine::FN_ANONYMOUS; + use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, mem, string::String}; #[cfg(feature = "sync")] @@ -138,6 +141,7 @@ impl FnPtr { self.1.as_ref() } /// Does this function pointer refer to an anonymous function? + #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn is_anonymous(&self) -> bool { self.0.starts_with(FN_ANONYMOUS) diff --git a/src/parser.rs b/src/parser.rs index 404d1ffd..b957ebeb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -670,17 +670,28 @@ impl<'e> ParseState<'e> { } /// Find a module by name in the `ParseState`, searching in reverse. - /// The return value is the offset to be deducted from `Stack::len`, + /// + /// Returns the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. - /// Return `None` when the variable name is not found in the `ParseState`. + /// + /// Returns `None` when the variable name is not found in the `ParseState`. + /// + /// # Panics + /// + /// Panics when called under `no_module`. #[inline(always)] pub fn find_module(&self, name: &str) -> Option { - self.modules + #[cfg(feature = "no_module")] + unreachable!(); + + #[cfg(not(feature = "no_module"))] + return self + .modules .iter() .rev() .enumerate() .find(|(_, n)| *n == name) - .and_then(|(i, _)| NonZeroUsize::new(i + 1)) + .and_then(|(i, _)| NonZeroUsize::new(i + 1)); } } From 46b92c9d1f22970a84c574c3200f9b3d94e5e5a2 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 18 Oct 2020 21:47:34 +0800 Subject: [PATCH 11/16] 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; From 6e5c903241e5d0753f2978fbf4bafa80bfc8a000 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 18 Oct 2020 22:10:08 +0800 Subject: [PATCH 12/16] Fix no_module build. --- RELEASES.md | 2 +- src/engine.rs | 4 ++-- src/module/mod.rs | 1 + src/packages/fn_basic.rs | 10 +++++++--- src/parser.rs | 18 +++++++++++------- src/settings.rs | 2 ++ 6 files changed, 24 insertions(+), 13 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 8a4cd93b..cb37b9d8 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -10,7 +10,7 @@ Breaking changes * `EvalAltResult::ErrorReadingScriptFile` is removed in favor of the new `EvalAltResult::ErrorSystem`. * `EvalAltResult::ErrorLoopBreak` is renamed to `EvalAltResult::LoopBreak`. -* `Engine::register_raw_fn` function signature has changed. +* `Engine::register_raw_fn` and `FnPtr::call_dynamic` function signatures have changed. New features ------------ diff --git a/src/engine.rs b/src/engine.rs index 46af0581..bc7063dc 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -425,7 +425,7 @@ pub struct Limits { pub max_operations: u64, /// Maximum number of modules allowed to load. /// Not available under `no_module`. - #[cfg(not(feature = "no_modules"))] + #[cfg(not(feature = "no_module"))] pub max_modules: usize, /// Maximum length of a string (0 = unlimited). pub max_string_size: usize, @@ -459,7 +459,7 @@ impl<'e, 'a, 's, 'm, 't, 'd> EvalContext<'e, 'a, 's, 'm, 't, 'd> { /// _[INTERNALS]_ The current set of modules imported via `import` statements. /// Available under the `internals` feature only. #[cfg(feature = "internals")] - #[cfg(not(feature = "no_modules"))] + #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn imports(&self) -> &'a Imports { self.mods diff --git a/src/module/mod.rs b/src/module/mod.rs index 66c08875..53fd915f 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1586,6 +1586,7 @@ impl ModuleRef { pub(crate) fn index(&self) -> Option { self.1 } + #[cfg(not(feature = "no_module"))] pub(crate) fn set_index(&mut self, index: Option) { self.1 = index } diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 6816407d..64494290 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -12,8 +12,12 @@ mod fn_ptr_functions { pub fn name(f: &mut FnPtr) -> ImmutableString { f.get_fn_name().clone() } - #[rhai_fn(name = "is_anonymous", get = "is_anonymous")] - pub fn is_anonymous(f: &mut FnPtr) -> bool { - f.is_anonymous() + + #[cfg(not(feature = "no_function"))] + pub mod anonymous { + #[rhai_fn(name = "is_anonymous", get = "is_anonymous")] + pub fn is_anonymous(f: &mut FnPtr) -> bool { + f.is_anonymous() + } } } diff --git a/src/parser.rs b/src/parser.rs index b957ebeb..8cedfae7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -679,19 +679,15 @@ impl<'e> ParseState<'e> { /// # Panics /// /// Panics when called under `no_module`. + #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn find_module(&self, name: &str) -> Option { - #[cfg(feature = "no_module")] - unreachable!(); - - #[cfg(not(feature = "no_module"))] - return self - .modules + self.modules .iter() .rev() .enumerate() .find(|(_, n)| *n == name) - .and_then(|(i, _)| NonZeroUsize::new(i + 1)); + .and_then(|(i, _)| NonZeroUsize::new(i + 1)) } } @@ -1422,6 +1418,7 @@ fn parse_fn_call( eat_token(input, Token::RightParen); let hash_script = if let Some(modules) = modules.as_mut() { + #[cfg(not(feature = "no_module"))] modules.set_index(state.find_module(&modules[0].0)); // Rust functions are indexed in two steps: @@ -1464,6 +1461,7 @@ fn parse_fn_call( eat_token(input, Token::RightParen); let hash_script = if let Some(modules) = modules.as_mut() { + #[cfg(not(feature = "no_module"))] modules.set_index(state.find_module(&modules[0].0)); // Rust functions are indexed in two steps: @@ -2049,6 +2047,8 @@ fn parse_primary( // Qualifiers + variable name *hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, 0, empty()); + + #[cfg(not(feature = "no_module"))] modules.set_index(state.find_module(&modules[0].0)); } _ => (), @@ -3092,6 +3092,8 @@ fn parse_block( let mut statements = StaticVec::new(); let prev_stack_len = state.stack.len(); + + #[cfg(not(feature = "no_module"))] let prev_mods_len = state.modules.len(); while !match_token(input, Token::RightBrace)? { @@ -3137,6 +3139,8 @@ fn parse_block( } state.stack.truncate(prev_stack_len); + + #[cfg(not(feature = "no_module"))] state.modules.truncate(prev_mods_len); Ok(Stmt::Block(Box::new((statements, settings.pos)))) diff --git a/src/settings.rs b/src/settings.rs index c72fe2d8..bd23114a 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -86,6 +86,7 @@ impl Engine { /// Set the maximum number of imported modules allowed for a script. #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn set_max_modules(&mut self, modules: usize) -> &mut Self { self.limits_set.max_modules = modules; @@ -94,6 +95,7 @@ impl Engine { /// The maximum number of imported modules allowed for a script. #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn max_modules(&self) -> usize { self.limits_set.max_modules From ea814779bf270258ee2dbe231bab0b215ea3a859 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 18 Oct 2020 22:24:01 +0800 Subject: [PATCH 13/16] Fix test. --- tests/stack.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/stack.rs b/tests/stack.rs index 03edee0e..e9ffae96 100644 --- a/tests/stack.rs +++ b/tests/stack.rs @@ -40,7 +40,11 @@ fn test_stack_overflow_parsing() -> Result<(), Box> { ParseErrorType::ExprTooDeep ); - engine.set_max_expr_depths(100, 6); + engine.set_max_expr_depths( + 100, + #[cfg(not(feature = "no_module"))] + 6, + ); engine.compile("1 + 2")?; engine.compile( From 25f820f5bfb50cb2db5bcc95a9938b8d2fdf0f81 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 18 Oct 2020 22:36:58 +0800 Subject: [PATCH 14/16] Reimplement pad for string. --- doc/src/language/arrays.md | 60 ++++++------- doc/src/language/fn-ptr.md | 2 +- doc/src/language/string-fn.md | 2 +- doc/src/plugins/function.md | 14 +-- doc/src/plugins/module.md | 8 +- doc/src/rust/register-raw.md | 3 +- src/packages/string_more.rs | 158 ++++++++++++++++++++++------------ 7 files changed, 150 insertions(+), 97 deletions(-) diff --git a/doc/src/language/arrays.md b/doc/src/language/arrays.md index 56706d86..1b654385 100644 --- a/doc/src/language/arrays.md +++ b/doc/src/language/arrays.md @@ -30,36 +30,36 @@ Built-in Functions The following methods (mostly defined in the [`BasicArrayPackage`][packages] but excluded if using a [raw `Engine`]) operate on arrays: -| Function | Parameter(s) | Description | -| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `push` | element to insert | inserts an element at the end | -| `append` | array to append | concatenates the second array to the end of the first | -| `+=` operator | 1) array
2) element to insert (not another array) | inserts an element at the end | -| `+=` operator | 1) array
2) array to append | concatenates the second array to the end of the first | -| `+` operator | 1) first array
2) second array | concatenates the first array with the second | -| `insert` | 1) element to insert
2) position, beginning if < 0, end if > length | inserts an element at a certain index | -| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | -| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | -| `extract` | 1) start position, beginning if < 0, end if > length,
2) _(optional)_ number of items to extract, none if < 0 | extracts a portion of the array into a new array | -| `remove` | index | removes an element at a particular index and returns it ([`()`] if the index is not valid) | -| `reverse` | _none_ | reverses the array | -| `len` method and property | _none_ | returns the number of elements | -| `pad` | 1) target length
2) element to pad | pads the array with an element to at least a specified length | -| `clear` | _none_ | empties the array | -| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | -| `chop` | target length | cuts off the head of the array, leaving the tail at exactly a specified length | -| `drain` | 1) [function pointer] to predicate (usually a [closure]),
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | removes all items (returning them) that return `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | -| `drain` | 1) start position, beginning if < 0, end if > length,
2) number of items to remove, none if < 0 | removes a portion of the array, returning the removed items (not in original order) | -| `retain` | 1) [function pointer] to predicate (usually a [closure]),
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | removes all items (returning them) that do not return `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | -| `retain` | 1) start position, beginning if < 0, end if > length,
2) number of items to retain, none if < 0 | retains a portion of the array, removes all other items and returning them (not in original order) | -| `splice` | 1) start position, beginning if < 0, end if > length,
2) number of items to remove, none if < 0,
3) array to insert | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) | -| `filter` | [function pointer] to predicate (usually a [closure]) | constructs a new array with all items that return `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | -| `map` | [function pointer] to conversion function (usually a [closure]) | constructs a new array with all items mapped to the result of applying the conversion function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | -| `reduce` | 1) [function pointer] to accumulator function (usually a [closure]),
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | reduces the array into a single value via the accumulator function:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
3rd parameter: _(optional)_ offset index | -| `reduce_rev` | 1) [function pointer] to accumulator function (usually a [closure]),
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | reduces the array (in reverse order) into a single value via the accumulator function:
1st parameter: accumulated value ([`()`] initially),
2nd parameter: array item,
3rd parameter: _(optional)_ offset index | -| `some` | [function pointer] to predicate (usually a [closure]) | returns `true` if any item returns `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | -| `all` | [function pointer] to predicate (usually a [closure]) | returns `true` if all items return `true` when called with the predicate function:
1st parameter: array item,
2nd parameter: _(optional)_ offset index | -| `sort` | [function pointer] to a comparison function (usually a [closure]) | sorts the array with a comparison function:
1st parameter: first item,
2nd parameter: second item,
return value: `INT` < 0 if first < second, > 0 if first > second, 0 if first == second | +| Function | Parameter(s) | Description | +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `push` | element to insert | inserts an element at the end | +| `append` | array to append | concatenates the second array to the end of the first | +| `+=` operator | 1) array
2) element to insert (not another array) | inserts an element at the end | +| `+=` operator | 1) array
2) array to append | concatenates the second array to the end of the first | +| `+` operator | 1) first array
2) second array | concatenates the first array with the second | +| `insert` | 1) element to insert
2) position, beginning if < 0, end if > length | inserts an element at a certain index | +| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | +| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | +| `extract` | 1) start position, beginning if < 0, end if > length
2) _(optional)_ number of items to extract, none if < 0 | extracts a portion of the array into a new array | +| `remove` | index | removes an element at a particular index and returns it ([`()`] if the index is not valid) | +| `reverse` | _none_ | reverses the array | +| `len` method and property | _none_ | returns the number of elements | +| `pad` | 1) target length
2) element to pad | pads the array with an element to at least a specified length | +| `clear` | _none_ | empties the array | +| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | +| `chop` | target length | cuts off the head of the array, leaving the tail at exactly a specified length | +| `drain` | 1) [function pointer] to predicate (usually a [closure])
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | removes all items (returning them) that return `true` when called with the predicate function:
1st parameter: array item
2nd parameter: _(optional)_ offset index | +| `drain` | 1) start position, beginning if < 0, end if > length
2) number of items to remove, none if < 0 | removes a portion of the array, returning the removed items (not in original order) | +| `retain` | 1) [function pointer] to predicate (usually a [closure])
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | removes all items (returning them) that do not return `true` when called with the predicate function:
1st parameter: array item
2nd parameter: _(optional)_ offset index | +| `retain` | 1) start position, beginning if < 0, end if > length
2) number of items to retain, none if < 0 | retains a portion of the array, removes all other items and returning them (not in original order) | +| `splice` | 1) start position, beginning if < 0, end if > length
2) number of items to remove, none if < 0
3) array to insert | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) | +| `filter` | [function pointer] to predicate (usually a [closure]) | constructs a new array with all items that return `true` when called with the predicate function:
1st parameter: array item
2nd parameter: _(optional)_ offset index | +| `map` | [function pointer] to conversion function (usually a [closure]) | constructs a new array with all items mapped to the result of applying the conversion function:
1st parameter: array item
2nd parameter: _(optional)_ offset index | +| `reduce` | 1) [function pointer] to accumulator function (usually a [closure])
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | reduces the array into a single value via the accumulator function:
1st parameter: accumulated value ([`()`] initially)
2nd parameter: array item
3rd parameter: _(optional)_ offset index | +| `reduce_rev` | 1) [function pointer] to accumulator function (usually a [closure])
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | reduces the array (in reverse order) into a single value via the accumulator function:
1st parameter: accumulated value ([`()`] initially)
2nd parameter: array item
3rd parameter: _(optional)_ offset index | +| `some` | [function pointer] to predicate (usually a [closure]) | returns `true` if any item returns `true` when called with the predicate function:
1st parameter: array item
2nd parameter: _(optional)_ offset index | +| `all` | [function pointer] to predicate (usually a [closure]) | returns `true` if all items return `true` when called with the predicate function:
1st parameter: array item
2nd parameter: _(optional)_ offset index | +| `sort` | [function pointer] to a comparison function (usually a [closure]) | sorts the array with a comparison function:
1st parameter: first item
2nd parameter: second item
return value: `INT` < 0 if first < second, > 0 if first > second, 0 if first == second | Use Custom Types With Arrays diff --git a/doc/src/language/fn-ptr.md b/doc/src/language/fn-ptr.md index acd301eb..00e9edac 100644 --- a/doc/src/language/fn-ptr.md +++ b/doc/src/language/fn-ptr.md @@ -226,7 +226,7 @@ engine.register_raw_fn("super_call", `FnPtr::call_dynamic` takes a parameter of type `NativeCallContext` which holds the _native call context_ of the particular call to a registered Rust function. -This type is normally provided by the [`Engine`] (e.g. when using `Engine::register_fn_raw`(../rust/register-raw.md)). +This type is normally provided by the [`Engine`] (e.g. when using [`Engine::register_fn_raw`](../rust/register-raw.md)). However, it may also be manually constructed from a tuple: ```rust diff --git a/doc/src/language/string-fn.md b/doc/src/language/string-fn.md index 00efd8e1..db3d8b88 100644 --- a/doc/src/language/string-fn.md +++ b/doc/src/language/string-fn.md @@ -9,7 +9,7 @@ using a [raw `Engine`]) operate on [strings]: | Function | Parameter(s) | Description | | ------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string | -| `pad` | 1) character to pad
2) target length | pads the string with an character to at least a specified length | +| `pad` | 1) target length
2) character/string to pad | pads the string with a character or a string to at least a specified length | | `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string | | `clear` | _none_ | empties the string | | `truncate` | target length | cuts off the string at exactly a specified number of characters | diff --git a/doc/src/plugins/function.md b/doc/src/plugins/function.md index 904d56be..bd31de0c 100644 --- a/doc/src/plugins/function.md +++ b/doc/src/plugins/function.md @@ -102,9 +102,10 @@ as a parameter to the function, thereby implementing a _callback_: use rhai::{Dynamic, FnPtr, NativeCallContext, EvalAltResult}; use rhai::plugin::*; // a "prelude" import for macros -#[export_fn(return_raw)] +#[export_fn] +#[rhai_fn(return_raw)] pub fn greet(context: NativeCallContext, callback: FnPtr) - -> Result> + -> Result> { // Call the callback closure with the current context // to obtain the name to greet! @@ -116,15 +117,16 @@ pub fn greet(context: NativeCallContext, callback: FnPtr) 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::{Dynamic, 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> +#[export_fn] +#[rhai_fn(return_raw)] +pub fn grow(context: NativeCallContext, size: i64) + -> Result> { // Make sure the function does not generate a // data structure larger than the allowed limit diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md index aeaeeb58..81195d47 100644 --- a/doc/src/plugins/module.md +++ b/doc/src/plugins/module.md @@ -363,7 +363,7 @@ use rhai::plugin::*; // a "prelude" import for macros mod my_module { #[rhai_fn(return_raw)] pub fn greet(context: NativeCallContext, callback: FnPtr) - -> Result> + -> Result> { // Call the callback closure with the current context // to obtain the name to greet! @@ -376,7 +376,7 @@ mod my_module { 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::{Dynamic, Array, NativeCallContext, EvalAltResult, Position}; use rhai::plugin::*; // a "prelude" import for macros #[export_module] @@ -385,8 +385,8 @@ mod my_module { // 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> + pub fn grow(context: NativeCallContext, size: i64) + -> Result> { // Make sure the function does not generate a // data structure larger than the allowed limit diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index 78990e1c..dc4e3ca2 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -60,7 +60,8 @@ Function Signature The function signature passed to `Engine::register_raw_fn` takes the following form: -> `Fn(context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> + 'static` +> `Fn(context: NativeCallContext, args: &mut [&mut Dynamic])` +> `-> Result> + 'static` where: diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 1a311b5d..cd71fd41 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -58,54 +58,6 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str combine_with_exported_module!(lib, "string", string_functions); - lib.set_raw_fn( - "pad", - &[TypeId::of::(), TypeId::of::(), TypeId::of::()], - |_context, args| { - let len = *args[1].read_lock::().unwrap(); - - // Check if string will be over max size limit - #[cfg(not(feature = "unchecked"))] - if _context.engine().max_string_size() > 0 && len > 0 - && (len as usize) > _context.engine().max_string_size() - { - return EvalAltResult::ErrorDataTooLarge( - "Length of string".to_string(), - _context.engine().max_string_size(), - len as usize, - Position::none(), - ).into(); - } - - if len > 0 { - let ch = mem::take(args[2]).cast::(); - let mut s = args[0].write_lock::().unwrap(); - - let orig_len = s.chars().count(); - - if len as usize > orig_len { - let p = s.make_mut(); - - for _ in 0..(len as usize - orig_len) { - p.push(ch); - } - - #[cfg(not(feature = "unchecked"))] - if _context.engine().max_string_size() > 0 && s.len() > _context.engine().max_string_size() { - return EvalAltResult::ErrorDataTooLarge( - "Length of string".to_string(), - _context.engine().max_string_size(), - s.len(), - Position::none(), - ).into(); - } - } - } - - Ok(()) - }, - ); - // Register string iterator lib.set_iter( TypeId::of::(), @@ -176,7 +128,7 @@ mod string_functions { pub fn index_of_char_starting_from(s: &str, ch: char, start: INT) -> INT { let start = if start < 0 { 0 - } else if (start as usize) >= s.chars().count() { + } else if start as usize >= s.chars().count() { return -1 as INT; } else { s.chars().take(start as usize).collect::().len() @@ -197,7 +149,7 @@ mod string_functions { pub fn index_of_string_starting_from(s: &str, find: ImmutableString, start: INT) -> INT { let start = if start < 0 { 0 - } else if (start as usize) >= s.chars().count() { + } else if start as usize >= s.chars().count() { return -1 as INT; } else { s.chars().take(start as usize).collect::().len() @@ -220,7 +172,7 @@ mod string_functions { return "".to_string().into(); } else if start < 0 { 0 - } else if (start as usize) >= s.chars().count() { + } else if start as usize >= s.chars().count() { return "".to_string().into(); } else { start as usize @@ -228,7 +180,7 @@ mod string_functions { let chars: StaticVec<_> = s.chars().collect(); - let len = if offset + (len as usize) > chars.len() { + let len = if offset + len as usize > chars.len() { chars.len() - offset } else { len as usize @@ -255,7 +207,7 @@ mod string_functions { return; } else if start < 0 { 0 - } else if (start as usize) >= s.chars().count() { + } else if start as usize >= s.chars().count() { s.make_mut().clear(); return; } else { @@ -264,7 +216,7 @@ mod string_functions { let chars: StaticVec<_> = s.chars().collect(); - let len = if offset + (len as usize) > chars.len() { + let len = if offset + len as usize > chars.len() { chars.len() - offset } else { len as usize @@ -295,6 +247,104 @@ mod string_functions { pub fn replace_char(s: &mut ImmutableString, find: char, sub: char) { *s = s.replace(&find.to_string(), &sub.to_string()).into(); } + #[rhai_fn(return_raw)] + pub fn pad( + _context: NativeCallContext, + s: &mut ImmutableString, + len: INT, + ch: char, + ) -> Result> { + // Check if string will be over max size limit + #[cfg(not(feature = "unchecked"))] + if _context.engine().max_string_size() > 0 + && len as usize > _context.engine().max_string_size() + { + return EvalAltResult::ErrorDataTooLarge( + "Length of string".to_string(), + _context.engine().max_string_size(), + len as usize, + Position::none(), + ) + .into(); + } + + if len > 0 { + let orig_len = s.chars().count(); + + if len as usize > orig_len { + let p = s.make_mut(); + + for _ in 0..(len as usize - orig_len) { + p.push(ch); + } + + #[cfg(not(feature = "unchecked"))] + if _context.engine().max_string_size() > 0 + && s.len() > _context.engine().max_string_size() + { + return EvalAltResult::ErrorDataTooLarge( + "Length of string".to_string(), + _context.engine().max_string_size(), + s.len(), + Position::none(), + ) + .into(); + } + } + } + + Ok(().into()) + } + #[rhai_fn(name = "pad", return_raw)] + pub fn pad_with_string( + _context: NativeCallContext, + s: &mut ImmutableString, + len: INT, + padding: &str, + ) -> Result> { + // Check if string will be over max size limit + #[cfg(not(feature = "unchecked"))] + if _context.engine().max_string_size() > 0 + && len as usize > _context.engine().max_string_size() + { + return EvalAltResult::ErrorDataTooLarge( + "Length of string".to_string(), + _context.engine().max_string_size(), + len as usize, + Position::none(), + ) + .into(); + } + + if len > 0 { + let mut str_len = s.chars().count(); + let padding_len = padding.chars().count(); + + if len as usize > str_len { + let p = s.make_mut(); + + while str_len < len as usize { + p.push_str(padding); + str_len += padding_len; + } + + #[cfg(not(feature = "unchecked"))] + if _context.engine().max_string_size() > 0 + && s.len() > _context.engine().max_string_size() + { + return EvalAltResult::ErrorDataTooLarge( + "Length of string".to_string(), + _context.engine().max_string_size(), + s.len(), + Position::none(), + ) + .into(); + } + } + } + + Ok(().into()) + } #[cfg(not(feature = "no_index"))] pub mod arrays { From 8a17856f276b900cd1ee28becfd540c0790cf758 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 18 Oct 2020 22:38:15 +0800 Subject: [PATCH 15/16] Fix test. --- tests/stack.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stack.rs b/tests/stack.rs index e9ffae96..b161e192 100644 --- a/tests/stack.rs +++ b/tests/stack.rs @@ -42,7 +42,7 @@ fn test_stack_overflow_parsing() -> Result<(), Box> { engine.set_max_expr_depths( 100, - #[cfg(not(feature = "no_module"))] + #[cfg(not(feature = "no_function"))] 6, ); From d68c9517953ac1de6472ccb03b547aacacedf53f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 18 Oct 2020 22:47:58 +0800 Subject: [PATCH 16/16] Pad string to exact length even when padding string is not multiple. --- src/fn_native.rs | 2 +- src/packages/string_more.rs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/fn_native.rs b/src/fn_native.rs index 113f19a1..8a3c9a1d 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -49,7 +49,7 @@ pub type Locked = RefCell; #[cfg(feature = "sync")] pub type Locked = RwLock; -/// Context of a script evaluation process. +/// Context of native Rust function call. #[derive(Debug, Copy, Clone)] pub struct NativeCallContext<'e, 'm> { engine: &'e Engine, diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index cd71fd41..06217d11 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -324,8 +324,13 @@ mod string_functions { let p = s.make_mut(); while str_len < len as usize { - p.push_str(padding); - str_len += padding_len; + if str_len + padding_len <= len as usize { + p.push_str(padding); + str_len += padding_len; + } else { + p.extend(padding.chars().take(len as usize - str_len)); + str_len = len as usize; + } } #[cfg(not(feature = "unchecked"))]