diff --git a/src/api/eval.rs b/src/api/eval.rs index 51dadfec..402b696b 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -209,9 +209,8 @@ impl Engine { level: usize, ) -> RhaiResult { let mut state = EvalState::new(); - if ast.source_raw().is_some() { - global.source = ast.source_raw().cloned(); - } + global.source = ast.source_raw().clone(); + #[cfg(not(feature = "no_module"))] { global.embedded_module_resolver = ast.resolver().cloned(); diff --git a/src/api/run.rs b/src/api/run.rs index aa27683f..ce33c049 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -46,9 +46,8 @@ impl Engine { pub fn run_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> { let global = &mut GlobalRuntimeState::new(); let mut state = EvalState::new(); - if ast.source_raw().is_some() { - global.source = ast.source_raw().cloned(); - } + global.source = ast.source_raw().clone(); + #[cfg(not(feature = "no_module"))] { global.embedded_module_resolver = ast.resolver().cloned(); diff --git a/src/ast/ast.rs b/src/ast/ast.rs index 82ca8087..1f9ec131 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -17,7 +17,8 @@ use std::{ #[derive(Debug, Clone)] pub struct AST { /// Source of the [`AST`]. - source: Option, + /// No source if string is empty. + source: Identifier, /// Global statements. body: StmtBlock, /// Script-defined functions. @@ -45,7 +46,7 @@ impl AST { #[cfg(not(feature = "no_function"))] functions: impl Into>, ) -> Self { Self { - source: None, + source: Identifier::new_const(), body: StmtBlock::new(statements, Position::NONE), #[cfg(not(feature = "no_function"))] functions: functions.into(), @@ -63,7 +64,7 @@ impl AST { #[cfg(not(feature = "no_function"))] functions: impl Into>, ) -> Self { Self { - source: None, + source: Identifier::new_const(), body: StmtBlock::new(statements, Position::NONE), #[cfg(not(feature = "no_function"))] functions: functions.into(), @@ -111,7 +112,7 @@ impl AST { #[must_use] pub fn empty() -> Self { Self { - source: None, + source: Identifier::new_const(), body: StmtBlock::NONE, #[cfg(not(feature = "no_function"))] functions: crate::Module::new().into(), @@ -123,13 +124,17 @@ impl AST { #[inline(always)] #[must_use] pub fn source(&self) -> Option<&str> { - self.source.as_ref().map(|s| s.as_str()) + if self.source.is_empty() { + None + } else { + Some(&self.source) + } } /// Get a reference to the source. #[inline(always)] #[must_use] - pub(crate) fn source_raw(&self) -> Option<&Identifier> { - self.source.as_ref() + pub(crate) fn source_raw(&self) -> &Identifier { + &self.source } /// Set the source. #[inline] @@ -139,13 +144,13 @@ impl AST { crate::Shared::get_mut(&mut self.functions) .as_mut() .map(|m| m.set_id(source.clone())); - self.source = Some(source); + self.source = source; self } /// Clear the source. #[inline(always)] pub fn clear_source(&mut self) -> &mut Self { - self.source = None; + self.source.clear(); self } /// Get the statements. @@ -467,8 +472,6 @@ impl AST { (true, true) => StmtBlock::NONE, }; - let source = other.source.clone().or_else(|| self.source.clone()); - #[cfg(not(feature = "no_function"))] let functions = { let mut functions = self.functions.as_ref().clone(); @@ -476,12 +479,12 @@ impl AST { functions }; - if let Some(source) = source { + if !other.source.is_empty() { Self::new_with_source( merged, #[cfg(not(feature = "no_function"))] functions, - source, + other.source.clone(), ) } else { Self::new( diff --git a/src/engine.rs b/src/engine.rs index 90f06b62..0519a4b3 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2,6 +2,7 @@ use crate::api::custom_syntax::CustomSyntax; use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; +use crate::func::call::FnResolutionCache; use crate::func::native::{OnDebugCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback}; use crate::func::{get_hasher, CallableFunction, IteratorFn}; use crate::module::Namespace; @@ -507,7 +508,8 @@ pub struct GlobalRuntimeState { /// Stack of imported [modules][Module]. modules: StaticVec>, /// Source of the current context. - pub source: Option, + /// No source if the string is empty. + pub source: Identifier, /// Number of operations performed. pub num_operations: u64, /// Number of modules loaded. @@ -539,7 +541,7 @@ impl GlobalRuntimeState { Self { keys: StaticVec::new_const(), modules: StaticVec::new_const(), - source: None, + source: Identifier::new_const(), num_operations: 0, num_modules_loaded: 0, #[cfg(not(feature = "no_module"))] @@ -629,11 +631,11 @@ impl GlobalRuntimeState { /// Get the specified function via its hash key from the stack of globally-imported [modules][Module]. #[inline] #[must_use] - pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&Identifier>)> { + pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&str>)> { self.modules .iter() .rev() - .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) + .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id()))) } /// Does the specified [`TypeId`][std::any::TypeId] iterator exist in the stack of /// globally-imported [modules][Module]? @@ -746,20 +748,6 @@ impl fmt::Debug for GlobalRuntimeState { } } -/// _(internals)_ An entry in a function resolution cache. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone)] -pub struct FnResolutionCacheEntry { - /// Function. - pub func: CallableFunction, - /// Optional source. - pub source: Option>, -} - -/// _(internals)_ A function resolution cache. -/// Exported under the `internals` feature only. -pub type FnResolutionCache = BTreeMap>>; - /// _(internals)_ A type that holds all the current states of the [`Engine`]. /// Exported under the `internals` feature only. #[derive(Debug, Clone)] @@ -844,7 +832,11 @@ impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> { #[inline(always)] #[must_use] pub fn source(&self) -> Option<&str> { - self.global.source.as_ref().map(|s| s.as_str()) + if self.global.source.is_empty() { + None + } else { + Some(&self.global.source) + } } /// The current [`Scope`]. #[inline(always)] @@ -3041,8 +3033,8 @@ impl Engine { err_map.insert("message".into(), err.to_string().into()); - if let Some(ref source) = global.source { - err_map.insert("source".into(), source.as_str().into()); + if !global.source.is_empty() { + err_map.insert("source".into(), global.source.clone().into()); } if err_pos.is_none() { @@ -3174,7 +3166,11 @@ impl Engine { { use crate::ModuleResolver; - let source = global.source.as_ref().map(|s| s.as_str()); + let source = if global.source.is_empty() { + None + } else { + Some(global.source.as_str()) + }; let path_pos = expr.position(); let module = global diff --git a/src/func/call.rs b/src/func/call.rs index fd20e4ad..99d241cc 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -6,9 +6,8 @@ use super::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; use crate::api::default_limits::MAX_DYNAMIC_PARAMETERS; use crate::ast::{Expr, FnCallHashes, Stmt}; use crate::engine::{ - EvalState, FnResolutionCacheEntry, GlobalRuntimeState, KEYWORD_DEBUG, KEYWORD_EVAL, - KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, - KEYWORD_TYPE_OF, + EvalState, GlobalRuntimeState, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, + KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; use crate::module::Namespace; use crate::tokenizer::Token; @@ -20,6 +19,7 @@ use crate::{ use std::prelude::v1::*; use std::{ any::{type_name, TypeId}, + collections::BTreeMap, convert::TryFrom, mem, }; @@ -125,6 +125,24 @@ pub fn ensure_no_data_race( Ok(()) } +/// _(internals)_ An entry in a function resolution cache. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone)] +pub struct FnResolutionCacheEntry { + /// Function. + pub func: CallableFunction, + /// Optional source. + /// No source if the string is empty. + pub source: Identifier, +} + +/// _(internals)_ A function resolution cache. +/// Exported under the `internals` feature only. +/// +/// [`FnResolutionCacheEntry`] is [`Box`]ed in order to pack as many entries inside a single B-Tree +/// level as possible. +pub type FnResolutionCache = BTreeMap>>; + impl Engine { /// Generate the signature for a function call. #[inline] @@ -206,14 +224,14 @@ impl Engine { .find_map(|m| { m.get_fn(hash).cloned().map(|func| FnResolutionCacheEntry { func, - source: m.id_raw().map(|s| s.to_string().into_boxed_str()), + source: m.id_raw().clone(), }) }) .or_else(|| { self.global_modules.iter().find_map(|m| { m.get_fn(hash).cloned().map(|func| FnResolutionCacheEntry { func, - source: m.id_raw().map(|s| s.to_string().into_boxed_str()), + source: m.id_raw().clone(), }) }) }) @@ -222,7 +240,8 @@ impl Engine { .get_fn(hash) .map(|(func, source)| FnResolutionCacheEntry { func: func.clone(), - source: source.map(|s| s.to_string().into_boxed_str()), + source: source + .map_or_else(|| Identifier::new_const(), Into::into), }) }) .or_else(|| { @@ -230,7 +249,7 @@ impl Engine { m.get_qualified_fn(hash).cloned().map(|func| { FnResolutionCacheEntry { func, - source: m.id_raw().map(|s| s.to_string().into_boxed_str()), + source: m.id_raw().clone(), } }) }) @@ -253,7 +272,7 @@ impl Engine { func: CallableFunction::from_method( Box::new(f) as Box ), - source: None, + source: Identifier::new_const(), } }) } else { @@ -265,7 +284,7 @@ impl Engine { func: CallableFunction::from_method( Box::new(f) as Box ), - source: None, + source: Identifier::new_const(), }) } .map(Box::new) @@ -353,10 +372,15 @@ impl Engine { } // Run external function - let source = source - .as_ref() - .map(|s| &**s) - .or_else(|| parent_source.as_ref().map(|s| s.as_str())); + let source = if source.is_empty() { + if parent_source.is_empty() { + None + } else { + Some(parent_source.as_str()) + } + } else { + Some(source.as_str()) + }; let context = (self, name, source, &*global, lib, pos).into(); @@ -400,7 +424,11 @@ impl Engine { pos, ) })?; - let source = global.source.as_ref().map(|s| s.as_str()); + let source = if global.source.is_empty() { + None + } else { + Some(global.source.as_str()) + }; (debug(&text, source, pos).into(), false) } else { (Dynamic::UNIT, false) @@ -572,7 +600,7 @@ impl Engine { // Script-defined function call? #[cfg(not(feature = "no_function"))] - if let Some(FnResolutionCacheEntry { func, source }) = self + if let Some(FnResolutionCacheEntry { func, mut source }) = self .resolve_fn( global, state, @@ -603,13 +631,12 @@ impl Engine { } }; + mem::swap(&mut global.source, &mut source); + let result = if _is_method_call { // Method call of script function - map first argument to `this` let (first_arg, rest_args) = args.split_first_mut().expect("not empty"); - let orig_source = global.source.take(); - global.source = source.map(Into::into); - let level = _level + 1; let result = self.call_script_fn( @@ -625,9 +652,6 @@ impl Engine { level, ); - // Restore the original source - global.source = orig_source; - result? } else { // Normal call of script function @@ -641,18 +665,12 @@ impl Engine { .change_first_arg_to_copy(args); } - let orig_source = global.source.take(); - global.source = source.map(Into::into); - let level = _level + 1; let result = self.call_script_fn( scope, global, state, lib, &mut None, func, args, pos, true, level, ); - // Restore the original source - global.source = orig_source; - // Restore the original reference if let Some(bk) = backup { bk.restore_first_arg(args) @@ -661,6 +679,9 @@ impl Engine { result? }; + // Restore the original source + mem::swap(&mut global.source, &mut source); + return Ok((result, false)); } @@ -1058,11 +1079,7 @@ impl Engine { return result.map_err(|err| { ERR::ErrorInFunctionCall( KEYWORD_EVAL.to_string(), - global - .source - .as_ref() - .map(Identifier::to_string) - .unwrap_or_default(), + global.source.to_string(), err, pos, ) @@ -1265,7 +1282,7 @@ impl Engine { } else { let new_scope = &mut Scope::new(); - let mut source = module.id_raw().cloned(); + let mut source = module.id_raw().clone(); mem::swap(&mut global.source, &mut source); let level = level + 1; diff --git a/src/func/native.rs b/src/func/native.rs index f3d45d1d..457dc2f9 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -90,7 +90,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef + 'a + ?Sized> Self { engine: value.0, fn_name: value.1.as_ref(), - source: value.2.map(|v| v.as_ref()), + source: value.2.map(S::as_ref), global: Some(value.3), lib: value.4.as_ref(), pos: value.5, @@ -156,7 +156,7 @@ impl<'a> NativeCallContext<'a> { Self { engine, fn_name: fn_name.as_ref(), - source: source.map(|v| v.as_ref()), + source: source.map(S::as_ref), global: Some(global), lib, pos, diff --git a/src/func/script.rs b/src/func/script.rs index 774648d1..715fbcf7 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -47,9 +47,8 @@ impl Engine { fn_def .lib .as_ref() - .and_then(|m| m.id().map(|id| id.to_string())) - .or_else(|| global.source.as_ref().map(|s| s.to_string())) - .unwrap_or_default(), + .and_then(|m| m.id().map(str::to_string)) + .unwrap_or_else(|| global.source.to_string()), err, pos, ) diff --git a/src/lib.rs b/src/lib.rs index 1fcf4cec..0298da18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -247,7 +247,10 @@ pub use ast::{ pub use ast::FloatWrapper; #[cfg(feature = "internals")] -pub use engine::{EvalState, FnResolutionCache, FnResolutionCacheEntry, GlobalRuntimeState}; +pub use engine::{EvalState, GlobalRuntimeState}; + +#[cfg(feature = "internals")] +pub use func::call::{FnResolutionCache, FnResolutionCacheEntry}; #[cfg(feature = "internals")] pub use module::Namespace; diff --git a/src/module/mod.rs b/src/module/mod.rs index 0662e483..9ac02eaf 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -131,7 +131,8 @@ pub fn calc_native_fn_hash( #[derive(Clone)] pub struct Module { /// ID identifying the module. - id: Option, + /// No ID if string is empty. + id: Identifier, /// Is this module internal? pub(crate) internal: bool, /// Is this module part of a standard library? @@ -170,8 +171,9 @@ impl fmt::Debug for Module { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut d = f.debug_struct("Module"); - self.id.as_ref().map(|id| d.field("id", id)); - + if !self.id.is_empty() { + d.field("id", &self.id); + } if !self.modules.is_empty() { d.field( "modules", @@ -241,7 +243,7 @@ impl Module { #[must_use] pub fn new() -> Self { Self { - id: None, + id: Identifier::new_const(), internal: false, standard: false, modules: BTreeMap::new(), @@ -270,18 +272,24 @@ impl Module { #[inline] #[must_use] pub fn id(&self) -> Option<&str> { - self.id_raw().map(|s| s.as_str()) + if self.id_raw().is_empty() { + None + } else { + Some(self.id_raw()) + } } /// Get the ID of the [`Module`] as an [`Identifier`], if any. #[inline(always)] #[must_use] - pub(crate) const fn id_raw(&self) -> Option<&Identifier> { - self.id.as_ref() + pub(crate) const fn id_raw(&self) -> &Identifier { + &self.id } /// Set the ID of the [`Module`]. /// + /// If the string is empty, it is equivalent to clearing the ID. + /// /// # Example /// /// ``` @@ -292,7 +300,7 @@ impl Module { /// ``` #[inline(always)] pub fn set_id(&mut self, id: impl Into) -> &mut Self { - self.id = Some(id.into()); + self.id = id.into(); self } /// Clear the ID of the [`Module`]. @@ -309,7 +317,7 @@ impl Module { /// ``` #[inline(always)] pub fn clear_id(&mut self) -> &mut Self { - self.id = None; + self.id.clear(); self } @@ -1616,11 +1624,7 @@ impl Module { }); } - if let Some(s) = ast.source_raw() { - module.set_id(s.clone()); - } else { - module.clear_id(); - } + module.set_id(ast.source_raw().clone()); module.build_index(); diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 1a1c05db..b6d6a289 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -80,21 +80,21 @@ fn test_optimizer_parse() -> Result<(), Box> { assert_eq!( format!("{:?}", ast), - "AST { source: None, body: Block[Expr(123 @ 1:53)], functions: Module, resolver: None }" + "AST { source: \"\", body: Block[Expr(123 @ 1:53)], functions: Module, resolver: None }" ); let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?; assert_eq!( format!("{:?}", ast), - r#"AST { source: None, body: Block[Var(false @ 1:18, "DECISION" @ 1:7, (Constant), 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"# + r#"AST { source: "", body: Block[Var(false @ 1:18, "DECISION" @ 1:7, (Constant), 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"# ); let ast = engine.compile("if 1 == 2 { 42 }")?; assert_eq!( format!("{:?}", ast), - "AST { source: None, body: Block[], functions: Module, resolver: None }" + "AST { source: \"\", body: Block[], functions: Module, resolver: None }" ); engine.set_optimization_level(OptimizationLevel::Full); @@ -103,7 +103,7 @@ fn test_optimizer_parse() -> Result<(), Box> { assert_eq!( format!("{:?}", ast), - "AST { source: None, body: Block[Expr(42 @ 1:1)], functions: Module, resolver: None }" + "AST { source: \"\", body: Block[Expr(42 @ 1:1)], functions: Module, resolver: None }" ); Ok(())