diff --git a/CHANGELOG.md b/CHANGELOG.md index 9178cf87..58425903 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Rhai Release Notes Version 1.12.0 ============== +Buf fixes +--------- + +* Integer numbers that are too large to deserialize into `INT` now fall back to `Decimal` or `FLOAT` instead of silently truncating. + Net features ------------ @@ -20,6 +25,7 @@ Enhancements * The `TypeBuilder` type and `CustomType` trait are no longer marked as volatile. * `FuncArgs` is also implemented for arrays. * `Engine::set_XXX` API can now be chained. +* `EvalContext::scope_mut` now returns `&mut Scope` instead of `&mut &mut Scope`. Version 1.11.0 diff --git a/build.template b/build.template index bce322e8..783764e1 100644 --- a/build.template +++ b/build.template @@ -1,3 +1,3 @@ //! This file is automatically recreated during build time by `build.rs` from `build.template`. -pub(crate) const AHASH_SEED: Option<[u64; 4]> = {{AHASH_SEED}}; +pub const AHASH_SEED: Option<[u64; 4]> = {{AHASH_SEED}}; diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index c1efde59..99583472 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -5,6 +5,8 @@ edition = "2018" resolver = "2" authors = ["jhwgh1968", "Stephen Chung"] description = "Procedural macros support package for Rhai, a scripting language and engine for Rust" +keywords = ["rhai", "scripting", "scripting-engine", "scripting-language", "embedded", "plugin", "macros", "code-generation"] +categories = ["no-std", "embedded", "wasm", "parser-implementations"] homepage = "https://rhai.rs/book/plugins/index.html" repository = "https://github.com/rhaiscript/rhai" license = "MIT OR Apache-2.0" diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 833e3d98..aaa07df5 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -313,7 +313,7 @@ impl Parse for ExportedFn { } } - let skip_slots = if pass_context { 1 } else { 0 }; + let skip_slots = usize::from(pass_context); // Determine whether function generates a special calling convention for a mutable receiver. let mut_receiver = match fn_all.sig.inputs.iter().nth(skip_slots) { @@ -485,12 +485,12 @@ impl ExportedFn { } pub fn arg_list(&self) -> impl Iterator { - let skip = if self.pass_context { 1 } else { 0 }; + let skip = usize::from(self.pass_context); self.signature.inputs.iter().skip(skip) } pub fn arg_count(&self) -> usize { - let skip = if self.pass_context { 1 } else { 0 }; + let skip = usize::from(self.pass_context); self.signature.inputs.len() - skip } diff --git a/examples/event_handler_js/main.rs b/examples/event_handler_js/main.rs index f2aac172..e6998d7a 100644 --- a/examples/event_handler_js/main.rs +++ b/examples/event_handler_js/main.rs @@ -8,7 +8,7 @@ pub fn main() { #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_object"))] pub fn main() { - use rhai::{CallFnOptions, Dynamic, Engine, ImmutableString, Map, Scope, AST}; + use rhai::{CallFnOptions, Dynamic, Engine, Map, Scope, AST}; use std::io::{stdin, stdout, Write}; const SCRIPT_FILE: &str = "event_handler_js/script.rhai"; diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index 0d0d5edf..5d584630 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -18,6 +18,7 @@ use std::{ /// Options for calling a script-defined function via [`Engine::call_fn_with_options`]. #[derive(Debug, Hash)] #[non_exhaustive] +#[must_use] pub struct CallFnOptions<'t> { /// A value for binding to the `this` pointer (if any). pub this_ptr: Option<&'t mut Dynamic>, @@ -120,7 +121,7 @@ impl Engine { name: impl AsRef, args: impl FuncArgs, ) -> RhaiResultOf { - self.call_fn_with_options(Default::default(), scope, ast, name, args) + self.call_fn_with_options(CallFnOptions::default(), scope, ast, name, args) } /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. /// @@ -255,25 +256,28 @@ impl Engine { #[cfg(not(feature = "no_closure"))] crate::func::ensure_no_data_race(name, args, false).map(|_| Dynamic::UNIT)?; - if let Some(fn_def) = ast.shared_lib().get_script_fn(name, args.len()) { - self.call_script_fn( - global, - caches, - scope, - this_ptr, - fn_def, - args, - rewind_scope, - Position::NONE, + ast.shared_lib() + .get_script_fn(name, args.len()) + .map_or_else( + || Err(ERR::ErrorFunctionNotFound(name.into(), Position::NONE).into()), + |fn_def| { + self.call_script_fn( + global, + caches, + scope, + this_ptr, + fn_def, + args, + rewind_scope, + Position::NONE, + ) + }, ) - } else { - Err(ERR::ErrorFunctionNotFound(name.into(), Position::NONE).into()) - } }); #[cfg(feature = "debugging")] if self.debugger.is_some() { - global.debugger.status = crate::eval::DebuggerStatus::Terminate; + global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; let node = &crate::ast::Stmt::Noop(Position::NONE); self.run_debugger(global, caches, scope, this_ptr, node)?; } diff --git a/src/api/compile.rs b/src/api/compile.rs index 2d187160..51af56f6 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -1,5 +1,6 @@ //! Module that defines the public compilation API of [`Engine`]. +use crate::func::native::locked_write; use crate::parser::{ParseResult, ParseState}; use crate::{Engine, OptimizationLevel, Scope, AST}; #[cfg(feature = "no_std")] @@ -221,7 +222,8 @@ impl Engine { scripts.as_ref(), self.token_mapper.as_ref().map(<_>::as_ref), ); - let mut state = ParseState::new(self, scope, Default::default(), tokenizer_control); + let interned_strings = &mut *locked_write(&self.interned_strings); + let mut state = ParseState::new(scope, interned_strings, tokenizer_control); let mut _ast = self.parse(&mut stream.peekable(), &mut state, optimization_level)?; #[cfg(feature = "metadata")] _ast.set_doc(state.tokenizer_control.borrow().global_comments.join("\n")); @@ -294,7 +296,8 @@ impl Engine { self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); let mut peekable = stream.peekable(); - let mut state = ParseState::new(self, scope, Default::default(), tokenizer_control); + let interned_strings = &mut *locked_write(&self.interned_strings); + let mut state = ParseState::new(scope, interned_strings, tokenizer_control); self.parse_global_expr(&mut peekable, &mut state, |_| {}, self.optimization_level) } } diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index b884cc24..e378e661 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -217,6 +217,7 @@ impl Engine { scope_may_be_changed: bool, func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, ) -> ParseResult<&mut Self> { + #[allow(clippy::wildcard_imports)] use markers::*; let mut segments = StaticVec::::new(); @@ -256,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(Default::default) + .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(), @@ -282,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(Default::default) + .insert(s.into(), None); } s.into() } @@ -372,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(Default::default) + .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 3ee43af0..86ce3f69 100644 --- a/src/api/definitions/mod.rs +++ b/src/api/definitions/mod.rs @@ -2,7 +2,7 @@ #![cfg(feature = "internals")] #![cfg(feature = "metadata")] -use crate::module::FuncInfo; +use crate::module::{FuncInfo, ModuleFlags}; use crate::tokenizer::{is_valid_function_name, Token}; use crate::{Engine, FnAccess, FnPtr, Module, Scope, INT}; @@ -77,7 +77,6 @@ pub struct DefinitionsConfig { impl Default for DefinitionsConfig { #[inline(always)] - #[must_use] fn default() -> Self { Self { write_headers: false, @@ -105,13 +104,13 @@ impl Definitions<'_> { /// Headers are always present in content that is expected to be written to a file /// (i.e. `write_to*` and `*_file` methods). #[inline(always)] - pub fn with_headers(mut self, headers: bool) -> Self { + pub const fn with_headers(mut self, headers: bool) -> Self { self.config.write_headers = headers; self } /// Include standard packages when writing definition files. #[inline(always)] - pub fn include_standard_packages(mut self, include_standard_packages: bool) -> Self { + pub const fn include_standard_packages(mut self, include_standard_packages: bool) -> Self { self.config.include_standard_packages = include_standard_packages; self } @@ -129,7 +128,6 @@ impl Definitions<'_> { } /// Get the configuration. #[inline(always)] - #[must_use] pub(crate) const fn config(&self) -> &DefinitionsConfig { &self.config } @@ -177,19 +175,19 @@ impl Definitions<'_> { let mut def_file = String::from("module static;\n\n"); if config.include_standard_packages { - def_file += &self.builtin_functions_operators_impl(&config); + def_file += &Self::builtin_functions_operators_impl(config); def_file += "\n"; - def_file += &self.builtin_functions_impl(&config); + def_file += &Self::builtin_functions_impl(config); def_file += "\n"; } - def_file += &self.static_module_impl(&config); + def_file += &self.static_module_impl(config); def_file += "\n"; #[cfg(not(feature = "no_module"))] { use std::fmt::Write; - for (module_name, module_def) in self.modules_impl(&config) { + for (module_name, module_def) in self.modules_impl(config) { write!( &mut def_file, "\nmodule {module_name} {{\n{module_def}\n}}\n" @@ -199,7 +197,7 @@ impl Definitions<'_> { def_file += "\n"; } - def_file += &self.scope_items_impl(&config); + def_file += &self.scope_items_impl(config); def_file += "\n"; @@ -220,11 +218,11 @@ impl Definitions<'_> { vec![ ( "__builtin__.d.rhai".to_string(), - self.builtin_functions_impl(&config), + Self::builtin_functions_impl(config), ), ( "__builtin-operators__.d.rhai".to_string(), - self.builtin_functions_operators_impl(&config), + Self::builtin_functions_operators_impl(config), ), ] } else { @@ -233,18 +231,18 @@ impl Definitions<'_> { .into_iter() .chain(std::iter::once(( "__static__.d.rhai".to_string(), - self.static_module_impl(&config), + self.static_module_impl(config), ))) .chain(self.scope.iter().map(move |_| { ( "__scope__.d.rhai".to_string(), - self.scope_items_impl(&config), + self.scope_items_impl(config), ) })) .chain( #[cfg(not(feature = "no_module"))] { - self.modules_impl(&config) + self.modules_impl(config) .map(|(name, def)| (format!("{name}.d.rhai"), def)) }, #[cfg(feature = "no_module")] @@ -258,12 +256,12 @@ impl Definitions<'_> { #[inline(always)] #[must_use] pub fn builtin_functions(&self) -> String { - self.builtin_functions_impl(&self.config) + Self::builtin_functions_impl(self.config) } /// Return definitions for all builtin functions. #[must_use] - fn builtin_functions_impl(&self, config: &DefinitionsConfig) -> String { + fn builtin_functions_impl(config: DefinitionsConfig) -> String { let def = include_str!("builtin-functions.d.rhai"); if config.write_headers { @@ -277,12 +275,12 @@ impl Definitions<'_> { #[inline(always)] #[must_use] pub fn builtin_functions_operators(&self) -> String { - self.builtin_functions_operators_impl(&self.config) + Self::builtin_functions_operators_impl(self.config) } /// Return definitions for all builtin operators. #[must_use] - fn builtin_functions_operators_impl(&self, config: &DefinitionsConfig) -> String { + fn builtin_functions_operators_impl(config: DefinitionsConfig) -> String { let def = include_str!("builtin-operators.d.rhai"); if config.write_headers { @@ -296,22 +294,28 @@ impl Definitions<'_> { #[inline(always)] #[must_use] pub fn static_module(&self) -> String { - self.static_module_impl(&self.config) + self.static_module_impl(self.config) } /// Return definitions for all globally available functions and constants. #[must_use] - fn static_module_impl(&self, config: &DefinitionsConfig) -> String { + fn static_module_impl(&self, config: DefinitionsConfig) -> String { let mut s = if config.write_headers { String::from("module static;\n\n") } else { String::new() }; + let exclude_flags = if self.config.include_standard_packages { + ModuleFlags::empty() + } else { + ModuleFlags::STANDARD_LIB + }; + self.engine .global_modules .iter() - .filter(|m| self.config.include_standard_packages || !m.standard) + .filter(|m| !m.flags.contains(exclude_flags)) .enumerate() .for_each(|(i, m)| { if i > 0 { @@ -327,12 +331,12 @@ impl Definitions<'_> { #[inline(always)] #[must_use] pub fn scope_items(&self) -> String { - self.scope_items_impl(&self.config) + self.scope_items_impl(self.config) } /// Return definitions for all items inside the [`Scope`], if any. #[must_use] - fn scope_items_impl(&self, config: &DefinitionsConfig) -> String { + fn scope_items_impl(&self, config: DefinitionsConfig) -> String { let mut s = if config.write_headers { String::from("module static;\n\n") } else { @@ -352,19 +356,20 @@ impl Definitions<'_> { #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn modules(&self) -> impl Iterator + '_ { - self.modules_impl(&self.config) + self.modules_impl(self.config) } /// Return a (module name, definitions) pair for each registered static [module][Module]. #[cfg(not(feature = "no_module"))] fn modules_impl( &self, - config: &DefinitionsConfig, + config: DefinitionsConfig, ) -> impl Iterator + '_ { let mut m = self .engine .global_sub_modules .iter() + .flat_map(|m| m.iter()) .map(move |(name, module)| { ( name.to_string(), @@ -425,10 +430,11 @@ impl Module { } let mut func_infos = self.iter_fn().collect::>(); - func_infos.sort_by(|a, b| match a.name.cmp(&b.name) { - Ordering::Equal => match a.num_params.cmp(&b.num_params) { - Ordering::Equal => (a.params_info.join("") + a.return_type.as_str()) - .cmp(&(b.params_info.join("") + b.return_type.as_str())), + func_infos.sort_by(|a, b| match a.metadata.name.cmp(&b.metadata.name) { + Ordering::Equal => match a.metadata.num_params.cmp(&b.metadata.num_params) { + Ordering::Equal => (a.metadata.params_info.join("") + + a.metadata.return_type.as_str()) + .cmp(&(b.metadata.params_info.join("") + b.metadata.return_type.as_str())), o => o, }, o => o, @@ -440,13 +446,17 @@ 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())); + if f.metadata.access != FnAccess::Private { + let operator = + !f.metadata.name.contains('$') && !is_valid_function_name(&f.metadata.name); - #[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.metadata.name.as_str())); f.write_definition(writer, def, operator)?; } @@ -464,7 +474,7 @@ impl FuncInfo { def: &Definitions, operator: bool, ) -> fmt::Result { - for comment in &*self.comments { + for comment in &*self.metadata.comments { writeln!(writer, "{comment}")?; } @@ -474,29 +484,33 @@ impl FuncInfo { writer.write_str("fn ")?; } - if let Some(name) = self.name.strip_prefix("get$") { + if let Some(name) = self.metadata.name.strip_prefix("get$") { write!(writer, "get {name}(")?; - } else if let Some(name) = self.name.strip_prefix("set$") { + } else if let Some(name) = self.metadata.name.strip_prefix("set$") { write!(writer, "set {name}(")?; } else { - write!(writer, "{}(", self.name)?; + write!(writer, "{}(", self.metadata.name)?; } let mut first = true; - for i in 0..self.num_params { + for i in 0..self.metadata.num_params { if !first { writer.write_str(", ")?; } first = false; - let (param_name, param_type) = self.params_info.get(i).map_or(("_", "?".into()), |s| { - let mut s = s.splitn(2, ':'); - ( - s.next().unwrap_or("_").split(' ').last().unwrap(), - s.next() - .map_or(Cow::Borrowed("?"), |ty| def_type_name(ty, def.engine)), - ) - }); + let (param_name, param_type) = + self.metadata + .params_info + .get(i) + .map_or(("_", "?".into()), |s| { + let mut s = s.splitn(2, ':'); + ( + s.next().unwrap_or("_").split(' ').last().unwrap(), + s.next() + .map_or(Cow::Borrowed("?"), |ty| def_type_name(ty, def.engine)), + ) + }); if operator { write!(writer, "{param_type}")?; @@ -508,7 +522,7 @@ impl FuncInfo { write!( writer, ") -> {};", - def_type_name(&self.return_type, def.engine) + def_type_name(&self.metadata.return_type, def.engine) )?; Ok(()) diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index b1bb63a0..1549335b 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -3,8 +3,8 @@ use crate::func::RegisterNativeFunction; use crate::types::dynamic::Variant; use crate::{ - Dynamic, Engine, EvalAltResult, FnPtr, Identifier, ImmutableString, NativeCallContext, - Position, RhaiResult, RhaiResultOf, Scope, SharedModule, AST, + Dynamic, Engine, EvalAltResult, FnPtr, Identifier, ImmutableString, Module, NativeCallContext, + Position, RhaiResult, RhaiResultOf, Scope, SharedModule, TypeBuilder, AST, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -534,7 +534,7 @@ impl Position { } #[allow(deprecated)] -impl<'a, T: Variant + Clone> crate::TypeBuilder<'a, T> { +impl<'a, T: Variant + Clone> TypeBuilder<'a, T> { /// Register a custom fallible function. /// /// # Deprecated @@ -642,3 +642,19 @@ impl<'a, T: Variant + Clone> crate::TypeBuilder<'a, T> { self.with_indexer_set(set_fn) } } + +impl Module { + /// Create a new [`Module`] with a pre-sized capacity for functions. + /// + /// # Deprecated + /// + /// This method is deprecated. Use `new` instead. + /// + /// This method will be removed in the next major version. + #[inline(always)] + #[must_use] + #[deprecated(since = "1.12.0", note = "use `new` instead")] + pub fn with_capacity(_capacity: usize) -> Self { + Self::new() + } +} diff --git a/src/api/eval.rs b/src/api/eval.rs index a919089d..5def7ccf 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -1,6 +1,7 @@ //! Module that defines the public evaluation API of [`Engine`]. use crate::eval::{Caches, GlobalRuntimeState}; +use crate::func::native::locked_write; use crate::parser::ParseState; use crate::types::dynamic::Variant; use crate::{ @@ -69,7 +70,7 @@ impl Engine { ) -> RhaiResultOf { let ast = self.compile_with_scope_and_optimization_level( scope, - &[script], + [script], self.optimization_level, )?; self.eval_ast_with_scope(scope, &ast) @@ -117,20 +118,25 @@ impl Engine { script: &str, ) -> RhaiResultOf { let scripts = [script]; - let (stream, tokenizer_control) = - self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); - let mut state = ParseState::new(self, scope, Default::default(), tokenizer_control); + let ast = { + let interned_strings = &mut *locked_write(&self.interned_strings); - // No need to optimize a lone expression - let ast = self.parse_global_expr( - &mut stream.peekable(), - &mut state, - |_| {}, - #[cfg(not(feature = "no_optimize"))] - OptimizationLevel::None, - #[cfg(feature = "no_optimize")] - OptimizationLevel::default(), - )?; + let (stream, tokenizer_control) = + self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); + + let mut state = ParseState::new(scope, interned_strings, tokenizer_control); + + // No need to optimize a lone expression + self.parse_global_expr( + &mut stream.peekable(), + &mut state, + |_| {}, + #[cfg(not(feature = "no_optimize"))] + OptimizationLevel::None, + #[cfg(feature = "no_optimize")] + OptimizationLevel::default(), + )? + }; self.eval_ast_with_scope(scope, &ast) } @@ -238,7 +244,7 @@ impl Engine { #[cfg(feature = "debugging")] if self.debugger.is_some() { - global.debugger.status = crate::eval::DebuggerStatus::Terminate; + global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; let mut this = Dynamic::NULL; let node = &crate::ast::Stmt::Noop(Position::NONE); 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/json.rs b/src/api/json.rs index 4365a4a8..3df63276 100644 --- a/src/api/json.rs +++ b/src/api/json.rs @@ -1,7 +1,8 @@ //! Module that defines JSON manipulation functions for [`Engine`]. #![cfg(not(feature = "no_object"))] -use crate::parser::ParseState; +use crate::func::native::locked_write; +use crate::parser::{ParseSettingFlags, ParseState}; use crate::tokenizer::Token; use crate::{Engine, LexError, Map, OptimizationLevel, RhaiResultOf, Scope}; #[cfg(feature = "no_std")] @@ -116,18 +117,21 @@ impl Engine { }, ); - let scope = Scope::new(); - let mut state = ParseState::new(self, &scope, Default::default(), tokenizer_control); + let ast = { + let scope = Scope::new(); + let interned_strings = &mut *locked_write(&self.interned_strings); + let mut state = ParseState::new(&scope, interned_strings, tokenizer_control); - let ast = self.parse_global_expr( - &mut stream.peekable(), - &mut state, - |s| s.allow_unquoted_map_properties = false, - #[cfg(not(feature = "no_optimize"))] - OptimizationLevel::None, - #[cfg(feature = "no_optimize")] - OptimizationLevel::default(), - )?; + self.parse_global_expr( + &mut stream.peekable(), + &mut state, + |s| s.flags |= ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES, + #[cfg(not(feature = "no_optimize"))] + OptimizationLevel::None, + #[cfg(feature = "no_optimize")] + OptimizationLevel::default(), + )? + }; self.eval_ast(&ast) } @@ -165,7 +169,7 @@ pub fn format_map_as_json(map: &Map) -> String { result.push(':'); if let Some(val) = value.read_lock::() { - result.push_str(&format_map_as_json(&*val)); + result.push_str(&format_map_as_json(&val)); } else if value.is_unit() { result.push_str("null"); } else { diff --git a/src/api/mod.rs b/src/api/mod.rs index 71973732..2080325e 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -107,7 +107,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(Default::default) + .insert(symbol.into()); self } @@ -163,24 +165,30 @@ impl Engine { // Active standard keywords cannot be made custom // Disabled keywords are OK Some(token) if token.is_standard_keyword() => { - if self.disabled_symbols.is_empty() - || !self.disabled_symbols.contains(&*token.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.is_empty() - || !self.disabled_symbols.contains(&*token.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.is_empty() - || !self.disabled_symbols.contains(&*token.syntax()) => + if !self + .disabled_symbols + .as_ref() + .map_or(false, |m| m.contains(token.literal_syntax())) => { return Err(format!("'{keyword}' is a reserved symbol")) } @@ -190,6 +198,7 @@ impl Engine { // Add to custom keywords self.custom_keywords + .get_or_insert_with(Default::default) .insert(keyword.into(), Some(precedence)); Ok(self) @@ -198,7 +207,7 @@ impl Engine { /// Get the default value of the custom state for each evaluation run. #[inline(always)] #[must_use] - pub fn default_tag(&self) -> &Dynamic { + pub const fn default_tag(&self) -> &Dynamic { &self.def_tag } /// Get a mutable reference to the default value of the custom state for each evaluation run. diff --git a/src/api/register.rs b/src/api/register.rs index a335faa1..1591c564 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -683,13 +683,14 @@ 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, ) { - let separator = crate::tokenizer::Token::DoubleColon.syntax(); - let separator = separator.as_ref(); + let separator = crate::tokenizer::Token::DoubleColon.literal_syntax(); if name.contains(separator) { let mut iter = name.splitn(2, separator); @@ -718,7 +719,11 @@ 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(Default::default), + name.as_ref(), + module, + ); self } /// _(metadata)_ Generate a list of all registered functions. @@ -738,15 +743,21 @@ 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}"))); } + let exclude_flags = if include_packages { + crate::module::ModuleFlags::INTERNAL + } else { + crate::module::ModuleFlags::INTERNAL | crate::module::ModuleFlags::STANDARD_LIB + }; + signatures.extend( self.global_modules .iter() .skip(1) - .filter(|m| !m.internal && (include_packages || !m.standard)) + .filter(|m| !m.flags.contains(exclude_flags)) .flat_map(|m| m.gen_fn_signatures()), ); diff --git a/src/api/run.rs b/src/api/run.rs index a83a3a73..ab854321 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -1,6 +1,7 @@ //! Module that defines the public evaluation API of [`Engine`]. use crate::eval::{Caches, GlobalRuntimeState}; +use crate::func::native::locked_write; use crate::parser::ParseState; use crate::{Engine, RhaiResultOf, Scope, AST}; #[cfg(feature = "no_std")] @@ -56,10 +57,16 @@ impl Engine { #[inline] pub fn run_with_scope(&self, scope: &mut Scope, script: &str) -> RhaiResultOf<()> { let scripts = [script]; - let (stream, tokenizer_control) = - self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); - let mut state = ParseState::new(self, scope, Default::default(), tokenizer_control); - let ast = self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?; + let ast = { + let interned_strings = &mut *locked_write(&self.interned_strings); + + let (stream, tokenizer_control) = + self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); + + let mut state = ParseState::new(scope, interned_strings, tokenizer_control); + + self.parse(&mut stream.peekable(), &mut state, self.optimization_level)? + }; self.run_ast_with_scope(scope, &ast) } /// Evaluate an [`AST`]. @@ -130,7 +137,7 @@ impl Engine { #[cfg(feature = "debugging")] if self.debugger.is_some() { - global.debugger.status = crate::eval::DebuggerStatus::Terminate; + global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; let mut this = crate::Dynamic::NULL; let node = &crate::ast::Stmt::Noop(crate::Position::NONE); self.run_debugger(global, caches, scope, &mut this, node)?; diff --git a/src/api/type_names.rs b/src/api/type_names.rs index dda879fc..258991ab 100644 --- a/src/api/type_names.rs +++ b/src/api/type_names.rs @@ -139,8 +139,8 @@ pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow { } else { format!("&mut {r}").into() }; - } else if typ.contains(" ") { - let typ = typ.replace(" ", ""); + } else if typ.contains(' ') { + let typ = typ.replace(' ', ""); let r = format_type(&typ, is_return_type); return r.into_owned().into(); } @@ -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/ast/ast.rs b/src/ast/ast.rs index 71b07669..3609064d 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -772,7 +772,7 @@ impl AST { /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] #[inline] - pub fn iter_functions<'a>(&'a self) -> impl Iterator + 'a { + pub fn iter_functions(&self) -> impl Iterator { self.lib .iter_script_fn() .map(|(.., fn_def)| fn_def.as_ref().into()) @@ -942,7 +942,7 @@ impl Borrow for AST { #[inline(always)] #[must_use] fn borrow(&self) -> &crate::Module { - &self.shared_lib() + self.shared_lib() } } @@ -1012,7 +1012,20 @@ impl PartialEq for ASTNode<'_> { impl Eq for ASTNode<'_> {} impl ASTNode<'_> { + /// Is this [`ASTNode`] a [`Stmt`]? + #[inline(always)] + #[must_use] + pub const fn is_stmt(&self) -> bool { + matches!(self, Self::Stmt(..)) + } + /// Is this [`ASTNode`] an [`Expr`]? + #[inline(always)] + #[must_use] + pub const fn is_expr(&self) -> bool { + matches!(self, Self::Expr(..)) + } /// Get the [`Position`] of this [`ASTNode`]. + #[inline] #[must_use] pub fn position(&self) -> Position { match self { diff --git a/src/ast/expr.rs b/src/ast/expr.rs index af718685..3636cb9e 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -173,7 +173,7 @@ impl FnCallHashes { /// The hash returned is never zero. #[inline(always)] #[must_use] - pub fn native(&self) -> u64 { + pub const fn native(&self) -> u64 { self.native.get() } /// Get the script hash. @@ -361,7 +361,7 @@ impl fmt::Debug for Expr { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut display_pos = format!(" @ {:?}", self.start_position()); + let mut display_pos = self.start_position(); match self { Self::DynamicConstant(value, ..) => write!(f, "{value:?}"), @@ -395,7 +395,7 @@ impl fmt::Debug for Expr { write!(f, "{}{}", x.1, Token::DoubleColon.literal_syntax())?; let pos = x.1.position(); if !pos.is_none() { - display_pos = format!(" @ {pos:?}"); + display_pos = pos; } } f.write_str(&x.3)?; @@ -413,7 +413,7 @@ impl fmt::Debug for Expr { Self::Stmt(x) => { let pos = x.span(); if !pos.is_none() { - display_pos = format!(" @ {pos:?}"); + display_pos = pos.start(); } f.write_str("ExprStmtBlock")?; f.debug_list().entries(x.iter()).finish() @@ -421,7 +421,7 @@ impl fmt::Debug for Expr { Self::FnCall(x, ..) => fmt::Debug::fmt(x, f), Self::Index(x, options, pos) => { if !pos.is_none() { - display_pos = format!(" @ {pos:?}"); + display_pos = *pos; } let mut f = f.debug_struct("Index"); @@ -434,7 +434,7 @@ impl fmt::Debug for Expr { } Self::Dot(x, options, pos) => { if !pos.is_none() { - display_pos = format!(" @ {pos:?}"); + display_pos = *pos; } let mut f = f.debug_struct("Dot"); @@ -454,7 +454,7 @@ impl fmt::Debug for Expr { }; if !pos.is_none() { - display_pos = format!(" @ {pos:?}"); + display_pos = *pos; } f.debug_struct(op_name) @@ -466,7 +466,7 @@ impl fmt::Debug for Expr { Self::Custom(x, ..) => f.debug_tuple("Custom").field(x).finish(), }?; - f.write_str(&display_pos) + write!(f, " @ {display_pos:?}") } } @@ -837,18 +837,12 @@ impl Expr { #[cfg(not(feature = "no_custom_syntax"))] Self::Custom(..) => false, - Self::Variable(..) => match token { - Token::LeftParen => true, - Token::Unit => true, - Token::Bang => true, - Token::DoubleColon => true, - _ => false, - }, + Self::Variable(..) => matches!( + token, + Token::LeftParen | Token::Unit | Token::Bang | Token::DoubleColon + ), - Self::Property(..) => match token { - Token::LeftParen => true, - _ => false, - }, + Self::Property(..) => matches!(token, Token::LeftParen), } } /// Recursively walk this expression. diff --git a/src/ast/flags.rs b/src/ast/flags.rs index 452a57e6..7085ee54 100644 --- a/src/ast/flags.rs +++ b/src/ast/flags.rs @@ -6,7 +6,7 @@ use std::prelude::v1::*; /// A type representing the access mode of a function. #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] -#[cfg_attr(feature = "metadata", derive(serde::Serialize))] +#[cfg_attr(feature = "metadata", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "metadata", serde(rename_all = "camelCase"))] #[non_exhaustive] pub enum FnAccess { diff --git a/src/ast/script_fn.rs b/src/ast/script_fn.rs index 743bf2f5..9e81b08f 100644 --- a/src/ast/script_fn.rs +++ b/src/ast/script_fn.rs @@ -2,7 +2,7 @@ #![cfg(not(feature = "no_function"))] use super::{FnAccess, StmtBlock}; -use crate::{ImmutableString, StaticVec}; +use crate::{FnArgsVec, ImmutableString}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{fmt, hash::Hash}; @@ -42,7 +42,7 @@ pub struct ScriptFnDef { /// Function access mode. pub access: FnAccess, /// Names of function parameters. - pub params: StaticVec, + pub params: FnArgsVec, /// _(metadata)_ Function doc-comments (if any). /// Exported under the `metadata` feature only. /// @@ -72,7 +72,7 @@ impl fmt::Display for ScriptFnDef { self.params .iter() .map(|s| s.as_str()) - .collect::>() + .collect::>() .join(", ") ) } @@ -121,7 +121,7 @@ impl fmt::Display for ScriptFnMetadata<'_> { self.params .iter() .copied() - .collect::>() + .collect::>() .join(", ") ) } diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 2fd3df49..72ae1e8c 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -926,10 +926,7 @@ impl Stmt { #[inline] #[must_use] pub const fn is_control_flow_break(&self) -> bool { - match self { - Self::Return(..) | Self::BreakLoop(..) => true, - _ => false, - } + matches!(self, Self::Return(..) | Self::BreakLoop(..)) } /// Recursively walk this statement. /// Return `false` from the callback to terminate the walk. diff --git a/src/bin/rhai-dbg.rs b/src/bin/rhai-dbg.rs index 04284050..b47f7a6d 100644 --- a/src/bin/rhai-dbg.rs +++ b/src/bin/rhai-dbg.rs @@ -61,7 +61,7 @@ fn print_current_source( ) { let current_source = &mut *context .global_runtime_state_mut() - .debugger + .debugger_mut() .state_mut() .write_lock::() .unwrap(); @@ -241,7 +241,7 @@ fn debug_callback( DebuggerEvent::End => println!("\x1b[31m! Script end\x1b[39m"), DebuggerEvent::Step => (), DebuggerEvent::BreakPoint(n) => { - match context.global_runtime_state().debugger.break_points()[n] { + match context.global_runtime_state().debugger().break_points()[n] { #[cfg(not(feature = "no_position"))] BreakPoint::AtPosition { .. } => (), BreakPoint::AtFunctionName { ref name, .. } @@ -260,7 +260,7 @@ fn debug_callback( "! Return from function call '{}' => {:?}", context .global_runtime_state() - .debugger + .debugger() .call_stack() .last() .unwrap() @@ -273,7 +273,7 @@ fn debug_callback( "! Return from function call '{}' with error: {}", context .global_runtime_state() - .debugger + .debugger() .call_stack() .last() .unwrap() @@ -373,7 +373,7 @@ fn debug_callback( ["backtrace" | "bt"] => { for frame in context .global_runtime_state() - .debugger + .debugger() .call_stack() .iter() .rev() @@ -384,7 +384,7 @@ fn debug_callback( ["info" | "i", "break" | "b"] => Iterator::for_each( context .global_runtime_state() - .debugger + .debugger() .break_points() .iter() .enumerate(), @@ -402,13 +402,13 @@ fn debug_callback( if let Ok(n) = n.parse::() { let range = 1..=context .global_runtime_state_mut() - .debugger + .debugger() .break_points() .len(); if range.contains(&n) { context .global_runtime_state_mut() - .debugger + .debugger_mut() .break_points_mut() .get_mut(n - 1) .unwrap() @@ -425,13 +425,13 @@ fn debug_callback( if let Ok(n) = n.parse::() { let range = 1..=context .global_runtime_state_mut() - .debugger + .debugger() .break_points() .len(); if range.contains(&n) { context .global_runtime_state_mut() - .debugger + .debugger_mut() .break_points_mut() .get_mut(n - 1) .unwrap() @@ -448,13 +448,13 @@ fn debug_callback( if let Ok(n) = n.parse::() { let range = 1..=context .global_runtime_state_mut() - .debugger + .debugger() .break_points() .len(); if range.contains(&n) { context .global_runtime_state_mut() - .debugger + .debugger_mut() .break_points_mut() .remove(n - 1); println!("Break-point #{n} deleted.") @@ -468,7 +468,7 @@ fn debug_callback( ["delete" | "d"] => { context .global_runtime_state_mut() - .debugger + .debugger_mut() .break_points_mut() .clear(); println!("All break-points deleted."); @@ -483,7 +483,7 @@ fn debug_callback( println!("Break-point added for {bp}"); context .global_runtime_state_mut() - .debugger + .debugger_mut() .break_points_mut() .push(bp); } else { @@ -500,7 +500,7 @@ fn debug_callback( println!("Break-point added for {bp}"); context .global_runtime_state_mut() - .debugger + .debugger_mut() .break_points_mut() .push(bp); } @@ -523,7 +523,7 @@ fn debug_callback( println!("Break-point added {bp}"); context .global_runtime_state_mut() - .debugger + .debugger_mut() .break_points_mut() .push(bp); } else { @@ -539,7 +539,7 @@ fn debug_callback( println!("Break-point added for {bp}"); context .global_runtime_state_mut() - .debugger + .debugger_mut() .break_points_mut() .push(bp); } @@ -553,7 +553,7 @@ fn debug_callback( println!("Break-point added {bp}"); context .global_runtime_state_mut() - .debugger + .debugger_mut() .break_points_mut() .push(bp); } diff --git a/src/config/hashing.rs b/src/config/hashing.rs index 314814a6..fcd655f1 100644 --- a/src/config/hashing.rs +++ b/src/config/hashing.rs @@ -71,7 +71,7 @@ impl WhenTheHokmaSuppression { #[inline] pub fn the_price_of_silence(self) { self.hokma.lock.store(self.state, Ordering::SeqCst); - mem::forget(self) + mem::forget(self); } } @@ -80,58 +80,65 @@ impl Drop for WhenTheHokmaSuppression { fn drop(&mut self) { self.hokma .lock - .store(self.state.wrapping_add(2), Ordering::SeqCst) + .store(self.state.wrapping_add(2), Ordering::SeqCst); } } #[inline(always)] -#[must_use] fn hokmalock(address: usize) -> &'static HokmaLock { const LEN: usize = 787; + #[allow(clippy::declare_interior_mutable_const)] const LCK: HokmaLock = HokmaLock::new(); static RECORDS: [HokmaLock; LEN] = [LCK; LEN]; &RECORDS[address % LEN] } -// Safety: lol, there is a reason its called `SusLock` +/// # Safety +/// +/// LOL, there is a reason its called `SusLock` #[must_use] -struct SusLock -where - T: 'static, -{ +pub struct SusLock { initialized: AtomicBool, data: UnsafeCell>, _marker: PhantomData, } -impl SusLock -where - T: 'static, -{ +impl Default for SusLock { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + +impl SusLock { + /// Create a new [`SusLock`]. #[inline] - pub const fn new() -> SusLock { - SusLock { + pub const fn new() -> Self { + Self { initialized: AtomicBool::new(false), data: UnsafeCell::new(MaybeUninit::uninit()), _marker: PhantomData, } } + /// Is the [`SusLock`] initialized? #[inline(always)] #[must_use] pub fn is_initialized(&self) -> bool { self.initialized.load(Ordering::SeqCst) } + /// Return the value of the [`SusLock`] (if initialized). + #[inline] #[must_use] pub fn get(&self) -> Option<&'static T> { if self.initialized.load(Ordering::SeqCst) { - let hokma = hokmalock(unsafe { mem::transmute(self.data.get()) }); + let hokma = hokmalock(self.data.get() as usize); // we forgo the optimistic read, because we don't really care let guard = hokma.write(); let cast: *const T = self.data.get().cast(); - let val = unsafe { mem::transmute::<*const T, &'static T>(cast) }; + let val = unsafe { &*cast.cast::() }; guard.the_price_of_silence(); Some(val) } else { @@ -139,11 +146,13 @@ where } } + /// Return the value of the [`SusLock`], initializing it if not yet done. + #[inline] #[must_use] pub fn get_or_init(&self, f: impl FnOnce() -> T) -> &'static T { if !self.initialized.load(Ordering::SeqCst) { self.initialized.store(true, Ordering::SeqCst); - let hokma = hokmalock(unsafe { mem::transmute(self.data.get()) }); + let hokma = hokmalock(self.data.get() as usize); hokma.write(); unsafe { self.data.get().write(MaybeUninit::new(f())); @@ -153,7 +162,13 @@ where self.get().unwrap() } - pub fn set(&self, value: T) -> Result<(), T> { + /// Initialize the value of the [`SusLock`]. + /// + /// # Error + /// + /// If the [`SusLock`] has already been initialized, the current value is returned as error. + #[inline] + pub fn init(&self, value: T) -> Result<(), T> { if self.initialized.load(Ordering::SeqCst) { Err(value) } else { @@ -163,18 +178,15 @@ where } } -unsafe impl Sync for SusLock where T: 'static {} -unsafe impl Send for SusLock where T: 'static {} -impl RefUnwindSafe for SusLock where T: 'static {} +unsafe impl Sync for SusLock {} +unsafe impl Send for SusLock {} +impl RefUnwindSafe for SusLock {} -impl Drop for SusLock -where - T: 'static, -{ +impl Drop for SusLock { #[inline] fn drop(&mut self) { if self.initialized.load(Ordering::SeqCst) { - unsafe { (&mut *self.data.get()).assume_init_drop() }; + unsafe { (*self.data.get()).assume_init_drop() }; } } } @@ -207,7 +219,7 @@ static AHASH_SEED: SusLock> = SusLock::new(); /// ``` #[inline(always)] pub fn set_ahash_seed(new_seed: Option<[u64; 4]>) -> Result<(), Option<[u64; 4]>> { - AHASH_SEED.set(new_seed) + AHASH_SEED.init(new_seed) } /// Get the current hashing Seed. diff --git a/src/config/hashing_env.rs b/src/config/hashing_env.rs index 59930ad8..9890487c 100644 --- a/src/config/hashing_env.rs +++ b/src/config/hashing_env.rs @@ -1,3 +1,3 @@ //! This file is automatically recreated during build time by `build.rs` from `build.template`. -pub(crate) const AHASH_SEED: Option<[u64; 4]> = None; +pub const AHASH_SEED: Option<[u64; 4]> = None; diff --git a/src/engine.rs b/src/engine.rs index 8d5df823..82993f46 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -5,6 +5,7 @@ use crate::func::native::{ locked_write, OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback, }; +use crate::module::ModuleFlags; use crate::packages::{Package, StandardPackage}; use crate::tokenizer::Token; use crate::types::StringsInterner; @@ -95,24 +96,27 @@ 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"))] pub(crate) module_resolver: Box, /// An empty [`ImmutableString`] for cloning purposes. - pub(crate) interned_strings: Locked>, + 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. @@ -143,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 { @@ -167,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::(), ); @@ -263,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, @@ -302,8 +309,8 @@ impl Engine { }; // Add the global namespace module - let mut global_namespace = Module::with_capacity(0); - global_namespace.internal = true; + let mut global_namespace = Module::new(); + global_namespace.flags |= ModuleFlags::INTERNAL; engine.global_modules.push(global_namespace.into()); engine diff --git a/src/eval/cache.rs b/src/eval/cache.rs index 96160d7e..affe2b69 100644 --- a/src/eval/cache.rs +++ b/src/eval/cache.rs @@ -72,7 +72,7 @@ impl Caches { /// Push an empty function resolution cache onto the stack and make it current. #[inline(always)] pub fn push_fn_resolution_cache(&mut self) { - self.0.push(Default::default()); + self.0.push(FnResolutionCache::default()); } /// Rewind the function resolution caches stack to a particular size. #[inline(always)] diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 8b80c50f..2b516a36 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -3,13 +3,36 @@ use super::{Caches, GlobalRuntimeState, Target}; use crate::ast::{ASTFlags, Expr, OpAssignment}; +use crate::config::hashing::SusLock; +use crate::engine::{FN_IDX_GET, FN_IDX_SET}; use crate::types::dynamic::Union; use crate::types::RestoreOnDrop; -use crate::{Dynamic, Engine, FnArgsVec, Position, RhaiResult, RhaiResultOf, Scope, ERR}; +use crate::{ + calc_fn_hash, Dynamic, Engine, FnArgsVec, Position, RhaiResult, RhaiResultOf, Scope, ERR, +}; use std::hash::Hash; #[cfg(feature = "no_std")] use std::prelude::v1::*; +/// Function call hashes to index getters and setters. +/// +/// # Safety +/// +/// Uses the extremely unsafe [`SusLock`]. Change to [`OnceCell`] when it is stabilized. +static INDEXER_HASHES: SusLock<(u64, u64)> = SusLock::new(); + +/// Get the pre-calculated index getter/setter hashes. +#[inline(always)] +#[must_use] +fn hash_idx() -> (u64, u64) { + *INDEXER_HASHES.get_or_init(|| { + ( + calc_fn_hash(None, FN_IDX_GET, 2), + calc_fn_hash(None, FN_IDX_SET, 3), + ) + }) +} + /// Method of chaining. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum ChainType { @@ -45,16 +68,21 @@ impl Engine { idx: &mut Dynamic, pos: Position, ) -> RhaiResultOf { - let args = &mut [target, idx]; - let hash = global.hash_idx_get(); - let fn_name = crate::engine::FN_IDX_GET; - let orig_level = global.level; global.level += 1; let global = &mut *RestoreOnDrop::lock(global, move |g| g.level = orig_level); - self.exec_native_fn_call(global, caches, fn_name, None, hash, args, true, pos) - .map(|(r, ..)| r) + self.exec_native_fn_call( + global, + caches, + FN_IDX_GET, + None, + hash_idx().0, + &mut [target, idx], + true, + pos, + ) + .map(|(r, ..)| r) } /// Call a set indexer. @@ -69,15 +97,20 @@ impl Engine { is_ref_mut: bool, pos: Position, ) -> RhaiResultOf<(Dynamic, bool)> { - let hash = global.hash_idx_set(); - let args = &mut [target, idx, new_val]; - let fn_name = crate::engine::FN_IDX_SET; - let orig_level = global.level; global.level += 1; let global = &mut *RestoreOnDrop::lock(global, move |g| g.level = orig_level); - self.exec_native_fn_call(global, caches, fn_name, None, hash, args, is_ref_mut, pos) + self.exec_native_fn_call( + global, + caches, + FN_IDX_SET, + None, + hash_idx().1, + &mut [target, idx, new_val], + is_ref_mut, + pos, + ) } /// Get the value at the indexed position of a base type. @@ -168,6 +201,7 @@ impl Engine { ERR::ErrorBitFieldBounds(crate::INT_BITS, end, idx_pos).into() })?; + #[allow(clippy::cast_possible_truncation)] if end <= start { (0, 0) } else if end == crate::INT_BITS && start == 0 { @@ -193,6 +227,7 @@ impl Engine { ERR::ErrorBitFieldBounds(crate::INT_BITS, end, idx_pos).into() })?; + #[allow(clippy::cast_possible_truncation)] if end < start { (0, 0) } else if end == crate::INT_BITS - 1 && start == 0 { @@ -237,6 +272,7 @@ impl Engine { Ok(Target::Bit { source: target, value: bit_value.into(), + #[allow(clippy::cast_possible_truncation)] bit: bit as u8, }) } @@ -249,12 +285,14 @@ impl Engine { .map_err(|typ| self.make_type_mismatch_err::(typ, idx_pos))?; let (ch, offset) = if index >= 0 { + #[allow(clippy::absurd_extreme_comparisons)] if index >= crate::MAX_USIZE_INT { return Err( ERR::ErrorStringBounds(s.chars().count(), index, idx_pos).into() ); } + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let offset = index as usize; ( s.chars().nth(offset).ok_or_else(|| { @@ -271,6 +309,7 @@ impl Engine { ); } + #[allow(clippy::cast_possible_truncation)] let offset = abs_index as usize; ( // Count from end if negative @@ -317,7 +356,7 @@ impl Engine { scope: &mut Scope, this_ptr: &mut Dynamic, expr: &Expr, - new_val: &mut Option<(Dynamic, &OpAssignment)>, + new_val: Option<(Dynamic, &OpAssignment)>, ) -> RhaiResult { let chain_type = ChainType::from(expr); @@ -507,7 +546,7 @@ impl Engine { target: &mut Target, rhs: &Expr, idx_values: &mut FnArgsVec, - new_val: &mut Option<(Dynamic, &OpAssignment)>, + new_val: Option<(Dynamic, &OpAssignment)>, ) -> RhaiResultOf<(Dynamic, bool)> { let is_ref_mut = target.is_ref(); let op_pos = parent.position(); @@ -576,7 +615,7 @@ impl Engine { #[cfg(feature = "debugging")] self.run_debugger(global, caches, scope, this_ptr, parent)?; - let (new_val, op_info) = new_val.take().expect("`Some`"); + let (new_val, op_info) = new_val.expect("`Some`"); let idx_val = &mut idx_values.pop().unwrap(); let idx = &mut idx_val.clone(); @@ -657,9 +696,10 @@ impl Engine { let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, rhs)?; #[cfg(feature = "debugging")] - let global = &mut *RestoreOnDrop::lock(global, move |g| { - g.debugger.reset_status(reset) - }); + let global = + &mut *RestoreOnDrop::lock_if(reset.is_some(), global, move |g| { + g.debugger_mut().reset_status(reset) + }); let crate::ast::FnCallExpr { name, hashes, args, .. @@ -686,12 +726,12 @@ impl Engine { unreachable!("function call in dot chain should not be namespace-qualified") } // {xxx:map}.id op= ??? - Expr::Property(x, pos) if target.is_map() && new_val.is_some() => { + Expr::Property(x, pos) if new_val.is_some() && target.is_map() => { #[cfg(feature = "debugging")] self.run_debugger(global, caches, scope, this_ptr, rhs)?; let index = &mut x.2.clone().into(); - let (new_val, op_info) = new_val.take().expect("`Some`"); + let (new_val, op_info) = new_val.expect("`Some`"); { let val_target = &mut self.get_indexed_mut( global, caches, target, index, *pos, op_pos, true, false, @@ -720,7 +760,7 @@ impl Engine { self.run_debugger(global, caches, scope, this_ptr, rhs)?; let ((getter, hash_get), (setter, hash_set), name) = &**x; - let (mut new_val, op_info) = new_val.take().expect("`Some`"); + let (mut new_val, op_info) = new_val.expect("`Some`"); if op_info.is_op_assignment() { let args = &mut [target.as_mut()]; @@ -826,9 +866,11 @@ impl Engine { global, caches, scope, this_ptr, _node, )?; #[cfg(feature = "debugging")] - let global = &mut *RestoreOnDrop::lock(global, move |g| { - g.debugger.reset_status(reset) - }); + let global = &mut *RestoreOnDrop::lock_if( + reset.is_some(), + global, + move |g| g.debugger_mut().reset_status(reset), + ); let crate::ast::FnCallExpr { name, hashes, args, .. @@ -950,9 +992,11 @@ impl Engine { global, caches, scope, this_ptr, _node, )?; #[cfg(feature = "debugging")] - let global = &mut *RestoreOnDrop::lock(global, move |g| { - g.debugger.reset_status(reset) - }); + let global = &mut *RestoreOnDrop::lock_if( + reset.is_some(), + global, + move |g| g.debugger_mut().reset_status(reset), + ); let crate::ast::FnCallExpr { name, hashes, args, .. diff --git a/src/eval/data_check.rs b/src/eval/data_check.rs index c629eba8..cf6c8ece 100644 --- a/src/eval/data_check.rs +++ b/src/eval/data_check.rs @@ -8,7 +8,7 @@ use std::borrow::Borrow; #[cfg(feature = "no_std")] use std::prelude::v1::*; -impl Engine { +impl Dynamic { /// Recursively calculate the sizes of a value. /// /// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`. @@ -16,20 +16,20 @@ impl Engine { /// # Panics /// /// Panics if any interior data is shared (should never happen). - pub(crate) fn calc_data_sizes(value: &Dynamic, _top: bool) -> (usize, usize, usize) { - match value.0 { + pub(crate) fn calc_data_sizes(&self, _top: bool) -> (usize, usize, usize) { + match self.0 { #[cfg(not(feature = "no_index"))] Union::Array(ref arr, ..) => { arr.iter() .fold((0, 0, 0), |(ax, mx, sx), value| match value.0 { Union::Array(..) => { - let (a, m, s) = Self::calc_data_sizes(value, false); + let (a, m, s) = value.calc_data_sizes(false); (ax + a + 1, mx + m, sx + s) } Union::Blob(ref a, ..) => (ax + 1 + a.len(), mx, sx), #[cfg(not(feature = "no_object"))] Union::Map(..) => { - let (a, m, s) = Self::calc_data_sizes(value, false); + let (a, m, s) = value.calc_data_sizes(false); (ax + a + 1, mx + m, sx + s) } Union::Str(ref s, ..) => (ax + 1, mx, sx + s.len()), @@ -44,13 +44,13 @@ impl Engine { .fold((0, 0, 0), |(ax, mx, sx), value| match value.0 { #[cfg(not(feature = "no_index"))] Union::Array(..) => { - let (a, m, s) = Self::calc_data_sizes(value, false); + let (a, m, s) = value.calc_data_sizes(false); (ax + a, mx + m + 1, sx + s) } #[cfg(not(feature = "no_index"))] Union::Blob(ref a, ..) => (ax + a.len(), mx, sx), Union::Map(..) => { - let (a, m, s) = Self::calc_data_sizes(value, false); + let (a, m, s) = value.calc_data_sizes(false); (ax + a, mx + m + 1, sx + s) } Union::Str(ref s, ..) => (ax, mx + 1, sx + s.len()), @@ -59,17 +59,17 @@ impl Engine { } Union::Str(ref s, ..) => (0, 0, s.len()), #[cfg(not(feature = "no_closure"))] - Union::Shared(..) if _top => { - Self::calc_data_sizes(&*value.read_lock::().unwrap(), true) - } + Union::Shared(..) if _top => self.read_lock::().unwrap().calc_data_sizes(true), #[cfg(not(feature = "no_closure"))] Union::Shared(..) => { - unreachable!("shared values discovered within data: {}", value) + unreachable!("shared values discovered within data: {}", self) } _ => (0, 0, 0), } } +} +impl Engine { /// Raise an error if any data size exceeds limit. /// /// [`Position`] in [`EvalAltResult`][crate::EvalAltResult] is always [`NONE`][Position::NONE] @@ -125,7 +125,7 @@ impl Engine { return Ok(value); } - let sizes = Self::calc_data_sizes(value.borrow(), true); + let sizes = value.borrow().calc_data_sizes(true); self.raise_err_if_over_data_size_limit(sizes) .map(|_| value) diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index bf74af3d..93663293 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -193,7 +193,7 @@ impl BreakPoint { /// Is this [`BreakPoint`] enabled? #[inline(always)] #[must_use] - pub fn is_enabled(&self) -> bool { + pub const fn is_enabled(&self) -> bool { match self { #[cfg(not(feature = "no_position"))] Self::AtPosition { enabled, .. } => *enabled, @@ -268,7 +268,7 @@ impl Debugger { /// Create a new [`Debugger`]. #[inline(always)] #[must_use] - pub fn new(status: DebuggerStatus, state: Dynamic) -> Self { + pub const fn new(status: DebuggerStatus, state: Dynamic) -> Self { Self { status, break_points: Vec::new(), @@ -297,7 +297,7 @@ impl Debugger { pos: Position, ) { self.call_stack.push(CallStackFrame { - fn_name: fn_name.into(), + fn_name, args, source, pos, @@ -419,7 +419,7 @@ impl Engine { if let Some(cmd) = self.run_debugger_with_reset_raw(global, caches, scope, this_ptr, node)? { - global.debugger.status = cmd; + global.debugger_mut().status = cmd; } } @@ -469,27 +469,28 @@ impl Engine { _ => (), } - let event = match global.debugger.status { - DebuggerStatus::Init => Some(DebuggerEvent::Start), - DebuggerStatus::CONTINUE => None, - DebuggerStatus::NEXT if matches!(node, ASTNode::Stmt(..)) => Some(DebuggerEvent::Step), - DebuggerStatus::NEXT => None, - DebuggerStatus::INTO if matches!(node, ASTNode::Expr(..)) => Some(DebuggerEvent::Step), - DebuggerStatus::INTO => None, - DebuggerStatus::STEP => Some(DebuggerEvent::Step), - DebuggerStatus::FunctionExit(..) => None, - DebuggerStatus::Terminate => Some(DebuggerEvent::End), - }; + if let Some(ref dbg) = global.debugger { + let event = match dbg.status { + DebuggerStatus::Init => Some(DebuggerEvent::Start), + DebuggerStatus::NEXT if node.is_stmt() => Some(DebuggerEvent::Step), + DebuggerStatus::INTO if node.is_expr() => Some(DebuggerEvent::Step), + DebuggerStatus::STEP => Some(DebuggerEvent::Step), + DebuggerStatus::Terminate => Some(DebuggerEvent::End), + _ => None, + }; - let event = match event { - Some(e) => e, - None => match global.debugger.is_break_point(global.source(), node) { - Some(bp) => DebuggerEvent::BreakPoint(bp), - None => return Ok(None), - }, - }; + let event = match event { + Some(e) => e, + None => match dbg.is_break_point(global.source(), node) { + Some(bp) => DebuggerEvent::BreakPoint(bp), + None => return Ok(None), + }, + }; - self.run_debugger_raw(global, caches, scope, this_ptr, node, event) + self.run_debugger_raw(global, caches, scope, this_ptr, node, event) + } else { + Ok(None) + } } /// Run the debugger callback unconditionally. /// @@ -511,24 +512,26 @@ 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 { DebuggerCommand::Continue => { - global.debugger.status = DebuggerStatus::CONTINUE; + global.debugger_mut().status = DebuggerStatus::CONTINUE; Ok(None) } DebuggerCommand::Next => { - global.debugger.status = DebuggerStatus::CONTINUE; + global.debugger_mut().status = DebuggerStatus::CONTINUE; Ok(Some(DebuggerStatus::NEXT)) } DebuggerCommand::StepOver => { - global.debugger.status = DebuggerStatus::CONTINUE; + global.debugger_mut().status = DebuggerStatus::CONTINUE; Ok(Some(DebuggerStatus::STEP)) } DebuggerCommand::StepInto => { - global.debugger.status = DebuggerStatus::STEP; + global.debugger_mut().status = DebuggerStatus::STEP; Ok(None) } DebuggerCommand::FunctionExit => { @@ -542,7 +545,7 @@ impl Engine { } _ => global.level, }; - global.debugger.status = DebuggerStatus::FunctionExit(level); + global.debugger_mut().status = DebuggerStatus::FunctionExit(level); Ok(None) } } diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index a38f800b..df6a87e3 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -61,8 +61,8 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> { /// Get a mutable reference to the current [`Scope`]. #[inline(always)] #[must_use] - pub fn scope_mut(&mut self) -> &mut &'s mut Scope<'ps> { - &mut self.scope + pub fn scope_mut(&mut self) -> &mut Scope<'ps> { + self.scope } /// Get an iterator over the current set of modules imported via `import` statements, /// in reverse order (i.e. modules imported last come first). @@ -105,7 +105,7 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> { #[cfg(not(feature = "no_function"))] #[inline] pub fn iter_namespaces(&self) -> impl Iterator { - self.global.lib.iter().map(|m| m.as_ref()) + self.global.lib.iter().map(AsRef::as_ref) } /// _(internals)_ The current set of namespaces containing definitions of all script-defined functions. /// Exported under the `internals` feature only. diff --git a/src/eval/expr.rs b/src/eval/expr.rs index c3fd045e..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), ) } @@ -156,7 +161,7 @@ impl Engine { .any(|(_, _, f, ..)| f == v.3.as_str()) => { let val: Dynamic = - crate::FnPtr::new_unchecked(v.3.as_str(), Default::default()).into(); + crate::FnPtr::new_unchecked(v.3.as_str(), crate::StaticVec::default()).into(); return Ok(val.into()); } Expr::Variable(v, None, ..) => v.0.map_or(0, NonZeroUsize::get), @@ -186,14 +191,20 @@ impl Engine { match scope.search(var_name) { Some(index) => index, None => { - return match self.global_modules.iter().find_map(|m| m.get_var(var_name)) { - Some(val) => Ok(val.into()), - None => Err(ERR::ErrorVariableNotFound( - var_name.to_string(), - expr.position(), + return self + .global_modules + .iter() + .find_map(|m| m.get_var(var_name)) + .map_or_else( + || { + Err(ERR::ErrorVariableNotFound( + var_name.to_string(), + expr.position(), + ) + .into()) + }, + |val| Ok(val.into()), ) - .into()), - } } } }; @@ -221,9 +232,10 @@ impl Engine { #[cfg(feature = "debugging")] let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, expr)?; #[cfg(feature = "debugging")] - let global = &mut *crate::types::RestoreOnDrop::lock(global, move |g| { - g.debugger.reset_status(reset) - }); + let global = + &mut *crate::types::RestoreOnDrop::lock_if(reset.is_some(), global, move |g| { + g.debugger_mut().reset_status(reset) + }); self.track_operation(global, expr.position())?; @@ -254,9 +266,10 @@ impl Engine { #[cfg(feature = "debugging")] let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, expr)?; #[cfg(feature = "debugging")] - let global = &mut *crate::types::RestoreOnDrop::lock(global, move |g| { - g.debugger.reset_status(reset) - }); + let global = + &mut *crate::types::RestoreOnDrop::lock_if(reset.is_some(), global, move |g| { + g.debugger_mut().reset_status(reset) + }); self.track_operation(global, expr.position())?; @@ -307,7 +320,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] if self.has_data_size_limit() { - let val_sizes = Self::calc_data_sizes(&value, true); + let val_sizes = value.calc_data_sizes(true); total_data_sizes = ( total_data_sizes.0 + val_sizes.0, @@ -339,7 +352,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] if self.has_data_size_limit() { - let delta = Self::calc_data_sizes(&value, true); + let delta = value.calc_data_sizes(true); total_data_sizes = ( total_data_sizes.0 + delta.0, total_data_sizes.1 + delta.1, @@ -393,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) @@ -411,13 +428,11 @@ impl Engine { #[cfg(not(feature = "no_index"))] Expr::Index(..) => { - self.eval_dot_index_chain(global, caches, scope, this_ptr, expr, &mut None) + self.eval_dot_index_chain(global, caches, scope, this_ptr, expr, None) } #[cfg(not(feature = "no_object"))] - Expr::Dot(..) => { - self.eval_dot_index_chain(global, caches, scope, this_ptr, expr, &mut None) - } + Expr::Dot(..) => self.eval_dot_index_chain(global, caches, scope, this_ptr, expr, None), _ => unreachable!("expression cannot be evaluated: {:?}", expr), } diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index 4cd6de33..408f71b0 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -25,10 +25,11 @@ pub type GlobalConstants = pub struct GlobalRuntimeState { /// Names of imported [modules][crate::Module]. #[cfg(not(feature = "no_module"))] - imports: crate::StaticVec, + imports: Option>>, /// Stack of imported [modules][crate::Module]. #[cfg(not(feature = "no_module"))] - modules: crate::StaticVec, + modules: Option>>, + /// The current stack of loaded [modules][crate::Module] containing script-defined functions. #[cfg(not(feature = "no_function"))] pub lib: crate::StaticVec, @@ -57,9 +58,6 @@ pub struct GlobalRuntimeState { /// /// When that happens, this flag is turned on. pub always_search_scope: bool, - /// Function call hashes to index getters and setters. - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - fn_hash_indexing: (u64, u64), /// Embedded [module][crate::Module] resolver. #[cfg(not(feature = "no_module"))] pub embedded_module_resolver: @@ -74,7 +72,7 @@ pub struct GlobalRuntimeState { pub tag: Dynamic, /// Debugging interface. #[cfg(feature = "debugging")] - pub debugger: super::Debugger, + pub(crate) debugger: Option, } impl GlobalRuntimeState { @@ -84,9 +82,9 @@ impl GlobalRuntimeState { pub fn new(engine: &Engine) -> Self { Self { #[cfg(not(feature = "no_module"))] - imports: crate::StaticVec::new_const(), + imports: None, #[cfg(not(feature = "no_module"))] - modules: crate::StaticVec::new_const(), + modules: None, #[cfg(not(feature = "no_function"))] lib: crate::StaticVec::new_const(), source: None, @@ -98,11 +96,6 @@ impl GlobalRuntimeState { always_search_scope: false, #[cfg(not(feature = "no_module"))] embedded_module_resolver: None, - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - fn_hash_indexing: ( - crate::calc_fn_hash(None, crate::engine::FN_IDX_GET, 2), - crate::calc_fn_hash(None, crate::engine::FN_IDX_SET, 3), - ), #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] constants: None, @@ -110,36 +103,28 @@ impl GlobalRuntimeState { tag: engine.default_tag().clone(), #[cfg(feature = "debugging")] - debugger: crate::eval::Debugger::new( - if engine.debugger.is_some() { - crate::eval::DebuggerStatus::Init - } else { - crate::eval::DebuggerStatus::CONTINUE - }, - match engine.debugger { - Some((ref init, ..)) => init(engine), - None => Dynamic::UNIT, - }, - ), + debugger: engine.debugger.as_ref().map(|x| { + crate::eval::Debugger::new(crate::eval::DebuggerStatus::Init, (x.0)(engine)) + }), } } /// Get the length of the stack of globally-imported [modules][crate::Module]. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] - #[inline(always)] + #[inline] #[must_use] pub fn num_imports(&self) -> usize { - self.modules.len() + self.modules.as_ref().map_or(0, |m| m.len()) } /// Get the globally-imported [module][crate::Module] at a particular index. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] - #[inline(always)] + #[inline] #[must_use] pub fn get_shared_import(&self, index: usize) -> Option { - self.modules.get(index).cloned() + self.modules.as_ref().and_then(|m| m.get(index).cloned()) } /// Get a mutable reference to the globally-imported [module][crate::Module] at a /// particular index. @@ -147,13 +132,13 @@ impl GlobalRuntimeState { /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] #[allow(dead_code)] - #[inline(always)] + #[inline] #[must_use] pub(crate) fn get_shared_import_mut( &mut self, index: usize, ) -> Option<&mut crate::SharedModule> { - self.modules.get_mut(index) + self.modules.as_mut().and_then(|m| m.get_mut(index)) } /// Get the index of a globally-imported [module][crate::Module] by name. /// @@ -162,33 +147,45 @@ impl GlobalRuntimeState { #[inline] #[must_use] pub fn find_import(&self, name: &str) -> Option { - self.imports - .iter() - .rev() - .position(|key| key.as_str() == name) - .map(|i| self.imports.len() - 1 - i) + self.imports.as_ref().and_then(|imports| { + imports + .iter() + .rev() + .position(|key| key.as_str() == name) + .map(|i| imports.len() - 1 - i) + }) } /// Push an imported [module][crate::Module] onto the stack. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] - #[inline(always)] + #[inline] pub fn push_import( &mut self, name: impl Into, module: impl Into, ) { - self.imports.push(name.into()); - self.modules.push(module.into()); + self.imports + .get_or_insert_with(|| crate::StaticVec::new_const().into()) + .push(name.into()); + + self.modules + .get_or_insert_with(|| crate::StaticVec::new_const().into()) + .push(module.into()); } /// Truncate the stack of globally-imported [modules][crate::Module] to a particular length. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] - #[inline(always)] + #[inline] pub fn truncate_imports(&mut self, size: usize) { - self.imports.truncate(size); - self.modules.truncate(size); + if size == 0 { + self.imports = None; + self.modules = None; + } else if self.imports.is_some() { + self.imports.as_mut().unwrap().truncate(size); + self.modules.as_mut().unwrap().truncate(size); + } } /// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order. /// @@ -198,8 +195,9 @@ impl GlobalRuntimeState { pub fn iter_imports(&self) -> impl Iterator { self.imports .iter() - .zip(self.modules.iter()) + .flat_map(|x| x.iter()) .rev() + .zip(self.modules.iter().flat_map(|x| x.iter()).rev()) .map(|(name, module)| (name.as_str(), &**module)) } /// Get an iterator to the stack of globally-imported [modules][crate::Module] in reverse order. @@ -210,7 +208,11 @@ impl GlobalRuntimeState { pub(crate) fn iter_imports_raw( &self, ) -> impl Iterator { - self.imports.iter().zip(self.modules.iter()).rev() + self.imports + .iter() + .flat_map(|x| x.iter()) + .rev() + .zip(self.modules.iter().flat_map(|x| x.iter()).rev()) } /// Get an iterator to the stack of globally-imported [modules][crate::Module] in forward order. /// @@ -220,18 +222,21 @@ impl GlobalRuntimeState { pub fn scan_imports_raw( &self, ) -> impl Iterator { - self.imports.iter().zip(self.modules.iter()) + self.imports + .iter() + .flat_map(|x| x.iter()) + .zip(self.modules.iter().flat_map(|x| x.iter())) } /// Can the particular function with [`Dynamic`] parameter(s) exist in the stack of /// globally-imported [modules][crate::Module]? /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] - #[inline(always)] + #[inline] pub(crate) fn may_contain_dynamic_fn(&self, hash_script: u64) -> bool { - self.modules - .iter() - .any(|m| m.may_contain_dynamic_fn(hash_script)) + self.modules.as_ref().map_or(false, |m| { + m.iter().any(|m| m.may_contain_dynamic_fn(hash_script)) + }) } /// Does the specified function hash key exist in the stack of globally-imported /// [modules][crate::Module]? @@ -242,7 +247,9 @@ impl GlobalRuntimeState { #[inline] #[must_use] pub fn contains_qualified_fn(&self, hash: u64) -> bool { - self.modules.iter().any(|m| m.contains_qualified_fn(hash)) + self.modules + .as_ref() + .map_or(false, |m| m.iter().any(|m| m.contains_qualified_fn(hash))) } /// Get the specified function via its hash key from the stack of globally-imported /// [modules][crate::Module]. @@ -255,10 +262,11 @@ impl GlobalRuntimeState { &self, hash: u64, ) -> Option<(&crate::func::CallableFunction, Option<&ImmutableString>)> { - self.modules - .iter() - .rev() - .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) + self.modules.as_ref().and_then(|m| { + m.iter() + .rev() + .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) + }) } /// Does the specified [`TypeId`][std::any::TypeId] iterator exist in the stack of /// globally-imported [modules][crate::Module]? @@ -269,7 +277,9 @@ impl GlobalRuntimeState { #[inline] #[must_use] pub fn contains_iter(&self, id: std::any::TypeId) -> bool { - self.modules.iter().any(|m| m.contains_qualified_iter(id)) + self.modules + .as_ref() + .map_or(false, |m| m.iter().any(|m| m.contains_qualified_iter(id))) } /// Get the specified [`TypeId`][std::any::TypeId] iterator from the stack of globally-imported /// [modules][crate::Module]. @@ -280,9 +290,8 @@ impl GlobalRuntimeState { #[must_use] pub fn get_iter(&self, id: std::any::TypeId) -> Option<&crate::func::IteratorFn> { self.modules - .iter() - .rev() - .find_map(|m| m.get_qualified_iter(id)) + .as_ref() + .and_then(|m| m.iter().rev().find_map(|m| m.get_qualified_iter(id))) } /// Get the current source. #[inline(always)] @@ -297,19 +306,24 @@ impl GlobalRuntimeState { pub(crate) const fn source_raw(&self) -> Option<&ImmutableString> { self.source.as_ref() } - /// Get the pre-calculated index getter hash. - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[inline(always)] - #[must_use] - pub(crate) fn hash_idx_get(&mut self) -> u64 { - self.fn_hash_indexing.0 + + /// Return a reference to the debugging interface. + /// + /// # Panics + /// + /// Panics if the debugging interface is not set. + #[cfg(feature = "debugging")] + pub fn debugger(&self) -> &super::Debugger { + self.debugger.as_ref().unwrap() } - /// Get the pre-calculated index setter hash. - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - #[inline(always)] - #[must_use] - pub(crate) fn hash_idx_set(&mut self) -> u64 { - self.fn_hash_indexing.1 + /// Return a mutable reference to the debugging interface. + /// + /// # Panics + /// + /// Panics if the debugging interface is not set. + #[cfg(feature = "debugging")] + pub fn debugger_mut(&mut self) -> &mut super::Debugger { + self.debugger.as_mut().unwrap() } } @@ -317,9 +331,16 @@ impl GlobalRuntimeState { impl, M: Into> Extend<(K, M)> for GlobalRuntimeState { #[inline] fn extend>(&mut self, iter: T) { + let imports = self + .imports + .get_or_insert_with(|| crate::StaticVec::new_const().into()); + let modules = self + .modules + .get_or_insert_with(|| crate::StaticVec::new_const().into()); + for (k, m) in iter { - self.imports.push(k.into()); - self.modules.push(m.into()); + imports.push(k.into()); + modules.push(m.into()); } } } @@ -344,9 +365,6 @@ impl fmt::Debug for GlobalRuntimeState { .field("scope_level", &self.scope_level) .field("always_search_scope", &self.always_search_scope); - #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - f.field("fn_hash_indexing", &self.fn_hash_indexing); - #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] f.field("constants", &self.constants); diff --git a/src/eval/mod.rs b/src/eval/mod.rs index b1b215dc..94e5e416 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -21,7 +21,9 @@ pub use eval_context::EvalContext; #[cfg(not(feature = "no_function"))] pub use global_state::GlobalConstants; pub use global_state::GlobalRuntimeState; -pub use target::{calc_index, calc_offset_len, Target}; +#[cfg(not(feature = "no_index"))] +pub use target::calc_offset_len; +pub use target::{calc_index, Target}; #[cfg(feature = "unchecked")] mod unchecked { diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 4842ac7f..198570a3 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -194,7 +194,9 @@ impl Engine { #[cfg(feature = "debugging")] let reset = self.run_debugger_with_reset(global, caches, scope, this_ptr, stmt)?; #[cfg(feature = "debugging")] - let global = &mut *RestoreOnDrop::lock(global, move |g| g.debugger.reset_status(reset)); + let global = &mut *RestoreOnDrop::lock_if(reset.is_some(), global, move |g| { + g.debugger_mut().reset_status(reset) + }); // Coded this way for better branch prediction. // Popular branches are lifted out of the `match` statement into their own branches. @@ -258,7 +260,7 @@ impl Engine { rhs_val = self.get_interned_string(value).into(); } - let _new_val = &mut Some((rhs_val, op_info)); + let _new_val = Some((rhs_val, op_info)); // Must be either `var[index] op= val` or `var.prop op= val` match lhs { @@ -500,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)) }); @@ -533,6 +536,7 @@ impl Engine { let index_value = x as INT; #[cfg(not(feature = "unchecked"))] + #[allow(clippy::absurd_extreme_comparisons)] if index_value > crate::MAX_USIZE_INT { return Err(ERR::ErrorArithmetic( format!("for-loop counter overflow: {x}"), @@ -764,6 +768,8 @@ impl Engine { // Import statement #[cfg(not(feature = "no_module"))] Stmt::Import(x, _pos) => { + use crate::ModuleResolver; + let (expr, export) = &**x; // Guard against too many modules @@ -777,8 +783,6 @@ impl Engine { self.make_type_mismatch_err::(typ, expr.position()) })?; - use crate::ModuleResolver; - let path_pos = expr.start_position(); let resolver = global.embedded_module_resolver.clone(); @@ -799,10 +803,10 @@ impl Engine { Err(ERR::ErrorModuleNotFound(path.to_string(), path_pos).into()) })?; - let (export, must_be_indexed) = if !export.is_empty() { - (export.name.clone(), true) - } else { + let (export, must_be_indexed) = if export.is_empty() { (self.const_empty_string(), false) + } else { + (export.name.clone(), true) }; if !must_be_indexed || module.is_indexed() { @@ -824,13 +828,14 @@ impl Engine { Stmt::Export(x, ..) => { let (Ident { name, pos, .. }, Ident { name: alias, .. }) = &**x; // Mark scope variables as public - if let Some(index) = scope.search(name) { - let alias = if alias.is_empty() { name } else { alias }.clone(); - scope.add_alias_by_index(index, alias.into()); - Ok(Dynamic::UNIT) - } else { - Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into()) - } + scope.search(name).map_or_else( + || Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into()), + |index| { + let alias = if alias.is_empty() { name } else { alias }.clone(); + scope.add_alias_by_index(index, alias); + Ok(Dynamic::UNIT) + }, + ) } // Share statement @@ -838,20 +843,21 @@ impl Engine { Stmt::Share(x) => { x.iter() .try_for_each(|(name, index, pos)| { - if let Some(index) = index + index .map(|n| scope.len() - n.get()) .or_else(|| scope.search(name)) - { - let val = scope.get_mut_by_index(index); + .map_or_else( + || Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into()), + |index| { + let val = scope.get_mut_by_index(index); - if !val.is_shared() { - // Replace the variable with a shared value. - *val = std::mem::take(val).into_shared(); - } - Ok(()) - } else { - Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into()) - } + if !val.is_shared() { + // Replace the variable with a shared value. + *val = std::mem::take(val).into_shared(); + } + Ok(()) + }, + ) }) .map(|_| Dynamic::UNIT) } diff --git a/src/eval/target.rs b/src/eval/target.rs index e9d9be74..59a89411 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -13,8 +13,13 @@ use std::{ /// Negative starting positions count from the end. /// /// Values going over bounds are limited to the actual length. +#[cfg(not(feature = "no_index"))] #[inline] -#[allow(dead_code)] +#[allow( + clippy::cast_sign_loss, + clippy::absurd_extreme_comparisons, + clippy::cast_possible_truncation +)] pub fn calc_offset_len(length: usize, start: crate::INT, len: crate::INT) -> (usize, usize) { let start = if start < 0 { let abs_start = start.unsigned_abs(); @@ -47,22 +52,26 @@ pub fn calc_offset_len(length: usize, start: crate::INT, len: crate::INT) -> (us /// Values going over bounds call the provided closure to return a default value or an error. #[inline] #[allow(dead_code)] +#[allow( + clippy::cast_sign_loss, + clippy::cast_possible_truncation, + clippy::absurd_extreme_comparisons +)] pub fn calc_index( length: usize, start: crate::INT, negative_count_from_end: bool, err_func: impl FnOnce() -> Result, ) -> Result { - if start < 0 { - if negative_count_from_end { - let abs_start = start.unsigned_abs() as usize; - - // Count from end if negative - if abs_start <= length { - return Ok(length - abs_start); - } - } + if start < 0 && negative_count_from_end { + let abs_start = start.unsigned_abs(); + return Ok(if abs_start as u64 > crate::MAX_USIZE_INT as u64 { + 0 + } else { + length - usize::min(abs_start as usize, length) + }); } + if start <= crate::MAX_USIZE_INT && (start as usize) < length { return Ok(start as usize); } @@ -315,7 +324,10 @@ impl<'a> Target<'a> { let value = &mut *source.write_lock::().expect("`Blob`"); - value[*index] = (new_byte & 0x00ff) as u8; + #[allow(clippy::cast_sign_loss)] + { + value[*index] = (new_byte & 0x00ff) as u8; + } } #[cfg(not(feature = "no_index"))] Self::StringChar { diff --git a/src/func/builtin.rs b/src/func/builtin.rs index b6461191..118dd37e 100644 --- a/src/func/builtin.rs +++ b/src/func/builtin.rs @@ -1,14 +1,18 @@ //! Built-in implementations for common operators. +#![allow(clippy::float_cmp)] + use super::call::FnCallArgs; use super::native::FnBuiltin; +#[allow(clippy::enum_glob_use)] use crate::tokenizer::{Token, Token::*}; use crate::{ - Dynamic, ExclusiveRange, ImmutableString, InclusiveRange, NativeCallContext, RhaiResult, INT, + Dynamic, ExclusiveRange, ImmutableString, InclusiveRange, NativeCallContext, RhaiResult, + SmartString, INT, }; -use std::any::TypeId; #[cfg(feature = "no_std")] use std::prelude::v1::*; +use std::{any::TypeId, fmt::Write}; #[cfg(not(feature = "no_float"))] use crate::FLOAT; @@ -67,13 +71,11 @@ fn is_numeric(type_id: TypeId) -> bool { /// A function that returns `true`. #[inline(always)] -#[must_use] fn const_true_fn(_: NativeCallContext, _: &mut [&mut Dynamic]) -> RhaiResult { Ok(Dynamic::TRUE) } /// A function that returns `false`. #[inline(always)] -#[must_use] fn const_false_fn(_: NativeCallContext, _: &mut [&mut Dynamic]) -> RhaiResult { Ok(Dynamic::FALSE) } @@ -142,6 +144,7 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option< if type1 == type2 { if type1 == TypeId::of::() { #[cfg(not(feature = "unchecked"))] + #[allow(clippy::wildcard_imports)] use crate::packages::arithmetic::arith_basic::INT::functions::*; #[cfg(not(feature = "unchecked"))] @@ -241,7 +244,9 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option< let x = args[0].as_char().expect(BUILTIN); let y = args[1].as_char().expect(BUILTIN); - let result = format!("{x}{y}"); + let mut result = SmartString::new_const(); + result.push(x); + result.push(y); #[cfg(not(feature = "unchecked"))] _ctx.engine() @@ -337,6 +342,7 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option< ($x:ty, $xx:ident, $y:ty, $yy:ident) => { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { #[cfg(not(feature = "unchecked"))] + #[allow(clippy::wildcard_imports)] use crate::packages::arithmetic::decimal_functions::builtin::*; #[cfg(not(feature = "unchecked"))] @@ -399,7 +405,10 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option< Plus => Some(|_ctx, args| { let x = args[0].as_char().expect(BUILTIN); let y = &*args[1].read_lock::().expect(BUILTIN); - let result = format!("{x}{y}"); + + let mut result = SmartString::new_const(); + result.push(x); + result.push_str(y); #[cfg(not(feature = "unchecked"))] _ctx.engine() @@ -523,24 +532,20 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option< } // Handle ranges here because ranges are implemented as custom type - if type1 == TypeId::of::() { - if type1 == type2 { - return match op { - EqualsTo => Some(impl_op!(ExclusiveRange == ExclusiveRange)), - NotEqualsTo => Some(impl_op!(ExclusiveRange != ExclusiveRange)), - _ => None, - }; - } + if type1 == TypeId::of::() && type1 == type2 { + return match op { + EqualsTo => Some(impl_op!(ExclusiveRange == ExclusiveRange)), + NotEqualsTo => Some(impl_op!(ExclusiveRange != ExclusiveRange)), + _ => None, + }; } - if type1 == TypeId::of::() { - if type1 == type2 { - return match op { - EqualsTo => Some(impl_op!(InclusiveRange == InclusiveRange)), - NotEqualsTo => Some(impl_op!(InclusiveRange != InclusiveRange)), - _ => None, - }; - } + if type1 == TypeId::of::() && type1 == type2 { + return match op { + EqualsTo => Some(impl_op!(InclusiveRange == InclusiveRange)), + NotEqualsTo => Some(impl_op!(InclusiveRange != InclusiveRange)), + _ => None, + }; } // One of the operands is a custom type, so it is never built-in @@ -630,6 +635,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt if type1 == type2 { if type1 == TypeId::of::() { #[cfg(not(feature = "unchecked"))] + #[allow(clippy::wildcard_imports)] use crate::packages::arithmetic::arith_basic::INT::functions::*; #[cfg(not(feature = "unchecked"))] @@ -679,7 +685,12 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt PlusAssign => Some(|_, args| { let y = args[1].as_char().expect(BUILTIN); let x = &mut *args[0].write_lock::().expect(BUILTIN); - Ok((*x = format!("{x}{y}").into()).into()) + + let mut buf = SmartString::new_const(); + write!(&mut buf, "{y}").unwrap(); + buf.push(y); + + Ok((*x = buf.into()).into()) }), _ => None, }; @@ -713,6 +724,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt #[cfg(not(feature = "no_index"))] if type1 == TypeId::of::() { + #[allow(clippy::wildcard_imports)] use crate::packages::array_basic::array_functions::*; use crate::Array; @@ -744,6 +756,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt #[cfg(not(feature = "no_index"))] if type1 == TypeId::of::() { + #[allow(clippy::wildcard_imports)] use crate::packages::blob_basic::blob_functions::*; use crate::Blob; @@ -794,6 +807,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt ($x:ident, $xx:ident, $y:ty, $yy:ident) => { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { #[cfg(not(feature = "unchecked"))] + #[allow(clippy::wildcard_imports)] use crate::packages::arithmetic::decimal_functions::builtin::*; #[cfg(not(feature = "unchecked"))] @@ -880,6 +894,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt // array op= any #[cfg(not(feature = "no_index"))] if type1 == TypeId::of::() { + #[allow(clippy::wildcard_imports)] use crate::packages::array_basic::array_functions::*; use crate::Array; @@ -909,6 +924,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt // blob op= int if (type1, type2) == (TypeId::of::(), TypeId::of::()) { + #[allow(clippy::wildcard_imports)] use crate::packages::blob_basic::blob_functions::*; return match op { @@ -928,6 +944,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt // blob op= char if (type1, type2) == (TypeId::of::(), TypeId::of::()) { + #[allow(clippy::wildcard_imports)] use crate::packages::blob_basic::blob_functions::*; return match op { @@ -947,6 +964,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt // blob op= string if (type1, type2) == (TypeId::of::(), TypeId::of::()) { + #[allow(clippy::wildcard_imports)] use crate::packages::blob_basic::blob_functions::*; return match op { diff --git a/src/func/call.rs b/src/func/call.rs index 4e6ef6cb..591a5d4b 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -90,11 +90,7 @@ impl<'a> ArgBackup<'a> { /// exiting the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak. #[inline(always)] pub fn restore_first_arg(&mut self, args: &mut FnCallArgs<'a>) { - if let Some(p) = self.orig_mut.take() { - args[0] = p; - } else { - unreachable!("`Some`"); - } + args[0] = self.orig_mut.take().expect("`Some`"); } } @@ -116,7 +112,7 @@ pub fn ensure_no_data_race(fn_name: &str, args: &FnCallArgs, is_ref_mut: bool) - if let Some((n, ..)) = args .iter() .enumerate() - .skip(if is_ref_mut { 1 } else { 0 }) + .skip(usize::from(is_ref_mut)) .find(|(.., a)| a.is_locked()) { return Err(ERR::ErrorDataRace( @@ -216,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()))) }) }; @@ -256,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 @@ -277,7 +275,7 @@ impl Engine { Some(token) if token.is_op_assignment() => { let (first_arg, rest_args) = args.split_first().unwrap(); - get_builtin_op_assignment_fn(token, *first_arg, rest_args[0]) + get_builtin_op_assignment_fn(token, first_arg, rest_args[0]) .map(|f| FnResolutionCacheEntry { func: CallableFunction::Method(Shared::new(f)), source: None, @@ -344,7 +342,7 @@ impl Engine { name: &str, op_token: Option<&Token>, hash: u64, - mut args: &mut FnCallArgs, + args: &mut FnCallArgs, is_ref_mut: bool, pos: Position, ) -> RhaiResultOf<(Dynamic, bool)> { @@ -368,7 +366,10 @@ impl Engine { // Push a new call stack frame #[cfg(feature = "debugging")] - let orig_call_stack_len = global.debugger.call_stack().len(); + let orig_call_stack_len = global + .debugger + .as_ref() + .map_or(0, |dbg| dbg.call_stack().len()); let backup = &mut ArgBackup::new(); @@ -381,14 +382,16 @@ impl Engine { } let args = - &mut *RestoreOnDrop::lock_if(swap, &mut args, move |a| backup.restore_first_arg(a)); + &mut *RestoreOnDrop::lock_if(swap, args, move |a| backup.restore_first_arg(a)); #[cfg(feature = "debugging")] if self.debugger.is_some() { - global.debugger.push_call_stack_frame( + let source = source.clone().or_else(|| global.source.clone()); + + global.debugger_mut().push_call_stack_frame( self.get_interned_string(name), args.iter().map(|v| (*v).clone()).collect(), - source.clone().or_else(|| global.source.clone()), + source, pos, ); } @@ -414,16 +417,16 @@ impl Engine { }; #[cfg(feature = "debugging")] - { + if self.debugger.is_some() { use crate::eval::{DebuggerEvent, DebuggerStatus}; - let trigger = match global.debugger.status { + let trigger = match global.debugger().status { DebuggerStatus::FunctionExit(n) => n >= global.level, DebuggerStatus::Next(.., true) => true, _ => false, }; if trigger { - let scope = &mut &mut Scope::new(); + let scope = &mut Scope::new(); let mut this = Dynamic::NULL; let node = crate::ast::Stmt::Noop(pos); let node = (&node).into(); @@ -440,7 +443,7 @@ impl Engine { } // Pop the call stack - global.debugger.rewind_call_stack(orig_call_stack_len); + global.debugger_mut().rewind_call_stack(orig_call_stack_len); } let result = _result?; @@ -597,12 +600,13 @@ impl Engine { let num_params = _args[1].as_int().expect("`INT`"); return Ok(( - if num_params < 0 || num_params > crate::MAX_USIZE_INT { - false - } else { + if (0..=crate::MAX_USIZE_INT).contains(&num_params) { + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let hash_script = calc_fn_hash(None, fn_name.as_str(), num_params as usize); self.has_script_fn(global, caches, hash_script) + } else { + false } .into(), false, @@ -647,12 +651,11 @@ impl Engine { } let mut empty_scope; - let scope = match _scope { - Some(scope) => scope, - None => { - empty_scope = Scope::new(); - &mut empty_scope - } + let scope = if let Some(scope) = _scope { + scope + } else { + empty_scope = Scope::new(); + &mut empty_scope }; let orig_source = mem::replace(&mut global.source, source.clone()); @@ -676,7 +679,7 @@ impl Engine { backup.change_first_arg_to_copy(_args); } - let args = &mut *RestoreOnDrop::lock_if(swap, &mut _args, move |a| { + let args = &mut *RestoreOnDrop::lock_if(swap, _args, move |a| { backup.restore_first_arg(a) }); @@ -718,11 +721,15 @@ impl Engine { // Do not match function exit for arguments #[cfg(feature = "debugging")] - let reset = global.debugger.clear_status_if(|status| { - matches!(status, crate::eval::DebuggerStatus::FunctionExit(..)) + let reset = global.debugger.as_mut().and_then(|dbg| { + dbg.clear_status_if(|status| { + matches!(status, crate::eval::DebuggerStatus::FunctionExit(..)) + }) }); #[cfg(feature = "debugging")] - let global = &mut *RestoreOnDrop::lock(global, move |g| g.debugger.reset_status(reset)); + let global = &mut *RestoreOnDrop::lock_if(reset.is_some(), global, move |g| { + g.debugger_mut().reset_status(reset) + }); self.eval_expr(global, caches, scope, this_ptr, arg_expr) .map(|r| (r, arg_expr.start_position())) @@ -901,7 +908,7 @@ impl Engine { call_args = &mut _arg_values; } // Recalculate the hash based on the new function name and new arguments - hash = if !is_anon && !is_valid_function_name(&fn_name) { + hash = if !is_anon && !is_valid_function_name(fn_name) { FnCallHashes::from_native(calc_fn_hash( None, fn_name, @@ -963,7 +970,7 @@ impl Engine { ) -> RhaiResult { let mut first_arg = first_arg; let mut a_expr = args_expr; - let mut total_args = if first_arg.is_some() { 1 } else { 0 } + a_expr.len(); + let mut total_args = usize::from(first_arg.is_some()) + a_expr.len(); let mut curry = FnArgsVec::new_const(); let mut name = fn_name; let mut hashes = hashes; @@ -1077,9 +1084,10 @@ impl Engine { .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; - return Ok(if num_params < 0 || num_params > crate::MAX_USIZE_INT { + return Ok(if !(0..=crate::MAX_USIZE_INT).contains(&num_params) { false } else { + #[allow(clippy::cast_sign_loss)] let hash_script = calc_fn_hash(None, &fn_name, num_params as usize); self.has_script_fn(global, caches, hash_script) } @@ -1437,7 +1445,7 @@ impl Engine { // No optimizations because we only run it once let ast = self.compile_with_scope_and_optimization_level( &Scope::new(), - &[script], + [script], #[cfg(not(feature = "no_optimize"))] OptimizationLevel::None, #[cfg(feature = "no_optimize")] diff --git a/src/func/callable_function.rs b/src/func/callable_function.rs index 893709b6..4d441e63 100644 --- a/src/func/callable_function.rs +++ b/src/func/callable_function.rs @@ -132,9 +132,7 @@ impl CallableFunction { #[cfg(not(feature = "no_function"))] match self { - Self::Pure(..) | Self::Method(..) => true, - Self::Plugin(..) => true, - Self::Iterator(..) => true, + Self::Pure(..) | Self::Method(..) | Self::Plugin(..) | Self::Iterator(..) => true, Self::Script(..) => false, } } @@ -147,8 +145,9 @@ impl CallableFunction { #[cfg(not(feature = "no_function"))] match self { - Self::Plugin(..) => FnAccess::Public, - Self::Pure(..) | Self::Method(..) | Self::Iterator(..) => FnAccess::Public, + Self::Plugin(..) | Self::Pure(..) | Self::Method(..) | Self::Iterator(..) => { + FnAccess::Public + } Self::Script(f) => f.access, } } diff --git a/src/func/func.rs b/src/func/func.rs index 7d211879..97bcfb04 100644 --- a/src/func/func.rs +++ b/src/func/func.rs @@ -80,7 +80,6 @@ pub trait Func { /// # Ok(()) /// # } /// ``` - #[must_use] fn create_from_script(self, script: &str, entry_point: &str) -> ParseResult; } diff --git a/src/func/native.rs b/src/func/native.rs index 7363606a..4114bf1a 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -151,7 +151,7 @@ impl<'a> NativeCallContext<'a> { #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] - pub fn new_with_all_fields( + pub const fn new_with_all_fields( engine: &'a Engine, fn_name: &'a str, source: Option<&'a str>, @@ -182,7 +182,7 @@ impl<'a> NativeCallContext<'a> { Self { engine, fn_name: &context.fn_name, - source: context.source.as_ref().map(String::as_str), + source: context.source.as_deref(), global: &context.global, pos: context.pos, } @@ -240,7 +240,7 @@ impl<'a> NativeCallContext<'a> { /// Custom state kept in a [`Dynamic`]. #[inline(always)] #[must_use] - pub fn tag(&self) -> Option<&Dynamic> { + pub const fn tag(&self) -> Option<&Dynamic> { Some(&self.global.tag) } /// Get an iterator over the current set of modules imported via `import` statements @@ -278,7 +278,7 @@ impl<'a> NativeCallContext<'a> { #[cfg(not(feature = "no_function"))] #[inline] pub fn iter_namespaces(&self) -> impl Iterator { - self.global.lib.iter().map(|m| m.as_ref()) + self.global.lib.iter().map(AsRef::as_ref) } /// _(internals)_ The current stack of namespaces containing definitions of all script-defined functions. /// Exported under the `internals` feature only. diff --git a/src/func/script.rs b/src/func/script.rs index 55ba1361..b1352450 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -83,7 +83,10 @@ impl Engine { let orig_imports_len = global.num_imports(); #[cfg(feature = "debugging")] - let orig_call_stack_len = global.debugger.call_stack().len(); + let orig_call_stack_len = global + .debugger + .as_ref() + .map_or(0, |dbg| dbg.call_stack().len()); // Put arguments into scope as variables scope.extend(fn_def.params.iter().cloned().zip(args.iter_mut().map(|v| { @@ -94,10 +97,12 @@ impl Engine { // Push a new call stack frame #[cfg(feature = "debugging")] if self.debugger.is_some() { - global.debugger.push_call_stack_frame( + let source = global.source.clone(); + + global.debugger_mut().push_call_stack_frame( fn_def.name.clone(), scope.iter().skip(orig_scope_len).map(|(.., v)| v).collect(), - global.source.clone(), + source, pos, ); } @@ -126,7 +131,7 @@ impl Engine { }; #[cfg(feature = "debugging")] - { + if self.debugger.is_some() { let node = crate::ast::Stmt::Noop(fn_def.body.position()); self.run_debugger(global, caches, scope, this_ptr, &node)?; } @@ -156,12 +161,13 @@ impl Engine { }); #[cfg(feature = "debugging")] - { - let trigger = match global.debugger.status { + if self.debugger.is_some() { + let trigger = match global.debugger_mut().status { crate::eval::DebuggerStatus::FunctionExit(n) => n >= global.level, crate::eval::DebuggerStatus::Next(.., true) => true, _ => false, }; + if trigger { let node = crate::ast::Stmt::Noop(fn_def.body.end_position().or_else(pos)); let node = (&node).into(); @@ -176,7 +182,11 @@ impl Engine { } // Pop the call stack - global.debugger.rewind_call_stack(orig_call_stack_len); + global + .debugger + .as_mut() + .unwrap() + .rewind_call_stack(orig_call_stack_len); } // Remove all local variables and imported modules @@ -226,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/lib.rs b/src/lib.rs index 96bee4eb..fa95cb95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,11 +65,15 @@ // #![warn(clippy::undocumented_unsafe_blocks)] #![allow(clippy::unit_arg)] #![allow(clippy::missing_errors_doc)] +#![allow(clippy::missing_panics_doc)] #![allow(clippy::used_underscore_binding)] #![allow(clippy::inline_always)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::negative_feature_names)] #![allow(clippy::module_inception)] +#![allow(clippy::box_collection)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::upper_case_acronyms)] #[cfg(feature = "no_std")] extern crate alloc; diff --git a/src/module/mod.rs b/src/module/mod.rs index 91be1a53..8054c89e 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -5,13 +5,14 @@ use crate::api::type_names::format_type; use crate::ast::FnAccess; use crate::func::{ shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, RegisterNativeFunction, - SendSync, + SendSync, StraightHashMap, }; use crate::types::{dynamic::Variant, BloomFilterU64, CustomTypesCollection}; use crate::{ calc_fn_hash, calc_fn_hash_full, Dynamic, Identifier, ImmutableString, NativeCallContext, RhaiResultOf, Shared, SharedModule, SmartString, StaticVec, }; +use bitflags::bitflags; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ @@ -24,7 +25,8 @@ use std::{ #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] use crate::func::register::Mut; -use crate::func::StraightHashMap; +/// Initial capacity of the hashmap for functions. +const FN_MAP_SIZE: usize = 16; /// A type representing the namespace of a function. #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] @@ -61,12 +63,10 @@ impl FnNamespace { } } -/// A type containing a single registered function. +/// A type containing the metadata of a single registered function. #[derive(Debug, Clone)] #[non_exhaustive] -pub struct FuncInfo { - /// Function instance. - pub func: CallableFunction, +pub struct FuncInfoMetadata { /// Function namespace. pub namespace: FnNamespace, /// Function access mode. @@ -88,50 +88,60 @@ pub struct FuncInfo { pub comments: Box<[Box]>, } +/// A type containing a single registered function. +#[derive(Debug, Clone)] +pub struct FuncInfo { + /// Function instance. + pub func: CallableFunction, + /// Function metadata. + pub metadata: Box, +} + impl FuncInfo { /// _(metadata)_ Generate a signature of the function. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] #[must_use] pub fn gen_signature(&self) -> String { - let mut sig = format!("{}(", self.name); + let mut signature = format!("{}(", self.metadata.name); - let return_type = format_type(&self.return_type, true); + let return_type = format_type(&self.metadata.return_type, true); - if self.params_info.is_empty() { - for x in 0..self.num_params { - sig.push('_'); - if x < self.num_params - 1 { - sig.push_str(", "); + if self.metadata.params_info.is_empty() { + for x in 0..self.metadata.num_params { + signature.push('_'); + if x < self.metadata.num_params - 1 { + signature.push_str(", "); } } } else { let params: StaticVec<_> = self + .metadata .params_info .iter() - .map(|s| { - let mut seg = s.splitn(2, ':'); - let name = match seg.next().unwrap().trim() { + .map(|param| { + let mut segment = param.splitn(2, ':'); + let name = match segment.next().unwrap().trim() { "" => "_", s => s, }; - let result: std::borrow::Cow = match seg.next() { + let result: std::borrow::Cow = match segment.next() { Some(typ) => format!("{name}: {}", format_type(typ, false)).into(), None => name.into(), }; result }) .collect(); - sig.push_str(¶ms.join(", ")); + signature.push_str(¶ms.join(", ")); } - sig.push(')'); + signature.push(')'); if !self.func.is_script() && !return_type.is_empty() { - sig.push_str(" -> "); - sig.push_str(&return_type); + signature.push_str(" -> "); + signature.push_str(&return_type); } - sig + signature } } @@ -156,6 +166,20 @@ pub fn calc_native_fn_hash<'a>( ) } +bitflags! { + /// Bit-flags containing all status for [`Module`]. + pub struct ModuleFlags: u8 { + /// Is the [`Module`] internal? + const INTERNAL = 0b0000_0001; + /// Is the [`Module`] part of a standard library? + const STANDARD_LIB = 0b0000_0010; + /// Is the [`Module`] indexed? + const INDEXED = 0b0000_0100; + /// Does the [`Module`] contain indexed functions that have been exposed to the global namespace? + const INDEXED_GLOBAL_FUNCTIONS = 0b0000_1000; + } +} + /// A module which may contain variables, sub-modules, external Rust functions, /// and/or script-defined functions. #[derive(Clone)] @@ -164,34 +188,28 @@ pub struct Module { id: Option, /// Module documentation. #[cfg(feature = "metadata")] - doc: crate::SmartString, - /// Is this module internal? - pub(crate) internal: bool, - /// Is this module part of a standard library? - pub(crate) standard: bool, + doc: Option>, /// Custom types. - custom_types: Option, + custom_types: Option>, /// Sub-modules. - modules: Option>, + modules: Option>>, /// [`Module`] variables. - variables: Option>, + variables: Option>>, /// Flattened collection of all [`Module`] variables, including those in sub-modules. - all_variables: Option>, + all_variables: Option>>, /// Functions (both native Rust and scripted). - functions: StraightHashMap, + functions: Option>, /// Flattened collection of all functions, native Rust and scripted. /// including those in sub-modules. - all_functions: Option>, + all_functions: Option>>, /// Bloom filter on native Rust functions (in scripted hash format) that contain [`Dynamic`] parameters. - dynamic_functions_filter: BloomFilterU64, + dynamic_functions_filter: Option>, /// Iterator functions, keyed by the type producing the iterator. - type_iterators: Option>>, + type_iterators: Option>>>, /// Flattened collection of iterator functions, including those in sub-modules. - all_type_iterators: Option>>, - /// Is the [`Module`] indexed? - indexed: bool, - /// Does the [`Module`] contain indexed functions that have been exposed to the global namespace? - contains_indexed_global_functions: bool, + all_type_iterators: Option>>>, + /// Flags. + pub(crate) flags: ModuleFlags, } impl Default for Module { @@ -275,39 +293,21 @@ impl Module { /// ``` #[inline(always)] #[must_use] - pub fn new() -> Self { - Self::with_capacity(16) - } - /// Create a new [`Module`] with a pre-sized capacity for functions. - /// - /// # Example - /// - /// ``` - /// # use rhai::Module; - /// let mut module = Module::with_capacity(10); - /// module.set_var("answer", 42_i64); - /// assert_eq!(module.get_var_value::("answer").expect("answer should exist"), 42); - /// ``` - #[inline] - #[must_use] - pub fn with_capacity(capacity: usize) -> Self { + pub const fn new() -> Self { Self { id: None, #[cfg(feature = "metadata")] - doc: crate::SmartString::new_const(), - internal: false, - standard: false, + doc: None, custom_types: None, modules: None, variables: None, all_variables: None, - functions: StraightHashMap::with_capacity_and_hasher(capacity, Default::default()), + functions: None, all_functions: None, - dynamic_functions_filter: BloomFilterU64::new(), + dynamic_functions_filter: None, type_iterators: None, all_type_iterators: None, - indexed: true, - contains_indexed_global_functions: false, + flags: ModuleFlags::INDEXED, } } @@ -392,7 +392,7 @@ impl Module { #[inline] #[must_use] pub fn doc(&self) -> &str { - &self.doc + self.doc.as_ref().map_or("", |s| s.as_str()) } /// Set the documentation of the [`Module`]. @@ -411,7 +411,7 @@ impl Module { #[cfg(feature = "metadata")] #[inline(always)] pub fn set_doc(&mut self, doc: impl Into) -> &mut Self { - self.doc = doc.into(); + self.doc = Some(Box::new(doc.into())); self } @@ -430,29 +430,27 @@ impl Module { #[cfg(feature = "metadata")] #[inline(always)] pub fn clear_doc(&mut self) -> &mut Self { - self.doc.clear(); + self.doc = None; self } /// Clear the [`Module`]. #[inline(always)] pub fn clear(&mut self) { - self.id = None; #[cfg(feature = "metadata")] - self.doc.clear(); - self.internal = false; - self.standard = false; + { + self.doc = None; + } self.custom_types = None; self.modules = None; self.variables = None; self.all_variables = None; - self.functions.clear(); + self.functions = None; self.all_functions = None; - self.dynamic_functions_filter.clear(); + self.dynamic_functions_filter = None; self.type_iterators = None; self.all_type_iterators = None; - self.indexed = false; - self.contains_indexed_global_functions = false; + self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; } /// Map a custom type to a friendly display name. @@ -475,7 +473,7 @@ impl Module { #[inline(always)] pub fn set_custom_type(&mut self, name: &str) -> &mut Self { self.custom_types - .get_or_insert_with(CustomTypesCollection::new) + .get_or_insert_with(Default::default) .add_type::(name); self } @@ -501,7 +499,7 @@ impl Module { name: impl Into, ) -> &mut Self { self.custom_types - .get_or_insert_with(CustomTypesCollection::new) + .get_or_insert_with(Default::default) .add(type_path, name); self } @@ -543,8 +541,8 @@ impl Module { #[inline] #[must_use] pub fn is_empty(&self) -> bool { - !self.contains_indexed_global_functions - && self.functions.is_empty() + !self.flags.contains(ModuleFlags::INDEXED_GLOBAL_FUNCTIONS) + && self.functions.as_ref().map_or(true, |m| m.is_empty()) && self.variables.as_ref().map_or(true, |m| m.is_empty()) && self.modules.as_ref().map_or(true, |m| m.is_empty()) && self.type_iterators.as_ref().map_or(true, |t| t.is_empty()) @@ -579,7 +577,7 @@ impl Module { #[inline(always)] #[must_use] pub const fn is_indexed(&self) -> bool { - self.indexed + self.flags.contains(ModuleFlags::INDEXED) } /// _(metadata)_ Generate signatures for all the non-private functions in the [`Module`]. @@ -588,7 +586,7 @@ impl Module { #[inline] pub fn gen_fn_signatures(&self) -> impl Iterator + '_ { self.iter_fn() - .filter(|&f| match f.access { + .filter(|&f| match f.metadata.access { FnAccess::Public => true, FnAccess::Private => false, }) @@ -666,14 +664,14 @@ impl Module { let ident = name.into(); let value = Dynamic::from(value); - if self.indexed { + if self.is_indexed() { let hash_var = crate::calc_var_hash(Some(""), &ident); self.all_variables - .get_or_insert_with(|| Default::default()) + .get_or_insert_with(Default::default) .insert(hash_var, value.clone()); } self.variables - .get_or_insert_with(|| Default::default()) + .get_or_insert_with(Default::default) .insert(ident, value); self } @@ -700,25 +698,31 @@ impl Module { let hash_script = crate::calc_fn_hash(None, &fn_def.name, num_params); #[cfg(feature = "metadata")] let params_info = fn_def.params.iter().map(Into::into).collect(); - self.functions.insert( - hash_script, - FuncInfo { - name: fn_def.name.as_str().into(), - namespace: FnNamespace::Internal, - access: fn_def.access, - num_params, - param_types: StaticVec::new_const(), - #[cfg(feature = "metadata")] - params_info, - #[cfg(feature = "metadata")] - return_type: "".into(), - #[cfg(feature = "metadata")] - comments: Box::default(), - func: fn_def.into(), - }, - ); - self.indexed = false; - self.contains_indexed_global_functions = false; + self.functions + .get_or_insert_with(|| { + StraightHashMap::with_capacity_and_hasher(FN_MAP_SIZE, Default::default()) + }) + .insert( + hash_script, + FuncInfo { + metadata: FuncInfoMetadata { + name: fn_def.name.as_str().into(), + namespace: FnNamespace::Internal, + access: fn_def.access, + num_params, + param_types: StaticVec::new_const(), + #[cfg(feature = "metadata")] + params_info, + #[cfg(feature = "metadata")] + return_type: "".into(), + #[cfg(feature = "metadata")] + comments: Box::default(), + } + .into(), + func: fn_def.into(), + }, + ); + self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; hash_script } @@ -732,15 +736,13 @@ impl Module { name: impl AsRef, num_params: usize, ) -> Option<&Shared> { - if self.functions.is_empty() { - None - } else { + self.functions.as_ref().and_then(|lib| { let name = name.as_ref(); - self.iter_fn() - .find(|f| f.num_params == num_params && f.name == name) + lib.values() + .find(|&f| f.metadata.num_params == num_params && f.metadata.name == name) .and_then(|f| f.func.get_script_fn_def()) - } + }) } /// Get a mutable reference to the underlying [`BTreeMap`] of sub-modules, @@ -759,10 +761,9 @@ impl Module { self.all_functions = None; self.all_variables = None; self.all_type_iterators = None; - self.indexed = false; - self.contains_indexed_global_functions = false; + self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; - self.modules.get_or_insert_with(|| Default::default()) + self.modules.get_or_insert_with(Default::default) } /// Does a sub-module exist in the [`Module`]? @@ -824,10 +825,9 @@ impl Module { sub_module: impl Into, ) -> &mut Self { self.modules - .get_or_insert_with(|| Default::default()) + .get_or_insert_with(Default::default) .insert(name.into(), sub_module.into()); - self.indexed = false; - self.contains_indexed_global_functions = false; + self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; self } @@ -843,14 +843,12 @@ impl Module { /// let hash = module.set_native_fn("calc", || Ok(42_i64)); /// assert!(module.contains_fn(hash)); /// ``` - #[inline(always)] + #[inline] #[must_use] pub fn contains_fn(&self, hash_fn: u64) -> bool { - if self.functions.is_empty() { - false - } else { - self.functions.contains_key(&hash_fn) - } + self.functions + .as_ref() + .map_or(false, |m| m.contains_key(&hash_fn)) } /// _(metadata)_ Update the metadata (parameter names/types and return type) of a registered function. @@ -879,15 +877,15 @@ impl Module { .map(|s| s.as_ref().into()) .collect(); - if let Some(f) = self.functions.get_mut(&hash_fn) { - let (param_names, return_type_name) = if param_names.len() > f.num_params { + if let Some(f) = self.functions.as_mut().and_then(|m| m.get_mut(&hash_fn)) { + let (param_names, return_type_name) = if param_names.len() > f.metadata.num_params { let return_type = param_names.pop().unwrap(); (param_names, return_type) } else { (param_names, crate::SmartString::new_const()) }; - f.params_info = param_names; - f.return_type = return_type_name; + f.metadata.params_info = param_names; + f.metadata.return_type = return_type_name; } self @@ -928,8 +926,12 @@ impl Module { let comments = comments.as_ref(); if !comments.is_empty() { - let f = self.functions.get_mut(&hash_fn).unwrap(); - f.comments = comments.iter().map(|s| s.as_ref().into()).collect(); + let f = self + .functions + .as_mut() + .and_then(|m| m.get_mut(&hash_fn)) + .unwrap(); + f.metadata.comments = comments.iter().map(|s| s.as_ref().into()).collect(); } self @@ -940,10 +942,9 @@ impl Module { /// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call. #[inline] pub fn update_fn_namespace(&mut self, hash_fn: u64, namespace: FnNamespace) -> &mut Self { - if let Some(f) = self.functions.get_mut(&hash_fn) { - f.namespace = namespace; - self.indexed = false; - self.contains_indexed_global_functions = false; + if let Some(f) = self.functions.as_mut().and_then(|m| m.get_mut(&hash_fn)) { + f.metadata.namespace = namespace; + self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; } self } @@ -1030,29 +1031,37 @@ impl Module { let hash_fn = calc_fn_hash_full(hash_script, param_types.iter().copied()); if is_dynamic { - self.dynamic_functions_filter.mark(hash_script); + self.dynamic_functions_filter + .get_or_insert_with(Default::default) + .mark(hash_script); } - self.functions.insert( - hash_fn, - FuncInfo { - func, - name: name.into(), - namespace, - access, - num_params: param_types.len(), - param_types, - #[cfg(feature = "metadata")] - params_info: param_names, - #[cfg(feature = "metadata")] - return_type: return_type_name, - #[cfg(feature = "metadata")] - comments: Box::default(), - }, - ); + self.functions + .get_or_insert_with(|| { + StraightHashMap::with_capacity_and_hasher(FN_MAP_SIZE, Default::default()) + }) + .insert( + hash_fn, + FuncInfo { + func, + metadata: FuncInfoMetadata { + name: name.into(), + namespace, + access, + num_params: param_types.len(), + param_types, + #[cfg(feature = "metadata")] + params_info: param_names, + #[cfg(feature = "metadata")] + return_type: return_type_name, + #[cfg(feature = "metadata")] + comments: Box::default(), + } + .into(), + }, + ); - self.indexed = false; - self.contains_indexed_global_functions = false; + self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; hash_fn } @@ -1100,8 +1109,8 @@ impl Module { let comments = comments.as_ref(); if !comments.is_empty() { - let f = self.functions.get_mut(&hash).unwrap(); - f.comments = comments.iter().map(|s| s.as_ref().into()).collect(); + let f = self.functions.as_mut().unwrap().get_mut(&hash).unwrap(); + f.metadata.comments = comments.iter().map(|s| s.as_ref().into()).collect(); } hash @@ -1538,20 +1547,22 @@ impl Module { #[inline] #[must_use] pub(crate) fn get_fn(&self, hash_native: u64) -> Option<&CallableFunction> { - if self.functions.is_empty() { - None - } else { - self.functions.get(&hash_native).map(|f| &f.func) - } + self.functions + .as_ref() + .and_then(|m| m.get(&hash_native)) + .map(|f| &f.func) } /// Can the particular function with [`Dynamic`] parameter(s) exist in the [`Module`]? /// /// A `true` return value does not automatically imply that the function _must_ exist. - #[inline(always)] + #[inline] #[must_use] pub(crate) fn may_contain_dynamic_fn(&self, hash_script: u64) -> bool { - !self.dynamic_functions_filter.is_absent(hash_script) + !self + .dynamic_functions_filter + .as_ref() + .map_or(false, |f| f.is_absent(hash_script)) } /// Does the particular namespace-qualified function exist in the [`Module`]? @@ -1595,8 +1606,20 @@ impl Module { Some(_) => (), None => self.variables = other.variables, } - self.functions.extend(other.functions.into_iter()); - self.dynamic_functions_filter += &other.dynamic_functions_filter; + match self.functions { + Some(ref mut m) if other.functions.is_some() => { + m.extend(other.functions.unwrap().into_iter()) + } + Some(_) => (), + None => self.functions = other.functions, + } + match self.dynamic_functions_filter { + Some(ref mut m) if other.dynamic_functions_filter.is_some() => { + **m += &**other.dynamic_functions_filter.as_ref().unwrap() + } + Some(_) => (), + None => self.dynamic_functions_filter = other.dynamic_functions_filter, + } match self.type_iterators { Some(ref mut m) if other.type_iterators.is_some() => { m.extend(other.type_iterators.unwrap().into_iter()) @@ -1607,15 +1630,16 @@ impl Module { self.all_functions = None; self.all_variables = None; self.all_type_iterators = None; - self.indexed = false; - self.contains_indexed_global_functions = false; + self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; #[cfg(feature = "metadata")] - if !other.doc.is_empty() { - if !self.doc.is_empty() { - self.doc.push('\n'); + if !other.doc.as_ref().map_or(true, |s| s.is_empty()) { + if !self.doc.as_ref().map_or(true, |s| s.is_empty()) { + self.doc.get_or_insert_with(Default::default).push('\n'); } - self.doc.push_str(&other.doc); + self.doc + .get_or_insert_with(Default::default) + .push_str(other.doc.as_ref().unwrap()); } self @@ -1638,8 +1662,20 @@ impl Module { Some(_) => (), None => self.variables = other.variables, } - self.functions.extend(other.functions.into_iter()); - self.dynamic_functions_filter += &other.dynamic_functions_filter; + match self.functions { + Some(ref mut m) if other.functions.is_some() => { + m.extend(other.functions.unwrap().into_iter()) + } + Some(_) => (), + None => self.functions = other.functions, + } + match self.dynamic_functions_filter { + Some(ref mut m) if other.dynamic_functions_filter.is_some() => { + **m += &**other.dynamic_functions_filter.as_ref().unwrap() + } + Some(_) => (), + None => self.dynamic_functions_filter = other.dynamic_functions_filter, + } match self.type_iterators { Some(ref mut m) if other.type_iterators.is_some() => { m.extend(other.type_iterators.unwrap().into_iter()) @@ -1650,15 +1686,16 @@ impl Module { self.all_functions = None; self.all_variables = None; self.all_type_iterators = None; - self.indexed = false; - self.contains_indexed_global_functions = false; + self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; #[cfg(feature = "metadata")] - if !other.doc.is_empty() { - if !self.doc.is_empty() { - self.doc.push('\n'); + if !other.doc.as_ref().map_or(true, |s| s.is_empty()) { + if !self.doc.as_ref().map_or(true, |s| s.is_empty()) { + self.doc.get_or_insert_with(Default::default).push('\n'); } - self.doc.push_str(&other.doc); + self.doc + .get_or_insert_with(Default::default) + .push_str(other.doc.as_ref().unwrap()); } self @@ -1669,48 +1706,61 @@ impl Module { #[inline] pub fn fill_with(&mut self, other: &Self) -> &mut Self { if let Some(ref modules) = other.modules { - let m = self.modules.get_or_insert_with(|| Default::default()); + let m = self.modules.get_or_insert_with(Default::default); - for (k, v) in modules { + for (k, v) in modules.iter() { if !m.contains_key(k) { m.insert(k.clone(), v.clone()); } } } if let Some(ref variables) = other.variables { - for (k, v) in variables { - let m = self.variables.get_or_insert_with(|| Default::default()); + for (k, v) in variables.iter() { + let map = self.variables.get_or_insert_with(Default::default); - if !m.contains_key(k) { - m.insert(k.clone(), v.clone()); + if !map.contains_key(k) { + map.insert(k.clone(), v.clone()); } } } - for (&k, v) in &other.functions { - self.functions.entry(k).or_insert_with(|| v.clone()); - } - self.dynamic_functions_filter += &other.dynamic_functions_filter; - if let Some(ref type_iterators) = other.type_iterators { - let t = self - .type_iterators - .get_or_insert_with(|| Default::default()); + if let Some(ref functions) = other.functions { + let others_len = functions.len(); - for (&k, v) in type_iterators { + for (&k, f) in functions.iter() { + let map = self.functions.get_or_insert_with(|| { + StraightHashMap::with_capacity_and_hasher(others_len, Default::default()) + }); + map.reserve(others_len); + map.entry(k).or_insert_with(|| f.clone()); + } + } + match self.dynamic_functions_filter { + Some(ref mut m) if other.dynamic_functions_filter.is_some() => { + **m += &**other.dynamic_functions_filter.as_ref().unwrap() + } + Some(_) => (), + None => self.dynamic_functions_filter = other.dynamic_functions_filter.clone(), + } + if let Some(ref type_iterators) = other.type_iterators { + let t = self.type_iterators.get_or_insert_with(Default::default); + + for (&k, v) in type_iterators.iter() { t.entry(k).or_insert_with(|| v.clone()); } } self.all_functions = None; self.all_variables = None; self.all_type_iterators = None; - self.indexed = false; - self.contains_indexed_global_functions = false; + self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; #[cfg(feature = "metadata")] - if !other.doc.is_empty() { - if !self.doc.is_empty() { - self.doc.push('\n'); + if !other.doc.as_ref().map_or(true, |s| s.is_empty()) { + if !self.doc.as_ref().map_or(true, |s| s.is_empty()) { + self.doc.get_or_insert_with(Default::default).push('\n'); } - self.doc.push_str(&other.doc); + self.doc + .get_or_insert_with(Default::default) + .push_str(other.doc.as_ref().unwrap()); } self @@ -1729,7 +1779,7 @@ impl Module { _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool + Copy, ) -> &mut Self { if let Some(ref modules) = other.modules { - for (k, v) in modules { + for (k, v) in modules.iter() { let mut m = Self::new(); m.merge_filtered(v, _filter); self.set_sub_module(k.clone(), m); @@ -1748,23 +1798,32 @@ impl Module { None => self.variables = other.variables.clone(), } } - - self.functions.extend( - other - .functions - .iter() - .filter(|&(.., f)| { - _filter( - f.namespace, - f.access, - f.func.is_script(), - f.name.as_str(), - f.num_params, - ) - }) - .map(|(&k, v)| (k, v.clone())), - ); - self.dynamic_functions_filter += &other.dynamic_functions_filter; + if let Some(ref functions) = other.functions { + match self.functions { + Some(ref mut m) => m.extend( + functions + .iter() + .filter(|&(.., f)| { + _filter( + f.metadata.namespace, + f.metadata.access, + f.func.is_script(), + f.metadata.name.as_str(), + f.metadata.num_params, + ) + }) + .map(|(&k, f)| (k, f.clone())), + ), + None => self.functions = other.functions.clone(), + } + } + match self.dynamic_functions_filter { + Some(ref mut m) if other.dynamic_functions_filter.is_some() => { + **m += &**other.dynamic_functions_filter.as_ref().unwrap() + } + Some(_) => (), + None => self.dynamic_functions_filter = other.dynamic_functions_filter.clone(), + } if let Some(ref type_iterators) = other.type_iterators { match self.type_iterators { @@ -1775,15 +1834,16 @@ impl Module { self.all_functions = None; self.all_variables = None; self.all_type_iterators = None; - self.indexed = false; - self.contains_indexed_global_functions = false; + self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; #[cfg(feature = "metadata")] - if !other.doc.is_empty() { - if !self.doc.is_empty() { - self.doc.push('\n'); + if !other.doc.as_ref().map_or(true, |s| s.is_empty()) { + if !self.doc.as_ref().map_or(true, |s| s.is_empty()) { + self.doc.get_or_insert_with(Default::default).push('\n'); } - self.doc.push_str(&other.doc); + self.doc + .get_or_insert_with(Default::default) + .push_str(other.doc.as_ref().unwrap()); } self @@ -1796,23 +1856,28 @@ impl Module { &mut self, filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool, ) -> &mut Self { - self.functions = std::mem::take(&mut self.functions) - .into_iter() - .filter(|(.., f)| { - if f.func.is_script() { - filter(f.namespace, f.access, f.name.as_str(), f.num_params) - } else { - false - } - }) - .collect(); + self.functions = std::mem::take(&mut self.functions).map(|m| { + m.into_iter() + .filter(|(.., f)| { + if f.func.is_script() { + filter( + f.metadata.namespace, + f.metadata.access, + f.metadata.name.as_str(), + f.metadata.num_params, + ) + } else { + false + } + }) + .collect() + }); - self.dynamic_functions_filter.clear(); + self.dynamic_functions_filter = None; self.all_functions = None; self.all_variables = None; self.all_type_iterators = None; - self.indexed = false; - self.contains_indexed_global_functions = false; + self.flags &= !ModuleFlags::INDEXED & !ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; self } @@ -1822,7 +1887,7 @@ impl Module { pub fn count(&self) -> (usize, usize, usize) { ( self.variables.as_ref().map_or(0, |m| m.len()), - self.functions.len(), + self.functions.as_ref().map_or(0, |m| m.len()), self.type_iterators.as_ref().map_or(0, |t| t.len()), ) } @@ -1847,7 +1912,7 @@ impl Module { #[inline] #[allow(dead_code)] pub(crate) fn iter_fn(&self) -> impl Iterator { - self.functions.values() + self.functions.iter().flat_map(|m| m.values()) } /// Get an iterator over all script-defined functions in the [`Module`]. @@ -1873,10 +1938,10 @@ impl Module { > + '_ { self.iter_fn().filter(|&f| f.func.is_script()).map(|f| { ( - f.namespace, - f.access, - f.name.as_str(), - f.num_params, + f.metadata.namespace, + f.metadata.access, + f.metadata.name.as_str(), + f.metadata.num_params, f.func.get_script_fn_def().expect("script-defined function"), ) }) @@ -1895,9 +1960,14 @@ impl Module { pub fn iter_script_fn_info( &self, ) -> impl Iterator { - self.iter_fn() - .filter(|&f| f.func.is_script()) - .map(|f| (f.namespace, f.access, f.name.as_str(), f.num_params)) + self.iter_fn().filter(|&f| f.func.is_script()).map(|f| { + ( + f.metadata.namespace, + f.metadata.access, + f.metadata.name.as_str(), + f.metadata.num_params, + ) + }) } /// _(internals)_ Get an iterator over all script-defined functions in the [`Module`]. @@ -2070,7 +2140,7 @@ impl Module { ast.shared_lib() .iter_fn() - .filter(|&f| match f.access { + .filter(|&f| match f.metadata.access { FnAccess::Public => true, FnAccess::Private => false, }) @@ -2108,7 +2178,7 @@ impl Module { #[inline(always)] #[must_use] pub fn contains_indexed_global_functions(&self) -> bool { - self.contains_indexed_global_functions + self.flags.contains(ModuleFlags::INDEXED_GLOBAL_FUNCTIONS) } /// Scan through all the sub-modules in the [`Module`] and build a hash index of all @@ -2127,7 +2197,7 @@ impl Module { let mut contains_indexed_global_functions = false; if let Some(ref modules) = module.modules { - for (name, m) in modules { + for (name, m) in modules.iter() { // Index all the sub-modules first. path.push(name); if index_module(m, path, variables, functions, type_iterators) { @@ -2139,7 +2209,7 @@ impl Module { // Index all variables if let Some(ref v) = module.variables { - for (var_name, value) in v { + for (var_name, value) in v.iter() { let hash_var = crate::calc_var_hash(path.iter().copied(), var_name); variables.insert(hash_var, value.clone()); } @@ -2147,14 +2217,14 @@ impl Module { // Index type iterators if let Some(ref t) = module.type_iterators { - for (&type_id, func) in t { + for (&type_id, func) in t.iter() { type_iterators.insert(type_id, func.clone()); } } // Index all Rust functions - for (&hash, f) in &module.functions { - match f.namespace { + for (&hash, f) in module.functions.iter().flat_map(|m| m.iter()) { + match f.metadata.namespace { FnNamespace::Global => { // Flatten all functions with global namespace functions.insert(hash, f.func.clone()); @@ -2162,18 +2232,24 @@ impl Module { } FnNamespace::Internal => (), } - match f.access { + match f.metadata.access { FnAccess::Public => (), FnAccess::Private => continue, // Do not index private functions } if !f.func.is_script() { - let hash_qualified_fn = - calc_native_fn_hash(path.iter().copied(), f.name.as_str(), &f.param_types); + let hash_qualified_fn = calc_native_fn_hash( + path.iter().copied(), + f.metadata.name.as_str(), + &f.metadata.param_types, + ); functions.insert(hash_qualified_fn, f.func.clone()); } else if cfg!(not(feature = "no_function")) { - let hash_qualified_script = - crate::calc_fn_hash(path.iter().copied(), &f.name, f.num_params); + let hash_qualified_script = crate::calc_fn_hash( + path.iter().copied(), + &f.metadata.name, + f.metadata.num_params, + ); functions.insert(hash_qualified_script, f.func.clone()); } } @@ -2181,15 +2257,21 @@ impl Module { contains_indexed_global_functions } - if !self.indexed { + if !self.is_indexed() { let mut path = Vec::with_capacity(4); - let mut variables = StraightHashMap::default(); - let mut functions = StraightHashMap::default(); + let mut variables = StraightHashMap::with_capacity_and_hasher( + self.variables.as_ref().map_or(0, |m| m.len()), + Default::default(), + ); + let mut functions = StraightHashMap::with_capacity_and_hasher( + self.functions.as_ref().map_or(0, |m| m.len()), + Default::default(), + ); let mut type_iterators = BTreeMap::new(); path.push(""); - self.contains_indexed_global_functions = index_module( + let r = index_module( self, &mut path, &mut variables, @@ -2197,23 +2279,27 @@ impl Module { &mut type_iterators, ); + if r { + self.flags |= ModuleFlags::INDEXED_GLOBAL_FUNCTIONS; + } + self.all_variables = if variables.is_empty() { None } else { - Some(variables) + Some(variables.into()) }; self.all_functions = if functions.is_empty() { None } else { - Some(functions) + Some(functions.into()) }; self.all_type_iterators = if type_iterators.is_empty() { None } else { - Some(type_iterators) + Some(type_iterators.into()) }; - self.indexed = true; + self.flags |= ModuleFlags::INDEXED; } self @@ -2257,13 +2343,13 @@ impl Module { func: impl Fn(Dynamic) -> Box>> + SendSync + 'static, ) -> &mut Self { let func = Shared::new(func); - if self.indexed { + if self.is_indexed() { self.all_type_iterators - .get_or_insert_with(|| Default::default()) + .get_or_insert_with(Default::default) .insert(type_id, func.clone()); } self.type_iterators - .get_or_insert_with(|| Default::default()) + .get_or_insert_with(Default::default) .insert(type_id, func); self } diff --git a/src/optimizer.rs b/src/optimizer.rs index 39ae36c0..4a3bc5ad 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -8,6 +8,7 @@ use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, use crate::eval::{Caches, GlobalRuntimeState}; use crate::func::builtin::get_builtin_binary_op_fn; use crate::func::hashing::get_hasher; +use crate::module::ModuleFlags; use crate::tokenizer::Token; use crate::types::dynamic::AccessMode; use crate::{ @@ -168,7 +169,7 @@ fn has_native_fn_override( if engine .global_modules .iter() - .filter(|m| !m.standard) + .filter(|m| !m.flags.contains(ModuleFlags::STANDARD_LIB)) .any(|m| m.contains_fn(hash)) { return true; @@ -178,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/arithmetic.rs b/src/packages/arithmetic.rs index 3dafc1ae..49fd6c1b 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -1,5 +1,6 @@ #![allow(non_snake_case)] +use crate::module::ModuleFlags; use crate::plugin::*; use crate::{def_package, Position, RhaiError, RhaiResultOf, ERR, INT}; #[cfg(feature = "no_std")] @@ -192,7 +193,7 @@ macro_rules! reg_functions { def_package! { /// Basic arithmetic package. pub ArithmeticPackage(lib) { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; combine_with_exported_module!(lib, "int", int_functions); reg_functions!(lib += signed_basic; INT); diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 098b7f7d..9309a2f6 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -2,6 +2,7 @@ use crate::engine::OP_EQUALS; use crate::eval::{calc_index, calc_offset_len}; +use crate::module::ModuleFlags; use crate::plugin::*; use crate::{ def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext, @@ -14,7 +15,7 @@ use std::{any::TypeId, cmp::Ordering, mem}; def_package! { /// Package of basic array utilities. pub BasicArrayPackage(lib) { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; combine_with_exported_module!(lib, "array", array_functions); @@ -229,6 +230,8 @@ pub mod array_functions { // Check if array will be over max size limit #[cfg(not(feature = "unchecked"))] { + use crate::types::dynamic::Union; + if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() { return Err( ERR::ErrorDataTooLarge("Size of array".to_string(), Position::NONE).into(), @@ -236,10 +239,10 @@ pub mod array_functions { } let check_sizes = match item.0 { - crate::types::dynamic::Union::Str(..) => true, - crate::types::dynamic::Union::Array(..) => true, + Union::Str(..) => true, + Union::Array(..) => true, #[cfg(not(feature = "no_object"))] - crate::types::dynamic::Union::Map(..) => true, + Union::Map(..) => true, _ => false, }; @@ -247,8 +250,8 @@ pub mod array_functions { let mut arr_len = array.len(); let mut arr = Dynamic::from_array(mem::take(array)); - let (mut a1, mut m1, mut s1) = crate::Engine::calc_data_sizes(&arr, true); - let (a2, m2, s2) = crate::Engine::calc_data_sizes(&item, true); + let (mut a1, mut m1, mut s1) = arr.calc_data_sizes(true); + let (a2, m2, s2) = item.calc_data_sizes(true); { let mut guard = arr.write_lock::().unwrap(); diff --git a/src/packages/bit_field.rs b/src/packages/bit_field.rs index 5deb09ce..9da781cf 100644 --- a/src/packages/bit_field.rs +++ b/src/packages/bit_field.rs @@ -1,4 +1,5 @@ use crate::eval::calc_index; +use crate::module::ModuleFlags; use crate::plugin::*; use crate::{ def_package, ExclusiveRange, InclusiveRange, Position, RhaiResultOf, ERR, INT, INT_BITS, @@ -10,7 +11,7 @@ use std::prelude::v1::*; def_package! { /// Package of basic bit-field utilities. pub BitFieldPackage(lib) { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; combine_with_exported_module!(lib, "bit_field", bit_field_functions); } @@ -142,7 +143,7 @@ mod bit_field_functions { } // 2^bits - 1 - let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as crate::INT; + let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as INT; Ok(((value & (mask << bit)) >> bit) & mask) } @@ -229,7 +230,7 @@ mod bit_field_functions { } // 2^bits - 1 - let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as crate::INT; + let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as INT; *value &= !(mask << bit); *value |= (new_value & mask) << bit; diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index 2e51b108..e8b0b9e8 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -1,10 +1,11 @@ #![cfg(not(feature = "no_index"))] use crate::eval::{calc_index, calc_offset_len}; +use crate::module::ModuleFlags; use crate::plugin::*; use crate::{ def_package, Array, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, Position, - RhaiResultOf, INT, INT_BYTES, MAX_USIZE_INT, + RhaiResultOf, ERR, INT, INT_BYTES, MAX_USIZE_INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -16,7 +17,7 @@ use crate::{FLOAT, FLOAT_BYTES}; def_package! { /// Package of basic BLOB utilities. pub BasicBlobPackage(lib) { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; combine_with_exported_module!(lib, "blob", blob_functions); combine_with_exported_module!(lib, "parse_int", parse_int_functions); @@ -362,9 +363,7 @@ pub mod blob_functions { // Check if blob will be over max size limit if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() { - return Err( - crate::ERR::ErrorDataTooLarge("Size of BLOB".to_string(), Position::NONE).into(), - ); + return Err(ERR::ErrorDataTooLarge("Size of BLOB".to_string(), Position::NONE).into()); } if len > blob.len() { diff --git a/src/packages/debugging.rs b/src/packages/debugging.rs index 4e3cbc35..c6b2a87e 100644 --- a/src/packages/debugging.rs +++ b/src/packages/debugging.rs @@ -1,6 +1,7 @@ #![cfg(feature = "debugging")] use crate::def_package; +use crate::module::ModuleFlags; use crate::plugin::*; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -17,7 +18,7 @@ use crate::Map; def_package! { /// Package of basic debugging utilities. pub DebuggingPackage(lib) { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; combine_with_exported_module!(lib, "debugging", debugging_functions); } @@ -33,47 +34,57 @@ mod debugging_functions { #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_index"))] pub fn back_trace(ctx: NativeCallContext) -> Array { - ctx.global_runtime_state() - .debugger - .call_stack() - .iter() - .rev() - .filter(|crate::debugger::CallStackFrame { fn_name, args, .. }| { - fn_name.as_str() != "back_trace" || !args.is_empty() - }) - .map( - |frame @ crate::debugger::CallStackFrame { - fn_name: _fn_name, - args: _args, - source: _source, - pos: _pos, - }| { - let display = frame.to_string(); + use crate::debugger::CallStackFrame; - #[cfg(not(feature = "no_object"))] - { - let mut map = Map::new(); - map.insert("display".into(), display.into()); - map.insert("fn_name".into(), _fn_name.into()); - if !_args.is_empty() { - map.insert("args".into(), Dynamic::from_array(_args.clone().to_vec())); + if let Some(ref debugger) = ctx.global_runtime_state().debugger { + debugger + .call_stack() + .iter() + .rev() + .filter(|CallStackFrame { fn_name, args, .. }| { + fn_name.as_str() != "back_trace" || !args.is_empty() + }) + .map( + |frame @ CallStackFrame { + fn_name: _fn_name, + args: _args, + source: _source, + pos: _pos, + }| { + let display = frame.to_string(); + + #[cfg(not(feature = "no_object"))] + { + use crate::INT; + + let mut map = Map::new(); + map.insert("display".into(), display.into()); + map.insert("fn_name".into(), _fn_name.into()); + if !_args.is_empty() { + map.insert( + "args".into(), + Dynamic::from_array(_args.clone().to_vec()), + ); + } + if let Some(source) = _source { + map.insert("source".into(), source.into()); + } + if !_pos.is_none() { + map.insert("line".into(), (_pos.line().unwrap() as INT).into()); + map.insert( + "position".into(), + (_pos.position().unwrap_or(0) as INT).into(), + ); + } + Dynamic::from_map(map) } - if let Some(source) = _source { - map.insert("source".into(), source.into()); - } - if !_pos.is_none() { - map.insert("line".into(), (_pos.line().unwrap() as crate::INT).into()); - map.insert( - "position".into(), - (_pos.position().unwrap_or(0) as crate::INT).into(), - ); - } - Dynamic::from_map(map) - } - #[cfg(feature = "no_object")] - display.into() - }, - ) - .collect() + #[cfg(feature = "no_object")] + display.into() + }, + ) + .collect() + } else { + Array::new() + } } } diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 130646c3..5e2bfcb4 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -1,3 +1,4 @@ +use crate::module::ModuleFlags; use crate::plugin::*; use crate::{def_package, FnPtr, ImmutableString, NativeCallContext}; #[cfg(feature = "no_std")] @@ -6,7 +7,7 @@ use std::prelude::v1::*; def_package! { /// Package of basic function pointer utilities. pub BasicFnPackage(lib) { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions); } diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index f7985004..b7a60dd5 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -1,7 +1,8 @@ use crate::eval::calc_index; +use crate::module::ModuleFlags; use crate::plugin::*; use crate::{ - def_package, ExclusiveRange, InclusiveRange, RhaiResultOf, INT, INT_BITS, MAX_USIZE_INT, + def_package, ExclusiveRange, InclusiveRange, RhaiResultOf, ERR, INT, INT_BITS, MAX_USIZE_INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -65,14 +66,11 @@ impl StepRange { if let Some(n) = add(from, step) { #[cfg(not(feature = "unchecked"))] if n == from { - return Err(crate::ERR::ErrorInFunctionCall( + return Err(ERR::ErrorInFunctionCall( "range".to_string(), String::new(), - crate::ERR::ErrorArithmetic( - "step value cannot be zero".to_string(), - Position::NONE, - ) - .into(), + ERR::ErrorArithmetic("step value cannot be zero".to_string(), Position::NONE) + .into(), Position::NONE, ) .into()); @@ -127,7 +125,7 @@ pub struct BitRange(INT, usize); impl BitRange { pub fn new(value: INT, from: INT, len: INT) -> RhaiResultOf { let from = calc_index(INT_BITS, from, true, || { - crate::ERR::ErrorBitFieldBounds(INT_BITS, from, Position::NONE).into() + ERR::ErrorBitFieldBounds(INT_BITS, from, Position::NONE).into() })?; let len = if len < 0 { @@ -329,7 +327,7 @@ macro_rules! reg_range { def_package! { /// Package of basic range iterators pub BasicIteratorPackage(lib) { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; reg_range!(lib | "range" => INT); diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index d423691d..717691d9 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -1,14 +1,18 @@ use crate::def_package; +use crate::module::ModuleFlags; use crate::plugin::*; use crate::types::dynamic::Tag; use crate::{Dynamic, RhaiResultOf, ERR, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +#[cfg(not(feature = "no_float"))] +use crate::FLOAT; + def_package! { /// Package of core language features. pub LanguageCorePackage(lib) { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; combine_with_exported_module!(lib, "core", core_functions); @@ -75,10 +79,17 @@ mod core_functions { } /// Block the current thread for a particular number of `seconds`. + /// + /// # Example + /// + /// ```rhai + /// // Do nothing for 10 seconds! + /// sleep(10.0); + /// ``` #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_std"))] #[rhai_fn(name = "sleep")] - pub fn sleep_float(seconds: crate::FLOAT) { + pub fn sleep_float(seconds: FLOAT) { if seconds <= 0.0 { return; } @@ -89,6 +100,13 @@ mod core_functions { std::thread::sleep(std::time::Duration::from_secs_f32(seconds)); } /// Block the current thread for a particular number of `seconds`. + /// + /// # Example + /// + /// ```rhai + /// // Do nothing for 10 seconds! + /// sleep(10); + /// ``` #[cfg(not(feature = "no_std"))] pub fn sleep(seconds: INT) { if seconds <= 0 { @@ -120,17 +138,24 @@ mod core_functions { #[cfg(not(feature = "no_object"))] #[export_module] mod reflection_functions { - pub fn get_fn_metadata_list(ctx: NativeCallContext) -> crate::Array { + use crate::Array; + + /// Return an array of object maps containing metadata of all script-defined functions. + pub fn get_fn_metadata_list(ctx: NativeCallContext) -> Array { collect_fn_metadata(ctx, |_, _, _, _, _| true) } + /// Return an array of object maps containing metadata of all script-defined functions + /// matching the specified name. #[rhai_fn(name = "get_fn_metadata_list")] - pub fn get_fn_metadata(ctx: NativeCallContext, name: &str) -> crate::Array { + pub fn get_fn_metadata(ctx: NativeCallContext, name: &str) -> Array { collect_fn_metadata(ctx, |_, _, n, _, _| n == name) } + /// Return an array of object maps containing metadata of all script-defined functions + /// matching the specified name and arity (number of parameters). #[rhai_fn(name = "get_fn_metadata_list")] - pub fn get_fn_metadata2(ctx: NativeCallContext, name: &str, params: INT) -> crate::Array { + pub fn get_fn_metadata2(ctx: NativeCallContext, name: &str, params: INT) -> Array { if params < 0 || params > crate::MAX_USIZE_INT { - crate::Array::new() + Array::new() } else { collect_fn_metadata(ctx, |_, _, n, p, _| p == (params as usize) && n == name) } @@ -145,38 +170,47 @@ fn collect_fn_metadata( filter: impl Fn(FnNamespace, FnAccess, &str, usize, &crate::Shared) -> bool + Copy, ) -> crate::Array { - use crate::{ast::ScriptFnDef, Array, Map}; + #[cfg(not(feature = "no_module"))] + use crate::Identifier; + use crate::{ast::ScriptFnDef, engine::FN_ANONYMOUS, Array, Map}; // Create a metadata record for a function. fn make_metadata( - dict: &mut crate::types::StringsInterner, - #[cfg(not(feature = "no_module"))] namespace: crate::Identifier, + engine: &Engine, + #[cfg(not(feature = "no_module"))] namespace: Identifier, func: &ScriptFnDef, ) -> Map { let mut map = Map::new(); #[cfg(not(feature = "no_module"))] if !namespace.is_empty() { - map.insert("namespace".into(), dict.get(namespace).into()); + map.insert( + "namespace".into(), + engine.get_interned_string(namespace).into(), + ); } - map.insert("name".into(), dict.get(func.name.as_str()).into()); + map.insert( + "name".into(), + engine.get_interned_string(func.name.clone()).into(), + ); map.insert( "access".into(), - dict.get(match func.access { - FnAccess::Public => "public", - FnAccess::Private => "private", - }) - .into(), + engine + .get_interned_string(match func.access { + FnAccess::Public => "public", + FnAccess::Private => "private", + }) + .into(), ); map.insert( "is_anonymous".into(), - func.name.starts_with(crate::engine::FN_ANONYMOUS).into(), + func.name.starts_with(FN_ANONYMOUS).into(), ); map.insert( "params".into(), func.params .iter() - .map(|p| dict.get(p.as_str()).into()) + .map(|p| engine.get_interned_string(p.clone()).into()) .collect::() .into(), ); @@ -186,7 +220,7 @@ fn collect_fn_metadata( "comments".into(), func.comments .iter() - .map(|s| dict.get(s.as_ref()).into()) + .map(|s| engine.get_interned_string(s.as_ref()).into()) .collect::() .into(), ); @@ -195,7 +229,7 @@ fn collect_fn_metadata( map } - let dict = &mut crate::types::StringsInterner::new(); + let engine = ctx.engine(); let mut list = Array::new(); ctx.iter_namespaces() @@ -204,9 +238,9 @@ fn collect_fn_metadata( .for_each(|(.., f)| { list.push( make_metadata( - dict, + engine, #[cfg(not(feature = "no_module"))] - crate::Identifier::new_const(), + Identifier::new_const(), f, ) .into(), @@ -221,9 +255,9 @@ fn collect_fn_metadata( .for_each(|(.., f)| { list.push( make_metadata( - dict, + engine, #[cfg(not(feature = "no_module"))] - crate::Identifier::new_const(), + Identifier::new_const(), f, ) .into(), @@ -233,15 +267,16 @@ 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)| { list.push( make_metadata( - dict, + engine, #[cfg(not(feature = "no_module"))] - crate::Identifier::new_const(), + Identifier::new_const(), f, ) .into(), @@ -250,36 +285,31 @@ fn collect_fn_metadata( #[cfg(not(feature = "no_module"))] { + use crate::{tokenizer::Token::DoubleColon, Shared, SmartString}; + // Recursively scan modules for script-defined functions. fn scan_module( - dict: &mut crate::types::StringsInterner, + engine: &Engine, list: &mut Array, namespace: &str, module: &Module, - filter: impl Fn( - FnNamespace, - FnAccess, - &str, - usize, - &crate::Shared, - ) -> bool - + Copy, + filter: impl Fn(FnNamespace, FnAccess, &str, usize, &Shared) -> bool + Copy, ) { module .iter_script_fn() .filter(|(s, a, n, p, f)| filter(*s, *a, n, *p, f)) - .for_each(|(.., f)| list.push(make_metadata(dict, namespace.into(), f).into())); - for (ns, m) in module.iter_sub_modules() { - let ns = format!( - "{namespace}{}{ns}", - crate::tokenizer::Token::DoubleColon.literal_syntax() - ); - scan_module(dict, list, &ns, &**m, filter); + .for_each(|(.., f)| list.push(make_metadata(engine, namespace.into(), f).into())); + for (name, m) in module.iter_sub_modules() { + use std::fmt::Write; + + let mut ns = SmartString::new_const(); + write!(&mut ns, "{namespace}{}{name}", DoubleColon.literal_syntax()).unwrap(); + scan_module(engine, list, &ns, m, filter); } } for (ns, m) in ctx.iter_imports_raw() { - scan_module(dict, &mut list, ns, &**m, filter); + scan_module(engine, &mut list, ns, m, filter); } } diff --git a/src/packages/logic.rs b/src/packages/logic.rs index af494f00..19e2fdde 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -1,4 +1,5 @@ use crate::def_package; +use crate::module::ModuleFlags; use crate::plugin::*; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -38,7 +39,7 @@ macro_rules! reg_functions { def_package! { /// Package of basic logic operators. pub LogicPackage(lib) { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] @@ -92,6 +93,7 @@ mod logic_functions { } #[cfg(not(feature = "no_float"))] +#[allow(clippy::cast_precision_loss)] #[export_module] mod f32_functions { use crate::INT; @@ -147,6 +149,7 @@ mod f32_functions { } #[cfg(not(feature = "no_float"))] +#[allow(clippy::cast_precision_loss)] #[export_module] mod f64_functions { use crate::INT; diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index e5174c1b..8851efb5 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -1,6 +1,7 @@ #![cfg(not(feature = "no_object"))] use crate::engine::OP_EQUALS; +use crate::module::ModuleFlags; use crate::plugin::*; use crate::{def_package, Dynamic, ImmutableString, Map, NativeCallContext, RhaiResultOf, INT}; #[cfg(feature = "no_std")] @@ -12,7 +13,7 @@ use crate::Array; def_package! { /// Package of basic object map utilities. pub BasicMapPackage(lib) { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; combine_with_exported_module!(lib, "map", map_functions); } diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index f7c3c40f..4c54f570 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -1,5 +1,6 @@ #![allow(non_snake_case)] +use crate::module::ModuleFlags; use crate::plugin::*; use crate::{def_package, Position, RhaiResultOf, ERR, INT}; #[cfg(feature = "no_std")] @@ -54,7 +55,7 @@ macro_rules! reg_functions { def_package! { /// Basic mathematical package. pub BasicMathPackage(lib) { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; // Integer functions combine_with_exported_module!(lib, "int", int_functions); @@ -144,6 +145,7 @@ mod int_functions { ); } + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] INT::from_str_radix(string.trim(), radix as u32).map_err(|err| { ERR::ErrorArithmetic( format!("Error parsing integer number '{string}': {err}"), @@ -157,8 +159,6 @@ mod int_functions { #[cfg(not(feature = "no_float"))] #[export_module] mod trig_functions { - use crate::FLOAT; - /// Return the sine of the floating-point number in radians. pub fn sin(x: FLOAT) -> FLOAT { x.sin() @@ -221,8 +221,6 @@ mod trig_functions { #[cfg(not(feature = "no_float"))] #[export_module] mod float_functions { - use crate::FLOAT; - /// Return the natural number _e_. #[rhai_fn(name = "E")] pub fn e() -> FLOAT { @@ -312,6 +310,7 @@ mod float_functions { /// Convert the floating-point number into an integer. #[rhai_fn(name = "to_int", return_raw)] pub fn f32_to_int(x: f32) -> RhaiResultOf { + #[allow(clippy::cast_precision_loss)] if cfg!(not(feature = "unchecked")) && (x > (INT::MAX as f32) || x < (INT::MIN as f32)) { Err( ERR::ErrorArithmetic(format!("Integer overflow: to_int({x})"), Position::NONE) @@ -324,6 +323,7 @@ mod float_functions { /// Convert the floating-point number into an integer. #[rhai_fn(name = "to_int", return_raw)] pub fn f64_to_int(x: f64) -> RhaiResultOf { + #[allow(clippy::cast_precision_loss)] if cfg!(not(feature = "unchecked")) && (x > (INT::MAX as f64) || x < (INT::MIN as f64)) { Err( ERR::ErrorArithmetic(format!("Integer overflow: to_int({x})"), Position::NONE) @@ -356,7 +356,7 @@ mod float_functions { #[cfg(not(feature = "f32_float"))] #[rhai_fn(name = "to_float")] pub fn f32_to_f64(x: f32) -> f64 { - x as f64 + x.into() } } @@ -477,6 +477,7 @@ mod decimal_functions { } } + #[allow(clippy::cast_sign_loss)] Ok(x.round_dp(digits as u32)) } /// Round the decimal number to the specified number of `digits` after the decimal point and return it. @@ -494,6 +495,7 @@ mod decimal_functions { } } + #[allow(clippy::cast_sign_loss)] Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::AwayFromZero)) } /// Round the decimal number to the specified number of `digits` after the decimal point and return it. @@ -511,6 +513,7 @@ mod decimal_functions { } } + #[allow(clippy::cast_sign_loss)] Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::ToZero)) } /// Round the decimal number to the specified number of `digits` after the decimal point and return it. @@ -528,6 +531,7 @@ mod decimal_functions { } } + #[allow(clippy::cast_sign_loss)] Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::MidpointAwayFromZero)) } /// Round the decimal number to the specified number of `digits` after the decimal point and return it. @@ -545,6 +549,7 @@ mod decimal_functions { } } + #[allow(clippy::cast_sign_loss)] Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::MidpointTowardZero)) } /// Convert the decimal number into an integer. @@ -562,14 +567,15 @@ mod decimal_functions { return Some(n); }); - match n { - Some(n) => Ok(n), - _ => Err(ERR::ErrorArithmetic( - format!("Integer overflow: to_int({x})"), - Position::NONE, - ) - .into()), - } + n.map_or_else( + || { + Err( + ERR::ErrorArithmetic(format!("Integer overflow: to_int({x})"), Position::NONE) + .into(), + ) + }, + Ok, + ) } /// Return the integral part of the decimal number. #[rhai_fn(name = "int", get = "int")] diff --git a/src/packages/pkg_core.rs b/src/packages/pkg_core.rs index 547274cd..0bd2cb19 100644 --- a/src/packages/pkg_core.rs +++ b/src/packages/pkg_core.rs @@ -3,6 +3,7 @@ use std::prelude::v1::*; use super::*; use crate::def_package; +use crate::module::ModuleFlags; def_package! { /// Core package containing basic facilities. @@ -23,6 +24,6 @@ def_package! { BasicFnPackage, #[cfg(feature = "debugging")] DebuggingPackage { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; } } diff --git a/src/packages/pkg_std.rs b/src/packages/pkg_std.rs index b054b3ae..b52b0589 100644 --- a/src/packages/pkg_std.rs +++ b/src/packages/pkg_std.rs @@ -3,6 +3,7 @@ use std::prelude::v1::*; use super::*; use crate::def_package; +use crate::module::ModuleFlags; def_package! { /// Standard package containing all built-in features. @@ -29,6 +30,6 @@ def_package! { #[cfg(not(feature = "no_time"))] BasicTimePackage, MoreStringPackage { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; } } diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 63218d2b..686951f7 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -1,7 +1,8 @@ +use crate::module::ModuleFlags; use crate::plugin::*; -use crate::{def_package, FnPtr, INT}; +use crate::{def_package, FnPtr, ImmutableString, SmartString, INT}; use std::any::TypeId; -use std::fmt::{Binary, LowerHex, Octal}; +use std::fmt::{Binary, LowerHex, Octal, Write}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -17,7 +18,7 @@ pub const FUNC_TO_DEBUG: &str = "to_debug"; def_package! { /// Package of basic string utilities (e.g. printing) pub BasicStringPackage(lib) { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; combine_with_exported_module!(lib, "print_debug", print_debug_functions); combine_with_exported_module!(lib, "number_formatting", number_formatting); @@ -25,7 +26,7 @@ def_package! { // Register characters iterator #[cfg(not(feature = "no_index"))] lib.set_iter(TypeId::of::(), |value| Box::new( - value.cast::().chars().map(Into::into).collect::().into_iter() + value.cast::().chars().map(Into::into).collect::().into_iter() )); } } @@ -37,7 +38,7 @@ pub fn print_with_func( fn_name: &str, ctx: &NativeCallContext, value: &mut Dynamic, -) -> crate::ImmutableString { +) -> ImmutableString { match ctx.call_native_fn_raw(fn_name, true, &mut [value]) { Ok(result) if result.is_string() => { result.into_immutable_string().expect("`ImmutableString`") @@ -49,8 +50,6 @@ pub fn print_with_func( #[export_module] mod print_debug_functions { - use crate::ImmutableString; - /// Convert the value of the `item` into a string. #[rhai_fn(name = "print", pure)] pub fn print_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { @@ -69,7 +68,9 @@ mod print_debug_functions { /// Convert the value of the `item` into a string in debug format. #[rhai_fn(name = "to_debug", pure)] pub fn to_debug_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { - ctx.engine().map_type_name(&format!("{item:?}")).into() + let mut buf = SmartString::new_const(); + write!(&mut buf, "{item:?}").unwrap(); + ctx.engine().map_type_name(&buf).into() } /// Return the empty string. @@ -86,7 +87,9 @@ mod print_debug_functions { /// Convert the string into debug format. #[rhai_fn(name = "debug", name = "to_debug", pure)] pub fn debug_string(string: &mut ImmutableString) -> ImmutableString { - format!("{string:?}").into() + let mut buf = SmartString::new_const(); + write!(&mut buf, "{string:?}").unwrap(); + buf.into() } /// Return the character into a string. @@ -97,7 +100,9 @@ mod print_debug_functions { /// Convert the string into debug format. #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_char(character: char) -> ImmutableString { - format!("{character:?}").into() + let mut buf = SmartString::new_const(); + buf.push(character); + buf.into() } /// Convert the function pointer into a string in debug format. @@ -114,7 +119,9 @@ mod print_debug_functions { /// Convert the boolean value into a string in debug format. #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_bool(value: bool) -> ImmutableString { - format!("{value:?}").into() + let mut buf = SmartString::new_const(); + write!(&mut buf, "{value:?}").unwrap(); + buf.into() } /// Return the empty string. @@ -147,14 +154,18 @@ mod print_debug_functions { #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_f64(number: f64) -> ImmutableString { let number = crate::types::FloatWrapper::new(number); - format!("{number:?}").into() + let mut buf = SmartString::new_const(); + write!(&mut buf, "{number:?}").unwrap(); + buf.into() } /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_f32(number: f32) -> ImmutableString { let number = crate::types::FloatWrapper::new(number); - format!("{number:?}").into() + let mut buf = SmartString::new_const(); + write!(&mut buf, "{number:?}").unwrap(); + buf.into() } /// Convert the array into a string. @@ -217,13 +228,19 @@ mod print_debug_functions { #[export_module] mod number_formatting { fn to_hex(value: T) -> ImmutableString { - format!("{value:x}").into() + let mut buf = SmartString::new_const(); + write!(&mut buf, "{value:x}").unwrap(); + buf.into() } fn to_octal(value: T) -> ImmutableString { - format!("{value:o}").into() + let mut buf = SmartString::new_const(); + write!(&mut buf, "{value:o}").unwrap(); + buf.into() } fn to_binary(value: T) -> ImmutableString { - format!("{value:b}").into() + let mut buf = SmartString::new_const(); + write!(&mut buf, "{value:b}").unwrap(); + buf.into() } /// Convert the `value` into a string in hex format. diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index bc39f8cb..70e95363 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,7 +1,8 @@ +use crate::module::ModuleFlags; use crate::plugin::*; use crate::{ - def_package, Dynamic, ExclusiveRange, InclusiveRange, RhaiResultOf, StaticVec, INT, - MAX_USIZE_INT, + def_package, Dynamic, ExclusiveRange, ImmutableString, InclusiveRange, Position, RhaiResultOf, + SmartString, StaticVec, ERR, INT, MAX_USIZE_INT, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -12,7 +13,7 @@ use super::string_basic::{print_with_func, FUNC_TO_STRING}; def_package! { /// Package of additional string utilities over [`BasicStringPackage`][super::BasicStringPackage] pub MoreStringPackage(lib) { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; combine_with_exported_module!(lib, "string", string_functions); } @@ -20,8 +21,6 @@ def_package! { #[export_module] mod string_functions { - use crate::{ImmutableString, SmartString}; - #[rhai_fn(name = "+", pure)] pub fn add_append( ctx: NativeCallContext, @@ -33,7 +32,9 @@ mod string_functions { if s.is_empty() { string.clone() } else { - format!("{string}{s}").into() + let mut buf = SmartString::from(string.as_str()); + buf.push_str(&s); + buf.into() } } #[rhai_fn(name = "+=", name = "append")] @@ -41,7 +42,9 @@ mod string_functions { let s = print_with_func(FUNC_TO_STRING, &ctx, &mut item); if !s.is_empty() { - *string = format!("{string}{s}").into(); + let mut buf = SmartString::from(string.as_str()); + buf.push_str(&s); + *string = buf.into(); } } #[rhai_fn(name = "+", pure)] @@ -74,7 +77,10 @@ mod string_functions { } #[rhai_fn(name = "+")] pub fn add_prepend_char(character: char, string: &str) -> ImmutableString { - format!("{character}{string}").into() + let mut buf = SmartString::new_const(); + buf.push(character); + buf.push_str(string); + buf.into() } #[rhai_fn(name = "+")] @@ -127,7 +133,7 @@ mod string_functions { } #[rhai_fn(name = "+=", name = "append")] pub fn add(string: &mut ImmutableString, utf8: Blob) { - let mut s = crate::SmartString::from(string.as_str()); + let mut s = SmartString::from(string.as_str()); if !utf8.is_empty() { s.push_str(&String::from_utf8_lossy(&utf8)); *string = s.into(); @@ -147,6 +153,25 @@ mod string_functions { s.into() } + + /// Convert the string into an UTF-8 encoded byte-stream as a BLOB. + /// + /// # Example + /// + /// ```rhai + /// let text = "朝には紅顔ありて夕べには白骨となる"; + /// + /// let bytes = text.to_blob(); + /// + /// print(bytes.len()); // prints 51 + /// ``` + pub fn to_blob(string: &str) -> Blob { + if string.is_empty() { + Blob::new() + } else { + string.as_bytes().into() + } + } } /// Return the length of the string, in number of characters. @@ -188,25 +213,6 @@ mod string_functions { string.len() as INT } } - /// Convert the string into an UTF-8 encoded byte-stream as a BLOB. - /// - /// # Example - /// - /// ```rhai - /// let text = "朝には紅顔ありて夕べには白骨となる"; - /// - /// let bytes = text.to_blob(); - /// - /// print(bytes.len()); // prints 51 - /// ``` - #[cfg(not(feature = "no_index"))] - pub fn to_blob(string: &str) -> crate::Blob { - if string.is_empty() { - crate::Blob::new() - } else { - string.as_bytes().into() - } - } /// Remove all occurrences of a sub-string from the string. /// /// # Example @@ -239,9 +245,10 @@ mod string_functions { /// Clear the string, making it empty. pub fn clear(string: &mut ImmutableString) { if !string.is_empty() { - match string.get_mut() { - Some(s) => s.clear(), - _ => *string = ImmutableString::new(), + if let Some(s) = string.get_mut() { + s.clear() + } else { + *string = ImmutableString::new() } } } @@ -265,6 +272,7 @@ mod string_functions { /// ``` pub fn truncate(string: &mut ImmutableString, len: INT) { if len > 0 { + #[allow(clippy::cast_sign_loss)] let len = len.min(MAX_USIZE_INT) as usize; let chars: StaticVec<_> = string.chars().collect(); let copy = string.make_mut(); @@ -286,20 +294,17 @@ mod string_functions { /// print(text); // prints "hello" /// ``` pub fn trim(string: &mut ImmutableString) { - match string.get_mut() { - Some(s) => { - let trimmed = s.trim(); + if let Some(s) = string.get_mut() { + let trimmed = s.trim(); - if trimmed != s { - *s = trimmed.into(); - } + if trimmed != s { + *s = trimmed.into(); } - None => { - let trimmed = string.trim(); + } else { + let trimmed = string.trim(); - if trimmed != string { - *string = trimmed.into(); - } + if trimmed != string { + *string = trimmed.into(); } } } @@ -536,7 +541,7 @@ mod string_functions { /// ``` #[rhai_fn(name = "contains")] pub fn contains_char(string: &str, character: char) -> bool { - string.contains(character).into() + string.contains(character) } /// Return `true` if the string starts with a specified string. @@ -1219,11 +1224,9 @@ mod string_functions { // Check if string will be over max size limit if _ctx.engine().max_string_size() > 0 && len > _ctx.engine().max_string_size() { - return Err(crate::ERR::ErrorDataTooLarge( - "Length of string".to_string(), - crate::Position::NONE, - ) - .into()); + return Err( + ERR::ErrorDataTooLarge("Length of string".to_string(), Position::NONE).into(), + ); } let orig_len = string.chars().count(); @@ -1237,11 +1240,9 @@ mod string_functions { if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size() { - return Err(crate::ERR::ErrorDataTooLarge( - "Length of string".to_string(), - crate::Position::NONE, - ) - .into()); + return Err( + ERR::ErrorDataTooLarge("Length of string".to_string(), Position::NONE).into(), + ); } } @@ -1279,11 +1280,9 @@ mod string_functions { // Check if string will be over max size limit if _ctx.engine().max_string_size() > 0 && len > _ctx.engine().max_string_size() { - return Err(crate::ERR::ErrorDataTooLarge( - "Length of string".to_string(), - crate::Position::NONE, - ) - .into()); + return Err( + ERR::ErrorDataTooLarge("Length of string".to_string(), Position::NONE).into(), + ); } let mut str_len = string.chars().count(); @@ -1304,11 +1303,9 @@ mod string_functions { if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size() { - return Err(crate::ERR::ErrorDataTooLarge( - "Length of string".to_string(), - crate::Position::NONE, - ) - .into()); + return Err( + ERR::ErrorDataTooLarge("Length of string".to_string(), Position::NONE).into(), + ); } } @@ -1317,7 +1314,7 @@ mod string_functions { #[cfg(not(feature = "no_index"))] pub mod arrays { - use crate::{Array, ImmutableString}; + use crate::Array; /// Split the string into two at the specified `index` position and return it both strings /// as an array. diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 59e198b8..2ea495c2 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -1,6 +1,7 @@ #![cfg(not(feature = "no_time"))] use super::arithmetic::make_err as make_arithmetic_err; +use crate::module::ModuleFlags; use crate::plugin::*; use crate::{def_package, Dynamic, RhaiResult, RhaiResultOf, INT}; @@ -16,7 +17,7 @@ use instant::{Duration, Instant}; def_package! { /// Package of basic timing utilities. pub BasicTimePackage(lib) { - lib.standard = true; + lib.flags |= ModuleFlags::STANDARD_LIB; // Register date/time functions combine_with_exported_module!(lib, "time", time_functions); diff --git a/src/parser.rs b/src/parser.rs index 0ee1d9f3..46091e0e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -17,10 +17,11 @@ use crate::tokenizer::{ use crate::types::dynamic::AccessMode; use crate::types::StringsInterner; use crate::{ - calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, Identifier, - ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position, Scope, - Shared, SmartString, StaticVec, AST, INT, PERR, + calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, FnArgsVec, + Identifier, ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position, + Scope, Shared, SmartString, StaticVec, AST, INT, PERR, }; +use bitflags::bitflags; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ @@ -45,24 +46,24 @@ const SMALL_SWITCH_RANGE: INT = 16; /// _(internals)_ A type that encapsulates the current state of the parser. /// Exported under the `internals` feature only. -pub struct ParseState<'e> { +pub struct ParseState<'e, 's> { /// Input stream buffer containing the next character to read. pub tokenizer_control: TokenizerControl, /// Controls whether parsing of an expression should stop given the next token. pub expr_filter: fn(&Token) -> bool, - /// String interners. - interned_strings: StringsInterner<'e>, + /// Strings interner. + pub interned_strings: &'s mut StringsInterner, /// External [scope][Scope] with constants. pub scope: &'e Scope<'e>, /// Global runtime state. - pub global: GlobalRuntimeState, + pub global: Option>, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. - pub stack: Scope<'e>, + pub stack: Option>>, /// Size of the local variables stack upon entry of the current block scope. pub block_stack_len: usize, /// Tracks a list of external variables (variables that are not explicitly declared in the scope). #[cfg(not(feature = "no_closure"))] - pub external_vars: Vec, + pub external_vars: Option>>, /// An indicator that disables variable capturing into externals one single time /// up until the nearest consumed Identifier token. /// If set to false the next call to [`access_var`][ParseState::access_var] will not capture the variable. @@ -71,16 +72,13 @@ pub struct ParseState<'e> { pub allow_capture: bool, /// Encapsulates a local stack with imported [module][crate::Module] names. #[cfg(not(feature = "no_module"))] - pub imports: StaticVec, + pub imports: Option>>, /// List of globally-imported [module][crate::Module] names. #[cfg(not(feature = "no_module"))] - pub global_imports: StaticVec, - /// Maximum levels of expression nesting (0 for unlimited). - #[cfg(not(feature = "unchecked"))] - pub max_expr_depth: usize, + pub global_imports: Option>>, } -impl fmt::Debug for ParseState<'_> { +impl fmt::Debug for ParseState<'_, '_> { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -98,40 +96,35 @@ impl fmt::Debug for ParseState<'_> { #[cfg(not(feature = "no_module"))] f.field("imports", &self.imports) .field("global_imports", &self.global_imports); - #[cfg(not(feature = "unchecked"))] - f.field("max_expr_depth", &self.max_expr_depth); f.finish() } } -impl<'e> ParseState<'e> { +impl<'e, 's> ParseState<'e, 's> { /// Create a new [`ParseState`]. #[inline] #[must_use] pub fn new( - engine: &Engine, scope: &'e Scope, - interned_strings: StringsInterner<'e>, + interned_strings: &'s mut StringsInterner, tokenizer_control: TokenizerControl, ) -> Self { Self { tokenizer_control, expr_filter: |_| true, #[cfg(not(feature = "no_closure"))] - external_vars: Vec::new(), + external_vars: None, #[cfg(not(feature = "no_closure"))] allow_capture: true, interned_strings, scope, - global: GlobalRuntimeState::new(engine), - stack: Scope::new(), + global: None, + stack: None, block_stack_len: 0, #[cfg(not(feature = "no_module"))] - imports: StaticVec::new_const(), + imports: None, #[cfg(not(feature = "no_module"))] - global_imports: StaticVec::new_const(), - #[cfg(not(feature = "unchecked"))] - max_expr_depth: engine.max_expr_depth(), + global_imports: None, } } @@ -149,7 +142,8 @@ impl<'e> ParseState<'e> { ( self.stack - .iter_rev_raw() + .iter() + .flat_map(|s| s.iter_rev_raw()) .enumerate() .find(|&(.., (n, ..))| { if n == SCOPE_SEARCH_BARRIER_MARKER { @@ -198,12 +192,20 @@ impl<'e> ParseState<'e> { #[cfg(not(feature = "no_closure"))] if self.allow_capture { - if !is_func_name && index == 0 && !self.external_vars.iter().any(|v| v.as_str() == name) + if !is_func_name + && index == 0 + && !self + .external_vars + .iter() + .flat_map(|v| v.iter()) + .any(|v| v.as_str() == name) { - self.external_vars.push(crate::ast::Ident { - name: name.into(), - pos: _pos, - }); + self.external_vars + .get_or_insert_with(Default::default) + .push(Ident { + name: name.into(), + pos: _pos, + }); } } else { self.allow_capture = true; @@ -233,6 +235,7 @@ impl<'e> ParseState<'e> { pub fn find_module(&self, name: &str) -> Option { self.imports .iter() + .flat_map(|m| m.iter()) .rev() .enumerate() .find(|(.., n)| n.as_str() == name) @@ -280,52 +283,67 @@ impl<'e> ParseState<'e> { } } +bitflags! { + /// Bit-flags containing all status for [`ParseSettings`]. + pub struct ParseSettingFlags: u8 { + /// Is the construct being parsed located at global level? + const GLOBAL_LEVEL = 0b0000_0001; + /// Is the construct being parsed located inside a function definition? + #[cfg(not(feature = "no_function"))] + const FN_SCOPE = 0b0000_0010; + /// Is the construct being parsed located inside a closure definition? + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_closure"))] + const CLOSURE_SCOPE = 0b0000_0100; + /// Is the construct being parsed located inside a breakable loop? + const BREAKABLE = 0b0000_1000; + /// Disallow statements in blocks? + const DISALLOW_STATEMENTS_IN_BLOCKS = 0b0001_0000; + /// Disallow unquoted map properties? + const DISALLOW_UNQUOTED_MAP_PROPERTIES = 0b0010_0000; + } +} + /// A type that encapsulates all the settings for a particular parsing function. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub(crate) struct ParseSettings { - /// Is the construct being parsed located at global level? - pub at_global_level: bool, - /// Is the construct being parsed located inside a function definition? - #[cfg(not(feature = "no_function"))] - pub in_fn_scope: bool, - /// Is the construct being parsed located inside a closure definition? - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_closure"))] - pub in_closure: bool, - /// Is the construct being parsed located inside a breakable loop? - pub is_breakable: bool, - /// Allow statements in blocks? - pub allow_statements: bool, - /// Allow unquoted map properties? - pub allow_unquoted_map_properties: bool, +pub struct ParseSettings { + /// Flags. + pub flags: ParseSettingFlags, /// Language options in effect (overrides Engine options). pub options: LangOptions, /// Current expression nesting level. pub level: usize, /// Current position. pub pos: Position, + /// Maximum levels of expression nesting (0 for unlimited). + #[cfg(not(feature = "unchecked"))] + pub max_expr_depth: usize, } impl ParseSettings { + /// Is a particular flag on? + #[inline(always)] + #[must_use] + pub const fn has_flag(&self, flag: ParseSettingFlags) -> bool { + self.flags.contains(flag) + } + /// Is a particular language option on? + #[inline(always)] + #[must_use] + pub const fn has_option(&self, option: LangOptions) -> bool { + self.options.contains(option) + } /// Create a new `ParseSettings` with one higher expression level. #[inline] - #[must_use] - pub const fn level_up(&self) -> Self { - Self { - level: self.level + 1, - ..*self - } - } - /// Make sure that the current level of expression nesting is within the maximum limit. - /// - /// If `limit` is zero, then checking is disabled. - #[cfg(not(feature = "unchecked"))] - #[inline] - pub fn ensure_level_within_max_limit(&self, limit: usize) -> ParseResult<()> { - if limit > 0 && self.level > limit { + pub fn level_up(&self) -> ParseResult { + let level = self.level + 1; + + #[cfg(not(feature = "unchecked"))] + if self.max_expr_depth > 0 && level > self.max_expr_depth { return Err(PERR::ExprTooDeep.into_err(self.pos)); } - Ok(()) + + Ok(Self { level, ..*self }) } } @@ -333,8 +351,12 @@ impl ParseSettings { #[cfg(not(feature = "no_function"))] #[inline] #[must_use] -pub fn make_anonymous_fn(hash: u64) -> String { - format!("{}{:016x}", crate::engine::FN_ANONYMOUS, hash) +pub fn make_anonymous_fn(hash: u64) -> Identifier { + use std::fmt::Write; + + let mut buf = Identifier::new_const(); + write!(&mut buf, "{}{:016x}", crate::engine::FN_ANONYMOUS, hash).unwrap(); + buf } /// Is this function an anonymous function? @@ -387,7 +409,7 @@ impl Expr { }; Err( - PERR::MismatchedType("a boolean expression".to_string(), type_name.to_string()) + PERR::MismatchedType("a boolean expression".into(), type_name.into()) .into_err(self.start_position()), ) } @@ -405,7 +427,7 @@ impl Expr { }; Err( - PERR::MismatchedType("an iterable value".to_string(), type_name.to_string()) + PERR::MismatchedType("an iterable value".into(), type_name.into()) .into_err(self.start_position()), ) } @@ -426,8 +448,8 @@ fn ensure_not_statement_expr( fn ensure_not_assignment(input: &mut TokenStream) -> ParseResult<()> { match input.peek().expect(NEVER_ENDS) { (Token::Equals, pos) => Err(LexError::ImproperSymbol( - "=".to_string(), - "Possibly a typo of '=='?".to_string(), + "=".into(), + "Possibly a typo of '=='?".into(), ) .into_err(*pos)), _ => Ok(()), @@ -438,15 +460,15 @@ fn ensure_not_assignment(input: &mut TokenStream) -> ParseResult<()> { /// /// # Panics /// -/// Panics if the next token is not the expected one. +/// Panics if the next token is not the expected one, or either tokens is not a literal symbol. fn eat_token(input: &mut TokenStream, expected_token: Token) -> Position { let (t, pos) = input.next().expect(NEVER_ENDS); if t != expected_token { unreachable!( "{} expected but gets {} at {}", - expected_token.syntax(), - t.syntax(), + expected_token.literal_syntax(), + t.literal_syntax(), pos ); } @@ -503,14 +525,11 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - // ( ... let mut settings = settings; settings.pos = eat_token(input, Token::LeftParen); - let expr = self.parse_expr(input, state, lib, settings.level_up())?; + let expr = self.parse_expr(input, state, lib, settings.level_up()?)?; match input.next().expect(NEVER_ENDS) { // ( ... ) @@ -538,9 +557,6 @@ impl Engine { #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let (token, token_pos) = if no_args { &(Token::RightParen, Position::NONE) } else { @@ -581,14 +597,21 @@ impl Engine { #[cfg(any(feature = "no_function", feature = "no_module"))] let is_global = false; - if settings.options.contains(LangOptions::STRICT_VAR) + if settings.has_option(LangOptions::STRICT_VAR) && index.is_none() && !is_global - && !state.global_imports.iter().any(|m| m.as_str() == root) - && !self.global_sub_modules.contains_key(root) + && !state + .global_imports + .iter() + .flat_map(|m| m.iter()) + .any(|m| m.as_str() == root) + && !self + .global_sub_modules + .as_ref() + .map_or(false, |m| m.contains_key(root)) { return Err( - PERR::ModuleUndefined(root.to_string()).into_err(namespace.position()) + PERR::ModuleUndefined(root.into()).into_err(namespace.position()) ); } @@ -622,7 +645,7 @@ impl Engine { _ => (), } - let settings = settings.level_up(); + let settings = settings.level_up()?; loop { match input.peek().expect(NEVER_ENDS) { @@ -649,14 +672,22 @@ impl Engine { #[cfg(any(feature = "no_function", feature = "no_module"))] let is_global = false; - if settings.options.contains(LangOptions::STRICT_VAR) + if settings.has_option(LangOptions::STRICT_VAR) && index.is_none() && !is_global - && !state.global_imports.iter().any(|m| m.as_str() == root) - && !self.global_sub_modules.contains_key(root) + && !state + .global_imports + .iter() + .flat_map(|m| m.iter()) + .any(|m| m.as_str() == root) + && !self + .global_sub_modules + .as_ref() + .map_or(false, |m| m.contains_key(root)) { - return Err(PERR::ModuleUndefined(root.to_string()) - .into_err(namespace.position())); + return Err( + PERR::ModuleUndefined(root.into()).into_err(namespace.position()) + ); } namespace.set_index(index); @@ -724,12 +755,9 @@ impl Engine { check_index_type: bool, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let mut settings = settings; - let idx_expr = self.parse_expr(input, state, lib, settings.level_up())?; + let idx_expr = self.parse_expr(input, state, lib, settings.level_up()?)?; // Check types of indexing that cannot be overridden // - arrays, maps, strings, bit-fields @@ -852,7 +880,7 @@ impl Engine { _ => unreachable!("`[` or `?[`"), }, false, - settings.level_up(), + settings.level_up()?, )?; // Indexing binds to right Ok(Expr::Index( @@ -887,9 +915,6 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - // [ ... let mut settings = settings; settings.pos = eat_token(input, Token::LeftBracket); @@ -901,7 +926,7 @@ impl Engine { if self.max_array_size() > 0 && array.len() >= self.max_array_size() { return Err(PERR::LiteralTooLarge( - "Size of array literal".to_string(), + "Size of array literal".into(), self.max_array_size(), ) .into_err(input.peek().expect(NEVER_ENDS).1)); @@ -920,7 +945,7 @@ impl Engine { .into_err(*pos)) } _ => { - let expr = self.parse_expr(input, state, lib, settings.level_up())?; + let expr = self.parse_expr(input, state, lib, settings.level_up()?)?; array.push(expr); } } @@ -962,9 +987,6 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - // #{ ... let mut settings = settings; settings.pos = eat_token(input, Token::MapStart); @@ -990,7 +1012,9 @@ impl Engine { } let (name, pos) = match input.next().expect(NEVER_ENDS) { - (Token::Identifier(..), pos) if !settings.allow_unquoted_map_properties => { + (Token::Identifier(..), pos) + if settings.has_flag(ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES) => + { return Err(PERR::PropertyExpected.into_err(pos)) } (Token::Identifier(s) | Token::StringConstant(s), pos) => { @@ -1037,13 +1061,13 @@ impl Engine { if self.max_map_size() > 0 && map.len() >= self.max_map_size() { return Err(PERR::LiteralTooLarge( - "Number of properties in object map literal".to_string(), + "Number of properties in object map literal".into(), self.max_map_size(), ) .into_err(input.peek().expect(NEVER_ENDS).1)); } - let expr = self.parse_expr(input, state, lib, settings.level_up())?; + let expr = self.parse_expr(input, state, lib, settings.level_up()?)?; template.insert(name.clone(), crate::Dynamic::UNIT); let name = state.get_interned_string(name); @@ -1084,14 +1108,11 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - // switch ... let mut settings = settings; settings.pos = eat_token(input, Token::Switch); - let item = self.parse_expr(input, state, lib, settings.level_up())?; + let item = self.parse_expr(input, state, lib, settings.level_up()?)?; match input.next().expect(NEVER_ENDS) { (Token::LeftBrace, ..) => (), @@ -1150,7 +1171,7 @@ impl Engine { loop { let filter = state.expr_filter; state.expr_filter = |t| t != &Token::Pipe; - let expr = self.parse_expr(input, state, lib, settings.level_up()); + let expr = self.parse_expr(input, state, lib, settings.level_up()?); state.expr_filter = filter; match expr { @@ -1168,7 +1189,7 @@ impl Engine { let condition = if match_token(input, Token::If).0 { ensure_not_statement_expr(input, "a boolean")?; let guard = self - .parse_expr(input, state, lib, settings.level_up())? + .parse_expr(input, state, lib, settings.level_up()?)? .ensure_bool_expr()?; ensure_not_assignment(input)?; guard @@ -1185,24 +1206,25 @@ impl Engine { (.., pos) => { return Err(PERR::MissingToken( Token::DoubleArrow.into(), - "in this switch case".to_string(), + "in this switch case".into(), ) .into_err(pos)) } }; - let (action_expr, need_comma) = if settings.allow_statements { - let stmt = self.parse_stmt(input, state, lib, settings.level_up())?; - let need_comma = !stmt.is_self_terminated(); + let (action_expr, need_comma) = + if !settings.has_flag(ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS) { + let stmt = self.parse_stmt(input, state, lib, settings.level_up()?)?; + let need_comma = !stmt.is_self_terminated(); - let stmt_block: StmtBlock = stmt.into(); - (Expr::Stmt(stmt_block.into()), need_comma) - } else { - ( - self.parse_expr(input, state, lib, settings.level_up())?, - true, - ) - }; + let stmt_block: StmtBlock = stmt.into(); + (Expr::Stmt(stmt_block.into()), need_comma) + } else { + ( + self.parse_expr(input, state, lib, settings.level_up()?)?, + true, + ) + }; let has_condition = !matches!(condition, Expr::BoolConstant(true, ..)); expressions.push((condition, action_expr).into()); @@ -1213,7 +1235,7 @@ impl Engine { } else { for expr in case_expr_list { let value = expr.get_literal_value().ok_or_else(|| { - PERR::ExprExpected("a literal".to_string()).into_err(expr.start_position()) + PERR::ExprExpected("a literal".into()).into_err(expr.start_position()) })?; let mut range_value: Option = None; @@ -1305,9 +1327,6 @@ impl Engine { is_property: bool, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let (token, token_pos) = input.peek().expect(NEVER_ENDS); let mut settings = settings; @@ -1315,9 +1334,7 @@ impl Engine { let root_expr = match token { _ if !(state.expr_filter)(token) => { - return Err( - LexError::UnexpectedInput(token.syntax().to_string()).into_err(settings.pos) - ) + return Err(LexError::UnexpectedInput(token.to_string()).into_err(settings.pos)) } Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), @@ -1355,58 +1372,52 @@ impl Engine { } // { - block statement as expression - Token::LeftBrace if settings.options.contains(LangOptions::STMT_EXPR) => { - match self.parse_block(input, state, lib, settings.level_up())? { + Token::LeftBrace if settings.has_option(LangOptions::STMT_EXPR) => { + match self.parse_block(input, state, lib, settings.level_up()?)? { block @ Stmt::Block(..) => Expr::Stmt(Box::new(block.into())), stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), } } // ( - grouped expression - Token::LeftParen => self.parse_paren_expr(input, state, lib, settings.level_up())?, + Token::LeftParen => self.parse_paren_expr(input, state, lib, settings.level_up()?)?, // If statement is allowed to act as expressions - Token::If if settings.options.contains(LangOptions::IF_EXPR) => Expr::Stmt(Box::new( - self.parse_if(input, state, lib, settings.level_up())? + Token::If if settings.has_option(LangOptions::IF_EXPR) => Expr::Stmt(Box::new( + self.parse_if(input, state, lib, settings.level_up()?)? .into(), )), // Loops are allowed to act as expressions - Token::While | Token::Loop if settings.options.contains(LangOptions::LOOP_EXPR) => { + Token::While | Token::Loop if settings.has_option(LangOptions::LOOP_EXPR) => { Expr::Stmt(Box::new( - self.parse_while_loop(input, state, lib, settings.level_up())? + self.parse_while_loop(input, state, lib, settings.level_up()?)? .into(), )) } - Token::Do if settings.options.contains(LangOptions::LOOP_EXPR) => Expr::Stmt(Box::new( - self.parse_do(input, state, lib, settings.level_up())? + Token::Do if settings.has_option(LangOptions::LOOP_EXPR) => Expr::Stmt(Box::new( + self.parse_do(input, state, lib, settings.level_up()?)? + .into(), + )), + Token::For if settings.has_option(LangOptions::LOOP_EXPR) => Expr::Stmt(Box::new( + self.parse_for(input, state, lib, settings.level_up()?)? .into(), )), - Token::For if settings.options.contains(LangOptions::LOOP_EXPR) => { - Expr::Stmt(Box::new( - self.parse_for(input, state, lib, settings.level_up())? - .into(), - )) - } // Switch statement is allowed to act as expressions - Token::Switch if settings.options.contains(LangOptions::SWITCH_EXPR) => { - Expr::Stmt(Box::new( - self.parse_switch(input, state, lib, settings.level_up())? - .into(), - )) - } + Token::Switch if settings.has_option(LangOptions::SWITCH_EXPR) => Expr::Stmt(Box::new( + self.parse_switch(input, state, lib, settings.level_up()?)? + .into(), + )), // | ... #[cfg(not(feature = "no_function"))] - Token::Pipe | Token::Or if settings.options.contains(LangOptions::ANON_FN) => { + Token::Pipe | Token::Or if settings.has_option(LangOptions::ANON_FN) => { // Build new parse state - let interned_strings = std::mem::take(&mut state.interned_strings); + let new_interner = &mut StringsInterner::new(); + let mut new_state = + ParseState::new(state.scope, new_interner, state.tokenizer_control.clone()); - let mut new_state = ParseState::new( - self, - state.scope, - interned_strings, - state.tokenizer_control.clone(), - ); + // We move the strings interner to the new parse state object by swapping it... + std::mem::swap(state.interned_strings, new_state.interned_strings); #[cfg(not(feature = "no_module"))] { @@ -1418,52 +1429,51 @@ impl Engine { new_state.global_imports.clone_from(&state.global_imports); new_state .global_imports - .extend(state.imports.iter().cloned()); + .get_or_insert_with(Default::default) + .extend(state.imports.iter().flat_map(|m| m.iter()).cloned()); } - #[cfg(not(feature = "unchecked"))] - { - new_state.max_expr_depth = self.max_function_expr_depth(); - } + #[cfg(not(feature = "no_closure"))] + let options = self.options & !LangOptions::STRICT_VAR; // A capturing closure can access variables not defined locally + #[cfg(feature = "no_closure")] + let options = self.options | (settings.options & LangOptions::STRICT_VAR); - let mut options = self.options; - options.set( - LangOptions::STRICT_VAR, - if cfg!(feature = "no_closure") { - settings.options.contains(LangOptions::STRICT_VAR) - } else { - // A capturing closure can access variables not defined locally - false - }, - ); + let flags = (settings.flags + & !ParseSettingFlags::GLOBAL_LEVEL + & ParseSettingFlags::BREAKABLE) + | ParseSettingFlags::FN_SCOPE; + + #[cfg(not(feature = "no_closure"))] + let flags = flags | ParseSettingFlags::CLOSURE_SCOPE; let new_settings = ParseSettings { - at_global_level: false, - in_fn_scope: true, - #[cfg(not(feature = "no_closure"))] - in_closure: true, - is_breakable: false, level: 0, + flags, options, + #[cfg(not(feature = "unchecked"))] + max_expr_depth: self.max_function_expr_depth(), ..settings }; let result = self.parse_anon_fn(input, &mut new_state, state, lib, new_settings); - // Restore parse state - state.interned_strings = new_state.interned_strings; + // Restore the strings interner by swapping it back + std::mem::swap(state.interned_strings, new_state.interned_strings); let (expr, func) = result?; #[cfg(not(feature = "no_closure"))] - new_state.external_vars.iter().try_for_each( - |crate::ast::Ident { name, pos }| { + new_state + .external_vars + .iter() + .flat_map(|v| v.iter()) + .try_for_each(|Ident { name, pos }| { let (index, is_func) = state.access_var(name, lib, *pos); if !is_func && index.is_none() - && !settings.in_closure - && settings.options.contains(LangOptions::STRICT_VAR) + && !settings.has_flag(ParseSettingFlags::CLOSURE_SCOPE) + && settings.has_option(LangOptions::STRICT_VAR) && !state.scope.contains(name) { // If the parent scope is not inside another capturing closure @@ -1473,8 +1483,7 @@ impl Engine { } else { Ok(()) } - }, - )?; + })?; let hash_script = calc_fn_hash(None, &func.name, func.params.len()); lib.insert(hash_script, func.into()); @@ -1497,7 +1506,7 @@ impl Engine { } loop { - let expr = match self.parse_block(input, state, lib, settings.level_up())? { + let expr = match self.parse_block(input, state, lib, settings.level_up()?)? { block @ Stmt::Block(..) => Expr::Stmt(Box::new(block.into())), stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), }; @@ -1547,21 +1556,28 @@ impl Engine { // Array literal #[cfg(not(feature = "no_index"))] Token::LeftBracket => { - self.parse_array_literal(input, state, lib, settings.level_up())? + self.parse_array_literal(input, state, lib, settings.level_up()?)? } // Map literal #[cfg(not(feature = "no_object"))] - Token::MapStart => self.parse_map_literal(input, state, lib, settings.level_up())?, + Token::MapStart => self.parse_map_literal(input, state, lib, settings.level_up()?)?, // 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(); + let settings2 = settings.level_up()?; self.parse_custom_syntax(input, state, lib, settings2, key, syntax, pos)? } @@ -1609,7 +1625,7 @@ impl Engine { if !is_property && !is_func && index.is_none() - && settings.options.contains(LangOptions::STRICT_VAR) + && settings.has_option(LangOptions::STRICT_VAR) && !state.scope.contains(&s) { return Err( @@ -1653,13 +1669,15 @@ impl Engine { } // Access to `this` as a variable is OK within a function scope #[cfg(not(feature = "no_function"))] - _ if &*s == KEYWORD_THIS && settings.in_fn_scope => Expr::Variable( - (None, ns, 0, state.get_interned_string(*s)).into(), - None, - settings.pos, - ), + _ if *s == KEYWORD_THIS && settings.has_flag(ParseSettingFlags::FN_SCOPE) => { + Expr::Variable( + (None, ns, 0, state.get_interned_string(*s)).into(), + None, + settings.pos, + ) + } // Cannot access to `this` as a variable not in a function scope - _ if &*s == KEYWORD_THIS => { + _ if *s == KEYWORD_THIS => { let msg = format!("'{s}' can only be used in functions"); return Err( LexError::ImproperSymbol(s.to_string(), msg).into_err(settings.pos) @@ -1674,11 +1692,7 @@ impl Engine { token => unreachable!("Token::LexError expected but gets {:?}", token), }, - _ => { - return Err( - LexError::UnexpectedInput(token.syntax().to_string()).into_err(settings.pos) - ) - } + _ => return Err(LexError::UnexpectedInput(token.to_string()).into_err(settings.pos)), }; if !(state.expr_filter)(&input.peek().expect(NEVER_ENDS).0) { @@ -1716,12 +1730,11 @@ impl Engine { (Expr::Variable(x, ..), Token::Bang) if !x.1.is_empty() => { return match input.peek().expect(NEVER_ENDS) { (Token::LeftParen | Token::Unit, ..) => { - Err(LexError::UnexpectedInput(Token::Bang.syntax().to_string()) - .into_err(tail_pos)) + Err(LexError::UnexpectedInput(Token::Bang.into()).into_err(tail_pos)) } _ => Err(LexError::ImproperSymbol( - "!".to_string(), - "'!' cannot be used to call module functions".to_string(), + "!".into(), + "'!' cannot be used to call module functions".into(), ) .into_err(tail_pos)), }; @@ -1732,7 +1745,7 @@ impl Engine { (Token::LeftParen | Token::Unit, ..) => (), (_, pos) => { return Err(PERR::MissingToken( - Token::LeftParen.syntax().into(), + Token::LeftParen.into(), "to start arguments list of function call".into(), ) .into_err(*pos)) @@ -1752,7 +1765,7 @@ impl Engine { true, #[cfg(not(feature = "no_module"))] _ns, - settings.level_up(), + settings.level_up()?, )? } // Function call @@ -1768,7 +1781,7 @@ impl Engine { false, #[cfg(not(feature = "no_module"))] _ns, - settings.level_up(), + settings.level_up()?, )? } // module access @@ -1794,7 +1807,15 @@ impl Engine { Token::QuestionBracket => ASTFlags::NEGATED, _ => unreachable!("`[` or `?[`"), }; - self.parse_index_chain(input, state, lib, expr, opt, true, settings.level_up())? + self.parse_index_chain( + input, + state, + lib, + expr, + opt, + true, + settings.level_up()?, + )? } // Property access #[cfg(not(feature = "no_object"))] @@ -1812,7 +1833,7 @@ impl Engine { (.., pos) => return Err(PERR::PropertyExpected.into_err(*pos)), } - let rhs = self.parse_primary(input, state, lib, true, settings.level_up())?; + let rhs = self.parse_primary(input, state, lib, true, settings.level_up()?)?; let op_flags = match op { Token::Period => ASTFlags::NONE, Token::Elvis => ASTFlags::NEGATED, @@ -1821,11 +1842,9 @@ impl Engine { Self::make_dot_expr(state, expr, rhs, ASTFlags::NONE, op_flags, tail_pos)? } // Unknown postfix operator - (expr, token) => unreachable!( - "unknown postfix operator '{}' for {:?}", - token.syntax(), - expr - ), + (expr, token) => { + unreachable!("unknown postfix operator '{}' for {:?}", token, expr) + } } } @@ -1856,14 +1875,21 @@ impl Engine { #[cfg(any(feature = "no_function", feature = "no_module"))] let is_global = false; - if settings.options.contains(LangOptions::STRICT_VAR) + if settings.has_option(LangOptions::STRICT_VAR) && index.is_none() && !is_global - && !state.global_imports.iter().any(|m| m.as_str() == root) - && !self.global_sub_modules.contains_key(root) + && !state + .global_imports + .iter() + .flat_map(|m| m.iter()) + .any(|m| m.as_str() == root) + && !self + .global_sub_modules + .as_ref() + .map_or(false, |m| m.contains_key(root)) { return Err( - PERR::ModuleUndefined(root.to_string()).into_err(namespace.position()) + PERR::ModuleUndefined(root.into()).into_err(namespace.position()) ); } @@ -1884,13 +1910,10 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let (token, token_pos) = input.peek().expect(NEVER_ENDS); if !(state.expr_filter)(token) { - return Err(LexError::UnexpectedInput(token.syntax().to_string()).into_err(*token_pos)); + return Err(LexError::UnexpectedInput(token.to_string()).into_err(*token_pos)); } let mut settings = settings; @@ -1902,7 +1925,7 @@ impl Engine { let token = token.clone(); let pos = eat_token(input, token.clone()); - match self.parse_unary(input, state, lib, settings.level_up())? { + match self.parse_unary(input, state, lib, settings.level_up()?)? { // Negative integer Expr::IntegerConstant(num, ..) => num .checked_neg() @@ -1943,7 +1966,7 @@ impl Engine { let token = token.clone(); let pos = eat_token(input, token.clone()); - match self.parse_unary(input, state, lib, settings.level_up())? { + match self.parse_unary(input, state, lib, settings.level_up()?)? { expr @ Expr::IntegerConstant(..) => Ok(expr), #[cfg(not(feature = "no_float"))] expr @ Expr::FloatConstant(..) => Ok(expr), @@ -1973,7 +1996,7 @@ impl Engine { let pos = eat_token(input, Token::Bang); let mut args = StaticVec::new_const(); - args.push(self.parse_unary(input, state, lib, settings.level_up())?); + args.push(self.parse_unary(input, state, lib, settings.level_up()?)?); args.shrink_to_fit(); Ok(FnCallExpr { @@ -1990,7 +2013,7 @@ impl Engine { // Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)), // All other tokens - _ => self.parse_primary(input, state, lib, false, settings.level_up()), + _ => self.parse_primary(input, state, lib, false, settings.level_up()?), } } @@ -2046,16 +2069,14 @@ impl Engine { } // var (indexed) = rhs Expr::Variable(ref x, i, var_pos) => { + let stack = state.stack.get_or_insert_with(Default::default); let (index, .., name) = &**x; let index = i.map_or_else( || index.expect("either long or short index is `None`").get(), |n| n.get() as usize, ); - match state - .stack - .get_mut_by_index(state.stack.len() - index) - .access_mode() - { + + match stack.get_mut_by_index(stack.len() - index).access_mode() { AccessMode::ReadWrite => { Ok(Stmt::Assignment((op_info, (lhs, rhs).into()).into())) } @@ -2092,8 +2113,8 @@ impl Engine { } // ??? && ??? = rhs, ??? || ??? = rhs, xxx ?? xxx = rhs Expr::And(..) | Expr::Or(..) | Expr::Coalesce(..) => Err(LexError::ImproperSymbol( - "=".to_string(), - "Possibly a typo of '=='?".to_string(), + "=".into(), + "Possibly a typo of '=='?".into(), ) .into_err(op_pos)), // expr = rhs @@ -2110,9 +2131,6 @@ impl Engine { lhs: Expr, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let (op, pos) = match input.peek().expect(NEVER_ENDS) { // var = ... (Token::Equals, ..) => (None, eat_token(input, Token::Equals)), @@ -2128,7 +2146,7 @@ impl Engine { let mut settings = settings; settings.pos = pos; - let rhs = self.parse_expr(input, state, lib, settings.level_up())?; + let rhs = self.parse_expr(input, state, lib, settings.level_up()?)?; Self::make_assignment_stmt(op, state, lhs, rhs, pos) } @@ -2293,9 +2311,6 @@ impl Engine { lhs: Expr, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let mut settings = settings; settings.pos = lhs.position(); @@ -2312,7 +2327,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) => { @@ -2337,7 +2353,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) => { @@ -2356,13 +2373,10 @@ impl Engine { rhs }; - settings = settings.level_up(); + settings = settings.level_up()?; settings.pos = pos; - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - - let op = op_token.syntax(); + let op = op_token.to_string(); let hash = calc_fn_hash(None, &op, 2); let is_valid_script_function = is_valid_function_name(&op); let operator_token = if is_valid_script_function { @@ -2379,7 +2393,7 @@ impl Engine { let mut op_base = FnCallExpr { #[cfg(not(feature = "no_module"))] namespace: Default::default(), - name: state.get_interned_string(op.as_ref()), + name: state.get_interned_string(&op), hashes: FnCallHashes::from_native(hash), args, op_token: operator_token, @@ -2403,12 +2417,12 @@ impl Engine { Token::Or => { let rhs = op_base.args.pop().unwrap().ensure_bool_expr()?; let lhs = op_base.args.pop().unwrap().ensure_bool_expr()?; - Expr::Or(BinaryExpr { lhs: lhs, rhs: rhs }.into(), pos) + Expr::Or(BinaryExpr { lhs, rhs }.into(), pos) } Token::And => { let rhs = op_base.args.pop().unwrap().ensure_bool_expr()?; let lhs = op_base.args.pop().unwrap().ensure_bool_expr()?; - Expr::And(BinaryExpr { lhs: lhs, rhs: rhs }.into(), pos) + Expr::And(BinaryExpr { lhs, rhs }.into(), pos) } Token::DoubleQuestion => { let rhs = op_base.args.pop().unwrap(); @@ -2432,7 +2446,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 { @@ -2463,7 +2478,9 @@ impl Engine { syntax: &crate::api::custom_syntax::CustomSyntax, pos: Position, ) -> ParseResult { + #[allow(clippy::wildcard_imports)] use crate::api::custom_syntax::markers::*; + const KEYWORD_SEMICOLON: &str = Token::SemiColon.literal_syntax(); const KEYWORD_CLOSE_BRACE: &str = Token::RightBrace.literal_syntax(); @@ -2477,7 +2494,10 @@ impl Engine { // Add a barrier variable to the stack so earlier variables will not be matched. // Variable searches stop at the first barrier. let marker = state.get_interned_string(SCOPE_SEARCH_BARRIER_MARKER); - state.stack.push(marker, ()); + state + .stack + .get_or_insert_with(Default::default) + .push(marker, ()); } let mut user_state = Dynamic::UNIT; @@ -2490,9 +2510,9 @@ impl Engine { loop { let (fwd_token, fwd_pos) = input.peek().expect(NEVER_ENDS); settings.pos = *fwd_pos; - let settings = settings.level_up(); + let settings = settings.level_up()?; - required_token = match parse_func(&segments, &*fwd_token.syntax(), &mut user_state) { + required_token = match parse_func(&segments, &fwd_token.to_string(), &mut user_state) { Ok(Some(seg)) if seg.starts_with(CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT) && seg.len() > CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT.len() => @@ -2515,7 +2535,7 @@ impl Engine { #[cfg(feature = "no_module")] let ns = (); - segments.push(name.clone().into()); + segments.push(name.clone()); tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_IDENT)); inputs.push(Expr::Variable((None, ns, 0, name).into(), None, pos)); } @@ -2529,7 +2549,7 @@ impl Engine { CUSTOM_SYNTAX_MARKER_EXPR => { inputs.push(self.parse_expr(input, state, lib, settings)?); let keyword = state.get_interned_string(CUSTOM_SYNTAX_MARKER_EXPR); - segments.push(keyword.clone().into()); + segments.push(keyword.clone()); tokens.push(keyword); } CUSTOM_SYNTAX_MARKER_BLOCK => { @@ -2537,7 +2557,7 @@ impl Engine { block @ Stmt::Block(..) => { inputs.push(Expr::Stmt(Box::new(block.into()))); let keyword = state.get_interned_string(CUSTOM_SYNTAX_MARKER_BLOCK); - segments.push(keyword.clone().into()); + segments.push(keyword.clone()); tokens.push(keyword); } stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), @@ -2551,8 +2571,7 @@ impl Engine { } (.., pos) => { return Err( - PERR::MissingSymbol("Expecting 'true' or 'false'".to_string()) - .into_err(pos), + PERR::MissingSymbol("Expecting 'true' or 'false'".into()).into_err(pos) ) } }, @@ -2564,8 +2583,7 @@ impl Engine { } (.., pos) => { return Err( - PERR::MissingSymbol("Expecting an integer number".to_string()) - .into_err(pos), + PERR::MissingSymbol("Expecting an integer number".into()).into_err(pos) ) } }, @@ -2577,10 +2595,10 @@ impl Engine { tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_FLOAT)); } (.., pos) => { - return Err(PERR::MissingSymbol( - "Expecting a floating-point number".to_string(), + return Err( + PERR::MissingSymbol("Expecting a floating-point number".into()) + .into_err(pos), ) - .into_err(pos)) } }, CUSTOM_SYNTAX_MARKER_STRING => match input.next().expect(NEVER_ENDS) { @@ -2591,20 +2609,24 @@ impl Engine { tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_STRING)); } (.., pos) => { - return Err( - PERR::MissingSymbol("Expecting a string".to_string()).into_err(pos) - ) + return Err(PERR::MissingSymbol("Expecting a string".into()).into_err(pos)) } }, s => match input.next().expect(NEVER_ENDS) { (Token::LexError(err), pos) => return Err(err.into_err(pos)), - (t, ..) if &*t.syntax() == s => { + (Token::Identifier(t) | Token::Reserved(t) | Token::Custom(t), ..) + if *t == s => + { segments.push(required_token.clone()); - tokens.push(required_token.clone().into()); + tokens.push(required_token.clone()); + } + (t, ..) if t.is_literal() && t.literal_syntax() == s => { + segments.push(required_token.clone()); + tokens.push(required_token.clone()); } (.., pos) => { return Err(PERR::MissingToken( - s.to_string(), + s.into(), format!("for '{}' expression", segments[0]), ) .into_err(pos)) @@ -2645,16 +2667,13 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let mut settings = settings; settings.pos = input.peek().expect(NEVER_ENDS).1; // Parse expression normally. let precedence = Precedence::new(1); - let lhs = self.parse_unary(input, state, lib, settings.level_up())?; - self.parse_binary_op(input, state, lib, precedence, lhs, settings.level_up()) + let lhs = self.parse_unary(input, state, lib, settings.level_up()?)?; + self.parse_binary_op(input, state, lib, precedence, lhs, settings.level_up()?) } /// Parse an if statement. @@ -2665,9 +2684,6 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - // if ... let mut settings = settings; settings.pos = eat_token(input, Token::If); @@ -2675,19 +2691,19 @@ impl Engine { // if guard { if_body } ensure_not_statement_expr(input, "a boolean")?; let guard = self - .parse_expr(input, state, lib, settings.level_up())? + .parse_expr(input, state, lib, settings.level_up()?)? .ensure_bool_expr()?; ensure_not_assignment(input)?; - let if_body = self.parse_block(input, state, lib, settings.level_up())?; + let if_body = self.parse_block(input, state, lib, settings.level_up()?)?; // if guard { if_body } else ... let else_body = if match_token(input, Token::Else).0 { if let (Token::If, ..) = input.peek().expect(NEVER_ENDS) { // if guard { if_body } else if ... - self.parse_if(input, state, lib, settings.level_up())? + self.parse_if(input, state, lib, settings.level_up()?)? } else { // if guard { if_body } else { else-body } - self.parse_block(input, state, lib, settings.level_up())? + self.parse_block(input, state, lib, settings.level_up()?)? } } else { Stmt::Noop(Position::NONE) @@ -2707,9 +2723,6 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let mut settings = settings; // while|loops ... @@ -2717,7 +2730,7 @@ impl Engine { (Token::While, pos) => { ensure_not_statement_expr(input, "a boolean")?; let expr = self - .parse_expr(input, state, lib, settings.level_up())? + .parse_expr(input, state, lib, settings.level_up()?)? .ensure_bool_expr()?; ensure_not_assignment(input)?; (expr, pos) @@ -2726,9 +2739,9 @@ impl Engine { token => unreachable!("Token::While or Token::Loop expected but gets {:?}", token), }; settings.pos = token_pos; - settings.is_breakable = true; + settings.flags |= ParseSettingFlags::BREAKABLE; - let body = self.parse_block(input, state, lib, settings.level_up())?; + let body = self.parse_block(input, state, lib, settings.level_up()?)?; Ok(Stmt::While((guard, body.into()).into(), settings.pos)) } @@ -2741,16 +2754,13 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - // do ... let mut settings = settings; settings.pos = eat_token(input, Token::Do); // do { body } [while|until] guard - settings.is_breakable = true; - let body = self.parse_block(input, state, lib, settings.level_up())?; + settings.flags |= ParseSettingFlags::BREAKABLE; + let body = self.parse_block(input, state, lib, settings.level_up()?)?; let negated = match input.next().expect(NEVER_ENDS) { (Token::While, ..) => ASTFlags::NONE, @@ -2763,11 +2773,11 @@ impl Engine { } }; - settings.is_breakable = false; + settings.flags &= !ParseSettingFlags::BREAKABLE; ensure_not_statement_expr(input, "a boolean")?; let guard = self - .parse_expr(input, state, lib, settings.level_up())? + .parse_expr(input, state, lib, settings.level_up()?)? .ensure_bool_expr()?; ensure_not_assignment(input)?; @@ -2782,9 +2792,6 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - // for ... let mut settings = settings; settings.pos = eat_token(input, Token::For); @@ -2805,9 +2812,7 @@ impl Engine { let (counter_name, counter_pos) = parse_var_name(input)?; if counter_name == name { - return Err( - PERR::DuplicatedVariable(counter_name.to_string()).into_err(counter_pos) - ); + return Err(PERR::DuplicatedVariable(counter_name.into()).into_err(counter_pos)); } let (has_close_paren, pos) = match_token(input, Token::RightParen); @@ -2841,30 +2846,36 @@ impl Engine { // for name in expr { body } ensure_not_statement_expr(input, "a boolean")?; let expr = self - .parse_expr(input, state, lib, settings.level_up())? + .parse_expr(input, state, lib, settings.level_up()?)? .ensure_iterable()?; - let prev_stack_len = state.stack.len(); - - if !counter_name.is_empty() { - state.stack.push(name.clone(), ()); - } let counter_var = Ident { name: state.get_interned_string(counter_name), pos: counter_pos, }; - let loop_var = state.get_interned_string(name); - state.stack.push(loop_var.clone(), ()); let loop_var = Ident { - name: loop_var, + name: state.get_interned_string(name), pos: name_pos, }; - settings.is_breakable = true; - let body = self.parse_block(input, state, lib, settings.level_up())?; + let prev_stack_len = { + let stack = state.stack.get_or_insert_with(Default::default); - state.stack.rewind(prev_stack_len); + let prev_stack_len = stack.len(); + + if !counter_var.name.is_empty() { + stack.push(counter_var.name.clone(), ()); + } + stack.push(&loop_var.name, ()); + + prev_stack_len + }; + + settings.flags |= ParseSettingFlags::BREAKABLE; + let body = self.parse_block(input, state, lib, settings.level_up()?)?; + + state.stack.as_mut().unwrap().rewind(prev_stack_len); Ok(Stmt::For( Box::new((loop_var, counter_var, expr, body.into())), @@ -2882,9 +2893,6 @@ impl Engine { is_export: bool, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - // let/const... (specified in `var_type`) let mut settings = settings; settings.pos = input.next().expect(NEVER_ENDS).1; @@ -2892,33 +2900,41 @@ impl Engine { // let name ... let (name, pos) = parse_var_name(input)?; - if !self.allow_shadowing() && state.stack.iter().any(|(v, ..)| v == name) { - return Err(PERR::VariableExists(name.to_string()).into_err(pos)); - } + { + let stack = state.stack.get_or_insert_with(Default::default); - if let Some(ref filter) = self.def_var_filter { - let will_shadow = state.stack.iter().any(|(v, ..)| v == name); - state.global.level = settings.level; - let is_const = access == AccessMode::ReadOnly; - let info = VarDefInfo { - name: &name, - is_const, - nesting_level: state.global.level, - will_shadow, - }; - let caches = &mut Caches::new(); - let mut this = Dynamic::NULL; + if !self.allow_shadowing() && stack.iter().any(|(v, ..)| v == name) { + return Err(PERR::VariableExists(name.into()).into_err(pos)); + } - let context = - EvalContext::new(self, &mut state.global, caches, &mut state.stack, &mut this); + if let Some(ref filter) = self.def_var_filter { + let will_shadow = stack.iter().any(|(v, ..)| v == name); - match filter(false, info, context) { - Ok(true) => (), - Ok(false) => return Err(PERR::ForbiddenVariable(name.to_string()).into_err(pos)), - Err(err) => match *err { - EvalAltResult::ErrorParsing(e, pos) => return Err(e.into_err(pos)), - _ => return Err(PERR::ForbiddenVariable(name.to_string()).into_err(pos)), - }, + let global = state + .global + .get_or_insert_with(|| GlobalRuntimeState::new(self).into()); + + global.level = settings.level; + let is_const = access == AccessMode::ReadOnly; + let info = VarDefInfo { + name: &name, + is_const, + nesting_level: settings.level, + will_shadow, + }; + let caches = &mut Caches::new(); + let mut this = Dynamic::NULL; + + let context = EvalContext::new(self, global, caches, stack, &mut this); + + match filter(false, info, context) { + Ok(true) => (), + Ok(false) => return Err(PERR::ForbiddenVariable(name.into()).into_err(pos)), + Err(err) => match *err { + EvalAltResult::ErrorParsing(e, pos) => return Err(e.into_err(pos)), + _ => return Err(PERR::ForbiddenVariable(name.into()).into_err(pos)), + }, + } } } @@ -2927,7 +2943,7 @@ impl Engine { // let name = ... let expr = if match_token(input, Token::Equals).0 { // let name = expr - self.parse_expr(input, state, lib, settings.level_up())? + self.parse_expr(input, state, lib, settings.level_up()?)? } else { Expr::Unit(Position::NONE) }; @@ -2939,8 +2955,11 @@ impl Engine { }; let (existing, hit_barrier) = state.find_var(&name); + + let stack = state.stack.as_mut().unwrap(); + let existing = if !hit_barrier && existing > 0 { - let offset = state.stack.len() - existing; + let offset = stack.len() - existing; if offset < state.block_stack_len { // Defined in parent block None @@ -2952,10 +2971,10 @@ impl Engine { }; let idx = if let Some(n) = existing { - state.stack.get_mut_by_index(n).set_access_mode(access); - Some(NonZeroUsize::new(state.stack.len() - n).unwrap()) + stack.get_mut_by_index(n).set_access_mode(access); + Some(NonZeroUsize::new(stack.len() - n).unwrap()) } else { - state.stack.push_entry(name.as_str(), access, Dynamic::UNIT); + stack.push_entry(name.as_str(), access, Dynamic::UNIT); None }; @@ -2978,32 +2997,32 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - // import ... let mut settings = settings; settings.pos = eat_token(input, Token::Import); // import expr ... - let expr = self.parse_expr(input, state, lib, settings.level_up())?; + let expr = self.parse_expr(input, state, lib, settings.level_up()?)?; - let export = if !match_token(input, Token::As).0 { - // import expr; - Ident { - name: state.get_interned_string(""), - pos: Position::NONE, - } - } else { + let export = if match_token(input, Token::As).0 { // import expr as name ... let (name, pos) = parse_var_name(input)?; Ident { name: state.get_interned_string(name), pos, } + } else { + // import expr; + Ident { + name: state.get_interned_string(""), + pos: Position::NONE, + } }; - state.imports.push(export.name.clone()); + state + .imports + .get_or_insert_with(Default::default) + .push(export.name.clone()); Ok(Stmt::Import((expr, export).into(), settings.pos)) } @@ -3017,9 +3036,6 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let mut settings = settings; settings.pos = eat_token(input, Token::Export); @@ -3072,9 +3088,6 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - // Must start with { let mut settings = settings; settings.pos = match input.next().expect(NEVER_ENDS) { @@ -3091,8 +3104,8 @@ impl Engine { let mut statements = StaticVec::new_const(); - if !settings.allow_statements { - let stmt = self.parse_expr_stmt(input, state, lib, settings.level_up())?; + if settings.has_flag(ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS) { + let stmt = self.parse_expr_stmt(input, state, lib, settings.level_up()?)?; statements.push(stmt); // Must end with } @@ -3108,10 +3121,10 @@ impl Engine { } let prev_entry_stack_len = state.block_stack_len; - state.block_stack_len = state.stack.len(); + state.block_stack_len = state.stack.as_ref().map_or(0, |s| s.len()); #[cfg(not(feature = "no_module"))] - let orig_imports_len = state.imports.len(); + let orig_imports_len = state.imports.as_ref().map_or(0, |m| m.len()); let end_pos = loop { // Terminated? @@ -3128,9 +3141,9 @@ impl Engine { } // Parse statements inside the block - settings.at_global_level = false; + settings.flags &= !ParseSettingFlags::GLOBAL_LEVEL; - let stmt = self.parse_stmt(input, state, lib, settings.level_up())?; + let stmt = self.parse_stmt(input, state, lib, settings.level_up()?)?; if stmt.is_noop() { continue; @@ -3168,11 +3181,15 @@ impl Engine { } }; - state.stack.rewind(state.block_stack_len); + if let Some(ref mut s) = state.stack { + s.rewind(state.block_stack_len); + } state.block_stack_len = prev_entry_stack_len; #[cfg(not(feature = "no_module"))] - state.imports.truncate(orig_imports_len); + if let Some(ref mut imports) = state.imports { + imports.truncate(orig_imports_len); + } Ok((statements, settings.pos, end_pos).into()) } @@ -3185,14 +3202,11 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let mut settings = settings; settings.pos = input.peek().expect(NEVER_ENDS).1; - let expr = self.parse_expr(input, state, lib, settings.level_up())?; - let stmt = self.parse_op_assignment_stmt(input, state, lib, expr, settings.level_up())?; + let expr = self.parse_expr(input, state, lib, settings.level_up()?)?; + let stmt = self.parse_op_assignment_stmt(input, state, lib, expr, settings.level_up()?)?; Ok(stmt) } @@ -3224,7 +3238,7 @@ impl Engine { unreachable!("doc-comment expected but gets {:?}", comment); } - if !settings.at_global_level { + if !settings.has_flag(ParseSettingFlags::GLOBAL_LEVEL) { return Err(PERR::WrongDocComment.into_err(comments_pos)); } @@ -3251,9 +3265,6 @@ impl Engine { }; settings.pos = token_pos; - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - match token { // ; - empty statement Token::SemiColon => { @@ -3262,11 +3273,11 @@ impl Engine { } // { - statements block - Token::LeftBrace => Ok(self.parse_block(input, state, lib, settings.level_up())?), + Token::LeftBrace => Ok(self.parse_block(input, state, lib, settings.level_up()?)?), // fn ... #[cfg(not(feature = "no_function"))] - Token::Fn if !settings.at_global_level => { + Token::Fn if !settings.has_flag(ParseSettingFlags::GLOBAL_LEVEL) => { Err(PERR::WrongFnDefinition.into_err(token_pos)) } @@ -3282,12 +3293,9 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::Fn, pos) => { // Build new parse state - let interned_strings = std::mem::take(&mut state.interned_strings); - let mut new_state = ParseState::new( - self, state.scope, - interned_strings, + state.interned_strings, state.tokenizer_control.clone(), ); @@ -3301,31 +3309,23 @@ impl Engine { new_state.global_imports.clone_from(&state.global_imports); new_state .global_imports - .extend(state.imports.iter().cloned()); + .get_or_insert_with(Default::default) + .extend(state.imports.iter().flat_map(|m| m.iter()).cloned()); } - #[cfg(not(feature = "unchecked"))] - { - new_state.max_expr_depth = self.max_function_expr_depth(); - } + let options = self.options | (settings.options & LangOptions::STRICT_VAR); - let mut options = self.options; - options.set( - LangOptions::STRICT_VAR, - settings.options.contains(LangOptions::STRICT_VAR), - ); + let flags = ParseSettingFlags::FN_SCOPE + | (settings.flags + & ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES); let new_settings = ParseSettings { - at_global_level: false, - in_fn_scope: true, - #[cfg(not(feature = "no_closure"))] - in_closure: false, - is_breakable: false, - allow_statements: true, - allow_unquoted_map_properties: settings.allow_unquoted_map_properties, + flags, level: 0, options, pos, + #[cfg(not(feature = "unchecked"))] + max_expr_depth: self.max_function_expr_depth(), }; let func = self.parse_fn( @@ -3340,8 +3340,6 @@ impl Engine { ); // Restore parse state - state.interned_strings = new_state.interned_strings; - let func = func?; let hash = calc_fn_hash(None, &func.name, func.params.len()); @@ -3361,29 +3359,33 @@ impl Engine { (.., pos) => Err(PERR::MissingToken( Token::Fn.into(), - format!("following '{}'", Token::Private.syntax()), + format!("following '{}'", Token::Private), ) .into_err(pos)), } } - Token::If => self.parse_if(input, state, lib, settings.level_up()), - Token::Switch => self.parse_switch(input, state, lib, settings.level_up()), + Token::If => self.parse_if(input, state, lib, settings.level_up()?), + Token::Switch => self.parse_switch(input, state, lib, settings.level_up()?), Token::While | Token::Loop if self.allow_looping() => { - self.parse_while_loop(input, state, lib, settings.level_up()) + self.parse_while_loop(input, state, lib, settings.level_up()?) } Token::Do if self.allow_looping() => { - self.parse_do(input, state, lib, settings.level_up()) + self.parse_do(input, state, lib, settings.level_up()?) } Token::For if self.allow_looping() => { - self.parse_for(input, state, lib, settings.level_up()) + self.parse_for(input, state, lib, settings.level_up()?) } - Token::Continue if self.allow_looping() && settings.is_breakable => { + Token::Continue + if self.allow_looping() && settings.has_flag(ParseSettingFlags::BREAKABLE) => + { let pos = eat_token(input, Token::Continue); Ok(Stmt::BreakLoop(None, ASTFlags::NONE, pos)) } - Token::Break if self.allow_looping() && settings.is_breakable => { + Token::Break + if self.allow_looping() && settings.has_flag(ParseSettingFlags::BREAKABLE) => + { let pos = eat_token(input, Token::Break); let expr = match input.peek().expect(NEVER_ENDS) { @@ -3395,7 +3397,7 @@ impl Engine { (Token::SemiColon, ..) => None, // `break` with expression _ => Some( - self.parse_expr(input, state, lib, settings.level_up())? + self.parse_expr(input, state, lib, settings.level_up()?)? .into(), ), }; @@ -3426,36 +3428,40 @@ impl Engine { // `return`/`throw` at (Token::EOF, ..) => Ok(Stmt::Return(None, return_type, token_pos)), // `return`/`throw` at end of block - (Token::RightBrace, ..) if !settings.at_global_level => { + (Token::RightBrace, ..) + if !settings.has_flag(ParseSettingFlags::GLOBAL_LEVEL) => + { Ok(Stmt::Return(None, return_type, token_pos)) } // `return;` or `throw;` (Token::SemiColon, ..) => Ok(Stmt::Return(None, return_type, token_pos)), // `return` or `throw` with expression _ => { - let expr = self.parse_expr(input, state, lib, settings.level_up())?; + let expr = self.parse_expr(input, state, lib, settings.level_up()?)?; Ok(Stmt::Return(Some(expr.into()), return_type, token_pos)) } } } - Token::Try => self.parse_try_catch(input, state, lib, settings.level_up()), + Token::Try => self.parse_try_catch(input, state, lib, settings.level_up()?), - Token::Let => self.parse_let(input, state, lib, ReadWrite, false, settings.level_up()), - Token::Const => self.parse_let(input, state, lib, ReadOnly, false, settings.level_up()), + Token::Let => self.parse_let(input, state, lib, ReadWrite, false, settings.level_up()?), + Token::Const => { + self.parse_let(input, state, lib, ReadOnly, false, settings.level_up()?) + } #[cfg(not(feature = "no_module"))] - Token::Import => self.parse_import(input, state, lib, settings.level_up()), + Token::Import => self.parse_import(input, state, lib, settings.level_up()?), #[cfg(not(feature = "no_module"))] - Token::Export if !settings.at_global_level => { + Token::Export if !settings.has_flag(ParseSettingFlags::GLOBAL_LEVEL) => { Err(PERR::WrongExport.into_err(token_pos)) } #[cfg(not(feature = "no_module"))] - Token::Export => self.parse_export(input, state, lib, settings.level_up()), + Token::Export => self.parse_export(input, state, lib, settings.level_up()?), - _ => self.parse_expr_stmt(input, state, lib, settings.level_up()), + _ => self.parse_expr_stmt(input, state, lib, settings.level_up()?), } } @@ -3467,15 +3473,12 @@ impl Engine { lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - // try ... let mut settings = settings; settings.pos = eat_token(input, Token::Try); // try { try_block } - let try_block = self.parse_block(input, state, lib, settings.level_up())?; + let try_block = self.parse_block(input, state, lib, settings.level_up()?)?; // try { try_block } catch let (matched, catch_pos) = match_token(input, Token::Catch); @@ -3501,7 +3504,10 @@ impl Engine { } let name = state.get_interned_string(name); - state.stack.push(name.clone(), ()); + state + .stack + .get_or_insert_with(Default::default) + .push(name.clone(), ()); Ident { name, pos } } else { Ident { @@ -3511,11 +3517,11 @@ impl Engine { }; // try { try_block } catch ( var ) { catch_block } - let catch_block = self.parse_block(input, state, lib, settings.level_up())?; + let catch_block = self.parse_block(input, state, lib, settings.level_up()?)?; if !catch_var.is_empty() { // Remove the error variable from the stack - state.stack.rewind(state.stack.len() - 1); + state.stack.as_mut().unwrap().pop(); } Ok(Stmt::TryCatch( @@ -3542,9 +3548,6 @@ impl Engine { #[cfg(feature = "metadata")] comments: StaticVec, ) -> ParseResult { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let mut settings = settings; let (token, pos) = input.next().expect(NEVER_ENDS); @@ -3564,7 +3567,7 @@ impl Engine { eat_token(input, Token::Unit); true } - (.., pos) => return Err(PERR::FnMissingParams(name.to_string()).into_err(*pos)), + (.., pos) => return Err(PERR::FnMissingParams(name.into()).into_err(*pos)), }; let mut params = StaticVec::<(ImmutableString, _)>::new_const(); @@ -3576,12 +3579,17 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::RightParen, ..) => break, (Token::Identifier(s), pos) => { - if params.iter().any(|(p, _)| p.as_str() == &*s) { - return Err(PERR::FnDuplicatedParam(name.to_string(), s.to_string()) - .into_err(pos)); + if params.iter().any(|(p, _)| p.as_str() == *s) { + return Err( + PERR::FnDuplicatedParam(name.into(), s.to_string()).into_err(pos) + ); } + let s = state.get_interned_string(*s); - state.stack.push(s.clone(), ()); + state + .stack + .get_or_insert_with(Default::default) + .push(s.clone(), ()); params.push((s, pos)); } (Token::LexError(err), pos) => return Err(err.into_err(pos)), @@ -3608,14 +3616,14 @@ impl Engine { // Parse function body let body = match input.peek().expect(NEVER_ENDS) { (Token::LeftBrace, ..) => { - settings.is_breakable = false; - self.parse_block(input, state, lib, settings.level_up())? + settings.flags &= !ParseSettingFlags::BREAKABLE; + self.parse_block(input, state, lib, settings.level_up()?)? } - (.., pos) => return Err(PERR::FnMissingBody(name.to_string()).into_err(*pos)), + (.., pos) => return Err(PERR::FnMissingBody(name.into()).into_err(*pos)), } .into(); - let mut params: StaticVec<_> = params.into_iter().map(|(p, ..)| p).collect(); + let mut params: FnArgsVec<_> = params.into_iter().map(|(p, ..)| p).collect(); params.shrink_to_fit(); Ok(ScriptFnDef { @@ -3643,7 +3651,7 @@ impl Engine { parent: &mut ParseState, lib: &FnLib, fn_expr: Expr, - externals: StaticVec, + externals: FnArgsVec, pos: Position, ) -> Expr { // If there are no captured variables, no need to curry @@ -3656,21 +3664,15 @@ impl Engine { args.push(fn_expr); - args.extend( - externals - .iter() - .cloned() - .map(|crate::ast::Ident { name, pos }| { - let (index, is_func) = parent.access_var(&name, lib, pos); - let idx = match index { - Some(n) if !is_func && n.get() <= u8::MAX as usize => { - NonZeroU8::new(n.get() as u8) - } - _ => None, - }; - Expr::Variable((index, Default::default(), 0, name).into(), idx, pos) - }), - ); + args.extend(externals.iter().cloned().map(|Ident { name, pos }| { + let (index, is_func) = parent.access_var(&name, lib, pos); + let idx = match index { + #[allow(clippy::cast_possible_truncation)] + Some(n) if !is_func && n.get() <= u8::MAX as usize => NonZeroU8::new(n.get() as u8), + _ => None, + }; + Expr::Variable((index, Default::default(), 0, name).into(), idx, pos) + })); let expr = FnCallExpr { #[cfg(not(feature = "no_module"))] @@ -3693,15 +3695,15 @@ impl Engine { statements.push(Stmt::Share( externals .into_iter() - .map(|crate::ast::Ident { name, pos }| { + .map(|Ident { name, pos }| { let (index, _) = parent.access_var(&name, lib, pos); (name, index, pos) }) - .collect::>() + .collect::>() .into(), )); statements.push(Stmt::Expr(expr.into())); - Expr::Stmt(crate::ast::StmtBlock::new(statements, pos, Position::NONE).into()) + Expr::Stmt(StmtBlock::new(statements, pos, Position::NONE).into()) } /// Parse an anonymous function definition. @@ -3710,13 +3712,10 @@ impl Engine { &self, input: &mut TokenStream, state: &mut ParseState, - parent: &mut ParseState, + _parent: &mut ParseState, lib: &mut FnLib, settings: ParseSettings, ) -> ParseResult<(Expr, ScriptFnDef)> { - #[cfg(not(feature = "unchecked"))] - settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let mut settings = settings; let mut params_list = StaticVec::::new_const(); @@ -3725,13 +3724,17 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::Pipe, ..) => break, (Token::Identifier(s), pos) => { - if params_list.iter().any(|p| p.as_str() == &*s) { + if params_list.iter().any(|p| p.as_str() == *s) { return Err( PERR::FnDuplicatedParam(String::new(), s.to_string()).into_err(pos) ); } + let s = state.get_interned_string(*s); - state.stack.push(s.clone(), ()); + state + .stack + .get_or_insert_with(Default::default) + .push(s.clone(), ()); params_list.push(s); } (Token::LexError(err), pos) => return Err(err.into_err(pos)), @@ -3760,26 +3763,27 @@ impl Engine { } // Parse function body - settings.is_breakable = false; - let body = self.parse_stmt(input, state, lib, settings.level_up())?; + settings.flags &= !ParseSettingFlags::BREAKABLE; + let body = self.parse_stmt(input, state, lib, settings.level_up()?)?; // External variables may need to be processed in a consistent order, // so extract them into a list. #[cfg(not(feature = "no_closure"))] - let (mut params, externals) = { - let externals: StaticVec<_> = state.external_vars.iter().cloned().collect(); + let (mut params, externals) = if let Some(ref external_vars) = state.external_vars { + let externals: FnArgsVec<_> = external_vars.iter().cloned().collect(); - let mut params = StaticVec::with_capacity(params_list.len() + externals.len()); - params.extend( - externals - .iter() - .map(|crate::ast::Ident { name, .. }| name.clone()), - ); + let mut params = FnArgsVec::with_capacity(params_list.len() + externals.len()); + params.extend(externals.iter().map(|Ident { name, .. }| name.clone())); (params, externals) + } else { + ( + FnArgsVec::with_capacity(params_list.len()), + FnArgsVec::new_const(), + ) }; #[cfg(feature = "no_closure")] - let mut params = StaticVec::with_capacity(params_list.len()); + let mut params = FnArgsVec::with_capacity(params_list.len()); params.append(&mut params_list); @@ -3808,7 +3812,7 @@ impl Engine { #[cfg(not(feature = "no_closure"))] let expr = - Self::make_curry_from_externals(state, parent, lib, expr, externals, settings.pos); + Self::make_curry_from_externals(state, _parent, lib, expr, externals, settings.pos); Ok((expr, script)) } @@ -3823,24 +3827,18 @@ impl Engine { ) -> ParseResult { let mut functions = StraightHashMap::default(); - let mut options = self.options; - options.remove(LangOptions::STMT_EXPR | LangOptions::LOOP_EXPR); + let options = self.options & !LangOptions::STMT_EXPR & !LangOptions::LOOP_EXPR; #[cfg(not(feature = "no_function"))] - options.remove(LangOptions::ANON_FN); + let options = options & !LangOptions::ANON_FN; let mut settings = ParseSettings { - at_global_level: true, - #[cfg(not(feature = "no_function"))] - in_fn_scope: false, - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_closure"))] - in_closure: false, - is_breakable: false, - allow_statements: false, - allow_unquoted_map_properties: true, level: 0, + flags: ParseSettingFlags::GLOBAL_LEVEL + | ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS, options, pos: Position::START, + #[cfg(not(feature = "unchecked"))] + max_expr_depth: self.max_expr_depth(), }; process_settings(&mut settings); @@ -3851,9 +3849,7 @@ impl Engine { match input.peek().expect(NEVER_ENDS) { (Token::EOF, ..) => (), // Return error if the expression doesn't end - (token, pos) => { - return Err(LexError::UnexpectedInput(token.syntax().to_string()).into_err(*pos)) - } + (token, pos) => return Err(LexError::UnexpectedInput(token.to_string()).into_err(*pos)), } let mut statements = StmtBlockContainer::new_const(); @@ -3886,19 +3882,14 @@ impl Engine { ) -> ParseResult<(StmtBlockContainer, StaticVec>)> { let mut statements = StmtBlockContainer::new_const(); let mut functions = StraightHashMap::default(); + let mut settings = ParseSettings { - at_global_level: true, - #[cfg(not(feature = "no_function"))] - in_fn_scope: false, - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_closure"))] - in_closure: false, - is_breakable: false, - allow_statements: true, - allow_unquoted_map_properties: true, - options: self.options, level: 0, + flags: ParseSettingFlags::GLOBAL_LEVEL, + options: self.options, pos: Position::START, + #[cfg(not(feature = "unchecked"))] + max_expr_depth: self.max_expr_depth(), }; process_settings(&mut settings); diff --git a/src/serde/de.rs b/src/serde/de.rs index 1e3f3664..2cf9ec3f 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -47,7 +47,7 @@ impl<'de> DynamicDeserializer<'de> { ) } #[inline(always)] - fn deserialize_int>(self, v: crate::INT, visitor: V) -> RhaiResultOf { + fn deserialize_int>(v: crate::INT, visitor: V) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] return visitor.visit_i64(v); #[cfg(feature = "only_i32")] @@ -185,7 +185,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_i8>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.0.as_int() { - self.deserialize_int(v, visitor) + Self::deserialize_int(v, visitor) } else { self.0 .downcast_ref::() @@ -195,7 +195,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_i16>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.0.as_int() { - self.deserialize_int(v, visitor) + Self::deserialize_int(v, visitor) } else { self.0 .downcast_ref::() @@ -205,7 +205,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_i32>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.0.as_int() { - self.deserialize_int(v, visitor) + Self::deserialize_int(v, visitor) } else if cfg!(feature = "only_i32") { self.type_error() } else { @@ -217,7 +217,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_i64>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.0.as_int() { - self.deserialize_int(v, visitor) + Self::deserialize_int(v, visitor) } else if cfg!(not(feature = "only_i32")) { self.type_error() } else { @@ -229,7 +229,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_i128>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.0.as_int() { - self.deserialize_int(v, visitor) + Self::deserialize_int(v, visitor) } else if cfg!(not(feature = "only_i32")) { self.type_error() } else { @@ -241,7 +241,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_u8>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.0.as_int() { - self.deserialize_int(v, visitor) + Self::deserialize_int(v, visitor) } else { self.0 .downcast_ref::() @@ -251,7 +251,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_u16>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.0.as_int() { - self.deserialize_int(v, visitor) + Self::deserialize_int(v, visitor) } else { self.0 .downcast_ref::() @@ -261,7 +261,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_u32>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.0.as_int() { - self.deserialize_int(v, visitor) + Self::deserialize_int(v, visitor) } else { self.0 .downcast_ref::() @@ -271,7 +271,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_u64>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.0.as_int() { - self.deserialize_int(v, visitor) + Self::deserialize_int(v, visitor) } else { self.0 .downcast_ref::() @@ -281,7 +281,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { fn deserialize_u128>(self, visitor: V) -> RhaiResultOf { if let Ok(v) = self.0.as_int() { - self.deserialize_int(v, visitor) + Self::deserialize_int(v, visitor) } else { self.0 .downcast_ref::() @@ -296,21 +296,21 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { .downcast_ref::() .map_or_else(|| self.type_error(), |&x| _visitor.visit_f32(x)); - #[cfg(feature = "no_float")] - #[cfg(feature = "decimal")] + #[allow(unreachable_code)] { - use rust_decimal::prelude::ToPrimitive; + #[cfg(feature = "decimal")] + { + use rust_decimal::prelude::ToPrimitive; - return self - .0 - .downcast_ref::() - .and_then(|&x| x.to_f32()) - .map_or_else(|| self.type_error(), |v| _visitor.visit_f32(v)); + return self + .0 + .downcast_ref::() + .and_then(|&x| x.to_f32()) + .map_or_else(|| self.type_error(), |v| _visitor.visit_f32(v)); + } + + self.type_error_str("f32") } - - #[cfg(feature = "no_float")] - #[cfg(not(feature = "decimal"))] - return self.type_error_str("f32"); } fn deserialize_f64>(self, _visitor: V) -> RhaiResultOf { @@ -320,21 +320,21 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { .downcast_ref::() .map_or_else(|| self.type_error(), |&x| _visitor.visit_f64(x)); - #[cfg(feature = "no_float")] - #[cfg(feature = "decimal")] + #[allow(unreachable_code)] { - use rust_decimal::prelude::ToPrimitive; + #[cfg(feature = "decimal")] + { + use rust_decimal::prelude::ToPrimitive; - return self - .0 - .downcast_ref::() - .and_then(|&x| x.to_f64()) - .map_or_else(|| self.type_error(), |v| _visitor.visit_f64(v)); + return self + .0 + .downcast_ref::() + .and_then(|&x| x.to_f64()) + .map_or_else(|| self.type_error(), |v| _visitor.visit_f64(v)); + } + + self.type_error_str("f64") } - - #[cfg(feature = "no_float")] - #[cfg(not(feature = "decimal"))] - return self.type_error_str("f64"); } fn deserialize_char>(self, visitor: V) -> RhaiResultOf { @@ -517,10 +517,9 @@ impl<'de, ITER: Iterator> serde::de::SeqAccess<'de> seed: T, ) -> RhaiResultOf> { // Deserialize each item coming out of the iterator. - match self.iter.next() { - Some(item) => seed.deserialize(item.into_deserializer()).map(Some), - None => Ok(None), - } + self.iter.next().map_or(Ok(None), |item| { + seed.deserialize(item.into_deserializer()).map(Some) + }) } } @@ -553,10 +552,10 @@ impl<'de, K: Iterator, V: Iterator> serde: seed: S, ) -> RhaiResultOf> { // Deserialize each `Identifier` key coming out of the keys iterator. - match self.keys.next().map(<_>::into_deserializer) { - Some(d) => seed.deserialize(d).map(Some), - None => Ok(None), - } + self.keys + .next() + .map(<_>::into_deserializer) + .map_or(Ok(None), |d| seed.deserialize(d).map(Some)) } fn next_value_seed>( diff --git a/src/serde/deserialize.rs b/src/serde/deserialize.rs index d16ef71f..c8f59bd8 100644 --- a/src/serde/deserialize.rs +++ b/src/serde/deserialize.rs @@ -9,6 +9,9 @@ use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; +#[cfg(feature = "decimal")] +use num_traits::FromPrimitive; + struct DynamicVisitor; impl<'de> Visitor<'de> for DynamicVisitor { @@ -38,14 +41,43 @@ impl<'de> Visitor<'de> for DynamicVisitor { #[inline] fn visit_i64(self, v: i64) -> Result { #[cfg(not(feature = "only_i32"))] - { - Ok(v.into()) - } + return Ok(v.into()); + #[cfg(feature = "only_i32")] - if v > i32::MAX as i64 { - Ok(Dynamic::from(v)) - } else { - self.visit_i32(v as i32) + if v <= INT::MAX as i64 { + return Ok(Dynamic::from(v as INT)); + } + + #[allow(unreachable_code)] + { + #[cfg(feature = "decimal")] + if let Some(n) = rust_decimal::Decimal::from_i64(v) { + return Ok(Dynamic::from_decimal(n)); + } + + #[cfg(not(feature = "no_float"))] + return Ok(Dynamic::from_float(v as crate::FLOAT)); + + Err(Error::custom(format!("integer number too large: {v}"))) + } + } + #[inline] + fn visit_i128(self, v: i128) -> Result { + if v <= i128::from(INT::MAX) { + return Ok(Dynamic::from(v as INT)); + } + + #[allow(unreachable_code)] + { + #[cfg(feature = "decimal")] + if let Some(n) = rust_decimal::Decimal::from_i128(v) { + return Ok(Dynamic::from_decimal(n)); + } + + #[cfg(not(feature = "no_float"))] + return Ok(Dynamic::from_float(v as crate::FLOAT)); + + Err(Error::custom(format!("integer number too large: {v}"))) } } #[inline(always)] @@ -59,57 +91,105 @@ impl<'de> Visitor<'de> for DynamicVisitor { #[inline] fn visit_u32(self, v: u32) -> Result { #[cfg(not(feature = "only_i32"))] - { - Ok(INT::from(v).into()) - } + return Ok(Dynamic::from(v as INT)); + #[cfg(feature = "only_i32")] - if v > i32::MAX as u32 { - Ok(Dynamic::from(v)) - } else { - self.visit_i32(v as i32) + if v <= INT::MAX as u32 { + return Ok(Dynamic::from(v as INT)); + } + + #[allow(unreachable_code)] + { + #[cfg(feature = "decimal")] + if let Some(n) = rust_decimal::Decimal::from_u32(v) { + return Ok(Dynamic::from_decimal(n)); + } + + #[cfg(not(feature = "no_float"))] + return Ok(Dynamic::from_float(v as crate::FLOAT)); + + Err(Error::custom(format!("integer number too large: {v}"))) } } #[inline] fn visit_u64(self, v: u64) -> Result { - #[cfg(not(feature = "only_i32"))] - if v > i64::MAX as u64 { - Ok(Dynamic::from(v)) - } else { - self.visit_i64(v as i64) + if v <= INT::MAX as u64 { + return Ok(Dynamic::from(v as INT)); } - #[cfg(feature = "only_i32")] - if v > i32::MAX as u64 { - Ok(Dynamic::from(v)) - } else { - self.visit_i32(v as i32) + + #[cfg(feature = "decimal")] + if let Some(n) = rust_decimal::Decimal::from_u64(v) { + return Ok(Dynamic::from_decimal(n)); } + + #[cfg(not(feature = "no_float"))] + return Ok(Dynamic::from_float(v as crate::FLOAT)); + + #[allow(unreachable_code)] + Err(Error::custom(format!("integer number too large: {v}"))) + } + #[inline] + fn visit_u128(self, v: u128) -> Result { + if v <= INT::MAX as u128 { + return Ok(Dynamic::from(v as INT)); + } + + #[cfg(feature = "decimal")] + if let Some(n) = rust_decimal::Decimal::from_u128(v) { + return Ok(Dynamic::from_decimal(n)); + } + + #[cfg(not(feature = "no_float"))] + return Ok(Dynamic::from_float(v as crate::FLOAT)); + + #[allow(unreachable_code)] + Err(Error::custom(format!("integer number too large: {v}"))) } #[cfg(not(feature = "no_float"))] #[inline(always)] fn visit_f32(self, v: f32) -> Result { - #[cfg(not(feature = "f32_float"))] - return self.visit_f64(v as f64); - #[cfg(feature = "f32_float")] - return Ok(v.into()); + #[cfg(not(feature = "no_float"))] + return Ok((v as crate::FLOAT).into()); + + #[allow(unreachable_code)] + { + #[cfg(feature = "decimal")] + if let Some(n) = rust_decimal::Decimal::from_f32(v) { + return Ok(Dynamic::from_decimal(n)); + } + + Err(Error::custom(format!( + "floating-point number is not supported: {v}" + ))) + } } #[cfg(not(feature = "no_float"))] #[inline(always)] fn visit_f64(self, v: f64) -> Result { - #[cfg(not(feature = "f32_float"))] - return Ok(v.into()); - #[cfg(feature = "f32_float")] - return self.visit_f32(v as f32); + #[cfg(not(feature = "no_float"))] + return Ok((v as crate::FLOAT).into()); + + #[allow(unreachable_code)] + { + #[cfg(feature = "decimal")] + if let Some(n) = rust_decimal::Decimal::from_f64(v) { + return Ok(Dynamic::from_decimal(n)); + } + + Err(Error::custom(format!( + "floating-point number is not supported: {v}" + ))) + } } #[cfg(feature = "no_float")] #[cfg(feature = "decimal")] #[inline] fn visit_f32(self, v: f32) -> Result { - use rust_decimal::Decimal; use std::convert::TryFrom; - Decimal::try_from(v) + rust_decimal::Decimal::try_from(v) .map(|v| v.into()) .map_err(Error::custom) } @@ -117,10 +197,9 @@ impl<'de> Visitor<'de> for DynamicVisitor { #[cfg(feature = "decimal")] #[inline] fn visit_f64(self, v: f64) -> Result { - use rust_decimal::Decimal; use std::convert::TryFrom; - Decimal::try_from(v) + rust_decimal::Decimal::try_from(v) .map(|v| v.into()) .map_err(Error::custom) } @@ -216,10 +295,9 @@ impl<'de> Deserialize<'de> for Scope<'de> { where A: SeqAccess<'de>, { - let mut scope = match access.size_hint() { - Some(size) => Scope::with_capacity(size), - None => Scope::new(), - }; + let mut scope = access + .size_hint() + .map_or_else(Scope::new, Scope::with_capacity); while let Some(ScopeEntry { name, diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 51011585..7249f1bd 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -2,7 +2,7 @@ #![cfg(feature = "metadata")] use crate::api::type_names::format_type; -use crate::module::{calc_native_fn_hash, FuncInfo}; +use crate::module::{calc_native_fn_hash, FuncInfo, ModuleFlags}; use crate::{calc_fn_hash, Engine, FnAccess, SmartString, StaticVec, AST}; use serde::Serialize; #[cfg(feature = "no_std")] @@ -66,13 +66,13 @@ impl Ord for FnMetadata<'_> { impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { fn from(info: &'a FuncInfo) -> Self { - let base_hash = calc_fn_hash(None, &info.name, info.num_params); + let base_hash = calc_fn_hash(None, &info.metadata.name, info.metadata.num_params); let (typ, full_hash) = if info.func.is_script() { (FnType::Script, base_hash) } else { ( FnType::Native, - calc_native_fn_hash(None, &info.name, &info.param_types), + calc_native_fn_hash(None, &info.metadata.name, &info.metadata.param_types), ) }; @@ -80,12 +80,13 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { base_hash, full_hash, #[cfg(not(feature = "no_module"))] - namespace: info.namespace, - access: info.access, - name: &info.name, + namespace: info.metadata.namespace, + access: info.metadata.access, + name: &info.metadata.name, typ, - num_params: info.num_params, + num_params: info.metadata.num_params, params: info + .metadata .params_info .iter() .map(|s| { @@ -99,7 +100,7 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { }) .collect(), _dummy: None, - return_type: format_type(&info.return_type, true), + return_type: format_type(&info.metadata.return_type, true), signature: info.gen_signature().into(), doc_comments: if info.func.is_script() { #[cfg(feature = "no_function")] @@ -114,7 +115,7 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { .map(<_>::as_ref) .collect() } else { - info.comments.iter().map(<_>::as_ref).collect() + info.metadata.comments.iter().map(<_>::as_ref).collect() }, } } @@ -170,14 +171,20 @@ 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()); } + let exclude_flags = if include_standard_packages { + ModuleFlags::empty() + } else { + ModuleFlags::STANDARD_LIB + }; + engine .global_modules .iter() - .filter(|m| include_standard_packages || !m.standard) + .filter(|m| !m.flags.contains(exclude_flags)) .flat_map(|m| m.iter_fn()) .for_each(|f| { #[allow(unused_mut)] diff --git a/src/serde/ser.rs b/src/serde/ser.rs index 4104fd19..c4030dbd 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -1,6 +1,6 @@ //! Implement serialization support of [`Dynamic`][crate::Dynamic] for [`serde`]. -use crate::{Dynamic, Identifier, Position, RhaiError, RhaiResult, RhaiResultOf, ERR}; +use crate::{Dynamic, Identifier, Position, RhaiError, RhaiResult, RhaiResultOf, ERR, INT}; use serde::ser::{ Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeTuple, SerializeTupleStruct, }; @@ -9,6 +9,9 @@ use std::fmt; #[cfg(feature = "no_std")] use std::prelude::v1::*; +#[cfg(feature = "decimal")] +use num_traits::FromPrimitive; + /// Serializer for [`Dynamic`][crate::Dynamic]. pub struct DynamicSerializer { /// Buffer to hold a temporary key. @@ -37,8 +40,9 @@ impl DynamicSerializer { /// # #[cfg(not(feature = "no_index"))] /// # #[cfg(not(feature = "no_object"))] /// # #[cfg(not(feature = "no_float"))] +/// # #[cfg(not(feature = "f32_float"))] /// # { -/// use rhai::{Dynamic, Array, Map, INT}; +/// use rhai::{Dynamic, Array, Map}; /// use rhai::serde::to_dynamic; /// use serde::Serialize; /// @@ -112,151 +116,166 @@ impl Serializer for &mut DynamicSerializer { #[inline(always)] fn serialize_i8(self, v: i8) -> RhaiResultOf { - #[cfg(not(feature = "only_i32"))] - return self.serialize_i64(i64::from(v)); - #[cfg(feature = "only_i32")] - return self.serialize_i32(i32::from(v)); + Ok(INT::from(v).into()) } #[inline(always)] fn serialize_i16(self, v: i16) -> RhaiResultOf { - #[cfg(not(feature = "only_i32"))] - return self.serialize_i64(i64::from(v)); - #[cfg(feature = "only_i32")] - return self.serialize_i32(i32::from(v)); + Ok(INT::from(v).into()) } #[inline(always)] fn serialize_i32(self, v: i32) -> RhaiResultOf { - #[cfg(not(feature = "only_i32"))] - return self.serialize_i64(i64::from(v)); - #[cfg(feature = "only_i32")] - return Ok(v.into()); + Ok(INT::from(v).into()) } #[inline] fn serialize_i64(self, v: i64) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] - { - Ok(v.into()) - } + return Ok(v.into()); + #[cfg(feature = "only_i32")] - if v > i32::MAX as i64 { - Ok(Dynamic::from(v)) - } else { - self.serialize_i32(v as i32) + if v <= INT::MAX as i64 { + return Ok(Dynamic::from(v as INT)); + } + + #[allow(unreachable_code)] + { + #[cfg(feature = "decimal")] + if let Some(n) = rust_decimal::Decimal::from_i64(v) { + return Ok(Dynamic::from_decimal(n)); + } + + #[cfg(not(feature = "no_float"))] + return Ok(Dynamic::from_float(v as crate::FLOAT)); + + Err(Error::custom(format!("integer number too large: {v}"))) } } #[inline] fn serialize_i128(self, v: i128) -> RhaiResultOf { - #[cfg(not(feature = "only_i32"))] - if v > i64::MAX as i128 { - Ok(Dynamic::from(v)) - } else { - self.serialize_i64(v as i64) + if v <= i128::from(INT::MAX) { + return Ok(Dynamic::from(v as INT)); } - #[cfg(feature = "only_i32")] - if v > i32::MAX as i128 { - Ok(Dynamic::from(v)) - } else { - self.serialize_i32(v as i32) + + #[allow(unreachable_code)] + { + #[cfg(feature = "decimal")] + if let Some(n) = rust_decimal::Decimal::from_i128(v) { + return Ok(Dynamic::from_decimal(n)); + } + + #[cfg(not(feature = "no_float"))] + return Ok(Dynamic::from_float(v as crate::FLOAT)); + + Err(Error::custom(format!("integer number too large: {v}"))) } } #[inline(always)] fn serialize_u8(self, v: u8) -> RhaiResultOf { - #[cfg(not(feature = "only_i32"))] - return self.serialize_i64(i64::from(v)); - #[cfg(feature = "only_i32")] - return self.serialize_i32(i32::from(v)); + Ok(INT::from(v).into()) } #[inline(always)] fn serialize_u16(self, v: u16) -> RhaiResultOf { - #[cfg(not(feature = "only_i32"))] - return self.serialize_i64(i64::from(v)); - #[cfg(feature = "only_i32")] - return self.serialize_i32(i32::from(v)); + Ok(INT::from(v).into()) } #[inline] fn serialize_u32(self, v: u32) -> RhaiResultOf { #[cfg(not(feature = "only_i32"))] - { - self.serialize_i64(i64::from(v)) - } + return Ok(Dynamic::from(v as INT)); + #[cfg(feature = "only_i32")] - if v > i32::MAX as u32 { - Ok(Dynamic::from(v)) - } else { - self.serialize_i32(v as i32) + if v <= INT::MAX as u32 { + return Ok(Dynamic::from(v as INT)); + } + + #[allow(unreachable_code)] + { + #[cfg(feature = "decimal")] + if let Some(n) = rust_decimal::Decimal::from_u32(v) { + return Ok(Dynamic::from_decimal(n)); + } + + #[cfg(not(feature = "no_float"))] + return Ok(Dynamic::from_float(v as crate::FLOAT)); + + Err(Error::custom(format!("integer number too large: {v}"))) } } #[inline] fn serialize_u64(self, v: u64) -> RhaiResultOf { - #[cfg(not(feature = "only_i32"))] - if v > i64::MAX as u64 { - Ok(Dynamic::from(v)) - } else { - self.serialize_i64(v as i64) + if v <= INT::MAX as u64 { + return Ok(Dynamic::from(v as INT)); } - #[cfg(feature = "only_i32")] - if v > i32::MAX as u64 { - Ok(Dynamic::from(v)) - } else { - self.serialize_i32(v as i32) + + #[cfg(feature = "decimal")] + if let Some(n) = rust_decimal::Decimal::from_u64(v) { + return Ok(Dynamic::from_decimal(n)); } + + #[cfg(not(feature = "no_float"))] + return Ok(Dynamic::from_float(v as crate::FLOAT)); + + #[allow(unreachable_code)] + Err(Error::custom(format!("integer number too large: {v}"))) } #[inline] fn serialize_u128(self, v: u128) -> RhaiResultOf { - #[cfg(not(feature = "only_i32"))] - if v > i64::MAX as u128 { - Ok(Dynamic::from(v)) - } else { - self.serialize_i64(v as i64) + if v <= INT::MAX as u128 { + return Ok(Dynamic::from(v as INT)); } - #[cfg(feature = "only_i32")] - if v > i32::MAX as u128 { - Ok(Dynamic::from(v)) - } else { - self.serialize_i32(v as i32) + + #[cfg(feature = "decimal")] + if let Some(n) = rust_decimal::Decimal::from_u128(v) { + return Ok(Dynamic::from_decimal(n)); } + + #[cfg(not(feature = "no_float"))] + return Ok(Dynamic::from_float(v as crate::FLOAT)); + + #[allow(unreachable_code)] + Err(Error::custom(format!("integer number too large: {v}"))) } - #[inline] + #[inline(always)] fn serialize_f32(self, v: f32) -> RhaiResultOf { - #[cfg(any(not(feature = "no_float"), not(feature = "decimal")))] - return Ok(Dynamic::from(v)); + #[cfg(not(feature = "no_float"))] + return Ok((v as crate::FLOAT).into()); - #[cfg(feature = "no_float")] - #[cfg(feature = "decimal")] + #[allow(unreachable_code)] { - use rust_decimal::Decimal; - use std::convert::TryFrom; + #[cfg(feature = "decimal")] + if let Some(n) = rust_decimal::Decimal::from_f32(v) { + return Ok(Dynamic::from_decimal(n)); + } - Decimal::try_from(v) - .map(|v| v.into()) - .map_err(Error::custom) + Err(Error::custom(format!( + "floating-point number is not supported: {v}" + ))) } } - #[inline] + #[inline(always)] fn serialize_f64(self, v: f64) -> RhaiResultOf { - #[cfg(any(not(feature = "no_float"), not(feature = "decimal")))] - return Ok(Dynamic::from(v)); + #[cfg(not(feature = "no_float"))] + return Ok((v as crate::FLOAT).into()); - #[cfg(feature = "no_float")] - #[cfg(feature = "decimal")] + #[allow(unreachable_code)] { - use rust_decimal::Decimal; - use std::convert::TryFrom; + #[cfg(feature = "decimal")] + if let Some(n) = rust_decimal::Decimal::from_f64(v) { + return Ok(Dynamic::from_decimal(n)); + } - Decimal::try_from(v) - .map(|v| v.into()) - .map_err(Error::custom) + Err(Error::custom(format!( + "floating-point number is not supported: {v}" + ))) } } @@ -332,10 +351,7 @@ impl Serializer for &mut DynamicSerializer { _value: &T, ) -> RhaiResultOf { #[cfg(not(feature = "no_object"))] - { - let content = to_dynamic(_value)?; - make_variant(_variant, content) - } + return Ok(make_variant(_variant, to_dynamic(_value)?)); #[cfg(feature = "no_object")] return Err(ERR::ErrorMismatchDataType( "".into(), @@ -688,7 +704,7 @@ impl serde::ser::SerializeTupleVariant for TupleVariantSerializer { #[inline] fn end(self) -> RhaiResultOf { - make_variant(self.variant, self.array.into()) + Ok(make_variant(self.variant, self.array.into())) } } @@ -716,14 +732,14 @@ impl serde::ser::SerializeStructVariant for StructVariantSerializer { #[inline] fn end(self) -> RhaiResultOf { - make_variant(self.variant, self.map.into()) + Ok(make_variant(self.variant, self.map.into())) } } #[cfg(not(feature = "no_object"))] #[inline] -fn make_variant(variant: &'static str, value: Dynamic) -> RhaiResult { +fn make_variant(variant: &'static str, value: Dynamic) -> Dynamic { let mut map = crate::Map::new(); map.insert(variant.into(), value); - Ok(map.into()) + map.into() } diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index dc98ce75..4cebe930 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -58,7 +58,7 @@ impl Serialize for Dynamic { #[cfg(not(feature = "no_index"))] Union::Array(ref a, ..) => (**a).serialize(ser), #[cfg(not(feature = "no_index"))] - Union::Blob(ref a, ..) => ser.serialize_bytes(&**a), + Union::Blob(ref a, ..) => ser.serialize_bytes(a), #[cfg(not(feature = "no_object"))] Union::Map(ref m, ..) => { let mut map = ser.serialize_map(Some(m.len()))?; diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 17f9087e..8d7ee4a4 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -9,7 +9,6 @@ use crate::{Engine, Identifier, LexError, SmartString, StaticVec, INT, UNSIGNED_ #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ - borrow::Cow, cell::RefCell, char, fmt, iter::{FusedIterator, Peekable}, @@ -591,14 +590,67 @@ pub enum Token { impl fmt::Display for Token { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.syntax()) + #[allow(clippy::enum_glob_use)] + use Token::*; + + match self { + IntegerConstant(i) => write!(f, "{i}"), + #[cfg(not(feature = "no_float"))] + FloatConstant(v) => write!(f, "{v}"), + #[cfg(feature = "decimal")] + DecimalConstant(d) => write!(f, "{d}"), + StringConstant(s) => write!(f, r#""{s}""#), + InterpolatedString(..) => f.write_str("string"), + CharConstant(c) => write!(f, "{c}"), + Identifier(s) => f.write_str(s), + Reserved(s) => f.write_str(s), + #[cfg(not(feature = "no_custom_syntax"))] + Custom(s) => f.write_str(s), + LexError(err) => write!(f, "{err}"), + Comment(s) => f.write_str(s), + + EOF => f.write_str("{EOF}"), + + token => f.write_str(token.literal_syntax()), + } } } impl Token { + /// Is the token a literal symbol? + #[must_use] + pub const fn is_literal(&self) -> bool { + #[allow(clippy::enum_glob_use)] + use Token::*; + + match self { + IntegerConstant(..) => false, + #[cfg(not(feature = "no_float"))] + FloatConstant(..) => false, + #[cfg(feature = "decimal")] + DecimalConstant(..) => false, + StringConstant(..) + | InterpolatedString(..) + | CharConstant(..) + | Identifier(..) + | Reserved(..) => false, + #[cfg(not(feature = "no_custom_syntax"))] + Custom(..) => false, + LexError(..) | Comment(..) => false, + + EOF => false, + + _ => true, + } + } /// Get the literal syntax of the token. + /// + /// # Panics + /// + /// Panics if the token is not a literal symbol. #[must_use] pub const fn literal_syntax(&self) -> &'static str { + #[allow(clippy::enum_glob_use)] use Token::*; match self { @@ -690,34 +742,7 @@ impl Token { #[cfg(not(feature = "no_module"))] As => "as", - _ => "ERROR: NOT A KEYWORD", - } - } - - /// Get the syntax of the token. - #[must_use] - pub fn syntax(&self) -> Cow<'static, str> { - use Token::*; - - match self { - IntegerConstant(i) => i.to_string().into(), - #[cfg(not(feature = "no_float"))] - FloatConstant(f) => f.to_string().into(), - #[cfg(feature = "decimal")] - DecimalConstant(d) => d.to_string().into(), - StringConstant(s) => format!("\"{s}\"").into(), - InterpolatedString(..) => "string".into(), - CharConstant(c) => c.to_string().into(), - Identifier(s) => s.to_string().into(), - Reserved(s) => s.to_string().into(), - #[cfg(not(feature = "no_custom_syntax"))] - Custom(s) => s.to_string().into(), - LexError(err) => err.to_string().into(), - Comment(s) => s.to_string().into(), - - EOF => "{EOF}".into(), - - token => token.literal_syntax().into(), + _ => panic!("token is not a literal symbol"), } } @@ -802,6 +827,7 @@ impl Token { /// Reverse lookup a symbol token from a piece of syntax. #[must_use] pub fn lookup_symbol_from_syntax(syntax: &str) -> Option { + #[allow(clippy::enum_glob_use)] use Token::*; Some(match syntax { @@ -941,21 +967,20 @@ impl Token { /// (not sure about `fn` name). #[must_use] pub const fn is_next_unary(&self) -> bool { + #[allow(clippy::enum_glob_use)] use Token::*; match self { - LexError(..) | SemiColon | // ; - is unary Colon | // #{ foo: - is unary Comma | // ( ... , -expr ) - is unary - //Period | - //Elvis | - //DoubleQuestion | - //QuestionBracket | - ExclusiveRange | // .. - is unary + //Period | + //Elvis | + DoubleQuestion | // ?? - is unary + ExclusiveRange | // .. - is unary InclusiveRange | // ..= - is unary LeftBrace | // { -expr } - is unary - // RightBrace | { expr } - expr not unary & is closing + // RightBrace | // { expr } - expr not unary & is closing LeftParen | // ( -expr ) - is unary // RightParen | // ( expr ) - expr not unary & is closing LeftBracket | // [ -expr ] - is unary @@ -989,7 +1014,7 @@ impl Token { Pipe | Ampersand | If | - //Do | + //Do | While | Until | In | @@ -1000,15 +1025,21 @@ impl Token { XOr | XOrAssign | Return | - Throw => true, + Throw => true, - _ => false, + #[cfg(not(feature = "no_index"))] + QuestionBracket => true, // ?[ - is unary + + LexError(..) => true, + + _ => false, } } /// Get the precedence number of the token. #[must_use] pub const fn precedence(&self) -> Option { + #[allow(clippy::enum_glob_use)] use Token::*; Precedence::new(match self { @@ -1041,6 +1072,7 @@ impl Token { /// Does an expression bind to the right (instead of left)? #[must_use] pub const fn is_bind_right(&self) -> bool { + #[allow(clippy::enum_glob_use)] use Token::*; match self { @@ -1054,6 +1086,7 @@ impl Token { /// Is this token a standard symbol used in the language? #[must_use] pub const fn is_standard_symbol(&self) -> bool { + #[allow(clippy::enum_glob_use)] use Token::*; match self { @@ -1080,6 +1113,7 @@ impl Token { #[inline] #[must_use] pub const fn is_standard_keyword(&self) -> bool { + #[allow(clippy::enum_glob_use)] use Token::*; match self { @@ -1127,7 +1161,7 @@ impl Token { impl From for String { #[inline(always)] fn from(token: Token) -> Self { - token.syntax().into() + token.to_string() } } @@ -1476,13 +1510,13 @@ pub fn get_next_token( /// Test if the given character is a hex character. #[inline(always)] -fn is_hex_digit(c: char) -> bool { +const fn is_hex_digit(c: char) -> bool { matches!(c, 'a'..='f' | 'A'..='F' | '0'..='9') } /// Test if the given character is a numeric digit. #[inline(always)] -fn is_numeric_digit(c: char) -> bool { +const fn is_numeric_digit(c: char) -> bool { matches!(c, '0'..='9') } @@ -1662,21 +1696,8 @@ fn get_next_token_inner( }); // Parse number - return Some(( - if let Some(radix) = radix_base { - let result = &result[2..]; - - UNSIGNED_INT::from_str_radix(&result, radix) - .map(|v| v as INT) - .map_or_else( - |_| { - Token::LexError( - LERR::MalformedNumber(result.to_string()).into(), - ) - }, - Token::IntegerConstant, - ) - } else { + let token = radix_base.map_or_else( + || { let num = INT::from_str(&result).map(Token::IntegerConstant); // If integer parsing is unnecessary, try float instead @@ -1705,8 +1726,23 @@ fn get_next_token_inner( Token::LexError(LERR::MalformedNumber(result.to_string()).into()) }) }, - num_pos, - )); + |radix| { + let result = &result[2..]; + + UNSIGNED_INT::from_str_radix(result, radix) + .map(|v| v as INT) + .map_or_else( + |_| { + Token::LexError( + LERR::MalformedNumber(result.to_string()).into(), + ) + }, + Token::IntegerConstant, + ) + }, + ); + + return Some((token, num_pos)); } // letter or underscore ... @@ -1735,7 +1771,7 @@ fn get_next_token_inner( Some('\r') => { eat_next(stream, pos); // `\r\n - if let Some('\n') = stream.peek_next() { + if stream.peek_next() == Some('\n') { eat_next(stream, pos); } pos.new_line(); @@ -1763,7 +1799,7 @@ fn get_next_token_inner( // ' - character literal ('\'', '\'') => { return Some(( - Token::LexError(LERR::MalformedChar("".to_string()).into()), + Token::LexError(LERR::MalformedChar(String::new()).into()), start_pos, )) } @@ -1916,7 +1952,7 @@ fn get_next_token_inner( while let Some(c) = stream.get_next() { if c == '\r' { // \r\n - if let Some('\n') = stream.peek_next() { + if stream.peek_next() == Some('\n') { eat_next(stream, pos); } pos.new_line(); @@ -2399,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.is_empty() && 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 ) @@ -2436,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.is_empty() && 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()) }, @@ -2445,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.is_empty() && 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 !self.engine.custom_keywords.is_empty() && self.engine.custom_keywords.contains_key(token.literal_syntax()) => { - if !self.engine.disabled_symbols.is_empty() && 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 { @@ -2460,7 +2496,7 @@ impl<'a> Iterator for TokenIterator<'a> { } } // Disabled symbol - Some((token, pos)) if !self.engine.disabled_symbols.is_empty() && 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 diff --git a/src/types/bloom_filter.rs b/src/types/bloom_filter.rs index 9400879b..51be6f3f 100644 --- a/src/types/bloom_filter.rs +++ b/src/types/bloom_filter.rs @@ -105,7 +105,7 @@ impl Add for &BloomFilterU64 { impl AddAssign for BloomFilterU64 { #[inline(always)] fn add_assign(&mut self, rhs: Self) { - *self += &rhs + *self += &rhs; } } diff --git a/src/types/custom_types.rs b/src/types/custom_types.rs index ca43e1e8..8135a506 100644 --- a/src/types/custom_types.rs +++ b/src/types/custom_types.rs @@ -13,7 +13,7 @@ pub struct CustomTypeInfo { /// _(internals)_ A collection of custom types. /// Exported under the `internals` feature only. -#[derive(Clone, Hash, Default)] +#[derive(Clone, Hash)] pub struct CustomTypesCollection(BTreeMap); impl fmt::Debug for CustomTypesCollection { @@ -25,6 +25,13 @@ impl fmt::Debug for CustomTypesCollection { } } +impl Default for CustomTypesCollection { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + impl CustomTypesCollection { /// Create a new [`CustomTypesCollection`]. #[inline(always)] diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 175b293f..47fa5c22 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -158,7 +158,7 @@ impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> { #[inline] fn deref(&self) -> &Self::Target { match self.0 { - DynamicWriteLockInner::Reference(ref reference) => *reference, + DynamicWriteLockInner::Reference(ref reference) => reference, #[cfg(not(feature = "no_closure"))] DynamicWriteLockInner::Guard(ref guard) => guard.downcast_ref().expect(CHECKED), } @@ -169,7 +169,7 @@ impl<'d, T: Any + Clone> DerefMut for DynamicWriteLock<'d, T> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { match self.0 { - DynamicWriteLockInner::Reference(ref mut reference) => *reference, + DynamicWriteLockInner::Reference(ref mut reference) => reference, #[cfg(not(feature = "no_closure"))] DynamicWriteLockInner::Guard(ref mut guard) => guard.downcast_mut().expect(CHECKED), } @@ -640,6 +640,7 @@ impl fmt::Debug for Dynamic { } } +#[allow(clippy::enum_glob_use)] use AccessMode::*; impl Clone for Dynamic { @@ -1088,7 +1089,7 @@ impl Dynamic { pub fn from(value: T) -> Self { // Coded this way in order to maximally leverage potentials for dead-code removal. - reify!(value, |v: Dynamic| return v); + reify!(value, |v: Self| return v); reify!(value, |v: INT| return v.into()); #[cfg(not(feature = "no_float"))] @@ -1187,7 +1188,7 @@ impl Dynamic { #[cfg(not(feature = "no_closure"))] self.flatten_in_place(); - if TypeId::of::() == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return Some(reify!(self => T)); } if TypeId::of::() == TypeId::of::<()>() { @@ -1309,7 +1310,7 @@ impl Dynamic { #[must_use] pub fn cast(self) -> T { // Bail out early if the return type needs no cast - if TypeId::of::() == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { return reify!(self => T); } @@ -1710,10 +1711,9 @@ impl Dynamic { match self.0 { Union::Unit(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Unit(..) => true, - _ => false, - }, + Union::Shared(ref cell, ..) => { + matches!(crate::func::locked_read(cell).0, Union::Unit(..)) + } _ => false, } } @@ -1724,10 +1724,9 @@ impl Dynamic { match self.0 { Union::Int(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Int(..) => true, - _ => false, - }, + Union::Shared(ref cell, ..) => { + matches!(crate::func::locked_read(cell).0, Union::Int(..)) + } _ => false, } } @@ -1741,10 +1740,9 @@ impl Dynamic { match self.0 { Union::Float(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Float(..) => true, - _ => false, - }, + Union::Shared(ref cell, ..) => { + matches!(crate::func::locked_read(cell).0, Union::Float(..)) + } _ => false, } } @@ -1758,10 +1756,9 @@ impl Dynamic { match self.0 { Union::Decimal(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Decimal(..) => true, - _ => false, - }, + Union::Shared(ref cell, ..) => { + matches!(crate::func::locked_read(cell).0, Union::Decimal(..)) + } _ => false, } } @@ -1772,10 +1769,9 @@ impl Dynamic { match self.0 { Union::Bool(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Bool(..) => true, - _ => false, - }, + Union::Shared(ref cell, ..) => { + matches!(crate::func::locked_read(cell).0, Union::Bool(..)) + } _ => false, } } @@ -1786,10 +1782,9 @@ impl Dynamic { match self.0 { Union::Char(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Char(..) => true, - _ => false, - }, + Union::Shared(ref cell, ..) => { + matches!(crate::func::locked_read(cell).0, Union::Char(..)) + } _ => false, } } @@ -1800,10 +1795,9 @@ impl Dynamic { match self.0 { Union::Str(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Str(..) => true, - _ => false, - }, + Union::Shared(ref cell, ..) => { + matches!(crate::func::locked_read(cell).0, Union::Str(..)) + } _ => false, } } @@ -1817,10 +1811,9 @@ impl Dynamic { match self.0 { Union::Array(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Array(..) => true, - _ => false, - }, + Union::Shared(ref cell, ..) => { + matches!(crate::func::locked_read(cell).0, Union::Array(..)) + } _ => false, } } @@ -1834,10 +1827,9 @@ impl Dynamic { match self.0 { Union::Blob(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Blob(..) => true, - _ => false, - }, + Union::Shared(ref cell, ..) => { + matches!(crate::func::locked_read(cell).0, Union::Blob(..)) + } _ => false, } } @@ -1851,10 +1843,9 @@ impl Dynamic { match self.0 { Union::Map(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Map(..) => true, - _ => false, - }, + Union::Shared(ref cell, ..) => { + matches!(crate::func::locked_read(cell).0, Union::Map(..)) + } _ => false, } } @@ -1865,10 +1856,9 @@ impl Dynamic { match self.0 { Union::FnPtr(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::FnPtr(..) => true, - _ => false, - }, + Union::Shared(ref cell, ..) => { + matches!(crate::func::locked_read(cell).0, Union::FnPtr(..)) + } _ => false, } } @@ -1882,10 +1872,9 @@ impl Dynamic { match self.0 { Union::TimeStamp(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::TimeStamp(..) => true, - _ => false, - }, + Union::Shared(ref cell, ..) => { + matches!(crate::func::locked_read(cell).0, Union::TimeStamp(..)) + } _ => false, } } diff --git a/src/types/error.rs b/src/types/error.rs index 411e4ed1..bc65040b 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -299,10 +299,7 @@ impl EvalAltResult { #[inline(never)] #[must_use] pub const fn is_pseudo_error(&self) -> bool { - match self { - Self::LoopBreak(..) | Self::Return(..) => true, - _ => false, - } + matches!(self, Self::LoopBreak(..) | Self::Return(..)) } /// Can this error be caught? #[cold] @@ -357,20 +354,17 @@ impl EvalAltResult { #[inline(never)] #[must_use] pub const fn is_system_exception(&self) -> bool { - match self { - Self::ErrorSystem(..) => true, - Self::ErrorParsing(..) => true, - - Self::ErrorCustomSyntax(..) - | Self::ErrorTooManyOperations(..) - | Self::ErrorTooManyModules(..) - | Self::ErrorStackOverflow(..) - | Self::ErrorDataTooLarge(..) => true, - - Self::ErrorTerminated(..) => true, - - _ => false, - } + matches!( + self, + Self::ErrorSystem(..) + | Self::ErrorParsing(..) + | Self::ErrorCustomSyntax(..) + | Self::ErrorTooManyOperations(..) + | Self::ErrorTooManyModules(..) + | Self::ErrorStackOverflow(..) + | Self::ErrorDataTooLarge(..) + | Self::ErrorTerminated(..) + ) } /// Get the [position][Position] of this error. #[cfg(not(feature = "no_object"))] @@ -459,7 +453,6 @@ impl EvalAltResult { /// Unwrap this error and get the very base error. #[cold] #[inline(never)] - #[must_use] pub fn unwrap_inner(&self) -> &Self { match self { Self::ErrorInFunctionCall(.., err, _) | Self::ErrorInModule(.., err, _) => { diff --git a/src/types/float.rs b/src/types/float.rs index a3b934a6..8cfeed77 100644 --- a/src/types/float.rs +++ b/src/types/float.rs @@ -14,7 +14,7 @@ use num_traits::float::FloatCore as Float; /// A type that wraps a floating-point number and implements [`Hash`]. /// /// Not available under `no_float`. -#[derive(Clone, Copy, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Eq, PartialEq, PartialOrd)] pub struct FloatWrapper(F); impl Hash for FloatWrapper { diff --git a/src/types/immutable_string.rs b/src/types/immutable_string.rs index f6dba61a..0d251081 100644 --- a/src/types/immutable_string.rs +++ b/src/types/immutable_string.rs @@ -147,7 +147,6 @@ impl FromStr for ImmutableString { type Err = (); #[inline(always)] - #[must_use] fn from_str(s: &str) -> Result { let s: SmartString = s.into(); Ok(Self(s.into())) diff --git a/src/types/interner.rs b/src/types/interner.rs index cd090fe9..430605e8 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -12,7 +12,6 @@ use std::prelude::v1::*; use std::{ fmt, hash::{Hash, Hasher}, - marker::PhantomData, ops::AddAssign, }; @@ -24,28 +23,23 @@ pub const MAX_STRING_LEN: usize = 24; /// _(internals)_ A cache for interned strings. /// Exported under the `internals` feature only. -pub struct StringsInterner<'a> { - /// Maximum number of strings interned. - pub capacity: usize, - /// Maximum string length. - pub max_string_len: usize, +#[derive(Clone)] +#[must_use] +pub struct StringsInterner { /// Cached strings. cache: StraightHashMap, /// Bloom filter to avoid caching "one-hit wonders". - filter: BloomFilterU64, - /// Take care of the lifetime parameter. - dummy: PhantomData<&'a ()>, + bloom_filter: BloomFilterU64, } -impl Default for StringsInterner<'_> { +impl Default for StringsInterner { #[inline(always)] - #[must_use] fn default() -> Self { Self::new() } } -impl fmt::Debug for StringsInterner<'_> { +impl fmt::Debug for StringsInterner { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -53,17 +47,13 @@ impl fmt::Debug for StringsInterner<'_> { } } -impl StringsInterner<'_> { +impl StringsInterner { /// Create a new [`StringsInterner`]. #[inline(always)] - #[must_use] pub fn new() -> Self { Self { - capacity: MAX_INTERNED_STRINGS, - max_string_len: MAX_STRING_LEN, cache: StraightHashMap::default(), - filter: BloomFilterU64::new(), - dummy: PhantomData, + bloom_filter: BloomFilterU64::new(), } } @@ -79,22 +69,26 @@ impl StringsInterner<'_> { #[must_use] pub fn get_with_mapper>( &mut self, - id: &str, + category: &str, mapper: impl FnOnce(S) -> ImmutableString, text: S, ) -> ImmutableString { let key = text.as_ref(); let hasher = &mut get_hasher(); - id.hash(hasher); + category.hash(hasher); key.hash(hasher); let hash = hasher.finish(); // Cache long strings only on the second try to avoid caching "one-hit wonders". - if key.len() > MAX_STRING_LEN && self.filter.is_absent_and_set(hash) { + if key.len() > MAX_STRING_LEN && self.bloom_filter.is_absent_and_set(hash) { return mapper(text); } + if self.cache.is_empty() { + self.cache.reserve(MAX_INTERNED_STRINGS); + } + let result = match self.cache.entry(hash) { Entry::Occupied(e) => return e.get().clone(), Entry::Vacant(e) => { @@ -114,26 +108,22 @@ impl StringsInterner<'_> { } /// If the interner is over capacity, remove the longest entry that has the lowest count - fn throttle_cache(&mut self, hash: u64) { - if self.cache.len() <= self.capacity { + #[inline] + fn throttle_cache(&mut self, skip_hash: u64) { + if self.cache.len() <= MAX_INTERNED_STRINGS { return; } // Leave some buffer to grow when shrinking the cache. // We leave at least two entries, one for the empty string, and one for the string // that has just been inserted. - let max = if self.capacity < 5 { - 2 - } else { - self.capacity - 3 - }; - - while self.cache.len() > max { + while self.cache.len() > MAX_INTERNED_STRINGS - 3 { let (_, _, n) = self .cache .iter() .fold((0, usize::MAX, 0), |(x, c, n), (&k, v)| { - if k != hash && (v.strong_count() < c || (v.strong_count() == c && v.len() > x)) + if k != skip_hash + && (v.strong_count() < c || (v.strong_count() == c && v.len() > x)) { (v.len(), v.strong_count(), k) } else { @@ -169,14 +159,14 @@ impl StringsInterner<'_> { } } -impl AddAssign for StringsInterner<'_> { +impl AddAssign for StringsInterner { #[inline(always)] fn add_assign(&mut self, rhs: Self) { self.cache.extend(rhs.cache.into_iter()); } } -impl AddAssign<&Self> for StringsInterner<'_> { +impl AddAssign<&Self> for StringsInterner { #[inline(always)] fn add_assign(&mut self, rhs: &Self) { self.cache diff --git a/src/types/parse_error.rs b/src/types/parse_error.rs index 86fedfdf..1457a1c4 100644 --- a/src/types/parse_error.rs +++ b/src/types/parse_error.rs @@ -175,7 +175,6 @@ impl ParseErrorType { /// Make a [`ParseError`] using the current type and position. #[cold] #[inline(never)] - #[must_use] pub(crate) fn into_err(self, pos: Position) -> ParseError { ParseError(self.into(), pos) } @@ -299,7 +298,6 @@ impl ParseError { /// Get the [type][ParseErrorType] of this parse error. #[cold] #[inline(never)] - #[must_use] pub const fn err_type(&self) -> &ParseErrorType { &self.0 } @@ -316,7 +314,7 @@ impl From for RhaiError { #[cold] #[inline(never)] fn from(err: ParseErrorType) -> Self { - Box::new(err.into()) + Self::new(err.into()) } } @@ -332,7 +330,7 @@ impl From for RhaiError { #[cold] #[inline(never)] fn from(err: ParseError) -> Self { - Box::new(err.into()) + Self::new(err.into()) } } diff --git a/src/types/restore.rs b/src/types/restore.rs index 6d0ad595..6d99848a 100644 --- a/src/types/restore.rs +++ b/src/types/restore.rs @@ -6,12 +6,12 @@ use std::prelude::v1::*; /// Run custom restoration logic upon the end of scope. #[must_use] -pub struct RestoreOnDrop<'a, T, R: FnOnce(&mut T)> { +pub struct RestoreOnDrop<'a, T: ?Sized, R: FnOnce(&mut T)> { value: &'a mut T, restore: Option, } -impl<'a, T, R: FnOnce(&mut T)> RestoreOnDrop<'a, T, R> { +impl<'a, T: ?Sized, R: FnOnce(&mut T)> RestoreOnDrop<'a, T, R> { /// Create a new [`RestoreOnDrop`] that locks a mutable reference and runs restoration logic at /// the end of scope only when `need_restore` is `true`. /// @@ -39,7 +39,7 @@ impl<'a, T, R: FnOnce(&mut T)> RestoreOnDrop<'a, T, R> { } } -impl<'a, T, R: FnOnce(&mut T)> Drop for RestoreOnDrop<'a, T, R> { +impl<'a, T: ?Sized, R: FnOnce(&mut T)> Drop for RestoreOnDrop<'a, T, R> { #[inline(always)] fn drop(&mut self) { if let Some(restore) = self.restore.take() { @@ -48,7 +48,7 @@ impl<'a, T, R: FnOnce(&mut T)> Drop for RestoreOnDrop<'a, T, R> { } } -impl<'a, T, R: FnOnce(&mut T)> Deref for RestoreOnDrop<'a, T, R> { +impl<'a, T: ?Sized, R: FnOnce(&mut T)> Deref for RestoreOnDrop<'a, T, R> { type Target = T; #[inline(always)] @@ -57,7 +57,7 @@ impl<'a, T, R: FnOnce(&mut T)> Deref for RestoreOnDrop<'a, T, R> { } } -impl<'a, T, R: FnOnce(&mut T)> DerefMut for RestoreOnDrop<'a, T, R> { +impl<'a, T: ?Sized, R: FnOnce(&mut T)> DerefMut for RestoreOnDrop<'a, T, R> { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { self.value diff --git a/src/types/scope.rs b/src/types/scope.rs index e217a698..639195cb 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -353,6 +353,43 @@ impl Scope<'_> { self.values.push(value); self } + /// Remove the last entry from the [`Scope`]. + /// + /// # Panics + /// + /// Panics is the [`Scope`] is empty. + /// + /// # Example + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// my_scope.push("y", 123_i64); + /// assert!(my_scope.contains("x")); + /// assert!(my_scope.contains("y")); + /// assert_eq!(my_scope.len(), 2); + /// + /// my_scope.pop(); + /// assert!(my_scope.contains("x")); + /// assert!(!my_scope.contains("y")); + /// assert_eq!(my_scope.len(), 1); + /// + /// my_scope.pop(); + /// assert!(!my_scope.contains("x")); + /// assert!(!my_scope.contains("y")); + /// assert_eq!(my_scope.len(), 0); + /// assert!(my_scope.is_empty()); + /// ``` + #[inline(always)] + pub fn pop(&mut self) -> &mut Self { + self.names.pop().expect("`Scope` must not be empty"); + self.values.pop().expect("`Scope` must not be empty"); + self.aliases.pop().expect("`Scope` must not be empty"); + self + } /// Truncate (rewind) the [`Scope`] to a previous size. /// /// # Example diff --git a/tests/closures.rs b/tests/closures.rs index 69df04e1..d2576e80 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -14,7 +14,7 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { engine.register_raw_fn( "call_with_arg", - &[TypeId::of::(), TypeId::of::()], + [TypeId::of::(), TypeId::of::()], |context, args| { let fn_ptr = std::mem::take(args[0]).cast::(); fn_ptr.call_raw(&context, None, [std::mem::take(args[1])]) @@ -165,7 +165,7 @@ fn test_closures() -> Result<(), Box> { engine.register_raw_fn( "custom_call", - &[TypeId::of::(), TypeId::of::()], + [TypeId::of::(), TypeId::of::()], |context, args| { let func = take(args[1]).cast::(); @@ -348,7 +348,7 @@ fn test_closures_shared_obj() -> Result<(), Box> { let p1 = Rc::new(RefCell::new(41)); let p2 = Rc::new(RefCell::new(1)); - f(p1.clone(), p2.clone())?; + f(p1.clone(), p2)?; assert_eq!(*p1.borrow(), 42); diff --git a/tests/custom_syntax.rs b/tests/custom_syntax.rs index dacca728..899d83a9 100644 --- a/tests/custom_syntax.rs +++ b/tests/custom_syntax.rs @@ -266,17 +266,18 @@ fn test_custom_syntax_raw() -> Result<(), Box> { *state = Dynamic::FALSE; Ok(Some("$ident$".into())) } - 2 => match stream[1].as_str() { - "world" if state.as_bool().unwrap_or(false) => Ok(Some("$$world".into())), - "world" => Ok(Some("$$hello".into())), - "kitty" => { - *state = (42 as INT).into(); - Ok(None) + 2 => { + match stream[1].as_str() { + "world" if state.as_bool().unwrap_or(false) => Ok(Some("$$world".into())), + "world" => Ok(Some("$$hello".into())), + "kitty" => { + *state = (42 as INT).into(); + Ok(None) + } + s => Err(LexError::ImproperSymbol(s.to_string(), String::new()) + .into_err(Position::NONE)), } - s => Err(LexError::ImproperSymbol(s.to_string(), String::new()) - .into_err(Position::NONE) - .into()), - }, + } _ => unreachable!(), }, true, diff --git a/tests/debugging.rs b/tests/debugging.rs index a35d41fc..762ed9d2 100644 --- a/tests/debugging.rs +++ b/tests/debugging.rs @@ -59,13 +59,13 @@ fn test_debugger_state() -> Result<(), Box> { // Print debugger state - which is an object map println!( "Current state = {}", - context.global_runtime_state_mut().debugger.state() + context.global_runtime_state_mut().debugger().state() ); // Modify state let mut state = context .global_runtime_state_mut() - .debugger + .debugger_mut() .state_mut() .write_lock::() .unwrap(); diff --git a/tests/serde.rs b/tests/serde.rs index 88483ec4..f3c7914f 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -13,22 +13,20 @@ use rhai::Array; use rhai::Map; #[cfg(not(feature = "no_float"))] use rhai::FLOAT; -#[cfg(feature = "no_float")] #[cfg(feature = "decimal")] use rust_decimal::Decimal; #[test] fn test_serde_ser_primary_types() -> Result<(), Box> { assert!(to_dynamic(42_u64)?.is_int()); - assert!(to_dynamic(u64::MAX)?.is::()); assert!(to_dynamic(42 as INT)?.is_int()); assert!(to_dynamic(true)?.is_bool()); assert!(to_dynamic(())?.is_unit()); #[cfg(not(feature = "no_float"))] { - assert!(to_dynamic(123.456_f64)?.is::()); - assert!(to_dynamic(123.456_f32)?.is::()); + assert!(to_dynamic(123.456_f64)?.is::()); + assert!(to_dynamic(123.456_f32)?.is::()); } #[cfg(feature = "no_float")] @@ -749,6 +747,31 @@ fn test_serde_json() -> serde_json::Result<()> { Ok(()) } +#[test] +#[cfg(feature = "metadata")] +#[cfg(feature = "decimal")] +#[cfg(not(feature = "no_float"))] +fn test_serde_json_numbers() -> serde_json::Result<()> { + use std::str::FromStr; + + let d: Dynamic = serde_json::from_str("100000000000")?; + assert!(d.is::()); + assert_eq!(d.as_int().unwrap(), 100000000000); + + let d: Dynamic = serde_json::from_str("10000000000000000000")?; + assert!(d.is::()); + assert_eq!( + d.as_decimal().unwrap(), + Decimal::from_str("10000000000000000000").unwrap() + ); + + let d: Dynamic = serde_json::from_str("10000000000000000000000000")?; + assert!(d.is::()); + assert_eq!(d.as_float().unwrap(), 10000000000000000000000000.0); + + Ok(()) +} + #[test] #[cfg(not(feature = "no_object"))] fn test_serde_optional() -> Result<(), Box> { @@ -825,7 +848,14 @@ fn test_serde_blob() -> Result<(), Box> { fn test_serde_json_borrowed_string() { let value = json!({ "a": "b" }); println!("value: {value:?}"); - let _: Dynamic = serde_json::from_value(value).unwrap(); + + let result: Dynamic = serde_json::from_value(value.clone()).unwrap(); + println!("result: {result:?}"); + + let value2 = serde_json::to_value(&result).unwrap(); + println!("value2: {value2:?}"); + + assert_eq!(value, value2); } #[test]