From 7c00b749169e82dd1e3a8190058219a675458768 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 27 Dec 2022 10:09:40 +0800 Subject: [PATCH] Allow string interpolation to work with no packages. --- CHANGELOG.md | 1 + src/engine.rs | 5 -- src/eval/expr.rs | 107 ++++++++++++++++++----------------- src/eval/stmt.rs | 3 +- src/optimizer.rs | 2 +- src/packages/string_basic.rs | 45 ++++++++++----- tests/data_size.rs | 2 +- tests/string.rs | 3 +- 8 files changed, 93 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a81820de..1b66ac38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Bug fixes * Integer numbers that are too large to deserialize into `INT` now fall back to `Decimal` or `FLOAT` instead of silently truncating. * Parsing deeply-nested closures (e.g. `||{||{||{||{||{||{||{...}}}}}}}`) no longer panics but will be confined to the nesting limit. * Closures containing a single expression are now allowed in `Engine::eval_expression` etc. +* Strings interpolation now works under `Engine::new_raw` without any standard package. Breaking API changes -------------------- diff --git a/src/engine.rs b/src/engine.rs index 2e239912..3caa8183 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -52,11 +52,6 @@ pub const FN_ANONYMOUS: &str = "anon$"; /// function to compare two [`Dynamic`] values. pub const OP_EQUALS: &str = Token::EqualsTo.literal_syntax(); -/// Standard concatenation operator. -/// -/// Used primarily to build up interpolated strings. -pub const OP_CONCAT: &str = Token::PlusAssign.literal_syntax(); - /// Standard containment testing function. /// /// The `in` operator is implemented as a call to this function. diff --git a/src/eval/expr.rs b/src/eval/expr.rs index a87d91e0..d04aec44 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -1,13 +1,14 @@ //! Module defining functions for evaluating an expression. use super::{Caches, EvalContext, GlobalRuntimeState, Target}; -use crate::ast::{Expr, OpAssignment}; -use crate::engine::{KEYWORD_THIS, OP_CONCAT}; +use crate::ast::Expr; +use crate::engine::KEYWORD_THIS; +use crate::packages::string_basic::{print_with_func, FUNC_TO_STRING}; use crate::types::dynamic::AccessMode; -use crate::{Dynamic, Engine, Position, RhaiResult, RhaiResultOf, Scope, ERR}; -use std::num::NonZeroUsize; +use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, SmartString, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +use std::{fmt::Write, num::NonZeroUsize}; impl Engine { /// Search for a module within an imports stack. @@ -283,72 +284,75 @@ impl Engine { // `... ${...} ...` Expr::InterpolatedString(x, _) => { - let mut concat = self.const_empty_string().into(); - let target = &mut concat; + let mut concat = SmartString::new_const(); - let mut op_info = OpAssignment::new_op_assignment(OP_CONCAT, Position::NONE); + x.iter().try_for_each(|expr| -> RhaiResultOf<()> { + let item = &mut self + .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)? + .flatten(); + let pos = expr.position(); - x.iter() - .try_for_each(|expr| { - let item = self - .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)? - .flatten(); + if item.is_string() { + write!(concat, "{item}").unwrap(); + } else { + let source = global.source(); + let context = &(self, FUNC_TO_STRING, source, &*global, pos).into(); + let display = print_with_func(FUNC_TO_STRING, context, item); + write!(concat, "{}", display).unwrap(); + } - op_info.pos = expr.start_position(); + #[cfg(not(feature = "unchecked"))] + self.throw_on_size((0, 0, concat.len())) + .map_err(|err| err.fill_position(pos))?; - self.eval_op_assignment(global, caches, &op_info, expr, target, item) - }) - .map(|_| concat.take_or_clone()) - .and_then(|r| self.check_data_size(r, expr.start_position())) + Ok(()) + })?; + + Ok(self.get_interned_string(concat).into()) } #[cfg(not(feature = "no_index"))] Expr::Array(x, ..) => { + let mut array = crate::Array::with_capacity(x.len()); + #[cfg(not(feature = "unchecked"))] let mut total_data_sizes = (0, 0, 0); - x.iter() - .try_fold( - crate::Array::with_capacity(x.len()), - |mut array, item_expr| { - let value = self - .eval_expr( - global, - caches, - scope, - this_ptr.as_deref_mut(), - item_expr, - )? - .flatten(); + x.iter().try_for_each(|item_expr| -> RhaiResultOf<()> { + let value = self + .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), item_expr)? + .flatten(); - #[cfg(not(feature = "unchecked"))] - if self.has_data_size_limit() { - let val_sizes = value.calc_data_sizes(true); + #[cfg(not(feature = "unchecked"))] + if self.has_data_size_limit() { + let val_sizes = value.calc_data_sizes(true); - total_data_sizes = ( - total_data_sizes.0 + val_sizes.0, - total_data_sizes.1 + val_sizes.1, - total_data_sizes.2 + val_sizes.2, - ); - self.throw_on_size(total_data_sizes) - .map_err(|err| err.fill_position(item_expr.position()))?; - } + total_data_sizes = ( + total_data_sizes.0 + val_sizes.0 + 1, + total_data_sizes.1 + val_sizes.1, + total_data_sizes.2 + val_sizes.2, + ); + self.throw_on_size(total_data_sizes) + .map_err(|err| err.fill_position(item_expr.position()))?; + } - array.push(value); + array.push(value); - Ok(array) - }, - ) - .map(Into::into) + Ok(()) + })?; + + Ok(Dynamic::from_array(array)) } #[cfg(not(feature = "no_object"))] Expr::Map(x, ..) => { + let mut map = x.1.clone(); + #[cfg(not(feature = "unchecked"))] let mut total_data_sizes = (0, 0, 0); x.0.iter() - .try_fold(x.1.clone(), |mut map, (key, value_expr)| { + .try_for_each(|(key, value_expr)| -> RhaiResultOf<()> { let value = self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), value_expr)? .flatten(); @@ -358,7 +362,7 @@ impl Engine { let delta = value.calc_data_sizes(true); total_data_sizes = ( total_data_sizes.0 + delta.0, - total_data_sizes.1 + delta.1, + total_data_sizes.1 + delta.1 + 1, total_data_sizes.2 + delta.2, ); self.throw_on_size(total_data_sizes) @@ -367,9 +371,10 @@ impl Engine { *map.get_mut(key.as_str()).unwrap() = value; - Ok(map) - }) - .map(Into::into) + Ok(()) + })?; + + Ok(Dynamic::from_map(map)) } Expr::And(x, ..) => Ok((self diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index c95c0321..49627c08 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -148,7 +148,8 @@ impl Engine { auto_restore! { let orig_level = global.level; global.level += 1 } let context = if need_context { - Some((self, op, None, &*global, *op_pos).into()) + let source = global.source(); + Some((self, op, source, &*global, *op_pos).into()) } else { None }; diff --git a/src/optimizer.rs b/src/optimizer.rs index 89d66e47..0b8ff895 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -1141,7 +1141,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { if let Some(result) = get_builtin_binary_op_fn(x.op_token.clone(), &arg_values[0], &arg_values[1]) .and_then(|(f, ctx)| { let context = if ctx { - Some((state.engine, x.name.as_str(),None, &state.global, *pos).into()) + Some((state.engine, x.name.as_str(), None, &state.global, *pos).into()) } else { None }; diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 686951f7..f2ff7304 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -44,7 +44,14 @@ pub fn print_with_func( result.into_immutable_string().expect("`ImmutableString`") } Ok(result) => ctx.engine().map_type_name(result.type_name()).into(), - Err(_) => ctx.engine().map_type_name(value.type_name()).into(), + Err(_) => { + let mut buf = SmartString::new_const(); + match fn_name { + FUNC_TO_DEBUG => write!(&mut buf, "{value:?}").unwrap(), + _ => write!(&mut buf, "{value}").unwrap(), + } + ctx.engine().map_type_name(&buf).into() + } } } @@ -58,7 +65,9 @@ mod print_debug_functions { /// Convert the value of the `item` into a string. #[rhai_fn(name = "to_string", pure)] pub fn to_string_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { - ctx.engine().map_type_name(&item.to_string()).into() + let mut buf = SmartString::new_const(); + write!(&mut buf, "{item}").unwrap(); + ctx.engine().map_type_name(&buf).into() } /// Convert the value of the `item` into a string in debug format. #[rhai_fn(name = "debug", pure)] @@ -95,7 +104,9 @@ mod print_debug_functions { /// Return the character into a string. #[rhai_fn(name = "print", name = "to_string")] pub fn print_char(character: char) -> ImmutableString { - character.to_string().into() + let mut buf = SmartString::new_const(); + buf.push(character); + buf.into() } /// Convert the string into debug format. #[rhai_fn(name = "debug", name = "to_debug")] @@ -108,13 +119,17 @@ mod print_debug_functions { /// Convert the function pointer into a string in debug format. #[rhai_fn(name = "debug", name = "to_debug", pure)] pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { - f.to_string().into() + let mut buf = SmartString::new_const(); + write!(&mut buf, "{f}").unwrap(); + buf.into() } /// Return the boolean value into a string. #[rhai_fn(name = "print", name = "to_string")] pub fn print_bool(value: bool) -> ImmutableString { - value.to_string().into() + let mut buf = SmartString::new_const(); + write!(&mut buf, "{value}").unwrap(); + buf.into() } /// Convert the boolean value into a string in debug format. #[rhai_fn(name = "debug", name = "to_debug")] @@ -141,30 +156,32 @@ mod print_debug_functions { #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "print", name = "to_string")] pub fn print_f64(number: f64) -> ImmutableString { - crate::types::FloatWrapper::new(number).to_string().into() + let mut buf = SmartString::new_const(); + write!(&mut buf, "{}", crate::types::FloatWrapper::new(number)).unwrap(); + buf.into() } /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "print", name = "to_string")] pub fn print_f32(number: f32) -> ImmutableString { - crate::types::FloatWrapper::new(number).to_string().into() + let mut buf = SmartString::new_const(); + write!(&mut buf, "{}", crate::types::FloatWrapper::new(number)).unwrap(); + buf.into() } /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_f64(number: f64) -> ImmutableString { - let number = crate::types::FloatWrapper::new(number); let mut buf = SmartString::new_const(); - write!(&mut buf, "{number:?}").unwrap(); + write!(&mut buf, "{:?}", crate::types::FloatWrapper::new(number)).unwrap(); buf.into() } /// Convert the value of `number` into a string. #[cfg(not(feature = "no_float"))] #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_f32(number: f32) -> ImmutableString { - let number = crate::types::FloatWrapper::new(number); let mut buf = SmartString::new_const(); - write!(&mut buf, "{number:?}").unwrap(); + write!(&mut buf, "{:?}", crate::types::FloatWrapper::new(number)).unwrap(); buf.into() } @@ -179,7 +196,7 @@ mod print_debug_functions { )] pub fn format_array(ctx: NativeCallContext, array: &mut Array) -> ImmutableString { let len = array.len(); - let mut result = String::with_capacity(len * 5 + 2); + let mut result = SmartString::new_const(); result.push('['); array.iter_mut().enumerate().for_each(|(i, x)| { @@ -204,12 +221,10 @@ mod print_debug_functions { )] pub fn format_map(ctx: NativeCallContext, map: &mut Map) -> ImmutableString { let len = map.len(); - let mut result = String::with_capacity(len * 5 + 3); + let mut result = SmartString::new_const(); result.push_str("#{"); map.iter_mut().enumerate().for_each(|(i, (k, v))| { - use std::fmt::Write; - write!( result, "{:?}: {}{}", diff --git a/tests/data_size.rs b/tests/data_size.rs index a0415aa3..50f35f50 100644 --- a/tests/data_size.rs +++ b/tests/data_size.rs @@ -175,7 +175,7 @@ fn test_max_array_size() -> Result<(), Box> { assert_eq!( engine.eval::( " - let x = [1,2,3]; + let x = [1,2]; len([x, x, x]) " )?, diff --git a/tests/string.rs b/tests/string.rs index 3bbed302..37988600 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -332,7 +332,8 @@ fn test_string_split() -> Result<(), Box> { #[test] fn test_string_interpolated() -> Result<(), Box> { - let engine = Engine::new(); + // Make sure strings interpolation works even under raw + let engine = Engine::new_raw(); assert_eq!(engine.eval::("`${}`")?, "");