From 3557db88e8e3c7368864472574adc1749b36b45c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 26 Sep 2021 21:18:52 +0800 Subject: [PATCH] Merge empty strings. --- src/dynamic.rs | 4 +-- src/engine.rs | 71 +++++++++++++++++++++++++++---------- src/fn_register.rs | 4 +-- src/optimize.rs | 2 +- src/packages/string_more.rs | 14 ++++---- 5 files changed, 63 insertions(+), 32 deletions(-) diff --git a/src/dynamic.rs b/src/dynamic.rs index 075ddab9..ae2096ab 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -1877,7 +1877,7 @@ impl Dynamic { _ => Err(self.type_name()), } } - /// Cast the [`Dynamic`] as an [`ImmutableString`] and return it. + /// Cast the [`Dynamic`] as an [`ImmutableString`] and return it as a string slice. /// Returns the name of the actual type if the cast fails. /// /// # Panics @@ -1888,7 +1888,7 @@ impl Dynamic { match self.0 { Union::Str(ref s, _, _) => Ok(s), #[cfg(not(feature = "no_closure"))] - Union::Shared(_, _, _) => panic!("as_str() cannot be called on shared values"), + Union::Shared(_, _, _) => panic!("as_str_ref() cannot be called on shared values"), _ => Err(self.type_name()), } } diff --git a/src/engine.rs b/src/engine.rs index 51391745..64b4659a 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,7 +3,7 @@ use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; use crate::custom_syntax::CustomSyntax; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; -use crate::fn_hash::get_hasher; +use crate::fn_hash::{calc_fn_hash, get_hasher}; use crate::fn_native::{ CallableFunction, IteratorFn, OnDebugCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback, @@ -790,6 +790,27 @@ impl Default for Limits { } } +/// A type containing useful constants for the [`Engine`]. +#[derive(Debug)] +pub struct GlobalConstants { + /// An empty [`ImmutableString`] for cloning purposes. + pub(crate) empty_string: ImmutableString, + /// Function call hash to FN_IDX_GET + pub(crate) fn_hash_idx_get: u64, + /// Function call hash to FN_IDX_SET + pub(crate) fn_hash_idx_set: u64, +} + +impl Default for GlobalConstants { + fn default() -> Self { + Self { + empty_string: Default::default(), + fn_hash_idx_get: calc_fn_hash(FN_IDX_GET, 2), + fn_hash_idx_set: calc_fn_hash(FN_IDX_SET, 3), + } + } +} + /// Context of a script evaluation process. #[derive(Debug)] pub struct EvalContext<'a, 'x, 'px, 'm, 's, 'b, 't, 'pt> { @@ -911,8 +932,8 @@ pub struct Engine { /// A map mapping type names to pretty-print names. pub(crate) type_names: BTreeMap>, - /// An empty [`ImmutableString`] for cloning purposes. - pub(crate) empty_string: ImmutableString, + /// Useful constants + pub(crate) constants: GlobalConstants, /// A set of symbols to disable. pub(crate) disabled_symbols: BTreeSet, @@ -1042,7 +1063,7 @@ impl Engine { module_resolver: None, type_names: Default::default(), - empty_string: Default::default(), + constants: Default::default(), disabled_symbols: Default::default(), custom_keywords: Default::default(), custom_syntax: Default::default(), @@ -1070,6 +1091,13 @@ impl Engine { engine } + /// Get an empty [`ImmutableString`]. + #[inline(always)] + #[must_use] + pub fn empty_string(&self) -> ImmutableString { + self.constants.empty_string.clone() + } + /// Search for a module within an imports stack. #[inline] #[must_use] @@ -1294,7 +1322,7 @@ impl Engine { if let Some(mut new_val) = try_setter { // Try to call index setter let hash_set = - FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_SET, 3)); + FnCallHashes::from_native(self.constants.fn_hash_idx_set); let args = &mut [target, &mut idx_val_for_setter, &mut new_val]; let pos = Position::NONE; @@ -1427,7 +1455,7 @@ impl Engine { EvalAltResult::ErrorDotExpr(_, _) => { let args = &mut [target, &mut name.into(), &mut new_val]; let hash_set = - FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_SET, 3)); + FnCallHashes::from_native(self.constants.fn_hash_idx_set); let pos = Position::NONE; self.exec_fn_call( @@ -1583,7 +1611,7 @@ impl Engine { let args = &mut [target.as_mut(), &mut name.into(), val]; let hash_set = FnCallHashes::from_native( - crate::calc_fn_hash(FN_IDX_SET, 3), + self.constants.fn_hash_idx_set, ); self.exec_fn_call( mods, state, lib, FN_IDX_SET, hash_set, args, @@ -1987,7 +2015,7 @@ impl Engine { _ if use_indexers => { let args = &mut [target, &mut idx]; - let hash_get = FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_GET, 2)); + let hash_get = FnCallHashes::from_native(self.constants.fn_hash_idx_get); let idx_pos = Position::NONE; self.exec_fn_call( @@ -2059,7 +2087,7 @@ impl Engine { // `... ${...} ...` Expr::InterpolatedString(x, pos) => { let mut pos = *pos; - let mut result: Dynamic = self.empty_string.clone().into(); + let mut result: Dynamic = self.empty_string().clone().into(); for expr in x.iter() { let item = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; @@ -3006,17 +3034,22 @@ impl Engine { } /// Check a result to ensure that the data size is within allowable limit. - #[cfg(feature = "unchecked")] - #[inline(always)] fn check_return_value(&self, result: RhaiResult) -> RhaiResult { - result - } - - /// Check a result to ensure that the data size is within allowable limit. - #[cfg(not(feature = "unchecked"))] - #[inline(always)] - fn check_return_value(&self, result: RhaiResult) -> RhaiResult { - result.and_then(|r| self.check_data_size(&r).map(|_| r)) + match result { + // Concentrate all empty strings into one instance to save memory + #[cfg(feature = "no_closure")] + Ok(r) if r.as_str_ref().map_or(false, &str::is_empty) => Ok(self.empty_string().into()), + // Concentrate all empty strings into one instance to save memory + #[cfg(not(feature = "no_closure"))] + Ok(r) if !r.is_shared() && r.as_str_ref().map_or(false, &str::is_empty) => { + Ok(self.empty_string().into()) + } + // Check data sizes + #[cfg(not(feature = "unchecked"))] + Ok(r) => self.check_data_size(&r).map(|_| r), + // Return all other results + _ => result, + } } #[cfg(feature = "unchecked")] diff --git a/src/fn_register.rs b/src/fn_register.rs index c20263ff..776bace3 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -44,9 +44,7 @@ pub fn by_value(data: &mut Dynamic) -> T { if TypeId::of::() == TypeId::of::<&str>() { // If T is `&str`, data must be `ImmutableString`, so map directly to it data.flatten_in_place(); - let ref_str = data - .as_str_ref() - .expect("argument passed by value is not shared"); + let ref_str = data.as_str_ref().expect("argument type is &str"); let ref_t = unsafe { mem::transmute::<_, &T>(&ref_str) }; ref_t.clone() } else if TypeId::of::() == TypeId::of::() { diff --git a/src/optimize.rs b/src/optimize.rs index 616f0db5..cfc51fff 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -822,7 +822,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { // `` Expr::InterpolatedString(x, pos) if x.is_empty() => { state.set_dirty(); - *expr = Expr::StringConstant(state.engine.empty_string.clone(), *pos); + *expr = Expr::StringConstant(state.engine.empty_string().clone(), *pos); } // `...` Expr::InterpolatedString(x, _) if x.len() == 1 && matches!(x[0], Expr::StringConstant(_, _)) => { diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 58a94777..36c1b5a1 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -126,7 +126,7 @@ mod string_functions { len: INT, ) -> ImmutableString { if string.is_empty() || len <= 0 { - return ctx.engine().empty_string.clone(); + return ctx.engine().empty_string().clone(); } let mut chars = StaticVec::::with_capacity(len as usize); @@ -305,13 +305,13 @@ mod string_functions { len: INT, ) -> ImmutableString { if string.is_empty() { - return ctx.engine().empty_string.clone(); + return ctx.engine().empty_string().clone(); } let mut chars = StaticVec::with_capacity(string.len()); let offset = if string.is_empty() || len <= 0 { - return ctx.engine().empty_string.clone(); + return ctx.engine().empty_string().clone(); } else if start < 0 { if let Some(n) = start.checked_abs() { chars.extend(string.chars()); @@ -324,7 +324,7 @@ mod string_functions { 0 } } else if start as usize >= string.chars().count() { - return ctx.engine().empty_string.clone(); + return ctx.engine().empty_string().clone(); } else { start as usize }; @@ -354,7 +354,7 @@ mod string_functions { start: INT, ) -> ImmutableString { if string.is_empty() { - ctx.engine().empty_string.clone() + ctx.engine().empty_string().clone() } else { let len = string.len() as INT; sub_string(ctx, string, start, len) @@ -571,14 +571,14 @@ mod string_functions { if let Some(n) = start.checked_abs() { let num_chars = string.chars().count(); if n as usize > num_chars { - vec![ctx.engine().empty_string.clone().into(), string.into()] + vec![ctx.engine().empty_string().clone().into(), string.into()] } else { let prefix: String = string.chars().take(num_chars - n as usize).collect(); let prefix_len = prefix.len(); vec![prefix.into(), string[prefix_len..].into()] } } else { - vec![ctx.engine().empty_string.clone().into(), string.into()] + vec![ctx.engine().empty_string().clone().into(), string.into()] } } else { let prefix: String = string.chars().take(start as usize).collect();