commit
8805f02a8f
@ -10,6 +10,8 @@ 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.
|
||||
* `Fn` now throws an error if the name is a reserved keyword as it cannot possibly map to such a function. This also disallows creating function pointers to custom operators which are defined as disabled keywords (a mouthful), but such custom operators are designed primarily to be used as operators.
|
||||
|
||||
Breaking API changes
|
||||
--------------------
|
||||
@ -57,8 +59,10 @@ Net features
|
||||
|
||||
### Enhanced array API
|
||||
|
||||
* Array methods that take a function pointer, usually a closure (e.g. `map`, `filter`, `index_of` etc.), can now provide a closure with one few parameter but binds the first parameter to `this`.
|
||||
* This vastly improves performance when working with arrays of object maps by avoiding unnecessary cloning of large types.
|
||||
* Array methods that take a function pointer, usually a closure (e.g. `map`, `filter`, `index_of`, `reduce` etc.), can now bind the array element to `this` when calling a closure.
|
||||
* This vastly improves performance when working with arrays of large types (e.g. object maps) by avoiding unnecessary cloning.
|
||||
* `find` and `find_map` are added for arrays.
|
||||
* `for_each` is also added for arrays, allowing a closure to mutate array elements (bound to `this`) in turn.
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
@ -77,7 +81,6 @@ Enhancements
|
||||
* `FnPtr::iter_curry` and `FnPtr::iter_curry_mut` are added.
|
||||
* `Dynamic::deep_scan` is added to recursively scan for `Dynamic` values.
|
||||
* `>>` and `<<` operators on integers no longer throw errors when the number of bits to shift is out of bounds. Shifting by a negative number of bits simply reverses the shift direction.
|
||||
* `find` and `find_map` are added for arrays.
|
||||
|
||||
|
||||
Version 1.11.0
|
||||
|
@ -4,7 +4,7 @@
|
||||
use crate::ast::Expr;
|
||||
use crate::func::SendSync;
|
||||
use crate::parser::ParseResult;
|
||||
use crate::tokenizer::{is_valid_identifier, Token, NO_TOKEN};
|
||||
use crate::tokenizer::{is_reserved_keyword_or_symbol, is_valid_identifier, Token};
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::{
|
||||
Dynamic, Engine, EvalContext, Identifier, ImmutableString, LexError, Position, RhaiResult,
|
||||
@ -231,11 +231,11 @@ impl Engine {
|
||||
continue;
|
||||
}
|
||||
|
||||
let token = Token::lookup_symbol_from_syntax(s).unwrap_or_else(|| {
|
||||
if Token::is_reserved_keyword(s) {
|
||||
Token::Reserved(Box::new(s.into()))
|
||||
let token = Token::lookup_symbol_from_syntax(s).or_else(|| {
|
||||
if is_reserved_keyword_or_symbol(s) {
|
||||
Some(Token::Reserved(Box::new(s.into())))
|
||||
} else {
|
||||
NO_TOKEN
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
@ -256,13 +256,13 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(),
|
||||
// Standard or reserved keyword/symbol not in first position
|
||||
_ if !segments.is_empty() && token != NO_TOKEN => {
|
||||
_ if !segments.is_empty() && token.is_some() => {
|
||||
// Make it a custom keyword/symbol if it is disabled or reserved
|
||||
if (self
|
||||
.disabled_symbols
|
||||
.as_deref()
|
||||
.map_or(false, |m| m.contains(s))
|
||||
|| token.is_reserved())
|
||||
|| token.as_ref().map_or(false, Token::is_reserved))
|
||||
&& !self
|
||||
.custom_keywords
|
||||
.as_deref()
|
||||
@ -276,7 +276,7 @@ impl Engine {
|
||||
}
|
||||
// Standard keyword in first position but not disabled
|
||||
_ if segments.is_empty()
|
||||
&& token.is_standard_keyword()
|
||||
&& token.as_ref().map_or(false, Token::is_standard_keyword)
|
||||
&& !self
|
||||
.disabled_symbols
|
||||
.as_deref()
|
||||
@ -298,7 +298,7 @@ impl Engine {
|
||||
.disabled_symbols
|
||||
.as_deref()
|
||||
.map_or(false, |m| m.contains(s))
|
||||
|| (token.is_reserved()
|
||||
|| (token.as_ref().map_or(false, Token::is_reserved)
|
||||
&& !self
|
||||
.custom_keywords
|
||||
.as_deref()
|
||||
|
@ -3,7 +3,7 @@
|
||||
use super::{ASTFlags, ASTNode, Ident, Namespace, Stmt, StmtBlock};
|
||||
use crate::engine::{KEYWORD_FN_PTR, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE};
|
||||
use crate::func::hashing::ALT_ZERO_HASH;
|
||||
use crate::tokenizer::{Token, NO_TOKEN};
|
||||
use crate::tokenizer::Token;
|
||||
use crate::types::dynamic::Union;
|
||||
use crate::{
|
||||
calc_fn_hash, Dynamic, FnPtr, Identifier, ImmutableString, Position, SmartString, StaticVec,
|
||||
@ -207,8 +207,7 @@ pub struct FnCallExpr {
|
||||
/// Does this function call capture the parent scope?
|
||||
pub capture_parent_scope: bool,
|
||||
/// Is this function call a native operator?
|
||||
/// Otherwise set to [`Token::NONE`].
|
||||
pub op_token: Token,
|
||||
pub op_token: Option<Token>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for FnCallExpr {
|
||||
@ -222,7 +221,7 @@ impl fmt::Debug for FnCallExpr {
|
||||
ff.field("hash", &self.hashes)
|
||||
.field("name", &self.name)
|
||||
.field("args", &self.args);
|
||||
if self.op_token != NO_TOKEN {
|
||||
if self.op_token.is_some() {
|
||||
ff.field("op_token", &self.op_token);
|
||||
}
|
||||
if self.capture_parent_scope {
|
||||
@ -582,7 +581,7 @@ impl Expr {
|
||||
hashes: calc_fn_hash(None, f.fn_name(), 1).into(),
|
||||
args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(),
|
||||
capture_parent_scope: false,
|
||||
op_token: NO_TOKEN,
|
||||
op_token: None,
|
||||
}
|
||||
.into(),
|
||||
pos,
|
||||
|
@ -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.
|
||||
|
@ -5,7 +5,6 @@ use super::{Caches, GlobalRuntimeState, Target};
|
||||
use crate::ast::{ASTFlags, BinaryExpr, Expr, OpAssignment};
|
||||
use crate::config::hashing::SusLock;
|
||||
use crate::engine::{FN_IDX_GET, FN_IDX_SET};
|
||||
use crate::tokenizer::NO_TOKEN;
|
||||
use crate::types::dynamic::Union;
|
||||
use crate::{
|
||||
calc_fn_hash, Dynamic, Engine, FnArgsVec, Position, RhaiResult, RhaiResultOf, Scope, ERR,
|
||||
@ -73,7 +72,7 @@ impl Engine {
|
||||
let hash = hash_idx().0;
|
||||
let args = &mut [target, idx];
|
||||
|
||||
self.exec_native_fn_call(global, caches, FN_IDX_GET, NO_TOKEN, hash, args, true, pos)
|
||||
self.exec_native_fn_call(global, caches, FN_IDX_GET, None, hash, args, true, pos)
|
||||
.map(|(r, ..)| r)
|
||||
}
|
||||
|
||||
@ -95,7 +94,7 @@ impl Engine {
|
||||
let args = &mut [target, idx, new_val];
|
||||
|
||||
self.exec_native_fn_call(
|
||||
global, caches, FN_IDX_SET, NO_TOKEN, hash, args, is_ref_mut, pos,
|
||||
global, caches, FN_IDX_SET, None, hash, args, is_ref_mut, pos,
|
||||
)
|
||||
}
|
||||
|
||||
@ -751,8 +750,7 @@ impl Engine {
|
||||
|
||||
let (mut orig_val, ..) = self
|
||||
.exec_native_fn_call(
|
||||
global, caches, getter, NO_TOKEN, *hash_get, args, is_ref_mut,
|
||||
*pos,
|
||||
global, caches, getter, None, *hash_get, args, is_ref_mut, *pos,
|
||||
)
|
||||
.or_else(|err| match *err {
|
||||
// Try an indexer if property does not exist
|
||||
@ -786,7 +784,7 @@ impl Engine {
|
||||
let args = &mut [target.as_mut(), &mut new_val];
|
||||
|
||||
self.exec_native_fn_call(
|
||||
global, caches, setter, NO_TOKEN, *hash_set, args, is_ref_mut, *pos,
|
||||
global, caches, setter, None, *hash_set, args, is_ref_mut, *pos,
|
||||
)
|
||||
.or_else(|err| match *err {
|
||||
// Try an indexer if property does not exist
|
||||
@ -813,7 +811,7 @@ impl Engine {
|
||||
let args = &mut [target.as_mut()];
|
||||
|
||||
self.exec_native_fn_call(
|
||||
global, caches, getter, NO_TOKEN, *hash_get, args, is_ref_mut, *pos,
|
||||
global, caches, getter, None, *hash_get, args, is_ref_mut, *pos,
|
||||
)
|
||||
.map_or_else(
|
||||
|err| match *err {
|
||||
@ -904,8 +902,8 @@ impl Engine {
|
||||
// Assume getters are always pure
|
||||
let (mut val, ..) = self
|
||||
.exec_native_fn_call(
|
||||
global, caches, getter, NO_TOKEN, *hash_get, args,
|
||||
is_ref_mut, pos,
|
||||
global, caches, getter, None, *hash_get, args, is_ref_mut,
|
||||
pos,
|
||||
)
|
||||
.or_else(|err| match *err {
|
||||
// Try an indexer if property does not exist
|
||||
@ -940,7 +938,7 @@ impl Engine {
|
||||
// The return value is thrown away and not used.
|
||||
let _ = self
|
||||
.exec_native_fn_call(
|
||||
global, caches, setter, NO_TOKEN, *hash_set, args,
|
||||
global, caches, setter, None, *hash_set, args,
|
||||
is_ref_mut, pos,
|
||||
)
|
||||
.or_else(|err| match *err {
|
||||
|
@ -9,6 +9,60 @@ use std::borrow::Borrow;
|
||||
use std::prelude::v1::*;
|
||||
|
||||
impl Dynamic {
|
||||
/// Recursively calculate the sizes of an array.
|
||||
///
|
||||
/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if any interior data is shared (should never happen).
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
#[inline]
|
||||
pub(crate) fn calc_array_sizes(array: &crate::Array, _top: bool) -> (usize, usize, usize) {
|
||||
array
|
||||
.iter()
|
||||
.fold((0, 0, 0), |(ax, mx, sx), value| match value.0 {
|
||||
Union::Array(..) => {
|
||||
let (a, m, s) = value.calc_data_sizes(false);
|
||||
(ax + a + 1, mx + m, sx + s)
|
||||
}
|
||||
Union::Blob(ref a, ..) => (ax + 1 + a.len(), mx, sx),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(..) => {
|
||||
let (a, m, s) = value.calc_data_sizes(false);
|
||||
(ax + a + 1, mx + m, sx + s)
|
||||
}
|
||||
Union::Str(ref s, ..) => (ax + 1, mx, sx + s.len()),
|
||||
_ => (ax + 1, mx, sx),
|
||||
})
|
||||
}
|
||||
/// Recursively calculate the sizes of a map.
|
||||
///
|
||||
/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if any interior data is shared (should never happen).
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[inline]
|
||||
pub(crate) fn calc_map_sizes(map: &crate::Map, _top: bool) -> (usize, usize, usize) {
|
||||
map.values()
|
||||
.fold((0, 0, 0), |(ax, mx, sx), value| match value.0 {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(..) => {
|
||||
let (a, m, s) = value.calc_data_sizes(false);
|
||||
(ax + a, mx + m + 1, sx + s)
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(ref a, ..) => (ax + a.len(), mx, sx),
|
||||
Union::Map(..) => {
|
||||
let (a, m, s) = value.calc_data_sizes(false);
|
||||
(ax + a, mx + m + 1, sx + s)
|
||||
}
|
||||
Union::Str(ref s, ..) => (ax, mx + 1, sx + s.len()),
|
||||
_ => (ax, mx + 1, sx),
|
||||
})
|
||||
}
|
||||
/// Recursively calculate the sizes of a value.
|
||||
///
|
||||
/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
|
||||
@ -16,47 +70,15 @@ impl Dynamic {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if any interior data is shared (should never happen).
|
||||
#[inline]
|
||||
pub(crate) fn calc_data_sizes(&self, _top: bool) -> (usize, usize, usize) {
|
||||
match self.0 {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(ref arr, ..) => {
|
||||
arr.iter()
|
||||
.fold((0, 0, 0), |(ax, mx, sx), value| match value.0 {
|
||||
Union::Array(..) => {
|
||||
let (a, m, s) = value.calc_data_sizes(false);
|
||||
(ax + a + 1, mx + m, sx + s)
|
||||
}
|
||||
Union::Blob(ref a, ..) => (ax + 1 + a.len(), mx, sx),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(..) => {
|
||||
let (a, m, s) = value.calc_data_sizes(false);
|
||||
(ax + a + 1, mx + m, sx + s)
|
||||
}
|
||||
Union::Str(ref s, ..) => (ax + 1, mx, sx + s.len()),
|
||||
_ => (ax + 1, mx, sx),
|
||||
})
|
||||
}
|
||||
Union::Array(ref arr, ..) => Self::calc_array_sizes(&**arr, _top),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(ref blob, ..) => (blob.len(), 0, 0),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref map, ..) => {
|
||||
map.values()
|
||||
.fold((0, 0, 0), |(ax, mx, sx), value| match value.0 {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(..) => {
|
||||
let (a, m, s) = value.calc_data_sizes(false);
|
||||
(ax + a, mx + m + 1, sx + s)
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Blob(ref a, ..) => (ax + a.len(), mx, sx),
|
||||
Union::Map(..) => {
|
||||
let (a, m, s) = value.calc_data_sizes(false);
|
||||
(ax + a, mx + m + 1, sx + s)
|
||||
}
|
||||
Union::Str(ref s, ..) => (ax, mx + 1, sx + s.len()),
|
||||
_ => (ax, mx + 1, sx),
|
||||
})
|
||||
}
|
||||
Union::Map(ref map, ..) => Self::calc_map_sizes(&**map, _top),
|
||||
Union::Str(ref s, ..) => (0, 0, s.len()),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(..) if _top => self.read_lock::<Self>().unwrap().calc_data_sizes(true),
|
||||
|
107
src/eval/expr.rs
107
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
|
||||
|
@ -140,15 +140,16 @@ impl Engine {
|
||||
let args = &mut [&mut *lock_guard, &mut new_val];
|
||||
|
||||
if self.fast_operators() {
|
||||
if let Some((func, ctx)) =
|
||||
if let Some((func, need_context)) =
|
||||
get_builtin_op_assignment_fn(op_assign_token.clone(), args[0], args[1])
|
||||
{
|
||||
// Built-in found
|
||||
let op = op_assign_token.literal_syntax();
|
||||
auto_restore! { let orig_level = global.level; global.level += 1 }
|
||||
|
||||
let context = if ctx {
|
||||
Some((self, op, None, &*global, *op_pos).into())
|
||||
let context = if need_context {
|
||||
let source = global.source();
|
||||
Some((self, op, source, &*global, *op_pos).into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@ -158,7 +159,7 @@ impl Engine {
|
||||
|
||||
let op_assign = op_assign_token.literal_syntax();
|
||||
let op = op_token.literal_syntax();
|
||||
let token = op_assign_token.clone();
|
||||
let token = Some(op_assign_token.clone());
|
||||
|
||||
match self
|
||||
.exec_native_fn_call(global, caches, op_assign, token, hash, args, true, *op_pos)
|
||||
@ -167,7 +168,7 @@ impl Engine {
|
||||
Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_assign)) =>
|
||||
{
|
||||
// Expand to `var = var op rhs`
|
||||
let token = op_token.clone();
|
||||
let token = Some(op_token.clone());
|
||||
|
||||
*args[0] = self
|
||||
.exec_native_fn_call(
|
||||
|
@ -91,65 +91,65 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
|
||||
let type2 = y.type_id();
|
||||
|
||||
macro_rules! impl_op {
|
||||
($xx:ident $op:tt $yy:ident) => { (|_, args| {
|
||||
($xx:ident $op:tt $yy:ident) => { Some((|_, args| {
|
||||
let x = &*args[0].read_lock::<$xx>().unwrap();
|
||||
let y = &*args[1].read_lock::<$yy>().unwrap();
|
||||
Ok((x $op y).into())
|
||||
}, false) };
|
||||
($xx:ident . $func:ident ( $yy:ty )) => { (|_, args| {
|
||||
}, false)) };
|
||||
($xx:ident . $func:ident ( $yy:ty )) => { Some((|_, args| {
|
||||
let x = &*args[0].read_lock::<$xx>().unwrap();
|
||||
let y = &*args[1].read_lock::<$yy>().unwrap();
|
||||
Ok(x.$func(y).into())
|
||||
}, false) };
|
||||
($xx:ident . $func:ident ( $yy:ident . $yyy:ident () )) => { (|_, args| {
|
||||
}, false)) };
|
||||
($xx:ident . $func:ident ( $yy:ident . $yyy:ident () )) => { Some((|_, args| {
|
||||
let x = &*args[0].read_lock::<$xx>().unwrap();
|
||||
let y = &*args[1].read_lock::<$yy>().unwrap();
|
||||
Ok(x.$func(y.$yyy()).into())
|
||||
}, false) };
|
||||
($func:ident ( $op:tt )) => { (|_, args| {
|
||||
}, false)) };
|
||||
($func:ident ( $op:tt )) => { Some((|_, args| {
|
||||
let (x, y) = $func(args);
|
||||
Ok((x $op y).into())
|
||||
}, false) };
|
||||
($base:ty => $xx:ident $op:tt $yy:ident) => { (|_, args| {
|
||||
}, false)) };
|
||||
($base:ty => $xx:ident $op:tt $yy:ident) => { Some((|_, args| {
|
||||
let x = args[0].$xx().unwrap() as $base;
|
||||
let y = args[1].$yy().unwrap() as $base;
|
||||
Ok((x $op y).into())
|
||||
}, false) };
|
||||
($base:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty)) => { (|_, args| {
|
||||
}, false)) };
|
||||
($base:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty)) => { Some((|_, args| {
|
||||
let x = args[0].$xx().unwrap() as $base;
|
||||
let y = args[1].$yy().unwrap() as $base;
|
||||
Ok(x.$func(y as $yyy).into())
|
||||
}, false) };
|
||||
($base:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { (|_, args| {
|
||||
}, false)) };
|
||||
($base:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { Some((|_, args| {
|
||||
let x = args[0].$xx().unwrap() as $base;
|
||||
let y = args[1].$yy().unwrap() as $base;
|
||||
Ok($func(x, y).into())
|
||||
}, false) };
|
||||
($base:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| {
|
||||
}, false)) };
|
||||
($base:ty => $func:ident ( $xx:ident, $yy:ident )) => { Some((|_, args| {
|
||||
let x = args[0].$xx().unwrap() as $base;
|
||||
let y = args[1].$yy().unwrap() as $base;
|
||||
$func(x, y).map(Into::into)
|
||||
}, false) };
|
||||
(from $base:ty => $xx:ident $op:tt $yy:ident) => { (|_, args| {
|
||||
}, false)) };
|
||||
(from $base:ty => $xx:ident $op:tt $yy:ident) => { Some((|_, args| {
|
||||
let x = <$base>::from(args[0].$xx().unwrap());
|
||||
let y = <$base>::from(args[1].$yy().unwrap());
|
||||
Ok((x $op y).into())
|
||||
}, false) };
|
||||
(from $base:ty => $xx:ident . $func:ident ( $yy:ident )) => { (|_, args| {
|
||||
}, false)) };
|
||||
(from $base:ty => $xx:ident . $func:ident ( $yy:ident )) => { Some((|_, args| {
|
||||
let x = <$base>::from(args[0].$xx().unwrap());
|
||||
let y = <$base>::from(args[1].$yy().unwrap());
|
||||
Ok(x.$func(y).into())
|
||||
}, false) };
|
||||
(from $base:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { (|_, args| {
|
||||
}, false)) };
|
||||
(from $base:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { Some((|_, args| {
|
||||
let x = <$base>::from(args[0].$xx().unwrap());
|
||||
let y = <$base>::from(args[1].$yy().unwrap());
|
||||
Ok($func(x, y).into())
|
||||
}, false) };
|
||||
(from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| {
|
||||
}, false)) };
|
||||
(from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => { Some((|_, args| {
|
||||
let x = <$base>::from(args[0].$xx().unwrap());
|
||||
let y = <$base>::from(args[1].$yy().unwrap());
|
||||
$func(x, y).map(Into::into)
|
||||
}, false) };
|
||||
}, false)) };
|
||||
}
|
||||
|
||||
// Check for common patterns
|
||||
@ -161,25 +161,25 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
match op {
|
||||
Plus => return Some(impl_op!(INT => add(as_int, as_int))),
|
||||
Minus => return Some(impl_op!(INT => subtract(as_int, as_int))),
|
||||
Multiply => return Some(impl_op!(INT => multiply(as_int, as_int))),
|
||||
Divide => return Some(impl_op!(INT => divide(as_int, as_int))),
|
||||
Modulo => return Some(impl_op!(INT => modulo(as_int, as_int))),
|
||||
PowerOf => return Some(impl_op!(INT => power(as_int, as_int))),
|
||||
RightShift => return Some(impl_op!(INT => Ok(shift_right(as_int, as_int)))),
|
||||
LeftShift => return Some(impl_op!(INT => Ok(shift_left(as_int, as_int)))),
|
||||
Plus => return impl_op!(INT => add(as_int, as_int)),
|
||||
Minus => return impl_op!(INT => subtract(as_int, as_int)),
|
||||
Multiply => return impl_op!(INT => multiply(as_int, as_int)),
|
||||
Divide => return impl_op!(INT => divide(as_int, as_int)),
|
||||
Modulo => return impl_op!(INT => modulo(as_int, as_int)),
|
||||
PowerOf => return impl_op!(INT => power(as_int, as_int)),
|
||||
RightShift => return impl_op!(INT => Ok(shift_right(as_int, as_int))),
|
||||
LeftShift => return impl_op!(INT => Ok(shift_left(as_int, as_int))),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
#[cfg(feature = "unchecked")]
|
||||
match op {
|
||||
Plus => return Some(impl_op!(INT => as_int + as_int)),
|
||||
Minus => return Some(impl_op!(INT => as_int - as_int)),
|
||||
Multiply => return Some(impl_op!(INT => as_int * as_int)),
|
||||
Divide => return Some(impl_op!(INT => as_int / as_int)),
|
||||
Modulo => return Some(impl_op!(INT => as_int % as_int)),
|
||||
PowerOf => return Some(impl_op!(INT => as_int.pow(as_int as u32))),
|
||||
Plus => return impl_op!(INT => as_int + as_int),
|
||||
Minus => return impl_op!(INT => as_int - as_int),
|
||||
Multiply => return impl_op!(INT => as_int * as_int),
|
||||
Divide => return impl_op!(INT => as_int / as_int),
|
||||
Modulo => return impl_op!(INT => as_int % as_int),
|
||||
PowerOf => return impl_op!(INT => as_int.pow(as_int as u32)),
|
||||
RightShift => {
|
||||
return Some((
|
||||
|_, args| {
|
||||
@ -204,32 +204,32 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
|
||||
}
|
||||
|
||||
return match op {
|
||||
EqualsTo => Some(impl_op!(INT => as_int == as_int)),
|
||||
NotEqualsTo => Some(impl_op!(INT => as_int != as_int)),
|
||||
GreaterThan => Some(impl_op!(INT => as_int > as_int)),
|
||||
GreaterThanEqualsTo => Some(impl_op!(INT => as_int >= as_int)),
|
||||
LessThan => Some(impl_op!(INT => as_int < as_int)),
|
||||
LessThanEqualsTo => Some(impl_op!(INT => as_int <= as_int)),
|
||||
Ampersand => Some(impl_op!(INT => as_int & as_int)),
|
||||
Pipe => Some(impl_op!(INT => as_int | as_int)),
|
||||
XOr => Some(impl_op!(INT => as_int ^ as_int)),
|
||||
ExclusiveRange => Some(impl_op!(INT => as_int .. as_int)),
|
||||
InclusiveRange => Some(impl_op!(INT => as_int ..= as_int)),
|
||||
EqualsTo => impl_op!(INT => as_int == as_int),
|
||||
NotEqualsTo => impl_op!(INT => as_int != as_int),
|
||||
GreaterThan => impl_op!(INT => as_int > as_int),
|
||||
GreaterThanEqualsTo => impl_op!(INT => as_int >= as_int),
|
||||
LessThan => impl_op!(INT => as_int < as_int),
|
||||
LessThanEqualsTo => impl_op!(INT => as_int <= as_int),
|
||||
Ampersand => impl_op!(INT => as_int & as_int),
|
||||
Pipe => impl_op!(INT => as_int | as_int),
|
||||
XOr => impl_op!(INT => as_int ^ as_int),
|
||||
ExclusiveRange => impl_op!(INT => as_int .. as_int),
|
||||
InclusiveRange => impl_op!(INT => as_int ..= as_int),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<bool>() {
|
||||
return match op {
|
||||
EqualsTo => Some(impl_op!(bool => as_bool == as_bool)),
|
||||
NotEqualsTo => Some(impl_op!(bool => as_bool != as_bool)),
|
||||
GreaterThan => Some(impl_op!(bool => as_bool > as_bool)),
|
||||
GreaterThanEqualsTo => Some(impl_op!(bool => as_bool >= as_bool)),
|
||||
LessThan => Some(impl_op!(bool => as_bool < as_bool)),
|
||||
LessThanEqualsTo => Some(impl_op!(bool => as_bool <= as_bool)),
|
||||
Ampersand => Some(impl_op!(bool => as_bool & as_bool)),
|
||||
Pipe => Some(impl_op!(bool => as_bool | as_bool)),
|
||||
XOr => Some(impl_op!(bool => as_bool ^ as_bool)),
|
||||
EqualsTo => impl_op!(bool => as_bool == as_bool),
|
||||
NotEqualsTo => impl_op!(bool => as_bool != as_bool),
|
||||
GreaterThan => impl_op!(bool => as_bool > as_bool),
|
||||
GreaterThanEqualsTo => impl_op!(bool => as_bool >= as_bool),
|
||||
LessThan => impl_op!(bool => as_bool < as_bool),
|
||||
LessThanEqualsTo => impl_op!(bool => as_bool <= as_bool),
|
||||
Ampersand => impl_op!(bool => as_bool & as_bool),
|
||||
Pipe => impl_op!(bool => as_bool | as_bool),
|
||||
XOr => impl_op!(bool => as_bool ^ as_bool),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
@ -250,13 +250,13 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
|
||||
},
|
||||
CHECKED_BUILD,
|
||||
)),
|
||||
Minus => Some(impl_op!(ImmutableString - ImmutableString)),
|
||||
EqualsTo => Some(impl_op!(ImmutableString == ImmutableString)),
|
||||
NotEqualsTo => Some(impl_op!(ImmutableString != ImmutableString)),
|
||||
GreaterThan => Some(impl_op!(ImmutableString > ImmutableString)),
|
||||
GreaterThanEqualsTo => Some(impl_op!(ImmutableString >= ImmutableString)),
|
||||
LessThan => Some(impl_op!(ImmutableString < ImmutableString)),
|
||||
LessThanEqualsTo => Some(impl_op!(ImmutableString <= ImmutableString)),
|
||||
Minus => impl_op!(ImmutableString - ImmutableString),
|
||||
EqualsTo => impl_op!(ImmutableString == ImmutableString),
|
||||
NotEqualsTo => impl_op!(ImmutableString != ImmutableString),
|
||||
GreaterThan => impl_op!(ImmutableString > ImmutableString),
|
||||
GreaterThanEqualsTo => impl_op!(ImmutableString >= ImmutableString),
|
||||
LessThan => impl_op!(ImmutableString < ImmutableString),
|
||||
LessThanEqualsTo => impl_op!(ImmutableString <= ImmutableString),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
@ -279,12 +279,12 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
|
||||
},
|
||||
CHECKED_BUILD,
|
||||
)),
|
||||
EqualsTo => Some(impl_op!(char => as_char == as_char)),
|
||||
NotEqualsTo => Some(impl_op!(char => as_char != as_char)),
|
||||
GreaterThan => Some(impl_op!(char => as_char > as_char)),
|
||||
GreaterThanEqualsTo => Some(impl_op!(char => as_char >= as_char)),
|
||||
LessThan => Some(impl_op!(char => as_char < as_char)),
|
||||
LessThanEqualsTo => Some(impl_op!(char => as_char <= as_char)),
|
||||
EqualsTo => impl_op!(char => as_char == as_char),
|
||||
NotEqualsTo => impl_op!(char => as_char != as_char),
|
||||
GreaterThan => impl_op!(char => as_char > as_char),
|
||||
GreaterThanEqualsTo => impl_op!(char => as_char >= as_char),
|
||||
LessThan => impl_op!(char => as_char < as_char),
|
||||
LessThanEqualsTo => impl_op!(char => as_char <= as_char),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
@ -316,8 +316,8 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
|
||||
},
|
||||
CHECKED_BUILD,
|
||||
)),
|
||||
EqualsTo => Some(impl_op!(Blob == Blob)),
|
||||
NotEqualsTo => Some(impl_op!(Blob != Blob)),
|
||||
EqualsTo => impl_op!(Blob == Blob),
|
||||
NotEqualsTo => impl_op!(Blob != Blob),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
@ -338,18 +338,18 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
|
||||
($x:ty, $xx:ident, $y:ty, $yy:ident) => {
|
||||
if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) {
|
||||
return match op {
|
||||
Plus => Some(impl_op!(FLOAT => $xx + $yy)),
|
||||
Minus => Some(impl_op!(FLOAT => $xx - $yy)),
|
||||
Multiply => Some(impl_op!(FLOAT => $xx * $yy)),
|
||||
Divide => Some(impl_op!(FLOAT => $xx / $yy)),
|
||||
Modulo => Some(impl_op!(FLOAT => $xx % $yy)),
|
||||
PowerOf => Some(impl_op!(FLOAT => $xx.powf($yy as FLOAT))),
|
||||
EqualsTo => Some(impl_op!(FLOAT => $xx == $yy)),
|
||||
NotEqualsTo => Some(impl_op!(FLOAT => $xx != $yy)),
|
||||
GreaterThan => Some(impl_op!(FLOAT => $xx > $yy)),
|
||||
GreaterThanEqualsTo => Some(impl_op!(FLOAT => $xx >= $yy)),
|
||||
LessThan => Some(impl_op!(FLOAT => $xx < $yy)),
|
||||
LessThanEqualsTo => Some(impl_op!(FLOAT => $xx <= $yy)),
|
||||
Plus => impl_op!(FLOAT => $xx + $yy),
|
||||
Minus => impl_op!(FLOAT => $xx - $yy),
|
||||
Multiply => impl_op!(FLOAT => $xx * $yy),
|
||||
Divide => impl_op!(FLOAT => $xx / $yy),
|
||||
Modulo => impl_op!(FLOAT => $xx % $yy),
|
||||
PowerOf => impl_op!(FLOAT => $xx.powf($yy as FLOAT)),
|
||||
EqualsTo => impl_op!(FLOAT => $xx == $yy),
|
||||
NotEqualsTo => impl_op!(FLOAT => $xx != $yy),
|
||||
GreaterThan => impl_op!(FLOAT => $xx > $yy),
|
||||
GreaterThanEqualsTo => impl_op!(FLOAT => $xx >= $yy),
|
||||
LessThan => impl_op!(FLOAT => $xx < $yy),
|
||||
LessThanEqualsTo => impl_op!(FLOAT => $xx <= $yy),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
@ -373,12 +373,12 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
match op {
|
||||
Plus => return Some(impl_op!(from Decimal => add($xx, $yy))),
|
||||
Minus => return Some(impl_op!(from Decimal => subtract($xx, $yy))),
|
||||
Multiply => return Some(impl_op!(from Decimal => multiply($xx, $yy))),
|
||||
Divide => return Some(impl_op!(from Decimal => divide($xx, $yy))),
|
||||
Modulo => return Some(impl_op!(from Decimal => modulo($xx, $yy))),
|
||||
PowerOf => return Some(impl_op!(from Decimal => power($xx, $yy))),
|
||||
Plus => return impl_op!(from Decimal => add($xx, $yy)),
|
||||
Minus => return impl_op!(from Decimal => subtract($xx, $yy)),
|
||||
Multiply => return impl_op!(from Decimal => multiply($xx, $yy)),
|
||||
Divide => return impl_op!(from Decimal => divide($xx, $yy)),
|
||||
Modulo => return impl_op!(from Decimal => modulo($xx, $yy)),
|
||||
PowerOf => return impl_op!(from Decimal => power($xx, $yy)),
|
||||
_ => ()
|
||||
}
|
||||
|
||||
@ -387,22 +387,22 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
|
||||
|
||||
#[cfg(feature = "unchecked")]
|
||||
match op {
|
||||
Plus => return Some(impl_op!(from Decimal => $xx + $yy)),
|
||||
Minus => return Some(impl_op!(from Decimal => $xx - $yy)),
|
||||
Multiply => return Some(impl_op!(from Decimal => $xx * $yy)),
|
||||
Divide => return Some(impl_op!(from Decimal => $xx / $yy)),
|
||||
Modulo => return Some(impl_op!(from Decimal => $xx % $yy)),
|
||||
PowerOf => return Some(impl_op!(from Decimal => $xx.powd($yy))),
|
||||
Plus => return impl_op!(from Decimal => $xx + $yy),
|
||||
Minus => return impl_op!(from Decimal => $xx - $yy),
|
||||
Multiply => return impl_op!(from Decimal => $xx * $yy),
|
||||
Divide => return impl_op!(from Decimal => $xx / $yy),
|
||||
Modulo => return impl_op!(from Decimal => $xx % $yy),
|
||||
PowerOf => return impl_op!(from Decimal => $xx.powd($yy)),
|
||||
_ => ()
|
||||
}
|
||||
|
||||
return match op {
|
||||
EqualsTo => Some(impl_op!(from Decimal => $xx == $yy)),
|
||||
NotEqualsTo => Some(impl_op!(from Decimal => $xx != $yy)),
|
||||
GreaterThan => Some(impl_op!(from Decimal => $xx > $yy)),
|
||||
GreaterThanEqualsTo => Some(impl_op!(from Decimal => $xx >= $yy)),
|
||||
LessThan => Some(impl_op!(from Decimal => $xx < $yy)),
|
||||
LessThanEqualsTo => Some(impl_op!(from Decimal => $xx <= $yy)),
|
||||
EqualsTo => impl_op!(from Decimal => $xx == $yy),
|
||||
NotEqualsTo => impl_op!(from Decimal => $xx != $yy),
|
||||
GreaterThan => impl_op!(from Decimal => $xx > $yy),
|
||||
GreaterThanEqualsTo => impl_op!(from Decimal => $xx >= $yy),
|
||||
LessThan => impl_op!(from Decimal => $xx < $yy),
|
||||
LessThanEqualsTo => impl_op!(from Decimal => $xx <= $yy),
|
||||
_ => None
|
||||
};
|
||||
}
|
||||
@ -444,12 +444,12 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
|
||||
},
|
||||
CHECKED_BUILD,
|
||||
)),
|
||||
EqualsTo => Some(impl_op!(get_s1s2(==))),
|
||||
NotEqualsTo => Some(impl_op!(get_s1s2(!=))),
|
||||
GreaterThan => Some(impl_op!(get_s1s2(>))),
|
||||
GreaterThanEqualsTo => Some(impl_op!(get_s1s2(>=))),
|
||||
LessThan => Some(impl_op!(get_s1s2(<))),
|
||||
LessThanEqualsTo => Some(impl_op!(get_s1s2(<=))),
|
||||
EqualsTo => impl_op!(get_s1s2(==)),
|
||||
NotEqualsTo => impl_op!(get_s1s2(!=)),
|
||||
GreaterThan => impl_op!(get_s1s2(>)),
|
||||
GreaterThanEqualsTo => impl_op!(get_s1s2(>=)),
|
||||
LessThan => impl_op!(get_s1s2(<)),
|
||||
LessThanEqualsTo => impl_op!(get_s1s2(<=)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
@ -486,12 +486,12 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
|
||||
},
|
||||
false,
|
||||
)),
|
||||
EqualsTo => Some(impl_op!(get_s1s2(==))),
|
||||
NotEqualsTo => Some(impl_op!(get_s1s2(!=))),
|
||||
GreaterThan => Some(impl_op!(get_s1s2(>))),
|
||||
GreaterThanEqualsTo => Some(impl_op!(get_s1s2(>=))),
|
||||
LessThan => Some(impl_op!(get_s1s2(<))),
|
||||
LessThanEqualsTo => Some(impl_op!(get_s1s2(<=))),
|
||||
EqualsTo => impl_op!(get_s1s2(==)),
|
||||
NotEqualsTo => impl_op!(get_s1s2(!=)),
|
||||
GreaterThan => impl_op!(get_s1s2(>)),
|
||||
GreaterThanEqualsTo => impl_op!(get_s1s2(>=)),
|
||||
LessThan => impl_op!(get_s1s2(<)),
|
||||
LessThanEqualsTo => impl_op!(get_s1s2(<=)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
@ -568,16 +568,16 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
|
||||
// Handle ranges here because ranges are implemented as custom type
|
||||
if type1 == TypeId::of::<ExclusiveRange>() && type1 == type2 {
|
||||
return match op {
|
||||
EqualsTo => Some(impl_op!(ExclusiveRange == ExclusiveRange)),
|
||||
NotEqualsTo => Some(impl_op!(ExclusiveRange != ExclusiveRange)),
|
||||
EqualsTo => impl_op!(ExclusiveRange == ExclusiveRange),
|
||||
NotEqualsTo => impl_op!(ExclusiveRange != ExclusiveRange),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<InclusiveRange>() && type1 == type2 {
|
||||
return match op {
|
||||
EqualsTo => Some(impl_op!(InclusiveRange == InclusiveRange)),
|
||||
NotEqualsTo => Some(impl_op!(InclusiveRange != InclusiveRange)),
|
||||
EqualsTo => impl_op!(InclusiveRange == InclusiveRange),
|
||||
NotEqualsTo => impl_op!(InclusiveRange != InclusiveRange),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
@ -626,54 +626,54 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
|
||||
let type2 = y.type_id();
|
||||
|
||||
macro_rules! impl_op {
|
||||
($x:ty = x $op:tt $yy:ident) => { (|_, args| {
|
||||
($x:ty = x $op:tt $yy:ident) => { Some((|_, args| {
|
||||
let x = args[0].$yy().unwrap();
|
||||
let y = args[1].$yy().unwrap() as $x;
|
||||
Ok((*args[0].write_lock::<$x>().unwrap() = x $op y).into())
|
||||
}, false) };
|
||||
($x:ident $op:tt $yy:ident) => { (|_, args| {
|
||||
}, false)) };
|
||||
($x:ident $op:tt $yy:ident) => { Some((|_, args| {
|
||||
let y = args[1].$yy().unwrap() as $x;
|
||||
Ok((*args[0].write_lock::<$x>().unwrap() $op y).into())
|
||||
}, false) };
|
||||
($x:ident $op:tt $yy:ident as $yyy:ty) => { (|_, args| {
|
||||
}, false)) };
|
||||
($x:ident $op:tt $yy:ident as $yyy:ty) => { Some((|_, args| {
|
||||
let y = args[1].$yy().unwrap() as $yyy;
|
||||
Ok((*args[0].write_lock::<$x>().unwrap() $op y).into())
|
||||
}, false) };
|
||||
($x:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty )) => { (|_, args| {
|
||||
}, false)) };
|
||||
($x:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty )) => { Some((|_, args| {
|
||||
let x = args[0].$xx().unwrap();
|
||||
let y = args[1].$yy().unwrap() as $x;
|
||||
Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y as $yyy)).into())
|
||||
}, false) };
|
||||
($x:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { (|_, args| {
|
||||
}, false)) };
|
||||
($x:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { Some((|_, args| {
|
||||
let x = args[0].$xx().unwrap();
|
||||
let y = args[1].$yy().unwrap() as $x;
|
||||
let v: Dynamic = $func(x, y).into();
|
||||
Ok((*args[0].write_lock().unwrap() = v).into())
|
||||
}, false) };
|
||||
($x:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| {
|
||||
}, false)) };
|
||||
($x:ty => $func:ident ( $xx:ident, $yy:ident )) => { Some((|_, args| {
|
||||
let x = args[0].$xx().unwrap();
|
||||
let y = args[1].$yy().unwrap() as $x;
|
||||
Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into())
|
||||
}, false) };
|
||||
(from $x:ident $op:tt $yy:ident) => { (|_, args| {
|
||||
}, false)) };
|
||||
(from $x:ident $op:tt $yy:ident) => { Some((|_, args| {
|
||||
let y = <$x>::from(args[1].$yy().unwrap());
|
||||
Ok((*args[0].write_lock::<$x>().unwrap() $op y).into())
|
||||
}, false) };
|
||||
(from $x:ty => $xx:ident . $func:ident ( $yy:ident )) => { (|_, args| {
|
||||
}, false)) };
|
||||
(from $x:ty => $xx:ident . $func:ident ( $yy:ident )) => { Some((|_, args| {
|
||||
let x = args[0].$xx().unwrap();
|
||||
let y = <$x>::from(args[1].$yy().unwrap());
|
||||
Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y)).into())
|
||||
}, false) };
|
||||
(from $x:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { (|_, args| {
|
||||
}, false)) };
|
||||
(from $x:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { Some((|_, args| {
|
||||
let x = args[0].$xx().unwrap();
|
||||
let y = <$x>::from(args[1].$yy().unwrap());
|
||||
Ok((*args[0].write_lock().unwrap() = $func(x, y).into()).into())
|
||||
}, false) };
|
||||
(from $x:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| {
|
||||
}, false)) };
|
||||
(from $x:ty => $func:ident ( $xx:ident, $yy:ident )) => { Some((|_, args| {
|
||||
let x = args[0].$xx().unwrap();
|
||||
let y = <$x>::from(args[1].$yy().unwrap());
|
||||
Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into())
|
||||
}, false) };
|
||||
}, false)) };
|
||||
}
|
||||
|
||||
// Check for common patterns
|
||||
@ -685,25 +685,25 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
match op {
|
||||
PlusAssign => return Some(impl_op!(INT => add(as_int, as_int))),
|
||||
MinusAssign => return Some(impl_op!(INT => subtract(as_int, as_int))),
|
||||
MultiplyAssign => return Some(impl_op!(INT => multiply(as_int, as_int))),
|
||||
DivideAssign => return Some(impl_op!(INT => divide(as_int, as_int))),
|
||||
ModuloAssign => return Some(impl_op!(INT => modulo(as_int, as_int))),
|
||||
PowerOfAssign => return Some(impl_op!(INT => power(as_int, as_int))),
|
||||
RightShiftAssign => return Some(impl_op!(INT => Ok(shift_right(as_int, as_int)))),
|
||||
LeftShiftAssign => return Some(impl_op!(INT => Ok(shift_left(as_int, as_int)))),
|
||||
PlusAssign => return impl_op!(INT => add(as_int, as_int)),
|
||||
MinusAssign => return impl_op!(INT => subtract(as_int, as_int)),
|
||||
MultiplyAssign => return impl_op!(INT => multiply(as_int, as_int)),
|
||||
DivideAssign => return impl_op!(INT => divide(as_int, as_int)),
|
||||
ModuloAssign => return impl_op!(INT => modulo(as_int, as_int)),
|
||||
PowerOfAssign => return impl_op!(INT => power(as_int, as_int)),
|
||||
RightShiftAssign => return impl_op!(INT => Ok(shift_right(as_int, as_int))),
|
||||
LeftShiftAssign => return impl_op!(INT => Ok(shift_left(as_int, as_int))),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
#[cfg(feature = "unchecked")]
|
||||
match op {
|
||||
PlusAssign => return Some(impl_op!(INT += as_int)),
|
||||
MinusAssign => return Some(impl_op!(INT -= as_int)),
|
||||
MultiplyAssign => return Some(impl_op!(INT *= as_int)),
|
||||
DivideAssign => return Some(impl_op!(INT /= as_int)),
|
||||
ModuloAssign => return Some(impl_op!(INT %= as_int)),
|
||||
PowerOfAssign => return Some(impl_op!(INT => as_int.pow(as_int as u32))),
|
||||
PlusAssign => return impl_op!(INT += as_int),
|
||||
MinusAssign => return impl_op!(INT -= as_int),
|
||||
MultiplyAssign => return impl_op!(INT *= as_int),
|
||||
DivideAssign => return impl_op!(INT /= as_int),
|
||||
ModuloAssign => return impl_op!(INT %= as_int),
|
||||
PowerOfAssign => return impl_op!(INT => as_int.pow(as_int as u32)),
|
||||
RightShiftAssign => {
|
||||
return Some((
|
||||
|_, args| {
|
||||
@ -730,17 +730,17 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
|
||||
}
|
||||
|
||||
return match op {
|
||||
AndAssign => Some(impl_op!(INT &= as_int)),
|
||||
OrAssign => Some(impl_op!(INT |= as_int)),
|
||||
XOrAssign => Some(impl_op!(INT ^= as_int)),
|
||||
AndAssign => impl_op!(INT &= as_int),
|
||||
OrAssign => impl_op!(INT |= as_int),
|
||||
XOrAssign => impl_op!(INT ^= as_int),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<bool>() {
|
||||
return match op {
|
||||
AndAssign => Some(impl_op!(bool = x && as_bool)),
|
||||
OrAssign => Some(impl_op!(bool = x || as_bool)),
|
||||
AndAssign => impl_op!(bool = x && as_bool),
|
||||
OrAssign => impl_op!(bool = x || as_bool),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
@ -861,12 +861,12 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
|
||||
($x:ident, $xx:ident, $y:ty, $yy:ident) => {
|
||||
if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) {
|
||||
return match op {
|
||||
PlusAssign => Some(impl_op!($x += $yy)),
|
||||
MinusAssign => Some(impl_op!($x -= $yy)),
|
||||
MultiplyAssign => Some(impl_op!($x *= $yy)),
|
||||
DivideAssign => Some(impl_op!($x /= $yy)),
|
||||
ModuloAssign => Some(impl_op!($x %= $yy)),
|
||||
PowerOfAssign => Some(impl_op!($x => $xx.powf($yy as $x))),
|
||||
PlusAssign => impl_op!($x += $yy),
|
||||
MinusAssign => impl_op!($x -= $yy),
|
||||
MultiplyAssign => impl_op!($x *= $yy),
|
||||
DivideAssign => impl_op!($x /= $yy),
|
||||
ModuloAssign => impl_op!($x %= $yy),
|
||||
PowerOfAssign => impl_op!($x => $xx.powf($yy as $x)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
@ -889,12 +889,12 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
return match op {
|
||||
PlusAssign => Some(impl_op!(from $x => add($xx, $yy))),
|
||||
MinusAssign => Some(impl_op!(from $x => subtract($xx, $yy))),
|
||||
MultiplyAssign => Some(impl_op!(from $x => multiply($xx, $yy))),
|
||||
DivideAssign => Some(impl_op!(from $x => divide($xx, $yy))),
|
||||
ModuloAssign => Some(impl_op!(from $x => modulo($xx, $yy))),
|
||||
PowerOfAssign => Some(impl_op!(from $x => power($xx, $yy))),
|
||||
PlusAssign => impl_op!(from $x => add($xx, $yy)),
|
||||
MinusAssign => impl_op!(from $x => subtract($xx, $yy)),
|
||||
MultiplyAssign => impl_op!(from $x => multiply($xx, $yy)),
|
||||
DivideAssign => impl_op!(from $x => divide($xx, $yy)),
|
||||
ModuloAssign => impl_op!(from $x => modulo($xx, $yy)),
|
||||
PowerOfAssign => impl_op!(from $x => power($xx, $yy)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@ -903,12 +903,12 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
|
||||
|
||||
#[cfg(feature = "unchecked")]
|
||||
return match op {
|
||||
PlusAssign => Some(impl_op!(from $x += $yy)),
|
||||
MinusAssign => Some(impl_op!(from $x -= $yy)),
|
||||
MultiplyAssign => Some(impl_op!(from $x *= $yy)),
|
||||
DivideAssign => Some(impl_op!(from $x /= $yy)),
|
||||
ModuloAssign => Some(impl_op!(from $x %= $yy)),
|
||||
PowerOfAssign => Some(impl_op!(from $x => $xx.powd($yy))),
|
||||
PlusAssign => impl_op!(from $x += $yy),
|
||||
MinusAssign => impl_op!(from $x -= $yy),
|
||||
MultiplyAssign => impl_op!(from $x *= $yy),
|
||||
DivideAssign => impl_op!(from $x /= $yy),
|
||||
ModuloAssign => impl_op!(from $x %= $yy),
|
||||
PowerOfAssign => impl_op!(from $x => $xx.powd($yy)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
@ -939,7 +939,7 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
|
||||
},
|
||||
CHECKED_BUILD,
|
||||
)),
|
||||
MinusAssign => Some(impl_op!(ImmutableString -= as_char as char)),
|
||||
MinusAssign => impl_op!(ImmutableString -= as_char as char),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use crate::engine::{
|
||||
KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||
};
|
||||
use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState};
|
||||
use crate::tokenizer::{is_valid_function_name, Token, NO_TOKEN};
|
||||
use crate::tokenizer::{is_valid_function_name, Token};
|
||||
use crate::{
|
||||
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString,
|
||||
OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, Shared, ERR,
|
||||
@ -164,7 +164,7 @@ impl Engine {
|
||||
_global: &GlobalRuntimeState,
|
||||
caches: &'s mut Caches,
|
||||
local_entry: &'s mut Option<FnResolutionCacheEntry>,
|
||||
op_token: Token,
|
||||
op_token: Option<Token>,
|
||||
hash_base: u64,
|
||||
args: Option<&mut FnCallArgs>,
|
||||
allow_dynamic: bool,
|
||||
@ -270,31 +270,30 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Try to find a built-in version
|
||||
let builtin = args.and_then(|args| match op_token {
|
||||
Token::NONE => None,
|
||||
token if token.is_op_assignment() => {
|
||||
let (first_arg, rest_args) = args.split_first().unwrap();
|
||||
let builtin =
|
||||
args.and_then(|args| match op_token {
|
||||
None => None,
|
||||
Some(token) if token.is_op_assignment() => {
|
||||
let (first_arg, rest_args) = args.split_first().unwrap();
|
||||
|
||||
get_builtin_op_assignment_fn(token, first_arg, rest_args[0]).map(
|
||||
|(f, has_context)| FnResolutionCacheEntry {
|
||||
get_builtin_op_assignment_fn(token, first_arg, rest_args[0])
|
||||
.map(|(f, has_context)| FnResolutionCacheEntry {
|
||||
func: CallableFunction::Method {
|
||||
func: Shared::new(f),
|
||||
has_context,
|
||||
},
|
||||
source: None,
|
||||
})
|
||||
}
|
||||
Some(token) => get_builtin_binary_op_fn(token, args[0], args[1])
|
||||
.map(|(f, has_context)| FnResolutionCacheEntry {
|
||||
func: CallableFunction::Method {
|
||||
func: Shared::new(f),
|
||||
has_context,
|
||||
},
|
||||
source: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
token => get_builtin_binary_op_fn(token, args[0], args[1]).map(
|
||||
|(f, has_context)| FnResolutionCacheEntry {
|
||||
func: CallableFunction::Method {
|
||||
func: Shared::new(f),
|
||||
has_context,
|
||||
},
|
||||
source: None,
|
||||
},
|
||||
),
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
return if cache.filter.is_absent_and_set(hash) {
|
||||
// Do not cache "one-hit wonders"
|
||||
@ -346,7 +345,7 @@ impl Engine {
|
||||
global: &mut GlobalRuntimeState,
|
||||
caches: &mut Caches,
|
||||
name: &str,
|
||||
op_token: Token,
|
||||
op_token: Option<Token>,
|
||||
hash: u64,
|
||||
args: &mut FnCallArgs,
|
||||
is_ref_mut: bool,
|
||||
@ -568,21 +567,13 @@ impl Engine {
|
||||
caches: &mut Caches,
|
||||
_scope: Option<&mut Scope>,
|
||||
fn_name: &str,
|
||||
op_token: Token,
|
||||
op_token: Option<Token>,
|
||||
hashes: FnCallHashes,
|
||||
mut _args: &mut FnCallArgs,
|
||||
is_ref_mut: bool,
|
||||
_is_method_call: bool,
|
||||
pos: Position,
|
||||
) -> RhaiResultOf<(Dynamic, bool)> {
|
||||
fn no_method_err(name: &str, pos: Position) -> RhaiResultOf<(Dynamic, bool)> {
|
||||
Err(ERR::ErrorRuntime(
|
||||
format!("'{name}' should not be called this way. Try {name}(...);").into(),
|
||||
pos,
|
||||
)
|
||||
.into())
|
||||
}
|
||||
|
||||
// Check for data race.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
ensure_no_data_race(fn_name, _args, is_ref_mut)?;
|
||||
@ -622,16 +613,13 @@ impl Engine {
|
||||
|
||||
// Handle is_shared()
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
crate::engine::KEYWORD_IS_SHARED if _args.len() == 1 => {
|
||||
return no_method_err(fn_name, pos)
|
||||
crate::engine::KEYWORD_IS_SHARED => {
|
||||
unreachable!("{} called as method", fn_name)
|
||||
}
|
||||
|
||||
KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR if _args.len() == 1 => {
|
||||
return no_method_err(fn_name, pos)
|
||||
}
|
||||
|
||||
KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY if !_args.is_empty() => {
|
||||
return no_method_err(fn_name, pos)
|
||||
KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR | KEYWORD_FN_PTR_CALL
|
||||
| KEYWORD_FN_PTR_CURRY => {
|
||||
unreachable!("{} called as method", fn_name)
|
||||
}
|
||||
|
||||
_ => (),
|
||||
@ -645,7 +633,7 @@ impl Engine {
|
||||
let local_entry = &mut None;
|
||||
|
||||
if let Some(FnResolutionCacheEntry { func, ref source }) = self
|
||||
.resolve_fn(global, caches, local_entry, NO_TOKEN, hash, None, false)
|
||||
.resolve_fn(global, caches, local_entry, None, hash, None, false)
|
||||
.cloned()
|
||||
{
|
||||
// Script function call
|
||||
@ -812,7 +800,7 @@ impl Engine {
|
||||
caches,
|
||||
None,
|
||||
fn_name,
|
||||
NO_TOKEN,
|
||||
None,
|
||||
new_hash,
|
||||
args,
|
||||
false,
|
||||
@ -899,7 +887,7 @@ impl Engine {
|
||||
caches,
|
||||
None,
|
||||
&fn_name,
|
||||
NO_TOKEN,
|
||||
None,
|
||||
new_hash,
|
||||
args,
|
||||
is_ref_mut,
|
||||
@ -986,7 +974,7 @@ impl Engine {
|
||||
caches,
|
||||
None,
|
||||
fn_name,
|
||||
NO_TOKEN,
|
||||
None,
|
||||
hash,
|
||||
&mut args,
|
||||
is_ref_mut,
|
||||
@ -1012,7 +1000,7 @@ impl Engine {
|
||||
scope: &mut Scope,
|
||||
mut this_ptr: Option<&mut Dynamic>,
|
||||
fn_name: &str,
|
||||
op_token: Token,
|
||||
op_token: Option<Token>,
|
||||
first_arg: Option<&Expr>,
|
||||
args_expr: &[Expr],
|
||||
hashes: FnCallHashes,
|
||||
@ -1028,7 +1016,7 @@ impl Engine {
|
||||
let redirected; // Handle call() - Redirect function call
|
||||
|
||||
match name {
|
||||
_ if op_token != NO_TOKEN => (),
|
||||
_ if op_token.is_some() => (),
|
||||
|
||||
// Handle call(fn_ptr, ...)
|
||||
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
|
||||
@ -1582,7 +1570,7 @@ impl Engine {
|
||||
let op_token = op_token.clone();
|
||||
|
||||
// Short-circuit native unary operator call if under Fast Operators mode
|
||||
if op_token == Token::Bang && self.fast_operators() && args.len() == 1 {
|
||||
if op_token == Some(Token::Bang) && self.fast_operators() && args.len() == 1 {
|
||||
let mut value = self
|
||||
.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])?
|
||||
.0
|
||||
@ -1598,7 +1586,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Short-circuit native binary operator call if under Fast Operators mode
|
||||
if op_token != NO_TOKEN && self.fast_operators() && args.len() == 2 {
|
||||
if op_token.is_some() && self.fast_operators() && args.len() == 2 {
|
||||
let mut lhs = self
|
||||
.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])?
|
||||
.0
|
||||
@ -1611,13 +1599,13 @@ impl Engine {
|
||||
|
||||
let operands = &mut [&mut lhs, &mut rhs];
|
||||
|
||||
if let Some((func, ctx)) =
|
||||
get_builtin_binary_op_fn(op_token.clone(), operands[0], operands[1])
|
||||
if let Some((func, need_context)) =
|
||||
get_builtin_binary_op_fn(op_token.clone().unwrap(), operands[0], operands[1])
|
||||
{
|
||||
// Built-in found
|
||||
auto_restore! { let orig_level = global.level; global.level += 1 }
|
||||
|
||||
let context = if ctx {
|
||||
let context = if need_context {
|
||||
Some((self, name.as_str(), None, &*global, pos).into())
|
||||
} else {
|
||||
None
|
||||
|
@ -4,7 +4,7 @@ use super::call::FnCallArgs;
|
||||
use crate::ast::FnCallHashes;
|
||||
use crate::eval::{Caches, GlobalRuntimeState};
|
||||
use crate::plugin::PluginFunction;
|
||||
use crate::tokenizer::{is_valid_function_name, Token, TokenizeState, NO_TOKEN};
|
||||
use crate::tokenizer::{is_valid_function_name, Token, TokenizeState};
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::{
|
||||
calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Position, RhaiResult, RhaiResultOf,
|
||||
@ -436,7 +436,7 @@ impl<'a> NativeCallContext<'a> {
|
||||
let caches = &mut Caches::new();
|
||||
|
||||
let fn_name = fn_name.as_ref();
|
||||
let op_token = Token::lookup_symbol_from_syntax(fn_name).unwrap_or(NO_TOKEN);
|
||||
let op_token = Token::lookup_symbol_from_syntax(fn_name);
|
||||
let args_len = args.len();
|
||||
|
||||
if native_only {
|
||||
|
@ -147,7 +147,7 @@ impl<'a> OptimizerState<'a> {
|
||||
pub fn call_fn_with_constant_arguments(
|
||||
&mut self,
|
||||
fn_name: &str,
|
||||
op_token: Token,
|
||||
op_token: Option<Token>,
|
||||
arg_values: &mut [Dynamic],
|
||||
) -> Option<Dynamic> {
|
||||
self.engine
|
||||
@ -1137,11 +1137,11 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
||||
return;
|
||||
}
|
||||
// Overloaded operators can override built-in.
|
||||
_ if x.args.len() == 2 && x.op_token != Token::NONE && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => {
|
||||
if let Some(result) = get_builtin_binary_op_fn(x.op_token.clone(), &arg_values[0], &arg_values[1])
|
||||
_ if x.args.len() == 2 && x.op_token.is_some() && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => {
|
||||
if let Some(result) = get_builtin_binary_op_fn(x.op_token.clone().unwrap(), &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
|
||||
};
|
||||
|
@ -235,52 +235,15 @@ pub mod array_functions {
|
||||
|
||||
// Check if array will be over max size limit
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
use crate::types::dynamic::Union;
|
||||
if _ctx.engine().max_array_size() > 0 {
|
||||
let pad = len - array.len();
|
||||
let (a, m, s) = Dynamic::calc_array_sizes(array, true);
|
||||
let (ax, mx, sx) = item.calc_data_sizes(true);
|
||||
|
||||
if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() {
|
||||
return Err(
|
||||
ERR::ErrorDataTooLarge("Size of array".to_string(), Position::NONE).into(),
|
||||
);
|
||||
}
|
||||
|
||||
let check_sizes = match item.0 {
|
||||
Union::Str(..) => true,
|
||||
Union::Array(..) => true,
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(..) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if check_sizes {
|
||||
let mut arr_len = array.len();
|
||||
let mut arr = Dynamic::from_array(mem::take(array));
|
||||
|
||||
let (mut a1, mut m1, mut s1) = arr.calc_data_sizes(true);
|
||||
let (a2, m2, s2) = item.calc_data_sizes(true);
|
||||
|
||||
{
|
||||
let mut guard = arr.write_lock::<Array>().unwrap();
|
||||
|
||||
while arr_len < len {
|
||||
a1 += a2;
|
||||
m1 += m2;
|
||||
s1 += s2;
|
||||
|
||||
_ctx.engine().throw_on_size((a1, m1, s1))?;
|
||||
|
||||
guard.push(item.clone());
|
||||
arr_len += 1;
|
||||
}
|
||||
}
|
||||
|
||||
*array = arr.into_array().unwrap();
|
||||
} else {
|
||||
array.resize(len, item);
|
||||
}
|
||||
_ctx.engine()
|
||||
.throw_on_size((a + pad + ax * pad, m + mx * pad, s + sx * pad))?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "unchecked")]
|
||||
array.resize(len, item);
|
||||
|
||||
Ok(())
|
||||
@ -644,6 +607,43 @@ pub mod array_functions {
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate through all the elements in the array, applying a `process` function to each element in turn.
|
||||
/// Each element is bound to `this` before calling the function.
|
||||
///
|
||||
/// # Function Parameters
|
||||
///
|
||||
/// * `this`: bound to array element (mutable)
|
||||
/// * `index` _(optional)_: current index in the array
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rhai
|
||||
/// let x = [1, 2, 3, 4, 5];
|
||||
///
|
||||
/// x.for_each(|| this *= this);
|
||||
///
|
||||
/// print(x); // prints "[1, 4, 9, 16, 25]"
|
||||
///
|
||||
/// x.for_each(|i| this *= i);
|
||||
///
|
||||
/// print(x); // prints "[0, 2, 6, 12, 20]"
|
||||
/// ```
|
||||
#[rhai_fn(return_raw)]
|
||||
pub fn for_each(ctx: NativeCallContext, array: &mut Array, map: FnPtr) -> RhaiResultOf<()> {
|
||||
if array.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for (i, item) in array.iter_mut().enumerate() {
|
||||
let ex = [(i as INT).into()];
|
||||
|
||||
let _ = map.call_raw_with_extra_args("map", &ctx, Some(item), [], ex, None)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Iterate through all the elements in the array, applying a `mapper` function to each element
|
||||
/// in turn, and return the results as a new array.
|
||||
///
|
||||
@ -679,8 +679,7 @@ pub mod array_functions {
|
||||
|
||||
for (i, item) in array.iter_mut().enumerate() {
|
||||
let ex = [(i as INT).into()];
|
||||
|
||||
ar.push(map.call_raw_with_extra_args("map", &ctx, Some(item), [], ex)?);
|
||||
ar.push(map.call_raw_with_extra_args("map", &ctx, Some(item), [], ex, Some(0))?);
|
||||
}
|
||||
|
||||
Ok(ar)
|
||||
@ -723,7 +722,7 @@ pub mod array_functions {
|
||||
let ex = [(i as INT).into()];
|
||||
|
||||
if filter
|
||||
.call_raw_with_extra_args("filter", &ctx, Some(item), [], ex)?
|
||||
.call_raw_with_extra_args("filter", &ctx, Some(item), [], ex, Some(0))?
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
@ -962,7 +961,7 @@ pub mod array_functions {
|
||||
let ex = [(i as INT).into()];
|
||||
|
||||
if filter
|
||||
.call_raw_with_extra_args("index_of", &ctx, Some(item), [], ex)?
|
||||
.call_raw_with_extra_args("index_of", &ctx, Some(item), [], ex, Some(0))?
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
@ -1128,7 +1127,8 @@ pub mod array_functions {
|
||||
for (i, item) in array.iter_mut().enumerate().skip(start) {
|
||||
let ex = [(i as INT).into()];
|
||||
|
||||
let value = filter.call_raw_with_extra_args("find_map", &ctx, Some(item), [], ex)?;
|
||||
let value =
|
||||
filter.call_raw_with_extra_args("find_map", &ctx, Some(item), [], ex, Some(0))?;
|
||||
|
||||
if !value.is_unit() {
|
||||
return Ok(value);
|
||||
@ -1169,7 +1169,7 @@ pub mod array_functions {
|
||||
let ex = [(i as INT).into()];
|
||||
|
||||
if filter
|
||||
.call_raw_with_extra_args("some", &ctx, Some(item), [], ex)?
|
||||
.call_raw_with_extra_args("some", &ctx, Some(item), [], ex, Some(0))?
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
@ -1211,7 +1211,7 @@ pub mod array_functions {
|
||||
let ex = [(i as INT).into()];
|
||||
|
||||
if !filter
|
||||
.call_raw_with_extra_args("all", &ctx, Some(item), [], ex)?
|
||||
.call_raw_with_extra_args("all", &ctx, Some(item), [], ex, Some(0))?
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
@ -1334,11 +1334,11 @@ pub mod array_functions {
|
||||
}
|
||||
|
||||
array
|
||||
.iter()
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.try_fold(initial, |result, (i, item)| {
|
||||
let ex = [(i as INT).into()];
|
||||
reducer.call_raw_with_extra_args("reduce", &ctx, None, [result, item.clone()], ex)
|
||||
reducer.call_raw_with_extra_args("reduce", &ctx, Some(item), [result], ex, Some(1))
|
||||
})
|
||||
}
|
||||
/// Reduce an array by iterating through all elements, in _reverse_ order,
|
||||
@ -1400,19 +1400,22 @@ pub mod array_functions {
|
||||
return Ok(initial);
|
||||
}
|
||||
|
||||
let len = array.len();
|
||||
|
||||
array
|
||||
.iter()
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.enumerate()
|
||||
.try_fold(initial, |result, (i, item)| {
|
||||
let ex = [((array.len() - 1 - i) as INT).into()];
|
||||
let ex = [((len - 1 - i) as INT).into()];
|
||||
|
||||
reducer.call_raw_with_extra_args(
|
||||
"reduce_rev",
|
||||
&ctx,
|
||||
None,
|
||||
[result, item.clone()],
|
||||
Some(item),
|
||||
[result],
|
||||
ex,
|
||||
Some(1),
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -1602,7 +1605,7 @@ pub mod array_functions {
|
||||
let ex = [(i as INT).into()];
|
||||
|
||||
if filter
|
||||
.call_raw_with_extra_args("drain", &ctx, Some(&mut array[x]), [], ex)?
|
||||
.call_raw_with_extra_args("drain", &ctx, Some(&mut array[x]), [], ex, Some(0))?
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
@ -1749,7 +1752,7 @@ pub mod array_functions {
|
||||
let ex = [(i as INT).into()];
|
||||
|
||||
if filter
|
||||
.call_raw_with_extra_args("retain", &ctx, Some(&mut array[x]), [], ex)?
|
||||
.call_raw_with_extra_args("retain", &ctx, Some(&mut array[x]), [], ex, Some(0))?
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
|
@ -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,
|
||||
"{:?}: {}{}",
|
||||
|
@ -12,7 +12,7 @@ use crate::eval::{Caches, GlobalRuntimeState};
|
||||
use crate::func::{hashing::get_hasher, StraightHashMap};
|
||||
use crate::tokenizer::{
|
||||
is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream,
|
||||
TokenizerControl, NO_TOKEN,
|
||||
TokenizerControl,
|
||||
};
|
||||
use crate::types::dynamic::AccessMode;
|
||||
use crate::types::StringsInterner;
|
||||
@ -627,7 +627,7 @@ impl Engine {
|
||||
return Ok(FnCallExpr {
|
||||
name: state.get_interned_string(id),
|
||||
capture_parent_scope,
|
||||
op_token: NO_TOKEN,
|
||||
op_token: None,
|
||||
namespace: _namespace,
|
||||
hashes,
|
||||
args,
|
||||
@ -702,7 +702,7 @@ impl Engine {
|
||||
return Ok(FnCallExpr {
|
||||
name: state.get_interned_string(id),
|
||||
capture_parent_scope,
|
||||
op_token: NO_TOKEN,
|
||||
op_token: None,
|
||||
namespace: _namespace,
|
||||
hashes,
|
||||
args,
|
||||
@ -1657,7 +1657,7 @@ impl Engine {
|
||||
|
||||
match input.peek().expect(NEVER_ENDS).0 {
|
||||
// Function call is allowed to have reserved keyword
|
||||
Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s) => {
|
||||
Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s).0 => {
|
||||
Expr::Variable(
|
||||
(None, ns, 0, state.get_interned_string(*s)).into(),
|
||||
None,
|
||||
@ -1800,7 +1800,10 @@ impl Engine {
|
||||
state.allow_capture = false;
|
||||
}
|
||||
}
|
||||
(Token::Reserved(s), ..) if is_keyword_function(s) => (),
|
||||
(Token::Reserved(s), ..) if is_keyword_function(s).1 => (),
|
||||
(Token::Reserved(s), pos) => {
|
||||
return Err(PERR::Reserved(s.to_string()).into_err(*pos))
|
||||
}
|
||||
(.., pos) => return Err(PERR::PropertyExpected.into_err(*pos)),
|
||||
}
|
||||
|
||||
@ -1924,7 +1927,7 @@ impl Engine {
|
||||
name: state.get_interned_string("-"),
|
||||
hashes: FnCallHashes::from_native(calc_fn_hash(None, "-", 1)),
|
||||
args,
|
||||
op_token: token,
|
||||
op_token: Some(token),
|
||||
capture_parent_scope: false,
|
||||
}
|
||||
.into_fn_call_expr(pos))
|
||||
@ -1952,7 +1955,7 @@ impl Engine {
|
||||
name: state.get_interned_string("+"),
|
||||
hashes: FnCallHashes::from_native(calc_fn_hash(None, "+", 1)),
|
||||
args,
|
||||
op_token: token,
|
||||
op_token: Some(token),
|
||||
capture_parent_scope: false,
|
||||
}
|
||||
.into_fn_call_expr(pos))
|
||||
@ -1973,7 +1976,7 @@ impl Engine {
|
||||
name: state.get_interned_string("!"),
|
||||
hashes: FnCallHashes::from_native(calc_fn_hash(None, "!", 1)),
|
||||
args,
|
||||
op_token: token,
|
||||
op_token: Some(token),
|
||||
capture_parent_scope: false,
|
||||
}
|
||||
.into_fn_call_expr(pos))
|
||||
@ -1987,7 +1990,7 @@ impl Engine {
|
||||
|
||||
/// Make an assignment statement.
|
||||
fn make_assignment_stmt(
|
||||
op: Token,
|
||||
op: Option<Token>,
|
||||
state: &mut ParseState,
|
||||
lhs: Expr,
|
||||
rhs: Expr,
|
||||
@ -2020,10 +2023,10 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
let op_info = if op == NO_TOKEN {
|
||||
OpAssignment::new_assignment(op_pos)
|
||||
} else {
|
||||
let op_info = if let Some(op) = op {
|
||||
OpAssignment::new_op_assignment_from_token(op, op_pos)
|
||||
} else {
|
||||
OpAssignment::new_assignment(op_pos)
|
||||
};
|
||||
|
||||
match lhs {
|
||||
@ -2109,9 +2112,7 @@ impl Engine {
|
||||
}
|
||||
// lhs.module::id - syntax error
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
(.., Expr::Variable(x, ..)) if !x.1.is_empty() => {
|
||||
Err(PERR::PropertyExpected.into_err(x.1.position()))
|
||||
}
|
||||
(.., Expr::Variable(x, ..)) if !x.1.is_empty() => unreachable!("lhs.ns::id"),
|
||||
// lhs.id
|
||||
(lhs, var_expr @ Expr::Variable(..)) => {
|
||||
let rhs = var_expr.into_property(state);
|
||||
@ -2125,9 +2126,7 @@ impl Engine {
|
||||
)),
|
||||
// lhs.nnn::func(...) - syntax error
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
(.., Expr::FnCall(f, ..)) if f.is_qualified() => {
|
||||
Err(PERR::PropertyExpected.into_err(f.namespace.position()))
|
||||
}
|
||||
(.., Expr::FnCall(f, ..)) if f.is_qualified() => unreachable!("lhs.ns::func()"),
|
||||
// lhs.Fn() or lhs.eval()
|
||||
(.., Expr::FnCall(f, func_pos))
|
||||
if f.args.is_empty()
|
||||
@ -2174,13 +2173,11 @@ impl Engine {
|
||||
match x.lhs {
|
||||
// lhs.module::id.dot_rhs or lhs.module::id[idx_rhs] - syntax error
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Expr::Variable(x, ..) if !x.1.is_empty() => {
|
||||
Err(PERR::PropertyExpected.into_err(x.1.position()))
|
||||
}
|
||||
Expr::Variable(x, ..) if !x.1.is_empty() => unreachable!("lhs.ns::id..."),
|
||||
// lhs.module::func().dot_rhs or lhs.module::func()[idx_rhs] - syntax error
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Expr::FnCall(f, ..) if f.is_qualified() => {
|
||||
Err(PERR::PropertyExpected.into_err(f.namespace.position()))
|
||||
unreachable!("lhs.ns::func()...")
|
||||
}
|
||||
// lhs.id.dot_rhs or lhs.id[idx_rhs]
|
||||
Expr::Variable(..) | Expr::Property(..) => {
|
||||
@ -2310,9 +2307,9 @@ impl Engine {
|
||||
let hash = calc_fn_hash(None, &op, 2);
|
||||
let is_valid_script_function = is_valid_function_name(&op);
|
||||
let operator_token = if is_valid_script_function {
|
||||
NO_TOKEN
|
||||
None
|
||||
} else {
|
||||
op_token.clone()
|
||||
Some(op_token.clone())
|
||||
};
|
||||
|
||||
let mut args = StaticVec::new_const();
|
||||
@ -2383,7 +2380,7 @@ impl Engine {
|
||||
name: state.get_interned_string(op),
|
||||
hashes: FnCallHashes::from_native(calc_fn_hash(None, op, 1)),
|
||||
args,
|
||||
op_token: Token::Bang,
|
||||
op_token: Some(Token::Bang),
|
||||
capture_parent_scope: false,
|
||||
};
|
||||
not_base.into_fn_call_expr(pos)
|
||||
@ -3186,11 +3183,12 @@ impl Engine {
|
||||
|
||||
let (op, pos) = match input.peek().expect(NEVER_ENDS) {
|
||||
// var = ...
|
||||
(Token::Equals, ..) => (NO_TOKEN, eat_token(input, Token::Equals)),
|
||||
(Token::Equals, ..) => (None, eat_token(input, Token::Equals)),
|
||||
// var op= ...
|
||||
(token, ..) if token.is_op_assignment() => {
|
||||
input.next().map(|(op, pos)| (op, pos)).expect(NEVER_ENDS)
|
||||
}
|
||||
(token, ..) if token.is_op_assignment() => input
|
||||
.next()
|
||||
.map(|(op, pos)| (Some(op), pos))
|
||||
.expect(NEVER_ENDS),
|
||||
// Not op-assignment
|
||||
_ => return Ok(Stmt::Expr(expr.into())),
|
||||
};
|
||||
@ -3686,7 +3684,7 @@ impl Engine {
|
||||
num_externals + 1,
|
||||
)),
|
||||
args,
|
||||
op_token: NO_TOKEN,
|
||||
op_token: None,
|
||||
capture_parent_scope: false,
|
||||
}
|
||||
.into_fn_call_expr(pos);
|
||||
|
@ -55,9 +55,6 @@ type LERR = LexError;
|
||||
/// Separator character for numbers.
|
||||
const NUMBER_SEPARATOR: char = '_';
|
||||
|
||||
/// No token.
|
||||
pub const NO_TOKEN: Token = Token::NONE;
|
||||
|
||||
/// A stream of tokens.
|
||||
pub type TokenStream<'a> = Peekable<TokenIterator<'a>>;
|
||||
|
||||
@ -631,41 +628,6 @@ impl Token {
|
||||
})
|
||||
}
|
||||
|
||||
/// Is a piece of syntax a reserved keyword?
|
||||
#[must_use]
|
||||
pub fn is_reserved_keyword(syntax: &str) -> bool {
|
||||
match syntax {
|
||||
#[cfg(feature = "no_object")]
|
||||
"?." => true,
|
||||
#[cfg(feature = "no_index")]
|
||||
"?[" => true,
|
||||
#[cfg(feature = "no_function")]
|
||||
"fn" | "private" => true,
|
||||
#[cfg(feature = "no_module")]
|
||||
"import" | "export" | "as" => true,
|
||||
|
||||
// List of reserved operators
|
||||
"===" | "!==" | "->" | "<-" | "?" | ":=" | ":;" | "~" | "!." | "::<" | "(*" | "*)"
|
||||
| "#" | "#!" | "@" | "$" | "++" | "--" | "..." | "<|" | "|>" => true,
|
||||
|
||||
// List of reserved keywords
|
||||
"public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var"
|
||||
| "static" | "shared" | "with" | "is" | "goto" | "exit" | "match" | "case"
|
||||
| "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync"
|
||||
| "async" | "await" | "yield" => true,
|
||||
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
crate::engine::KEYWORD_IS_DEF_FN => true,
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// If another operator is after these, it's probably a unary operator
|
||||
/// (not sure about `fn` name).
|
||||
#[must_use]
|
||||
@ -1994,7 +1956,8 @@ fn parse_identifier_token(
|
||||
if let Some(token) = Token::lookup_symbol_from_syntax(&identifier) {
|
||||
return (token, start_pos);
|
||||
}
|
||||
if Token::is_reserved_keyword(&identifier) {
|
||||
|
||||
if is_reserved_keyword_or_symbol(&identifier) {
|
||||
return (Token::Reserved(Box::new(identifier)), start_pos);
|
||||
}
|
||||
|
||||
@ -2008,18 +1971,27 @@ fn parse_identifier_token(
|
||||
(Token::Identifier(identifier.into()), start_pos)
|
||||
}
|
||||
|
||||
/// Is a keyword allowed as a function?
|
||||
/// Can a keyword be called like a function?
|
||||
///
|
||||
/// # Return values
|
||||
///
|
||||
/// The first `bool` indicates whether the keyword can be called normally as a function.
|
||||
///
|
||||
/// The second `bool` indicates whether the keyword can be called in method-call style.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_keyword_function(name: &str) -> bool {
|
||||
pub fn is_keyword_function(name: &str) -> (bool, bool) {
|
||||
match name {
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR => true,
|
||||
KEYWORD_TYPE_OF | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => (true, true),
|
||||
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_IS_DEF_VAR => {
|
||||
(true, false)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
crate::engine::KEYWORD_IS_DEF_FN => true,
|
||||
crate::engine::KEYWORD_IS_DEF_FN => (true, false),
|
||||
|
||||
_ => false,
|
||||
_ => (false, false),
|
||||
}
|
||||
}
|
||||
|
||||
@ -2047,7 +2019,9 @@ pub fn is_valid_identifier(name: &str) -> bool {
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn is_valid_function_name(name: &str) -> bool {
|
||||
is_valid_identifier(name) && !is_keyword_function(name)
|
||||
is_valid_identifier(name)
|
||||
&& !is_reserved_keyword_or_symbol(name)
|
||||
&& Token::lookup_symbol_from_syntax(name).is_none()
|
||||
}
|
||||
|
||||
/// Is a character valid to start an identifier?
|
||||
@ -2082,6 +2056,39 @@ pub const fn is_id_continue(x: char) -> bool {
|
||||
x.is_ascii_alphanumeric() || x == '_'
|
||||
}
|
||||
|
||||
/// Is a piece of syntax a reserved keyword or symbol?
|
||||
#[must_use]
|
||||
pub fn is_reserved_keyword_or_symbol(syntax: &str) -> bool {
|
||||
match syntax {
|
||||
#[cfg(feature = "no_object")]
|
||||
"?." => true,
|
||||
#[cfg(feature = "no_index")]
|
||||
"?[" => true,
|
||||
#[cfg(feature = "no_function")]
|
||||
"fn" | "private" => true,
|
||||
#[cfg(feature = "no_module")]
|
||||
"import" | "export" | "as" => true,
|
||||
|
||||
// List of reserved operators
|
||||
"===" | "!==" | "->" | "<-" | "?" | ":=" | ":;" | "~" | "!." | "::<" | "(*" | "*)"
|
||||
| "#" | "#!" | "@" | "$" | "++" | "--" | "..." | "<|" | "|>" => true,
|
||||
|
||||
// List of reserved keywords
|
||||
"public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var"
|
||||
| "static" | "shared" | "with" | "is" | "goto" | "exit" | "match" | "case" | "default"
|
||||
| "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "async" | "await"
|
||||
| "yield" => true,
|
||||
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => true,
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
crate::engine::KEYWORD_IS_DEF_FN => true,
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// _(internals)_ A type that implements the [`InputStream`] trait.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
use crate::eval::GlobalRuntimeState;
|
||||
use crate::func::EncapsulatedEnviron;
|
||||
use crate::tokenizer::is_valid_function_name;
|
||||
use crate::tokenizer::{is_reserved_keyword_or_symbol, is_valid_function_name, Token};
|
||||
use crate::types::dynamic::Variant;
|
||||
use crate::{
|
||||
Dynamic, Engine, FnArgsVec, FuncArgs, ImmutableString, NativeCallContext, Position, RhaiError,
|
||||
RhaiResult, RhaiResultOf, Shared, StaticVec, AST, ERR,
|
||||
Dynamic, Engine, FnArgsVec, FuncArgs, ImmutableString, NativeCallContext, ParseErrorType,
|
||||
Position, RhaiError, RhaiResult, RhaiResultOf, Shared, StaticVec, AST, ERR,
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -360,7 +360,9 @@ impl FnPtr {
|
||||
/// arguments attached.
|
||||
///
|
||||
/// If `this_ptr` is provided, it is first provided to script-defined functions bound to `this`.
|
||||
/// When an appropriate function is not found, it is then removed and mapped to the first parameter.
|
||||
///
|
||||
/// When an appropriate function is not found and `move_this_ptr_to_args` is `Some`, `this_ptr`
|
||||
/// is removed and inserted as the appropriate parameter number.
|
||||
///
|
||||
/// This is useful for calling predicate closures within an iteration loop where the extra argument
|
||||
/// is the current element's index.
|
||||
@ -374,17 +376,19 @@ impl FnPtr {
|
||||
fn_name: &str,
|
||||
ctx: &NativeCallContext,
|
||||
this_ptr: Option<&mut Dynamic>,
|
||||
items: [Dynamic; N],
|
||||
args: [Dynamic; N],
|
||||
extras: [Dynamic; E],
|
||||
move_this_ptr_to_args: Option<usize>,
|
||||
) -> RhaiResult {
|
||||
self._call_with_extra_args(fn_name, ctx, this_ptr, items, extras)
|
||||
self._call_with_extra_args(fn_name, ctx, this_ptr, args, extras, move_this_ptr_to_args)
|
||||
}
|
||||
/// _(internals)_ Make a call to a function pointer with either a specified number of arguments,
|
||||
/// or with extra arguments attached.
|
||||
/// Exported under the `internals` feature only.
|
||||
/// or with extra arguments attached. Exported under the `internals` feature only.
|
||||
///
|
||||
/// If `this_ptr` is provided, it is first provided to script-defined functions bound to `this`.
|
||||
/// When an appropriate function is not found, it is then removed and mapped to the first parameter.
|
||||
///
|
||||
/// When an appropriate function is not found and `move_this_ptr_to_args` is `Some`, `this_ptr`
|
||||
/// is removed and inserted as the appropriate parameter number.
|
||||
///
|
||||
/// This is useful for calling predicate closures within an iteration loop where the extra
|
||||
/// argument is the current element's index.
|
||||
@ -398,10 +402,11 @@ impl FnPtr {
|
||||
fn_name: &str,
|
||||
ctx: &NativeCallContext,
|
||||
this_ptr: Option<&mut Dynamic>,
|
||||
items: [Dynamic; N],
|
||||
args: [Dynamic; N],
|
||||
extras: [Dynamic; E],
|
||||
move_this_ptr_to_args: Option<usize>,
|
||||
) -> RhaiResult {
|
||||
self._call_with_extra_args(fn_name, ctx, this_ptr, items, extras)
|
||||
self._call_with_extra_args(fn_name, ctx, this_ptr, args, extras, move_this_ptr_to_args)
|
||||
}
|
||||
/// Make a call to a function pointer with either a specified number of arguments, or with extra
|
||||
/// arguments attached.
|
||||
@ -410,51 +415,94 @@ impl FnPtr {
|
||||
fn_name: &str,
|
||||
ctx: &NativeCallContext,
|
||||
mut this_ptr: Option<&mut Dynamic>,
|
||||
items: [Dynamic; N],
|
||||
args: [Dynamic; N],
|
||||
extras: [Dynamic; E],
|
||||
move_this_ptr_to_args: Option<usize>,
|
||||
) -> RhaiResult {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if let Some(arity) = self.fn_def().map(|f| f.params.len()) {
|
||||
if arity == N + 1 && this_ptr.is_some() {
|
||||
let mut args = FnArgsVec::with_capacity(items.len() + 1);
|
||||
args.push(this_ptr.as_mut().unwrap().clone());
|
||||
args.extend(items);
|
||||
return self.call_raw(ctx, None, args);
|
||||
if arity == N + self.curry().len() {
|
||||
return self.call_raw(ctx, this_ptr, args);
|
||||
}
|
||||
if arity == N {
|
||||
return self.call_raw(ctx, this_ptr, items);
|
||||
if let Some(move_to_args) = move_this_ptr_to_args {
|
||||
if this_ptr.is_some() {
|
||||
if arity == N + 1 + self.curry().len() {
|
||||
let mut args2 = FnArgsVec::with_capacity(args.len() + 1);
|
||||
if move_to_args == 0 {
|
||||
args2.push(this_ptr.as_mut().unwrap().clone());
|
||||
args2.extend(args);
|
||||
} else {
|
||||
args2.extend(args);
|
||||
args2.insert(move_to_args, this_ptr.as_mut().unwrap().clone());
|
||||
}
|
||||
return self.call_raw(ctx, None, args2);
|
||||
}
|
||||
if arity == N + E + 1 + self.curry().len() {
|
||||
let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len() + 1);
|
||||
if move_to_args == 0 {
|
||||
args2.push(this_ptr.as_mut().unwrap().clone());
|
||||
args2.extend(args);
|
||||
args2.extend(extras);
|
||||
} else {
|
||||
args2.extend(args);
|
||||
args2.insert(move_to_args, this_ptr.as_mut().unwrap().clone());
|
||||
args2.extend(extras);
|
||||
}
|
||||
return self.call_raw(ctx, None, args2);
|
||||
}
|
||||
}
|
||||
}
|
||||
if arity == N + E {
|
||||
let mut items2 = FnArgsVec::with_capacity(items.len() + extras.len());
|
||||
items2.extend(IntoIterator::into_iter(items));
|
||||
items2.extend(IntoIterator::into_iter(extras));
|
||||
return self.call_raw(ctx, this_ptr, items2);
|
||||
if arity == N + E + self.curry().len() {
|
||||
let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len());
|
||||
args2.extend(args);
|
||||
args2.extend(extras);
|
||||
return self.call_raw(ctx, this_ptr, args2);
|
||||
}
|
||||
}
|
||||
|
||||
self.call_raw(ctx, this_ptr.as_deref_mut(), items.clone())
|
||||
self.call_raw(ctx, this_ptr.as_deref_mut(), args.clone())
|
||||
.or_else(|err| match *err {
|
||||
ERR::ErrorFunctionNotFound(sig, ..)
|
||||
if this_ptr.is_some() && sig.starts_with(self.fn_name()) =>
|
||||
if move_this_ptr_to_args.is_some()
|
||||
&& this_ptr.is_some()
|
||||
&& sig.starts_with(self.fn_name()) =>
|
||||
{
|
||||
let mut args = FnArgsVec::with_capacity(items.len() + 1);
|
||||
args.push(this_ptr.as_mut().unwrap().clone());
|
||||
args.extend(IntoIterator::into_iter(items.clone()));
|
||||
self.call_raw(ctx, this_ptr.as_deref_mut(), args)
|
||||
let mut args2 = FnArgsVec::with_capacity(args.len() + 1);
|
||||
let move_to_args = move_this_ptr_to_args.unwrap();
|
||||
if move_to_args == 0 {
|
||||
args2.push(this_ptr.as_mut().unwrap().clone());
|
||||
args2.extend(args.clone());
|
||||
} else {
|
||||
args2.extend(args.clone());
|
||||
args2.insert(move_to_args, this_ptr.as_mut().unwrap().clone());
|
||||
}
|
||||
self.call_raw(ctx, None, args2)
|
||||
}
|
||||
_ => Err(err),
|
||||
})
|
||||
.or_else(|err| match *err {
|
||||
ERR::ErrorFunctionNotFound(sig, ..) if sig.starts_with(self.fn_name()) => {
|
||||
let mut args = FnArgsVec::with_capacity(
|
||||
items.len() + extras.len() + if this_ptr.is_some() { 1 } else { 0 },
|
||||
);
|
||||
if let Some(ref mut this_ptr) = this_ptr {
|
||||
args.push(this_ptr.clone());
|
||||
if let Some(move_to_args) = move_this_ptr_to_args {
|
||||
if let Some(ref mut this_ptr) = this_ptr {
|
||||
let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len() + 1);
|
||||
if move_to_args == 0 {
|
||||
args2.push(this_ptr.clone());
|
||||
args2.extend(args);
|
||||
args2.extend(extras);
|
||||
} else {
|
||||
args2.extend(args);
|
||||
args2.extend(extras);
|
||||
args2.insert(move_to_args, this_ptr.clone());
|
||||
}
|
||||
return self.call_raw(ctx, None, args2);
|
||||
}
|
||||
}
|
||||
args.extend(IntoIterator::into_iter(items));
|
||||
args.extend(IntoIterator::into_iter(extras));
|
||||
self.call_raw(ctx, this_ptr, args)
|
||||
|
||||
let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len());
|
||||
args2.extend(args);
|
||||
args2.extend(extras);
|
||||
|
||||
self.call_raw(ctx, this_ptr, args2)
|
||||
}
|
||||
_ => Err(err),
|
||||
})
|
||||
@ -488,6 +536,13 @@ impl TryFrom<ImmutableString> for FnPtr {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
fn_def: None,
|
||||
})
|
||||
} else if is_reserved_keyword_or_symbol(&value)
|
||||
|| Token::lookup_symbol_from_syntax(&value).is_some()
|
||||
{
|
||||
Err(
|
||||
ERR::ErrorParsing(ParseErrorType::Reserved(value.to_string()), Position::NONE)
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
Err(ERR::ErrorFunctionNotFound(value.to_string(), Position::NONE).into())
|
||||
}
|
||||
|
@ -299,11 +299,31 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
assert_eq!(engine.eval::<INT>("[1].map(|x| x + 41)[0]")?, 42);
|
||||
assert_eq!(engine.eval::<INT>("[1].map(|| this + 41)[0]")?, 42);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>("let x = [1, 2, 3]; x.for_each(|| this += 41); x[0]")?,
|
||||
42
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
let x = [1, 2, 3];
|
||||
let sum = 0;
|
||||
let factor = 2;
|
||||
x.for_each(|| sum += this * factor);
|
||||
sum
|
||||
"
|
||||
)?,
|
||||
12
|
||||
);
|
||||
assert_eq!(engine.eval::<INT>("([1].map(|x| x + 41))[0]")?, 42);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>("let c = 40; let y = 1; [1].map(|x, i| c + x + y + i)[0]")?,
|
||||
42
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>("let x = [1, 2, 3]; x.for_each(|i| this += i); x[2]")?,
|
||||
5
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine
|
||||
@ -387,18 +407,31 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
|
||||
14
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
let x = [1, 2, 3];
|
||||
x.reduce(|sum, v, i| {
|
||||
if i == 0 { sum = 10 }
|
||||
sum + v * v
|
||||
})
|
||||
"
|
||||
)?,
|
||||
24
|
||||
);
|
||||
// assert_eq!(
|
||||
// engine.eval::<INT>(
|
||||
// "
|
||||
// let x = [1, 2, 3];
|
||||
// x.reduce(|sum, v, i| {
|
||||
// if i == 0 { sum = 10 }
|
||||
// sum + v * v
|
||||
// })
|
||||
// "
|
||||
// )?,
|
||||
// 24
|
||||
// );
|
||||
|
||||
// assert_eq!(
|
||||
// engine.eval::<INT>(
|
||||
// "
|
||||
// let x = [1, 2, 3];
|
||||
// x.reduce(|sum, i| {
|
||||
// if i == 0 { sum = 10 }
|
||||
// sum + this * this
|
||||
// })
|
||||
// "
|
||||
// )?,
|
||||
// 24
|
||||
// );
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
|
@ -175,7 +175,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
let x = [1,2,3];
|
||||
let x = [1,2];
|
||||
len([x, x, x])
|
||||
"
|
||||
)?,
|
||||
|
@ -332,7 +332,8 @@ fn test_string_split() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
#[test]
|
||||
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>("`${}`")?, "");
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user