Merge empty strings.

This commit is contained in:
Stephen Chung 2021-09-26 21:18:52 +08:00
parent 65a1c24d7b
commit 3557db88e8
5 changed files with 63 additions and 32 deletions

View File

@ -1877,7 +1877,7 @@ impl Dynamic {
_ => Err(self.type_name()), _ => 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. /// Returns the name of the actual type if the cast fails.
/// ///
/// # Panics /// # Panics
@ -1888,7 +1888,7 @@ impl Dynamic {
match self.0 { match self.0 {
Union::Str(ref s, _, _) => Ok(s), Union::Str(ref s, _, _) => Ok(s),
#[cfg(not(feature = "no_closure"))] #[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()), _ => Err(self.type_name()),
} }
} }

View File

@ -3,7 +3,7 @@
use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
use crate::custom_syntax::CustomSyntax; use crate::custom_syntax::CustomSyntax;
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; 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::{ use crate::fn_native::{
CallableFunction, IteratorFn, OnDebugCallback, OnParseTokenCallback, OnPrintCallback, CallableFunction, IteratorFn, OnDebugCallback, OnParseTokenCallback, OnPrintCallback,
OnVarCallback, 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. /// Context of a script evaluation process.
#[derive(Debug)] #[derive(Debug)]
pub struct EvalContext<'a, 'x, 'px, 'm, 's, 'b, 't, 'pt> { 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. /// A map mapping type names to pretty-print names.
pub(crate) type_names: BTreeMap<Identifier, Box<Identifier>>, pub(crate) type_names: BTreeMap<Identifier, Box<Identifier>>,
/// An empty [`ImmutableString`] for cloning purposes. /// Useful constants
pub(crate) empty_string: ImmutableString, pub(crate) constants: GlobalConstants,
/// A set of symbols to disable. /// A set of symbols to disable.
pub(crate) disabled_symbols: BTreeSet<Identifier>, pub(crate) disabled_symbols: BTreeSet<Identifier>,
@ -1042,7 +1063,7 @@ impl Engine {
module_resolver: None, module_resolver: None,
type_names: Default::default(), type_names: Default::default(),
empty_string: Default::default(), constants: Default::default(),
disabled_symbols: Default::default(), disabled_symbols: Default::default(),
custom_keywords: Default::default(), custom_keywords: Default::default(),
custom_syntax: Default::default(), custom_syntax: Default::default(),
@ -1070,6 +1091,13 @@ impl Engine {
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. /// Search for a module within an imports stack.
#[inline] #[inline]
#[must_use] #[must_use]
@ -1294,7 +1322,7 @@ impl Engine {
if let Some(mut new_val) = try_setter { if let Some(mut new_val) = try_setter {
// Try to call index setter // Try to call index setter
let hash_set = 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 args = &mut [target, &mut idx_val_for_setter, &mut new_val];
let pos = Position::NONE; let pos = Position::NONE;
@ -1427,7 +1455,7 @@ impl Engine {
EvalAltResult::ErrorDotExpr(_, _) => { EvalAltResult::ErrorDotExpr(_, _) => {
let args = &mut [target, &mut name.into(), &mut new_val]; let args = &mut [target, &mut name.into(), &mut new_val];
let hash_set = 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; let pos = Position::NONE;
self.exec_fn_call( self.exec_fn_call(
@ -1583,7 +1611,7 @@ impl Engine {
let args = let args =
&mut [target.as_mut(), &mut name.into(), val]; &mut [target.as_mut(), &mut name.into(), val];
let hash_set = FnCallHashes::from_native( let hash_set = FnCallHashes::from_native(
crate::calc_fn_hash(FN_IDX_SET, 3), self.constants.fn_hash_idx_set,
); );
self.exec_fn_call( self.exec_fn_call(
mods, state, lib, FN_IDX_SET, hash_set, args, mods, state, lib, FN_IDX_SET, hash_set, args,
@ -1987,7 +2015,7 @@ impl Engine {
_ if use_indexers => { _ if use_indexers => {
let args = &mut [target, &mut idx]; 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; let idx_pos = Position::NONE;
self.exec_fn_call( self.exec_fn_call(
@ -2059,7 +2087,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.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)?;
@ -3006,17 +3034,22 @@ impl Engine {
} }
/// Check a result to ensure that the data size is within allowable limit. /// 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 { fn check_return_value(&self, result: RhaiResult) -> RhaiResult {
result match result {
} // Concentrate all empty strings into one instance to save memory
#[cfg(feature = "no_closure")]
/// Check a result to ensure that the data size is within allowable limit. Ok(r) if r.as_str_ref().map_or(false, &str::is_empty) => Ok(self.empty_string().into()),
#[cfg(not(feature = "unchecked"))] // Concentrate all empty strings into one instance to save memory
#[inline(always)] #[cfg(not(feature = "no_closure"))]
fn check_return_value(&self, result: RhaiResult) -> RhaiResult { Ok(r) if !r.is_shared() && r.as_str_ref().map_or(false, &str::is_empty) => {
result.and_then(|r| self.check_data_size(&r).map(|_| r)) 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")] #[cfg(feature = "unchecked")]

View File

@ -44,9 +44,7 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
if TypeId::of::<T>() == TypeId::of::<&str>() { if TypeId::of::<T>() == TypeId::of::<&str>() {
// If T is `&str`, data must be `ImmutableString`, so map directly to it // If T is `&str`, data must be `ImmutableString`, so map directly to it
data.flatten_in_place(); data.flatten_in_place();
let ref_str = data let ref_str = data.as_str_ref().expect("argument type is &str");
.as_str_ref()
.expect("argument passed by value is not shared");
let ref_t = unsafe { mem::transmute::<_, &T>(&ref_str) }; let ref_t = unsafe { mem::transmute::<_, &T>(&ref_str) };
ref_t.clone() ref_t.clone()
} else if TypeId::of::<T>() == TypeId::of::<String>() { } else if TypeId::of::<T>() == TypeId::of::<String>() {

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.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().empty_string().clone();
} }
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().empty_string().clone();
} }
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().empty_string().clone();
} 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().empty_string().clone();
} 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().empty_string().clone()
} 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().empty_string().clone().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().empty_string().clone().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();