diff --git a/src/engine.rs b/src/engine.rs index c438602d..5b8e379e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -159,15 +159,11 @@ impl<'a> State<'a> { } } /// Does a certain script-defined function exist in the `State`? - pub fn has_function(&self, name: &str, params: usize) -> bool { - // Qualifiers (none) + function name + placeholders (one for each parameter). - let hash = calc_fn_hash(empty(), name, repeat(EMPTY_TYPE_ID()).take(params)); + pub fn has_function(&self, hash: u64) -> bool { self.fn_lib.contains_key(&hash) } /// Get a script-defined function definition from the `State`. - pub fn get_function(&self, name: &str, params: usize) -> Option<&FnDef> { - // Qualifiers (none) + function name + placeholders (one for each parameter). - let hash = calc_fn_hash(empty(), name, repeat(EMPTY_TYPE_ID()).take(params)); + pub fn get_function(&self, hash: u64) -> Option<&FnDef> { self.fn_lib.get(&hash).map(|f| f.as_ref()) } } @@ -442,14 +438,14 @@ fn default_print(s: &str) { fn search_scope<'a>( scope: &'a mut Scope, name: &str, - #[cfg(not(feature = "no_module"))] modules: &Option>, - #[cfg(feature = "no_module")] _: &Option, + #[cfg(not(feature = "no_module"))] modules: Option<(&Box, u64)>, + #[cfg(feature = "no_module")] _: Option<(&ModuleRef, u64)>, index: Option, pos: Position, ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box> { #[cfg(not(feature = "no_module"))] { - if let Some(modules) = modules { + if let Some((modules, hash)) = modules { let (id, root_pos) = modules.get(0); let module = if let Some(index) = modules.index() { @@ -468,7 +464,7 @@ fn search_scope<'a>( }; return Ok(( - module.get_qualified_var_mut(name, modules.key(), pos)?, + module.get_qualified_var_mut(name, hash, pos)?, // Module variables are constant ScopeEntryType::Constant, )); @@ -574,6 +570,8 @@ impl Engine { scope: Option<&mut Scope>, state: &State, fn_name: &str, + hash_fn_spec: u64, + hash_fn_def: u64, args: &mut FnCallArgs, def_val: Option<&Dynamic>, pos: Position, @@ -585,18 +583,17 @@ impl Engine { } // First search in script-defined functions (can override built-in) - if let Some(fn_def) = state.get_function(fn_name, args.len()) { - return self.call_script_fn(scope, state, fn_def, args, pos, level); + if hash_fn_def > 0 { + if let Some(fn_def) = state.get_function(hash_fn_def) { + return self.call_script_fn(scope, state, fn_def, args, pos, level); + } } // Search built-in's and external functions - // Qualifiers (none) + function name + argument `TypeId`'s. - let fn_spec = calc_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id())); - if let Some(func) = self .base_package - .get_function(fn_spec) - .or_else(|| self.packages.get_function(fn_spec)) + .get_function(hash_fn_spec) + .or_else(|| self.packages.get_function(hash_fn_spec)) { // Run external function let result = func(args, pos)?; @@ -739,16 +736,13 @@ impl Engine { } // Has a system function an override? - fn has_override(&self, state: &State, name: &str) -> bool { - // Qualifiers (none) + function name + argument `TypeId`'s. - let hash = calc_fn_hash(empty(), name, once(TypeId::of::())); - + fn has_override(&self, state: &State, hash_fn_spec: u64, hash_fn_def: u64) -> bool { // First check registered functions - self.base_package.contains_function(hash) + self.base_package.contains_function(hash_fn_spec) // Then check packages - || self.packages.contains_function(hash) + || self.packages.contains_function(hash_fn_spec) // Then check script-defined functions - || state.has_function(name, 1) + || state.has_function(hash_fn_def) } // Perform an actual function call, taking care of special functions @@ -762,26 +756,45 @@ impl Engine { &self, state: &State, fn_name: &str, + hash_fn_def: u64, args: &mut FnCallArgs, def_val: Option<&Dynamic>, pos: Position, level: usize, ) -> Result> { + // Qualifiers (none) + function name + argument `TypeId`'s. + let hash_fn_spec = calc_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id())); + match fn_name { // type_of - KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(state, KEYWORD_TYPE_OF) => { + KEYWORD_TYPE_OF + if args.len() == 1 && !self.has_override(state, hash_fn_spec, hash_fn_def) => + { Ok(self.map_type_name(args[0].type_name()).to_string().into()) } // eval - reaching this point it must be a method-style call - KEYWORD_EVAL if args.len() == 1 && !self.has_override(state, KEYWORD_EVAL) => { + KEYWORD_EVAL + if args.len() == 1 && !self.has_override(state, hash_fn_spec, hash_fn_def) => + { Err(Box::new(EvalAltResult::ErrorRuntime( "'eval' should not be called in method style. Try eval(...);".into(), pos, ))) } + // Normal method call - _ => self.call_fn_raw(None, state, fn_name, args, def_val, pos, level), + _ => self.call_fn_raw( + None, + state, + fn_name, + hash_fn_spec, + hash_fn_def, + args, + def_val, + pos, + level, + ), } } @@ -869,17 +882,17 @@ impl Engine { } else { match rhs { // xxx.fn_name(arg_expr_list) - Expr::FnCall(fn_name, None,_, def_val, pos) => { + Expr::FnCall(fn_name, None, hash, _, def_val, pos) => { let mut args: Vec<_> = once(obj) .chain(idx_val.downcast_mut::>().unwrap().iter_mut()) .collect(); let def_val = def_val.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, fn_name, &mut args, def_val, *pos, 0).map(|v| (v, true)) + self.exec_fn_call(state, fn_name, *hash, &mut args, def_val, *pos, 0).map(|v| (v, true)) } // xxx.module::fn_name(...) - syntax error - Expr::FnCall(_,_,_,_,_) => unreachable!(), + Expr::FnCall(_, _, _, _, _, _) => unreachable!(), // {xxx:map}.id = ??? #[cfg(not(feature = "no_object"))] Expr::Property(id, pos) if obj.is::() && new_val.is_some() => { @@ -899,13 +912,13 @@ impl Engine { Expr::Property(id, pos) if new_val.is_some() => { let fn_name = make_setter(id); let mut args = [obj, new_val.as_mut().unwrap()]; - self.exec_fn_call(state, &fn_name, &mut args, None, *pos, 0).map(|v| (v, true)) + self.exec_fn_call(state, &fn_name, 0, &mut args, None, *pos, 0).map(|v| (v, true)) } // xxx.id Expr::Property(id, pos) => { let fn_name = make_getter(id); let mut args = [obj]; - self.exec_fn_call(state, &fn_name, &mut args, None, *pos, 0).map(|v| (v, false)) + self.exec_fn_call(state, &fn_name, 0, &mut args, None, *pos, 0).map(|v| (v, false)) } #[cfg(not(feature = "no_object"))] // {xxx:map}.idx_lhs[idx_expr] @@ -936,7 +949,7 @@ impl Engine { let indexed_val = &mut (if let Expr::Property(id, pos) = dot_lhs.as_ref() { let fn_name = make_getter(id); - self.exec_fn_call(state, &fn_name, &mut args[..1], None, *pos, 0)? + self.exec_fn_call(state, &fn_name, 0, &mut args[..1], None, *pos, 0)? } else { // Syntax error return Err(Box::new(EvalAltResult::ErrorDotExpr( @@ -954,7 +967,7 @@ impl Engine { let fn_name = make_setter(id); // Re-use args because the first &mut parameter will not be consumed args[1] = indexed_val; - self.exec_fn_call(state, &fn_name, &mut args, None, *pos, 0).or_else(|err| match *err { + self.exec_fn_call(state, &fn_name, 0, &mut args, None, *pos, 0).or_else(|err| match *err { // If there is no setter, no need to feed it back because the property is read-only EvalAltResult::ErrorDotExpr(_,_) => Ok(Default::default()), err => Err(Box::new(err)) @@ -991,9 +1004,10 @@ impl Engine { match dot_lhs { // id.??? or id[???] - Expr::Variable(id, modules, index, pos) => { + Expr::Variable(id, modules, hash, index, pos) => { let index = if state.always_search { None } else { *index }; - let (target, typ) = search_scope(scope, id, modules, index, *pos)?; + let (target, typ) = + search_scope(scope, id, modules.as_ref().map(|m| (m, *hash)), index, *pos)?; // Constants cannot be modified match typ { @@ -1046,7 +1060,7 @@ impl Engine { level: usize, ) -> Result<(), Box> { match expr { - Expr::FnCall(_, None, arg_exprs, _, _) => { + Expr::FnCall(_, None, _, arg_exprs, _, _) => { let mut arg_values = StaticVec::::new(); for arg_expr in arg_exprs.iter() { @@ -1055,7 +1069,7 @@ impl Engine { idx_values.push(Dynamic::from(arg_values)); } - Expr::FnCall(_, _, _, _, _) => unreachable!(), + Expr::FnCall(_, _, _, _, _, _) => unreachable!(), Expr::Property(_, _) => idx_values.push(()), // Store a placeholder - no need to copy the property name Expr::Index(lhs, rhs, _) | Expr::Dot(lhs, rhs, _) => { // Evaluate in left-to-right order @@ -1158,7 +1172,7 @@ impl Engine { _ => { let args = &mut [val, &mut idx]; - self.exec_fn_call(state, FUNC_INDEXER, args, None, op_pos, 0) + self.exec_fn_call(state, FUNC_INDEXER, 0, args, None, op_pos, 0) .map(|v| v.into()) .map_err(|_| { Box::new(EvalAltResult::ErrorIndexingType( @@ -1196,9 +1210,16 @@ impl Engine { let args = &mut [&mut lhs_value.clone(), &mut value.clone()]; let def_value = Some(&def_value); let pos = rhs.position(); + let op = "=="; + + // Qualifiers (none) + function name + argument `TypeId`'s. + let fn_spec = calc_fn_hash(empty(), op, args.iter().map(|a| a.type_id())); + let fn_def = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2)); if self - .call_fn_raw(None, state, "==", args, def_value, pos, level)? + .call_fn_raw( + None, state, op, fn_spec, fn_def, args, def_value, pos, level, + )? .as_bool() .unwrap_or(false) { @@ -1239,9 +1260,10 @@ impl Engine { Expr::FloatConstant(f, _) => Ok((*f).into()), Expr::StringConstant(s, _) => Ok(s.to_string().into()), Expr::CharConstant(c, _) => Ok((*c).into()), - Expr::Variable(id, modules, index, pos) => { + Expr::Variable(id, modules, hash, index, pos) => { let index = if state.always_search { None } else { *index }; - let (val, _) = search_scope(scope, id, modules, index, *pos)?; + let mod_and_hash = modules.as_ref().map(|m| (m, *hash)); + let (val, _) = search_scope(scope, id, mod_and_hash, index, *pos)?; Ok(val.clone()) } Expr::Property(_, _) => unreachable!(), @@ -1255,9 +1277,10 @@ impl Engine { match lhs.as_ref() { // name = rhs - Expr::Variable(id, modules, index, pos) => { + Expr::Variable(id, modules, hash, index, pos) => { let index = if state.always_search { None } else { *index }; - let (value_ptr, typ) = search_scope(scope, id, modules, index, *pos)?; + let mod_and_hash = modules.as_ref().map(|m| (m, *hash)); + let (value_ptr, typ) = search_scope(scope, id, mod_and_hash, index, *pos)?; match typ { ScopeEntryType::Constant => Err(Box::new( EvalAltResult::ErrorAssignmentToConstant(id.to_string(), *pos), @@ -1332,7 +1355,7 @@ impl Engine { )))), // Normal function call - Expr::FnCall(fn_name, None, arg_exprs, def_val, pos) => { + Expr::FnCall(fn_name, None, hash, arg_exprs, def_val, pos) => { let mut arg_values = arg_exprs .iter() .map(|expr| self.eval_expr(scope, state, expr, level)) @@ -1340,9 +1363,12 @@ impl Engine { let mut args: Vec<_> = arg_values.iter_mut().collect(); + let hash_fn_spec = + calc_fn_hash(empty(), KEYWORD_EVAL, once(TypeId::of::())); + if fn_name.as_ref() == KEYWORD_EVAL && args.len() == 1 - && !self.has_override(state, KEYWORD_EVAL) + && !self.has_override(state, hash_fn_spec, *hash) { // eval - only in function call style let prev_len = scope.len(); @@ -1361,13 +1387,13 @@ impl Engine { } else { // Normal function call - except for eval (handled above) let def_value = def_val.as_deref(); - self.exec_fn_call(state, fn_name, &mut args, def_value, *pos, level) + self.exec_fn_call(state, fn_name, *hash, &mut args, def_value, *pos, level) } } // Module-qualified function call #[cfg(not(feature = "no_module"))] - Expr::FnCall(fn_name, Some(modules), arg_exprs, def_val, pos) => { + Expr::FnCall(fn_name, Some(modules), hash1, arg_exprs, def_val, pos) => { let modules = modules.as_ref(); let mut arg_values = arg_exprs @@ -1392,7 +1418,7 @@ impl Engine { }; // First search in script-defined functions (can override built-in) - if let Some(fn_def) = module.get_qualified_scripted_fn(modules.key()) { + if let Some(fn_def) = module.get_qualified_scripted_fn(*hash1) { self.call_script_fn(None, state, fn_def, &mut args, *pos, level) } else { // Then search in Rust functions @@ -1400,12 +1426,11 @@ impl Engine { // 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 = modules.key(); // 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())); // 3) The final hash is the XOR of the two hashes. - let hash = hash1 ^ hash2; + let hash = *hash1 ^ hash2; match module.get_qualified_fn(fn_name, hash, *pos) { Ok(func) => func(&mut args, *pos), diff --git a/src/module.rs b/src/module.rs index 32ae0874..75c2b126 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)); + /// let hash = module.set_fn_0("calc", || Ok(42_i64), false); /// assert!(module.contains_fn(hash)); /// ``` pub fn contains_fn(&self, hash: u64) -> bool { @@ -567,7 +567,7 @@ impl Module { /// use rhai::Module; /// /// let mut module = Module::new(); - /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); + /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1), false); /// assert!(module.get_fn(hash).is_some()); /// ``` pub fn get_fn(&self, hash: u64) -> Option<&Box> { @@ -894,18 +894,14 @@ mod file { /// 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)>, Option, u64); +pub struct ModuleRef(StaticVec<(String, Position)>, Option); impl fmt::Debug for ModuleRef { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f)?; - if self.2 > 0 { - if let Some(index) = self.1 { - write!(f, " -> {},{:0>16x}", index, self.2) - } else { - write!(f, " -> {:0>16x}", self.2) - } + if let Some(index) = self.1 { + write!(f, " -> {}", index) } else { Ok(()) } @@ -937,17 +933,11 @@ impl fmt::Display for ModuleRef { impl From> for ModuleRef { fn from(modules: StaticVec<(String, Position)>) -> Self { - Self(modules, None, 0) + Self(modules, None) } } impl ModuleRef { - pub(crate) fn key(&self) -> u64 { - self.2 - } - pub(crate) fn set_key(&mut self, key: u64) { - self.2 = key - } pub(crate) fn index(&self) -> Option { self.1 } diff --git a/src/optimize.rs b/src/optimize.rs index 2c4e183a..8a1ad745 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -367,12 +367,17 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { //id = id2 = expr2 Expr::Assignment(id2, expr2, pos2) => match (*id, *id2) { // var = var = expr2 -> var = expr2 - (Expr::Variable(var, None, sp, _), Expr::Variable(var2, None, sp2, _)) + (Expr::Variable(var, None, index, sp, _), Expr::Variable(var2, None, _, sp2, _)) if var == var2 && sp == sp2 => { // Assignment to the same variable - fold state.set_dirty(); - Expr::Assignment(Box::new(Expr::Variable(var, None, sp, pos)), Box::new(optimize_expr(*expr2, state)), pos) + + Expr::Assignment( + Box::new(Expr::Variable(var, None, index, sp, pos)), + Box::new(optimize_expr(*expr2, state)) + , pos + ) } // id1 = id2 = expr2 (id1, id2) => Expr::Assignment( @@ -544,18 +549,18 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { }, // Do not call some special keywords - Expr::FnCall(id, None, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref().as_ref())=> - Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), + Expr::FnCall(id, None, index, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref().as_ref())=> + Expr::FnCall(id, None, index, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), // Eagerly call functions - Expr::FnCall(id, None, args, def_value, pos) + Expr::FnCall(id, None, index, args, def_value, pos) if state.optimization_level == OptimizationLevel::Full // full optimizations && args.iter().all(|expr| expr.is_constant()) // all arguments are constants => { // First search in script-defined functions (can override built-in) if state.fn_lib.iter().find(|(name, len)| name == id.as_ref() && *len == args.len()).is_some() { // A script-defined function overrides the built-in function - do not make the call - return Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos); + return Expr::FnCall(id, None, index, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos); } let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); @@ -586,16 +591,16 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { }) ).unwrap_or_else(|| // Optimize function call arguments - Expr::FnCall(id, None, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos) + Expr::FnCall(id, None, index, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos) ) } // id(args ..) -> optimize function call arguments - Expr::FnCall(id, modules, args, def_value, pos) => - Expr::FnCall(id, modules, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), + Expr::FnCall(id, modules, index, args, def_value, pos) => + Expr::FnCall(id, modules, index, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos), // constant-name - Expr::Variable(name, None, _, pos) if state.contains_constant(&name) => { + Expr::Variable(name, None, _, _, pos) if state.contains_constant(&name) => { state.set_dirty(); // Replace constant with value diff --git a/src/parser.rs b/src/parser.rs index 9585b53a..fbca086e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -382,6 +382,7 @@ pub enum Expr { Box, #[cfg(not(feature = "no_module"))] Option>, #[cfg(feature = "no_module")] Option, + u64, Option, Position, ), @@ -389,13 +390,14 @@ pub enum Expr { Property(String, Position), /// { stmt } Stmt(Box, Position), - /// func(expr, ... ) - (function name, optional modules, arguments, optional default value, position) + /// func(expr, ... ) - (function name, optional modules, hash, arguments, optional default value, position) /// 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>, #[cfg(not(feature = "no_module"))] Option>, #[cfg(feature = "no_module")] Option, + u64, Box>, Option>, Position, @@ -499,10 +501,10 @@ impl Expr { | Self::StringConstant(_, pos) | Self::Array(_, pos) | Self::Map(_, pos) - | Self::Variable(_, _, _, pos) + | Self::Variable(_, _, _, _, pos) | Self::Property(_, pos) | Self::Stmt(_, pos) - | Self::FnCall(_, _, _, _, pos) + | Self::FnCall(_, _, _, _, _, pos) | Self::And(_, _, pos) | Self::Or(_, _, pos) | Self::In(_, _, pos) @@ -527,10 +529,10 @@ impl Expr { | Self::StringConstant(_, pos) | Self::Array(_, pos) | Self::Map(_, pos) - | Self::Variable(_, _, _, pos) + | Self::Variable(_, _, _, _, pos) | Self::Property(_, pos) | Self::Stmt(_, pos) - | Self::FnCall(_, _, _, _, pos) + | Self::FnCall(_, _, _, _, _, pos) | Self::And(_, _, pos) | Self::Or(_, _, pos) | Self::In(_, _, pos) @@ -558,7 +560,7 @@ impl Expr { Self::Stmt(stmt, _) => stmt.is_pure(), - Self::Variable(_, _, _, _) => true, + Self::Variable(_, _, _, _, _) => true, expr => expr.is_constant(), } @@ -611,7 +613,7 @@ impl Expr { Self::StringConstant(_, _) | Self::Stmt(_, _) - | Self::FnCall(_, _, _, _, _) + | Self::FnCall(_, _, _, _, _, _) | Self::Assignment(_, _, _) | Self::Dot(_, _, _) | Self::Index(_, _, _) @@ -621,7 +623,7 @@ impl Expr { _ => false, }, - Self::Variable(_, _, _, _) => match token { + Self::Variable(_, _, _, _, _) => match token { Token::LeftBracket | Token::LeftParen => true, #[cfg(not(feature = "no_module"))] Token::DoubleColon => true, @@ -638,7 +640,7 @@ impl Expr { /// Convert a `Variable` into a `Property`. All other variants are untouched. pub(crate) fn into_property(self) -> Self { match self { - Self::Variable(id, None, _, pos) => Self::Property(*id, pos), + Self::Variable(id, None, _, _, pos) => Self::Property(*id, pos), _ => self, } } @@ -725,24 +727,31 @@ fn parse_call_expr<'a>( eat_token(input, Token::RightParen); #[cfg(not(feature = "no_module"))] - { + let hash1 = { 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). + // 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. - // Cache the first hash - modules.set_key(hash1); - modules.set_index(stack.find_module(&modules.get(0).0)); + hash1 + } 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()); + return Ok(Expr::FnCall( Box::new(id.into()), modules, + hash1, Box::new(args), None, begin, @@ -761,8 +770,10 @@ fn parse_call_expr<'a>( eat_token(input, Token::RightParen); #[cfg(not(feature = "no_module"))] - { + let hash1 = { 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). @@ -775,14 +786,19 @@ fn parse_call_expr<'a>( // the actual list of parameter `TypeId`'.s // 3) The final hash is the XOR of the two hashes. - // Cache the first hash - modules.set_key(hash1); - modules.set_index(stack.find_module(&modules.get(0).0)); + hash1 + } 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())); + return Ok(Expr::FnCall( Box::new(id.into()), modules, + hash1, Box::new(args), None, begin, @@ -1139,7 +1155,7 @@ fn parse_primary<'a>( Token::StringConst(s) => Expr::StringConstant(s, pos), Token::Identifier(s) => { let index = stack.find(&s); - Expr::Variable(Box::new(s), None, index, pos) + Expr::Variable(Box::new(s), None, 0, index, pos) } Token::LeftParen => parse_paren_expr(input, stack, pos, allow_stmt_expr)?, #[cfg(not(feature = "no_index"))] @@ -1166,7 +1182,7 @@ fn parse_primary<'a>( root_expr = match (root_expr, token) { // Function call - (Expr::Variable(id, modules, _, pos), Token::LeftParen) => { + (Expr::Variable(id, modules, _, _, pos), Token::LeftParen) => { parse_call_expr(input, stack, *id, modules, pos, allow_stmt_expr)? } (Expr::Property(id, pos), Token::LeftParen) => { @@ -1174,7 +1190,7 @@ fn parse_primary<'a>( } // module access #[cfg(not(feature = "no_module"))] - (Expr::Variable(id, mut modules, index, pos), Token::DoubleColon) => { + (Expr::Variable(id, mut modules, _, index, pos), Token::DoubleColon) => { match input.next().unwrap() { (Token::Identifier(id2), pos2) => { if let Some(ref mut modules) = modules { @@ -1185,7 +1201,7 @@ fn parse_primary<'a>( modules = Some(Box::new(m)); } - Expr::Variable(Box::new(id2), modules, index, pos2) + Expr::Variable(Box::new(id2), modules, 0, index, pos2) } (_, pos2) => return Err(PERR::VariableExpected.into_err(pos2)), } @@ -1203,10 +1219,9 @@ fn parse_primary<'a>( match &mut root_expr { // Cache the hash key for module-qualified variables #[cfg(not(feature = "no_module"))] - Expr::Variable(id, Some(modules), _, _) => { + Expr::Variable(id, Some(modules), hash, _, _) => { // Qualifiers + variable name - let hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), id, empty()); - modules.set_key(hash); + *hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), id, empty()); modules.set_index(stack.find_module(&modules.get(0).0)); } _ => (), @@ -1259,13 +1274,19 @@ fn parse_unary<'a>( Expr::FloatConstant(f, pos) => Ok(Expr::FloatConstant(-f, pos)), // Call negative function - e => Ok(Expr::FnCall( - Box::new("-".into()), - None, - Box::new(vec![e]), - None, - pos, - )), + e => { + let op = "-"; + let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2)); + + Ok(Expr::FnCall( + Box::new(op.into()), + None, + hash, + Box::new(vec![e]), + None, + pos, + )) + } } } // +expr @@ -1276,9 +1297,13 @@ fn parse_unary<'a>( // !expr (Token::Bang, _) => { let pos = eat_token(input, Token::Bang); + let op = "!"; + let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2)); + Ok(Expr::FnCall( - Box::new("!".into()), + Box::new(op.into()), None, + hash, Box::new(vec![parse_primary(input, stack, allow_stmt_expr)?]), Some(Box::new(false.into())), // NOT operator, when operating on invalid operand, defaults to false pos, @@ -1298,8 +1323,8 @@ fn make_assignment_stmt<'a>( pos: Position, ) -> Result> { match &lhs { - Expr::Variable(_, _, None, _) => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)), - Expr::Variable(name, _, Some(index), var_pos) => { + Expr::Variable(_, _, _, None, _) => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)), + Expr::Variable(name, _, _, Some(index), var_pos) => { match stack[(stack.len() - index.get())].1 { ScopeEntryType::Normal => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)), // Constant values cannot be assigned to @@ -1310,10 +1335,10 @@ fn make_assignment_stmt<'a>( } } Expr::Index(idx_lhs, _, _) | Expr::Dot(idx_lhs, _, _) => match idx_lhs.as_ref() { - Expr::Variable(_, _, None, _) => { + Expr::Variable(_, _, _, None, _) => { Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)) } - Expr::Variable(name, _, Some(index), var_pos) => { + Expr::Variable(name, _, _, Some(index), var_pos) => { match stack[(stack.len() - index.get())].1 { ScopeEntryType::Normal => { Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)) @@ -1368,7 +1393,8 @@ fn parse_op_assignment_stmt<'a>( // lhs op= rhs -> lhs = op(lhs, rhs) let args = vec![lhs_copy, rhs]; - let rhs_expr = Expr::FnCall(Box::new(op.into()), None, Box::new(args), None, pos); + 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, Box::new(args), None, pos); make_assignment_stmt(stack, lhs, rhs_expr, pos) } @@ -1388,12 +1414,12 @@ fn make_dot_expr( idx_pos, ), // lhs.id - (lhs, rhs @ Expr::Variable(_, None, _, _)) | (lhs, rhs @ Expr::Property(_, _)) => { + (lhs, rhs @ Expr::Variable(_, None, _, _, _)) | (lhs, rhs @ Expr::Property(_, _)) => { let lhs = if is_index { lhs.into_property() } else { lhs }; Expr::Dot(Box::new(lhs), Box::new(rhs.into_property()), op_pos) } // lhs.module::id - syntax error - (_, Expr::Variable(_, Some(modules), _, _)) => { + (_, Expr::Variable(_, Some(modules), _, _, _)) => { #[cfg(feature = "no_module")] unreachable!(); #[cfg(not(feature = "no_module"))] @@ -1609,6 +1635,7 @@ fn parse_binary_op<'a>( Token::Plus => Expr::FnCall( Box::new("+".into()), None, + calc_fn_hash(empty(), "+", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), None, pos, @@ -1616,6 +1643,7 @@ fn parse_binary_op<'a>( Token::Minus => Expr::FnCall( Box::new("-".into()), None, + calc_fn_hash(empty(), "-", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), None, pos, @@ -1623,6 +1651,7 @@ fn parse_binary_op<'a>( Token::Multiply => Expr::FnCall( Box::new("*".into()), None, + calc_fn_hash(empty(), "*", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), None, pos, @@ -1630,6 +1659,7 @@ fn parse_binary_op<'a>( Token::Divide => Expr::FnCall( Box::new("/".into()), None, + calc_fn_hash(empty(), "/", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), None, pos, @@ -1638,6 +1668,7 @@ fn parse_binary_op<'a>( Token::LeftShift => Expr::FnCall( Box::new("<<".into()), None, + calc_fn_hash(empty(), "<<", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), None, pos, @@ -1645,6 +1676,7 @@ fn parse_binary_op<'a>( Token::RightShift => Expr::FnCall( Box::new(">>".into()), None, + calc_fn_hash(empty(), ">>", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), None, pos, @@ -1652,6 +1684,7 @@ fn parse_binary_op<'a>( Token::Modulo => Expr::FnCall( Box::new("%".into()), None, + calc_fn_hash(empty(), "%", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), None, pos, @@ -1659,6 +1692,7 @@ fn parse_binary_op<'a>( Token::PowerOf => Expr::FnCall( Box::new("~".into()), None, + calc_fn_hash(empty(), "~", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), None, pos, @@ -1668,6 +1702,7 @@ fn parse_binary_op<'a>( Token::EqualsTo => Expr::FnCall( Box::new("==".into()), None, + calc_fn_hash(empty(), "==", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), cmp_default, pos, @@ -1675,6 +1710,7 @@ fn parse_binary_op<'a>( Token::NotEqualsTo => Expr::FnCall( Box::new("!=".into()), None, + calc_fn_hash(empty(), "!=", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), cmp_default, pos, @@ -1682,6 +1718,7 @@ fn parse_binary_op<'a>( Token::LessThan => Expr::FnCall( Box::new("<".into()), None, + calc_fn_hash(empty(), "<", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), cmp_default, pos, @@ -1689,6 +1726,7 @@ fn parse_binary_op<'a>( Token::LessThanEqualsTo => Expr::FnCall( Box::new("<=".into()), None, + calc_fn_hash(empty(), "<=", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), cmp_default, pos, @@ -1696,6 +1734,7 @@ fn parse_binary_op<'a>( Token::GreaterThan => Expr::FnCall( Box::new(">".into()), None, + calc_fn_hash(empty(), ">", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), cmp_default, pos, @@ -1703,6 +1742,7 @@ fn parse_binary_op<'a>( Token::GreaterThanEqualsTo => Expr::FnCall( Box::new(">=".into()), None, + calc_fn_hash(empty(), ">=", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), cmp_default, pos, @@ -1713,6 +1753,7 @@ fn parse_binary_op<'a>( Token::Ampersand => Expr::FnCall( Box::new("&".into()), None, + calc_fn_hash(empty(), "&", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), None, pos, @@ -1720,6 +1761,7 @@ fn parse_binary_op<'a>( Token::Pipe => Expr::FnCall( Box::new("|".into()), None, + calc_fn_hash(empty(), "|", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), None, pos, @@ -1727,6 +1769,7 @@ fn parse_binary_op<'a>( Token::XOr => Expr::FnCall( Box::new("^".into()), None, + calc_fn_hash(empty(), "^", repeat(EMPTY_TYPE_ID()).take(2)), Box::new(vec![current_lhs, rhs]), None, pos,