From e59f6939f78a31ad65d02e9eb602b44c550ff94b Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Sat, 25 Apr 2020 18:50:10 -0500 Subject: [PATCH 1/8] Added basic Plugin trait and test --- Cargo.toml | 1 + src/fn_register.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ 3 files changed, 62 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index a50bc0b0..431473e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ num-traits = { version = "0.2.11", default-features = false } [features] #default = ["no_stdlib", "no_function", "no_index", "no_object", "no_float", "only_i32", "unchecked", "no_optimize", "sync"] default = [] +plugins = [] unchecked = [] # unchecked arithmetic no_index = [] # no arrays and indexing no_float = [] # no floating-point diff --git a/src/fn_register.rs b/src/fn_register.rs index e711ee54..90289784 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -9,6 +9,49 @@ use crate::token::Position; use crate::stdlib::{any::TypeId, boxed::Box, string::ToString}; +/// A trait to register custom plugins with the `Engine`. +/// +/// A plugin consists of a number of functions. All functions will be registered with the engine. +#[cfg(feature = "plugins")] +pub trait RegisterPlugin { + /// Register a custom function with the `Engine`. + /// + /// # Example + /// + /// ``` + /// use rhai::{Dynamic, Engine, INT, Plugin, RegisterDynamicFn, RegisterPlugin}; + /// + /// // A simple custom plugin type. This should not usually be done with hand-written code. + /// struct AddOffsetPlugin(INT); + /// impl AddOffsetPlugin { + /// fn add_offset(&self, x: INT) -> Dynamic { + /// Dynamic::from(x + self.0) + /// } + /// } + /// impl Plugin for AddOffsetPlugin { + /// fn name(&self) -> &str { + /// "My Plugin" + /// } + /// fn register_contents(self, engine: &mut Engine) { + /// let add_offset_fn: Box Dynamic> = { + /// Box::new(move |x| self.add_offset(x)) + /// }; + /// engine.register_dynamic_fn("add_offset", add_offset_fn); + /// } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// + /// let mut engine = Engine::new(); + /// engine.register_plugin(AddOffsetPlugin(50)); + /// + /// assert_eq!(engine.eval::("add_offset(42)")?, 92); + /// # Ok(()) + /// # } + /// ``` + fn register_plugin(&mut self, plugin: PL); +} + /// A trait to register custom functions with the `Engine`. pub trait RegisterFn { /// Register a custom function with the `Engine`. @@ -98,6 +141,15 @@ pub trait RegisterResultFn { fn register_result_fn(&mut self, name: &str, f: FN); } +/// Represents an externally-written plugin for the Rhai interpreter. +/// +/// This should not be used directly. Use the `plugin_module!` macro instead. +#[cfg(feature = "plugins")] +pub trait Plugin { + fn register_contents(self, engine: &mut Engine); + fn name(&self) -> &str; +} + // These types are used to build a unique _marker_ tuple type for each combination // of function parameter types in order to make each trait implementation unique. // That is because stable Rust currently does not allow distinguishing implementations @@ -127,6 +179,13 @@ pub fn cloned(data: &mut T) -> T { data.clone() } +#[cfg(feature = "plugins")] +impl RegisterPlugin for Engine { + fn register_plugin(&mut self, plugin: PL) { + plugin.register_contents(self) + } +} + /// This macro counts the number of arguments via recursion. macro_rules! count_args { () => { 0_usize }; diff --git a/src/lib.rs b/src/lib.rs index e13eaf0f..d442ae9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,6 +89,8 @@ pub use engine::{calc_fn_spec as calc_fn_hash, Engine}; pub use error::{ParseError, ParseErrorType}; pub use fn_call::FuncArgs; pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; +#[cfg(feature = "plugins")] +pub use fn_register::{Plugin, RegisterPlugin}; pub use parser::{AST, INT}; pub use result::EvalAltResult; pub use scope::Scope; From 41366d08fe3ccfe0d56ce3835d606a4520309a80 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 27 Apr 2020 22:52:20 +0800 Subject: [PATCH 2/8] Fix tests and packages for all features. --- src/packages/map_basic.rs | 6 ++---- src/packages/string_more.rs | 2 +- src/packages/time_basic.rs | 4 ++-- tests/maps.rs | 1 + 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 44009e8a..40e2cec0 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -12,12 +12,10 @@ use crate::stdlib::{ }; fn map_get_keys(map: &mut Map) -> Vec { - map.iter() - .map(|(k, _)| k.to_string().into()) - .collect::>() + map.iter().map(|(k, _)| k.to_string().into()).collect() } fn map_get_values(map: &mut Map) -> Vec { - map.iter().map(|(_, v)| v.clone()).collect::>() + map.iter().map(|(_, v)| v.clone()).collect() } #[cfg(not(feature = "no_object"))] diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 4edef10e..0144792e 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -37,7 +37,7 @@ fn sub_string(s: &mut String, start: INT, len: INT) -> String { len as usize }; - chars[offset..][..len].into_iter().collect::() + chars[offset..][..len].into_iter().collect() } fn crop_string(s: &mut String, start: INT, len: INT) { let offset = if s.is_empty() || len <= 0 { diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index cb0c5f8e..173c3e22 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -87,10 +87,10 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, { #[cfg(not(feature = "unchecked"))] { if seconds > (MAX_INT as u64) { - return Err(EvalAltResult::ErrorArithmetic( + return Err(Box::new(EvalAltResult::ErrorArithmetic( format!("Integer overflow for timestamp.elapsed(): {}", seconds), Position::none(), - )); + ))); } } return Ok(seconds as INT); diff --git a/tests/maps.rs b/tests/maps.rs index 897f8430..64c5a4c2 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -143,6 +143,7 @@ fn test_map_return() -> Result<(), Box> { } #[test] +#[cfg(not(feature = "no_index"))] fn test_map_for() -> Result<(), Box> { let engine = Engine::new(); From 29159b359b151e136c76237ebf2e9389f818c0d8 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 10 May 2020 00:13:49 +0800 Subject: [PATCH 3/8] Refactor. --- src/engine.rs | 158 ++++++++++++++++------------ src/module.rs | 80 ++++----------- src/optimize.rs | 79 +++++++------- src/parser.rs | 262 +++++++++++++++++++++++++---------------------- tests/modules.rs | 13 +-- 5 files changed, 291 insertions(+), 301 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 110b29ee..7b29129c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -890,6 +890,8 @@ impl Engine { match rhs { // xxx.fn_name(arg_expr_list) Expr::FnCall(x) if x.1.is_none() => { + let ((name, pos), modules, hash, args, def_val) = x.as_ref(); + let mut args: Vec<_> = once(obj) .chain( idx_val @@ -898,10 +900,9 @@ impl Engine { .iter_mut(), ) .collect(); - let def_val = x.4.as_deref(); // A function call is assumed to have side effects, so the value is changed // TODO - Remove assumption of side effects by checking whether the first parameter is &mut - self.exec_fn_call(state, &x.0, x.2, &mut args, def_val, x.5, 0) + self.exec_fn_call(state, name, *hash, &mut args, def_val.as_ref(), *pos, 0) .map(|v| (v, true)) } // xxx.module::fn_name(...) - syntax error @@ -1034,17 +1035,18 @@ impl Engine { match dot_lhs { // id.??? or id[???] Expr::Variable(x) => { - let index = if state.always_search { None } else { x.3 }; - let (target, typ) = - search_scope(scope, &x.0, x.1.as_ref().map(|m| (m, x.2)), index, x.4)?; + let ((name, pos), modules, hash, index) = x.as_ref(); + let index = if state.always_search { None } else { *index }; + let mod_and_hash = modules.as_ref().map(|m| (m, *hash)); + let (target, typ) = search_scope(scope, &name, mod_and_hash, index, *pos)?; // Constants cannot be modified match typ { ScopeEntryType::Module => unreachable!(), ScopeEntryType::Constant if new_val.is_some() => { return Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( - x.0.clone(), - x.4, + name.clone(), + *pos, ))); } ScopeEntryType::Constant | ScopeEntryType::Normal => (), @@ -1290,9 +1292,10 @@ impl Engine { Expr::StringConstant(x) => Ok(x.0.to_string().into()), Expr::CharConstant(x) => Ok(x.0.into()), Expr::Variable(x) => { - let index = if state.always_search { None } else { x.3 }; - let mod_and_hash = x.1.as_ref().map(|m| (m, x.2)); - let (val, _) = search_scope(scope, &x.0, mod_and_hash, index, x.4)?; + let ((name, pos), modules, hash, index) = x.as_ref(); + let index = if state.always_search { None } else { *index }; + let mod_and_hash = modules.as_ref().map(|m| (m, *hash)); + let (val, _) = search_scope(scope, name, mod_and_hash, index, *pos)?; Ok(val.clone()) } Expr::Property(_) => unreachable!(), @@ -1308,12 +1311,14 @@ impl Engine { match &x.0 { // name = rhs Expr::Variable(x) => { - let index = if state.always_search { None } else { x.3 }; - let mod_and_hash = x.1.as_ref().map(|m| (m, x.2)); - let (value_ptr, typ) = search_scope(scope, &x.0, mod_and_hash, index, x.4)?; + let ((name, pos), modules, hash, index) = x.as_ref(); + let index = if state.always_search { None } else { *index }; + let mod_and_hash = modules.as_ref().map(|m| (m, *hash)); + let (value_ptr, typ) = + search_scope(scope, name, mod_and_hash, index, *pos)?; match typ { ScopeEntryType::Constant => Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant(x.0.clone(), x.4), + EvalAltResult::ErrorAssignmentToConstant(name.clone(), *pos), )), ScopeEntryType::Normal => { *value_ptr = rhs_val; @@ -1375,7 +1380,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] Expr::Map(x) => Ok(Dynamic(Union::Map(Box::new( x.0.iter() - .map(|(key, expr, _)| { + .map(|((key, _), expr)| { self.eval_expr(scope, state, expr, level) .map(|val| (key.clone(), val)) }) @@ -1384,25 +1389,28 @@ impl Engine { // Normal function call Expr::FnCall(x) if x.1.is_none() => { - let mut arg_values = - x.3.iter() - .map(|expr| self.eval_expr(scope, state, expr, level)) - .collect::, _>>()?; + let ((name, pos), _, hash_fn_def, args_expr, def_val) = x.as_ref(); + + let mut arg_values = args_expr + .iter() + .map(|expr| self.eval_expr(scope, state, expr, level)) + .collect::, _>>()?; let mut args: Vec<_> = arg_values.iter_mut().collect(); let hash_fn_spec = calc_fn_hash(empty(), KEYWORD_EVAL, once(TypeId::of::())); - if x.0 == KEYWORD_EVAL + if name == KEYWORD_EVAL && args.len() == 1 - && !self.has_override(state, hash_fn_spec, x.2) + && !self.has_override(state, hash_fn_spec, *hash_fn_def) { // eval - only in function call style let prev_len = scope.len(); // Evaluate the text string as a script - let result = self.eval_script_expr(scope, state, args[0], x.3[0].position()); + let result = + self.eval_script_expr(scope, state, args[0], args_expr[0].position()); if scope.len() != prev_len { // IMPORTANT! If the eval defines new variables in the current scope, @@ -1413,20 +1421,28 @@ impl Engine { result } else { // Normal function call - except for eval (handled above) - let def_value = x.4.as_deref(); - self.exec_fn_call(state, &x.0, x.2, &mut args, def_value, x.5, level) + self.exec_fn_call( + state, + name, + *hash_fn_def, + &mut args, + def_val.as_ref(), + *pos, + level, + ) } } // Module-qualified function call #[cfg(not(feature = "no_module"))] Expr::FnCall(x) if x.1.is_some() => { - let modules = x.1.as_ref().unwrap(); + let ((name, pos), modules, hash_fn_def, args_expr, def_val) = x.as_ref(); + let modules = modules.as_ref().unwrap(); - let mut arg_values = - x.3.iter() - .map(|expr| self.eval_expr(scope, state, expr, level)) - .collect::, _>>()?; + let mut arg_values = args_expr + .iter() + .map(|expr| self.eval_expr(scope, state, expr, level)) + .collect::, _>>()?; let mut args: Vec<_> = arg_values.iter_mut().collect(); @@ -1445,8 +1461,8 @@ impl Engine { }; // First search in script-defined functions (can override built-in) - if let Some(fn_def) = module.get_qualified_scripted_fn(x.2) { - self.call_script_fn(None, state, fn_def, &mut args, x.5, level) + if let Some(fn_def) = module.get_qualified_scripted_fn(*hash_fn_def) { + self.call_script_fn(None, state, fn_def, &mut args, *pos, level) } else { // Then search in Rust functions @@ -1455,13 +1471,13 @@ impl Engine { // i.e. qualifiers + function name + dummy parameter types (one for each parameter). // 2) Calculate a second hash with no qualifiers, empty function name, and // the actual list of parameter `TypeId`'.s - let hash2 = calc_fn_hash(empty(), "", args.iter().map(|a| a.type_id())); + let hash_fn_args = calc_fn_hash(empty(), "", args.iter().map(|a| a.type_id())); // 3) The final hash is the XOR of the two hashes. - let hash = x.2 ^ hash2; + let hash = *hash_fn_def ^ hash_fn_args; - match module.get_qualified_fn(&x.0, hash, x.5) { - Ok(func) => func(&mut args, x.5), - Err(_) if x.4.is_some() => Ok(x.4.as_deref().unwrap().clone()), + match module.get_qualified_fn(name, hash, *pos) { + Ok(func) => func(&mut args, *pos), + Err(_) if def_val.is_some() => Ok(def_val.clone().unwrap()), Err(err) => Err(err), } } @@ -1469,35 +1485,41 @@ impl Engine { Expr::In(x) => self.eval_in_expr(scope, state, &x.0, &x.1, level), - Expr::And(x) => Ok((self - .eval_expr(scope, state, &x.0, level)? + Expr::And(x) => { + let (lhs, rhs, _) = x.as_ref(); + Ok((self + .eval_expr(scope, state, lhs, level)? .as_bool() .map_err(|_| { - EvalAltResult::ErrorBooleanArgMismatch("AND".into(), x.0.position()) + EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) })? && // Short-circuit using && self - .eval_expr(scope, state, &x.1, level)? + .eval_expr(scope, state, rhs, level)? .as_bool() .map_err(|_| { - EvalAltResult::ErrorBooleanArgMismatch("AND".into(), x.1.position()) + EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) })?) - .into()), + .into()) + } - Expr::Or(x) => Ok((self - .eval_expr(scope, state, &x.0, level)? + Expr::Or(x) => { + let (lhs, rhs, _) = x.as_ref(); + Ok((self + .eval_expr(scope, state, lhs, level)? .as_bool() .map_err(|_| { - EvalAltResult::ErrorBooleanArgMismatch("OR".into(), x.0.position()) + EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) })? || // Short-circuit using || self - .eval_expr(scope, state, &x.1, level)? + .eval_expr(scope, state, rhs, level)? .as_bool() .map_err(|_| { - EvalAltResult::ErrorBooleanArgMismatch("OR".into(), x.1.position()) + EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) })?) - .into()), + .into()) + } Expr::True(_) => Ok(true.into()), Expr::False(_) => Ok(false.into()), @@ -1633,56 +1655,56 @@ impl Engine { Stmt::Break(pos) => Err(Box::new(EvalAltResult::ErrorLoopBreak(true, *pos))), // Return value - Stmt::ReturnWithVal(x) if x.0.is_some() && x.1 == ReturnType::Return => { + Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => { Err(Box::new(EvalAltResult::Return( - self.eval_expr(scope, state, x.0.as_ref().unwrap(), level)?, - x.2, + self.eval_expr(scope, state, x.1.as_ref().unwrap(), level)?, + (x.0).1, ))) } // Empty return - Stmt::ReturnWithVal(x) if x.1 == ReturnType::Return => { - Err(Box::new(EvalAltResult::Return(Default::default(), x.2))) + Stmt::ReturnWithVal(x) if (x.0).0 == ReturnType::Return => { + Err(Box::new(EvalAltResult::Return(Default::default(), (x.0).1))) } // Throw value - Stmt::ReturnWithVal(x) if x.0.is_some() && x.1 == ReturnType::Exception => { - let val = self.eval_expr(scope, state, x.0.as_ref().unwrap(), level)?; + Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => { + let val = self.eval_expr(scope, state, x.1.as_ref().unwrap(), level)?; Err(Box::new(EvalAltResult::ErrorRuntime( val.take_string().unwrap_or_else(|_| "".to_string()), - x.2, + (x.0).1, ))) } // Empty throw - Stmt::ReturnWithVal(x) if x.1 == ReturnType::Exception => { - Err(Box::new(EvalAltResult::ErrorRuntime("".into(), x.2))) + Stmt::ReturnWithVal(x) if (x.0).0 == ReturnType::Exception => { + Err(Box::new(EvalAltResult::ErrorRuntime("".into(), (x.0).1))) } Stmt::ReturnWithVal(_) => unreachable!(), // Let statement Stmt::Let(x) if x.1.is_some() => { - let val = self.eval_expr(scope, state, x.1.as_ref().unwrap(), level)?; + let ((var_name, _), expr) = x.as_ref(); + let val = self.eval_expr(scope, state, expr.as_ref().unwrap(), level)?; // TODO - avoid copying variable name in inner block? - let var_name = x.0.clone(); - scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false); + scope.push_dynamic_value(var_name.clone(), ScopeEntryType::Normal, val, false); Ok(Default::default()) } Stmt::Let(x) => { + let ((var_name, _), _) = x.as_ref(); // TODO - avoid copying variable name in inner block? - let var_name = x.0.clone(); - scope.push(var_name, ()); + scope.push(var_name.clone(), ()); Ok(Default::default()) } // Const statement Stmt::Const(x) if x.1.is_constant() => { - let val = self.eval_expr(scope, state, &x.1, level)?; + let ((var_name, _), expr) = x.as_ref(); + let val = self.eval_expr(scope, state, &expr, level)?; // TODO - avoid copying variable name in inner block? - let var_name = x.0.clone(); - scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true); + scope.push_dynamic_value(var_name.clone(), ScopeEntryType::Constant, val, true); Ok(Default::default()) } @@ -1691,7 +1713,7 @@ impl Engine { // Import statement Stmt::Import(x) => { - let (expr, name, _) = x.as_ref(); + let (expr, (name, _)) = x.as_ref(); #[cfg(feature = "no_module")] unreachable!(); @@ -1725,7 +1747,7 @@ impl Engine { // Export statement Stmt::Export(list) => { - for (id, id_pos, rename) in list.as_ref() { + for ((id, id_pos), rename) in list.as_ref() { let mut found = false; // Mark scope variables as public diff --git a/src/module.rs b/src/module.rs index 75c2b126..0519a44b 100644 --- a/src/module.rs +++ b/src/module.rs @@ -250,7 +250,7 @@ impl Module { /// use rhai::Module; /// /// let mut module = Module::new(); - /// let hash = module.set_fn_0("calc", || Ok(42_i64), false); + /// let hash = module.set_fn_0("calc", || Ok(42_i64)); /// assert!(module.contains_fn(hash)); /// ``` pub fn contains_fn(&self, hash: u64) -> bool { @@ -289,7 +289,7 @@ impl Module { /// use rhai::Module; /// /// let mut module = Module::new(); - /// let hash = module.set_fn_0("calc", || Ok(42_i64), false); + /// let hash = module.set_fn_0("calc", || Ok(42_i64)); /// assert!(module.get_fn(hash).is_some()); /// ``` pub fn set_fn_0, T: Into>( @@ -297,7 +297,6 @@ impl Module { fn_name: K, #[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn + 'static, #[cfg(feature = "sync")] func: impl Fn() -> FuncReturn + Send + Sync + 'static, - is_private: bool, ) -> u64 { let f = move |_: &mut FnCallArgs, pos| { func() @@ -305,12 +304,7 @@ impl Module { .map_err(|err| EvalAltResult::set_position(err, pos)) }; let arg_types = vec![]; - let access = if is_private { - FnAccess::Private - } else { - FnAccess::Public - }; - self.set_fn(fn_name.into(), access, arg_types, Box::new(f)) + self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f)) } /// Set a Rust function taking one parameter into the module, returning a hash key. @@ -323,7 +317,7 @@ impl Module { /// use rhai::Module; /// /// let mut module = Module::new(); - /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1), false); + /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); /// assert!(module.get_fn(hash).is_some()); /// ``` pub fn set_fn_1, A: Variant + Clone, T: Into>( @@ -331,7 +325,6 @@ impl Module { fn_name: K, #[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn + 'static, #[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn + Send + Sync + 'static, - is_private: bool, ) -> u64 { let f = move |args: &mut FnCallArgs, pos| { func(mem::take(args[0]).cast::()) @@ -339,12 +332,7 @@ impl Module { .map_err(|err| EvalAltResult::set_position(err, pos)) }; let arg_types = vec![TypeId::of::()]; - let access = if is_private { - FnAccess::Private - } else { - FnAccess::Public - }; - self.set_fn(fn_name.into(), access, arg_types, Box::new(f)) + self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f)) } /// Set a Rust function taking one mutable parameter into the module, returning a hash key. @@ -357,7 +345,7 @@ impl Module { /// use rhai::Module; /// /// let mut module = Module::new(); - /// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) }, false); + /// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) }); /// assert!(module.get_fn(hash).is_some()); /// ``` pub fn set_fn_1_mut, A: Variant + Clone, T: Into>( @@ -365,7 +353,6 @@ impl Module { fn_name: K, #[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn + 'static, #[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn + Send + Sync + 'static, - is_private: bool, ) -> u64 { let f = move |args: &mut FnCallArgs, pos| { func(args[0].downcast_mut::().unwrap()) @@ -373,12 +360,7 @@ impl Module { .map_err(|err| EvalAltResult::set_position(err, pos)) }; let arg_types = vec![TypeId::of::()]; - let access = if is_private { - FnAccess::Private - } else { - FnAccess::Public - }; - self.set_fn(fn_name.into(), access, arg_types, Box::new(f)) + self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f)) } /// Set a Rust function taking two parameters into the module, returning a hash key. @@ -393,7 +375,7 @@ impl Module { /// let mut module = Module::new(); /// let hash = module.set_fn_2("calc", |x: i64, y: String| { /// Ok(x + y.len() as i64) - /// }, false); + /// }); /// assert!(module.get_fn(hash).is_some()); /// ``` pub fn set_fn_2, A: Variant + Clone, B: Variant + Clone, T: Into>( @@ -401,7 +383,6 @@ impl Module { fn_name: K, #[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> FuncReturn + 'static, #[cfg(feature = "sync")] func: impl Fn(A, B) -> FuncReturn + Send + Sync + 'static, - is_private: bool, ) -> u64 { let f = move |args: &mut FnCallArgs, pos| { let a = mem::take(args[0]).cast::(); @@ -412,12 +393,7 @@ impl Module { .map_err(|err| EvalAltResult::set_position(err, pos)) }; let arg_types = vec![TypeId::of::(), TypeId::of::()]; - let access = if is_private { - FnAccess::Private - } else { - FnAccess::Public - }; - self.set_fn(fn_name.into(), access, arg_types, Box::new(f)) + self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f)) } /// Set a Rust function taking two parameters (the first one mutable) into the module, @@ -431,7 +407,7 @@ impl Module { /// let mut module = Module::new(); /// let hash = module.set_fn_2_mut("calc", |x: &mut i64, y: String| { /// *x += y.len() as i64; Ok(*x) - /// }, false); + /// }); /// assert!(module.get_fn(hash).is_some()); /// ``` pub fn set_fn_2_mut< @@ -444,7 +420,6 @@ impl Module { fn_name: K, #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn + 'static, #[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn + Send + Sync + 'static, - is_private: bool, ) -> u64 { let f = move |args: &mut FnCallArgs, pos| { let b = mem::take(args[1]).cast::(); @@ -455,12 +430,7 @@ impl Module { .map_err(|err| EvalAltResult::set_position(err, pos)) }; let arg_types = vec![TypeId::of::(), TypeId::of::()]; - let access = if is_private { - FnAccess::Private - } else { - FnAccess::Public - }; - self.set_fn(fn_name.into(), access, arg_types, Box::new(f)) + self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f)) } /// Set a Rust function taking three parameters into the module, returning a hash key. @@ -475,7 +445,7 @@ impl Module { /// let mut module = Module::new(); /// let hash = module.set_fn_3("calc", |x: i64, y: String, z: i64| { /// Ok(x + y.len() as i64 + z) - /// }, false); + /// }); /// assert!(module.get_fn(hash).is_some()); /// ``` pub fn set_fn_3< @@ -489,7 +459,6 @@ impl Module { fn_name: K, #[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> FuncReturn + 'static, #[cfg(feature = "sync")] func: impl Fn(A, B, C) -> FuncReturn + Send + Sync + 'static, - is_private: bool, ) -> u64 { let f = move |args: &mut FnCallArgs, pos| { let a = mem::take(args[0]).cast::(); @@ -501,12 +470,7 @@ impl Module { .map_err(|err| EvalAltResult::set_position(err, pos)) }; let arg_types = vec![TypeId::of::(), TypeId::of::(), TypeId::of::()]; - let access = if is_private { - FnAccess::Private - } else { - FnAccess::Public - }; - self.set_fn(fn_name.into(), access, arg_types, Box::new(f)) + self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f)) } /// Set a Rust function taking three parameters (the first one mutable) into the module, @@ -522,7 +486,7 @@ impl Module { /// let mut module = Module::new(); /// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: String, z: i64| { /// *x += y.len() as i64 + z; Ok(*x) - /// }, false); + /// }); /// assert!(module.get_fn(hash).is_some()); /// ``` pub fn set_fn_3_mut< @@ -536,7 +500,6 @@ impl Module { fn_name: K, #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> FuncReturn + 'static, #[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> FuncReturn + Send + Sync + 'static, - is_private: bool, ) -> u64 { let f = move |args: &mut FnCallArgs, pos| { let b = mem::take(args[1]).cast::(); @@ -548,12 +511,7 @@ impl Module { .map_err(|err| EvalAltResult::set_position(err, pos)) }; let arg_types = vec![TypeId::of::(), TypeId::of::(), TypeId::of::()]; - let access = if is_private { - FnAccess::Private - } else { - FnAccess::Public - }; - self.set_fn(fn_name.into(), access, arg_types, Box::new(f)) + self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f)) } /// Get a Rust function. @@ -567,7 +525,7 @@ impl Module { /// use rhai::Module; /// /// let mut module = Module::new(); - /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1), false); + /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); /// assert!(module.get_fn(hash).is_some()); /// ``` pub fn get_fn(&self, hash: u64) -> Option<&Box> { @@ -694,16 +652,16 @@ impl Module { // Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, // i.e. qualifiers + function name + dummy parameter types (one for each parameter). - let hash1 = calc_fn_hash( + let hash_fn_def = calc_fn_hash( qualifiers.iter().map(|v| *v), fn_name, repeat(EMPTY_TYPE_ID()).take(params.len()), ); // 2) Calculate a second hash with no qualifiers, empty function name, and // the actual list of parameter `TypeId`'.s - let hash2 = calc_fn_hash(empty(), "", params.iter().cloned()); + let hash_fn_args = calc_fn_hash(empty(), "", params.iter().cloned()); // 3) The final hash is the XOR of the two hashes. - let hash = hash1 ^ hash2; + let hash = hash_fn_def ^ hash_fn_args; functions.push((hash, func.clone())); } diff --git a/src/optimize.rs b/src/optimize.rs index 223949d6..09f294c6 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -220,15 +220,13 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - optimize_stmt(x.2, state, false), ))), // let id = expr; - Stmt::Let(x) if x.1.is_some() => Stmt::Let(Box::new(( - x.0, - Some(optimize_expr(x.1.unwrap(), state)), - x.2, - ))), + Stmt::Let(x) if x.1.is_some() => { + Stmt::Let(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state))))) + } // let id; stmt @ Stmt::Let(_) => stmt, // import expr as id; - Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1, x.2))), + Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1))), // { block } Stmt::Block(x) => { let orig_len = x.0.len(); // Original number of statements in the block, for change detection @@ -241,9 +239,10 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - .map(|stmt| match stmt { // Add constant into the state Stmt::Const(v) => { - state.push_constant(&v.0, v.1); + let ((name, pos), expr) = *v; + state.push_constant(&name, expr); state.set_dirty(); - Stmt::Noop(v.2) // No need to keep constants + Stmt::Noop(pos) // No need to keep constants } // Optimize the statement _ => optimize_stmt(stmt, state, preserve_result), @@ -336,11 +335,9 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - // expr; Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))), // return expr; - Stmt::ReturnWithVal(x) if x.0.is_some() => Stmt::ReturnWithVal(Box::new(( - Some(optimize_expr(x.0.unwrap(), state)), - x.1, - x.2, - ))), + Stmt::ReturnWithVal(x) if x.1.is_some() => { + Stmt::ReturnWithVal(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state))))) + } // All other statements - skip stmt => stmt, } @@ -394,13 +391,13 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { #[cfg(not(feature = "no_object"))] Expr::Dot(x) => match (x.0, x.1) { // map.string - (Expr::Map(m), Expr::Property(p)) if m.0.iter().all(|(_, x, _)| x.is_pure()) => { + (Expr::Map(m), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => { // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); let pos = m.1; - m.0.into_iter().find(|(name, _, _)| name == &p.0) - .map(|(_, expr, _)| expr.set_position(pos)) + m.0.into_iter().find(|((name, _), _)| name == &p.0) + .map(|(_, expr)| expr.set_position(pos)) .unwrap_or_else(|| Expr::Unit(pos)) } // lhs.rhs @@ -420,13 +417,13 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { a.0.remove(i.0 as usize).set_position(a.1) } // map[string] - (Expr::Map(m), Expr::StringConstant(s)) if m.0.iter().all(|(_, x, _)| x.is_pure()) => { + (Expr::Map(m), Expr::StringConstant(s)) if m.0.iter().all(|(_, x)| x.is_pure()) => { // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); let pos = m.1; - m.0.into_iter().find(|(name, _, _)| name == &s.0) - .map(|(_, expr, _)| expr.set_position(pos)) + m.0.into_iter().find(|((name, _), _)| name == &s.0) + .map(|(_, expr)| expr.set_position(pos)) .unwrap_or_else(|| Expr::Unit(pos)) } // string[int] @@ -448,7 +445,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { #[cfg(not(feature = "no_object"))] Expr::Map(m) => Expr::Map(Box::new((m.0 .into_iter() - .map(|(key, expr, pos)| (key, optimize_expr(expr, state), pos)) + .map(|((key, pos), expr)| ((key, pos), optimize_expr(expr, state))) .collect(), m.1))), // lhs in rhs Expr::In(x) => match (x.0, x.1) { @@ -465,7 +462,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { // "xxx" in #{...} (Expr::StringConstant(a), Expr::Map(b)) => { state.set_dirty(); - if b.0.iter().find(|(name, _, _)| name == &a.0).is_some() { + if b.0.iter().find(|((name, _), _)| name == &a.0).is_some() { Expr::True(a.1) } else { Expr::False(a.1) @@ -476,7 +473,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { state.set_dirty(); let ch = a.0.to_string(); - if b.0.iter().find(|(name, _, _)| name == &ch).is_some() { + if b.0.iter().find(|((name, _), _)| name == &ch).is_some() { Expr::True(a.1) } else { Expr::False(a.1) @@ -527,7 +524,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { }, // Do not call some special keywords - Expr::FnCall(mut x) if DONT_EVAL_KEYWORDS.contains(&x.0.as_ref().as_ref())=> { + Expr::FnCall(mut x) if DONT_EVAL_KEYWORDS.contains(&(x.0).0.as_ref())=> { x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); Expr::FnCall(x) } @@ -538,25 +535,27 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { && state.optimization_level == OptimizationLevel::Full // full optimizations && x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants => { + let ((name, pos), _, _, args, def_value) = x.as_mut(); + // First search in script-defined functions (can override built-in) - if state.fn_lib.iter().find(|(name, len)| *name == x.0 && *len == x.3.len()).is_some() { + if state.fn_lib.iter().find(|(id, len)| *id == name && *len == args.len()).is_some() { // A script-defined function overrides the built-in function - do not make the call x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); return Expr::FnCall(x); } - let mut arg_values: Vec<_> = x.3.iter().map(Expr::get_constant_value).collect(); + let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); let mut call_args: Vec<_> = arg_values.iter_mut().collect(); // Save the typename of the first argument if it is `type_of()` // This is to avoid `call_args` being passed into the closure - let arg_for_type_of = if x.0 == KEYWORD_TYPE_OF && call_args.len() == 1 { + let arg_for_type_of = if name == KEYWORD_TYPE_OF && call_args.len() == 1 { state.engine.map_type_name(call_args[0].type_name()) } else { "" }; - call_fn(&state.engine.packages, &state.engine.base_package, &x.0, &mut call_args, x.5).ok() + call_fn(&state.engine.packages, &state.engine.base_package, name, &mut call_args, *pos).ok() .and_then(|result| result.or_else(|| { if !arg_for_type_of.is_empty() { @@ -564,9 +563,9 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { Some(arg_for_type_of.to_string().into()) } else { // Otherwise use the default value, if any - x.4.clone().map(|v| *v) + def_value.clone() } - }).and_then(|result| map_dynamic_to_expr(result, x.5)) + }).and_then(|result| map_dynamic_to_expr(result, *pos)) .map(|expr| { state.set_dirty(); expr @@ -585,11 +584,12 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { } // constant-name - Expr::Variable(x) if x.1.is_none() && state.contains_constant(&x.0) => { + Expr::Variable(x) if x.1.is_none() && state.contains_constant(&(x.0).0) => { + let (name, pos) = x.0; state.set_dirty(); // Replace constant with value - state.find_constant(&x.0).expect("should find constant in scope!").clone().set_position(x.4) + state.find_constant(&name).expect("should find constant in scope!").clone().set_position(pos) } // All other expressions - skip @@ -643,9 +643,10 @@ fn optimize<'a>( .enumerate() .map(|(i, stmt)| { match &stmt { - Stmt::Const(x) => { + Stmt::Const(v) => { // Load constants - state.push_constant(&x.0, x.1.clone()); + let ((name, _), expr) = v.as_ref(); + state.push_constant(&name, expr.clone()); stmt // Keep it in the global scope } _ => { @@ -718,12 +719,16 @@ pub fn optimize_into_ast( // {} -> Noop fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { // { return val; } -> val - Stmt::ReturnWithVal(x) if x.0.is_some() && x.1 == ReturnType::Return => { - Stmt::Expr(Box::new(x.0.unwrap())) + Stmt::ReturnWithVal(x) + if x.1.is_some() && (x.0).0 == ReturnType::Return => + { + Stmt::Expr(Box::new(x.1.unwrap())) } // { return; } -> () - Stmt::ReturnWithVal(x) if x.0.is_none() && x.1 == ReturnType::Return => { - Stmt::Expr(Box::new(Expr::Unit(x.2))) + Stmt::ReturnWithVal(x) + if x.1.is_none() && (x.0).0 == ReturnType::Return => + { + Stmt::Expr(Box::new(Expr::Unit((x.0).1))) } // All others stmt => stmt, diff --git a/src/parser.rs b/src/parser.rs index 7a88abfc..8041dcef 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -280,9 +280,9 @@ pub enum Stmt { /// for id in expr { stmt } For(Box<(String, Expr, Stmt)>), /// let id = expr - Let(Box<(String, Option, Position)>), + Let(Box<((String, Position), Option)>), /// const id = expr - Const(Box<(String, Expr, Position)>), + Const(Box<((String, Position), Expr)>), /// { stmt; ... } Block(Box<(Vec, Position)>), /// { stmt } @@ -292,11 +292,11 @@ pub enum Stmt { /// break Break(Position), /// return/throw - ReturnWithVal(Box<(Option, ReturnType, Position)>), + ReturnWithVal(Box<((ReturnType, Position), Option)>), /// import expr as module - Import(Box<(Expr, String, Position)>), + Import(Box<(Expr, (String, Position))>), /// expr id as name, ... - Export(Box)>>), + Export(Box)>>), } impl Stmt { @@ -304,17 +304,17 @@ impl Stmt { pub fn position(&self) -> Position { match self { Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos, - Stmt::Let(x) => x.2, - Stmt::Const(x) => x.2, - Stmt::ReturnWithVal(x) => x.2, + Stmt::Let(x) => (x.0).1, + Stmt::Const(x) => (x.0).1, + Stmt::ReturnWithVal(x) => (x.0).1, Stmt::Block(x) => x.1, Stmt::IfThenElse(x) => x.0.position(), Stmt::Expr(x) => x.position(), Stmt::While(x) => x.1.position(), Stmt::Loop(x) => x.position(), Stmt::For(x) => x.2.position(), - Stmt::Import(x) => x.2, - Stmt::Export(x) => x.get(0).unwrap().1, + Stmt::Import(x) => (x.1).1, + Stmt::Export(x) => (x.get(0).unwrap().0).1, } } @@ -379,23 +379,22 @@ pub enum Expr { CharConstant(Box<(char, Position)>), /// String constant. StringConstant(Box<(String, Position)>), - /// Variable access - (variable name, optional modules, hash, optional index, position) - Variable(Box<(String, MRef, u64, Option, Position)>), + /// Variable access - ((variable name, position), optional modules, hash, optional index) + Variable(Box<((String, Position), MRef, u64, Option)>), /// Property access. Property(Box<(String, Position)>), /// { stmt } Stmt(Box<(Stmt, Position)>), - /// func(expr, ... ) - (function name, optional modules, hash, arguments, optional default value, position) + /// func(expr, ... ) - ((function name, position), optional modules, hash, arguments, optional default value) /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls /// and the function names are predictable, so no need to allocate a new `String`. FnCall( Box<( - Cow<'static, str>, + (Cow<'static, str>, Position), MRef, u64, Vec, - Option>, - Position, + Option, )>, ), /// expr = expr @@ -407,7 +406,7 @@ pub enum Expr { /// [ expr, ... ] Array(Box<(Vec, Position)>), /// #{ name:expr, ... } - Map(Box<(Vec<(String, Expr, Position)>, Position)>), + Map(Box<(Vec<((String, Position), Expr)>, Position)>), /// lhs in rhs In(Box<(Expr, Expr, Position)>), /// lhs && rhs @@ -445,10 +444,10 @@ impl Expr { ))), #[cfg(not(feature = "no_object"))] - Self::Map(x) if x.0.iter().all(|(_, v, _)| v.is_constant()) => { + Self::Map(x) if x.0.iter().all(|(_, v)| v.is_constant()) => { Dynamic(Union::Map(Box::new( x.0.iter() - .map(|(k, v, _)| (k.clone(), v.get_constant_value())) + .map(|((k, _), v)| (k.clone(), v.get_constant_value())) .collect::>(), ))) } @@ -493,8 +492,8 @@ impl Expr { Self::Map(x) => x.1, Self::Property(x) => x.1, Self::Stmt(x) => x.1, - Self::Variable(x) => x.4, - Self::FnCall(x) => x.5, + Self::Variable(x) => (x.0).1, + Self::FnCall(x) => (x.0).1, Self::And(x) | Self::Or(x) | Self::In(x) => x.2, @@ -515,10 +514,10 @@ impl Expr { Self::StringConstant(x) => x.1 = new_pos, Self::Array(x) => x.1 = new_pos, Self::Map(x) => x.1 = new_pos, - Self::Variable(x) => x.4 = new_pos, + Self::Variable(x) => (x.0).1 = new_pos, Self::Property(x) => x.1 = new_pos, Self::Stmt(x) => x.1 = new_pos, - Self::FnCall(x) => x.5 = new_pos, + Self::FnCall(x) => (x.0).1 = new_pos, Self::And(x) => x.2 = new_pos, Self::Or(x) => x.2 = new_pos, Self::In(x) => x.2 = new_pos, @@ -541,7 +540,8 @@ impl Expr { Self::Array(x) => x.0.iter().all(Self::is_pure), Self::Index(x) | Self::And(x) | Self::Or(x) | Self::In(x) => { - x.0.is_pure() && x.1.is_pure() + let (lhs, rhs, _) = x.as_ref(); + lhs.is_pure() && rhs.is_pure() } Self::Stmt(x) => x.0.is_pure(), @@ -569,7 +569,7 @@ impl Expr { Self::Array(x) => x.0.iter().all(Self::is_constant), // An map literal is constant if all items are constant - Self::Map(x) => x.0.iter().map(|(_, expr, _)| expr).all(Self::is_constant), + Self::Map(x) => x.0.iter().map(|(_, expr)| expr).all(Self::is_constant), // Check in expression Self::In(x) => match (&x.0, &x.1) { @@ -626,7 +626,10 @@ impl Expr { /// Convert a `Variable` into a `Property`. All other variants are untouched. pub(crate) fn into_property(self) -> Self { match self { - Self::Variable(x) if x.1.is_none() => Self::Property(Box::new((x.0.clone(), x.4))), + Self::Variable(x) if x.1.is_none() => { + let (name, pos) = x.0; + Self::Property(Box::new((name.clone(), pos))) + } _ => self, } } @@ -713,34 +716,31 @@ fn parse_call_expr<'a>( eat_token(input, Token::RightParen); #[cfg(not(feature = "no_module"))] - let hash1 = { + let hash_fn_def = { if let Some(modules) = modules.as_mut() { modules.set_index(stack.find_module(&modules.get(0).0)); // Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, // i.e. qualifiers + function name + no parameters. - let hash1 = calc_fn_hash(modules.iter().map(|(m, _)| m.as_str()), &id, empty()); // 2) Calculate a second hash with no qualifiers, empty function name, and // the actual list of parameter `TypeId`'.s // 3) The final hash is the XOR of the two hashes. - - hash1 + calc_fn_hash(modules.iter().map(|(m, _)| m.as_str()), &id, empty()) } else { calc_fn_hash(empty(), &id, empty()) } }; // Qualifiers (none) + function name + no parameters. #[cfg(feature = "no_module")] - let hash1 = calc_fn_hash(empty(), &id, empty()); + let hash_fn_def = calc_fn_hash(empty(), &id, empty()); return Ok(Expr::FnCall(Box::new(( - id.into(), + (id.into(), begin), modules, - hash1, + hash_fn_def, args, None, - begin, )))); } // id... @@ -756,38 +756,36 @@ fn parse_call_expr<'a>( eat_token(input, Token::RightParen); #[cfg(not(feature = "no_module"))] - let hash1 = { + let hash_fn_def = { if let Some(modules) = modules.as_mut() { modules.set_index(stack.find_module(&modules.get(0).0)); // Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, // i.e. qualifiers + function name + dummy parameter types (one for each parameter). - let hash1 = calc_fn_hash( - modules.iter().map(|(m, _)| m.as_str()), - &id, - repeat(EMPTY_TYPE_ID()).take(args.len()), - ); // 2) Calculate a second hash with no qualifiers, empty function name, and // the actual list of parameter `TypeId`'.s // 3) The final hash is the XOR of the two hashes. - - hash1 + calc_fn_hash( + modules.iter().map(|(m, _)| m.as_str()), + &id, + repeat(EMPTY_TYPE_ID()).take(args.len()), + ) } else { calc_fn_hash(empty(), &id, repeat(EMPTY_TYPE_ID()).take(args.len())) } }; // Qualifiers (none) + function name + dummy parameter types (one for each parameter). #[cfg(feature = "no_module")] - let hash1 = calc_fn_hash(empty(), &id, repeat(EMPTY_TYPE_ID()).take(args.len())); + let hash_fn_def = + calc_fn_hash(empty(), &id, repeat(EMPTY_TYPE_ID()).take(args.len())); return Ok(Expr::FnCall(Box::new(( - id.into(), + (id.into(), begin), modules, - hash1, + hash_fn_def, args, None, - begin, )))); } // id(...args, @@ -1094,7 +1092,7 @@ fn parse_map_literal<'a>( let expr = parse_expr(input, stack, allow_stmt_expr)?; - map.push((name, expr, pos)); + map.push(((name, pos), expr)); match input.peek().unwrap() { (Token::Comma, _) => { @@ -1127,11 +1125,11 @@ fn parse_map_literal<'a>( // Check for duplicating properties map.iter() .enumerate() - .try_for_each(|(i, (k1, _, _))| { + .try_for_each(|(i, ((k1, _), _))| { map.iter() .skip(i + 1) - .find(|(k2, _, _)| k2 == k1) - .map_or_else(|| Ok(()), |(k2, _, pos)| Err((k2, *pos))) + .find(|((k2, _), _)| k2 == k1) + .map_or_else(|| Ok(()), |((k2, pos), _)| Err((k2, *pos))) }) .map_err(|(key, pos)| PERR::DuplicatedProperty(key.to_string()).into_err(pos))?; @@ -1163,7 +1161,7 @@ fn parse_primary<'a>( Token::StringConst(s) => Expr::StringConstant(Box::new((s, pos))), Token::Identifier(s) => { let index = stack.find(&s); - Expr::Variable(Box::new((s, None, 0, index, pos))) + Expr::Variable(Box::new(((s, pos), None, 0, index))) } Token::LeftParen => parse_paren_expr(input, stack, pos, allow_stmt_expr)?, #[cfg(not(feature = "no_index"))] @@ -1191,24 +1189,27 @@ fn parse_primary<'a>( root_expr = match (root_expr, token) { // Function call (Expr::Variable(x), Token::LeftParen) => { - parse_call_expr(input, stack, x.0, x.1, x.4, allow_stmt_expr)? + let ((name, pos), modules, _, _) = *x; + parse_call_expr(input, stack, name, modules, pos, allow_stmt_expr)? } (Expr::Property(x), Token::LeftParen) => { - parse_call_expr(input, stack, x.0, None, x.1, allow_stmt_expr)? + let (name, pos) = *x; + parse_call_expr(input, stack, name, None, pos, allow_stmt_expr)? } // module access #[cfg(not(feature = "no_module"))] - (Expr::Variable(mut x), Token::DoubleColon) => match input.next().unwrap() { + (Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() { (Token::Identifier(id2), pos2) => { - if let Some(ref mut modules) = x.1 { - modules.push((x.0, x.4)); + let ((name, pos), mut modules, _, index) = *x; + if let Some(ref mut modules) = modules { + modules.push((name, pos)); } else { let mut m: ModuleRef = Default::default(); - m.push((x.0, x.4)); - x.1 = Some(Box::new(m)); + m.push((name, pos)); + modules = Some(Box::new(m)); } - Expr::Variable(Box::new((id2, x.1, 0, x.3, pos2))) + Expr::Variable(Box::new(((id2, pos2), modules, 0, index))) } (_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)), }, @@ -1226,10 +1227,11 @@ fn parse_primary<'a>( // Cache the hash key for module-qualified variables #[cfg(not(feature = "no_module"))] Expr::Variable(x) if x.1.is_some() => { - let modules = x.1.as_mut().unwrap(); + let ((name, _), modules, hash, _) = x.as_mut(); + let modules = modules.as_mut().unwrap(); // Qualifiers + variable name - x.2 = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), &x.0, empty()); + *hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, empty()); modules.set_index(stack.find_module(&modules.get(0).0)); } _ => (), @@ -1292,12 +1294,11 @@ fn parse_unary<'a>( let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2)); Ok(Expr::FnCall(Box::new(( - op.into(), + (op.into(), pos), None, hash, vec![e], None, - pos, )))) } } @@ -1310,16 +1311,17 @@ fn parse_unary<'a>( // !expr (Token::Bang, _) => { let pos = eat_token(input, Token::Bang); + let expr = vec![parse_primary(input, stack, allow_stmt_expr)?]; + let op = "!"; let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2)); Ok(Expr::FnCall(Box::new(( - op.into(), + (op.into(), pos), None, hash, - vec![parse_primary(input, stack, allow_stmt_expr)?], - Some(Box::new(false.into())), // NOT operator, when operating on invalid operand, defaults to false - pos, + expr, + Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false )))) } // @@ -1338,11 +1340,12 @@ fn make_assignment_stmt<'a>( match &lhs { Expr::Variable(x) if x.3.is_none() => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))), Expr::Variable(x) => { - match stack[(stack.len() - x.3.unwrap().get())].1 { + let ((name, name_pos), _, _, index) = x.as_ref(); + match stack[(stack.len() - index.unwrap().get())].1 { ScopeEntryType::Normal => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))), // Constant values cannot be assigned to ScopeEntryType::Constant => { - Err(PERR::AssignmentToConstant(x.0.to_string()).into_err(x.4)) + Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos)) } ScopeEntryType::Module => unreachable!(), } @@ -1350,11 +1353,12 @@ fn make_assignment_stmt<'a>( Expr::Index(x) | Expr::Dot(x) => match &x.0 { Expr::Variable(x) if x.3.is_none() => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))), Expr::Variable(x) => { - match stack[(stack.len() - x.3.unwrap().get())].1 { + let ((name, name_pos), _, _, index) = x.as_ref(); + match stack[(stack.len() - index.unwrap().get())].1 { ScopeEntryType::Normal => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))), // Constant values cannot be assigned to ScopeEntryType::Constant => { - Err(PERR::AssignmentToConstant(x.0.to_string()).into_err(x.4)) + Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos)) } ScopeEntryType::Module => unreachable!(), } @@ -1403,7 +1407,7 @@ fn parse_op_assignment_stmt<'a>( // lhs op= rhs -> lhs = op(lhs, rhs) let args = vec![lhs_copy, rhs]; let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(args.len())); - let rhs_expr = Expr::FnCall(Box::new((op.into(), None, hash, args, None, pos))); + let rhs_expr = Expr::FnCall(Box::new(((op.into(), pos), None, hash, args, None))); make_assignment_stmt(stack, lhs, rhs_expr, pos) } @@ -1423,15 +1427,13 @@ fn make_dot_expr( // lhs.id (lhs, Expr::Variable(x)) if x.1.is_none() => { let lhs = if is_index { lhs.into_property() } else { lhs }; - Expr::Dot(Box::new(( - lhs, - Expr::Property(Box::new((x.0, x.4))), - op_pos, - ))) + let rhs = Expr::Property(Box::new(x.0)); + Expr::Dot(Box::new((lhs, rhs, op_pos))) } (lhs, Expr::Property(x)) => { let lhs = if is_index { lhs.into_property() } else { lhs }; - Expr::Dot(Box::new((lhs, Expr::Property(x), op_pos))) + let rhs = Expr::Property(x); + Expr::Dot(Box::new((lhs, rhs, op_pos))) } // lhs.module::id - syntax error (_, Expr::Variable(x)) if x.1.is_some() => { @@ -1441,17 +1443,31 @@ fn make_dot_expr( return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get(0).1)); } // lhs.dot_lhs.dot_rhs - (lhs, Expr::Dot(x)) => Expr::Dot(Box::new(( - lhs, - Expr::Dot(Box::new((x.0.into_property(), x.1.into_property(), x.2))), - op_pos, - ))), + (lhs, Expr::Dot(x)) => { + let (dot_lhs, dot_rhs, pos) = *x; + Expr::Dot(Box::new(( + lhs, + Expr::Dot(Box::new(( + dot_lhs.into_property(), + dot_rhs.into_property(), + pos, + ))), + op_pos, + ))) + } // lhs.idx_lhs[idx_rhs] - (lhs, Expr::Index(x)) => Expr::Dot(Box::new(( - lhs, - Expr::Index(Box::new((x.0.into_property(), x.1.into_property(), x.2))), - op_pos, - ))), + (lhs, Expr::Index(x)) => { + let (idx_lhs, idx_rhs, pos) = *x; + Expr::Dot(Box::new(( + lhs, + Expr::Index(Box::new(( + idx_lhs.into_property(), + idx_rhs.into_property(), + pos, + ))), + op_pos, + ))) + } // lhs.rhs (lhs, rhs) => Expr::Dot(Box::new((lhs, rhs.into_property(), op_pos))), }) @@ -1664,32 +1680,32 @@ fn parse_binary_op<'a>( rhs }; - let cmp_default = Some(Box::new(false.into())); + let cmp_def = Some(false.into()); let op = op_token.syntax(); let hash = calc_fn_hash(empty(), &op, repeat(EMPTY_TYPE_ID()).take(2)); let mut args = vec![current_lhs, rhs]; current_lhs = match op_token { - Token::Plus => Expr::FnCall(Box::new((op, None, hash, args, None, pos))), - Token::Minus => Expr::FnCall(Box::new((op, None, hash, args, None, pos))), - Token::Multiply => Expr::FnCall(Box::new((op, None, hash, args, None, pos))), - Token::Divide => Expr::FnCall(Box::new((op, None, hash, args, None, pos))), + Token::Plus => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), + Token::Minus => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), + Token::Multiply => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), + Token::Divide => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), - Token::LeftShift => Expr::FnCall(Box::new((op, None, hash, args, None, pos))), - Token::RightShift => Expr::FnCall(Box::new((op, None, hash, args, None, pos))), - Token::Modulo => Expr::FnCall(Box::new((op, None, hash, args, None, pos))), - Token::PowerOf => Expr::FnCall(Box::new((op, None, hash, args, None, pos))), + Token::LeftShift => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), + Token::RightShift => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), + Token::Modulo => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), + Token::PowerOf => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), // Comparison operators default to false when passed invalid operands - Token::EqualsTo => Expr::FnCall(Box::new((op, None, hash, args, cmp_default, pos))), - Token::NotEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, cmp_default, pos))), - Token::LessThan => Expr::FnCall(Box::new((op, None, hash, args, cmp_default, pos))), + Token::EqualsTo => Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))), + Token::NotEqualsTo => Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))), + Token::LessThan => Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))), Token::LessThanEqualsTo => { - Expr::FnCall(Box::new((op, None, hash, args, cmp_default, pos))) + Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))) } - Token::GreaterThan => Expr::FnCall(Box::new((op, None, hash, args, cmp_default, pos))), + Token::GreaterThan => Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))), Token::GreaterThanEqualsTo => { - Expr::FnCall(Box::new((op, None, hash, args, cmp_default, pos))) + Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))) } Token::Or => { @@ -1702,9 +1718,9 @@ fn parse_binary_op<'a>( let current_lhs = args.pop().unwrap(); Expr::And(Box::new((current_lhs, rhs, pos))) } - Token::Ampersand => Expr::FnCall(Box::new((op, None, hash, args, None, pos))), - Token::Pipe => Expr::FnCall(Box::new((op, None, hash, args, None, pos))), - Token::XOr => Expr::FnCall(Box::new((op, None, hash, args, None, pos))), + Token::Ampersand => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), + Token::Pipe => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), + Token::XOr => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), Token::In => { let rhs = args.pop().unwrap(); @@ -1916,12 +1932,12 @@ fn parse_let<'a>( // let name = expr ScopeEntryType::Normal => { stack.push((name.clone(), ScopeEntryType::Normal)); - Ok(Stmt::Let(Box::new((name, Some(init_value), pos)))) + Ok(Stmt::Let(Box::new(((name, pos), Some(init_value))))) } // const name = { expr:constant } ScopeEntryType::Constant if init_value.is_constant() => { stack.push((name.clone(), ScopeEntryType::Constant)); - Ok(Stmt::Const(Box::new((name, init_value, pos)))) + Ok(Stmt::Const(Box::new(((name, pos), init_value)))) } // const name = expr - error ScopeEntryType::Constant => { @@ -1935,11 +1951,11 @@ fn parse_let<'a>( match var_type { ScopeEntryType::Normal => { stack.push((name.clone(), ScopeEntryType::Normal)); - Ok(Stmt::Let(Box::new((name, None, pos)))) + Ok(Stmt::Let(Box::new(((name, pos), None)))) } ScopeEntryType::Constant => { stack.push((name.clone(), ScopeEntryType::Constant)); - Ok(Stmt::Const(Box::new((name, Expr::Unit(pos), pos)))) + Ok(Stmt::Const(Box::new(((name, pos), Expr::Unit(pos))))) } // Variable cannot be a module ScopeEntryType::Module => unreachable!(), @@ -1978,7 +1994,7 @@ fn parse_import<'a>( }; stack.push((name.clone(), ScopeEntryType::Module)); - Ok(Stmt::Import(Box::new((expr, name, pos)))) + Ok(Stmt::Import(Box::new((expr, (name, pos))))) } /// Parse an export statement. @@ -2005,7 +2021,7 @@ fn parse_export<'a>(input: &mut Peekable>) -> Result { @@ -2026,14 +2042,14 @@ fn parse_export<'a>(input: &mut Peekable>) -> Result( match input.peek().unwrap() { // `return`/`throw` at - (Token::EOF, pos) => Ok(Stmt::ReturnWithVal(Box::new((None, return_type, *pos)))), + (Token::EOF, pos) => Ok(Stmt::ReturnWithVal(Box::new(((return_type, *pos), None)))), // `return;` or `throw;` (Token::SemiColon, _) => { - Ok(Stmt::ReturnWithVal(Box::new((None, return_type, pos)))) + Ok(Stmt::ReturnWithVal(Box::new(((return_type, pos), None)))) } // `return` or `throw` with expression (_, _) => { @@ -2176,9 +2192,8 @@ fn parse_stmt<'a>( let pos = expr.position(); Ok(Stmt::ReturnWithVal(Box::new(( + (return_type, pos), Some(expr), - return_type, - pos, )))) } } @@ -2448,13 +2463,14 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { Union::Map(map) => { let items: Vec<_> = map .into_iter() - .map(|(k, v)| (k, map_dynamic_to_expr(v, pos), pos)) + .map(|(k, v)| ((k, pos), map_dynamic_to_expr(v, pos))) .collect(); - if items.iter().all(|(_, expr, _)| expr.is_some()) { + + if items.iter().all(|(_, expr)| expr.is_some()) { Some(Expr::Map(Box::new(( items .into_iter() - .map(|(k, expr, pos)| (k, expr.unwrap(), pos)) + .map(|((k, pos), expr)| ((k, pos), expr.unwrap())) .collect(), pos, )))) diff --git a/tests/modules.rs b/tests/modules.rs index 1fc4d895..52f6818e 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -18,12 +18,7 @@ fn test_module_sub_module() -> Result<(), Box> { let mut sub_module2 = Module::new(); sub_module2.set_var("answer", 41 as INT); - let hash_inc = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1), false); - let hash_hidden = sub_module2.set_fn_0( - "hidden", - || Err("shouldn't see me!".into()) as Result<(), Box>, - true, - ); + let hash_inc = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1)); sub_module.set_sub_module("universe", sub_module2); module.set_sub_module("life", sub_module); @@ -36,7 +31,6 @@ fn test_module_sub_module() -> Result<(), Box> { assert!(m2.contains_var("answer")); assert!(m2.contains_fn(hash_inc)); - assert!(m2.contains_fn(hash_hidden)); assert_eq!(m2.get_var_value::("answer").unwrap(), 41); @@ -59,11 +53,6 @@ fn test_module_sub_module() -> Result<(), Box> { )?, 42 ); - assert!(matches!( - *engine.eval_expression_with_scope::<()>(&mut scope, "question::life::universe::hidden()") - .expect_err("should error"), - EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "hidden" - )); Ok(()) } From cd570558c32e9b67e298d524a542a3e4ef9174d8 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 21 May 2020 09:31:31 +0800 Subject: [PATCH 4/8] Remove count_args macro. --- src/fn_register.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/fn_register.rs b/src/fn_register.rs index 4b98f184..f33c5855 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -190,12 +190,6 @@ impl RegisterPlugin for Engine { } } -/// This macro counts the number of arguments via recursion. -macro_rules! count_args { - () => { 0_usize }; - ( $head:ident $($tail:ident)* ) => { 1_usize + count_args!($($tail)*) }; -} - /// This macro creates a closure wrapping a registered function. macro_rules! make_func { ($fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => { From 27b8f9929d38254e94bd4565361399ae27aab7ed Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Tue, 2 Jun 2020 21:44:26 -0500 Subject: [PATCH 5/8] Basic Implementation of Plugins and Plugin Functions --- src/engine.rs | 1 + src/fn_native.rs | 47 +++++++++++++++++++++++++ src/fn_register.rs | 87 ++++++++++++++++++++++++++++++---------------- src/lib.rs | 9 +++-- src/plugin.rs | 42 ++++++++++++++++++++++ 5 files changed, 153 insertions(+), 33 deletions(-) create mode 100644 src/plugin.rs diff --git a/src/engine.rs b/src/engine.rs index 453ee184..d578aaf8 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1652,6 +1652,7 @@ impl Engine { let mut scope = Scope::new(); self.call_script_fn(&mut scope, state, lib, name, fn_def, args, *pos, level) } + Ok(x) if x.is_plugin_fn() => x.get_plugin_fn().call(args.as_mut(), *pos), Ok(x) => x.get_native_fn()(args.as_mut()).map_err(|err| err.new_position(*pos)), Err(err) if def_val.is_some() diff --git a/src/fn_native.rs b/src/fn_native.rs index 6c43adc6..35ddcfdf 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,6 +1,7 @@ use crate::any::Dynamic; use crate::parser::FnDef; use crate::result::EvalAltResult; +use crate::token::Position; use crate::stdlib::{boxed::Box, rc::Rc, sync::Arc}; @@ -57,6 +58,13 @@ pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result> pub type IteratorFn = fn(Dynamic) -> Box>; +#[cfg(feature = "sync")] +pub type SharedPluginFunction = Arc; +#[cfg(not(feature = "sync"))] +pub type SharedPluginFunction = Rc; + +use crate::plugin::PluginFunction; + /// A type encapsulating a function callable by Rhai. #[derive(Clone)] pub enum CallableFunction { @@ -69,6 +77,8 @@ pub enum CallableFunction { Iterator(IteratorFn), /// A script-defined function. Script(Shared), + /// A plugin-defined function, + Plugin(SharedPluginFunction), } impl CallableFunction { @@ -77,6 +87,7 @@ impl CallableFunction { match self { Self::Pure(_) => true, Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false, + Self::Plugin(_) => false, } } /// Is this a pure native Rust method-call? @@ -84,6 +95,7 @@ impl CallableFunction { match self { Self::Method(_) => true, Self::Pure(_) | Self::Iterator(_) | Self::Script(_) => false, + Self::Plugin(_) => false, } } /// Is this an iterator function? @@ -91,6 +103,7 @@ impl CallableFunction { match self { Self::Iterator(_) => true, Self::Pure(_) | Self::Method(_) | Self::Script(_) => false, + Self::Plugin(_) => false, } } /// Is this a Rhai-scripted function? @@ -98,6 +111,14 @@ impl CallableFunction { match self { Self::Script(_) => true, Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false, + Self::Plugin(_) => false, + } + } + /// Is this a plugin-defined function? + pub fn is_plugin_fn(&self) -> bool { + match self { + Self::Plugin(_) => true, + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false, } } /// Get a reference to a native Rust function. @@ -109,6 +130,7 @@ impl CallableFunction { match self { Self::Pure(f) | Self::Method(f) => f.as_ref(), Self::Iterator(_) | Self::Script(_) => panic!(), + Self::Plugin(_) => panic!(), } } /// Get a reference to a script-defined function definition. @@ -120,6 +142,7 @@ impl CallableFunction { match self { Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => panic!(), Self::Script(f) => f, + Self::Plugin(_) => panic!(), } } /// Get a reference to an iterator function. @@ -131,6 +154,18 @@ impl CallableFunction { match self { Self::Iterator(f) => *f, Self::Pure(_) | Self::Method(_) | Self::Script(_) => panic!(), + Self::Plugin(_) => panic!(), + } + } + /// Get a reference to a plugin function. + /// + /// # Panics + /// + /// Panics if the `CallableFunction` is not `Plugin`. + pub fn get_plugin_fn<'s>(&'s self) -> SharedPluginFunction { + match self { + Self::Plugin(f) => f.clone(), + Self::Pure(_) | Self::Method(_) | Self::Script(_) | Self::Iterator(_) => panic!(), } } /// Create a new `CallableFunction::Pure`. @@ -141,4 +176,16 @@ impl CallableFunction { pub fn from_method(func: Box) -> Self { Self::Method(func.into()) } + + #[cfg(feature = "sync")] + /// Create a new `CallableFunction::Plugin`. + pub fn from_plugin(plugin: impl PluginFunction + 'static + Send + Sync) -> Self { + Self::Plugin(Arc::new(plugin)) + } + + #[cfg(not(feature = "sync"))] + /// Create a new `CallableFunction::Plugin`. + pub fn from_plugin(plugin: impl PluginFunction + 'static) -> Self { + Self::Plugin(Rc::new(plugin)) + } } diff --git a/src/fn_register.rs b/src/fn_register.rs index f3bfc17c..4d962cf9 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -6,6 +6,7 @@ use crate::any::{Dynamic, Variant}; use crate::engine::Engine; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs}; use crate::parser::FnAccess; +use crate::plugin::Plugin; use crate::result::EvalAltResult; use crate::stdlib::{any::TypeId, boxed::Box, mem}; @@ -13,40 +14,76 @@ use crate::stdlib::{any::TypeId, boxed::Box, mem}; /// A trait to register custom plugins with the `Engine`. /// /// A plugin consists of a number of functions. All functions will be registered with the engine. -#[cfg(feature = "plugins")] -pub trait RegisterPlugin { - /// Register a custom function with the `Engine`. +pub trait RegisterPlugin { + /// Allow extensions of the engine's behavior. + /// + /// This can include importing modules, registering functions to the global name space, and + /// more. /// /// # Example /// /// ``` - /// use rhai::{Dynamic, Engine, INT, Plugin, RegisterDynamicFn, RegisterPlugin}; + /// use rhai::{FLOAT, INT, Module, ModuleResolver, RegisterFn, RegisterPlugin}; + /// use rhai::plugin::*; + /// use rhai::module_resolvers::*; /// - /// // A simple custom plugin type. This should not usually be done with hand-written code. - /// struct AddOffsetPlugin(INT); - /// impl AddOffsetPlugin { - /// fn add_offset(&self, x: INT) -> Dynamic { - /// Dynamic::from(x + self.0) + /// // A function we want to expose to Rhai. + /// #[derive(Copy, Clone)] + /// struct DistanceFunction(); + /// + /// impl PluginFunction for DistanceFunction { + /// fn is_method_call(&self) -> bool { false } + /// fn is_varadic(&self) -> bool { false } + /// + /// fn call(&self, args: &[&mut Dynamic], pos: Position) -> Result> { + /// let x1: &FLOAT = args[0].downcast_ref::().unwrap(); + /// let y1: &FLOAT = args[1].downcast_ref::().unwrap(); + /// let x2: &FLOAT = args[2].downcast_ref::().unwrap(); + /// let y2: &FLOAT = args[3].downcast_ref::().unwrap(); + /// let square_sum = (y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0); + /// Ok(Dynamic::from(square_sum.sqrt())) + /// } + /// + /// fn clone_boxed(&self) -> Box { + /// Box::new(DistanceFunction()) /// } /// } - /// impl Plugin for AddOffsetPlugin { - /// fn name(&self) -> &str { - /// "My Plugin" - /// } + /// + /// // A simple custom plugin. This should not usually be done with hand-written code. + /// #[derive(Copy, Clone)] + /// pub struct AdvancedMathPlugin(); + /// + /// impl Plugin for AdvancedMathPlugin { /// fn register_contents(self, engine: &mut Engine) { - /// let add_offset_fn: Box Dynamic> = { - /// Box::new(move |x| self.add_offset(x)) - /// }; - /// engine.register_dynamic_fn("add_offset", add_offset_fn); + /// // Plugins are allowed to have side-effects on the engine. + /// engine.register_fn("get_mystic_number", || { 42 as FLOAT }); + /// + /// // Main purpose: create a module to expose the functions to Rhai. + /// // + /// // This is currently a hack. There needs to be a better API here for "plugin" + /// // modules. + /// let mut m = Module::new(); + /// m.set_fn("euclidean_distance".to_string(), FnAccess::Public, + /// &[std::any::TypeId::of::(), + /// std::any::TypeId::of::(), + /// std::any::TypeId::of::(), + /// std::any::TypeId::of::()], + /// CallableFunction::from_plugin(DistanceFunction())); + /// let mut r = StaticModuleResolver::new(); + /// r.insert("Math::Advanced".to_string(), m); + /// engine.set_module_resolver(Some(r)); /// } /// } /// + /// /// # fn main() -> Result<(), Box> { /// /// let mut engine = Engine::new(); - /// engine.register_plugin(AddOffsetPlugin(50)); + /// engine.register_plugin(AdvancedMathPlugin()); /// - /// assert_eq!(engine.eval::("add_offset(42)")?, 92); + /// assert_eq!(engine.eval::( + /// r#"import "Math::Advanced" as math; + /// let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#)?, 41.0); /// # Ok(()) /// # } /// ``` @@ -115,15 +152,6 @@ pub trait RegisterResultFn { fn register_result_fn(&mut self, name: &str, f: FN); } -/// Represents an externally-written plugin for the Rhai interpreter. -/// -/// This should not be used directly. Use the `plugin_module!` macro instead. -#[cfg(feature = "plugins")] -pub trait Plugin { - fn register_contents(self, engine: &mut Engine); - fn name(&self) -> &str; -} - // These types are used to build a unique _marker_ tuple type for each combination // of function parameter types in order to make each trait implementation unique. // That is because stable Rust currently does not allow distinguishing implementations @@ -156,10 +184,9 @@ pub fn by_value(data: &mut Dynamic) -> T { mem::take(data).cast::() } -#[cfg(feature = "plugins")] impl RegisterPlugin for Engine { fn register_plugin(&mut self, plugin: PL) { - plugin.register_contents(self) + plugin.register_contents(self); } } diff --git a/src/lib.rs b/src/lib.rs index 39920c0f..e0f2245c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,10 +75,14 @@ mod engine; mod error; mod fn_call; mod fn_func; -mod fn_native; +pub mod fn_native; mod fn_register; mod module; mod optimize; +#[cfg(not(feature = "no_module"))] +pub mod plugin; +#[cfg(feature = "no_module")] +mod plugin; pub mod packages; mod parser; mod result; @@ -91,9 +95,8 @@ mod utils; pub use any::Dynamic; pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; -#[cfg(feature = "plugins")] -pub use fn_register::{Plugin, RegisterPlugin}; pub use fn_register::{RegisterFn, RegisterResultFn}; +pub use fn_register::RegisterPlugin; pub use module::Module; pub use parser::{ImmutableString, AST, INT}; pub use result::EvalAltResult; diff --git a/src/plugin.rs b/src/plugin.rs new file mode 100644 index 00000000..8159d3c8 --- /dev/null +++ b/src/plugin.rs @@ -0,0 +1,42 @@ +//! Module defining plugins in Rhai. Is exported for use by plugin authors. + +pub use crate::Engine; +pub use crate::any::{Dynamic, Variant}; +pub use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn}; +pub use crate::parser::{ + FnAccess, + FnAccess::{Private, Public}, + AST, +}; +pub use crate::result::EvalAltResult; +pub use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; +pub use crate::token::{Position, Token}; +pub use crate::utils::StaticVec; + +#[cfg(features = "sync")] +/// Represents an externally-written plugin for the Rhai interpreter. +/// +/// This trait should not be used directly. Use the `#[plugin]` procedural attribute instead. +pub trait Plugin: Send { + fn register_contents(self, engine: &mut Engine); +} + +#[cfg(not(features = "sync"))] +/// Represents an externally-written plugin for the Rhai interpreter. +/// +/// This trait should not be used directly. Use the `#[plugin]` procedural attribute instead. +pub trait Plugin: Send + Sync { + fn register_contents(self, engine: &mut Engine); +} + +/// Represents a function that is statically defined within a plugin. +/// +/// This trait should not be used directly. Use the `#[plugin]` procedural attribute instead. +pub trait PluginFunction { + fn is_method_call(&self) -> bool; + fn is_varadic(&self) -> bool; + + fn call(&self, args: &[&mut Dynamic], pos: Position) -> Result>; + + fn clone_boxed(&self) -> Box; +} From ead366aac80b51298aea655d699a84f7fc8aeff9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 8 Jun 2020 10:26:32 +0800 Subject: [PATCH 6/8] Better String parameter error message. --- src/engine.rs | 6 +++++- src/fn_register.rs | 8 ++++---- tests/string.rs | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 9be3c04c..4ad31b36 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -708,7 +708,11 @@ impl Engine { "{} ({})", fn_name, args.iter() - .map(|name| self.map_type_name(name.type_name())) + .map(|name| if name.is::() { + "&str | ImmutableString" + } else { + self.map_type_name(name.type_name()) + }) .collect::>() .join(", ") ), diff --git a/src/fn_register.rs b/src/fn_register.rs index 1384f666..1285c119 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -101,10 +101,10 @@ pub fn by_ref(data: &mut Dynamic) -> &mut T { #[inline(always)] pub fn by_value(data: &mut Dynamic) -> T { if TypeId::of::() == TypeId::of::<&str>() { - // &str parameters are mapped to the underlying ImmutableString - let r = data.as_str().unwrap(); - let x = unsafe { mem::transmute::<_, &T>(&r) }; - x.clone() + // If T is &str, data must be ImmutableString, so map directly to it + let ref_str = data.as_str().unwrap(); + let ref_T = unsafe { mem::transmute::<_, &T>(&ref_str) }; + ref_T.clone() } else { // We consume the argument and then replace it with () - the argument is not supposed to be used again. // This way, we avoid having to clone the argument again, because it is already a clone when passed here. diff --git a/tests/string.rs b/tests/string.rs index 27854629..c93278de 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -168,7 +168,7 @@ fn test_string_fn() -> Result<(), Box> { assert!(matches!( *engine.eval::(r#"foo3("hello")"#).expect_err("should error"), - EvalAltResult::ErrorFunctionNotFound(ref x, _) if x == "foo3 (string)" + EvalAltResult::ErrorFunctionNotFound(ref x, _) if x == "foo3 (&str | ImmutableString)" )); Ok(()) From 4603f8026f28809985af20ea8912b3bb4b497830 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 15 Jun 2020 23:20:50 +0800 Subject: [PATCH 7/8] Add getters for Engine setting fields. --- src/engine.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 7 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 79924129..e1beb635 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -230,6 +230,9 @@ pub fn get_script_function_by_signature<'a>( /// /// Currently, `Engine` is neither `Send` nor `Sync`. Use the `sync` feature to make it `Send + Sync`. pub struct Engine { + /// A unique ID identifying this scripting `Engine`. + pub id: Option, + /// A module containing all functions directly loaded into the Engine. pub(crate) global_module: Module, /// A collection of all library packages loaded into the Engine. @@ -274,6 +277,8 @@ impl Default for Engine { fn default() -> Self { // Create the new scripting Engine let mut engine = Self { + id: None, + packages: Default::default(), global_module: Default::default(), @@ -431,6 +436,8 @@ impl Engine { /// Use the `load_package` method to load additional packages of functions. pub fn new_raw() -> Self { Self { + id: None, + packages: Default::default(), global_module: Default::default(), module_resolver: None, @@ -483,6 +490,15 @@ impl Engine { self.optimization_level = optimization_level } + /// The current optimization level. + /// It controls whether and how the `Engine` will optimize an AST after compilation. + /// + /// Not available under the `no_optimize` feature. + #[cfg(not(feature = "no_optimize"))] + pub fn optimization_level(&self) -> OptimizationLevel { + self.optimization_level + } + /// Set the maximum levels of function calls allowed for a script in order to avoid /// infinite recursion and stack overflows. #[cfg(not(feature = "unchecked"))] @@ -490,11 +506,27 @@ impl Engine { self.max_call_stack_depth = levels } + /// The maximum levels of function calls allowed for a script. + #[cfg(not(feature = "unchecked"))] + pub fn max_call_levels(&self) -> usize { + self.max_call_stack_depth + } + /// Set the maximum number of operations allowed for a script to run to avoid /// consuming too much resources (0 for unlimited). #[cfg(not(feature = "unchecked"))] pub fn set_max_operations(&mut self, operations: u64) { - self.max_operations = operations; + self.max_operations = if operations == u64::MAX { + 0 + } else { + operations + }; + } + + /// The maximum number of operations allowed for a script to run (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + pub fn max_operations(&self) -> u64 { + self.max_operations } /// Set the maximum number of imported modules allowed for a script. @@ -503,31 +535,77 @@ impl Engine { self.max_modules = modules; } - /// Set the depth limits for expressions/statements (0 for unlimited). + /// The maximum number of imported modules allowed for a script. + #[cfg(not(feature = "unchecked"))] + pub fn max_modules(&self) -> usize { + self.max_modules + } + + /// Set the depth limits for expressions (0 for unlimited). #[cfg(not(feature = "unchecked"))] pub fn set_max_expr_depths(&mut self, max_expr_depth: usize, max_function_expr_depth: usize) { - self.max_expr_depth = max_expr_depth; - self.max_function_expr_depth = max_function_expr_depth; + self.max_expr_depth = if max_expr_depth == usize::MAX { + 0 + } else { + max_expr_depth + }; + self.max_function_expr_depth = if max_function_expr_depth == usize::MAX { + 0 + } else { + max_function_expr_depth + }; + } + + /// The depth limit for expressions (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + pub fn max_expr_depth(&self) -> usize { + self.max_expr_depth + } + + /// The depth limit for expressions in functions (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + pub fn max_function_expr_depth(&self) -> usize { + self.max_function_expr_depth } /// Set the maximum length of strings (0 for unlimited). #[cfg(not(feature = "unchecked"))] pub fn set_max_string_size(&mut self, max_size: usize) { - self.max_string_size = max_size; + self.max_string_size = if max_size == usize::MAX { 0 } else { max_size }; + } + + /// The maximum length of strings (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + pub fn max_string_size(&self) -> usize { + self.max_string_size } /// Set the maximum length of arrays (0 for unlimited). #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_index"))] pub fn set_max_array_size(&mut self, max_size: usize) { - self.max_array_size = max_size; + self.max_array_size = if max_size == usize::MAX { 0 } else { max_size }; + } + + /// The maximum length of arrays (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_index"))] + pub fn max_array_size(&self) -> usize { + self.max_array_size } /// Set the maximum length of object maps (0 for unlimited). #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_object"))] pub fn set_max_map_size(&mut self, max_size: usize) { - self.max_map_size = max_size; + self.max_map_size = if max_size == usize::MAX { 0 } else { max_size }; + } + + /// The maximum length of object maps (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_object"))] + pub fn max_map_size(&self) -> usize { + self.max_map_size } /// Set the module resolution service used by the `Engine`. From ff37e024438ab78efe18e56e81816121fc00c3e4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 16 Jun 2020 09:34:30 +0800 Subject: [PATCH 8/8] Add iterator support for strings. --- README.md | 16 +++++++++++++++- examples/repl.rs | 2 +- src/engine.rs | 24 ++++++++++++------------ src/packages/string_more.rs | 9 +++++++++ tests/for.rs | 20 ++++++++++++++++++++ 5 files changed, 57 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 78fbae94..401b49fc 100644 --- a/README.md +++ b/README.md @@ -1499,6 +1499,11 @@ record == "Bob X. Davis: age 42 ❤\n"; "Davis" in record == true; 'X' in record == true; 'C' in record == false; + +// Strings can be iterated with a 'for' statement, yielding characters +for ch in record { + print(ch); +} ``` The maximum allowed length of a string can be controlled via `Engine::set_max_string_size` @@ -2011,9 +2016,18 @@ loop { Iterating through a range or an [array] is provided by the `for` ... `in` loop. ```rust -let array = [1, 3, 5, 7, 9, 42]; +// Iterate through string, yielding characters +let s = "hello, world!"; + +for ch in s { + if ch > 'z' { continue; } // skip to the next iteration + print(ch); + if x == '@' { break; } // break out of for loop +} // Iterate through array +let array = [1, 3, 5, 7, 9, 42]; + for x in array { if x > 10 { continue; } // skip to the next iteration print(x); diff --git a/examples/repl.rs b/examples/repl.rs index 4a3bd561..f18a56e5 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -125,7 +125,7 @@ fn main() { match engine .compile_with_scope(&scope, &script) - .map_err(|err| err.into()) + .map_err(Into::into) .and_then(|r| { ast_u = r.clone(); diff --git a/src/engine.rs b/src/engine.rs index e1beb635..c94d1423 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2265,14 +2265,14 @@ fn run_builtin_binary_op( #[cfg(not(feature = "unchecked"))] match op { - "+" => return add(x, y).map(Into::::into).map(Some), - "-" => return sub(x, y).map(Into::::into).map(Some), - "*" => return mul(x, y).map(Into::::into).map(Some), - "/" => return div(x, y).map(Into::::into).map(Some), - "%" => return modulo(x, y).map(Into::::into).map(Some), - "~" => return pow_i_i(x, y).map(Into::::into).map(Some), - ">>" => return shr(x, y).map(Into::::into).map(Some), - "<<" => return shl(x, y).map(Into::::into).map(Some), + "+" => return add(x, y).map(Into::into).map(Some), + "-" => return sub(x, y).map(Into::into).map(Some), + "*" => return mul(x, y).map(Into::into).map(Some), + "/" => return div(x, y).map(Into::into).map(Some), + "%" => return modulo(x, y).map(Into::into).map(Some), + "~" => return pow_i_i(x, y).map(Into::into).map(Some), + ">>" => return shr(x, y).map(Into::into).map(Some), + "<<" => return shl(x, y).map(Into::into).map(Some), _ => (), } @@ -2283,9 +2283,9 @@ fn run_builtin_binary_op( "*" => return Ok(Some((x * y).into())), "/" => return Ok(Some((x / y).into())), "%" => return Ok(Some((x % y).into())), - "~" => return pow_i_i_u(x, y).map(Into::::into).map(Some), - ">>" => return shr_u(x, y).map(Into::::into).map(Some), - "<<" => return shl_u(x, y).map(Into::::into).map(Some), + "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), + ">>" => return shr_u(x, y).map(Into::into).map(Some), + "<<" => return shl_u(x, y).map(Into::into).map(Some), _ => (), } @@ -2359,7 +2359,7 @@ fn run_builtin_binary_op( "*" => return Ok(Some((x * y).into())), "/" => return Ok(Some((x / y).into())), "%" => return Ok(Some((x % y).into())), - "~" => return pow_f_f(x, y).map(Into::::into).map(Some), + "~" => return pow_f_f(x, y).map(Into::into).map(Some), "==" => return Ok(Some((x == y).into())), "!=" => return Ok(Some((x != y).into())), ">" => return Ok(Some((x > y).into())), diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 6b5928c1..4223b375 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -15,6 +15,7 @@ use crate::stdlib::{ fmt::Display, format, string::{String, ToString}, + vec::Vec, }; fn prepend(x: T, y: ImmutableString) -> FuncReturn { @@ -290,4 +291,12 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str Ok(()) }, ); + + // Register string iterator + lib.set_iter( + TypeId::of::(), + |arr| Box::new( + arr.cast::().chars().collect::>().into_iter().map(Into::into) + ) as Box>, + ); }); diff --git a/tests/for.rs b/tests/for.rs index cddff6ed..4f99c06f 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -30,6 +30,26 @@ fn test_for_array() -> Result<(), Box> { Ok(()) } +#[test] +fn test_for_string() -> Result<(), Box> { + let engine = Engine::new(); + + let script = r#" + let s = "hello"; + let sum = 0; + + for ch in s { + sum += ch.to_int(); + } + + sum + "#; + + assert_eq!(engine.eval::(script)?, 532); + + Ok(()) +} + #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] #[test]