Allow string interpolation to work with no packages.
This commit is contained in:
parent
07f522e6d7
commit
7c00b74916
@ -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.
|
* 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.
|
* 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.
|
* 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
|
Breaking API changes
|
||||||
--------------------
|
--------------------
|
||||||
|
@ -52,11 +52,6 @@ pub const FN_ANONYMOUS: &str = "anon$";
|
|||||||
/// function to compare two [`Dynamic`] values.
|
/// function to compare two [`Dynamic`] values.
|
||||||
pub const OP_EQUALS: &str = Token::EqualsTo.literal_syntax();
|
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.
|
/// Standard containment testing function.
|
||||||
///
|
///
|
||||||
/// The `in` operator is implemented as a call to this function.
|
/// The `in` operator is implemented as a call to this function.
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
//! Module defining functions for evaluating an expression.
|
//! Module defining functions for evaluating an expression.
|
||||||
|
|
||||||
use super::{Caches, EvalContext, GlobalRuntimeState, Target};
|
use super::{Caches, EvalContext, GlobalRuntimeState, Target};
|
||||||
use crate::ast::{Expr, OpAssignment};
|
use crate::ast::Expr;
|
||||||
use crate::engine::{KEYWORD_THIS, OP_CONCAT};
|
use crate::engine::KEYWORD_THIS;
|
||||||
|
use crate::packages::string_basic::{print_with_func, FUNC_TO_STRING};
|
||||||
use crate::types::dynamic::AccessMode;
|
use crate::types::dynamic::AccessMode;
|
||||||
use crate::{Dynamic, Engine, Position, RhaiResult, RhaiResultOf, Scope, ERR};
|
use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, SmartString, ERR};
|
||||||
use std::num::NonZeroUsize;
|
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
|
use std::{fmt::Write, num::NonZeroUsize};
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Search for a module within an imports stack.
|
/// Search for a module within an imports stack.
|
||||||
@ -283,42 +284,43 @@ impl Engine {
|
|||||||
|
|
||||||
// `... ${...} ...`
|
// `... ${...} ...`
|
||||||
Expr::InterpolatedString(x, _) => {
|
Expr::InterpolatedString(x, _) => {
|
||||||
let mut concat = self.const_empty_string().into();
|
let mut concat = SmartString::new_const();
|
||||||
let target = &mut concat;
|
|
||||||
|
|
||||||
let mut op_info = OpAssignment::new_op_assignment(OP_CONCAT, Position::NONE);
|
x.iter().try_for_each(|expr| -> RhaiResultOf<()> {
|
||||||
|
let item = &mut self
|
||||||
x.iter()
|
|
||||||
.try_for_each(|expr| {
|
|
||||||
let item = self
|
|
||||||
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)?
|
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)?
|
||||||
.flatten();
|
.flatten();
|
||||||
|
let pos = expr.position();
|
||||||
|
|
||||||
op_info.pos = expr.start_position();
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
self.eval_op_assignment(global, caches, &op_info, expr, target, item)
|
#[cfg(not(feature = "unchecked"))]
|
||||||
})
|
self.throw_on_size((0, 0, concat.len()))
|
||||||
.map(|_| concat.take_or_clone())
|
.map_err(|err| err.fill_position(pos))?;
|
||||||
.and_then(|r| self.check_data_size(r, expr.start_position()))
|
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(self.get_interned_string(concat).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(x, ..) => {
|
Expr::Array(x, ..) => {
|
||||||
|
let mut array = crate::Array::with_capacity(x.len());
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
let mut total_data_sizes = (0, 0, 0);
|
let mut total_data_sizes = (0, 0, 0);
|
||||||
|
|
||||||
x.iter()
|
x.iter().try_for_each(|item_expr| -> RhaiResultOf<()> {
|
||||||
.try_fold(
|
|
||||||
crate::Array::with_capacity(x.len()),
|
|
||||||
|mut array, item_expr| {
|
|
||||||
let value = self
|
let value = self
|
||||||
.eval_expr(
|
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), item_expr)?
|
||||||
global,
|
|
||||||
caches,
|
|
||||||
scope,
|
|
||||||
this_ptr.as_deref_mut(),
|
|
||||||
item_expr,
|
|
||||||
)?
|
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
@ -326,7 +328,7 @@ impl Engine {
|
|||||||
let val_sizes = value.calc_data_sizes(true);
|
let val_sizes = value.calc_data_sizes(true);
|
||||||
|
|
||||||
total_data_sizes = (
|
total_data_sizes = (
|
||||||
total_data_sizes.0 + val_sizes.0,
|
total_data_sizes.0 + val_sizes.0 + 1,
|
||||||
total_data_sizes.1 + val_sizes.1,
|
total_data_sizes.1 + val_sizes.1,
|
||||||
total_data_sizes.2 + val_sizes.2,
|
total_data_sizes.2 + val_sizes.2,
|
||||||
);
|
);
|
||||||
@ -336,19 +338,21 @@ impl Engine {
|
|||||||
|
|
||||||
array.push(value);
|
array.push(value);
|
||||||
|
|
||||||
Ok(array)
|
Ok(())
|
||||||
},
|
})?;
|
||||||
)
|
|
||||||
.map(Into::into)
|
Ok(Dynamic::from_array(array))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Map(x, ..) => {
|
Expr::Map(x, ..) => {
|
||||||
|
let mut map = x.1.clone();
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
let mut total_data_sizes = (0, 0, 0);
|
let mut total_data_sizes = (0, 0, 0);
|
||||||
|
|
||||||
x.0.iter()
|
x.0.iter()
|
||||||
.try_fold(x.1.clone(), |mut map, (key, value_expr)| {
|
.try_for_each(|(key, value_expr)| -> RhaiResultOf<()> {
|
||||||
let value = self
|
let value = self
|
||||||
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), value_expr)?
|
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), value_expr)?
|
||||||
.flatten();
|
.flatten();
|
||||||
@ -358,7 +362,7 @@ impl Engine {
|
|||||||
let delta = value.calc_data_sizes(true);
|
let delta = value.calc_data_sizes(true);
|
||||||
total_data_sizes = (
|
total_data_sizes = (
|
||||||
total_data_sizes.0 + delta.0,
|
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,
|
total_data_sizes.2 + delta.2,
|
||||||
);
|
);
|
||||||
self.throw_on_size(total_data_sizes)
|
self.throw_on_size(total_data_sizes)
|
||||||
@ -367,9 +371,10 @@ impl Engine {
|
|||||||
|
|
||||||
*map.get_mut(key.as_str()).unwrap() = value;
|
*map.get_mut(key.as_str()).unwrap() = value;
|
||||||
|
|
||||||
Ok(map)
|
Ok(())
|
||||||
})
|
})?;
|
||||||
.map(Into::into)
|
|
||||||
|
Ok(Dynamic::from_map(map))
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr::And(x, ..) => Ok((self
|
Expr::And(x, ..) => Ok((self
|
||||||
|
@ -148,7 +148,8 @@ impl Engine {
|
|||||||
auto_restore! { let orig_level = global.level; global.level += 1 }
|
auto_restore! { let orig_level = global.level; global.level += 1 }
|
||||||
|
|
||||||
let context = if need_context {
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -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])
|
if let Some(result) = get_builtin_binary_op_fn(x.op_token.clone(), &arg_values[0], &arg_values[1])
|
||||||
.and_then(|(f, ctx)| {
|
.and_then(|(f, ctx)| {
|
||||||
let context = if 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 {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
@ -44,7 +44,14 @@ pub fn print_with_func(
|
|||||||
result.into_immutable_string().expect("`ImmutableString`")
|
result.into_immutable_string().expect("`ImmutableString`")
|
||||||
}
|
}
|
||||||
Ok(result) => ctx.engine().map_type_name(result.type_name()).into(),
|
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.
|
/// Convert the value of the `item` into a string.
|
||||||
#[rhai_fn(name = "to_string", pure)]
|
#[rhai_fn(name = "to_string", pure)]
|
||||||
pub fn to_string_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString {
|
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.
|
/// Convert the value of the `item` into a string in debug format.
|
||||||
#[rhai_fn(name = "debug", pure)]
|
#[rhai_fn(name = "debug", pure)]
|
||||||
@ -95,7 +104,9 @@ mod print_debug_functions {
|
|||||||
/// Return the character into a string.
|
/// Return the character into a string.
|
||||||
#[rhai_fn(name = "print", name = "to_string")]
|
#[rhai_fn(name = "print", name = "to_string")]
|
||||||
pub fn print_char(character: char) -> ImmutableString {
|
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.
|
/// Convert the string into debug format.
|
||||||
#[rhai_fn(name = "debug", name = "to_debug")]
|
#[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.
|
/// Convert the function pointer into a string in debug format.
|
||||||
#[rhai_fn(name = "debug", name = "to_debug", pure)]
|
#[rhai_fn(name = "debug", name = "to_debug", pure)]
|
||||||
pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString {
|
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.
|
/// Return the boolean value into a string.
|
||||||
#[rhai_fn(name = "print", name = "to_string")]
|
#[rhai_fn(name = "print", name = "to_string")]
|
||||||
pub fn print_bool(value: bool) -> ImmutableString {
|
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.
|
/// Convert the boolean value into a string in debug format.
|
||||||
#[rhai_fn(name = "debug", name = "to_debug")]
|
#[rhai_fn(name = "debug", name = "to_debug")]
|
||||||
@ -141,30 +156,32 @@ mod print_debug_functions {
|
|||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
#[rhai_fn(name = "print", name = "to_string")]
|
#[rhai_fn(name = "print", name = "to_string")]
|
||||||
pub fn print_f64(number: f64) -> ImmutableString {
|
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.
|
/// Convert the value of `number` into a string.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
#[rhai_fn(name = "print", name = "to_string")]
|
#[rhai_fn(name = "print", name = "to_string")]
|
||||||
pub fn print_f32(number: f32) -> ImmutableString {
|
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.
|
/// Convert the value of `number` into a string.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
#[rhai_fn(name = "debug", name = "to_debug")]
|
#[rhai_fn(name = "debug", name = "to_debug")]
|
||||||
pub fn debug_f64(number: f64) -> ImmutableString {
|
pub fn debug_f64(number: f64) -> ImmutableString {
|
||||||
let number = crate::types::FloatWrapper::new(number);
|
|
||||||
let mut buf = SmartString::new_const();
|
let mut buf = SmartString::new_const();
|
||||||
write!(&mut buf, "{number:?}").unwrap();
|
write!(&mut buf, "{:?}", crate::types::FloatWrapper::new(number)).unwrap();
|
||||||
buf.into()
|
buf.into()
|
||||||
}
|
}
|
||||||
/// Convert the value of `number` into a string.
|
/// Convert the value of `number` into a string.
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
#[rhai_fn(name = "debug", name = "to_debug")]
|
#[rhai_fn(name = "debug", name = "to_debug")]
|
||||||
pub fn debug_f32(number: f32) -> ImmutableString {
|
pub fn debug_f32(number: f32) -> ImmutableString {
|
||||||
let number = crate::types::FloatWrapper::new(number);
|
|
||||||
let mut buf = SmartString::new_const();
|
let mut buf = SmartString::new_const();
|
||||||
write!(&mut buf, "{number:?}").unwrap();
|
write!(&mut buf, "{:?}", crate::types::FloatWrapper::new(number)).unwrap();
|
||||||
buf.into()
|
buf.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +196,7 @@ mod print_debug_functions {
|
|||||||
)]
|
)]
|
||||||
pub fn format_array(ctx: NativeCallContext, array: &mut Array) -> ImmutableString {
|
pub fn format_array(ctx: NativeCallContext, array: &mut Array) -> ImmutableString {
|
||||||
let len = array.len();
|
let len = array.len();
|
||||||
let mut result = String::with_capacity(len * 5 + 2);
|
let mut result = SmartString::new_const();
|
||||||
result.push('[');
|
result.push('[');
|
||||||
|
|
||||||
array.iter_mut().enumerate().for_each(|(i, x)| {
|
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 {
|
pub fn format_map(ctx: NativeCallContext, map: &mut Map) -> ImmutableString {
|
||||||
let len = map.len();
|
let len = map.len();
|
||||||
let mut result = String::with_capacity(len * 5 + 3);
|
let mut result = SmartString::new_const();
|
||||||
result.push_str("#{");
|
result.push_str("#{");
|
||||||
|
|
||||||
map.iter_mut().enumerate().for_each(|(i, (k, v))| {
|
map.iter_mut().enumerate().for_each(|(i, (k, v))| {
|
||||||
use std::fmt::Write;
|
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
result,
|
result,
|
||||||
"{:?}: {}{}",
|
"{:?}: {}{}",
|
||||||
|
@ -175,7 +175,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
"
|
"
|
||||||
let x = [1,2,3];
|
let x = [1,2];
|
||||||
len([x, x, x])
|
len([x, x, x])
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
|
@ -332,7 +332,8 @@ fn test_string_split() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_string_interpolated() -> Result<(), Box<EvalAltResult>> {
|
fn test_string_interpolated() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
// Make sure strings interpolation works even under raw
|
||||||
|
let engine = Engine::new_raw();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<String>("`${}`")?, "");
|
assert_eq!(engine.eval::<String>("`${}`")?, "");
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user