Fix empty strings.

This commit is contained in:
Stephen Chung 2021-09-26 21:25:29 +08:00
parent 3557db88e8
commit a130960627
4 changed files with 15 additions and 11 deletions

View File

@ -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::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`. * `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::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 ### Custom Syntax

View File

@ -1092,9 +1092,12 @@ impl Engine {
} }
/// Get an empty [`ImmutableString`]. /// 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)] #[inline(always)]
#[must_use] #[must_use]
pub fn empty_string(&self) -> ImmutableString { pub fn const_empty_string(&self) -> ImmutableString {
self.constants.empty_string.clone() self.constants.empty_string.clone()
} }
@ -2087,7 +2090,7 @@ impl Engine {
// `... ${...} ...` // `... ${...} ...`
Expr::InterpolatedString(x, pos) => { Expr::InterpolatedString(x, pos) => {
let mut pos = *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() { for expr in x.iter() {
let item = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; 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 // Concentrate all empty strings into one instance to save memory
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Ok(r) if !r.is_shared() && r.as_str_ref().map_or(false, &str::is_empty) => { 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 // Check data sizes
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]

View File

@ -822,7 +822,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
// `` // ``
Expr::InterpolatedString(x, pos) if x.is_empty() => { Expr::InterpolatedString(x, pos) if x.is_empty() => {
state.set_dirty(); 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(_, _)) => { Expr::InterpolatedString(x, _) if x.len() == 1 && matches!(x[0], Expr::StringConstant(_, _)) => {

View File

@ -126,7 +126,7 @@ mod string_functions {
len: INT, len: INT,
) -> ImmutableString { ) -> ImmutableString {
if string.is_empty() || len <= 0 { if string.is_empty() || len <= 0 {
return ctx.engine().empty_string().clone(); return ctx.engine().const_empty_string();
} }
let mut chars = StaticVec::<char>::with_capacity(len as usize); let mut chars = StaticVec::<char>::with_capacity(len as usize);
@ -305,13 +305,13 @@ mod string_functions {
len: INT, len: INT,
) -> ImmutableString { ) -> ImmutableString {
if string.is_empty() { 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 mut chars = StaticVec::with_capacity(string.len());
let offset = if string.is_empty() || len <= 0 { let offset = if string.is_empty() || len <= 0 {
return ctx.engine().empty_string().clone(); return ctx.engine().const_empty_string();
} else if start < 0 { } else if start < 0 {
if let Some(n) = start.checked_abs() { if let Some(n) = start.checked_abs() {
chars.extend(string.chars()); chars.extend(string.chars());
@ -324,7 +324,7 @@ mod string_functions {
0 0
} }
} else if start as usize >= string.chars().count() { } else if start as usize >= string.chars().count() {
return ctx.engine().empty_string().clone(); return ctx.engine().const_empty_string();
} else { } else {
start as usize start as usize
}; };
@ -354,7 +354,7 @@ mod string_functions {
start: INT, start: INT,
) -> ImmutableString { ) -> ImmutableString {
if string.is_empty() { if string.is_empty() {
ctx.engine().empty_string().clone() ctx.engine().const_empty_string()
} else { } else {
let len = string.len() as INT; let len = string.len() as INT;
sub_string(ctx, string, start, len) sub_string(ctx, string, start, len)
@ -571,14 +571,14 @@ mod string_functions {
if let Some(n) = start.checked_abs() { if let Some(n) = start.checked_abs() {
let num_chars = string.chars().count(); let num_chars = string.chars().count();
if n as usize > num_chars { 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 { } else {
let prefix: String = string.chars().take(num_chars - n as usize).collect(); let prefix: String = string.chars().take(num_chars - n as usize).collect();
let prefix_len = prefix.len(); let prefix_len = prefix.len();
vec![prefix.into(), string[prefix_len..].into()] vec![prefix.into(), string[prefix_len..].into()]
} }
} else { } else {
vec![ctx.engine().empty_string().clone().into(), string.into()] vec![ctx.engine().const_empty_string().into(), string.into()]
} }
} else { } else {
let prefix: String = string.chars().take(start as usize).collect(); let prefix: String = string.chars().take(start as usize).collect();