commit
56602927d5
19
CHANGELOG.md
19
CHANGELOG.md
@ -21,6 +21,8 @@ Breaking changes
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
* Custom syntax now works properly inside binary expressions and with method calls.
|
||||
* Hex numbers with the high-bit set now parse correctly into negative integer numbers.
|
||||
* Constructing a literal array or object map now checks for size limits for each item instead of at the very end when it is already too late.
|
||||
* Non-`INT` integer types are now treated exactly as custom types under `only_i64` and `only_i32`.
|
||||
* Calling `pad` on an array now checks for total size over limit after each item added.
|
||||
@ -29,10 +31,12 @@ New features
|
||||
------------
|
||||
|
||||
* Added support for integer _ranges_ via the `..` and `..=` operators.
|
||||
* Added `EvalAltResult::ErrorCustomSyntax` to catch errors in custom syntax, which should not happen unless an `AST` is compiled on one `Engine` but evaluated on another unrelated `Engine`.
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
* `BLOB`'s are refined to display in a more compact hex format.
|
||||
* A new syntax is introduced for `def_package!` that will replace the old syntax in future versions.
|
||||
* Added `NativeCallContext::call_fn` to easily call a function.
|
||||
* Doc-comments on plugin module functions are extracted into the functions' metadata.
|
||||
@ -44,21 +48,6 @@ Deprecated API's
|
||||
* The old syntax of `def_package!` is deprecated in favor of the new syntax.
|
||||
|
||||
|
||||
Version 1.3.1
|
||||
=============
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
* Custom syntax now works properly inside binary expressions and with method calls.
|
||||
* Hex numbers with the high-bit set now parse correctly into negative integer numbers.
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
* `BLOB`'s are refined to display in a more compact hex format.
|
||||
|
||||
|
||||
Version 1.3.0
|
||||
=============
|
||||
|
||||
|
@ -106,6 +106,21 @@ fn bench_eval_call(bench: &mut Bencher) {
|
||||
bench.iter(|| engine.eval::<bool>(script).unwrap());
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_eval_deeply_nested(bench: &mut Bencher) {
|
||||
let script = r#"
|
||||
(1 + 2 * 3 - 9) * 4 < 5 * 6 - 70 / 8 &&
|
||||
(42 + 99 > 1 + 2 - 3 + 4 * 5 || 123 - 88 < 123 + 88 - 99 + 100)
|
||||
&& true
|
||||
&& !!!!!!!!false
|
||||
"#;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.set_optimization_level(OptimizationLevel::None);
|
||||
|
||||
bench.iter(|| engine.eval::<bool>(script).unwrap());
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_eval_loop_number(bench: &mut Bencher) {
|
||||
let script = "
|
||||
|
@ -1,5 +1,3 @@
|
||||
cargo-features = ["named-profiles"]
|
||||
|
||||
[workspace]
|
||||
|
||||
[package]
|
||||
|
@ -18,7 +18,7 @@ cargo +nightly build --release
|
||||
A specific profile can also be used:
|
||||
|
||||
```bash
|
||||
cargo +nightly build --profile unix -Z unstable-options
|
||||
cargo +nightly build --profile unix
|
||||
```
|
||||
|
||||
Three profiles are defined: `unix`, `windows` and `macos`.
|
||||
|
@ -1,18 +1,19 @@
|
||||
//! Module defining script expressions.
|
||||
|
||||
use super::{ASTNode, Ident, Stmt, StmtBlock};
|
||||
use crate::engine::{OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE};
|
||||
use crate::engine::{KEYWORD_FN_PTR, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE};
|
||||
use crate::func::hashing::ALT_ZERO_HASH;
|
||||
use crate::module::Namespace;
|
||||
use crate::tokenizer::Token;
|
||||
use crate::types::dynamic::Union;
|
||||
use crate::{Dynamic, Identifier, ImmutableString, Position, StaticVec, INT};
|
||||
use crate::{calc_fn_hash, Dynamic, FnPtr, Identifier, ImmutableString, Position, StaticVec, INT};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fmt,
|
||||
hash::Hash,
|
||||
iter::once,
|
||||
num::{NonZeroU8, NonZeroUsize},
|
||||
};
|
||||
|
||||
@ -522,8 +523,23 @@ impl Expr {
|
||||
}))
|
||||
}
|
||||
|
||||
// Fn
|
||||
Self::FnCall(ref x, _)
|
||||
if !x.is_qualified() && x.args.len() == 1 && x.name == KEYWORD_FN_PTR =>
|
||||
{
|
||||
if let Expr::StringConstant(ref s, _) = x.args[0] {
|
||||
if let Ok(fn_ptr) = FnPtr::new(s) {
|
||||
fn_ptr.into()
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// Binary operators
|
||||
Self::FnCall(x, _) if x.args.len() == 2 => match x.name.as_str() {
|
||||
Self::FnCall(x, _) if !x.is_qualified() && x.args.len() == 2 => match x.name.as_str() {
|
||||
// x..y
|
||||
OP_EXCLUSIVE_RANGE => {
|
||||
if let Expr::IntegerConstant(ref start, _) = x.args[0] {
|
||||
@ -577,6 +593,19 @@ impl Expr {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(m, _, _) => Self::DynamicConstant(Box::new((*m).into()), pos),
|
||||
|
||||
Union::FnPtr(f, _, _) if !f.is_curried() => Self::FnCall(
|
||||
FnCallExpr {
|
||||
namespace: None,
|
||||
name: KEYWORD_FN_PTR.into(),
|
||||
hashes: calc_fn_hash(f.fn_name(), 1).into(),
|
||||
args: once(Self::Stack(0, pos)).collect(),
|
||||
constants: once(f.fn_name().into()).collect(),
|
||||
capture_parent_scope: false,
|
||||
}
|
||||
.into(),
|
||||
pos,
|
||||
),
|
||||
|
||||
_ => Self::DynamicConstant(value.into(), pos),
|
||||
}
|
||||
}
|
||||
|
@ -483,7 +483,7 @@ impl Stmt {
|
||||
Self::Expr(Expr::Stmt(s)) => s.iter().all(Stmt::is_block_dependent),
|
||||
|
||||
Self::FnCall(x, _) | Self::Expr(Expr::FnCall(x, _)) => {
|
||||
x.namespace.is_none() && x.name == KEYWORD_EVAL
|
||||
!x.is_qualified() && x.name == KEYWORD_EVAL
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
|
@ -140,9 +140,37 @@ pub struct Engine {
|
||||
}
|
||||
|
||||
impl fmt::Debug for Engine {
|
||||
#[inline(always)]
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("Engine")
|
||||
let mut f = f.debug_struct("Engine");
|
||||
|
||||
f.field("global_modules", &self.global_modules)
|
||||
.field("global_sub_modules", &self.global_sub_modules);
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
f.field("module_resolver", &self.module_resolver.is_some());
|
||||
|
||||
f.field("type_names", &self.type_names)
|
||||
.field("disabled_symbols", &self.disabled_symbols)
|
||||
.field("custom_keywords", &self.custom_keywords)
|
||||
.field("custom_syntax", &(!self.custom_syntax.is_empty()))
|
||||
.field("resolve_var", &self.resolve_var.is_some())
|
||||
.field("token_mapper", &self.token_mapper.is_some())
|
||||
.field("print", &self.print.is_some())
|
||||
.field("debug", &self.debug.is_some());
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
f.field("progress", &self.progress.is_some());
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
f.field("optimization_level", &self.optimization_level);
|
||||
|
||||
f.field("options", &self.options);
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
f.field("limits", &self.limits);
|
||||
|
||||
f.finish()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,9 +229,14 @@ impl Engine {
|
||||
)
|
||||
} else {
|
||||
// Normal function call
|
||||
let (first_arg, args) = args.split_first().map_or_else(
|
||||
|| (None, args.as_ref()),
|
||||
|(first, rest)| (Some(first), rest),
|
||||
);
|
||||
|
||||
self.make_function_call(
|
||||
scope, global, state, lib, this_ptr, name, args, constants, *hashes, pos, *capture,
|
||||
level,
|
||||
scope, global, state, lib, this_ptr, name, first_arg, args, constants, *hashes,
|
||||
pos, *capture, level,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -411,10 +416,18 @@ impl Engine {
|
||||
.into())
|
||||
}
|
||||
|
||||
Expr::Custom(custom, _) => {
|
||||
Expr::Custom(custom, pos) => {
|
||||
let expressions: StaticVec<_> = custom.inputs.iter().map(Into::into).collect();
|
||||
// The first token acts as the custom syntax's key
|
||||
let key_token = custom.tokens.first().unwrap();
|
||||
let custom_def = self.custom_syntax.get(key_token).unwrap();
|
||||
// The key should exist, unless the AST is compiled in a different Engine
|
||||
let custom_def = self.custom_syntax.get(key_token).ok_or_else(|| {
|
||||
Box::new(ERR::ErrorCustomSyntax(
|
||||
format!("Invalid custom syntax prefix: {}", key_token),
|
||||
custom.tokens.iter().map(|s| s.to_string()).collect(),
|
||||
*pos,
|
||||
))
|
||||
})?;
|
||||
let mut context = EvalContext {
|
||||
engine: self,
|
||||
scope,
|
||||
|
@ -245,6 +245,7 @@ impl IntoIterator for GlobalRuntimeState {
|
||||
}
|
||||
|
||||
impl<K: Into<Identifier>, M: Into<Shared<Module>>> FromIterator<(K, M)> for GlobalRuntimeState {
|
||||
#[inline]
|
||||
fn from_iter<T: IntoIterator<Item = (K, M)>>(iter: T) -> Self {
|
||||
let mut lib = Self::new();
|
||||
lib.extend(iter);
|
||||
@ -253,6 +254,7 @@ impl<K: Into<Identifier>, M: Into<Shared<Module>>> FromIterator<(K, M)> for Glob
|
||||
}
|
||||
|
||||
impl<K: Into<Identifier>, M: Into<Shared<Module>>> Extend<(K, M)> for GlobalRuntimeState {
|
||||
#[inline]
|
||||
fn extend<T: IntoIterator<Item = (K, M)>>(&mut self, iter: T) {
|
||||
iter.into_iter().for_each(|(k, m)| {
|
||||
self.keys.push(k.into());
|
||||
@ -262,10 +264,25 @@ impl<K: Into<Identifier>, M: Into<Shared<Module>>> Extend<(K, M)> for GlobalRunt
|
||||
}
|
||||
|
||||
impl fmt::Debug for GlobalRuntimeState {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("Imports")?;
|
||||
f.debug_map()
|
||||
.entries(self.keys.iter().zip(self.modules.iter()))
|
||||
.finish()
|
||||
let mut f = f.debug_struct("GlobalRuntimeState");
|
||||
|
||||
f.field("imports", &self.keys.iter().zip(self.modules.iter()))
|
||||
.field("source", &self.source)
|
||||
.field("num_operations", &self.num_operations)
|
||||
.field("num_modules_loaded", &self.num_modules_loaded);
|
||||
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
f.field("fn_hash_indexing", &self.fn_hash_indexing);
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
f.field("embedded_module_resolver", &self.embedded_module_resolver);
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
f.field("constants", &self.constants);
|
||||
|
||||
f.finish()
|
||||
}
|
||||
}
|
||||
|
@ -141,6 +141,10 @@ impl Engine {
|
||||
let args = &mut [lhs_ptr_inner, &mut new_val];
|
||||
|
||||
match self.call_native_fn(global, state, lib, op, hash, args, true, true, op_pos) {
|
||||
Ok(_) => {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.check_data_size(&mut args[0], root.1)?;
|
||||
}
|
||||
Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, _) if f.starts_with(op)) =>
|
||||
{
|
||||
// Expand to `var = var op rhs`
|
||||
@ -153,7 +157,7 @@ impl Engine {
|
||||
|
||||
*args[0] = value.flatten();
|
||||
}
|
||||
err => return err.map(|_| ()),
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
} else {
|
||||
// Normal assignment
|
||||
|
152
src/func/call.rs
152
src/func/call.rs
@ -887,12 +887,16 @@ impl Engine {
|
||||
arg_expr: &Expr,
|
||||
constants: &[Dynamic],
|
||||
) -> RhaiResultOf<(Dynamic, Position)> {
|
||||
match arg_expr {
|
||||
Expr::Stack(slot, pos) => Ok((constants[*slot].clone(), *pos)),
|
||||
ref arg => self
|
||||
.eval_expr(scope, global, state, lib, this_ptr, arg, level)
|
||||
.map(|v| (v, arg.position())),
|
||||
}
|
||||
Ok((
|
||||
if let Expr::Stack(slot, _) = arg_expr {
|
||||
constants[*slot].clone()
|
||||
} else if let Some(value) = arg_expr.get_literal_value() {
|
||||
value
|
||||
} else {
|
||||
self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level)?
|
||||
},
|
||||
arg_expr.position(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Call a function in normal function-call style.
|
||||
@ -904,6 +908,7 @@ impl Engine {
|
||||
lib: &[&Module],
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
fn_name: &str,
|
||||
first_arg: Option<&Expr>,
|
||||
args_expr: &[Expr],
|
||||
constants: &[Dynamic],
|
||||
hashes: FnCallHashes,
|
||||
@ -911,8 +916,9 @@ impl Engine {
|
||||
capture_scope: bool,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
let mut first_arg = first_arg;
|
||||
let mut a_expr = args_expr;
|
||||
let mut total_args = a_expr.len();
|
||||
let mut total_args = if first_arg.is_some() { 1 } else { 0 } + a_expr.len();
|
||||
let mut curry = FnArgsVec::new_const();
|
||||
let mut name = fn_name;
|
||||
let mut hashes = hashes;
|
||||
@ -921,26 +927,29 @@ impl Engine {
|
||||
match name {
|
||||
// Handle call()
|
||||
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
|
||||
let (arg, arg_pos) = self.get_arg_value(
|
||||
scope, global, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
let arg = first_arg.unwrap();
|
||||
let (arg_value, arg_pos) =
|
||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
|
||||
|
||||
if !arg.is::<FnPtr>() {
|
||||
if !arg_value.is::<FnPtr>() {
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||
self.map_type_name(arg.type_name()),
|
||||
self.map_type_name(arg_value.type_name()),
|
||||
arg_pos,
|
||||
));
|
||||
}
|
||||
|
||||
let fn_ptr = arg.cast::<FnPtr>();
|
||||
let fn_ptr = arg_value.cast::<FnPtr>();
|
||||
curry.extend(fn_ptr.curry().iter().cloned());
|
||||
|
||||
// Redirect function name
|
||||
redirected = fn_ptr.take_data().0;
|
||||
name = &redirected;
|
||||
|
||||
// Skip the first argument
|
||||
a_expr = &a_expr[1..];
|
||||
// Shift the arguments
|
||||
first_arg = a_expr.get(0);
|
||||
if !a_expr.is_empty() {
|
||||
a_expr = &a_expr[1..];
|
||||
}
|
||||
total_args -= 1;
|
||||
|
||||
// Recalculate hash
|
||||
@ -953,12 +962,12 @@ impl Engine {
|
||||
}
|
||||
// Handle Fn()
|
||||
KEYWORD_FN_PTR if total_args == 1 => {
|
||||
let (arg, arg_pos) = self.get_arg_value(
|
||||
scope, global, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
let arg = first_arg.unwrap();
|
||||
let (arg_value, arg_pos) =
|
||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
|
||||
|
||||
// Fn - only in function call style
|
||||
return arg
|
||||
return arg_value
|
||||
.into_immutable_string()
|
||||
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))
|
||||
.and_then(FnPtr::try_from)
|
||||
@ -968,30 +977,30 @@ impl Engine {
|
||||
|
||||
// Handle curry()
|
||||
KEYWORD_FN_PTR_CURRY if total_args > 1 => {
|
||||
let (arg, arg_pos) = self.get_arg_value(
|
||||
scope, global, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
let first = first_arg.unwrap();
|
||||
let (arg_value, arg_pos) = self
|
||||
.get_arg_value(scope, global, state, lib, this_ptr, level, first, constants)?;
|
||||
|
||||
if !arg.is::<FnPtr>() {
|
||||
if !arg_value.is::<FnPtr>() {
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||
self.map_type_name(arg.type_name()),
|
||||
self.map_type_name(arg_value.type_name()),
|
||||
arg_pos,
|
||||
));
|
||||
}
|
||||
|
||||
let (name, fn_curry) = arg.cast::<FnPtr>().take_data();
|
||||
let (name, fn_curry) = arg_value.cast::<FnPtr>().take_data();
|
||||
|
||||
// Append the new curried arguments to the existing list.
|
||||
let fn_curry = a_expr.iter().skip(1).try_fold(
|
||||
fn_curry,
|
||||
|mut curried, expr| -> RhaiResultOf<_> {
|
||||
let (value, _) = self.get_arg_value(
|
||||
scope, global, state, lib, this_ptr, level, expr, constants,
|
||||
)?;
|
||||
curried.push(value);
|
||||
Ok(curried)
|
||||
},
|
||||
)?;
|
||||
let fn_curry =
|
||||
a_expr
|
||||
.iter()
|
||||
.try_fold(fn_curry, |mut curried, expr| -> RhaiResultOf<_> {
|
||||
let (value, _) = self.get_arg_value(
|
||||
scope, global, state, lib, this_ptr, level, expr, constants,
|
||||
)?;
|
||||
curried.push(value);
|
||||
Ok(curried)
|
||||
})?;
|
||||
|
||||
return Ok(FnPtr::new_unchecked(name, fn_curry).into());
|
||||
}
|
||||
@ -999,28 +1008,28 @@ impl Engine {
|
||||
// Handle is_shared()
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
|
||||
let (arg, _) = self.get_arg_value(
|
||||
scope, global, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
return Ok(arg.is_shared().into());
|
||||
let arg = first_arg.unwrap();
|
||||
let (arg_value, _) =
|
||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
|
||||
return Ok(arg_value.is_shared().into());
|
||||
}
|
||||
|
||||
// Handle is_def_fn()
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => {
|
||||
let (arg, arg_pos) = self.get_arg_value(
|
||||
scope, global, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
let first = first_arg.unwrap();
|
||||
let (arg_value, arg_pos) = self
|
||||
.get_arg_value(scope, global, state, lib, this_ptr, level, first, constants)?;
|
||||
|
||||
let fn_name = arg
|
||||
let fn_name = arg_value
|
||||
.into_immutable_string()
|
||||
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
|
||||
|
||||
let (arg, arg_pos) = self.get_arg_value(
|
||||
scope, global, state, lib, this_ptr, level, &a_expr[1], constants,
|
||||
let (arg_value, arg_pos) = self.get_arg_value(
|
||||
scope, global, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
|
||||
let num_params = arg
|
||||
let num_params = arg_value
|
||||
.as_int()
|
||||
.map_err(|typ| self.make_type_mismatch_err::<crate::INT>(typ, arg_pos))?;
|
||||
|
||||
@ -1035,10 +1044,10 @@ impl Engine {
|
||||
|
||||
// Handle is_def_var()
|
||||
KEYWORD_IS_DEF_VAR if total_args == 1 => {
|
||||
let (arg, arg_pos) = self.get_arg_value(
|
||||
scope, global, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
let var_name = arg
|
||||
let arg = first_arg.unwrap();
|
||||
let (arg_value, arg_pos) =
|
||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
|
||||
let var_name = arg_value
|
||||
.into_immutable_string()
|
||||
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, arg_pos))?;
|
||||
return Ok(scope.contains(&var_name).into());
|
||||
@ -1048,10 +1057,10 @@ impl Engine {
|
||||
KEYWORD_EVAL if total_args == 1 => {
|
||||
// eval - only in function call style
|
||||
let orig_scope_len = scope.len();
|
||||
let (value, pos) = self.get_arg_value(
|
||||
scope, global, state, lib, this_ptr, level, &a_expr[0], constants,
|
||||
)?;
|
||||
let script = &value
|
||||
let arg = first_arg.unwrap();
|
||||
let (arg_value, pos) =
|
||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, arg, constants)?;
|
||||
let script = &arg_value
|
||||
.into_immutable_string()
|
||||
.map_err(|typ| self.make_type_mismatch_err::<ImmutableString>(typ, pos))?;
|
||||
let result = self.eval_script_expr_in_place(
|
||||
@ -1085,8 +1094,8 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Normal function call - except for Fn, curry, call and eval (handled above)
|
||||
let mut arg_values = FnArgsVec::with_capacity(a_expr.len());
|
||||
let mut args = FnArgsVec::with_capacity(a_expr.len() + curry.len());
|
||||
let mut arg_values = FnArgsVec::with_capacity(total_args);
|
||||
let mut args = FnArgsVec::with_capacity(total_args + curry.len());
|
||||
let mut is_ref_mut = false;
|
||||
|
||||
// Capture parent scope?
|
||||
@ -1094,10 +1103,14 @@ impl Engine {
|
||||
// If so, do it separately because we cannot convert the first argument (if it is a simple
|
||||
// variable access) to &mut because `scope` is needed.
|
||||
if capture_scope && !scope.is_empty() {
|
||||
a_expr.iter().try_for_each(|expr| {
|
||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
|
||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||
})?;
|
||||
first_arg
|
||||
.iter()
|
||||
.map(|&v| v)
|
||||
.chain(a_expr.iter())
|
||||
.try_for_each(|expr| {
|
||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
|
||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||
})?;
|
||||
args.extend(curry.iter_mut());
|
||||
args.extend(arg_values.iter_mut());
|
||||
|
||||
@ -1113,17 +1126,17 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Call with blank scope
|
||||
if a_expr.is_empty() && curry.is_empty() {
|
||||
if total_args == 0 && curry.is_empty() {
|
||||
// No arguments
|
||||
} else {
|
||||
// If the first argument is a variable, and there is no curried arguments,
|
||||
// convert to method-call style in order to leverage potential &mut first argument and
|
||||
// avoid cloning the value
|
||||
if curry.is_empty() && !a_expr.is_empty() && a_expr[0].is_variable_access(false) {
|
||||
if curry.is_empty() && first_arg.map_or(false, |expr| expr.is_variable_access(false)) {
|
||||
// func(x, ...) -> x.func(...)
|
||||
let (first_expr, rest_expr) = a_expr.split_first().unwrap();
|
||||
let first_expr = first_arg.unwrap();
|
||||
|
||||
rest_expr.iter().try_for_each(|expr| {
|
||||
a_expr.iter().try_for_each(|expr| {
|
||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
|
||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||
})?;
|
||||
@ -1155,10 +1168,15 @@ impl Engine {
|
||||
}
|
||||
} else {
|
||||
// func(..., ...)
|
||||
a_expr.iter().try_for_each(|expr| {
|
||||
self.get_arg_value(scope, global, state, lib, this_ptr, level, expr, constants)
|
||||
first_arg
|
||||
.into_iter()
|
||||
.chain(a_expr.iter())
|
||||
.try_for_each(|expr| {
|
||||
self.get_arg_value(
|
||||
scope, global, state, lib, this_ptr, level, expr, constants,
|
||||
)
|
||||
.map(|(value, _)| arg_values.push(value.flatten()))
|
||||
})?;
|
||||
})?;
|
||||
args.extend(curry.iter_mut());
|
||||
args.extend(arg_values.iter_mut());
|
||||
}
|
||||
|
@ -71,6 +71,7 @@ pub enum EvalAltResult {
|
||||
ErrorDotExpr(String, Position),
|
||||
/// Arithmetic error encountered. Wrapped value is the error message.
|
||||
ErrorArithmetic(String, Position),
|
||||
|
||||
/// Number of operations over maximum limit.
|
||||
ErrorTooManyOperations(Position),
|
||||
/// [Modules][crate::Module] over maximum limit.
|
||||
@ -81,6 +82,14 @@ pub enum EvalAltResult {
|
||||
ErrorDataTooLarge(String, Position),
|
||||
/// The script is prematurely terminated. Wrapped value is the termination token.
|
||||
ErrorTerminated(Dynamic, Position),
|
||||
|
||||
/// Error encountered for a custom syntax. Wrapped values are the error message and
|
||||
/// custom syntax symbols stream.
|
||||
///
|
||||
/// Normally this should never happen, unless an [`AST`][crate::AST] is compiled on one
|
||||
/// [`Engine`][crate::Engine] but evaluated on another unrelated [`Engine`][crate::Engine].
|
||||
ErrorCustomSyntax(String, Vec<String>, Position),
|
||||
|
||||
/// Run-time error encountered. Wrapped value is the error token.
|
||||
ErrorRuntime(Dynamic, Position),
|
||||
|
||||
@ -204,6 +213,8 @@ impl fmt::Display for EvalAltResult {
|
||||
index, max
|
||||
)?,
|
||||
Self::ErrorDataTooLarge(typ, _) => write!(f, "{} exceeds maximum limit", typ)?,
|
||||
|
||||
Self::ErrorCustomSyntax(s, tokens, _) => write!(f, "{}: {}", s, tokens.join(" "))?,
|
||||
}
|
||||
|
||||
// Do not write any position if None
|
||||
@ -266,7 +277,8 @@ impl EvalAltResult {
|
||||
| Self::ErrorArithmetic(_, _)
|
||||
| Self::ErrorRuntime(_, _) => true,
|
||||
|
||||
Self::ErrorTooManyOperations(_)
|
||||
Self::ErrorCustomSyntax(_, _, _)
|
||||
| Self::ErrorTooManyOperations(_)
|
||||
| Self::ErrorTooManyModules(_)
|
||||
| Self::ErrorStackOverflow(_)
|
||||
| Self::ErrorDataTooLarge(_, _)
|
||||
@ -282,7 +294,8 @@ impl EvalAltResult {
|
||||
Self::ErrorSystem(_, _) => true,
|
||||
Self::ErrorParsing(_, _) => true,
|
||||
|
||||
Self::ErrorTooManyOperations(_)
|
||||
Self::ErrorCustomSyntax(_, _, _)
|
||||
| Self::ErrorTooManyOperations(_)
|
||||
| Self::ErrorTooManyModules(_)
|
||||
| Self::ErrorStackOverflow(_)
|
||||
| Self::ErrorDataTooLarge(_, _) => true,
|
||||
@ -358,6 +371,20 @@ impl EvalAltResult {
|
||||
Self::ErrorTerminated(t, _) => {
|
||||
map.insert("token".into(), t.clone());
|
||||
}
|
||||
Self::ErrorCustomSyntax(_, tokens, _) => {
|
||||
map.insert(
|
||||
"tokens".into(),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Dynamic::from_array(tokens.iter().map(Into::into).collect()),
|
||||
#[cfg(feature = "no_index")]
|
||||
tokens
|
||||
.iter()
|
||||
.map(|s| s.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
/// Get the [position][Position] of this error.
|
||||
@ -389,6 +416,7 @@ impl EvalAltResult {
|
||||
| Self::ErrorStackOverflow(pos)
|
||||
| Self::ErrorDataTooLarge(_, pos)
|
||||
| Self::ErrorTerminated(_, pos)
|
||||
| Self::ErrorCustomSyntax(_, _, pos)
|
||||
| Self::ErrorRuntime(_, pos)
|
||||
| Self::LoopBreak(_, pos)
|
||||
| Self::Return(_, pos) => *pos,
|
||||
@ -436,6 +464,7 @@ impl EvalAltResult {
|
||||
| Self::ErrorStackOverflow(pos)
|
||||
| Self::ErrorDataTooLarge(_, pos)
|
||||
| Self::ErrorTerminated(_, pos)
|
||||
| Self::ErrorCustomSyntax(_, _, pos)
|
||||
| Self::ErrorRuntime(_, pos)
|
||||
| Self::LoopBreak(_, pos)
|
||||
| Self::Return(_, pos) => *pos = new_position,
|
||||
|
@ -120,6 +120,18 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
|
||||
.into_typed_array::<INT>()?,
|
||||
[1, 2, 3, 4, 5]
|
||||
);
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
assert!(!engine.eval::<bool>(
|
||||
"
|
||||
let x = 42;
|
||||
let y = [];
|
||||
let f = || x;
|
||||
for n in 0..10 {
|
||||
y += x;
|
||||
}
|
||||
some(y, |x| is_shared(x))
|
||||
"
|
||||
)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ fn test_comments() -> Result<(), Box<EvalAltResult>> {
|
||||
42
|
||||
);
|
||||
|
||||
assert_eq!(engine.eval::<()>("/* Hello world */")?, ());
|
||||
assert_eq!(engine.run("/* Hello world */")?, ());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
#![cfg(not(feature = "unchecked"))]
|
||||
use rhai::{Engine, EvalAltResult, ParseErrorType};
|
||||
use rhai::{Engine, EvalAltResult, ParseErrorType, INT};
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use rhai::Array;
|
||||
@ -101,6 +101,39 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
EvalAltResult::ErrorDataTooLarge(_, _)
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
let x = 42;
|
||||
let y = [];
|
||||
let f = || x;
|
||||
for n in 0..10 {
|
||||
y += x;
|
||||
}
|
||||
len(y)
|
||||
"
|
||||
)?,
|
||||
10
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.run(
|
||||
"
|
||||
let x = 42;
|
||||
let y = [];
|
||||
let f = || x;
|
||||
for n in 0..11 {
|
||||
y += x;
|
||||
}
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorDataTooLarge(_, _)
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.run(
|
||||
@ -113,13 +146,25 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
EvalAltResult::ErrorDataTooLarge(_, _)
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
let x = [1,2,3,4,5,6];
|
||||
x.pad(10, 42);
|
||||
len(x)
|
||||
"
|
||||
)?,
|
||||
10
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.run(
|
||||
"
|
||||
let x = [1,2,3,4,5,6];
|
||||
x.pad(100, 42);
|
||||
x.pad(11, 42);
|
||||
x
|
||||
"
|
||||
)
|
||||
@ -127,6 +172,16 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
EvalAltResult::ErrorDataTooLarge(_, _)
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
let x = [1,2,3];
|
||||
len([x, x, x])
|
||||
"
|
||||
)?,
|
||||
3
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.run(
|
||||
|
@ -36,7 +36,7 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
|
||||
5
|
||||
);
|
||||
|
||||
engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?;
|
||||
engine.run("let y = #{a: 1, b: 2, c: 3}; y.z")?;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
assert_eq!(
|
||||
|
@ -243,7 +243,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.set_max_modules(1000);
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
engine.eval::<()>(
|
||||
engine.run(
|
||||
r#"
|
||||
fn foo() {
|
||||
import "hello" as h;
|
||||
|
@ -15,18 +15,48 @@ fn test_max_operations() -> Result<(), Box<EvalAltResult>> {
|
||||
None
|
||||
});
|
||||
|
||||
engine.eval::<()>("let x = 0; while x < 20 { x += 1; }")?;
|
||||
engine.run("let x = 0; while x < 20 { x += 1; }")?;
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<()>("for x in 0..500 {}")
|
||||
.expect_err("should error"),
|
||||
*engine.run("for x in 0..500 {}").expect_err("should error"),
|
||||
EvalAltResult::ErrorTooManyOperations(_)
|
||||
));
|
||||
|
||||
engine.set_max_operations(0);
|
||||
|
||||
engine.eval::<()>("for x in 0..10000 {}")?;
|
||||
engine.run("for x in 0..10000 {}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_max_operations_literal() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
engine.set_optimization_level(rhai::OptimizationLevel::None);
|
||||
engine.set_max_operations(10);
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
engine.run("[1, 2, 3, 4, 5, 6, 7]")?;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.run("[1, 2, 3, 4, 5, 6, 7, 8, 9]")
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorTooManyOperations(_)
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
engine.run("#{a:1, b:2, c:3, d:4, e:5, f:6, g:7}")?;
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.run("#{a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9}")
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorTooManyOperations(_)
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -43,7 +73,7 @@ fn test_max_operations_functions() -> Result<(), Box<EvalAltResult>> {
|
||||
None
|
||||
});
|
||||
|
||||
engine.eval::<()>(
|
||||
engine.run(
|
||||
r#"
|
||||
print("Test1");
|
||||
let x = 0;
|
||||
@ -56,7 +86,7 @@ fn test_max_operations_functions() -> Result<(), Box<EvalAltResult>> {
|
||||
)?;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
engine.eval::<()>(
|
||||
engine.run(
|
||||
r#"
|
||||
print("Test2");
|
||||
fn inc(x) { x + 1 }
|
||||
@ -68,7 +98,7 @@ fn test_max_operations_functions() -> Result<(), Box<EvalAltResult>> {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<()>(
|
||||
.run(
|
||||
r#"
|
||||
print("Test3");
|
||||
fn inc(x) { x + 1 }
|
||||
@ -101,7 +131,7 @@ fn test_max_operations_eval() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<()>(
|
||||
.run(
|
||||
r#"
|
||||
let script = "for x in 0..500 {}";
|
||||
eval(script);
|
||||
@ -131,7 +161,7 @@ fn test_max_operations_progress() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<()>("for x in 0..500 {}")
|
||||
.run("for x in 0..500 {}")
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorTerminated(x, _) if x.as_int()? == 42
|
||||
));
|
||||
|
@ -21,7 +21,7 @@ fn test_stack_overflow_fn_calls() -> Result<(), Box<EvalAltResult>> {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<()>(&format!(
|
||||
.run(&format!(
|
||||
"
|
||||
fn foo(n) {{ if n == 0 {{ 0 }} else {{ n + foo(n-1) }} }}
|
||||
foo({})
|
||||
|
@ -11,7 +11,7 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
|
||||
'a'
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<()>("switch 3 { 1 => (), 2 => 'a', 42 => true }")?,
|
||||
engine.run("switch 3 { 1 => (), 2 => 'a', 42 => true }")?,
|
||||
()
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -5,12 +5,12 @@ fn test_throw() {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert!(matches!(
|
||||
*engine.eval::<()>("if true { throw 42 }").expect_err("expects error"),
|
||||
*engine.run("if true { throw 42 }").expect_err("expects error"),
|
||||
EvalAltResult::ErrorRuntime(s, _) if s.as_int().unwrap() == 42
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
*engine.eval::<()>(r#"throw"#).expect_err("expects error"),
|
||||
*engine.run(r#"throw"#).expect_err("expects error"),
|
||||
EvalAltResult::ErrorRuntime(s, _) if s.is::<()>()
|
||||
));
|
||||
}
|
||||
@ -88,7 +88,7 @@ fn test_try_catch() -> Result<(), Box<EvalAltResult>> {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<()>("try { 42/0; } catch { throw; }")
|
||||
.run("try { 42/0; } catch { throw; }")
|
||||
.expect_err("expects error"),
|
||||
EvalAltResult::ErrorArithmetic(_, _)
|
||||
));
|
||||
|
@ -3,7 +3,7 @@ use rhai::{Engine, EvalAltResult};
|
||||
#[test]
|
||||
fn test_unit() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
engine.eval::<()>("let x = (); x")?;
|
||||
engine.run("let x = (); x")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -17,6 +17,6 @@ fn test_unit_eq() -> Result<(), Box<EvalAltResult>> {
|
||||
#[test]
|
||||
fn test_unit_with_spaces() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
engine.eval::<()>("let x = ( ); x")?;
|
||||
engine.run("let x = ( ); x")?;
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user