diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index 1a557b43..7cc299c1 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -12,7 +12,7 @@ use crate::{ }; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{borrow::Borrow, ops::Deref}; +use std::{borrow::Borrow, collections::BTreeMap, ops::Deref}; /// Collection of special markers for custom syntax definition. pub mod markers { @@ -257,19 +257,29 @@ impl Engine { // Standard or reserved keyword/symbol not in first position _ if !segments.is_empty() && token.is_some() => { // Make it a custom keyword/symbol if it is disabled or reserved - if ((!self.disabled_symbols.is_empty() && self.disabled_symbols.contains(s)) + if (self + .disabled_symbols + .as_ref() + .map_or(false, |m| m.contains(s)) || token.map_or(false, |v| v.is_reserved())) - && (self.custom_keywords.is_empty() - || !self.custom_keywords.contains_key(s)) + && !self + .custom_keywords + .as_ref() + .map_or(false, |m| m.contains_key(s)) { - self.custom_keywords.insert(s.into(), None); + self.custom_keywords + .get_or_insert_with(|| BTreeMap::new().into()) + .insert(s.into(), None); } s.into() } // Standard keyword in first position but not disabled _ if segments.is_empty() && token.as_ref().map_or(false, Token::is_standard_keyword) - && (self.disabled_symbols.is_empty() || !self.disabled_symbols.contains(s)) => + && !self + .disabled_symbols + .as_ref() + .map_or(false, |m| m.contains(s)) => { return Err(LexError::ImproperSymbol( s.to_string(), @@ -283,12 +293,19 @@ impl Engine { // Identifier in first position _ if segments.is_empty() && is_valid_identifier(s) => { // Make it a custom keyword/symbol if it is disabled or reserved - if (!self.disabled_symbols.is_empty() && self.disabled_symbols.contains(s)) - || token.map_or(false, |v| v.is_reserved()) - && self.custom_keywords.is_empty() - || !self.custom_keywords.contains_key(s) + if self + .disabled_symbols + .as_ref() + .map_or(false, |m| m.contains(s)) + || (token.map_or(false, |v| v.is_reserved()) + && !self + .custom_keywords + .as_ref() + .map_or(false, |m| m.contains_key(s))) { - self.custom_keywords.insert(s.into(), None); + self.custom_keywords + .get_or_insert_with(|| BTreeMap::new().into()) + .insert(s.into(), None); } s.into() } @@ -373,14 +390,16 @@ impl Engine { scope_may_be_changed: bool, func: impl Fn(&mut EvalContext, &[Expression], &Dynamic) -> RhaiResult + SendSync + 'static, ) -> &mut Self { - self.custom_syntax.insert( - key.into(), - CustomSyntax { - parse: Box::new(parse), - func: Box::new(func), - scope_may_be_changed, - }, - ); + self.custom_syntax + .get_or_insert_with(|| BTreeMap::new().into()) + .insert( + key.into(), + CustomSyntax { + parse: Box::new(parse), + func: Box::new(func), + scope_may_be_changed, + }, + ); self } } diff --git a/src/api/definitions/mod.rs b/src/api/definitions/mod.rs index 0c6a84b1..e09159ef 100644 --- a/src/api/definitions/mod.rs +++ b/src/api/definitions/mod.rs @@ -369,6 +369,7 @@ impl Definitions<'_> { .engine .global_sub_modules .iter() + .flat_map(|m| m.iter()) .map(move |(name, module)| { ( name.to_string(), @@ -445,13 +446,16 @@ impl Module { first = false; if f.access != FnAccess::Private { - #[cfg(not(feature = "no_custom_syntax"))] - let operator = def.engine.custom_keywords.contains_key(f.name.as_str()) - || (!f.name.contains('$') && !is_valid_function_name(f.name.as_str())); - - #[cfg(feature = "no_custom_syntax")] let operator = !f.name.contains('$') && !is_valid_function_name(&f.name); + #[cfg(not(feature = "no_custom_syntax"))] + let operator = operator + || def + .engine + .custom_keywords + .as_ref() + .map_or(false, |m| m.contains_key(f.name.as_str())); + f.write_definition(writer, def, operator)?; } } diff --git a/src/api/events.rs b/src/api/events.rs index a44ee2a4..eff11efd 100644 --- a/src/api/events.rs +++ b/src/api/events.rs @@ -360,7 +360,7 @@ impl Engine { + SendSync + 'static, ) -> &mut Self { - self.debugger = Some((Box::new(init), Box::new(callback))); + self.debugger = Some(Box::new((Box::new(init), Box::new(callback)))); self } } diff --git a/src/api/mod.rs b/src/api/mod.rs index 6f1ad649..6b48c744 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -35,6 +35,7 @@ pub mod definitions; use crate::{Dynamic, Engine, Identifier}; +use std::collections::BTreeSet; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -107,7 +108,9 @@ impl Engine { /// ``` #[inline(always)] pub fn disable_symbol(&mut self, symbol: impl Into) -> &mut Self { - self.disabled_symbols.insert(symbol.into()); + self.disabled_symbols + .get_or_insert_with(|| BTreeSet::new().into()) + .insert(symbol.into()); self } @@ -163,18 +166,31 @@ impl Engine { // Active standard keywords cannot be made custom // Disabled keywords are OK Some(token) if token.is_standard_keyword() => { - if !self.disabled_symbols.contains(token.literal_syntax()) { + if !self + .disabled_symbols + .as_ref() + .map_or(false, |m| m.contains(token.literal_syntax())) + { return Err(format!("'{keyword}' is a reserved keyword")); } } // Active standard symbols cannot be made custom Some(token) if token.is_standard_symbol() => { - if !self.disabled_symbols.contains(token.literal_syntax()) { + if !self + .disabled_symbols + .as_ref() + .map_or(false, |m| m.contains(token.literal_syntax())) + { return Err(format!("'{keyword}' is a reserved operator")); } } // Active standard symbols cannot be made custom - Some(token) if !self.disabled_symbols.contains(token.literal_syntax()) => { + Some(token) + if !self + .disabled_symbols + .as_ref() + .map_or(false, |m| m.contains(token.literal_syntax())) => + { return Err(format!("'{keyword}' is a reserved symbol")) } // Disabled symbols are OK @@ -183,6 +199,7 @@ impl Engine { // Add to custom keywords self.custom_keywords + .get_or_insert_with(|| std::collections::BTreeMap::new().into()) .insert(keyword.into(), Some(precedence)); Ok(self) diff --git a/src/api/register.rs b/src/api/register.rs index 7f590266..518c524b 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -683,8 +683,10 @@ impl Engine { name: impl AsRef, module: SharedModule, ) -> &mut Self { + use std::collections::BTreeMap; + fn register_static_module_raw( - root: &mut std::collections::BTreeMap, + root: &mut BTreeMap, name: &str, module: SharedModule, ) { @@ -717,7 +719,12 @@ impl Engine { } } - register_static_module_raw(&mut self.global_sub_modules, name.as_ref(), module); + register_static_module_raw( + self.global_sub_modules + .get_or_insert_with(|| BTreeMap::new().into()), + name.as_ref(), + module, + ); self } /// _(metadata)_ Generate a list of all registered functions. @@ -737,7 +744,7 @@ impl Engine { signatures.extend(self.global_namespace().gen_fn_signatures()); #[cfg(not(feature = "no_module"))] - for (name, m) in &self.global_sub_modules { + for (name, m) in self.global_sub_modules.iter().flat_map(|m| m.iter()) { signatures.extend(m.gen_fn_signatures().map(|f| format!("{name}::{f}"))); } diff --git a/src/api/type_names.rs b/src/api/type_names.rs index 07434e4d..258991ab 100644 --- a/src/api/type_names.rs +++ b/src/api/type_names.rs @@ -205,6 +205,7 @@ impl Engine { return self .global_sub_modules .iter() + .flat_map(|m| m.iter()) .find_map(|(_, m)| m.get_custom_type(name)); #[cfg(feature = "no_module")] return None; @@ -238,6 +239,7 @@ impl Engine { return self .global_sub_modules .iter() + .flat_map(|m| m.iter()) .find_map(|(_, m)| m.get_custom_type(name)); #[cfg(feature = "no_module")] return None; diff --git a/src/engine.rs b/src/engine.rs index a17fb24d..82993f46 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -96,7 +96,8 @@ pub struct Engine { pub(crate) global_modules: StaticVec, /// A collection of all sub-modules directly loaded into the Engine. #[cfg(not(feature = "no_module"))] - pub(crate) global_sub_modules: std::collections::BTreeMap, + pub(crate) global_sub_modules: + Option>>, /// A module resolution service. #[cfg(not(feature = "no_module"))] @@ -106,14 +107,16 @@ pub struct Engine { pub(crate) interned_strings: Locked, /// A set of symbols to disable. - pub(crate) disabled_symbols: BTreeSet, + pub(crate) disabled_symbols: Option>>, /// A map containing custom keywords and precedence to recognize. #[cfg(not(feature = "no_custom_syntax"))] - pub(crate) custom_keywords: std::collections::BTreeMap>, + pub(crate) custom_keywords: + Option>>>, /// Custom syntax. #[cfg(not(feature = "no_custom_syntax"))] - pub(crate) custom_syntax: - std::collections::BTreeMap, + pub(crate) custom_syntax: Option< + Box>, + >, /// Callback closure for filtering variable definition. pub(crate) def_var_filter: Option>, /// Callback closure for resolving variable access. @@ -144,10 +147,12 @@ pub struct Engine { /// Callback closure for debugging. #[cfg(feature = "debugging")] - pub(crate) debugger: Option<( - Box, - Box, - )>, + pub(crate) debugger: Option< + Box<( + Box, + Box, + )>, + >, } impl fmt::Debug for Engine { @@ -168,7 +173,8 @@ impl fmt::Debug for Engine { "custom_syntax", &self .custom_syntax - .keys() + .iter() + .flat_map(|m| m.keys()) .map(crate::SmartString::as_str) .collect::(), ); @@ -264,17 +270,17 @@ impl Engine { global_modules: StaticVec::new_const(), #[cfg(not(feature = "no_module"))] - global_sub_modules: std::collections::BTreeMap::new(), + global_sub_modules: None, #[cfg(not(feature = "no_module"))] module_resolver: Box::new(crate::module::resolvers::DummyModuleResolver::new()), interned_strings: StringsInterner::new().into(), - disabled_symbols: BTreeSet::new(), + disabled_symbols: None, #[cfg(not(feature = "no_custom_syntax"))] - custom_keywords: std::collections::BTreeMap::new(), + custom_keywords: None, #[cfg(not(feature = "no_custom_syntax"))] - custom_syntax: std::collections::BTreeMap::new(), + custom_syntax: None, def_var_filter: None, resolve_var: None, diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index 92f6bc0a..93663293 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -512,7 +512,9 @@ impl Engine { let src = src.as_ref().map(|s| s.as_str()); let context = crate::EvalContext::new(self, global, caches, scope, this_ptr); - if let Some((.., ref on_debugger)) = self.debugger { + if let Some(ref x) = self.debugger { + let (.., ref on_debugger) = **x; + let command = on_debugger(context, event, node, src, node.position())?; match command { diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 0ee555f4..a7c4a260 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -36,7 +36,12 @@ impl Engine { // Do a text-match search if the index doesn't work global.find_import(root).map_or_else( - || self.global_sub_modules.get(root).cloned(), + || { + self.global_sub_modules + .as_ref() + .and_then(|m| m.get(root)) + .cloned() + }, |offset| global.get_shared_import(offset), ) } @@ -401,13 +406,17 @@ impl Engine { // The first token acts as the custom syntax's key let key_token = custom.tokens.first().unwrap(); // The key should exist, unless the AST is compiled in a different Engine - let custom_def = self.custom_syntax.get(key_token.as_str()).ok_or_else(|| { - Box::new(ERR::ErrorCustomSyntax( - format!("Invalid custom syntax prefix: {key_token}"), - custom.tokens.iter().map(<_>::to_string).collect(), - *pos, - )) - })?; + let custom_def = self + .custom_syntax + .as_ref() + .and_then(|m| m.get(key_token.as_str())) + .ok_or_else(|| { + Box::new(ERR::ErrorCustomSyntax( + format!("Invalid custom syntax prefix: {key_token}"), + custom.tokens.iter().map(<_>::to_string).collect(), + *pos, + )) + })?; let mut context = EvalContext::new(self, global, caches, scope, this_ptr); (custom_def.func)(&mut context, &expressions, &custom.state) diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index 98ba8263..6070ca11 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -111,8 +111,8 @@ impl GlobalRuntimeState { tag: engine.default_tag().clone(), #[cfg(feature = "debugging")] - debugger: engine.debugger.as_ref().map(|(init, ..)| { - crate::eval::Debugger::new(crate::eval::DebuggerStatus::Init, init(engine)) + debugger: engine.debugger.as_ref().map(|x| { + crate::eval::Debugger::new(crate::eval::DebuggerStatus::Init, (x.0)(engine)) }), } } diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index d0ddbfcc..198570a3 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -502,7 +502,8 @@ impl Engine { #[cfg(not(feature = "no_module"))] let func = func.or_else(|| global.get_iter(iter_type)).or_else(|| { self.global_sub_modules - .values() + .iter() + .flat_map(|m| m.values()) .find_map(|m| m.get_qualified_iter(iter_type)) }); diff --git a/src/func/call.rs b/src/func/call.rs index 532c0892..591a5d4b 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -212,7 +212,8 @@ impl Engine { } else { func.or_else(|| _global.get_qualified_fn(hash)).or_else(|| { self.global_sub_modules - .values() + .iter() + .flat_map(|m| m.values()) .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) }) }; @@ -252,7 +253,8 @@ impl Engine { || _global.may_contain_dynamic_fn(hash_base) || self .global_sub_modules - .values() + .iter() + .flat_map(|m| m.values()) .any(|m| m.may_contain_dynamic_fn(hash_base)); // Set maximum bitmask when there are dynamic versions of the function diff --git a/src/func/script.rs b/src/func/script.rs index efdc69de..b1352450 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -236,7 +236,7 @@ impl Engine { // Then check imported modules global.contains_qualified_fn(hash_script) // Then check sub-modules - || self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script)); + || self.global_sub_modules.iter().flat_map(|m| m.values()).any(|m| m.contains_qualified_fn(hash_script)); if !result && !cache.filter.is_absent_and_set(hash_script) { // Do not cache "one-hit wonders" diff --git a/src/optimizer.rs b/src/optimizer.rs index 63b519f8..4a3bc5ad 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -179,7 +179,8 @@ fn has_native_fn_override( #[cfg(not(feature = "no_module"))] if engine .global_sub_modules - .values() + .iter() + .flat_map(|m| m.values()) .any(|m| m.contains_qualified_fn(hash)) { return true; diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index 40d1f1b2..a4396514 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -267,7 +267,8 @@ fn collect_fn_metadata( #[cfg(not(feature = "no_module"))] ctx.engine() .global_sub_modules - .values() + .iter() + .flat_map(|m| m.values()) .flat_map(|m| m.iter_script_fn()) .filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f)) .for_each(|(.., f)| { diff --git a/src/parser.rs b/src/parser.rs index d1882614..d299e121 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -600,7 +600,10 @@ impl Engine { && index.is_none() && !is_global && !state.global_imports.iter().any(|m| m.as_str() == root) - && !self.global_sub_modules.contains_key(root) + && !self + .global_sub_modules + .as_ref() + .map_or(false, |m| m.contains_key(root)) { return Err( PERR::ModuleUndefined(root.into()).into_err(namespace.position()) @@ -668,7 +671,10 @@ impl Engine { && index.is_none() && !is_global && !state.global_imports.iter().any(|m| m.as_str() == root) - && !self.global_sub_modules.contains_key(root) + && !self + .global_sub_modules + .as_ref() + .map_or(false, |m| m.contains_key(root)) { return Err( PERR::ModuleUndefined(root.into()).into_err(namespace.position()) @@ -1550,9 +1556,16 @@ impl Engine { // Custom syntax. #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) - if !self.custom_syntax.is_empty() && self.custom_syntax.contains_key(&**key) => + if self + .custom_syntax + .as_ref() + .map_or(false, |m| m.contains_key(&**key)) => { - let (key, syntax) = self.custom_syntax.get_key_value(&**key).unwrap(); + let (key, syntax) = self + .custom_syntax + .as_ref() + .and_then(|m| m.get_key_value(&**key)) + .unwrap(); let (.., pos) = input.next().expect(NEVER_ENDS); let settings2 = settings.level_up()?; self.parse_custom_syntax(input, state, lib, settings2, key, syntax, pos)? @@ -1856,7 +1869,10 @@ impl Engine { && index.is_none() && !is_global && !state.global_imports.iter().any(|m| m.as_str() == root) - && !self.global_sub_modules.contains_key(root) + && !self + .global_sub_modules + .as_ref() + .map_or(false, |m| m.contains_key(root)) { return Err( PERR::ModuleUndefined(root.into()).into_err(namespace.position()) @@ -2297,7 +2313,8 @@ impl Engine { #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(c) => self .custom_keywords - .get(&**c) + .as_ref() + .and_then(|m| m.get(&**c)) .copied() .ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*current_pos))?, Token::Reserved(c) if !is_valid_identifier(c) => { @@ -2322,7 +2339,8 @@ impl Engine { #[cfg(not(feature = "no_custom_syntax"))] Token::Custom(c) => self .custom_keywords - .get(&**c) + .as_ref() + .and_then(|m| m.get(&**c)) .copied() .ok_or_else(|| PERR::Reserved(c.to_string()).into_err(*next_pos))?, Token::Reserved(c) if !is_valid_identifier(c) => { @@ -2414,7 +2432,8 @@ impl Engine { Token::Custom(s) if self .custom_keywords - .get(s.as_str()) + .as_ref() + .and_then(|m| m.get(s.as_str())) .map_or(false, Option::is_some) => { op_base.hashes = if is_valid_script_function { diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 2481c028..50c76d86 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -170,7 +170,7 @@ pub fn gen_metadata_to_json( let mut global = ModuleMetadata::new(); #[cfg(not(feature = "no_module"))] - for (name, m) in &engine.global_sub_modules { + for (name, m) in engine.global_sub_modules.iter().flat_map(|m| m.iter()) { global.modules.insert(name, m.as_ref().into()); } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index df776d9b..8d7ee4a4 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -2435,7 +2435,7 @@ impl<'a> Iterator for TokenIterator<'a> { Some((Token::Reserved(s), pos)) => (match (s.as_str(), #[cfg(not(feature = "no_custom_syntax"))] - self.engine.custom_keywords.contains_key(&*s), + self.engine.custom_keywords.as_ref().map_or(false, |m| m.contains_key(&*s)), #[cfg(feature = "no_custom_syntax")] false ) @@ -2472,7 +2472,7 @@ impl<'a> Iterator for TokenIterator<'a> { #[cfg(feature = "no_custom_syntax")] (.., true) => unreachable!("no custom operators"), // Reserved keyword that is not custom and disabled. - (token, false) if self.engine.disabled_symbols.contains(token) => { + (token, false) if self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token)) => { let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token) { "keyword"} else {"symbol"}); Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into()) }, @@ -2481,13 +2481,13 @@ impl<'a> Iterator for TokenIterator<'a> { }, pos), // Custom keyword #[cfg(not(feature = "no_custom_syntax"))] - Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&*s) => { + Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.as_ref().map_or(false,|m| m.contains_key(&*s)) => { (Token::Custom(s), pos) } // Custom keyword/symbol - must be disabled #[cfg(not(feature = "no_custom_syntax"))] - Some((token, pos)) if token.is_literal() && self.engine.custom_keywords.contains_key(token.literal_syntax()) => { - if self.engine.disabled_symbols.contains(token.literal_syntax()) { + Some((token, pos)) if token.is_literal() && self.engine.custom_keywords.as_ref().map_or(false,|m| m.contains_key(token.literal_syntax())) => { + if self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token.literal_syntax())) { // Disabled standard keyword/symbol (Token::Custom(Box::new(token.literal_syntax().into())), pos) } else { @@ -2496,7 +2496,7 @@ impl<'a> Iterator for TokenIterator<'a> { } } // Disabled symbol - Some((token, pos)) if token.is_literal() && self.engine.disabled_symbols.contains(token.literal_syntax()) => { + Some((token, pos)) if token.is_literal() && self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token.literal_syntax())) => { (Token::Reserved(Box::new(token.literal_syntax().into())), pos) } // Normal symbol