Merge pull request #505 from schungx/master

Ready release of 1.4.0.
This commit is contained in:
Stephen Chung 2022-01-11 11:52:52 +08:00 committed by GitHub
commit 56602927d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 361 additions and 124 deletions

View File

@ -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
=============

View File

@ -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 = "

View File

@ -1,5 +1,3 @@
cargo-features = ["named-profiles"]
[workspace]
[package]

View File

@ -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`.

View File

@ -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),
}
}

View File

@ -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"))]

View File

@ -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()
}
}

View File

@ -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,

View File

@ -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()
}
}

View File

@ -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

View File

@ -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());
}

View File

@ -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,

View File

@ -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(())
}

View File

@ -21,7 +21,7 @@ fn test_comments() -> Result<(), Box<EvalAltResult>> {
42
);
assert_eq!(engine.eval::<()>("/* Hello world */")?, ());
assert_eq!(engine.run("/* Hello world */")?, ());
Ok(())
}

View File

@ -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(

View File

@ -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!(

View File

@ -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;

View File

@ -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
));

View File

@ -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({})

View File

@ -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!(

View File

@ -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(_, _)
));

View File

@ -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(())
}