From a130960627c0184e9906ed61bf5e4759d32e0950 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 26 Sep 2021 21:25:29 +0800 Subject: [PATCH] Fix empty strings. --- CHANGELOG.md | 1 + src/engine.rs | 9 ++++++--- src/optimize.rs | 2 +- src/packages/string_more.rs | 14 +++++++------- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a982c131..86567a04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Enhancements * `Engine::consume_XXX` methods are renamed to `Engine::run_XXX` to make meanings clearer. The `consume_XXX` API is deprecated. * `Engine::register_type_XXX` are now available even under `no_object`. * Added `Engine::on_parse_token` to allow remapping certain tokens during parsing. +* Added `Engine::const_empty_string` to merge empty strings into a single instance. ### Custom Syntax diff --git a/src/engine.rs b/src/engine.rs index 64b4659a..0b8b2740 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1092,9 +1092,12 @@ impl Engine { } /// Get an empty [`ImmutableString`]. + /// + /// [`Engine`] keeps a single instance of an empty [`ImmutableString`] and uses this to create + /// shared instances for subsequent uses. This minimizes unnecessary allocations for empty strings. #[inline(always)] #[must_use] - pub fn empty_string(&self) -> ImmutableString { + pub fn const_empty_string(&self) -> ImmutableString { self.constants.empty_string.clone() } @@ -2087,7 +2090,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.const_empty_string().clone().into(); for expr in x.iter() { let item = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; @@ -3042,7 +3045,7 @@ impl Engine { // 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()) + Ok(self.const_empty_string().into()) } // Check data sizes #[cfg(not(feature = "unchecked"))] diff --git a/src/optimize.rs b/src/optimize.rs index cfc51fff..d4ec1945 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.const_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 36c1b5a1..fb8d29e7 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().const_empty_string(); } 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().const_empty_string(); } 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().const_empty_string(); } 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().const_empty_string(); } 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().const_empty_string() } 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().const_empty_string().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().const_empty_string().into(), string.into()] } } else { let prefix: String = string.chars().take(start as usize).collect();