Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-08-06 10:31:15 +08:00
commit 26c5b10ef7
37 changed files with 431 additions and 404 deletions

View File

@ -4,7 +4,6 @@ on:
push: push:
branches: branches:
- master - master
- closures
pull_request: {} pull_request: {}
jobs: jobs:
@ -51,7 +50,7 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: test command: test
args: --all {{matrix.flags}} args: --all ${{matrix.flags}}
# no-std builds are a bit more extensive to test # no-std builds are a bit more extensive to test
no_std_build: no_std_build:
name: NoStdBuild name: NoStdBuild

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rhai" name = "rhai"
version = "0.18.0" version = "0.19.0"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"
@ -32,7 +32,7 @@ only_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
no_index = [] # no arrays and indexing no_index = [] # no arrays and indexing
no_object = [] # no custom objects no_object = [] # no custom objects
no_function = [] # no script-defined functions no_function = [ "no_closure "] # no script-defined functions (meaning no closures)
no_closure = [] # no automatic sharing and capture of anonymous functions to external variables no_closure = [] # no automatic sharing and capture of anonymous functions to external variables
no_module = [] # no modules no_module = [] # no modules
internals = [] # expose internal data structures internals = [] # expose internal data structures

View File

@ -32,7 +32,7 @@ Standard features
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
* [Function overloading](https://schungx.github.io/rhai/language/overload.html). * [Function overloading](https://schungx.github.io/rhai/language/overload.html).
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html). * [Operator overloading](https://schungx.github.io/rhai/rust/operators.html).
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html) with additional support for [currying](https://schungx.github.io/rhai/language/fn-curry.html). * Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html) with additional support for [currying](https://schungx.github.io/rhai/language/fn-curry.html) and [closures](https://schungx.github.io/rhai/language/fn-closure.html).
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html). * Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
* Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html). * Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html).
* Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature).

View File

@ -1,15 +1,19 @@
Rhai Release Notes Rhai Release Notes
================== ==================
Version 0.18.0 Version 0.19.0
==============
Version 0.18.1
============== ==============
This version adds: This version adds:
* Binding the `this` pointer in a function pointer `call`.
* Anonymous functions (in Rust closure syntax). Simplifies creation of single-use ad-hoc functions. * Anonymous functions (in Rust closure syntax). Simplifies creation of single-use ad-hoc functions.
* Currying of function pointers. * Currying of function pointers.
* Closures - auto-currying of anonymous functions to capture shared variables from the external scope. * Closures - auto-currying of anonymous functions to capture shared variables from the external scope. Use the `no_closure` feature to disable sharing values and capturing.
* Binding the `this` pointer in a function pointer `call`.
* Capturing call scope via `func!(...)` syntax. * Capturing call scope via `func!(...)` syntax.
New features New features
@ -36,6 +40,7 @@ Breaking changes
* `PackagesCollection::get_fn`, `PackagesCollection::contains_fn`, `Module::get_fn` and `Module::contains_fn` now take an additional `public_only` parameter indicating whether only public functions are accepted. * `PackagesCollection::get_fn`, `PackagesCollection::contains_fn`, `Module::get_fn` and `Module::contains_fn` now take an additional `public_only` parameter indicating whether only public functions are accepted.
* The iterator returned by `Scope::iter` now contains a clone of the `Dynamic` value (unshared). * The iterator returned by `Scope::iter` now contains a clone of the `Dynamic` value (unshared).
* `Engine::load_package` takes any type that is `Into<PackageLibrary>`. * `Engine::load_package` takes any type that is `Into<PackageLibrary>`.
* Error in `Engine::register_custom_syntax` is no longer `Box`-ed.
Housekeeping Housekeeping
------------ ------------

View File

@ -1,5 +1,5 @@
{ {
"version": "0.18.0", "version": "0.19.0",
"repoHome": "https://github.com/jonathandturner/rhai/blob/master", "repoHome": "https://github.com/jonathandturner/rhai/blob/master",
"repoTree": "https://github.com/jonathandturner/rhai/tree/master", "repoTree": "https://github.com/jonathandturner/rhai/tree/master",
"rootUrl": "", "rootUrl": "",

View File

@ -200,8 +200,10 @@ fn implementation_func(
// Evaluate the condition expression // Evaluate the condition expression
let stop = !engine.eval_expression_tree(context, scope, condition)? let stop = !engine.eval_expression_tree(context, scope, condition)?
.as_bool().map_err(|_| EvalAltResult::ErrorBooleanArgMismatch( .as_bool()
"do-while".into(), expr.position()))?; .map_err(|_| EvalAltResult::ErrorBooleanArgMismatch(
"do-while".into(), expr.position()
))?;
if stop { if stop {
break; break;

View File

@ -7,10 +7,17 @@ Import a Module
`import` Statement `import` Statement
----------------- -----------------
A module can be _imported_ via the `import` statement, and its members are accessed via '`::`' similar to C++. A module can be _imported_ via the `import` statement, and be given a name.
Its members can be accessed via '`::`' similar to C++.
A module that is only `import`-ed but not under any module name is commonly used for initialization purposes,
where the module script contains initialization statements that puts the functions registered with the
[`Engine`] into a particular state.
```rust ```rust
import "crypto" as lock; // import the script file 'crypto.rhai' as a module named 'lock' import "crypto_init"; // run the script file 'crypto_init.rhai' without creating an imported module
import "crypto" as lock; // run the script file 'crypto.rhai' and import it as a module named 'lock'
lock::encrypt(secret); // use functions defined under the module via '::' lock::encrypt(secret); // use functions defined under the module via '::'

View File

@ -38,7 +38,7 @@ impl ModuleResolver for MyModuleResolver {
let module: Module = load_secret_module(path); let module: Module = load_secret_module(path);
Ok(module) Ok(module)
} else { } else {
Err(Box::new(EvalAltResult::ErrorModuleNotFound(path.into(), pos))) Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
} }
} }
} }

View File

@ -38,8 +38,8 @@ Omitting arrays ([`no_index`]) yields the most code-size savings, followed by fl
([`no_float`]), checked arithmetic/script resource limits ([`unchecked`]) and finally object maps and custom types ([`no_object`]). ([`no_float`]), checked arithmetic/script resource limits ([`unchecked`]) and finally object maps and custom types ([`no_object`]).
Where the usage scenario does not call for loading externally-defined modules, use [`no_module`] to save some bytes. Where the usage scenario does not call for loading externally-defined modules, use [`no_module`] to save some bytes.
Disable script-defined functions ([`no_function`]) when the feature is not needed. Disable script-defined functions ([`no_function`]) and possibly closures ([`no_closure`]) when the features are not needed.
Both of these have little code size savings. Both of these have some code size savings but not much.
Use a Raw [`Engine`] Use a Raw [`Engine`]

View File

@ -3,11 +3,12 @@ Performance Build
{{#include ../../links.md}} {{#include ../../links.md}}
Some features are for performance. For example, using [`only_i32`] or [`only_i64`] disables all other integer types (such as `u16`).
Use Only One Integer Type Use Only One Integer Type
------------------------ ------------------------
Some features are for performance. For example, using [`only_i32`] or [`only_i64`] disables all other integer types (such as `u16`).
If only a single integer type is needed in scripts - most of the time this is the case - it is best to avoid registering If only a single integer type is needed in scripts - most of the time this is the case - it is best to avoid registering
lots of functions related to other integer types that will never be used. As a result, [`Engine`] creation will be faster lots of functions related to other integer types that will never be used. As a result, [`Engine`] creation will be faster
because fewer functions need to be loaded. because fewer functions need to be loaded.
@ -22,8 +23,8 @@ On 64-bit targets this may not gain much, but on some 32-bit targets this improv
requiring more CPU cycles to complete. requiring more CPU cycles to complete.
Minimize Size of [`Dynamic`] Minimize Size of `Dynamic`
--------------------------- -------------------------
Turning on [`no_float`], and [`only_i32`] makes the key [`Dynamic`] data type only 8 bytes small on 32-bit targets Turning on [`no_float`], and [`only_i32`] makes the key [`Dynamic`] data type only 8 bytes small on 32-bit targets
while normally it can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`. while normally it can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`.
@ -42,3 +43,15 @@ It is cheap to clone, but expensive to modify (a new copy of the string must be
Therefore, functions taking `String` parameters should use `ImmutableString` or `&str` (which maps to `ImmutableString`) Therefore, functions taking `String` parameters should use `ImmutableString` or `&str` (which maps to `ImmutableString`)
for the best performance with Rhai. for the best performance with Rhai.
Disable Closures
----------------
Support for [closures], including capturing shared values, adds material overhead to script evaluation.
This is because every data access must be checked whether it is a shared value, and if so, take a read
or write lock before reading it.
Use [`no_closure`] to disable closure and capturing support and optimize the hot path
because there is no need to take locks for shared data.

View File

@ -20,7 +20,7 @@ There are also a number of examples scripts that showcase Rhai's features, all i
| [`function_decl3.rhai`]({{repoTree}}/scripts/function_decl3.rhai) | A [function] with many parameters | | [`function_decl3.rhai`]({{repoTree}}/scripts/function_decl3.rhai) | A [function] with many parameters |
| [`if1.rhai`]({{repoTree}}/scripts/if1.rhai) | [`if`]({{rootUrl}}/language/if.md) example | | [`if1.rhai`]({{repoTree}}/scripts/if1.rhai) | [`if`]({{rootUrl}}/language/if.md) example |
| [`loop.rhai`]({{repoTree}}/scripts/loop.rhai) | Count-down [`loop`]({{rootUrl}}/language/loop.md) in Rhai, emulating a `do` .. `while` loop | | [`loop.rhai`]({{repoTree}}/scripts/loop.rhai) | Count-down [`loop`]({{rootUrl}}/language/loop.md) in Rhai, emulating a `do` .. `while` loop |
| [`oop.rhai`]({{repoTree}}/scripts/oop.rhai) | Simulate [object-oriented programming (OOP)][OOP] | | [`oop.rhai`]({{repoTree}}/scripts/oop.rhai) | Simulate [object-oriented programming (OOP)][OOP] with [closures] |
| [`op1.rhai`]({{repoTree}}/scripts/op1.rhai) | Just simple addition | | [`op1.rhai`]({{repoTree}}/scripts/op1.rhai) | Just simple addition |
| [`op2.rhai`]({{repoTree}}/scripts/op2.rhai) | Simple addition and multiplication | | [`op2.rhai`]({{repoTree}}/scripts/op2.rhai) | Simple addition and multiplication |
| [`op3.rhai`]({{repoTree}}/scripts/op3.rhai) | Change evaluation order with parenthesis | | [`op3.rhai`]({{repoTree}}/scripts/op3.rhai) | Change evaluation order with parenthesis |

View File

@ -1,45 +1,41 @@
// This script simulates object-oriented programming (OOP) techniques // This script simulates object-oriented programming (OOP) techniques using closures.
// using function pointers (Fn) and object maps.
// External variable that will be captured.
let last_value = ();
// Define object // Define object
let obj1 = #{ let obj1 = #{
_data: 42, // data field _data: 42, // data field
get_data: Fn("getData"), // property getter get_data: || this._data, // property getter
action: Fn("action"), // method action: || print("Data=" + this._data), // method
update: Fn("update1") // property setter update: |x| { // property setter
this._data = x;
last_value = this._data; // capture 'last_value'
this.action();
}
}; };
fn getData() { if obj1.get_data() > 0 { // property access
this._data obj1.update(123); // call method
}
fn action() {
print("Data=" + this._data);
}
fn update1(x) {
this._data = x;
this.action();
}
if obj1.get_data() > 0 { // property access
obj1.update(123); // call method
} else { } else {
print("we have a problem here"); print("we have a problem here");
} }
// Define another object based on the first object // Define another object based on the first object
let obj2 = #{ let obj2 = #{
_data: 0, // data field - new value _data: 0, // data field - new value
update: Fn("update2") // property setter - another function update: |x| { // property setter - another function
this._data = x * 2;
last_value = this._data; // capture 'last_value'
this.action();
}
}; };
obj2.fill_with(obj1); // add all other fields from obj1 obj2.fill_with(obj1); // add all other fields from obj1
fn update2(x) { if obj2.get_data() > 0 { // property access
this._data = x * 2; print("we have another problem here");
this.action();
}
if obj2.get_data() > 0 { // property access
obj2.update(0); // call method
} else { } else {
obj2.update(42); // call method obj2.update(42); // call method
} }
print("Should be 84: " + last_value);

View File

@ -410,7 +410,7 @@ impl fmt::Display for Dynamic {
} }
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => fmt::Display::fmt(*cell.read_lock().unwrap(), f), Union::Shared(cell) => fmt::Display::fmt(&*cell.read().unwrap(), f),
} }
} }
} }
@ -448,7 +448,7 @@ impl fmt::Debug for Dynamic {
} }
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell) => fmt::Display::fmt(*cell.read_lock().unwrap(), f), Union::Shared(cell) => fmt::Debug::fmt(&*cell.read().unwrap(), f),
} }
} }
} }
@ -600,6 +600,7 @@ impl Dynamic {
/// # Panics /// # Panics
/// ///
/// Panics under the `no_closure` feature. /// Panics under the `no_closure` feature.
#[inline(always)]
pub fn into_shared(self) -> Self { pub fn into_shared(self) -> Self {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
return match self.0 { return match self.0 {
@ -833,7 +834,6 @@ impl Dynamic {
let data = cell.read().unwrap(); let data = cell.read().unwrap();
let type_id = (*data).type_id(); let type_id = (*data).type_id();
println!("Type = {}", (*data).type_name());
if type_id != TypeId::of::<T>() && TypeId::of::<Dynamic>() != TypeId::of::<T>() { if type_id != TypeId::of::<T>() && TypeId::of::<Dynamic>() != TypeId::of::<T>() {
None None

View File

@ -3,10 +3,9 @@
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::engine::{Engine, Imports, State}; use crate::engine::{Engine, Imports, State};
use crate::error::ParseError; use crate::error::ParseError;
use crate::fn_call::ensure_no_data_race;
use crate::fn_native::{IteratorFn, SendSync}; use crate::fn_native::{IteratorFn, SendSync};
use crate::module::{FuncReturn, Module}; use crate::module::{FuncReturn, Module};
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::OptimizationLevel;
use crate::parser::AST; use crate::parser::AST;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::Scope;
@ -24,14 +23,22 @@ use crate::{
}; };
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::{engine::get_script_function_by_signature, fn_args::FuncArgs, utils::StaticVec}; use crate::{
engine::get_script_function_by_signature, fn_args::FuncArgs, fn_call::ensure_no_data_race,
utils::StaticVec,
};
#[cfg(not(feature = "no_optimize"))]
use crate::optimize::optimize_into_ast;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
boxed::Box, boxed::Box,
mem,
}; };
#[cfg(not(feature = "no_optimize"))]
use crate::stdlib::mem;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf};
@ -601,21 +608,13 @@ impl Engine {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
fn read_file(path: PathBuf) -> Result<String, Box<EvalAltResult>> { fn read_file(path: PathBuf) -> Result<String, Box<EvalAltResult>> {
let mut f = File::open(path.clone()).map_err(|err| { let mut f = File::open(path.clone()).map_err(|err| {
Box::new(EvalAltResult::ErrorReadingScriptFile( EvalAltResult::ErrorReadingScriptFile(path.clone(), Position::none(), err)
path.clone(),
Position::none(),
err,
))
})?; })?;
let mut contents = String::new(); let mut contents = String::new();
f.read_to_string(&mut contents).map_err(|err| { f.read_to_string(&mut contents).map_err(|err| {
Box::new(EvalAltResult::ErrorReadingScriptFile( EvalAltResult::ErrorReadingScriptFile(path.clone(), Position::none(), err)
path.clone(),
Position::none(),
err,
))
})?; })?;
Ok(contents) Ok(contents)
@ -1035,11 +1034,12 @@ impl Engine {
let typ = self.map_type_name(result.type_name()); let typ = self.map_type_name(result.type_name());
return result.try_cast::<T>().ok_or_else(|| { return result.try_cast::<T>().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<T>()).into(), self.map_type_name(type_name::<T>()).into(),
typ.into(), typ.into(),
Position::none(), Position::none(),
)) )
.into()
}); });
} }
@ -1183,11 +1183,12 @@ impl Engine {
let typ = self.map_type_name(result.type_name()); let typ = self.map_type_name(result.type_name());
return result.try_cast().ok_or_else(|| { return result.try_cast().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<T>()).into(), self.map_type_name(type_name::<T>()).into(),
typ.into(), typ.into(),
Position::none(), Position::none(),
)) )
.into()
}); });
} }
@ -1271,13 +1272,8 @@ impl Engine {
) -> FuncReturn<Dynamic> { ) -> FuncReturn<Dynamic> {
let lib = lib.as_ref(); let lib = lib.as_ref();
let mut args: StaticVec<_> = arg_values.iter_mut().collect(); let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let fn_def = let fn_def = get_script_function_by_signature(lib, name, args.len(), true)
get_script_function_by_signature(lib, name, args.len(), true).ok_or_else(|| { .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::none()))?;
Box::new(EvalAltResult::ErrorFunctionNotFound(
name.into(),
Position::none(),
))
})?;
let mut state = State::new(); let mut state = State::new();
let mut mods = Imports::new(); let mut mods = Imports::new();

View File

@ -228,9 +228,7 @@ impl Target<'_> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Self::LockGuard((r, _)) => **r = new_val, Self::LockGuard((r, _)) => **r = new_val,
Self::Value(_) => { Self::Value(_) => {
return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( return EvalAltResult::ErrorAssignmentToUnknownLHS(Position::none()).into();
Position::none(),
)))
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::StringChar(string, index, _) if string.is::<ImmutableString>() => { Self::StringChar(string, index, _) if string.is::<ImmutableString>() => {
@ -517,12 +515,7 @@ pub fn search_imports<'s>(
.rev() .rev()
.find(|(n, _)| n == root) .find(|(n, _)| n == root)
.map(|(_, m)| m) .map(|(_, m)| m)
.ok_or_else(|| { .ok_or_else(|| EvalAltResult::ErrorModuleNotFound(root.to_string(), *root_pos))?
Box::new(EvalAltResult::ErrorModuleNotFound(
root.to_string(),
*root_pos,
))
})?
}) })
} }
@ -550,12 +543,7 @@ pub fn search_imports_mut<'s>(
.rev() .rev()
.find(|(n, _)| n == root) .find(|(n, _)| n == root)
.map(|(_, m)| m) .map(|(_, m)| m)
.ok_or_else(|| { .ok_or_else(|| EvalAltResult::ErrorModuleNotFound(root.to_string(), *root_pos))?
Box::new(EvalAltResult::ErrorModuleNotFound(
root.to_string(),
*root_pos,
))
})?
}) })
} }
@ -577,10 +565,11 @@ pub fn search_namespace<'s, 'a>(
.get_qualified_var_mut(*hash_var) .get_qualified_var_mut(*hash_var)
.map_err(|err| match *err { .map_err(|err| match *err {
EvalAltResult::ErrorVariableNotFound(_, _) => { EvalAltResult::ErrorVariableNotFound(_, _) => {
Box::new(EvalAltResult::ErrorVariableNotFound( EvalAltResult::ErrorVariableNotFound(
format!("{}{}", modules, name), format!("{}{}", modules, name),
*pos, *pos,
)) )
.into()
} }
_ => err.new_position(*pos), _ => err.new_position(*pos),
})?; })?;
@ -612,7 +601,7 @@ pub fn search_scope_only<'s, 'a>(
if let Some(val) = this_ptr { if let Some(val) = this_ptr {
return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos)); return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos));
} else { } else {
return Err(Box::new(EvalAltResult::ErrorUnboundThis(*pos))); return EvalAltResult::ErrorUnboundThis(*pos).into();
} }
} }
@ -625,7 +614,7 @@ pub fn search_scope_only<'s, 'a>(
// Find the variable in the scope // Find the variable in the scope
scope scope
.get_index(name) .get_index(name)
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), *pos)))? .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(name.into(), *pos))?
.0 .0
}; };
@ -634,7 +623,7 @@ pub fn search_scope_only<'s, 'a>(
// Check for data race - probably not necessary because the only place it should conflict is in a method call // Check for data race - probably not necessary because the only place it should conflict is in a method call
// when the object variable is also used as a parameter. // when the object variable is also used as a parameter.
// if cfg!(not(feature = "no_closure")) && val.is_locked() { // if cfg!(not(feature = "no_closure")) && val.is_locked() {
// return Err(Box::new(EvalAltResult::ErrorDataRace(name.into(), *pos))); // return EvalAltResult::ErrorDataRace(name.into(), *pos).into();
// } // }
Ok((val, name, typ, *pos)) Ok((val, name, typ, *pos))
@ -966,10 +955,7 @@ impl Engine {
} }
} }
// Syntax error // Syntax error
_ => Err(Box::new(EvalAltResult::ErrorDotExpr( _ => EvalAltResult::ErrorDotExpr("".into(), rhs.position()).into(),
"".into(),
rhs.position(),
))),
} }
} }
@ -1016,10 +1002,8 @@ impl Engine {
// Constants cannot be modified // Constants cannot be modified
match typ { match typ {
ScopeEntryType::Constant if new_val.is_some() => { ScopeEntryType::Constant if new_val.is_some() => {
return Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( return EvalAltResult::ErrorAssignmentToConstant(var_name.to_string(), pos)
var_name.to_string(), .into();
pos,
)));
} }
ScopeEntryType::Constant | ScopeEntryType::Normal => (), ScopeEntryType::Constant | ScopeEntryType::Normal => (),
} }
@ -1033,9 +1017,7 @@ impl Engine {
} }
// {expr}.??? = ??? or {expr}[???] = ??? // {expr}.??? = ??? or {expr}[???] = ???
expr if new_val.is_some() => { expr if new_val.is_some() => {
return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( return EvalAltResult::ErrorAssignmentToUnknownLHS(expr.position()).into();
expr.position(),
)));
} }
// {expr}.??? or {expr}[???] // {expr}.??? or {expr}[???]
expr => { expr => {
@ -1160,12 +1142,10 @@ impl Engine {
arr.get_mut(index as usize) arr.get_mut(index as usize)
.map(Target::from) .map(Target::from)
.ok_or_else(|| { .ok_or_else(|| {
Box::new(EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos)) EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos).into()
}) })
} else { } else {
Err(Box::new(EvalAltResult::ErrorArrayBounds( EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos).into()
arr_len, index, idx_pos,
)))
} }
} }
@ -1200,13 +1180,11 @@ impl Engine {
if index >= 0 { if index >= 0 {
let offset = index as usize; let offset = index as usize;
let ch = s.chars().nth(offset).ok_or_else(|| { let ch = s.chars().nth(offset).ok_or_else(|| {
Box::new(EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos)) EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos)
})?; })?;
Ok(Target::StringChar(val, offset, ch.into())) Ok(Target::StringChar(val, offset, ch.into()))
} else { } else {
Err(Box::new(EvalAltResult::ErrorStringBounds( EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos).into()
chars_len, index, idx_pos,
)))
} }
} }
@ -1227,10 +1205,11 @@ impl Engine {
}) })
} }
_ => Err(Box::new(EvalAltResult::ErrorIndexingType( _ => EvalAltResult::ErrorIndexingType(
self.map_type_name(val.type_name()).into(), self.map_type_name(val.type_name()).into(),
Position::none(), Position::none(),
))), )
.into(),
} }
} }
@ -1284,15 +1263,15 @@ impl Engine {
// Only allows String or char // Only allows String or char
Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(&s).into()), Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(&s).into()),
Dynamic(Union::Char(c)) => Ok(rhs_value.contains_key(&c.to_string()).into()), Dynamic(Union::Char(c)) => Ok(rhs_value.contains_key(&c.to_string()).into()),
_ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))), _ => EvalAltResult::ErrorInExpr(lhs.position()).into(),
}, },
Dynamic(Union::Str(rhs_value)) => match lhs_value { Dynamic(Union::Str(rhs_value)) => match lhs_value {
// Only allows String or char // Only allows String or char
Dynamic(Union::Str(s)) => Ok(rhs_value.contains(s.as_str()).into()), Dynamic(Union::Str(s)) => Ok(rhs_value.contains(s.as_str()).into()),
Dynamic(Union::Char(c)) => Ok(rhs_value.contains(c).into()), Dynamic(Union::Char(c)) => Ok(rhs_value.contains(c).into()),
_ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))), _ => EvalAltResult::ErrorInExpr(lhs.position()).into(),
}, },
_ => Err(Box::new(EvalAltResult::ErrorInExpr(rhs.position()))), _ => EvalAltResult::ErrorInExpr(rhs.position()).into(),
} }
} }
@ -1323,7 +1302,7 @@ impl Engine {
if let Some(val) = this_ptr { if let Some(val) = this_ptr {
Ok(val.clone()) Ok(val.clone())
} else { } else {
Err(Box::new(EvalAltResult::ErrorUnboundThis((x.0).1))) EvalAltResult::ErrorUnboundThis((x.0).1).into()
} }
} }
Expr::Variable(_) => { Expr::Variable(_) => {
@ -1454,16 +1433,13 @@ impl Engine {
Ok(Default::default()) Ok(Default::default())
} }
// Error assignment to constant // Error assignment to constant
expr if expr.is_constant() => { expr if expr.is_constant() => EvalAltResult::ErrorAssignmentToConstant(
Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( expr.get_constant_str(),
expr.get_constant_str(),
expr.position(),
)))
}
// Syntax error
expr => Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(
expr.position(), expr.position(),
))), )
.into(),
// Syntax error
expr => EvalAltResult::ErrorAssignmentToUnknownLHS(expr.position()).into(),
} }
} }
@ -1632,7 +1608,7 @@ impl Engine {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.as_bool() .as_bool()
.map_err(|_| Box::new(EvalAltResult::ErrorLogicGuard(expr.position()))) .map_err(|_| EvalAltResult::ErrorLogicGuard(expr.position()).into())
.and_then(|guard_val| { .and_then(|guard_val| {
if guard_val { if guard_val {
self.eval_stmt(scope, mods, state, lib, this_ptr, if_block, level) self.eval_stmt(scope, mods, state, lib, this_ptr, if_block, level)
@ -1665,9 +1641,7 @@ impl Engine {
} }
} }
Ok(false) => return Ok(Default::default()), Ok(false) => return Ok(Default::default()),
Err(_) => { Err(_) => return EvalAltResult::ErrorLogicGuard(expr.position()).into(),
return Err(Box::new(EvalAltResult::ErrorLogicGuard(expr.position())))
}
} }
}, },
@ -1727,43 +1701,45 @@ impl Engine {
state.scope_level -= 1; state.scope_level -= 1;
Ok(Default::default()) Ok(Default::default())
} else { } else {
Err(Box::new(EvalAltResult::ErrorFor(x.1.position()))) EvalAltResult::ErrorFor(x.1.position()).into()
} }
} }
// Continue statement // Continue statement
Stmt::Continue(pos) => Err(Box::new(EvalAltResult::ErrorLoopBreak(false, *pos))), Stmt::Continue(pos) => EvalAltResult::ErrorLoopBreak(false, *pos).into(),
// Break statement // Break statement
Stmt::Break(pos) => Err(Box::new(EvalAltResult::ErrorLoopBreak(true, *pos))), Stmt::Break(pos) => EvalAltResult::ErrorLoopBreak(true, *pos).into(),
// Return value // Return value
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => { Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
let expr = x.1.as_ref().unwrap(); let expr = x.1.as_ref().unwrap();
Err(Box::new(EvalAltResult::Return( EvalAltResult::Return(
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?, self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?,
(x.0).1, (x.0).1,
))) )
.into()
} }
// Empty return // Empty return
Stmt::ReturnWithVal(x) if (x.0).0 == ReturnType::Return => { Stmt::ReturnWithVal(x) if (x.0).0 == ReturnType::Return => {
Err(Box::new(EvalAltResult::Return(Default::default(), (x.0).1))) EvalAltResult::Return(Default::default(), (x.0).1).into()
} }
// Throw value // Throw value
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => { Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => {
let expr = x.1.as_ref().unwrap(); let expr = x.1.as_ref().unwrap();
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
Err(Box::new(EvalAltResult::ErrorRuntime( EvalAltResult::ErrorRuntime(
val.take_string().unwrap_or_else(|_| "".into()), val.take_string().unwrap_or_else(|_| "".into()),
(x.0).1, (x.0).1,
))) )
.into()
} }
// Empty throw // Empty throw
Stmt::ReturnWithVal(x) if (x.0).0 == ReturnType::Exception => { Stmt::ReturnWithVal(x) if (x.0).0 == ReturnType::Exception => {
Err(Box::new(EvalAltResult::ErrorRuntime("".into(), (x.0).1))) EvalAltResult::ErrorRuntime("".into(), (x.0).1).into()
} }
Stmt::ReturnWithVal(_) => unreachable!(), Stmt::ReturnWithVal(_) => unreachable!(),
@ -1806,12 +1782,12 @@ impl Engine {
// Import statement // Import statement
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Import(x) => { Stmt::Import(x) => {
let (expr, (name, _pos), _) = x.as_ref(); let (expr, alias, _pos) = x.as_ref();
// Guard against too many modules // Guard against too many modules
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if state.modules >= self.limits.max_modules { if state.modules >= self.limits.max_modules {
return Err(Box::new(EvalAltResult::ErrorTooManyModules(*_pos))); return EvalAltResult::ErrorTooManyModules(*_pos).into();
} }
if let Some(path) = self if let Some(path) = self
@ -1820,20 +1796,23 @@ impl Engine {
{ {
if let Some(resolver) = &self.module_resolver { if let Some(resolver) = &self.module_resolver {
let mut module = resolver.resolve(self, &path, expr.position())?; let mut module = resolver.resolve(self, &path, expr.position())?;
module.index_all_sub_modules();
mods.push((name.clone().into(), module)); if let Some((name, _)) = alias {
module.index_all_sub_modules();
mods.push((name.clone().into(), module));
}
state.modules += 1; state.modules += 1;
Ok(Default::default()) Ok(Default::default())
} else { } else {
Err(Box::new(EvalAltResult::ErrorModuleNotFound( Err(
path.to_string(), EvalAltResult::ErrorModuleNotFound(path.to_string(), expr.position())
expr.position(), .into(),
))) )
} }
} else { } else {
Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position()))) EvalAltResult::ErrorImportExpr(expr.position()).into()
} }
} }
@ -1846,10 +1825,7 @@ impl Engine {
let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id); let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id);
scope.set_entry_alias(index, alias.clone()); scope.set_entry_alias(index, alias.clone());
} else { } else {
return Err(Box::new(EvalAltResult::ErrorVariableNotFound( return EvalAltResult::ErrorVariableNotFound(id.into(), *id_pos).into();
id.into(),
*id_pos,
)));
} }
} }
Ok(Default::default()) Ok(Default::default())
@ -1973,26 +1949,29 @@ impl Engine {
let (arr, map, s) = calc_size(result.as_ref().unwrap()); let (arr, map, s) = calc_size(result.as_ref().unwrap());
if s > self.limits.max_string_size { if s > self.limits.max_string_size {
Err(Box::new(EvalAltResult::ErrorDataTooLarge( EvalAltResult::ErrorDataTooLarge(
"Length of string".to_string(), "Length of string".to_string(),
self.limits.max_string_size, self.limits.max_string_size,
s, s,
Position::none(), Position::none(),
))) )
.into()
} else if arr > self.limits.max_array_size { } else if arr > self.limits.max_array_size {
Err(Box::new(EvalAltResult::ErrorDataTooLarge( EvalAltResult::ErrorDataTooLarge(
"Size of array".to_string(), "Size of array".to_string(),
self.limits.max_array_size, self.limits.max_array_size,
arr, arr,
Position::none(), Position::none(),
))) )
.into()
} else if map > self.limits.max_map_size { } else if map > self.limits.max_map_size {
Err(Box::new(EvalAltResult::ErrorDataTooLarge( EvalAltResult::ErrorDataTooLarge(
"Number of properties in object map".to_string(), "Number of properties in object map".to_string(),
self.limits.max_map_size, self.limits.max_map_size,
map, map,
Position::none(), Position::none(),
))) )
.into()
} else { } else {
result result
} }
@ -2006,16 +1985,14 @@ impl Engine {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
// Guard against too many operations // Guard against too many operations
if self.limits.max_operations > 0 && state.operations > self.limits.max_operations { if self.limits.max_operations > 0 && state.operations > self.limits.max_operations {
return Err(Box::new(EvalAltResult::ErrorTooManyOperations( return EvalAltResult::ErrorTooManyOperations(Position::none()).into();
Position::none(),
)));
} }
// Report progress - only in steps // Report progress - only in steps
if let Some(progress) = &self.progress { if let Some(progress) = &self.progress {
if !progress(&state.operations) { if !progress(&state.operations) {
// Terminate script if progress returns false // Terminate script if progress returns false
return Err(Box::new(EvalAltResult::ErrorTerminated(Position::none()))); return EvalAltResult::ErrorTerminated(Position::none()).into();
} }
} }

View File

@ -34,6 +34,7 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET};
use crate::engine::{Map, Target, FN_GET, FN_SET}; use crate::engine::{Map, Target, FN_GET, FN_SET};
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_function"))]
use crate::scope::Entry as ScopeEntry; use crate::scope::Entry as ScopeEntry;
use crate::stdlib::{ use crate::stdlib::{
@ -48,6 +49,7 @@ use crate::stdlib::{
}; };
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_function"))]
use crate::stdlib::{collections::HashSet, string::String}; use crate::stdlib::{collections::HashSet, string::String};
/// Extract the property name from a getter function name. /// Extract the property name from a getter function name.
@ -140,6 +142,7 @@ impl Drop for ArgBackup<'_> {
// Add captured variables into scope // Add captured variables into scope
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_function"))]
fn add_captured_variables_into_scope<'s>( fn add_captured_variables_into_scope<'s>(
externals: &HashSet<String>, externals: &HashSet<String>,
captured: Scope<'s>, captured: Scope<'s>,
@ -175,10 +178,11 @@ pub fn ensure_no_data_race(
.enumerate() .enumerate()
.find(|(_, a)| a.is_locked()) .find(|(_, a)| a.is_locked())
{ {
return Err(Box::new(EvalAltResult::ErrorDataRace( return EvalAltResult::ErrorDataRace(
format!("argument #{} of function '{}'", n + 1 + skip, fn_name), format!("argument #{} of function '{}'", n + 1 + skip, fn_name),
Position::none(), Position::none(),
))); )
.into();
} }
} }
@ -238,22 +242,22 @@ impl Engine {
return Ok(match fn_name { return Ok(match fn_name {
KEYWORD_PRINT => ( KEYWORD_PRINT => (
(self.print)(result.as_str().map_err(|typ| { (self.print)(result.as_str().map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(), self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(), typ.into(),
Position::none(), Position::none(),
)) )
})?) })?)
.into(), .into(),
false, false,
), ),
KEYWORD_DEBUG => ( KEYWORD_DEBUG => (
(self.debug)(result.as_str().map_err(|typ| { (self.debug)(result.as_str().map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(), self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(), typ.into(),
Position::none(), Position::none(),
)) )
})?) })?)
.into(), .into(),
false, false,
@ -277,56 +281,60 @@ impl Engine {
// Getter function not found? // Getter function not found?
if let Some(prop) = extract_prop_from_getter(fn_name) { if let Some(prop) = extract_prop_from_getter(fn_name) {
return Err(Box::new(EvalAltResult::ErrorDotExpr( return EvalAltResult::ErrorDotExpr(
format!( format!(
"Unknown property '{}' for {}, or it is write-only", "Unknown property '{}' for {}, or it is write-only",
prop, prop,
self.map_type_name(args[0].type_name()) self.map_type_name(args[0].type_name())
), ),
Position::none(), Position::none(),
))); )
.into();
} }
// Setter function not found? // Setter function not found?
if let Some(prop) = extract_prop_from_setter(fn_name) { if let Some(prop) = extract_prop_from_setter(fn_name) {
return Err(Box::new(EvalAltResult::ErrorDotExpr( return EvalAltResult::ErrorDotExpr(
format!( format!(
"Unknown property '{}' for {}, or it is read-only", "Unknown property '{}' for {}, or it is read-only",
prop, prop,
self.map_type_name(args[0].type_name()) self.map_type_name(args[0].type_name())
), ),
Position::none(), Position::none(),
))); )
.into();
} }
// index getter function not found? // index getter function not found?
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if fn_name == FN_IDX_GET && args.len() == 2 { if fn_name == FN_IDX_GET && args.len() == 2 {
return Err(Box::new(EvalAltResult::ErrorFunctionNotFound( return EvalAltResult::ErrorFunctionNotFound(
format!( format!(
"{} [{}]", "{} [{}]",
self.map_type_name(args[0].type_name()), self.map_type_name(args[0].type_name()),
self.map_type_name(args[1].type_name()), self.map_type_name(args[1].type_name()),
), ),
Position::none(), Position::none(),
))); )
.into();
} }
// index setter function not found? // index setter function not found?
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if fn_name == FN_IDX_SET { if fn_name == FN_IDX_SET {
return Err(Box::new(EvalAltResult::ErrorFunctionNotFound( return EvalAltResult::ErrorFunctionNotFound(
format!( format!(
"{} [{}]=", "{} [{}]=",
self.map_type_name(args[0].type_name()), self.map_type_name(args[0].type_name()),
self.map_type_name(args[1].type_name()), self.map_type_name(args[1].type_name()),
), ),
Position::none(), Position::none(),
))); )
.into();
} }
// Raise error // Raise error
Err(Box::new(EvalAltResult::ErrorFunctionNotFound( EvalAltResult::ErrorFunctionNotFound(
format!( format!(
"{} ({})", "{} ({})",
fn_name, fn_name,
@ -340,7 +348,8 @@ impl Engine {
.join(", ") .join(", ")
), ),
Position::none(), Position::none(),
))) )
.into()
} }
/// Call a script-defined function. /// Call a script-defined function.
@ -401,17 +410,15 @@ impl Engine {
// Convert return statement to return value // Convert return statement to return value
EvalAltResult::Return(x, _) => Ok(x), EvalAltResult::Return(x, _) => Ok(x),
EvalAltResult::ErrorInFunctionCall(name, err, _) => { EvalAltResult::ErrorInFunctionCall(name, err, _) => {
Err(Box::new(EvalAltResult::ErrorInFunctionCall( EvalAltResult::ErrorInFunctionCall(
format!("{} > {}", fn_name, name), format!("{} > {}", fn_name, name),
err, err,
Position::none(), Position::none(),
))) )
.into()
} }
_ => Err(Box::new(EvalAltResult::ErrorInFunctionCall( _ => EvalAltResult::ErrorInFunctionCall(fn_name.to_string(), err, Position::none())
fn_name.to_string(), .into(),
err,
Position::none(),
))),
}); });
// Remove all local variables // Remove all local variables
@ -453,11 +460,11 @@ impl Engine {
hash_script: u64, hash_script: u64,
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref: bool, is_ref: bool,
is_method: bool, _is_method: bool,
pub_only: bool, pub_only: bool,
_capture: Option<Scope>, _capture: Option<Scope>,
def_val: Option<bool>, def_val: Option<bool>,
level: usize, _level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
// Check for data race. // Check for data race.
if cfg!(not(feature = "no_closure")) { if cfg!(not(feature = "no_closure")) {
@ -483,20 +490,22 @@ impl Engine {
KEYWORD_FN_PTR KEYWORD_FN_PTR
if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) =>
{ {
Err(Box::new(EvalAltResult::ErrorRuntime( EvalAltResult::ErrorRuntime(
"'Fn' should not be called in method style. Try Fn(...);".into(), "'Fn' should not be called in method style. Try Fn(...);".into(),
Position::none(), Position::none(),
))) )
.into()
} }
// eval - reaching this point it must be a method-style call // eval - reaching this point it must be a method-style call
KEYWORD_EVAL KEYWORD_EVAL
if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) =>
{ {
Err(Box::new(EvalAltResult::ErrorRuntime( EvalAltResult::ErrorRuntime(
"'eval' should not be called in method style. Try eval(...);".into(), "'eval' should not be called in method style. Try eval(...);".into(),
Position::none(), Position::none(),
))) )
.into()
} }
// Normal script function call // Normal script function call
@ -514,7 +523,7 @@ impl Engine {
add_captured_variables_into_scope(&func.externals, captured, scope); add_captured_variables_into_scope(&func.externals, captured, scope);
} }
let result = if is_method { let result = if _is_method {
// Method call of script function - map first argument to `this` // Method call of script function - map first argument to `this`
let (first, rest) = args.split_at_mut(1); let (first, rest) = args.split_at_mut(1);
self.call_script_fn( self.call_script_fn(
@ -526,7 +535,7 @@ impl Engine {
fn_name, fn_name,
func, func,
rest, rest,
level, _level,
)? )?
} else { } else {
// Normal call of script function - map first argument to `this` // Normal call of script function - map first argument to `this`
@ -535,7 +544,7 @@ impl Engine {
backup.change_first_arg_to_copy(is_ref, args); backup.change_first_arg_to_copy(is_ref, args);
let result = self.call_script_fn( let result = self.call_script_fn(
scope, mods, state, lib, &mut None, fn_name, func, args, level, scope, mods, state, lib, &mut None, fn_name, func, args, _level,
); );
// Restore the original reference // Restore the original reference
@ -698,29 +707,42 @@ impl Engine {
&& _fn_name == KEYWORD_IS_SHARED && _fn_name == KEYWORD_IS_SHARED
&& idx.is_empty() && idx.is_empty()
{ {
// take call // is_shared call
Ok((target.is_shared().into(), false)) Ok((target.is_shared().into(), false))
} else { } else {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
let redirected; let redirected;
let mut _hash = hash_script; let mut hash = hash_script;
// Check if it is a map method call in OOP style // Check if it is a map method call in OOP style
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if let Some(map) = obj.read_lock::<Map>() { if let Some(map) = obj.read_lock::<Map>() {
if let Some(val) = map.get(_fn_name) { if let Some(val) = map.get(_fn_name) {
if let Some(f) = val.read_lock::<FnPtr>() { if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
// Remap the function name // Remap the function name
redirected = f.get_fn_name().clone(); redirected = fn_ptr.get_fn_name().clone();
_fn_name = &redirected; _fn_name = &redirected;
// Recalculate the hash based on the new function name // Add curried arguments
_hash = calc_fn_hash(empty(), _fn_name, idx.len(), empty()); if !fn_ptr.curry().is_empty() {
fn_ptr
.curry()
.iter()
.cloned()
.enumerate()
.for_each(|(i, v)| idx.insert(i, v));
}
// Recalculate the hash based on the new function name and new arguments
hash = if native {
0
} else {
calc_fn_hash(empty(), _fn_name, idx.len(), empty())
};
} }
} }
}; };
if native { if native {
_hash = 0; hash = 0;
} }
// Attached object pointer in front of the arguments // Attached object pointer in front of the arguments
@ -728,7 +750,7 @@ impl Engine {
let args = arg_values.as_mut(); let args = arg_values.as_mut();
self.exec_fn_call( self.exec_fn_call(
state, lib, _fn_name, _hash, args, is_ref, true, pub_only, None, def_val, level, state, lib, _fn_name, hash, args, is_ref, true, pub_only, None, def_val, level,
) )
}?; }?;
@ -771,11 +793,12 @@ impl Engine {
return arg_value return arg_value
.take_immutable_string() .take_immutable_string()
.map_err(|typ| { .map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(), self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(), typ.into(),
expr.position(), expr.position(),
)) )
.into()
}) })
.and_then(|s| FnPtr::try_from(s)) .and_then(|s| FnPtr::try_from(s))
.map(Into::<Dynamic>::into) .map(Into::<Dynamic>::into)
@ -789,11 +812,12 @@ impl Engine {
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
if !fn_ptr.is::<FnPtr>() { if !fn_ptr.is::<FnPtr>() {
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( return EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<FnPtr>()).into(), self.map_type_name(type_name::<FnPtr>()).into(),
self.map_type_name(fn_ptr.type_name()).into(), self.map_type_name(fn_ptr.type_name()).into(),
expr.position(), expr.position(),
))); )
.into();
} }
let (fn_name, fn_curry) = fn_ptr.cast::<FnPtr>().take_data(); let (fn_name, fn_curry) = fn_ptr.cast::<FnPtr>().take_data();
@ -843,11 +867,12 @@ impl Engine {
// Recalculate hash // Recalculate hash
hash_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); hash_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty());
} else { } else {
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( return EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<FnPtr>()).into(), self.map_type_name(type_name::<FnPtr>()).into(),
fn_name.type_name().into(), fn_name.type_name().into(),
expr.position(), expr.position(),
))); )
.into();
} }
} }
@ -1037,7 +1062,7 @@ impl Engine {
Some(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut(), Position::none()), Some(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut(), Position::none()),
Some(f) => f.get_native_fn()(self, lib, args.as_mut()), Some(f) => f.get_native_fn()(self, lib, args.as_mut()),
None if def_val.is_some() => Ok(def_val.unwrap().into()), None if def_val.is_some() => Ok(def_val.unwrap().into()),
None => Err(Box::new(EvalAltResult::ErrorFunctionNotFound( None => EvalAltResult::ErrorFunctionNotFound(
format!( format!(
"{}{} ({})", "{}{} ({})",
modules, modules,
@ -1052,7 +1077,8 @@ impl Engine {
.join(", ") .join(", ")
), ),
Position::none(), Position::none(),
))), )
.into(),
} }
} }
} }

View File

@ -1,22 +1,21 @@
//! Module defining interfaces to native-Rust functions. //! Module defining interfaces to native-Rust functions.
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::calc_fn_hash;
use crate::engine::Engine; use crate::engine::Engine;
use crate::module::Module; use crate::module::Module;
use crate::parser::FnAccess; use crate::parser::{FnAccess, ScriptFnDef};
use crate::plugin::PluginFunction; use crate::plugin::PluginFunction;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::{is_valid_identifier, Position}; use crate::token::{is_valid_identifier, Position};
use crate::utils::ImmutableString; use crate::utils::ImmutableString;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::{module::FuncReturn, parser::ScriptFnDef, utils::StaticVec}; use crate::{calc_fn_hash, module::FuncReturn, utils::StaticVec};
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, string::String, vec::Vec}; use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, string::String, vec::Vec};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::stdlib::mem; use crate::stdlib::{iter::empty, mem};
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
use crate::stdlib::rc::Rc; use crate::stdlib::rc::Rc;
@ -177,10 +176,7 @@ impl TryFrom<ImmutableString> for FnPtr {
if is_valid_identifier(value.chars()) { if is_valid_identifier(value.chars()) {
Ok(Self(value, Default::default())) Ok(Self(value, Default::default()))
} else { } else {
Err(Box::new(EvalAltResult::ErrorFunctionNotFound( EvalAltResult::ErrorFunctionNotFound(value.into(), Position::none()).into()
value.into(),
Position::none(),
)))
} }
} }
} }
@ -303,10 +299,11 @@ impl CallableFunction {
} }
} }
/// Is this a Rhai-scripted function? /// Is this a Rhai-scripted function?
#[cfg(not(feature = "no_function"))]
pub fn is_script(&self) -> bool { pub fn is_script(&self) -> bool {
match self { match self {
#[cfg(not(feature = "no_function"))]
Self::Script(_) => true, Self::Script(_) => true,
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => false, Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => false,
} }
} }
@ -314,13 +311,17 @@ impl CallableFunction {
pub fn is_plugin_fn(&self) -> bool { pub fn is_plugin_fn(&self) -> bool {
match self { match self {
Self::Plugin(_) => true, Self::Plugin(_) => true,
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false, Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
} }
} }
/// Is this a native Rust function? /// Is this a native Rust function?
pub fn is_native(&self) -> bool { pub fn is_native(&self) -> bool {
match self { match self {
Self::Pure(_) | Self::Method(_) => true, Self::Pure(_) | Self::Method(_) => true,
Self::Plugin(_) => true,
Self::Iterator(_) => true, Self::Iterator(_) => true,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -332,6 +333,8 @@ impl CallableFunction {
match self { match self {
Self::Plugin(_) => FnAccess::Public, Self::Plugin(_) => FnAccess::Public,
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => FnAccess::Public, Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => FnAccess::Public,
#[cfg(not(feature = "no_function"))]
Self::Script(f) => f.access, Self::Script(f) => f.access,
} }
} }
@ -366,10 +369,11 @@ impl CallableFunction {
/// # Panics /// # Panics
/// ///
/// Panics if the `CallableFunction` is not `Script`. /// Panics if the `CallableFunction` is not `Script`.
#[cfg(not(feature = "no_function"))]
pub fn get_fn_def(&self) -> &ScriptFnDef { pub fn get_fn_def(&self) -> &ScriptFnDef {
match self { match self {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(), Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
#[cfg(not(feature = "no_function"))]
Self::Script(f) => f, Self::Script(f) => f,
} }
} }
@ -429,16 +433,22 @@ impl From<IteratorFn> for CallableFunction {
} }
} }
#[cfg(not(feature = "no_function"))]
impl From<ScriptFnDef> for CallableFunction { impl From<ScriptFnDef> for CallableFunction {
fn from(func: ScriptFnDef) -> Self { fn from(_func: ScriptFnDef) -> Self {
Self::Script(func.into()) #[cfg(feature = "no_function")]
unreachable!();
#[cfg(not(feature = "no_function"))]
Self::Script(_func.into())
} }
} }
#[cfg(not(feature = "no_function"))]
impl From<Shared<ScriptFnDef>> for CallableFunction { impl From<Shared<ScriptFnDef>> for CallableFunction {
fn from(func: Shared<ScriptFnDef>) -> Self { fn from(_func: Shared<ScriptFnDef>) -> Self {
Self::Script(func) #[cfg(feature = "no_function")]
unreachable!();
#[cfg(not(feature = "no_function"))]
Self::Script(_func)
} }
} }

View File

@ -2,7 +2,7 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::any::{Dynamic, Variant, DynamicWriteLock}; use crate::any::{Dynamic, DynamicWriteLock, Variant};
use crate::engine::Engine; use crate::engine::Engine;
use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync};
use crate::module::Module; use crate::module::Module;
@ -12,12 +12,7 @@ use crate::r#unsafe::unsafe_cast_box;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::utils::ImmutableString; use crate::utils::ImmutableString;
use crate::stdlib::{ use crate::stdlib::{any::TypeId, boxed::Box, mem, string::String};
any::TypeId,
boxed::Box,
mem,
string::String,
};
/// A trait to register custom plugins with the `Engine`. /// A trait to register custom plugins with the `Engine`.
/// ///

View File

@ -4,13 +4,13 @@ use crate::any::{Dynamic, Variant};
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::Engine; use crate::engine::Engine;
use crate::fn_native::{CallableFunction as Func, FnCallArgs, IteratorFn, SendSync}; use crate::fn_native::{CallableFunction as Func, FnCallArgs, IteratorFn, SendSync};
use crate::parser::{FnAccess, FnAccess::Public}; use crate::parser::{FnAccess, FnAccess::Public, ScriptFnDef};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::{Position, Token}; use crate::token::{Position, Token};
use crate::utils::{StaticVec, StraightHasherBuilder}; use crate::utils::{StaticVec, StraightHasherBuilder};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::{fn_native::Shared, parser::ScriptFnDef}; use crate::fn_native::Shared;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
use crate::{ use crate::{
@ -248,17 +248,13 @@ impl Module {
hash_var: u64, hash_var: u64,
) -> Result<&mut Dynamic, Box<EvalAltResult>> { ) -> Result<&mut Dynamic, Box<EvalAltResult>> {
self.all_variables.get_mut(&hash_var).ok_or_else(|| { self.all_variables.get_mut(&hash_var).ok_or_else(|| {
Box::new(EvalAltResult::ErrorVariableNotFound( EvalAltResult::ErrorVariableNotFound(String::new(), Position::none()).into()
String::new(),
Position::none(),
))
}) })
} }
/// Set a script-defined function into the module. /// Set a script-defined function into the module.
/// ///
/// If there is an existing function of the same name and number of arguments, it is replaced. /// If there is an existing function of the same name and number of arguments, it is replaced.
#[cfg(not(feature = "no_function"))]
pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) -> &mut Self { pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) -> &mut Self {
// None + function name + number of arguments. // None + function name + number of arguments.
let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty()); let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty());
@ -1003,7 +999,6 @@ impl Module {
} }
/// Get an iterator to the functions in the module. /// Get an iterator to the functions in the module.
#[cfg(not(feature = "no_function"))]
pub(crate) fn iter_fn( pub(crate) fn iter_fn(
&self, &self,
) -> impl Iterator<Item = &(String, FnAccess, StaticVec<TypeId>, Func)> { ) -> impl Iterator<Item = &(String, FnAccess, StaticVec<TypeId>, Func)> {
@ -1519,7 +1514,7 @@ mod stat {
self.0 self.0
.get(path) .get(path)
.cloned() .cloned()
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(path.into(), pos))) .ok_or_else(|| EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
} }
} }
} }
@ -1599,10 +1594,7 @@ mod collection {
} }
} }
Err(Box::new(EvalAltResult::ErrorModuleNotFound( EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()
path.into(),
pos,
)))
} }
} }
} }

View File

@ -7,13 +7,10 @@ use crate::engine::{
}; };
use crate::fn_native::FnPtr; use crate::fn_native::FnPtr;
use crate::module::Module; use crate::module::Module;
use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST}; use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST};
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::utils::StaticVec; use crate::utils::StaticVec;
#[cfg(not(feature = "no_function"))]
use crate::parser::ReturnType;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
use crate::parser::CustomExpr; use crate::parser::CustomExpr;
@ -46,10 +43,12 @@ impl OptimizationLevel {
self == Self::None self == Self::None
} }
/// Is the `OptimizationLevel` Simple. /// Is the `OptimizationLevel` Simple.
#[cfg(not(feature = "no_optimize"))]
pub fn is_simple(self) -> bool { pub fn is_simple(self) -> bool {
self == Self::Simple self == Self::Simple
} }
/// Is the `OptimizationLevel` Full. /// Is the `OptimizationLevel` Full.
#[cfg(not(feature = "no_optimize"))]
pub fn is_full(self) -> bool { pub fn is_full(self) -> bool {
self == Self::Full self == Self::Full
} }

View File

@ -17,7 +17,6 @@ use num_traits::{
use num_traits::float::Float; use num_traits::float::Float;
use crate::stdlib::{ use crate::stdlib::{
boxed::Box,
fmt::Display, fmt::Display,
format, format,
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub},
@ -26,28 +25,31 @@ use crate::stdlib::{
// Checked add // Checked add
pub fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> { pub fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
x.checked_add(&y).ok_or_else(|| { x.checked_add(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Addition overflow: {} + {}", x, y), format!("Addition overflow: {} + {}", x, y),
Position::none(), Position::none(),
)) )
.into()
}) })
} }
// Checked subtract // Checked subtract
pub fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> { pub fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
x.checked_sub(&y).ok_or_else(|| { x.checked_sub(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Subtraction underflow: {} - {}", x, y), format!("Subtraction underflow: {} - {}", x, y),
Position::none(), Position::none(),
)) )
.into()
}) })
} }
// Checked multiply // Checked multiply
pub fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> { pub fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
x.checked_mul(&y).ok_or_else(|| { x.checked_mul(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Multiplication overflow: {} * {}", x, y), format!("Multiplication overflow: {} * {}", x, y),
Position::none(), Position::none(),
)) )
.into()
}) })
} }
// Checked divide // Checked divide
@ -57,26 +59,26 @@ where
{ {
// Detect division by zero // Detect division by zero
if y == T::zero() { if y == T::zero() {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return EvalAltResult::ErrorArithmetic(
format!("Division by zero: {} / {}", x, y), format!("Division by zero: {} / {}", x, y),
Position::none(), Position::none(),
))); )
.into();
} }
x.checked_div(&y).ok_or_else(|| { x.checked_div(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Division overflow: {} / {}", x, y), format!("Division overflow: {} / {}", x, y),
Position::none(), Position::none(),
)) )
.into()
}) })
} }
// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX // Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
pub fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> { pub fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
x.checked_neg().ok_or_else(|| { x.checked_neg().ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none())
format!("Negation overflow: -{}", x), .into()
Position::none(),
))
}) })
} }
// Checked absolute // Checked absolute
@ -87,10 +89,8 @@ pub fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> FuncReturn<T> {
Ok(x) Ok(x)
} else { } else {
x.checked_neg().ok_or_else(|| { x.checked_neg().ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(format!("Negation overflow: -{}", x), Position::none())
format!("Negation overflow: -{}", x), .into()
Position::none(),
))
}) })
} }
} }
@ -140,34 +140,38 @@ fn binary_xor<T: BitXor>(x: T, y: T) -> FuncReturn<<T as BitXor>::Output> {
pub fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> { pub fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
// Cannot shift by a negative number of bits // Cannot shift by a negative number of bits
if y < 0 { if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return EvalAltResult::ErrorArithmetic(
format!("Left-shift by a negative number: {} << {}", x, y), format!("Left-shift by a negative number: {} << {}", x, y),
Position::none(), Position::none(),
))); )
.into();
} }
CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| { CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Left-shift by too many bits: {} << {}", x, y), format!("Left-shift by too many bits: {} << {}", x, y),
Position::none(), Position::none(),
)) )
.into()
}) })
} }
// Checked right-shift // Checked right-shift
pub fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> { pub fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
// Cannot shift by a negative number of bits // Cannot shift by a negative number of bits
if y < 0 { if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return EvalAltResult::ErrorArithmetic(
format!("Right-shift by a negative number: {} >> {}", x, y), format!("Right-shift by a negative number: {} >> {}", x, y),
Position::none(), Position::none(),
))); )
.into();
} }
CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| { CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Right-shift by too many bits: {} % {}", x, y), format!("Right-shift by too many bits: {} % {}", x, y),
Position::none(), Position::none(),
)) )
.into()
}) })
} }
// Unchecked left-shift - may panic if shifting by a negative number of bits // Unchecked left-shift - may panic if shifting by a negative number of bits
@ -181,10 +185,11 @@ pub fn shr_u<T: Shr<T>>(x: T, y: T) -> FuncReturn<<T as Shr<T>>::Output> {
// Checked modulo // Checked modulo
pub fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> { pub fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> {
x.checked_rem(&y).ok_or_else(|| { x.checked_rem(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Modulo division by zero or overflow: {} % {}", x, y), format!("Modulo division by zero or overflow: {} % {}", x, y),
Position::none(), Position::none(),
)) )
.into()
}) })
} }
// Unchecked modulo - may panic if dividing by zero // Unchecked modulo - may panic if dividing by zero
@ -195,35 +200,40 @@ fn modulo_u<T: Rem>(x: T, y: T) -> FuncReturn<<T as Rem>::Output> {
pub fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> { pub fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
if cfg!(not(feature = "only_i32")) { if cfg!(not(feature = "only_i32")) {
if y > (u32::MAX as INT) { if y > (u32::MAX as INT) {
Err(Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Integer raised to too large an index: {} ~ {}", x, y), format!("Integer raised to too large an index: {} ~ {}", x, y),
Position::none(), Position::none(),
))) )
.into()
} else if y < 0 { } else if y < 0 {
Err(Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Integer raised to a negative index: {} ~ {}", x, y), format!("Integer raised to a negative index: {} ~ {}", x, y),
Position::none(), Position::none(),
))) )
.into()
} else { } else {
x.checked_pow(y as u32).ok_or_else(|| { x.checked_pow(y as u32).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y), format!("Power overflow: {} ~ {}", x, y),
Position::none(), Position::none(),
)) )
.into()
}) })
} }
} else { } else {
if y < 0 { if y < 0 {
Err(Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Integer raised to a negative index: {} ~ {}", x, y), format!("Integer raised to a negative index: {} ~ {}", x, y),
Position::none(), Position::none(),
))) )
.into()
} else { } else {
x.checked_pow(y as u32).ok_or_else(|| { x.checked_pow(y as u32).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y), format!("Power overflow: {} ~ {}", x, y),
Position::none(), Position::none(),
)) )
.into()
}) })
} }
} }
@ -242,10 +252,11 @@ pub fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn<FLOAT> {
pub fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> { pub fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
// Raise to power that is larger than an i32 // Raise to power that is larger than an i32
if y > (i32::MAX as INT) { if y > (i32::MAX as INT) {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return EvalAltResult::ErrorArithmetic(
format!("Number raised to too large an index: {} ~ {}", x, y), format!("Number raised to too large an index: {} ~ {}", x, y),
Position::none(), Position::none(),
))); )
.into();
} }
Ok(x.powi(y as i32)) Ok(x.powi(y as i32))

View File

@ -43,12 +43,13 @@ fn pad<T: Variant + Clone>(
&& len > 0 && len > 0
&& (len as usize) > _engine.limits.max_array_size && (len as usize) > _engine.limits.max_array_size
{ {
return Err(Box::new(EvalAltResult::ErrorDataTooLarge( return EvalAltResult::ErrorDataTooLarge(
"Size of array".to_string(), "Size of array".to_string(),
_engine.limits.max_array_size, _engine.limits.max_array_size,
len as usize, len as usize,
Position::none(), Position::none(),
))); )
.into();
} }
if len > 0 { if len > 0 {

View File

@ -12,7 +12,7 @@ use crate::{result::EvalAltResult, token::Position};
use num_traits::float::Float; use num_traits::float::Float;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::stdlib::{boxed::Box, format}; use crate::stdlib::format;
#[allow(dead_code)] #[allow(dead_code)]
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]
@ -109,10 +109,10 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
"to_int", "to_int",
|x: f32| { |x: f32| {
if x > (MAX_INT as f32) { if x > (MAX_INT as f32) {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return EvalAltResult::ErrorArithmetic(
format!("Integer overflow: to_int({})", x), format!("Integer overflow: to_int({})", x),
Position::none(), Position::none(),
))); ).into();
} }
Ok(x.trunc() as INT) Ok(x.trunc() as INT)
@ -122,10 +122,10 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
"to_int", "to_int",
|x: FLOAT| { |x: FLOAT| {
if x > (MAX_INT as FLOAT) { if x > (MAX_INT as FLOAT) {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return EvalAltResult::ErrorArithmetic(
format!("Integer overflow: to_int({})", x), format!("Integer overflow: to_int({})", x),
Position::none(), Position::none(),
))); ).into();
} }
Ok(x.trunc() as INT) Ok(x.trunc() as INT)

View File

@ -231,12 +231,12 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
// Check if string will be over max size limit // Check if string will be over max size limit
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if _engine.limits.max_string_size > 0 && len > 0 && (len as usize) > _engine.limits.max_string_size { if _engine.limits.max_string_size > 0 && len > 0 && (len as usize) > _engine.limits.max_string_size {
return Err(Box::new(EvalAltResult::ErrorDataTooLarge( return EvalAltResult::ErrorDataTooLarge(
"Length of string".to_string(), "Length of string".to_string(),
_engine.limits.max_string_size, _engine.limits.max_string_size,
len as usize, len as usize,
Position::none(), Position::none(),
))); ).into();
} }
if len > 0 { if len > 0 {
@ -254,12 +254,12 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if _engine.limits.max_string_size > 0 && s.len() > _engine.limits.max_string_size { if _engine.limits.max_string_size > 0 && s.len() > _engine.limits.max_string_size {
return Err(Box::new(EvalAltResult::ErrorDataTooLarge( return EvalAltResult::ErrorDataTooLarge(
"Length of string".to_string(), "Length of string".to_string(),
_engine.limits.max_string_size, _engine.limits.max_string_size,
s.len(), s.len(),
Position::none(), Position::none(),
))); ).into();
} }
} }
} }

View File

@ -41,13 +41,13 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if seconds > (MAX_INT as u64) { if seconds > (MAX_INT as u64) {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return EvalAltResult::ErrorArithmetic(
format!( format!(
"Integer overflow for timestamp duration: {}", "Integer overflow for timestamp duration: {}",
-(seconds as i64) -(seconds as i64)
), ),
Position::none(), Position::none(),
))); ).into();
} }
return Ok(-(seconds as INT)); return Ok(-(seconds as INT));
@ -62,10 +62,10 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if seconds > (MAX_INT as u64) { if seconds > (MAX_INT as u64) {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return EvalAltResult::ErrorArithmetic(
format!("Integer overflow for timestamp duration: {}", seconds), format!("Integer overflow for timestamp duration: {}", seconds),
Position::none(), Position::none(),
))); ).into();
} }
return Ok(seconds as INT); return Ok(seconds as INT);
@ -92,10 +92,10 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if seconds > (MAX_INT as u64) { if seconds > (MAX_INT as u64) {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return EvalAltResult::ErrorArithmetic(
format!("Integer overflow for timestamp.elapsed: {}", seconds), format!("Integer overflow for timestamp.elapsed: {}", seconds),
Position::none(), Position::none(),
))); ).into();
} }
Ok(seconds as INT) Ok(seconds as INT)

View File

@ -2,9 +2,7 @@
use crate::any::{Dynamic, Union}; use crate::any::{Dynamic, Union};
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{ use crate::engine::{Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
Engine, KEYWORD_FN_PTR_CURRY, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT,
};
use crate::error::{LexError, ParseError, ParseErrorType}; use crate::error::{LexError, ParseError, ParseErrorType};
use crate::fn_native::{FnPtr, Shared}; use crate::fn_native::{FnPtr, Shared};
use crate::module::{Module, ModuleRef}; use crate::module::{Module, ModuleRef};
@ -15,7 +13,7 @@ use crate::token::{is_keyword_function, is_valid_identifier, Position, Token, To
use crate::utils::{StaticVec, StraightHasherBuilder}; use crate::utils::{StaticVec, StraightHasherBuilder};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::engine::FN_ANONYMOUS; use crate::engine::{FN_ANONYMOUS, KEYWORD_FN_PTR_CURRY};
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::{make_getter, make_setter}; use crate::engine::{make_getter, make_setter};
@ -570,7 +568,7 @@ pub enum Stmt {
ReturnWithVal(Box<((ReturnType, Position), Option<Expr>, Position)>), ReturnWithVal(Box<((ReturnType, Position), Option<Expr>, Position)>),
/// import expr as module /// import expr as module
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Import(Box<(Expr, (String, Position), Position)>), Import(Box<(Expr, Option<(String, Position)>, Position)>),
/// expr id as name, ... /// expr id as name, ...
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Export( Export(
@ -2687,14 +2685,8 @@ fn parse_import(
let expr = parse_expr(input, state, lib, settings.level_up())?; let expr = parse_expr(input, state, lib, settings.level_up())?;
// import expr as ... // import expr as ...
match input.next().unwrap() { if !match_token(input, Token::As)? {
(Token::As, _) => (), return Ok(Stmt::Import(Box::new((expr, None, token_pos))));
(_, pos) => {
return Err(
PERR::MissingToken(Token::As.into(), "in this import statement".into())
.into_err(pos),
)
}
} }
// import expr as name ... // import expr as name ...
@ -2711,7 +2703,7 @@ fn parse_import(
Ok(Stmt::Import(Box::new(( Ok(Stmt::Import(Box::new((
expr, expr,
(name, settings.pos), Some((name, settings.pos)),
token_pos, token_pos,
)))) ))))
} }
@ -3135,6 +3127,7 @@ fn parse_fn(
} }
/// Creates a curried expression from a list of external variables /// Creates a curried expression from a list of external variables
#[cfg(not(feature = "no_function"))]
fn make_curry_from_externals( fn make_curry_from_externals(
fn_expr: Expr, fn_expr: Expr,
externals: StaticVec<(String, Position)>, externals: StaticVec<(String, Position)>,

View File

@ -361,3 +361,9 @@ impl EvalAltResult {
self self
} }
} }
impl<T> From<EvalAltResult> for Result<T, Box<EvalAltResult>> {
fn from(err: EvalAltResult) -> Self {
Err(err.into())
}
}

View File

@ -53,11 +53,12 @@ impl<'de> DynamicDeserializer<'de> {
} }
/// Shortcut for a type conversion error. /// Shortcut for a type conversion error.
fn type_error_str<T>(&self, error: &str) -> Result<T, Box<EvalAltResult>> { fn type_error_str<T>(&self, error: &str) -> Result<T, Box<EvalAltResult>> {
Err(Box::new(EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
error.into(), error.into(),
self.value.type_name().into(), self.value.type_name().into(),
Position::none(), Position::none(),
))) )
.into()
} }
fn deserialize_int<V: Visitor<'de>>( fn deserialize_int<V: Visitor<'de>>(
&mut self, &mut self,
@ -134,10 +135,7 @@ pub fn from_dynamic<'de, T: Deserialize<'de>>(
impl Error for Box<EvalAltResult> { impl Error for Box<EvalAltResult> {
fn custom<T: fmt::Display>(err: T) -> Self { fn custom<T: fmt::Display>(err: T) -> Self {
Box::new(EvalAltResult::ErrorParsing( EvalAltResult::ErrorParsing(ParseErrorType::BadInput(err.to_string()), Position::none())
ParseErrorType::BadInput(err.to_string()),
Position::none(),
))
} }
} }

View File

@ -99,10 +99,7 @@ pub fn to_dynamic<T: Serialize>(value: T) -> Result<Dynamic, Box<EvalAltResult>>
impl Error for Box<EvalAltResult> { impl Error for Box<EvalAltResult> {
fn custom<T: fmt::Display>(err: T) -> Self { fn custom<T: fmt::Display>(err: T) -> Self {
Box::new(EvalAltResult::ErrorRuntime( EvalAltResult::ErrorRuntime(err.to_string(), Position::none())
err.to_string(),
Position::none(),
))
} }
} }
@ -298,22 +295,24 @@ impl Serializer for &mut DynamicSerializer {
make_variant(_variant, content) make_variant(_variant, content)
} }
#[cfg(feature = "no_object")] #[cfg(feature = "no_object")]
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( return EvalAltResult::ErrorMismatchOutputType(
"Dynamic".into(), "Dynamic".into(),
"map".into(), "map".into(),
Position::none(), Position::none(),
))); )
.into();
} }
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Box<EvalAltResult>> { fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
return Ok(DynamicSerializer::new(Array::new().into())); return Ok(DynamicSerializer::new(Array::new().into()));
#[cfg(feature = "no_index")] #[cfg(feature = "no_index")]
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( return EvalAltResult::ErrorMismatchOutputType(
"Dynamic".into(), "Dynamic".into(),
"array".into(), "array".into(),
Position::none(), Position::none(),
))); )
.into();
} }
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Box<EvalAltResult>> { fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Box<EvalAltResult>> {
@ -346,11 +345,12 @@ impl Serializer for &mut DynamicSerializer {
let err_type = "map"; let err_type = "map";
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
let err_type = "array"; let err_type = "array";
Err(Box::new(EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
"Dynamic".into(), "Dynamic".into(),
err_type.into(), err_type.into(),
Position::none(), Position::none(),
))) )
.into()
} }
} }
@ -358,11 +358,12 @@ impl Serializer for &mut DynamicSerializer {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
return Ok(DynamicSerializer::new(Map::new().into())); return Ok(DynamicSerializer::new(Map::new().into()));
#[cfg(feature = "no_object")] #[cfg(feature = "no_object")]
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( return EvalAltResult::ErrorMismatchOutputType(
"Dynamic".into(), "Dynamic".into(),
"map".into(), "map".into(),
Position::none(), Position::none(),
))); )
.into();
} }
fn serialize_struct( fn serialize_struct(
@ -386,11 +387,12 @@ impl Serializer for &mut DynamicSerializer {
map: Map::with_capacity(_len), map: Map::with_capacity(_len),
}); });
#[cfg(feature = "no_object")] #[cfg(feature = "no_object")]
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( return EvalAltResult::ErrorMismatchOutputType(
"Dynamic".into(), "Dynamic".into(),
"map".into(), "map".into(),
Position::none(), Position::none(),
))); )
.into();
} }
} }
@ -499,11 +501,11 @@ impl SerializeMap for DynamicSerializer {
let key = mem::take(&mut self._key) let key = mem::take(&mut self._key)
.take_immutable_string() .take_immutable_string()
.map_err(|typ| { .map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
"string".into(), "string".into(),
typ.into(), typ.into(),
Position::none(), Position::none(),
)) )
})?; })?;
let _value = _value.serialize(&mut *self)?; let _value = _value.serialize(&mut *self)?;
let map = self._value.downcast_mut::<Map>().unwrap(); let map = self._value.downcast_mut::<Map>().unwrap();
@ -523,11 +525,11 @@ impl SerializeMap for DynamicSerializer {
{ {
let _key: Dynamic = _key.serialize(&mut *self)?; let _key: Dynamic = _key.serialize(&mut *self)?;
let _key = _key.take_immutable_string().map_err(|typ| { let _key = _key.take_immutable_string().map_err(|typ| {
Box::new(EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
"string".into(), "string".into(),
typ.into(), typ.into(),
Position::none(), Position::none(),
)) )
})?; })?;
let _value = _value.serialize(&mut *self)?; let _value = _value.serialize(&mut *self)?;
let map = self._value.downcast_mut::<Map>().unwrap(); let map = self._value.downcast_mut::<Map>().unwrap();

View File

@ -20,11 +20,12 @@ impl<'a> ImmutableStringDeserializer<'a> {
} }
/// Shortcut for a type conversion error. /// Shortcut for a type conversion error.
fn type_error<T>(&self) -> Result<T, Box<EvalAltResult>> { fn type_error<T>(&self) -> Result<T, Box<EvalAltResult>> {
Err(Box::new(EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
type_name::<T>().into(), type_name::<T>().into(),
"string".into(), "string".into(),
Position::none(), Position::none(),
))) )
.into()
} }
} }

View File

@ -1,13 +1,15 @@
//! Configuration settings for `Engine`. //! Configuration settings for `Engine`.
use crate::engine::Engine; use crate::engine::Engine;
use crate::optimize::OptimizationLevel;
use crate::packages::PackageLibrary; use crate::packages::PackageLibrary;
use crate::token::{is_valid_identifier, Token}; use crate::token::{is_valid_identifier, Token};
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
use crate::module::ModuleResolver; use crate::module::ModuleResolver;
#[cfg(not(feature = "no_optimize"))]
use crate::optimize::OptimizationLevel;
use crate::stdlib::{format, string::String}; use crate::stdlib::{format, string::String};
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]

View File

@ -100,7 +100,7 @@ impl Engine {
) -> Result<Dynamic, Box<EvalAltResult>> ) -> Result<Dynamic, Box<EvalAltResult>>
+ SendSync + SendSync
+ 'static, + 'static,
) -> Result<&mut Self, Box<ParseError>> { ) -> Result<&mut Self, ParseError> {
let mut segments: StaticVec<_> = Default::default(); let mut segments: StaticVec<_> = Default::default();
for s in keywords { for s in keywords {

View File

@ -680,6 +680,7 @@ impl Token {
} }
/// Convert a token into a function name, if possible. /// Convert a token into a function name, if possible.
#[cfg(not(feature = "no_function"))]
pub(crate) fn into_function_name_for_override(self) -> Result<String, Self> { pub(crate) fn into_function_name_for_override(self) -> Result<String, Self> {
match self { match self {
Self::Reserved(s) if can_override_keyword(&s) => Ok(s), Self::Reserved(s) if can_override_keyword(&s) => Ok(s),
@ -1442,6 +1443,7 @@ pub fn is_keyword_function(name: &str) -> bool {
} }
/// Can this keyword be overridden as a function? /// Can this keyword be overridden as a function?
#[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
pub fn can_override_keyword(name: &str) -> bool { pub fn can_override_keyword(name: &str) -> bool {
match name { match name {

View File

@ -83,6 +83,7 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
#[test] #[test]
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "sync"))]
fn test_closures_data_race() -> Result<(), Box<EvalAltResult>> { fn test_closures_data_race() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();

View File

@ -5,16 +5,16 @@ use std::sync::{Arc, Mutex, RwLock};
/// Simulate a command object. /// Simulate a command object.
struct Command { struct Command {
/// Simulate an external state. /// Simulate an external state.
state: i64, state: INT,
} }
impl Command { impl Command {
/// Do some action. /// Do some action.
pub fn action(&mut self, val: i64) { pub fn action(&mut self, val: INT) {
self.state = val; self.state = val;
} }
/// Get current value. /// Get current value.
pub fn get(&self) -> i64 { pub fn get(&self) -> INT {
self.state self.state
} }
} }
@ -39,7 +39,7 @@ fn test_side_effects_command() -> Result<(), Box<EvalAltResult>> {
// Register type. // Register type.
engine.register_type_with_name::<API>("CommandType"); engine.register_type_with_name::<API>("CommandType");
engine.register_fn("action", |api: &mut API, x: i64| { engine.register_fn("action", |api: &mut API, x: INT| {
let mut command = api.lock().unwrap(); let mut command = api.lock().unwrap();
let val = command.get(); let val = command.get();
command.action(val + x); command.action(val + x);

View File

@ -1,5 +1,5 @@
#![cfg(not(feature = "unchecked"))] #![cfg(not(feature = "unchecked"))]
use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType}; use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, INT};
#[test] #[test]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -7,7 +7,7 @@ fn test_stack_overflow_fn_calls() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
assert_eq!( assert_eq!(
engine.eval::<i64>( engine.eval::<INT>(
r" r"
fn foo(n) { if n <= 1 { 0 } else { n + foo(n-1) } } fn foo(n) { if n <= 1 { 0 } else { n + foo(n-1) } }
foo(8) foo(8)

View File

@ -1,6 +1,4 @@
use rhai::{ use rhai::{Engine, EvalAltResult, EvalContext, Expression, ParseErrorType, Scope, INT};
Engine, EvalAltResult, EvalContext, Expression, ParseError, ParseErrorType, Scope, INT,
};
#[test] #[test]
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> { fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
@ -19,43 +17,35 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
ParseErrorType::Reserved(err) if err == "while" ParseErrorType::Reserved(err) if err == "while"
)); ));
engine engine.register_custom_syntax(
.register_custom_syntax( &[
&[ "do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$",
"do", "|", "$ident$", "|", "->", "$block$", "while", "$expr$", ],
], 1,
1, |engine: &Engine, context: &mut EvalContext, scope: &mut Scope, inputs: &[Expression]| {
|engine: &Engine, let var_name = inputs[0].get_variable_name().unwrap().to_string();
context: &mut EvalContext, let stmt = inputs.get(1).unwrap();
scope: &mut Scope, let expr = inputs.get(2).unwrap();
inputs: &[Expression]| {
let var_name = inputs[0].get_variable_name().unwrap().to_string();
let stmt = inputs.get(1).unwrap();
let expr = inputs.get(2).unwrap();
scope.push(var_name, 0 as INT); scope.push(var_name, 0 as INT);
loop { loop {
engine.eval_expression_tree(context, scope, stmt)?; engine.eval_expression_tree(context, scope, stmt)?;
if !engine if !engine
.eval_expression_tree(context, scope, expr)? .eval_expression_tree(context, scope, expr)?
.as_bool() .as_bool()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch( EvalAltResult::ErrorBooleanArgMismatch("do-while".into(), expr.position())
"do-while".into(), })?
expr.position(), {
) break;
})?
{
break;
}
} }
}
Ok(().into()) Ok(().into())
}, },
) )?;
.unwrap();
// 'while' is now a custom keyword so this it can no longer be a variable // 'while' is now a custom keyword so this it can no longer be a variable
engine.consume("let while = 0").expect_err("should error"); engine.consume("let while = 0").expect_err("should error");
@ -71,10 +61,13 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
); );
// The first symbol must be an identifier // The first symbol must be an identifier
assert!(matches!( assert_eq!(
*engine.register_custom_syntax(&["!"], 0, |_, _, _, _| Ok(().into())).expect_err("should error"), *engine
ParseError(err, _) if *err == ParseErrorType::BadInput("Improper symbol for custom syntax: '!'".to_string()) .register_custom_syntax(&["!"], 0, |_, _, _, _| Ok(().into()))
)); .expect_err("should error")
.0,
ParseErrorType::BadInput("Improper symbol for custom syntax: '!'".to_string())
);
Ok(()) Ok(())
} }