From 5f12391ec6c6a35e5fa81d96197683c15c5c9829 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 8 May 2020 00:19:08 +0800 Subject: [PATCH] Use hashed lookup for module-qualified functions and variables. --- src/engine.rs | 47 ++++---- src/fn_register.rs | 8 +- src/module.rs | 250 ++++++++++++++++++++++++++++++------------ src/optimize.rs | 9 +- src/packages/utils.rs | 11 +- src/parser.rs | 40 ++++--- src/scope.rs | 4 +- src/utils.rs | 20 +++- 8 files changed, 268 insertions(+), 121 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index f10d77a2..136a3b17 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,14 +3,15 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; use crate::error::ParseErrorType; +use crate::module::ModuleRef; use crate::optimize::OptimizationLevel; use crate::packages::{ CorePackage, Package, PackageLibrary, PackageStore, PackagesCollection, StandardPackage, }; -use crate::parser::{Expr, FnDef, ModuleRef, ReturnType, Stmt, AST}; +use crate::parser::{Expr, FnDef, ReturnType, Stmt, AST}; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; -use crate::token::Position; +use crate::token::{Position, Token}; use crate::utils::{calc_fn_def, StaticVec}; #[cfg(not(feature = "no_module"))] @@ -21,7 +22,7 @@ use crate::stdlib::{ boxed::Box, collections::HashMap, format, - iter::once, + iter::{empty, once}, mem, num::NonZeroUsize, ops::{Deref, DerefMut}, @@ -392,14 +393,14 @@ fn default_print(s: &str) { fn search_scope<'a>( scope: &'a mut Scope, name: &str, - modules: &ModuleRef, + modules: &Option>, index: Option, pos: Position, ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { #[cfg(not(feature = "no_module"))] { if let Some(modules) = modules { - let (id, root_pos) = modules.get(0); // First module + let (id, root_pos) = modules.get(0); let module = if let Some(index) = index { scope @@ -409,12 +410,15 @@ fn search_scope<'a>( .unwrap() } else { scope.find_module(id).ok_or_else(|| { - Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)) + Box::new(EvalAltResult::ErrorModuleNotFound( + id.to_string(), + *root_pos, + )) })? }; return Ok(( - module.get_qualified_var_mut(name, modules.as_ref(), pos)?, + module.get_qualified_var_mut(name, modules.key(), pos)?, // Module variables are constant ScopeEntryType::Constant, )); @@ -527,7 +531,7 @@ impl Engine { } // Search built-in's and external functions - let fn_spec = calc_fn_hash(fn_name, args.iter().map(|a| a.type_id())); + let fn_spec = calc_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id())); if let Some(func) = self .base_package @@ -676,7 +680,7 @@ impl Engine { // Has a system function an override? fn has_override(&self, state: &State, name: &str) -> bool { - let hash = calc_fn_hash(name, once(TypeId::of::())); + let hash = calc_fn_hash(empty(), name, once(TypeId::of::())); // First check registered functions self.base_package.contains_function(hash) @@ -1176,8 +1180,8 @@ impl Engine { Expr::CharConstant(c, _) => Ok((*c).into()), Expr::Variable(id, modules, index, pos) => { let index = if state.always_search { None } else { *index }; - let val = search_scope(scope, id, modules, index, *pos)?; - Ok(val.0.clone()) + let (val, _) = search_scope(scope, id, modules, index, *pos)?; + Ok(val.clone()) } Expr::Property(_, _) => unreachable!(), @@ -1190,18 +1194,19 @@ impl Engine { match lhs.as_ref() { // name = rhs - Expr::Variable(name, modules, index, pos) => { + Expr::Variable(id, modules, index, pos) => { let index = if state.always_search { None } else { *index }; - match search_scope(scope, name, modules, index, *pos)? { - (_, ScopeEntryType::Constant) => Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *pos), + let (value_ptr, typ) = search_scope(scope, id, modules, index, *pos)?; + match typ { + ScopeEntryType::Constant => Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant(id.to_string(), *pos), )), - (value_ptr, ScopeEntryType::Normal) => { + ScopeEntryType::Normal => { *value_ptr = rhs_val; Ok(Default::default()) } // End variable cannot be a module - (_, ScopeEntryType::Module) => unreachable!(), + ScopeEntryType::Module => unreachable!(), } } // idx_lhs[idx_expr] = rhs @@ -1322,9 +1327,13 @@ impl Engine { self.call_script_fn(None, state, fn_def, &mut args, *pos, level) } else { // Then search in Rust functions - let hash = calc_fn_hash(fn_name, args.iter().map(|a| a.type_id())); + let hash = calc_fn_hash( + modules.iter().map(|(m, _)| m.as_str()), + fn_name, + args.iter().map(|a| a.type_id()), + ); - match module.get_qualified_fn(fn_name, hash, modules, *pos) { + match module.get_qualified_fn(fn_name, hash, *pos) { Ok(func) => func(&mut args, *pos), Err(_) if def_val.is_some() => Ok(def_val.as_deref().unwrap().clone()), Err(err) => Err(err), diff --git a/src/fn_register.rs b/src/fn_register.rs index 2e9a5325..c1549185 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -8,7 +8,7 @@ use crate::result::EvalAltResult; use crate::token::Position; use crate::utils::calc_fn_spec; -use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString}; +use crate::stdlib::{any::TypeId, boxed::Box, iter::empty, mem, string::ToString}; /// A trait to register custom functions with the `Engine`. pub trait RegisterFn { @@ -220,7 +220,7 @@ macro_rules! def_register { fn register_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); let func = make_func!(fn_name : f : map_dynamic ; $($par => $clone),*); - let hash = calc_fn_spec(name, [$(TypeId::of::<$par>()),*].iter().cloned()); + let hash = calc_fn_spec(empty(), name, [$(TypeId::of::<$par>()),*].iter().cloned()); self.base_package.functions.insert(hash, Box::new(func)); } } @@ -238,7 +238,7 @@ macro_rules! def_register { fn register_dynamic_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); let func = make_func!(fn_name : f : map_identity ; $($par => $clone),*); - let hash = calc_fn_spec(name, [$(TypeId::of::<$par>()),*].iter().cloned()); + let hash = calc_fn_spec(empty(), name, [$(TypeId::of::<$par>()),*].iter().cloned()); self.base_package.functions.insert(hash, Box::new(func)); } } @@ -257,7 +257,7 @@ macro_rules! def_register { fn register_result_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); let func = make_func!(fn_name : f : map_result ; $($par => $clone),*); - let hash = calc_fn_spec(name, [$(TypeId::of::<$par>()),*].iter().cloned()); + let hash = calc_fn_spec(empty(), name, [$(TypeId::of::<$par>()),*].iter().cloned()); self.base_package.functions.insert(hash, Box::new(func)); } } diff --git a/src/module.rs b/src/module.rs index 1fa704c8..374f22d6 100644 --- a/src/module.rs +++ b/src/module.rs @@ -7,21 +7,29 @@ use crate::engine::{Engine, FnAny, FnCallArgs, FunctionsLib}; use crate::parser::{FnDef, AST}; use crate::result::EvalAltResult; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; -use crate::token::Position; -use crate::token::Token; +use crate::token::{Position, Token}; use crate::utils::StaticVec; use crate::stdlib::{ any::TypeId, boxed::Box, collections::HashMap, - fmt, mem, + fmt, + iter::{empty, once}, + mem, ops::{Deref, DerefMut}, rc::Rc, string::{String, ToString}, sync::Arc, + vec, + vec::Vec, }; +#[cfg(not(feature = "sync"))] +type NativeFunction = Rc>; +#[cfg(feature = "sync")] +type NativeFunction = Arc>; + /// A trait that encapsulates a module resolution service. pub trait ModuleResolver { /// Resolve a module based on a path string. @@ -44,15 +52,18 @@ type FuncReturn = Result>; pub struct Module { /// Sub-modules. modules: HashMap, - /// Module variables, including sub-modules. + + /// Module variables. variables: HashMap, + /// Flattened collection of all module variables, including those in sub-modules. + all_variables: HashMap, + /// External Rust functions. - #[cfg(not(feature = "sync"))] - functions: HashMap>>, - /// External Rust functions. - #[cfg(feature = "sync")] - functions: HashMap>>, + functions: HashMap, NativeFunction)>, + + /// Flattened collection of all external Rust functions, including those in sub-modules. + all_functions: HashMap, /// Script-defined functions. fn_lib: FunctionsLib, @@ -113,7 +124,7 @@ impl Module { /// assert_eq!(module.get_var_value::("answer").unwrap(), 42); /// ``` pub fn get_var_value(&self, name: &str) -> Option { - self.get_var(name).and_then(|v| v.try_cast::()) + self.get_var(name).and_then(Dynamic::try_cast::) } /// Get a module variable as a `Dynamic`. @@ -131,11 +142,6 @@ impl Module { self.variables.get(name).cloned() } - /// Get a mutable reference to a module variable. - pub fn get_var_mut(&mut self, name: &str) -> Option<&mut Dynamic> { - self.variables.get_mut(name) - } - /// Set a variable into the module. /// /// If there is an existing variable of the same name, it is replaced. @@ -154,16 +160,17 @@ impl Module { } /// Get a mutable reference to a modules-qualified variable. + /// + /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. pub(crate) fn get_qualified_var_mut( &mut self, name: &str, - modules: &StaticVec<(String, Position)>, + hash: u64, pos: Position, ) -> Result<&mut Dynamic, Box> { - Ok(self - .get_qualified_module_mut(modules)? - .get_var_mut(name) - .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), pos)))?) + self.all_variables + .get_mut(&hash) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.to_string(), pos))) } /// Does a sub-module exist in the module? @@ -273,13 +280,15 @@ impl Module { /// 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. - pub fn set_fn(&mut self, fn_name: &str, params: &[TypeId], func: Box) -> u64 { - let hash = calc_fn_hash(fn_name, params.iter().cloned()); + pub fn set_fn(&mut self, fn_name: String, params: Vec, func: Box) -> u64 { + let hash = calc_fn_hash(empty(), &fn_name, params.iter().cloned()); #[cfg(not(feature = "sync"))] - self.functions.insert(hash, Rc::new(func)); + self.functions + .insert(hash, (fn_name, params, Rc::new(func))); #[cfg(feature = "sync")] - self.functions.insert(hash, Arc::new(func)); + self.functions + .insert(hash, (fn_name, params, Arc::new(func))); hash } @@ -297,9 +306,9 @@ impl Module { /// let hash = module.set_fn_0("calc", || Ok(42_i64)); /// assert!(module.get_fn(hash).is_some()); /// ``` - pub fn set_fn_0>( + pub fn set_fn_0, T: Into>( &mut self, - fn_name: &str, + fn_name: K, #[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn + 'static, #[cfg(feature = "sync")] func: impl Fn() -> FuncReturn + Send + Sync + 'static, ) -> u64 { @@ -308,8 +317,8 @@ impl Module { .map(|v| v.into()) .map_err(|err| EvalAltResult::set_position(err, pos)) }; - let arg_types = &[]; - self.set_fn(fn_name, arg_types, Box::new(f)) + let arg_types = vec![]; + self.set_fn(fn_name.into(), arg_types, Box::new(f)) } /// Set a Rust function taking one parameter into the module, returning a hash key. @@ -325,9 +334,9 @@ impl Module { /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); /// assert!(module.get_fn(hash).is_some()); /// ``` - pub fn set_fn_1>( + pub fn set_fn_1, A: Variant + Clone, T: Into>( &mut self, - fn_name: &str, + fn_name: K, #[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn + 'static, #[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn + Send + Sync + 'static, ) -> u64 { @@ -336,8 +345,8 @@ impl Module { .map(|v| v.into()) .map_err(|err| EvalAltResult::set_position(err, pos)) }; - let arg_types = &[TypeId::of::()]; - self.set_fn(fn_name, arg_types, Box::new(f)) + let arg_types = vec![TypeId::of::()]; + self.set_fn(fn_name.into(), arg_types, Box::new(f)) } /// Set a Rust function taking one mutable parameter into the module, returning a hash key. @@ -353,9 +362,9 @@ impl Module { /// 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>( + pub fn set_fn_1_mut, A: Variant + Clone, T: Into>( &mut self, - fn_name: &str, + 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, ) -> u64 { @@ -364,8 +373,8 @@ impl Module { .map(|v| v.into()) .map_err(|err| EvalAltResult::set_position(err, pos)) }; - let arg_types = &[TypeId::of::()]; - self.set_fn(fn_name, arg_types, Box::new(f)) + let arg_types = vec![TypeId::of::()]; + self.set_fn(fn_name.into(), arg_types, Box::new(f)) } /// Set a Rust function taking two parameters into the module, returning a hash key. @@ -383,9 +392,9 @@ impl Module { /// }); /// assert!(module.get_fn(hash).is_some()); /// ``` - pub fn set_fn_2>( + pub fn set_fn_2, A: Variant + Clone, B: Variant + Clone, T: Into>( &mut self, - fn_name: &str, + 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, ) -> u64 { @@ -397,8 +406,8 @@ impl Module { .map(|v| v.into()) .map_err(|err| EvalAltResult::set_position(err, pos)) }; - let arg_types = &[TypeId::of::(), TypeId::of::()]; - self.set_fn(fn_name, arg_types, Box::new(f)) + let arg_types = vec![TypeId::of::(), TypeId::of::()]; + self.set_fn(fn_name.into(), arg_types, Box::new(f)) } /// Set a Rust function taking two parameters (the first one mutable) into the module, @@ -415,9 +424,14 @@ impl Module { /// }); /// assert!(module.get_fn(hash).is_some()); /// ``` - pub fn set_fn_2_mut>( + pub fn set_fn_2_mut< + K: Into, + A: Variant + Clone, + B: Variant + Clone, + T: Into, + >( &mut self, - fn_name: &str, + 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, ) -> u64 { @@ -429,8 +443,8 @@ impl Module { .map(|v| v.into()) .map_err(|err| EvalAltResult::set_position(err, pos)) }; - let arg_types = &[TypeId::of::(), TypeId::of::()]; - self.set_fn(fn_name, arg_types, Box::new(f)) + let arg_types = vec![TypeId::of::(), TypeId::of::()]; + self.set_fn(fn_name.into(), arg_types, Box::new(f)) } /// Set a Rust function taking three parameters into the module, returning a hash key. @@ -449,13 +463,14 @@ impl Module { /// assert!(module.get_fn(hash).is_some()); /// ``` pub fn set_fn_3< + K: Into, A: Variant + Clone, B: Variant + Clone, C: Variant + Clone, T: Into, >( &mut self, - fn_name: &str, + 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, ) -> u64 { @@ -468,8 +483,8 @@ impl Module { .map(|v| v.into()) .map_err(|err| EvalAltResult::set_position(err, pos)) }; - let arg_types = &[TypeId::of::(), TypeId::of::(), TypeId::of::()]; - self.set_fn(fn_name, arg_types, Box::new(f)) + let arg_types = vec![TypeId::of::(), TypeId::of::(), TypeId::of::()]; + self.set_fn(fn_name.into(), arg_types, Box::new(f)) } /// Set a Rust function taking three parameters (the first one mutable) into the module, @@ -489,13 +504,14 @@ impl Module { /// assert!(module.get_fn(hash).is_some()); /// ``` pub fn set_fn_3_mut< + K: Into, A: Variant + Clone, B: Variant + Clone, C: Variant + Clone, T: Into, >( &mut self, - fn_name: &str, + 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, ) -> u64 { @@ -508,8 +524,8 @@ impl Module { .map(|v| v.into()) .map_err(|err| EvalAltResult::set_position(err, pos)) }; - let arg_types = &[TypeId::of::(), TypeId::of::(), TypeId::of::()]; - self.set_fn(fn_name, arg_types, Box::new(f)) + let arg_types = vec![TypeId::of::(), TypeId::of::(), TypeId::of::()]; + self.set_fn(fn_name.into(), arg_types, Box::new(f)) } /// Get a Rust function. @@ -527,7 +543,7 @@ impl Module { /// assert!(module.get_fn(hash).is_some()); /// ``` pub fn get_fn(&self, hash: u64) -> Option<&Box> { - self.functions.get(&hash).map(|v| v.as_ref()) + self.functions.get(&hash).map(|(_, _, v)| v.as_ref()) } /// Get a modules-qualified function. @@ -538,24 +554,12 @@ impl Module { &mut self, name: &str, hash: u64, - modules: &StaticVec<(String, Position)>, pos: Position, ) -> Result<&Box, Box> { - Ok(self - .get_qualified_module_mut(modules)? - .get_fn(hash) - .ok_or_else(|| { - let mut fn_name: String = Default::default(); - - modules.iter().for_each(|(n, _)| { - fn_name.push_str(n); - fn_name.push_str(Token::DoubleColon.syntax().as_ref()); - }); - - fn_name.push_str(name); - - Box::new(EvalAltResult::ErrorFunctionNotFound(fn_name, pos)) - })?) + self.all_functions + .get(&hash) + .map(|f| f.as_ref()) + .ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.to_string(), pos))) } /// Get the script-defined functions. @@ -634,6 +638,52 @@ impl Module { Ok(module) } + + /// Scan through all the sub-modules in the `Module` build an index of all + /// variables and external Rust functions via hashing. + pub(crate) fn collect_all_sub_modules(&mut self) { + // Collect a particular module. + fn collect<'a>( + module: &'a mut Module, + names: &mut Vec<&'a str>, + variables: &mut Vec<(u64, Dynamic)>, + functions: &mut Vec<(u64, NativeFunction)>, + ) { + for (n, m) in module.modules.iter_mut() { + // Collect all the sub-modules first. + names.push(n); + collect(m, names, variables, functions); + names.pop(); + } + + // Collect all variables + for (var_name, value) in module.variables.iter() { + let hash = calc_fn_hash(names.iter().map(|v| *v), var_name, empty()); + variables.push((hash, value.clone())); + } + // Collect all functions + for (fn_name, params, func) in module.functions.values() { + let hash = calc_fn_hash(names.iter().map(|v| *v), fn_name, params.iter().cloned()); + functions.push((hash, func.clone())); + } + } + + let mut variables = Vec::new(); + let mut functions = Vec::new(); + + collect(self, &mut vec!["root"], &mut variables, &mut functions); + + self.all_variables.clear(); + self.all_functions.clear(); + + for (key, value) in variables { + self.all_variables.insert(key, value); + } + + for (key, value) in functions { + self.all_functions.insert(key, value); + } + } } /// Re-export module resolvers. @@ -669,12 +719,18 @@ mod file { /// let mut engine = Engine::new(); /// engine.set_module_resolver(Some(resolver)); /// ``` - #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] + #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct FileModuleResolver { path: PathBuf, extension: String, } + impl Default for FileModuleResolver { + fn default() -> Self { + Self::new_with_path(PathBuf::default()) + } + } + impl FileModuleResolver { /// Create a new `FileModuleResolver` with a specific base path. /// @@ -774,6 +830,64 @@ mod file { } } +/// A chain of module names to qualify a variable or function call. +/// A `u64` hash key is kept for quick search purposes. +/// +/// A `StaticVec` is used because most module-level access contains only one level, +/// and it is wasteful to always allocate a `Vec` with one element. +#[derive(Clone, Hash, Default)] +pub struct ModuleRef(StaticVec<(String, Position)>, u64); + +impl fmt::Debug for ModuleRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.0, f)?; + + if self.1 > 0 { + write!(f, " -> {}", self.1) + } else { + Ok(()) + } + } +} + +impl Deref for ModuleRef { + type Target = StaticVec<(String, Position)>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ModuleRef { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl fmt::Display for ModuleRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (m, _) in self.0.iter() { + write!(f, "{}{}", m, Token::DoubleColon.syntax())?; + } + Ok(()) + } +} + +impl From> for ModuleRef { + fn from(modules: StaticVec<(String, Position)>) -> Self { + Self(modules, 0) + } +} + +impl ModuleRef { + pub fn key(&self) -> u64 { + self.1 + } + pub fn set_key(&mut self, key: u64) { + self.1 = key + } +} + /// Static module resolver. mod stat { use super::*; diff --git a/src/optimize.rs b/src/optimize.rs index 018a210e..2c4e183a 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -13,6 +13,7 @@ use crate::token::Position; use crate::stdlib::{ boxed::Box, collections::HashMap, + iter::empty, string::{String, ToString}, vec, vec::Vec, @@ -117,7 +118,7 @@ fn call_fn( pos: Position, ) -> Result, Box> { // Search built-in's and external functions - let hash = calc_fn_hash(fn_name, args.iter().map(|a| a.type_id())); + let hash = calc_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id())); base_package .get_function(hash) @@ -376,11 +377,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { // id1 = id2 = expr2 (id1, id2) => Expr::Assignment( Box::new(id1), - Box::new(Expr::Assignment( - Box::new(id2), - Box::new(optimize_expr(*expr2, state)), - pos2, - )), + Box::new(Expr::Assignment(Box::new(id2), Box::new(optimize_expr(*expr2, state)), pos2)), pos, ), }, diff --git a/src/packages/utils.rs b/src/packages/utils.rs index a02e475c..f8c60f2c 100644 --- a/src/packages/utils.rs +++ b/src/packages/utils.rs @@ -9,6 +9,7 @@ use crate::token::Position; use crate::stdlib::{ any::TypeId, boxed::Box, + iter::empty, mem, string::{String, ToString}, }; @@ -115,7 +116,7 @@ pub fn reg_none( + Sync + 'static, ) { - let hash = calc_fn_hash(fn_name, ([] as [TypeId; 0]).iter().cloned()); + let hash = calc_fn_hash(empty(), fn_name, ([] as [TypeId; 0]).iter().cloned()); let f = Box::new(move |args: &mut FnCallArgs, pos: Position| { check_num_args(fn_name, 0, args, pos)?; @@ -165,7 +166,7 @@ pub fn reg_unary( ) { //println!("register {}({})", fn_name, crate::std::any::type_name::()); - let hash = calc_fn_hash(fn_name, [TypeId::of::()].iter().cloned()); + let hash = calc_fn_hash(empty(), fn_name, [TypeId::of::()].iter().cloned()); let f = Box::new(move |args: &mut FnCallArgs, pos: Position| { check_num_args(fn_name, 1, args, pos)?; @@ -225,7 +226,7 @@ pub fn reg_unary_mut( ) { //println!("register {}(&mut {})", fn_name, crate::std::any::type_name::()); - let hash = calc_fn_hash(fn_name, [TypeId::of::()].iter().cloned()); + let hash = calc_fn_hash(empty(), fn_name, [TypeId::of::()].iter().cloned()); let f = Box::new(move |args: &mut FnCallArgs, pos: Position| { check_num_args(fn_name, 1, args, pos)?; @@ -279,6 +280,7 @@ pub fn reg_binary( //println!("register {}({}, {})", fn_name, crate::std::any::type_name::(), crate::std::any::type_name::()); let hash = calc_fn_hash( + empty(), fn_name, [TypeId::of::(), TypeId::of::()].iter().cloned(), ); @@ -343,6 +345,7 @@ pub fn reg_binary_mut( //println!("register {}(&mut {}, {})", fn_name, crate::std::any::type_name::(), crate::std::any::type_name::()); let hash = calc_fn_hash( + empty(), fn_name, [TypeId::of::(), TypeId::of::()].iter().cloned(), ); @@ -381,6 +384,7 @@ pub fn reg_trinary(), crate::std::any::type_name::(), crate::std::any::type_name::()); let hash = calc_fn_hash( + empty(), fn_name, [TypeId::of::(), TypeId::of::(), TypeId::of::()] .iter() @@ -422,6 +426,7 @@ pub fn reg_trinary_mut(), crate::std::any::type_name::(), crate::std::any::type_name::()); let hash = calc_fn_hash( + empty(), fn_name, [TypeId::of::(), TypeId::of::(), TypeId::of::()] .iter() diff --git a/src/parser.rs b/src/parser.rs index 953c12a0..d19a7e07 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,8 +1,10 @@ //! Main module defining the lexer and parser. use crate::any::{Dynamic, Union}; +use crate::calc_fn_hash; use crate::engine::{Engine, FunctionsLib}; use crate::error::{LexError, ParseError, ParseErrorType}; +use crate::module::ModuleRef; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::{Position, Token, TokenIterator}; @@ -14,7 +16,7 @@ use crate::stdlib::{ char, collections::HashMap, format, - iter::Peekable, + iter::{empty, Peekable}, num::NonZeroUsize, ops::{Add, Deref, DerefMut}, rc::Rc, @@ -44,11 +46,6 @@ pub type FLOAT = f64; type PERR = ParseErrorType; -/// A chain of module names to qualify a variable or function call. -/// A `StaticVec` is used because most module-level access contains only one level, -/// and it is wasteful to always allocate a `Vec` with one element. -pub type ModuleRef = Option>>; - /// Compiled AST (abstract syntax tree) of a Rhai script. /// /// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. @@ -357,8 +354,13 @@ pub enum Expr { CharConstant(char, Position), /// String constant. StringConstant(String, Position), - /// Variable access - (variable name, optional modules, optional index, position) - Variable(Box, ModuleRef, Option, Position), + /// Variable access - (variable name, optional modules, hash, optional index, position) + Variable( + Box, + Option>, + Option, + Position, + ), /// Property access. Property(String, Position), /// { stmt } @@ -368,7 +370,7 @@ pub enum Expr { /// and the function names are predictable, so no need to allocate a new `String`. FnCall( Box>, - ModuleRef, + Option>, Box>, Option>, Position, @@ -675,7 +677,7 @@ fn parse_call_expr<'a>( input: &mut Peekable>, stack: &mut Stack, id: String, - modules: ModuleRef, + modules: Option>, begin: Position, allow_stmt_expr: bool, ) -> Result> { @@ -1110,12 +1112,11 @@ fn parse_primary<'a>( if let Some(ref mut modules) = modules { modules.push((*id, pos)); } else { + index = stack.find_module(id.as_ref()); + let mut vec = StaticVec::new(); vec.push((*id, pos)); - modules = Some(Box::new(vec)); - - let root = modules.as_ref().unwrap().iter().next().unwrap(); - index = stack.find_module(&root.0); + modules = Some(Box::new(vec.into())); } Expr::Variable(Box::new(id2), modules, index, pos2) @@ -1133,6 +1134,15 @@ fn parse_primary<'a>( } } + match &mut root_expr { + // Calculate hash key for module-qualified variables + Expr::Variable(id, Some(modules), _, _) => { + let hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), id, empty()); + modules.set_key(hash); + } + _ => (), + } + Ok(root_expr) } @@ -1315,7 +1325,7 @@ fn make_dot_expr( } // lhs.module::id - syntax error (_, Expr::Variable(_, Some(modules), _, _)) => { - return Err(PERR::PropertyExpected.into_err(modules.iter().next().unwrap().1)) + return Err(PERR::PropertyExpected.into_err(modules.get(0).1)) } // lhs.dot_lhs.dot_rhs (lhs, Expr::Dot(dot_lhs, dot_rhs, dot_pos)) => Expr::Dot( diff --git a/src/scope.rs b/src/scope.rs index 2411dcc1..04e447bd 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -175,7 +175,9 @@ impl<'a> Scope<'a> { /// /// Modules are used for accessing member variables, functions and plugins under a namespace. #[cfg(not(feature = "no_module"))] - pub fn push_module>>(&mut self, name: K, value: Module) { + pub fn push_module>>(&mut self, name: K, mut value: Module) { + value.collect_all_sub_modules(); + self.push_dynamic_value( name, EntryType::Module, diff --git a/src/utils.rs b/src/utils.rs index 6e11e39b..e4aeb17a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -14,16 +14,26 @@ use crate::stdlib::collections::hash_map::DefaultHasher; #[cfg(feature = "no_std")] use ahash::AHasher; -/// Calculate a `u64` hash key from a function name and parameter types. +/// Calculate a `u64` hash key from a module-qualified function name and parameter types. /// -/// Parameter types are passed in via `TypeId` values from an iterator -/// which can come from any source. -pub fn calc_fn_spec(fn_name: &str, params: impl Iterator) -> u64 { +/// Module names are passed in via `&str` references from an iterator. +/// Parameter types are passed in via `TypeId` values from an iterator. +/// +/// ### Note +/// +/// The first module name is skipped. Hashing starts from the _second_ module in the chain. +pub fn calc_fn_spec<'a>( + modules: impl Iterator, + fn_name: &str, + params: impl Iterator, +) -> u64 { #[cfg(feature = "no_std")] let mut s: AHasher = Default::default(); #[cfg(not(feature = "no_std"))] let mut s = DefaultHasher::new(); + // We always skip the first module + modules.skip(1).for_each(|m| m.hash(&mut s)); s.write(fn_name.as_bytes()); params.for_each(|t| t.hash(&mut s)); s.finish() @@ -45,7 +55,7 @@ pub(crate) fn calc_fn_def(fn_name: &str, num_params: usize) -> u64 { /// /// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate. /// This simplified implementation here is to avoid pulling in another crate. -#[derive(Clone, Default)] +#[derive(Clone, Hash, Default)] pub struct StaticVec { /// Total number of values held. len: usize,