commit
e5584b2471
208
README.md
208
README.md
@ -13,20 +13,25 @@ to add scripting to any application.
|
|||||||
|
|
||||||
Rhai's current features set:
|
Rhai's current features set:
|
||||||
|
|
||||||
* Easy-to-use language similar to JS+Rust
|
* Easy-to-use language similar to JS+Rust with dynamic typing but _no_ garbage collector.
|
||||||
* Easy integration with Rust [native functions](#working-with-functions) and [types](#custom-types-and-methods),
|
* Tight integration with native Rust [functions](#working-with-functions) and [types](#custom-types-and-methods),
|
||||||
including [getters/setters](#getters-and-setters), [methods](#members-and-methods) and [indexers](#indexers)
|
including [getters/setters](#getters-and-setters), [methods](#members-and-methods) and [indexers](#indexers).
|
||||||
* Easily [call a script-defined function](#calling-rhai-functions-from-rust) from Rust
|
* Freely pass Rust variables/constants into a script via an external [`Scope`].
|
||||||
* Freely pass variables/constants into a script via an external [`Scope`]
|
* Easily [call a script-defined function](#calling-rhai-functions-from-rust) from Rust.
|
||||||
* Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop)
|
* Low compile-time overhead (~0.6 sec debug/~3 sec release for `rhai_runner` sample app).
|
||||||
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
|
* Fairly efficient evaluation (1 million iterations in 0.75 sec on my 5 year old laptop).
|
||||||
* Relatively little `unsafe` code (yes there are some for performance reasons)
|
* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to
|
||||||
* [`no-std`](#optional-features) support
|
one single source file, all with names starting with `"unsafe_"`).
|
||||||
* [Function overloading](#function-overloading)
|
* Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature).
|
||||||
* [Operator overloading](#operator-overloading)
|
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment without explicit permission.
|
||||||
* [Modules]
|
* Rugged (protection against [stack-overflow](#maximum-stack-depth) and [runaway scripts](#maximum-number-of-operations) etc.).
|
||||||
* Compiled script is [optimized](#script-optimization) for repeat evaluations
|
* Track script evaluation [progress](#tracking-progress) and manually terminate a script run.
|
||||||
* Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features)
|
* [`no-std`](#optional-features) support.
|
||||||
|
* [Function overloading](#function-overloading).
|
||||||
|
* [Operator overloading](#operator-overloading).
|
||||||
|
* Organize code base with dynamically-loadable [Modules].
|
||||||
|
* Compiled script is [optimized](#script-optimization) for repeated evaluations.
|
||||||
|
* Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features).
|
||||||
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
|
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
|
||||||
to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are
|
to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are
|
||||||
pulled in to provide for functionalities that used to be in `std`.
|
pulled in to provide for functionalities that used to be in `std`.
|
||||||
@ -63,23 +68,24 @@ Beware that in order to use pre-releases (e.g. alpha and beta), the exact versio
|
|||||||
Optional features
|
Optional features
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
| Feature | Description |
|
| Feature | Description |
|
||||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
|
| `unchecked` | Exclude arithmetic checking (such as over-flows and division by zero), stack depth limit and operations count limit. Beware that a bad script may panic the entire system! |
|
||||||
| `no_function` | Disable script-defined functions if not needed. |
|
| `no_function` | Disable script-defined functions. |
|
||||||
| `no_index` | Disable [arrays] and indexing features if not needed. |
|
| `no_index` | Disable [arrays] and indexing features. |
|
||||||
| `no_object` | Disable support for custom types and objects. |
|
| `no_object` | Disable support for custom types and object maps. |
|
||||||
| `no_float` | Disable floating-point numbers and math if not needed. |
|
| `no_float` | Disable floating-point numbers and math. |
|
||||||
| `no_optimize` | Disable the script optimizer. |
|
| `no_optimize` | Disable the script optimizer. |
|
||||||
| `no_module` | Disable modules. |
|
| `no_module` | Disable modules. |
|
||||||
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
|
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
|
||||||
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
|
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
|
||||||
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||||
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Engine`], [`Scope`] and `AST` are all `Send + Sync`. |
|
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and `AST`, are all `Send + Sync`. |
|
||||||
|
|
||||||
By default, Rhai includes all the standard functionalities in a small, tight package.
|
By default, Rhai includes all the standard functionalities in a small, tight package.
|
||||||
Most features are here to opt-**out** of certain functionalities that are not needed.
|
Most features are here to opt-**out** of certain functionalities that are not needed.
|
||||||
Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language.
|
Excluding unneeded functionalities can result in smaller, faster builds
|
||||||
|
as well as more control over what a script can (or cannot) do.
|
||||||
|
|
||||||
[`unchecked`]: #optional-features
|
[`unchecked`]: #optional-features
|
||||||
[`no_index`]: #optional-features
|
[`no_index`]: #optional-features
|
||||||
@ -105,7 +111,7 @@ requiring more CPU cycles to complete.
|
|||||||
|
|
||||||
Also, turning on `no_float`, and `only_i32` makes the key [`Dynamic`] data type only 8 bytes small on 32-bit targets
|
Also, 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`.
|
||||||
Making [`Dynamic`] small helps performance due to more caching efficiency.
|
Making [`Dynamic`] small helps performance due to better cache efficiency.
|
||||||
|
|
||||||
### Minimal builds
|
### Minimal builds
|
||||||
|
|
||||||
@ -114,6 +120,8 @@ the correct linker flags are used in `cargo.toml`:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
lto = "fat" # turn on Link-Time Optimizations
|
||||||
|
codegen-units = 1 # trade compile time with maximum optimization
|
||||||
opt-level = "z" # optimize for size
|
opt-level = "z" # optimize for size
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -269,7 +277,7 @@ let ast = engine.compile_file("hello_world.rhai".into())?;
|
|||||||
|
|
||||||
### Calling Rhai functions from Rust
|
### Calling Rhai functions from Rust
|
||||||
|
|
||||||
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust via `call_fn`.
|
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust via `Engine::call_fn`.
|
||||||
Functions declared with `private` are hidden and cannot be called from Rust (see also [modules]).
|
Functions declared with `private` are hidden and cannot be called from Rust (see also [modules]).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -372,7 +380,7 @@ Use `Engine::new_raw` to create a _raw_ `Engine`, in which _nothing_ is added, n
|
|||||||
|
|
||||||
### Packages
|
### Packages
|
||||||
|
|
||||||
Rhai functional features are provided in different _packages_ that can be loaded via a call to `load_package`.
|
Rhai functional features are provided in different _packages_ that can be loaded via a call to `Engine::load_package`.
|
||||||
Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package` must be loaded in order for
|
Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package` must be loaded in order for
|
||||||
packages to be used.
|
packages to be used.
|
||||||
|
|
||||||
@ -411,7 +419,7 @@ Therefore, a package only has to be created _once_.
|
|||||||
|
|
||||||
Packages are actually implemented as [modules], so they share a lot of behavior and characteristics.
|
Packages are actually implemented as [modules], so they share a lot of behavior and characteristics.
|
||||||
The main difference is that a package loads under the _global_ namespace, while a module loads under its own
|
The main difference is that a package loads under the _global_ namespace, while a module loads under its own
|
||||||
namespace alias specified in an `import` statement (see also [modules]).
|
namespace alias specified in an [`import`] statement (see also [modules]).
|
||||||
A package is _static_ (i.e. pre-loaded into an [`Engine`]), while a module is _dynamic_ (i.e. loaded with
|
A package is _static_ (i.e. pre-loaded into an [`Engine`]), while a module is _dynamic_ (i.e. loaded with
|
||||||
the `import` statement).
|
the `import` statement).
|
||||||
|
|
||||||
@ -759,7 +767,7 @@ Because they [_short-circuit_](#boolean-operators), `&&` and `||` are handled sp
|
|||||||
overriding them has no effect at all.
|
overriding them has no effect at all.
|
||||||
|
|
||||||
Operator functions cannot be defined as a script function (because operators syntax are not valid function names).
|
Operator functions cannot be defined as a script function (because operators syntax are not valid function names).
|
||||||
However, operator functions _can_ be registered to the [`Engine`] via `register_fn`, `register_result_fn` etc.
|
However, operator functions _can_ be registered to the [`Engine`] via the methods `Engine::register_fn`, `Engine::register_result_fn` etc.
|
||||||
When a custom operator function is registered with the same name as an operator, it _overloads_ (or overrides) the built-in version.
|
When a custom operator function is registered with the same name as an operator, it _overloads_ (or overrides) the built-in version.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -963,7 +971,7 @@ Indexers
|
|||||||
--------
|
--------
|
||||||
|
|
||||||
Custom types can also expose an _indexer_ by registering an indexer function.
|
Custom types can also expose an _indexer_ by registering an indexer function.
|
||||||
A custom with an indexer function defined can use the bracket '`[]`' notation to get a property value
|
A custom type with an indexer function defined can use the bracket '`[]`' notation to get a property value
|
||||||
(but not update it - indexers are read-only).
|
(but not update it - indexers are read-only).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -2043,7 +2051,7 @@ debug("world!"); // prints "world!" to stdout using debug formatting
|
|||||||
### Overriding `print` and `debug` with callback functions
|
### Overriding `print` and `debug` with callback functions
|
||||||
|
|
||||||
When embedding Rhai into an application, it is usually necessary to trap `print` and `debug` output
|
When embedding Rhai into an application, it is usually necessary to trap `print` and `debug` output
|
||||||
(for logging into a tracking log, for example).
|
(for logging into a tracking log, for example) with the `Engine::on_print` and `Engine::on_debug` methods:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Any function or closure that takes an '&str' argument can be used to override
|
// Any function or closure that takes an '&str' argument can be used to override
|
||||||
@ -2107,6 +2115,8 @@ export x as answer; // the variable 'x' is exported under the alias 'ans
|
|||||||
|
|
||||||
### Importing modules
|
### Importing modules
|
||||||
|
|
||||||
|
[`import`]: #importing-modules
|
||||||
|
|
||||||
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 its members are accessed via '`::`' similar to C++.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -2237,7 +2247,7 @@ Built-in module resolvers are grouped under the `rhai::module_resolvers` module
|
|||||||
| `FileModuleResolver` | The default module resolution service, not available under the [`no_std`] feature. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. |
|
| `FileModuleResolver` | The default module resolution service, not available under the [`no_std`] feature. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. |
|
||||||
| `StaticModuleResolver` | Loads modules that are statically added. This can be used when the [`no_std`] feature is turned on. |
|
| `StaticModuleResolver` | Loads modules that are statically added. This can be used when the [`no_std`] feature is turned on. |
|
||||||
|
|
||||||
An [`Engine`]'s module resolver is set via a call to `set_module_resolver`:
|
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Use the 'StaticModuleResolver'
|
// Use the 'StaticModuleResolver'
|
||||||
@ -2248,6 +2258,128 @@ engine.set_module_resolver(Some(resolver));
|
|||||||
engine.set_module_resolver(None);
|
engine.set_module_resolver(None);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Ruggedization - protect against DoS attacks
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
For scripting systems open to user-land scripts, it is always best to limit the amount of resources used by a script
|
||||||
|
so that it does not consume more resources that it is allowed to.
|
||||||
|
|
||||||
|
The most important resources to watch out for are:
|
||||||
|
|
||||||
|
* **Memory**: A malignant script may continuously grow an [array] or [object map] until all memory is consumed.
|
||||||
|
* **CPU**: A malignant script may run an infinite tight loop that consumes all CPU cycles.
|
||||||
|
* **Time**: A malignant script may run indefinitely, thereby blocking the calling system which is waiting for a result.
|
||||||
|
* **Stack**: A malignant script may attempt an infinite recursive call that exhausts the call stack.
|
||||||
|
* **Overflows**: A malignant script may deliberately cause numeric over-flows and/or under-flows, divide by zero, and/or
|
||||||
|
create bad floating-point representations, in order to crash the system.
|
||||||
|
* **Files**: A malignant script may continuously [`import`] an external module within an infinite loop,
|
||||||
|
thereby putting heavy load on the file-system (or even the network if the file is not local).
|
||||||
|
Even when modules are not created from files, they still typically consume a lot of resources to load.
|
||||||
|
* **Data**: A malignant script may attempt to read from and/or write to data that it does not own. If this happens,
|
||||||
|
it is a severe security breach and may put the entire system at risk.
|
||||||
|
|
||||||
|
### Maximum number of operations
|
||||||
|
|
||||||
|
Rhai by default does not limit how much time or CPU a script consumes.
|
||||||
|
This can be changed via the `Engine::set_max_operations` method, with zero being unlimited (the default).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine.set_max_operations(500); // allow only up to 500 operations for this script
|
||||||
|
|
||||||
|
engine.set_max_operations(0); // allow unlimited operations
|
||||||
|
```
|
||||||
|
|
||||||
|
The concept of one single _operation_ in Rhai is volatile - it roughly equals one expression node,
|
||||||
|
loading one variable/constant, one operator call, one iteration of a loop, or one function call etc.
|
||||||
|
with sub-expressions, statements and function calls executed inside these contexts accumulated on top.
|
||||||
|
A good rule-of-thumb is that one simple non-trivial expression consumes on average 5-10 operations.
|
||||||
|
|
||||||
|
One _operation_ can take an unspecified amount of time and real CPU cycles, depending on the particulars.
|
||||||
|
For example, loading a constant consumes very few CPU cycles, while calling an external Rust function,
|
||||||
|
though also counted as only one operation, may consume much more computing resources.
|
||||||
|
If it helps to visualize, think of an _operation_ as roughly equals to one _instruction_ of a hypothetical CPU.
|
||||||
|
|
||||||
|
The _operation count_ is intended to be a very course-grained measurement of the amount of CPU that a script
|
||||||
|
is consuming, and allows the system to impose a hard upper limit.
|
||||||
|
|
||||||
|
A script exceeding the maximum operations count will terminate with an error result.
|
||||||
|
This check can be disabled via the [`unchecked`] feature for higher performance
|
||||||
|
(but higher risks as well).
|
||||||
|
|
||||||
|
### Tracking progress
|
||||||
|
|
||||||
|
To track script evaluation progress and to force-terminate a script prematurely (for any reason),
|
||||||
|
provide a closure to the `Engine::on_progress` method:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine.on_progress(|count| { // 'count' is the number of operations performed
|
||||||
|
if count % 1000 == 0 {
|
||||||
|
println!("{}", count); // print out a progress log every 1,000 operations
|
||||||
|
}
|
||||||
|
true // return 'true' to continue the script
|
||||||
|
// returning 'false' will terminate the script
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The closure passed to `Engine::on_progress` will be called once every operation.
|
||||||
|
Return `false` to terminate the script immediately.
|
||||||
|
|
||||||
|
### Maximum number of modules
|
||||||
|
|
||||||
|
Rhai by default does not limit how many [modules] are loaded via the [`import`] statement.
|
||||||
|
This can be changed via the `Engine::set_max_modules` method, with zero being unlimited (the default).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine.set_max_modules(5); // allow loading only up to 5 modules
|
||||||
|
|
||||||
|
engine.set_max_modules(0); // allow unlimited modules
|
||||||
|
```
|
||||||
|
|
||||||
|
### Maximum stack depth
|
||||||
|
|
||||||
|
Rhai by default limits function calls to a maximum depth of 256 levels (28 levels in debug build).
|
||||||
|
This limit may be changed via the `Engine::set_max_call_levels` method.
|
||||||
|
The limit can be disabled via the [`unchecked`] feature for higher performance
|
||||||
|
(but higher risks as well).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine.set_max_call_levels(10); // allow only up to 10 levels of function calls
|
||||||
|
|
||||||
|
engine.set_max_call_levels(0); // allow no function calls at all (max depth = zero)
|
||||||
|
```
|
||||||
|
|
||||||
|
A script exceeding the maximum call stack depth will terminate with an error result.
|
||||||
|
|
||||||
|
### Checked arithmetic
|
||||||
|
|
||||||
|
All arithmetic calculations in Rhai are _checked_, meaning that the script terminates with an error whenever
|
||||||
|
it detects a numeric over-flow/under-flow condition or an invalid floating-point operation, instead of
|
||||||
|
crashing the entire system. This checking can be turned off via the [`unchecked`] feature for higher performance
|
||||||
|
(but higher risks as well).
|
||||||
|
|
||||||
|
### Blocking access to external data
|
||||||
|
|
||||||
|
Rhai is _sand-boxed_ so a script can never read from outside its own environment.
|
||||||
|
Furthermore, an [`Engine`] created non-`mut` cannot mutate any state outside of itself;
|
||||||
|
so it is highly recommended that [`Engine`]'s are created immutable as much as possible.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mut engine = Engine::new(); // create mutable 'Engine'
|
||||||
|
|
||||||
|
engine.register_get("add", add); // configure 'engine'
|
||||||
|
|
||||||
|
let engine = engine; // shadow the variable so that 'engine' is now immutable
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
Script optimization
|
Script optimization
|
||||||
===================
|
===================
|
||||||
|
|
||||||
@ -2358,7 +2490,7 @@ There are actually three levels of optimizations: `None`, `Simple` and `Full`.
|
|||||||
* `Full` is _much_ more aggressive, _including_ running functions on constant arguments to determine their result.
|
* `Full` is _much_ more aggressive, _including_ running functions on constant arguments to determine their result.
|
||||||
One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators.
|
One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators.
|
||||||
|
|
||||||
An [`Engine`]'s optimization level is set via a call to `set_optimization_level`:
|
An [`Engine`]'s optimization level is set via a call to `Engine::set_optimization_level`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Turn on aggressive optimizations
|
// Turn on aggressive optimizations
|
||||||
@ -2536,7 +2668,7 @@ let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run
|
|||||||
Or override it from Rust:
|
Or override it from Rust:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn alt_eval(script: String) -> Result<(), EvalAltResult> {
|
fn alt_eval(script: String) -> Result<(), Box<EvalAltResult>> {
|
||||||
Err(format!("eval is evil! I refuse to run {}", script).into())
|
Err(format!("eval is evil! I refuse to run {}", script).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
66
RELEASES.md
Normal file
66
RELEASES.md
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
Rhai Release Notes
|
||||||
|
==================
|
||||||
|
|
||||||
|
Version 0.14.1
|
||||||
|
==============
|
||||||
|
|
||||||
|
The major features for this release is modules, script resource limits, and speed improvements
|
||||||
|
(mainly due to avoiding allocations).
|
||||||
|
|
||||||
|
New features
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Modules and _module resolvers_ allow loading external scripts under a module namespace.
|
||||||
|
A module can contain constant variables, Rust functions and Rhai functions.
|
||||||
|
* `export` variables and `private` functions.
|
||||||
|
* _Indexers_ for Rust types.
|
||||||
|
* Track script evaluation progress and terminate script run.
|
||||||
|
* Set limit on maximum number of operations allowed per script run.
|
||||||
|
* Set limit on maximum number of modules loaded per script run.
|
||||||
|
* A new API, `Engine::compile_scripts_with_scope`, can compile a list of script segments without needing to
|
||||||
|
first concatenate them together into one large string.
|
||||||
|
* Stepped `range` function with a custom step.
|
||||||
|
|
||||||
|
Speed improvements
|
||||||
|
------------------
|
||||||
|
|
||||||
|
### `StaticVec`
|
||||||
|
|
||||||
|
A script contains many lists - statements in a block, arguments to a function call etc.
|
||||||
|
In a typical script, most of these lists tend to be short - e.g. the vast majority of function calls contain
|
||||||
|
fewer than 4 arguments, while most statement blocks have fewer than 4-5 statements, with one or two being
|
||||||
|
the most common. Before, dynamic `Vec`'s are used to hold these short lists for very brief periods of time,
|
||||||
|
causing allocations churn.
|
||||||
|
|
||||||
|
In this version, large amounts of allocations are avoided by converting to a `StaticVec` -
|
||||||
|
a list type based on a static array for a small number of items (currently four) -
|
||||||
|
wherever possible plus other tricks. Most real-life scripts should see material speed increases.
|
||||||
|
|
||||||
|
### Pre-computed variable lookups
|
||||||
|
|
||||||
|
Almost all variable lookups, as well as lookups in loaded modules, are now pre-computed.
|
||||||
|
A variable's name is almost never used to search for the variable in the current scope.
|
||||||
|
|
||||||
|
_Getters_ and _setter_ function names are also pre-computed and cached, so no string allocations are
|
||||||
|
performed during a property get/set call.
|
||||||
|
|
||||||
|
### Pre-computed function call hashes
|
||||||
|
|
||||||
|
Lookup of all function calls, including Rust and Rhai ones, are now through pre-computed hashes.
|
||||||
|
The function name is no longer used to search for a function, making function call dispatches
|
||||||
|
much faster.
|
||||||
|
|
||||||
|
### Large Boxes for expressions and statements
|
||||||
|
|
||||||
|
The expression (`Expr`) and statement (`Stmt`) types are modified so that all of the variants contain only
|
||||||
|
one single `Box` to a large allocated structure containing _all_ the fields. This makes the `Expr` and
|
||||||
|
`Stmt` types very small (only one single pointer) and improves evaluation speed due to cache efficiency.
|
||||||
|
|
||||||
|
Error handling
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Previously, when an error occurs inside a function call, the error position reported is the function
|
||||||
|
call site. This makes it difficult to diagnose the actual location of the error within the function.
|
||||||
|
|
||||||
|
A new error variant `EvalAltResult::ErrorInFunctionCall` is added in this version.
|
||||||
|
It wraps the internal error returned by the called function, including the error position within the function.
|
@ -19,7 +19,7 @@ use crate::stdlib::{
|
|||||||
any::{type_name, Any, TypeId},
|
any::{type_name, Any, TypeId},
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt, mem, ptr,
|
fmt,
|
||||||
string::String,
|
string::String,
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
@ -27,7 +27,7 @@ use crate::stdlib::{
|
|||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
use crate::stdlib::time::Instant;
|
use crate::stdlib::time::Instant;
|
||||||
|
|
||||||
/// A trait to represent any type.
|
/// Trait to represent any type.
|
||||||
///
|
///
|
||||||
/// Currently, `Variant` is not `Send` nor `Sync`, so it can practically be any type.
|
/// Currently, `Variant` is not `Send` nor `Sync`, so it can practically be any type.
|
||||||
/// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`.
|
/// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`.
|
||||||
@ -81,7 +81,7 @@ impl<T: Any + Clone> Variant for T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait to represent any type.
|
/// Trait to represent any type.
|
||||||
///
|
///
|
||||||
/// `From<_>` is implemented for `i64` (`i32` if `only_i32`), `f64` (if not `no_float`),
|
/// `From<_>` is implemented for `i64` (`i32` if `only_i32`), `f64` (if not `no_float`),
|
||||||
/// `bool`, `String`, `char`, `Vec<T>` (into `Array`) and `HashMap<String, T>` (into `Map`).
|
/// `bool`, `String`, `char`, `Vec<T>` (into `Array`) and `HashMap<String, T>` (into `Map`).
|
||||||
@ -142,7 +142,7 @@ impl dyn Variant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A dynamic type containing any value.
|
/// Dynamic type containing any value.
|
||||||
pub struct Dynamic(pub(crate) Union);
|
pub struct Dynamic(pub(crate) Union);
|
||||||
|
|
||||||
/// Internal `Dynamic` representation.
|
/// Internal `Dynamic` representation.
|
||||||
|
116
src/api.rs
116
src/api.rs
@ -21,7 +21,6 @@ use crate::engine::Map;
|
|||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::{type_name, TypeId},
|
any::{type_name, TypeId},
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
collections::HashMap,
|
|
||||||
mem,
|
mem,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
};
|
};
|
||||||
@ -653,7 +652,10 @@ impl Engine {
|
|||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
let stream = lex(&scripts);
|
let stream = lex(&scripts);
|
||||||
|
|
||||||
parse_global_expr(&mut stream.peekable(), self, scope, self.optimization_level)
|
{
|
||||||
|
let mut peekable = stream.peekable();
|
||||||
|
parse_global_expr(&mut peekable, self, scope, self.optimization_level)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a script file.
|
/// Evaluate a script file.
|
||||||
@ -749,11 +751,10 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
script: &str,
|
script: &str,
|
||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
// Since the AST will be thrown away afterwards, don't bother to optimize it
|
|
||||||
let ast = self.compile_with_scope_and_optimization_level(
|
let ast = self.compile_with_scope_and_optimization_level(
|
||||||
scope,
|
scope,
|
||||||
&[script],
|
&[script],
|
||||||
OptimizationLevel::None,
|
self.optimization_level,
|
||||||
)?;
|
)?;
|
||||||
self.eval_ast_with_scope(scope, &ast)
|
self.eval_ast_with_scope(scope, &ast)
|
||||||
}
|
}
|
||||||
@ -865,7 +866,7 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
let result = self.eval_ast_with_scope_raw(scope, ast)?;
|
let (result, _) = self.eval_ast_with_scope_raw(scope, ast)?;
|
||||||
|
|
||||||
let return_type = self.map_type_name(result.type_name());
|
let return_type = self.map_type_name(result.type_name());
|
||||||
|
|
||||||
@ -881,7 +882,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
|
||||||
let mut state = State::new(ast.fn_lib());
|
let mut state = State::new(ast.fn_lib());
|
||||||
|
|
||||||
ast.statements()
|
ast.statements()
|
||||||
@ -893,6 +894,7 @@ impl Engine {
|
|||||||
EvalAltResult::Return(out, _) => Ok(out),
|
EvalAltResult::Return(out, _) => Ok(out),
|
||||||
_ => Err(err),
|
_ => Err(err),
|
||||||
})
|
})
|
||||||
|
.map(|v| (v, state.operations))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a file, but throw away the result and only return error (if any).
|
/// Evaluate a file, but throw away the result and only return error (if any).
|
||||||
@ -1016,9 +1018,9 @@ impl Engine {
|
|||||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.into(), pos)))?;
|
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.into(), pos)))?;
|
||||||
|
|
||||||
let state = State::new(fn_lib);
|
let state = State::new(fn_lib);
|
||||||
|
let args = args.as_mut();
|
||||||
|
|
||||||
let result =
|
let (result, _) = self.call_script_fn(Some(scope), state, name, fn_def, args, pos, 0)?;
|
||||||
self.call_script_fn(Some(scope), &state, name, fn_def, args.as_mut(), pos, 0)?;
|
|
||||||
|
|
||||||
let return_type = self.map_type_name(result.type_name());
|
let return_type = self.map_type_name(result.type_name());
|
||||||
|
|
||||||
@ -1058,6 +1060,84 @@ impl Engine {
|
|||||||
optimize_into_ast(self, scope, stmt, fn_lib, optimization_level)
|
optimize_into_ast(self, scope, stmt, fn_lib, optimization_level)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a callback for script evaluation progress.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
/// # use std::sync::RwLock;
|
||||||
|
/// # use std::sync::Arc;
|
||||||
|
/// use rhai::Engine;
|
||||||
|
///
|
||||||
|
/// let result = Arc::new(RwLock::new(0_u64));
|
||||||
|
/// let logger = result.clone();
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// engine.on_progress(move |ops| {
|
||||||
|
/// if ops > 10000 {
|
||||||
|
/// false
|
||||||
|
/// } else if ops % 800 == 0 {
|
||||||
|
/// *logger.write().unwrap() = ops;
|
||||||
|
/// true
|
||||||
|
/// } else {
|
||||||
|
/// true
|
||||||
|
/// }
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// engine.consume("for x in range(0, 50000) {}")
|
||||||
|
/// .expect_err("should error");
|
||||||
|
///
|
||||||
|
/// assert_eq!(*result.read().unwrap(), 9600);
|
||||||
|
///
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
pub fn on_progress(&mut self, callback: impl Fn(u64) -> bool + Send + Sync + 'static) {
|
||||||
|
self.progress = Some(Box::new(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a callback for script evaluation progress.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
/// # use std::cell::Cell;
|
||||||
|
/// # use std::rc::Rc;
|
||||||
|
/// use rhai::Engine;
|
||||||
|
///
|
||||||
|
/// let result = Rc::new(Cell::new(0_u64));
|
||||||
|
/// let logger = result.clone();
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// engine.on_progress(move |ops| {
|
||||||
|
/// if ops > 10000 {
|
||||||
|
/// false
|
||||||
|
/// } else if ops % 800 == 0 {
|
||||||
|
/// logger.set(ops);
|
||||||
|
/// true
|
||||||
|
/// } else {
|
||||||
|
/// true
|
||||||
|
/// }
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// engine.consume("for x in range(0, 50000) {}")
|
||||||
|
/// .expect_err("should error");
|
||||||
|
///
|
||||||
|
/// assert_eq!(result.get(), 9600);
|
||||||
|
///
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
pub fn on_progress(&mut self, callback: impl Fn(u64) -> bool + 'static) {
|
||||||
|
self.progress = Some(Box::new(callback));
|
||||||
|
}
|
||||||
|
|
||||||
/// Override default action of `print` (print to stdout using `println!`)
|
/// Override default action of `print` (print to stdout using `println!`)
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -1092,21 +1172,21 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
/// # use std::sync::RwLock;
|
/// # use std::cell::RefCell;
|
||||||
/// # use std::sync::Arc;
|
/// # use std::rc::Rc;
|
||||||
/// use rhai::Engine;
|
/// use rhai::Engine;
|
||||||
///
|
///
|
||||||
/// let result = Arc::new(RwLock::new(String::from("")));
|
/// let result = Rc::new(RefCell::new(String::from("")));
|
||||||
///
|
///
|
||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// // Override action of 'print' function
|
/// // Override action of 'print' function
|
||||||
/// let logger = result.clone();
|
/// let logger = result.clone();
|
||||||
/// engine.on_print(move |s| logger.write().unwrap().push_str(s));
|
/// engine.on_print(move |s| logger.borrow_mut().push_str(s));
|
||||||
///
|
///
|
||||||
/// engine.consume("print(40 + 2);")?;
|
/// engine.consume("print(40 + 2);")?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(*result.read().unwrap(), "42");
|
/// assert_eq!(*result.borrow(), "42");
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
@ -1149,21 +1229,21 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
/// # use std::sync::RwLock;
|
/// # use std::cell::RefCell;
|
||||||
/// # use std::sync::Arc;
|
/// # use std::rc::Rc;
|
||||||
/// use rhai::Engine;
|
/// use rhai::Engine;
|
||||||
///
|
///
|
||||||
/// let result = Arc::new(RwLock::new(String::from("")));
|
/// let result = Rc::new(RefCell::new(String::from("")));
|
||||||
///
|
///
|
||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// // Override action of 'print' function
|
/// // Override action of 'print' function
|
||||||
/// let logger = result.clone();
|
/// let logger = result.clone();
|
||||||
/// engine.on_debug(move |s| logger.write().unwrap().push_str(s));
|
/// engine.on_debug(move |s| logger.borrow_mut().push_str(s));
|
||||||
///
|
///
|
||||||
/// engine.consume(r#"debug("hello");"#)?;
|
/// engine.consume(r#"debug("hello");"#)?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(*result.read().unwrap(), r#""hello""#);
|
/// assert_eq!(*result.borrow(), r#""hello""#);
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
|
367
src/engine.rs
367
src/engine.rs
@ -3,12 +3,12 @@
|
|||||||
use crate::any::{Dynamic, Union};
|
use crate::any::{Dynamic, Union};
|
||||||
use crate::calc_fn_hash;
|
use crate::calc_fn_hash;
|
||||||
use crate::error::ParseErrorType;
|
use crate::error::ParseErrorType;
|
||||||
use crate::fn_native::{FnCallArgs, NativeFunctionABI, PrintCallback};
|
use crate::fn_native::{FnCallArgs, NativeFunctionABI, PrintCallback, ProgressCallback};
|
||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
use crate::optimize::OptimizationLevel;
|
use crate::optimize::OptimizationLevel;
|
||||||
use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage};
|
use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage};
|
||||||
use crate::parser::{Expr, FnAccess, FnDef, ReturnType, SharedFnDef, Stmt, AST};
|
use crate::parser::{Expr, FnAccess, FnDef, ReturnType, SharedFnDef, Stmt, AST};
|
||||||
use crate::r#unsafe::unsafe_cast_var_name;
|
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||||
use crate::token::Position;
|
use crate::token::Position;
|
||||||
@ -22,13 +22,12 @@ use crate::parser::ModuleRef;
|
|||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
borrow::Cow,
|
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
format,
|
format,
|
||||||
iter::{empty, once, repeat},
|
iter::{empty, once, repeat},
|
||||||
mem,
|
mem,
|
||||||
num::NonZeroUsize,
|
num::{NonZeroU64, NonZeroUsize},
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
@ -36,24 +35,29 @@ use crate::stdlib::{
|
|||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An dynamic array of `Dynamic` values.
|
/// Variable-sized array of `Dynamic` values.
|
||||||
///
|
///
|
||||||
/// Not available under the `no_index` feature.
|
/// Not available under the `no_index` feature.
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub type Array = Vec<Dynamic>;
|
pub type Array = Vec<Dynamic>;
|
||||||
|
|
||||||
/// An dynamic hash map of `Dynamic` values with `String` keys.
|
/// Hash map of `Dynamic` values with `String` keys.
|
||||||
///
|
///
|
||||||
/// Not available under the `no_object` feature.
|
/// Not available under the `no_object` feature.
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
pub type Map = HashMap<String, Dynamic>;
|
pub type Map = HashMap<String, Dynamic>;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub const MAX_CALL_STACK_DEPTH: usize = 28;
|
pub const MAX_CALL_STACK_DEPTH: usize = 28;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
pub const MAX_CALL_STACK_DEPTH: usize = 256;
|
pub const MAX_CALL_STACK_DEPTH: usize = 256;
|
||||||
|
|
||||||
|
#[cfg(feature = "unchecked")]
|
||||||
|
pub const MAX_CALL_STACK_DEPTH: usize = usize::MAX;
|
||||||
|
|
||||||
pub const KEYWORD_PRINT: &str = "print";
|
pub const KEYWORD_PRINT: &str = "print";
|
||||||
pub const KEYWORD_DEBUG: &str = "debug";
|
pub const KEYWORD_DEBUG: &str = "debug";
|
||||||
pub const KEYWORD_TYPE_OF: &str = "type_of";
|
pub const KEYWORD_TYPE_OF: &str = "type_of";
|
||||||
@ -68,19 +72,36 @@ enum Target<'a> {
|
|||||||
/// The target is a mutable reference to a `Dynamic` value somewhere.
|
/// The target is a mutable reference to a `Dynamic` value somewhere.
|
||||||
Ref(&'a mut Dynamic),
|
Ref(&'a mut Dynamic),
|
||||||
/// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects).
|
/// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects).
|
||||||
Value(Box<Dynamic>),
|
Value(Dynamic),
|
||||||
/// The target is a character inside a String.
|
/// The target is a character inside a String.
|
||||||
/// This is necessary because directly pointing to a char inside a String is impossible.
|
/// This is necessary because directly pointing to a char inside a String is impossible.
|
||||||
StringChar(Box<(&'a mut Dynamic, usize, Dynamic)>),
|
StringChar(&'a mut Dynamic, usize, Dynamic),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Target<'_> {
|
impl Target<'_> {
|
||||||
/// Get the value of the `Target` as a `Dynamic`.
|
/// Is the `Target` a reference pointing to other data?
|
||||||
|
pub fn is_ref(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Target::Ref(_) => true,
|
||||||
|
Target::Value(_) | Target::StringChar(_, _, _) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the value of the `Target` as a `Dynamic`, cloning a referenced value if necessary.
|
||||||
pub fn clone_into_dynamic(self) -> Dynamic {
|
pub fn clone_into_dynamic(self) -> Dynamic {
|
||||||
match self {
|
match self {
|
||||||
Target::Ref(r) => r.clone(),
|
Target::Ref(r) => r.clone(), // Referenced value is cloned
|
||||||
Target::Value(v) => *v,
|
Target::Value(v) => v, // Owned value is simply taken
|
||||||
Target::StringChar(s) => s.2,
|
Target::StringChar(_, _, ch) => ch, // Character is taken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference from the `Target`.
|
||||||
|
pub fn as_mut(&mut self) -> &mut Dynamic {
|
||||||
|
match self {
|
||||||
|
Target::Ref(r) => *r,
|
||||||
|
Target::Value(ref mut r) => r,
|
||||||
|
Target::StringChar(_, _, ref mut r) => r,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,25 +112,23 @@ impl Target<'_> {
|
|||||||
Target::Value(_) => {
|
Target::Value(_) => {
|
||||||
return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(pos)))
|
return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(pos)))
|
||||||
}
|
}
|
||||||
Target::StringChar(x) => match x.0 {
|
Target::StringChar(Dynamic(Union::Str(s)), index, _) => {
|
||||||
Dynamic(Union::Str(s)) => {
|
// Replace the character at the specified index position
|
||||||
// Replace the character at the specified index position
|
let new_ch = new_val
|
||||||
let new_ch = new_val
|
.as_char()
|
||||||
.as_char()
|
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
||||||
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
|
||||||
|
|
||||||
let mut chars: StaticVec<char> = s.chars().collect();
|
let mut chars: StaticVec<char> = s.chars().collect();
|
||||||
let ch = *chars.get_ref(x.1);
|
let ch = chars[*index];
|
||||||
|
|
||||||
// See if changed - if so, update the String
|
// See if changed - if so, update the String
|
||||||
if ch != new_ch {
|
if ch != new_ch {
|
||||||
*chars.get_mut(x.1) = new_ch;
|
chars[*index] = new_ch;
|
||||||
s.clear();
|
s.clear();
|
||||||
chars.iter().for_each(|&ch| s.push(ch));
|
chars.iter().for_each(|&ch| s.push(ch));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
}
|
||||||
},
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -123,7 +142,7 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> {
|
|||||||
}
|
}
|
||||||
impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
||||||
fn from(value: T) -> Self {
|
fn from(value: T) -> Self {
|
||||||
Self::Value(Box::new(value.into()))
|
Self::Value(value.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,15 +165,23 @@ pub struct State<'a> {
|
|||||||
/// Level of the current scope. The global (root) level is zero, a new block (or function call)
|
/// Level of the current scope. The global (root) level is zero, a new block (or function call)
|
||||||
/// is one level higher, and so on.
|
/// is one level higher, and so on.
|
||||||
pub scope_level: usize,
|
pub scope_level: usize,
|
||||||
|
|
||||||
|
/// Number of operations performed.
|
||||||
|
pub operations: u64,
|
||||||
|
|
||||||
|
/// Number of modules loaded.
|
||||||
|
pub modules: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> State<'a> {
|
impl<'a> State<'a> {
|
||||||
/// Create a new `State`.
|
/// Create a new `State`.
|
||||||
pub fn new(fn_lib: &'a FunctionsLib) -> Self {
|
pub fn new(fn_lib: &'a FunctionsLib) -> Self {
|
||||||
Self {
|
Self {
|
||||||
always_search: false,
|
|
||||||
fn_lib,
|
fn_lib,
|
||||||
|
always_search: false,
|
||||||
scope_level: 0,
|
scope_level: 0,
|
||||||
|
operations: 0,
|
||||||
|
modules: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Does a certain script-defined function exist in the `State`?
|
/// Does a certain script-defined function exist in the `State`?
|
||||||
@ -163,7 +190,7 @@ impl<'a> State<'a> {
|
|||||||
}
|
}
|
||||||
/// Get a script-defined function definition from the `State`.
|
/// Get a script-defined function definition from the `State`.
|
||||||
pub fn get_function(&self, hash: u64) -> Option<&FnDef> {
|
pub fn get_function(&self, hash: u64) -> Option<&FnDef> {
|
||||||
self.fn_lib.get(&hash).map(|f| f.as_ref())
|
self.fn_lib.get(&hash).map(|fn_def| fn_def.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,6 +331,8 @@ pub struct Engine {
|
|||||||
pub(crate) print: Box<PrintCallback>,
|
pub(crate) print: Box<PrintCallback>,
|
||||||
/// Closure for implementing the `debug` command.
|
/// Closure for implementing the `debug` command.
|
||||||
pub(crate) debug: Box<PrintCallback>,
|
pub(crate) debug: Box<PrintCallback>,
|
||||||
|
/// Closure for progress reporting.
|
||||||
|
pub(crate) progress: Option<Box<ProgressCallback>>,
|
||||||
|
|
||||||
/// Optimize the AST after compilation.
|
/// Optimize the AST after compilation.
|
||||||
pub(crate) optimization_level: OptimizationLevel,
|
pub(crate) optimization_level: OptimizationLevel,
|
||||||
@ -311,6 +340,10 @@ pub struct Engine {
|
|||||||
///
|
///
|
||||||
/// Defaults to 28 for debug builds and 256 for non-debug builds.
|
/// Defaults to 28 for debug builds and 256 for non-debug builds.
|
||||||
pub(crate) max_call_stack_depth: usize,
|
pub(crate) max_call_stack_depth: usize,
|
||||||
|
/// Maximum number of operations allowed to run.
|
||||||
|
pub(crate) max_operations: Option<NonZeroU64>,
|
||||||
|
/// Maximum number of modules allowed to load.
|
||||||
|
pub(crate) max_modules: Option<NonZeroU64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Engine {
|
impl Default for Engine {
|
||||||
@ -333,6 +366,9 @@ impl Default for Engine {
|
|||||||
print: Box::new(default_print),
|
print: Box::new(default_print),
|
||||||
debug: Box::new(default_print),
|
debug: Box::new(default_print),
|
||||||
|
|
||||||
|
// progress callback
|
||||||
|
progress: None,
|
||||||
|
|
||||||
// optimization level
|
// optimization level
|
||||||
#[cfg(feature = "no_optimize")]
|
#[cfg(feature = "no_optimize")]
|
||||||
optimization_level: OptimizationLevel::None,
|
optimization_level: OptimizationLevel::None,
|
||||||
@ -346,6 +382,8 @@ impl Default for Engine {
|
|||||||
optimization_level: OptimizationLevel::Full,
|
optimization_level: OptimizationLevel::Full,
|
||||||
|
|
||||||
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
||||||
|
max_operations: None,
|
||||||
|
max_modules: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "no_stdlib")]
|
#[cfg(feature = "no_stdlib")]
|
||||||
@ -425,7 +463,7 @@ fn search_scope<'a>(
|
|||||||
.downcast_mut::<Module>()
|
.downcast_mut::<Module>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
} else {
|
} else {
|
||||||
let (id, root_pos) = modules.get_ref(0);
|
let (id, root_pos) = modules.get(0);
|
||||||
|
|
||||||
scope.find_module(id).ok_or_else(|| {
|
scope.find_module(id).ok_or_else(|| {
|
||||||
Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos))
|
Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos))
|
||||||
@ -471,6 +509,7 @@ impl Engine {
|
|||||||
type_names: Default::default(),
|
type_names: Default::default(),
|
||||||
print: Box::new(|_| {}),
|
print: Box::new(|_| {}),
|
||||||
debug: Box::new(|_| {}),
|
debug: Box::new(|_| {}),
|
||||||
|
progress: None,
|
||||||
|
|
||||||
#[cfg(feature = "no_optimize")]
|
#[cfg(feature = "no_optimize")]
|
||||||
optimization_level: OptimizationLevel::None,
|
optimization_level: OptimizationLevel::None,
|
||||||
@ -484,6 +523,8 @@ impl Engine {
|
|||||||
optimization_level: OptimizationLevel::Full,
|
optimization_level: OptimizationLevel::Full,
|
||||||
|
|
||||||
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
||||||
|
max_operations: None,
|
||||||
|
max_modules: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,10 +556,24 @@ impl Engine {
|
|||||||
|
|
||||||
/// Set the maximum levels of function calls allowed for a script in order to avoid
|
/// Set the maximum levels of function calls allowed for a script in order to avoid
|
||||||
/// infinite recursion and stack overflows.
|
/// infinite recursion and stack overflows.
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
pub fn set_max_call_levels(&mut self, levels: usize) {
|
pub fn set_max_call_levels(&mut self, levels: usize) {
|
||||||
self.max_call_stack_depth = levels
|
self.max_call_stack_depth = levels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the maximum number of operations allowed for a script to run to avoid
|
||||||
|
/// consuming too much resources (0 for unlimited).
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
pub fn set_max_operations(&mut self, operations: u64) {
|
||||||
|
self.max_operations = NonZeroU64::new(operations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the maximum number of imported modules allowed for a script (0 for unlimited).
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
pub fn set_max_modules(&mut self, modules: u64) {
|
||||||
|
self.max_modules = NonZeroU64::new(modules);
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the module resolution service used by the `Engine`.
|
/// Set the module resolution service used by the `Engine`.
|
||||||
///
|
///
|
||||||
/// Not available under the `no_module` feature.
|
/// Not available under the `no_module` feature.
|
||||||
@ -537,7 +592,7 @@ impl Engine {
|
|||||||
pub(crate) fn call_fn_raw(
|
pub(crate) fn call_fn_raw(
|
||||||
&self,
|
&self,
|
||||||
scope: Option<&mut Scope>,
|
scope: Option<&mut Scope>,
|
||||||
state: &State,
|
state: &mut State,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
hashes: (u64, u64),
|
hashes: (u64, u64),
|
||||||
args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
@ -546,6 +601,8 @@ impl Engine {
|
|||||||
pos: Position,
|
pos: Position,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||||
|
self.inc_operations(state, pos)?;
|
||||||
|
|
||||||
// Check for stack overflow
|
// Check for stack overflow
|
||||||
if level > self.max_call_stack_depth {
|
if level > self.max_call_stack_depth {
|
||||||
return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos)));
|
return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos)));
|
||||||
@ -554,9 +611,10 @@ impl Engine {
|
|||||||
// First search in script-defined functions (can override built-in)
|
// First search in script-defined functions (can override built-in)
|
||||||
if hashes.1 > 0 {
|
if hashes.1 > 0 {
|
||||||
if let Some(fn_def) = state.get_function(hashes.1) {
|
if let Some(fn_def) = state.get_function(hashes.1) {
|
||||||
return self
|
let (result, state2) =
|
||||||
.call_script_fn(scope, state, fn_name, fn_def, args, pos, level)
|
self.call_script_fn(scope, *state, fn_name, fn_def, args, pos, level)?;
|
||||||
.map(|v| (v, false));
|
*state = state2;
|
||||||
|
return Ok((result, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -670,21 +728,21 @@ impl Engine {
|
|||||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
||||||
pub(crate) fn call_script_fn<'s>(
|
pub(crate) fn call_script_fn<'s>(
|
||||||
&self,
|
&self,
|
||||||
scope: Option<&mut Scope<'s>>,
|
scope: Option<&mut Scope>,
|
||||||
state: &State,
|
mut state: State<'s>,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
fn_def: &FnDef,
|
fn_def: &FnDef,
|
||||||
args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<(Dynamic, State<'s>), Box<EvalAltResult>> {
|
||||||
|
let orig_scope_level = state.scope_level;
|
||||||
|
state.scope_level += 1;
|
||||||
|
|
||||||
match scope {
|
match scope {
|
||||||
// Extern scope passed in which is not empty
|
// Extern scope passed in which is not empty
|
||||||
Some(scope) if scope.len() > 0 => {
|
Some(scope) if scope.len() > 0 => {
|
||||||
let scope_len = scope.len();
|
let scope_len = scope.len();
|
||||||
let mut state = State::new(state.fn_lib);
|
|
||||||
|
|
||||||
state.scope_level += 1;
|
|
||||||
|
|
||||||
// Put arguments into scope as variables
|
// Put arguments into scope as variables
|
||||||
scope.extend(
|
scope.extend(
|
||||||
@ -696,7 +754,8 @@ impl Engine {
|
|||||||
args.into_iter().map(|v| mem::take(*v)),
|
args.into_iter().map(|v| mem::take(*v)),
|
||||||
)
|
)
|
||||||
.map(|(name, value)| {
|
.map(|(name, value)| {
|
||||||
let var_name = unsafe_cast_var_name(name.as_str(), &state);
|
let var_name =
|
||||||
|
unsafe_cast_var_name_to_lifetime(name.as_str(), &mut state);
|
||||||
(var_name, ScopeEntryType::Normal, value)
|
(var_name, ScopeEntryType::Normal, value)
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -722,16 +781,14 @@ impl Engine {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Remove all local variables
|
// Remove all local variables
|
||||||
// No need to reset `state.scope_level` because it is thrown away
|
|
||||||
scope.rewind(scope_len);
|
scope.rewind(scope_len);
|
||||||
|
state.scope_level = orig_scope_level;
|
||||||
|
|
||||||
return result;
|
return result.map(|v| (v, state));
|
||||||
}
|
}
|
||||||
// No new scope - create internal scope
|
// No new scope - create internal scope
|
||||||
_ => {
|
_ => {
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
let mut state = State::new(state.fn_lib);
|
|
||||||
state.scope_level += 1;
|
|
||||||
|
|
||||||
// Put arguments into scope as variables
|
// Put arguments into scope as variables
|
||||||
scope.extend(
|
scope.extend(
|
||||||
@ -746,8 +803,7 @@ impl Engine {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Evaluate the function at one higher level of call depth
|
// Evaluate the function at one higher level of call depth
|
||||||
// No need to reset `state.scope_level` because it is thrown away
|
let result = self
|
||||||
return self
|
|
||||||
.eval_stmt(&mut scope, &mut state, &fn_def.body, level + 1)
|
.eval_stmt(&mut scope, &mut state, &fn_def.body, level + 1)
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
// Convert return statement to return value
|
// Convert return statement to return value
|
||||||
@ -765,6 +821,9 @@ impl Engine {
|
|||||||
pos,
|
pos,
|
||||||
))),
|
))),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
state.scope_level = orig_scope_level;
|
||||||
|
return result.map(|v| (v, state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -788,7 +847,7 @@ impl Engine {
|
|||||||
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
|
||||||
fn exec_fn_call(
|
fn exec_fn_call(
|
||||||
&self,
|
&self,
|
||||||
state: &State,
|
state: &mut State,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
hash_fn_def: u64,
|
hash_fn_def: u64,
|
||||||
args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
@ -827,7 +886,7 @@ impl Engine {
|
|||||||
fn eval_script_expr(
|
fn eval_script_expr(
|
||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
state: &State,
|
state: &mut State,
|
||||||
script: &Dynamic,
|
script: &Dynamic,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
@ -854,15 +913,21 @@ impl Engine {
|
|||||||
let ast = AST::new(statements, state.fn_lib.clone());
|
let ast = AST::new(statements, state.fn_lib.clone());
|
||||||
|
|
||||||
// Evaluate the AST
|
// Evaluate the AST
|
||||||
self.eval_ast_with_scope_raw(scope, &ast)
|
let (result, operations) = self
|
||||||
.map_err(|err| err.new_position(pos))
|
.eval_ast_with_scope_raw(scope, &ast)
|
||||||
|
.map_err(|err| err.new_position(pos))?;
|
||||||
|
|
||||||
|
state.operations += operations;
|
||||||
|
self.inc_operations(state, pos)?;
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chain-evaluate a dot/index chain.
|
/// Chain-evaluate a dot/index chain.
|
||||||
fn eval_dot_index_chain_helper(
|
fn eval_dot_index_chain_helper(
|
||||||
&self,
|
&self,
|
||||||
state: &State,
|
state: &mut State,
|
||||||
mut target: Target,
|
target: &mut Target,
|
||||||
rhs: &Expr,
|
rhs: &Expr,
|
||||||
idx_values: &mut StaticVec<Dynamic>,
|
idx_values: &mut StaticVec<Dynamic>,
|
||||||
is_index: bool,
|
is_index: bool,
|
||||||
@ -870,12 +935,10 @@ impl Engine {
|
|||||||
level: usize,
|
level: usize,
|
||||||
mut new_val: Option<Dynamic>,
|
mut new_val: Option<Dynamic>,
|
||||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||||
|
let is_ref = target.is_ref();
|
||||||
|
|
||||||
// Get a reference to the mutation target Dynamic
|
// Get a reference to the mutation target Dynamic
|
||||||
let (obj, is_ref) = match target {
|
let obj = target.as_mut();
|
||||||
Target::Ref(r) => (r, true),
|
|
||||||
Target::Value(ref mut r) => (r.as_mut(), false),
|
|
||||||
Target::StringChar(ref mut x) => (&mut x.2, false),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Pop the last index value
|
// Pop the last index value
|
||||||
let mut idx_val = idx_values.pop();
|
let mut idx_val = idx_values.pop();
|
||||||
@ -886,20 +949,20 @@ impl Engine {
|
|||||||
Expr::Dot(x) | Expr::Index(x) => {
|
Expr::Dot(x) | Expr::Index(x) => {
|
||||||
let is_idx = matches!(rhs, Expr::Index(_));
|
let is_idx = matches!(rhs, Expr::Index(_));
|
||||||
let pos = x.0.position();
|
let pos = x.0.position();
|
||||||
let val =
|
let this_ptr = &mut self
|
||||||
self.get_indexed_mut(state, obj, is_ref, idx_val, pos, op_pos, false)?;
|
.get_indexed_mut(state, obj, is_ref, idx_val, pos, op_pos, false)?;
|
||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, val, &x.1, idx_values, is_idx, x.2, level, new_val,
|
state, this_ptr, &x.1, idx_values, is_idx, x.2, level, new_val,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// xxx[rhs] = new_val
|
// xxx[rhs] = new_val
|
||||||
_ if new_val.is_some() => {
|
_ if new_val.is_some() => {
|
||||||
let pos = rhs.position();
|
let pos = rhs.position();
|
||||||
let mut val =
|
let this_ptr = &mut self
|
||||||
self.get_indexed_mut(state, obj, is_ref, idx_val, pos, op_pos, true)?;
|
.get_indexed_mut(state, obj, is_ref, idx_val, pos, op_pos, true)?;
|
||||||
|
|
||||||
val.set_value(new_val.unwrap(), rhs.position())?;
|
this_ptr.set_value(new_val.unwrap(), rhs.position())?;
|
||||||
Ok((Default::default(), true))
|
Ok((Default::default(), true))
|
||||||
}
|
}
|
||||||
// xxx[rhs]
|
// xxx[rhs]
|
||||||
@ -968,7 +1031,7 @@ impl Engine {
|
|||||||
Expr::Index(x) | Expr::Dot(x) if obj.is::<Map>() => {
|
Expr::Index(x) | Expr::Dot(x) if obj.is::<Map>() => {
|
||||||
let is_idx = matches!(rhs, Expr::Index(_));
|
let is_idx = matches!(rhs, Expr::Index(_));
|
||||||
|
|
||||||
let val = if let Expr::Property(p) = &x.0 {
|
let mut val = if let Expr::Property(p) = &x.0 {
|
||||||
let ((prop, _, _), _) = p.as_ref();
|
let ((prop, _, _), _) = p.as_ref();
|
||||||
let index = prop.clone().into();
|
let index = prop.clone().into();
|
||||||
self.get_indexed_mut(state, obj, is_ref, index, x.2, op_pos, false)?
|
self.get_indexed_mut(state, obj, is_ref, index, x.2, op_pos, false)?
|
||||||
@ -981,7 +1044,7 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, val, &x.1, idx_values, is_idx, x.2, level, new_val,
|
state, &mut val, &x.1, idx_values, is_idx, x.2, level, new_val,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// xxx.idx_lhs[idx_expr] | xxx.dot_lhs.rhs
|
// xxx.idx_lhs[idx_expr] | xxx.dot_lhs.rhs
|
||||||
@ -1000,16 +1063,10 @@ impl Engine {
|
|||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
let val = &mut val;
|
let val = &mut val;
|
||||||
|
let target = &mut val.into();
|
||||||
|
|
||||||
let (result, may_be_changed) = self.eval_dot_index_chain_helper(
|
let (result, may_be_changed) = self.eval_dot_index_chain_helper(
|
||||||
state,
|
state, target, &x.1, idx_values, is_idx, x.2, level, new_val,
|
||||||
val.into(),
|
|
||||||
&x.1,
|
|
||||||
idx_values,
|
|
||||||
is_idx,
|
|
||||||
x.2,
|
|
||||||
level,
|
|
||||||
new_val,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Feed the value back via a setter just in case it has been updated
|
// Feed the value back via a setter just in case it has been updated
|
||||||
@ -1061,6 +1118,7 @@ impl Engine {
|
|||||||
let index = if state.always_search { None } else { *index };
|
let index = if state.always_search { None } else { *index };
|
||||||
let mod_and_hash = modules.as_ref().map(|m| (m, *hash_var));
|
let mod_and_hash = modules.as_ref().map(|m| (m, *hash_var));
|
||||||
let (target, typ) = search_scope(scope, &name, mod_and_hash, index, *pos)?;
|
let (target, typ) = search_scope(scope, &name, mod_and_hash, index, *pos)?;
|
||||||
|
self.inc_operations(state, *pos)?;
|
||||||
|
|
||||||
// Constants cannot be modified
|
// Constants cannot be modified
|
||||||
match typ {
|
match typ {
|
||||||
@ -1074,7 +1132,7 @@ impl Engine {
|
|||||||
ScopeEntryType::Constant | ScopeEntryType::Normal => (),
|
ScopeEntryType::Constant | ScopeEntryType::Normal => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let this_ptr = target.into();
|
let this_ptr = &mut target.into();
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, this_ptr, dot_rhs, idx_values, is_index, op_pos, level, new_val,
|
state, this_ptr, dot_rhs, idx_values, is_index, op_pos, level, new_val,
|
||||||
)
|
)
|
||||||
@ -1089,7 +1147,7 @@ impl Engine {
|
|||||||
// {expr}.??? or {expr}[???]
|
// {expr}.??? or {expr}[???]
|
||||||
expr => {
|
expr => {
|
||||||
let val = self.eval_expr(scope, state, expr, level)?;
|
let val = self.eval_expr(scope, state, expr, level)?;
|
||||||
let this_ptr = val.into();
|
let this_ptr = &mut val.into();
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, this_ptr, dot_rhs, idx_values, is_index, op_pos, level, new_val,
|
state, this_ptr, dot_rhs, idx_values, is_index, op_pos, level, new_val,
|
||||||
)
|
)
|
||||||
@ -1112,13 +1170,14 @@ impl Engine {
|
|||||||
size: usize,
|
size: usize,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
|
self.inc_operations(state, expr.position())?;
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
Expr::FnCall(x) if x.1.is_none() => {
|
Expr::FnCall(x) if x.1.is_none() => {
|
||||||
let mut arg_values = StaticVec::<Dynamic>::new();
|
let arg_values =
|
||||||
|
x.3.iter()
|
||||||
for arg_expr in x.3.iter() {
|
.map(|arg_expr| self.eval_expr(scope, state, arg_expr, level))
|
||||||
arg_values.push(self.eval_expr(scope, state, arg_expr, level)?);
|
.collect::<Result<StaticVec<Dynamic>, _>>()?;
|
||||||
}
|
|
||||||
|
|
||||||
idx_values.push(Dynamic::from(arg_values));
|
idx_values.push(Dynamic::from(arg_values));
|
||||||
}
|
}
|
||||||
@ -1145,7 +1204,7 @@ impl Engine {
|
|||||||
/// Get the value at the indexed position of a base type
|
/// Get the value at the indexed position of a base type
|
||||||
fn get_indexed_mut<'a>(
|
fn get_indexed_mut<'a>(
|
||||||
&self,
|
&self,
|
||||||
state: &State,
|
state: &mut State,
|
||||||
val: &'a mut Dynamic,
|
val: &'a mut Dynamic,
|
||||||
is_ref: bool,
|
is_ref: bool,
|
||||||
mut idx: Dynamic,
|
mut idx: Dynamic,
|
||||||
@ -1153,6 +1212,8 @@ impl Engine {
|
|||||||
op_pos: Position,
|
op_pos: Position,
|
||||||
create: bool,
|
create: bool,
|
||||||
) -> Result<Target<'a>, Box<EvalAltResult>> {
|
) -> Result<Target<'a>, Box<EvalAltResult>> {
|
||||||
|
self.inc_operations(state, op_pos)?;
|
||||||
|
|
||||||
match val {
|
match val {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Dynamic(Union::Array(arr)) => {
|
Dynamic(Union::Array(arr)) => {
|
||||||
@ -1205,7 +1266,7 @@ impl Engine {
|
|||||||
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))
|
Box::new(EvalAltResult::ErrorStringBounds(chars_len, index, idx_pos))
|
||||||
})?;
|
})?;
|
||||||
Ok(Target::StringChar(Box::new((val, offset, ch.into()))))
|
Ok(Target::StringChar(val, offset, ch.into()))
|
||||||
} else {
|
} else {
|
||||||
Err(Box::new(EvalAltResult::ErrorStringBounds(
|
Err(Box::new(EvalAltResult::ErrorStringBounds(
|
||||||
chars_len, index, idx_pos,
|
chars_len, index, idx_pos,
|
||||||
@ -1234,6 +1295,8 @@ impl Engine {
|
|||||||
rhs: &Expr,
|
rhs: &Expr,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
self.inc_operations(state, rhs.position())?;
|
||||||
|
|
||||||
let mut lhs_value = self.eval_expr(scope, state, lhs, level)?;
|
let mut lhs_value = self.eval_expr(scope, state, lhs, level)?;
|
||||||
let rhs_value = self.eval_expr(scope, state, rhs, level)?;
|
let rhs_value = self.eval_expr(scope, state, rhs, level)?;
|
||||||
|
|
||||||
@ -1266,13 +1329,13 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Dynamic(Union::Map(rhs_value)) => match lhs_value {
|
Dynamic(Union::Map(rhs_value)) => match lhs_value {
|
||||||
// Only allows String or char
|
// Only allows String or char
|
||||||
Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(s.as_ref()).into()),
|
Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(s.as_str()).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()))),
|
_ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))),
|
||||||
},
|
},
|
||||||
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_ref()).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()))),
|
_ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))),
|
||||||
},
|
},
|
||||||
@ -1288,6 +1351,8 @@ impl Engine {
|
|||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
self.inc_operations(state, expr.position())?;
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
Expr::IntegerConstant(x) => Ok(x.0.into()),
|
Expr::IntegerConstant(x) => Ok(x.0.into()),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
@ -1318,6 +1383,8 @@ impl Engine {
|
|||||||
let index = if state.always_search { None } else { *index };
|
let index = if state.always_search { None } else { *index };
|
||||||
let mod_and_hash = modules.as_ref().map(|m| (m, *hash_var));
|
let mod_and_hash = modules.as_ref().map(|m| (m, *hash_var));
|
||||||
let (lhs_ptr, typ) = search_scope(scope, name, mod_and_hash, index, *pos)?;
|
let (lhs_ptr, typ) = search_scope(scope, name, mod_and_hash, index, *pos)?;
|
||||||
|
self.inc_operations(state, *pos)?;
|
||||||
|
|
||||||
match typ {
|
match typ {
|
||||||
ScopeEntryType::Constant => Err(Box::new(
|
ScopeEntryType::Constant => Err(Box::new(
|
||||||
EvalAltResult::ErrorAssignmentToConstant(name.clone(), *pos),
|
EvalAltResult::ErrorAssignmentToConstant(name.clone(), *pos),
|
||||||
@ -1401,20 +1468,16 @@ impl Engine {
|
|||||||
|
|
||||||
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
|
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
|
||||||
|
|
||||||
if name == KEYWORD_EVAL && args.len() == 1 && args.get_ref(0).is::<String>() {
|
if name == KEYWORD_EVAL && args.len() == 1 && args.get(0).is::<String>() {
|
||||||
let hash_fn = calc_fn_hash(empty(), name, once(TypeId::of::<String>()));
|
let hash_fn = calc_fn_hash(empty(), name, once(TypeId::of::<String>()));
|
||||||
|
|
||||||
if !self.has_override(state, (hash_fn, *hash_fn_def)) {
|
if !self.has_override(state, (hash_fn, *hash_fn_def)) {
|
||||||
// eval - only in function call style
|
// eval - only in function call style
|
||||||
let prev_len = scope.len();
|
let prev_len = scope.len();
|
||||||
|
let pos = args_expr.get(0).position();
|
||||||
|
|
||||||
// Evaluate the text string as a script
|
// Evaluate the text string as a script
|
||||||
let result = self.eval_script_expr(
|
let result = self.eval_script_expr(scope, state, args.pop(), pos);
|
||||||
scope,
|
|
||||||
state,
|
|
||||||
args.pop(),
|
|
||||||
args_expr[0].position(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if scope.len() != prev_len {
|
if scope.len() != prev_len {
|
||||||
// IMPORTANT! If the eval defines new variables in the current scope,
|
// IMPORTANT! If the eval defines new variables in the current scope,
|
||||||
@ -1445,7 +1508,7 @@ impl Engine {
|
|||||||
|
|
||||||
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
|
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
|
||||||
|
|
||||||
let (id, root_pos) = modules.get_ref(0); // First module
|
let (id, root_pos) = modules.get(0); // First module
|
||||||
|
|
||||||
let module = if let Some(index) = modules.index() {
|
let module = if let Some(index) = modules.index() {
|
||||||
scope
|
scope
|
||||||
@ -1461,9 +1524,14 @@ impl Engine {
|
|||||||
|
|
||||||
// First search in script-defined functions (can override built-in)
|
// First search in script-defined functions (can override built-in)
|
||||||
if let Some(fn_def) = module.get_qualified_scripted_fn(*hash_fn_def) {
|
if let Some(fn_def) = module.get_qualified_scripted_fn(*hash_fn_def) {
|
||||||
self.call_script_fn(None, state, name, fn_def, args.as_mut(), *pos, level)
|
let args = args.as_mut();
|
||||||
|
let (result, state2) =
|
||||||
|
self.call_script_fn(None, *state, name, fn_def, args, *pos, level)?;
|
||||||
|
*state = state2;
|
||||||
|
Ok(result)
|
||||||
} else {
|
} else {
|
||||||
// Then search in Rust functions
|
// Then search in Rust functions
|
||||||
|
self.inc_operations(state, *pos)?;
|
||||||
|
|
||||||
// Rust functions are indexed in two steps:
|
// Rust functions are indexed in two steps:
|
||||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||||
@ -1538,6 +1606,8 @@ impl Engine {
|
|||||||
stmt: &Stmt,
|
stmt: &Stmt,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
self.inc_operations(state, stmt.position())?;
|
||||||
|
|
||||||
match stmt {
|
match stmt {
|
||||||
// No-op
|
// No-op
|
||||||
Stmt::Noop(_) => Ok(Default::default()),
|
Stmt::Noop(_) => Ok(Default::default()),
|
||||||
@ -1546,11 +1616,10 @@ impl Engine {
|
|||||||
Stmt::Expr(expr) => {
|
Stmt::Expr(expr) => {
|
||||||
let result = self.eval_expr(scope, state, expr, level)?;
|
let result = self.eval_expr(scope, state, expr, level)?;
|
||||||
|
|
||||||
Ok(if let Expr::Assignment(_) = *expr.as_ref() {
|
Ok(match expr.as_ref() {
|
||||||
// If it is an assignment, erase the result at the root
|
// If it is an assignment, erase the result at the root
|
||||||
Default::default()
|
Expr::Assignment(_) => Default::default(),
|
||||||
} else {
|
_ => result,
|
||||||
result
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1574,24 +1643,29 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If-else statement
|
// If-else statement
|
||||||
Stmt::IfThenElse(x) => self
|
Stmt::IfThenElse(x) => {
|
||||||
.eval_expr(scope, state, &x.0, level)?
|
let (expr, if_block, else_block) = x.as_ref();
|
||||||
.as_bool()
|
|
||||||
.map_err(|_| Box::new(EvalAltResult::ErrorLogicGuard(x.0.position())))
|
self.eval_expr(scope, state, expr, level)?
|
||||||
.and_then(|guard_val| {
|
.as_bool()
|
||||||
if guard_val {
|
.map_err(|_| Box::new(EvalAltResult::ErrorLogicGuard(expr.position())))
|
||||||
self.eval_stmt(scope, state, &x.1, level)
|
.and_then(|guard_val| {
|
||||||
} else if let Some(stmt) = &x.2 {
|
if guard_val {
|
||||||
self.eval_stmt(scope, state, stmt, level)
|
self.eval_stmt(scope, state, if_block, level)
|
||||||
} else {
|
} else if let Some(stmt) = else_block {
|
||||||
Ok(Default::default())
|
self.eval_stmt(scope, state, stmt, level)
|
||||||
}
|
} else {
|
||||||
}),
|
Ok(Default::default())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// While loop
|
// While loop
|
||||||
Stmt::While(x) => loop {
|
Stmt::While(x) => loop {
|
||||||
match self.eval_expr(scope, state, &x.0, level)?.as_bool() {
|
let (expr, body) = x.as_ref();
|
||||||
Ok(true) => match self.eval_stmt(scope, state, &x.1, level) {
|
|
||||||
|
match self.eval_expr(scope, state, expr, level)?.as_bool() {
|
||||||
|
Ok(true) => match self.eval_stmt(scope, state, body, level) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => match *err {
|
Err(err) => match *err {
|
||||||
EvalAltResult::ErrorLoopBreak(false, _) => (),
|
EvalAltResult::ErrorLoopBreak(false, _) => (),
|
||||||
@ -1600,7 +1674,9 @@ impl Engine {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Ok(false) => return Ok(Default::default()),
|
Ok(false) => return Ok(Default::default()),
|
||||||
Err(_) => return Err(Box::new(EvalAltResult::ErrorLogicGuard(x.0.position()))),
|
Err(_) => {
|
||||||
|
return Err(Box::new(EvalAltResult::ErrorLogicGuard(expr.position())))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1618,7 +1694,8 @@ impl Engine {
|
|||||||
|
|
||||||
// For loop
|
// For loop
|
||||||
Stmt::For(x) => {
|
Stmt::For(x) => {
|
||||||
let iter_type = self.eval_expr(scope, state, &x.1, level)?;
|
let (name, expr, stmt) = x.as_ref();
|
||||||
|
let iter_type = self.eval_expr(scope, state, expr, level)?;
|
||||||
let tid = iter_type.type_id();
|
let tid = iter_type.type_id();
|
||||||
|
|
||||||
if let Some(iter_fn) = self
|
if let Some(iter_fn) = self
|
||||||
@ -1627,15 +1704,16 @@ impl Engine {
|
|||||||
.or_else(|| self.packages.get_iter(tid))
|
.or_else(|| self.packages.get_iter(tid))
|
||||||
{
|
{
|
||||||
// Add the loop variable
|
// Add the loop variable
|
||||||
let var_name = unsafe_cast_var_name(&x.0, &state);
|
let var_name = unsafe_cast_var_name_to_lifetime(name, &state);
|
||||||
scope.push(var_name, ());
|
scope.push(var_name, ());
|
||||||
let index = scope.len() - 1;
|
let index = scope.len() - 1;
|
||||||
state.scope_level += 1;
|
state.scope_level += 1;
|
||||||
|
|
||||||
for loop_var in iter_fn(iter_type) {
|
for loop_var in iter_fn(iter_type) {
|
||||||
*scope.get_mut(index).0 = loop_var;
|
*scope.get_mut(index).0 = loop_var;
|
||||||
|
self.inc_operations(state, stmt.position())?;
|
||||||
|
|
||||||
match self.eval_stmt(scope, state, &x.2, level) {
|
match self.eval_stmt(scope, state, stmt, level) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => match *err {
|
Err(err) => match *err {
|
||||||
EvalAltResult::ErrorLoopBreak(false, _) => (),
|
EvalAltResult::ErrorLoopBreak(false, _) => (),
|
||||||
@ -1692,14 +1770,14 @@ impl Engine {
|
|||||||
Stmt::Let(x) if x.1.is_some() => {
|
Stmt::Let(x) if x.1.is_some() => {
|
||||||
let ((var_name, _), expr) = x.as_ref();
|
let ((var_name, _), expr) = x.as_ref();
|
||||||
let val = self.eval_expr(scope, state, expr.as_ref().unwrap(), level)?;
|
let val = self.eval_expr(scope, state, expr.as_ref().unwrap(), level)?;
|
||||||
let var_name = unsafe_cast_var_name(var_name, &state);
|
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
|
||||||
scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false);
|
scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false);
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
Stmt::Let(x) => {
|
Stmt::Let(x) => {
|
||||||
let ((var_name, _), _) = x.as_ref();
|
let ((var_name, _), _) = x.as_ref();
|
||||||
let var_name = unsafe_cast_var_name(var_name, &state);
|
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
|
||||||
scope.push(var_name, ());
|
scope.push(var_name, ());
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
@ -1708,7 +1786,7 @@ impl Engine {
|
|||||||
Stmt::Const(x) if x.1.is_constant() => {
|
Stmt::Const(x) if x.1.is_constant() => {
|
||||||
let ((var_name, _), expr) = x.as_ref();
|
let ((var_name, _), expr) = x.as_ref();
|
||||||
let val = self.eval_expr(scope, state, &expr, level)?;
|
let val = self.eval_expr(scope, state, &expr, level)?;
|
||||||
let var_name = unsafe_cast_var_name(var_name, &state);
|
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
|
||||||
scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true);
|
scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true);
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
@ -1718,24 +1796,34 @@ impl Engine {
|
|||||||
|
|
||||||
// Import statement
|
// Import statement
|
||||||
Stmt::Import(x) => {
|
Stmt::Import(x) => {
|
||||||
let (expr, (name, _)) = x.as_ref();
|
|
||||||
|
|
||||||
#[cfg(feature = "no_module")]
|
#[cfg(feature = "no_module")]
|
||||||
unreachable!();
|
unreachable!();
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
{
|
{
|
||||||
|
let (expr, (name, pos)) = x.as_ref();
|
||||||
|
|
||||||
|
// Guard against too many modules
|
||||||
|
if let Some(max) = self.max_modules {
|
||||||
|
if state.modules >= max.get() {
|
||||||
|
return Err(Box::new(EvalAltResult::ErrorTooManyModules(*pos)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(path) = self
|
if let Some(path) = self
|
||||||
.eval_expr(scope, state, &expr, level)?
|
.eval_expr(scope, state, &expr, level)?
|
||||||
.try_cast::<String>()
|
.try_cast::<String>()
|
||||||
{
|
{
|
||||||
if let Some(resolver) = self.module_resolver.as_ref() {
|
if let Some(resolver) = &self.module_resolver {
|
||||||
// Use an empty scope to create a module
|
// Use an empty scope to create a module
|
||||||
let module =
|
let module =
|
||||||
resolver.resolve(self, Scope::new(), &path, expr.position())?;
|
resolver.resolve(self, Scope::new(), &path, expr.position())?;
|
||||||
|
|
||||||
let mod_name = unsafe_cast_var_name(name, &state);
|
let mod_name = unsafe_cast_var_name_to_lifetime(name, &state);
|
||||||
scope.push_module(mod_name, module);
|
scope.push_module(mod_name, module);
|
||||||
|
|
||||||
|
state.modules += 1;
|
||||||
|
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
} else {
|
} else {
|
||||||
Err(Box::new(EvalAltResult::ErrorModuleNotFound(
|
Err(Box::new(EvalAltResult::ErrorModuleNotFound(
|
||||||
@ -1751,7 +1839,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Export statement
|
// Export statement
|
||||||
Stmt::Export(list) => {
|
Stmt::Export(list) => {
|
||||||
for ((id, id_pos), rename) in list.as_ref() {
|
for ((id, id_pos), rename) in list.iter() {
|
||||||
// Mark scope variables as public
|
// Mark scope variables as public
|
||||||
if let Some(index) = scope
|
if let Some(index) = scope
|
||||||
.get_index(id)
|
.get_index(id)
|
||||||
@ -1776,6 +1864,31 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the number of operations stay within limit.
|
||||||
|
fn inc_operations(&self, state: &mut State, pos: Position) -> Result<(), Box<EvalAltResult>> {
|
||||||
|
state.operations += 1;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
{
|
||||||
|
// Guard against too many operations
|
||||||
|
if let Some(max) = self.max_operations {
|
||||||
|
if state.operations > max.get() {
|
||||||
|
return Err(Box::new(EvalAltResult::ErrorTooManyOperations(pos)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report progress - only in steps
|
||||||
|
if let Some(progress) = &self.progress {
|
||||||
|
if !progress(state.operations) {
|
||||||
|
// Terminate script if progress returns false
|
||||||
|
return Err(Box::new(EvalAltResult::ErrorTerminated(pos)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Map a type_name into a pretty-print name
|
/// Map a type_name into a pretty-print name
|
||||||
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
|
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
|
||||||
self.type_names
|
self.type_names
|
||||||
|
@ -9,7 +9,7 @@ use crate::utils::StaticVec;
|
|||||||
/// Any data type that can be converted into a `Vec<Dynamic>` can be used
|
/// Any data type that can be converted into a `Vec<Dynamic>` can be used
|
||||||
/// as arguments to a function call.
|
/// as arguments to a function call.
|
||||||
pub trait FuncArgs {
|
pub trait FuncArgs {
|
||||||
/// Convert to a `Vec<Dynamic>` of the function call arguments.
|
/// Convert to a `StaticVec<Dynamic>` of the function call arguments.
|
||||||
fn into_vec(self) -> StaticVec<Dynamic>;
|
fn into_vec(self) -> StaticVec<Dynamic>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ use crate::scope::Scope;
|
|||||||
|
|
||||||
use crate::stdlib::{boxed::Box, string::ToString};
|
use crate::stdlib::{boxed::Box, string::ToString};
|
||||||
|
|
||||||
/// A trait to create a Rust anonymous function from a script.
|
/// Trait to create a Rust anonymous function from a script.
|
||||||
pub trait Func<ARGS, RET> {
|
pub trait Func<ARGS, RET> {
|
||||||
type Output;
|
type Output;
|
||||||
|
|
||||||
|
@ -20,6 +20,11 @@ pub type PrintCallback = dyn Fn(&str) + Send + Sync + 'static;
|
|||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
pub type PrintCallback = dyn Fn(&str) + 'static;
|
pub type PrintCallback = dyn Fn(&str) + 'static;
|
||||||
|
|
||||||
|
#[cfg(feature = "sync")]
|
||||||
|
pub type ProgressCallback = dyn Fn(u64) -> bool + Send + Sync + 'static;
|
||||||
|
#[cfg(not(feature = "sync"))]
|
||||||
|
pub type ProgressCallback = dyn Fn(u64) -> bool + 'static;
|
||||||
|
|
||||||
// Define callback function types
|
// Define callback function types
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + Send + Sync + 'static {}
|
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + Send + Sync + 'static {}
|
||||||
@ -77,7 +82,7 @@ pub enum NativeFunctionABI {
|
|||||||
Method,
|
Method,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait implemented by all native Rust functions that are callable by Rhai.
|
/// Trait implemented by all native Rust functions that are callable by Rhai.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
pub trait NativeCallable {
|
pub trait NativeCallable {
|
||||||
/// Get the ABI type of a native Rust function.
|
/// Get the ABI type of a native Rust function.
|
||||||
@ -86,7 +91,7 @@ pub trait NativeCallable {
|
|||||||
fn call(&self, args: &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
|
fn call(&self, args: &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait implemented by all native Rust functions that are callable by Rhai.
|
/// Trait implemented by all native Rust functions that are callable by Rhai.
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub trait NativeCallable: Send + Sync {
|
pub trait NativeCallable: Send + Sync {
|
||||||
/// Get the ABI type of a native Rust function.
|
/// Get the ABI type of a native Rust function.
|
||||||
|
@ -10,7 +10,7 @@ use crate::result::EvalAltResult;
|
|||||||
|
|
||||||
use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString};
|
use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString};
|
||||||
|
|
||||||
/// A trait to register custom functions with the `Engine`.
|
/// Trait to register custom functions with the `Engine`.
|
||||||
pub trait RegisterFn<FN, ARGS, RET> {
|
pub trait RegisterFn<FN, ARGS, RET> {
|
||||||
/// Register a custom function with the `Engine`.
|
/// Register a custom function with the `Engine`.
|
||||||
///
|
///
|
||||||
@ -42,7 +42,7 @@ pub trait RegisterFn<FN, ARGS, RET> {
|
|||||||
fn register_fn(&mut self, name: &str, f: FN);
|
fn register_fn(&mut self, name: &str, f: FN);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait to register custom functions that return `Dynamic` values with the `Engine`.
|
/// Trait to register custom functions that return `Dynamic` values with the `Engine`.
|
||||||
pub trait RegisterDynamicFn<FN, ARGS> {
|
pub trait RegisterDynamicFn<FN, ARGS> {
|
||||||
/// Register a custom function returning `Dynamic` values with the `Engine`.
|
/// Register a custom function returning `Dynamic` values with the `Engine`.
|
||||||
///
|
///
|
||||||
@ -69,7 +69,7 @@ pub trait RegisterDynamicFn<FN, ARGS> {
|
|||||||
fn register_dynamic_fn(&mut self, name: &str, f: FN);
|
fn register_dynamic_fn(&mut self, name: &str, f: FN);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait to register fallible custom functions returning `Result<_, Box<EvalAltResult>>` with the `Engine`.
|
/// Trait to register fallible custom functions returning `Result<_, Box<EvalAltResult>>` with the `Engine`.
|
||||||
pub trait RegisterResultFn<FN, ARGS, RET> {
|
pub trait RegisterResultFn<FN, ARGS, RET> {
|
||||||
/// Register a custom fallible function with the `Engine`.
|
/// Register a custom fallible function with the `Engine`.
|
||||||
///
|
///
|
||||||
|
@ -91,7 +91,6 @@ mod utils;
|
|||||||
pub use any::Dynamic;
|
pub use any::Dynamic;
|
||||||
pub use engine::Engine;
|
pub use engine::Engine;
|
||||||
pub use error::{ParseError, ParseErrorType};
|
pub use error::{ParseError, ParseErrorType};
|
||||||
pub use fn_call::FuncArgs;
|
|
||||||
pub use fn_native::NativeCallable;
|
pub use fn_native::NativeCallable;
|
||||||
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
||||||
pub use module::Module;
|
pub use module::Module;
|
||||||
@ -116,6 +115,7 @@ pub use parser::FLOAT;
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
pub use module::ModuleResolver;
|
pub use module::ModuleResolver;
|
||||||
|
|
||||||
|
/// Module containing all built-in _module resolvers_ available to Rhai.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
pub mod module_resolvers {
|
pub mod module_resolvers {
|
||||||
pub use crate::module::resolvers::*;
|
pub use crate::module::resolvers::*;
|
||||||
|
@ -51,7 +51,7 @@ pub struct Module {
|
|||||||
all_variables: HashMap<u64, Dynamic>,
|
all_variables: HashMap<u64, Dynamic>,
|
||||||
|
|
||||||
/// External Rust functions.
|
/// External Rust functions.
|
||||||
functions: HashMap<u64, (String, FnAccess, Vec<TypeId>, SharedNativeFunction)>,
|
functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, SharedNativeFunction)>,
|
||||||
|
|
||||||
/// Flattened collection of all external Rust functions, including those in sub-modules.
|
/// Flattened collection of all external Rust functions, including those in sub-modules.
|
||||||
all_functions: HashMap<u64, SharedNativeFunction>,
|
all_functions: HashMap<u64, SharedNativeFunction>,
|
||||||
@ -292,8 +292,9 @@ impl Module {
|
|||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
let func = Arc::new(f);
|
let func = Arc::new(f);
|
||||||
|
|
||||||
self.functions
|
let params = params.into_iter().cloned().collect();
|
||||||
.insert(hash_fn, (name, access, params.to_vec(), func));
|
|
||||||
|
self.functions.insert(hash_fn, (name, access, params, func));
|
||||||
|
|
||||||
hash_fn
|
hash_fn
|
||||||
}
|
}
|
||||||
@ -616,13 +617,13 @@ impl Module {
|
|||||||
pub(crate) fn index_all_sub_modules(&mut self) {
|
pub(crate) fn index_all_sub_modules(&mut self) {
|
||||||
// Collect a particular module.
|
// Collect a particular module.
|
||||||
fn index_module<'a>(
|
fn index_module<'a>(
|
||||||
module: &'a mut Module,
|
module: &'a Module,
|
||||||
qualifiers: &mut Vec<&'a str>,
|
qualifiers: &mut Vec<&'a str>,
|
||||||
variables: &mut Vec<(u64, Dynamic)>,
|
variables: &mut Vec<(u64, Dynamic)>,
|
||||||
functions: &mut Vec<(u64, SharedNativeFunction)>,
|
functions: &mut Vec<(u64, SharedNativeFunction)>,
|
||||||
fn_lib: &mut Vec<(u64, SharedFnDef)>,
|
fn_lib: &mut Vec<(u64, SharedFnDef)>,
|
||||||
) {
|
) {
|
||||||
for (name, m) in module.modules.iter_mut() {
|
for (name, m) in &module.modules {
|
||||||
// Index all the sub-modules first.
|
// Index all the sub-modules first.
|
||||||
qualifiers.push(name);
|
qualifiers.push(name);
|
||||||
index_module(m, qualifiers, variables, functions, fn_lib);
|
index_module(m, qualifiers, variables, functions, fn_lib);
|
||||||
@ -630,7 +631,7 @@ impl Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Index all variables
|
// Index all variables
|
||||||
for (var_name, value) in module.variables.iter() {
|
for (var_name, value) in &module.variables {
|
||||||
// Qualifiers + variable name
|
// Qualifiers + variable name
|
||||||
let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, empty());
|
let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, empty());
|
||||||
variables.push((hash_var, value.clone()));
|
variables.push((hash_var, value.clone()));
|
||||||
@ -716,7 +717,7 @@ impl Module {
|
|||||||
///
|
///
|
||||||
/// A `StaticVec` is used because most module-level access contains only one level,
|
/// A `StaticVec` is used because most module-level access contains only one level,
|
||||||
/// and it is wasteful to always allocate a `Vec` with one element.
|
/// and it is wasteful to always allocate a `Vec` with one element.
|
||||||
#[derive(Clone, Eq, PartialEq, Hash, Default)]
|
#[derive(Clone, Eq, PartialEq, Default)]
|
||||||
pub struct ModuleRef(StaticVec<(String, Position)>, Option<NonZeroUsize>);
|
pub struct ModuleRef(StaticVec<(String, Position)>, Option<NonZeroUsize>);
|
||||||
|
|
||||||
impl fmt::Debug for ModuleRef {
|
impl fmt::Debug for ModuleRef {
|
||||||
@ -769,7 +770,7 @@ impl ModuleRef {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait that encapsulates a module resolution service.
|
/// Trait that encapsulates a module resolution service.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
pub trait ModuleResolver {
|
pub trait ModuleResolver {
|
||||||
@ -783,7 +784,7 @@ pub trait ModuleResolver {
|
|||||||
) -> Result<Module, Box<EvalAltResult>>;
|
) -> Result<Module, Box<EvalAltResult>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait that encapsulates a module resolution service.
|
/// Trait that encapsulates a module resolution service.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub trait ModuleResolver: Send + Sync {
|
pub trait ModuleResolver: Send + Sync {
|
||||||
@ -812,7 +813,7 @@ mod file {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::stdlib::path::PathBuf;
|
use crate::stdlib::path::PathBuf;
|
||||||
|
|
||||||
/// A module resolution service that loads module script files from the file system.
|
/// Module resolution service that loads module script files from the file system.
|
||||||
///
|
///
|
||||||
/// The `new_with_path` and `new_with_path_and_extension` constructor functions
|
/// The `new_with_path` and `new_with_path_and_extension` constructor functions
|
||||||
/// allow specification of a base directory with module path used as a relative path offset
|
/// allow specification of a base directory with module path used as a relative path offset
|
||||||
@ -949,7 +950,7 @@ mod file {
|
|||||||
mod stat {
|
mod stat {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// A module resolution service that serves modules added into it.
|
/// Module resolution service that serves modules added into it.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
@ -10,11 +10,12 @@ use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
|
|||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
||||||
use crate::token::Position;
|
use crate::token::Position;
|
||||||
|
use crate::utils::StaticVec;
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
collections::HashMap,
|
|
||||||
iter::empty,
|
iter::empty,
|
||||||
|
mem,
|
||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
vec,
|
vec,
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
@ -141,7 +142,11 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
|||||||
|
|
||||||
if preserve_result {
|
if preserve_result {
|
||||||
// -> { expr, Noop }
|
// -> { expr, Noop }
|
||||||
Stmt::Block(Box::new((vec![Stmt::Expr(Box::new(expr)), x.1], pos)))
|
let mut statements = StaticVec::new();
|
||||||
|
statements.push(Stmt::Expr(Box::new(expr)));
|
||||||
|
statements.push(x.1);
|
||||||
|
|
||||||
|
Stmt::Block(Box::new((statements, pos)))
|
||||||
} else {
|
} else {
|
||||||
// -> expr
|
// -> expr
|
||||||
Stmt::Expr(Box::new(expr))
|
Stmt::Expr(Box::new(expr))
|
||||||
@ -194,7 +199,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
|||||||
Stmt::Break(pos) => {
|
Stmt::Break(pos) => {
|
||||||
// Only a single break statement - turn into running the guard expression once
|
// Only a single break statement - turn into running the guard expression once
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
let mut statements = vec![Stmt::Expr(Box::new(optimize_expr(expr, state)))];
|
let mut statements = StaticVec::new();
|
||||||
|
statements.push(Stmt::Expr(Box::new(optimize_expr(expr, state))));
|
||||||
if preserve_result {
|
if preserve_result {
|
||||||
statements.push(Stmt::Noop(pos))
|
statements.push(Stmt::Noop(pos))
|
||||||
}
|
}
|
||||||
@ -325,13 +331,13 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
|||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
// Only one let/import statement - leave it alone
|
// Only one let/import statement - leave it alone
|
||||||
[Stmt::Let(_)] | [Stmt::Import(_)] => Stmt::Block(Box::new((result, pos))),
|
[Stmt::Let(_)] | [Stmt::Import(_)] => Stmt::Block(Box::new((result.into(), pos))),
|
||||||
// Only one statement - promote
|
// Only one statement - promote
|
||||||
[_] => {
|
[_] => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
result.remove(0)
|
result.remove(0)
|
||||||
}
|
}
|
||||||
_ => Stmt::Block(Box::new((result, pos))),
|
_ => Stmt::Block(Box::new((result.into(), pos))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// expr;
|
// expr;
|
||||||
@ -417,7 +423,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
// Array literal where everything is pure - promote the indexed item.
|
// Array literal where everything is pure - promote the indexed item.
|
||||||
// All other items can be thrown away.
|
// All other items can be thrown away.
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
a.0.remove(i.0 as usize).set_position(a.1)
|
a.0.take(i.0 as usize).set_position(a.1)
|
||||||
}
|
}
|
||||||
// map[string]
|
// map[string]
|
||||||
(Expr::Map(m), Expr::StringConstant(s)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
|
(Expr::Map(m), Expr::StringConstant(s)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
|
||||||
@ -441,14 +447,12 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
// [ items .. ]
|
// [ items .. ]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(a) => Expr::Array(Box::new((a.0
|
Expr::Array(a) => Expr::Array(Box::new((a.0
|
||||||
.into_iter()
|
.into_iter().map(|expr| optimize_expr(expr, state))
|
||||||
.map(|expr| optimize_expr(expr, state))
|
.collect(), a.1))),
|
||||||
.collect(), a.1))),
|
|
||||||
// [ items .. ]
|
// [ items .. ]
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Map(m) => Expr::Map(Box::new((m.0
|
Expr::Map(m) => Expr::Map(Box::new((m.0
|
||||||
.into_iter()
|
.into_iter().map(|((key, pos), expr)| ((key, pos), optimize_expr(expr, state)))
|
||||||
.map(|((key, pos), expr)| ((key, pos), optimize_expr(expr, state)))
|
|
||||||
.collect(), m.1))),
|
.collect(), m.1))),
|
||||||
// lhs in rhs
|
// lhs in rhs
|
||||||
Expr::In(x) => match (x.0, x.1) {
|
Expr::In(x) => match (x.0, x.1) {
|
||||||
@ -547,8 +551,8 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
return Expr::FnCall(x);
|
return Expr::FnCall(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
|
let mut arg_values: StaticVec<_> = args.iter().map(Expr::get_constant_value).collect();
|
||||||
let mut call_args: Vec<_> = arg_values.iter_mut().collect();
|
let mut call_args: StaticVec<_> = arg_values.iter_mut().collect();
|
||||||
|
|
||||||
// Save the typename of the first argument if it is `type_of()`
|
// Save the typename of the first argument if it is `type_of()`
|
||||||
// This is to avoid `call_args` being passed into the closure
|
// This is to avoid `call_args` being passed into the closure
|
||||||
@ -558,7 +562,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
""
|
""
|
||||||
};
|
};
|
||||||
|
|
||||||
call_fn(&state.engine.packages, &state.engine.global_module, name, &mut call_args, *pos).ok()
|
call_fn(&state.engine.packages, &state.engine.global_module, name, call_args.as_mut(), *pos).ok()
|
||||||
.and_then(|result|
|
.and_then(|result|
|
||||||
result.or_else(|| {
|
result.or_else(|| {
|
||||||
if !arg_for_type_of.is_empty() {
|
if !arg_for_type_of.is_empty() {
|
||||||
@ -698,11 +702,14 @@ pub fn optimize_into_ast(
|
|||||||
const level: OptimizationLevel = OptimizationLevel::None;
|
const level: OptimizationLevel = OptimizationLevel::None;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
let fn_lib: Vec<_> = functions
|
let fn_lib_values: StaticVec<_> = functions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|fn_def| (fn_def.name.as_str(), fn_def.params.len()))
|
.map(|fn_def| (fn_def.name.as_str(), fn_def.params.len()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
let fn_lib = fn_lib_values.as_ref();
|
||||||
|
|
||||||
#[cfg(feature = "no_function")]
|
#[cfg(feature = "no_function")]
|
||||||
const fn_lib: &[(&str, usize)] = &[];
|
const fn_lib: &[(&str, usize)] = &[];
|
||||||
|
|
||||||
@ -712,7 +719,7 @@ pub fn optimize_into_ast(
|
|||||||
let pos = fn_def.body.position();
|
let pos = fn_def.body.position();
|
||||||
|
|
||||||
// Optimize the function body
|
// Optimize the function body
|
||||||
let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &fn_lib, level);
|
let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), fn_lib, level);
|
||||||
|
|
||||||
// {} -> Noop
|
// {} -> Noop
|
||||||
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
|
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
|
||||||
@ -738,7 +745,7 @@ pub fn optimize_into_ast(
|
|||||||
match level {
|
match level {
|
||||||
OptimizationLevel::None => statements,
|
OptimizationLevel::None => statements,
|
||||||
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
||||||
optimize(statements, engine, &scope, &fn_lib, level)
|
optimize(statements, engine, &scope, fn_lib, level)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
lib,
|
lib,
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
//! This module contains all built-in _packages_ available to Rhai, plus facilities to define custom packages.
|
//! Module containing all built-in _packages_ available to Rhai, plus facilities to define custom packages.
|
||||||
|
|
||||||
use crate::fn_native::{NativeCallable, SharedIteratorFunction};
|
use crate::fn_native::{NativeCallable, SharedIteratorFunction};
|
||||||
use crate::module::Module;
|
use crate::module::Module;
|
||||||
|
use crate::utils::StaticVec;
|
||||||
|
|
||||||
use crate::stdlib::{any::TypeId, boxed::Box, collections::HashMap, rc::Rc, sync::Arc, vec::Vec};
|
use crate::stdlib::{any::TypeId, boxed::Box, collections::HashMap, rc::Rc, sync::Arc, vec::Vec};
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ pub type PackageLibrary = Arc<Module>;
|
|||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub(crate) struct PackagesCollection {
|
pub(crate) struct PackagesCollection {
|
||||||
/// Collection of `PackageLibrary` instances.
|
/// Collection of `PackageLibrary` instances.
|
||||||
packages: Vec<PackageLibrary>,
|
packages: StaticVec<PackageLibrary>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PackagesCollection {
|
impl PackagesCollection {
|
||||||
@ -89,7 +90,7 @@ impl PackagesCollection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This macro makes it easy to define a _package_ (which is basically a shared module)
|
/// Macro that makes it easy to define a _package_ (which is basically a shared module)
|
||||||
/// and register functions into it.
|
/// and register functions into it.
|
||||||
///
|
///
|
||||||
/// Functions can be added to the package using the standard module methods such as
|
/// Functions can be added to the package using the standard module methods such as
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::def_package;
|
use crate::def_package;
|
||||||
use crate::module::FuncReturn;
|
use crate::module::FuncReturn;
|
||||||
use crate::parser::INT;
|
use crate::parser::INT;
|
||||||
|
use crate::utils::StaticVec;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
use crate::engine::Array;
|
use crate::engine::Array;
|
||||||
@ -29,7 +30,7 @@ fn sub_string(s: &mut String, start: INT, len: INT) -> FuncReturn<String> {
|
|||||||
start as usize
|
start as usize
|
||||||
};
|
};
|
||||||
|
|
||||||
let chars: Vec<_> = s.chars().collect();
|
let chars: StaticVec<_> = s.chars().collect();
|
||||||
|
|
||||||
let len = if offset + (len as usize) > chars.len() {
|
let len = if offset + (len as usize) > chars.len() {
|
||||||
chars.len() - offset
|
chars.len() - offset
|
||||||
@ -37,7 +38,7 @@ fn sub_string(s: &mut String, start: INT, len: INT) -> FuncReturn<String> {
|
|||||||
len as usize
|
len as usize
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(chars[offset..][..len].into_iter().collect())
|
Ok(chars.iter().skip(offset).take(len).cloned().collect())
|
||||||
}
|
}
|
||||||
fn crop_string(s: &mut String, start: INT, len: INT) -> FuncReturn<()> {
|
fn crop_string(s: &mut String, start: INT, len: INT) -> FuncReturn<()> {
|
||||||
let offset = if s.is_empty() || len <= 0 {
|
let offset = if s.is_empty() || len <= 0 {
|
||||||
@ -52,7 +53,7 @@ fn crop_string(s: &mut String, start: INT, len: INT) -> FuncReturn<()> {
|
|||||||
start as usize
|
start as usize
|
||||||
};
|
};
|
||||||
|
|
||||||
let chars: Vec<_> = s.chars().collect();
|
let chars: StaticVec<_> = s.chars().collect();
|
||||||
|
|
||||||
let len = if offset + (len as usize) > chars.len() {
|
let len = if offset + (len as usize) > chars.len() {
|
||||||
chars.len() - offset
|
chars.len() - offset
|
||||||
@ -62,8 +63,10 @@ fn crop_string(s: &mut String, start: INT, len: INT) -> FuncReturn<()> {
|
|||||||
|
|
||||||
s.clear();
|
s.clear();
|
||||||
|
|
||||||
chars[offset..][..len]
|
chars
|
||||||
.into_iter()
|
.iter()
|
||||||
|
.skip(offset)
|
||||||
|
.take(len)
|
||||||
.for_each(|&ch| s.push(ch));
|
.for_each(|&ch| s.push(ch));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -189,9 +192,9 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|
|||||||
"truncate",
|
"truncate",
|
||||||
|s: &mut String, len: INT| {
|
|s: &mut String, len: INT| {
|
||||||
if len >= 0 {
|
if len >= 0 {
|
||||||
let chars: Vec<_> = s.chars().take(len as usize).collect();
|
let chars: StaticVec<_> = s.chars().take(len as usize).collect();
|
||||||
s.clear();
|
s.clear();
|
||||||
chars.into_iter().for_each(|ch| s.push(ch));
|
chars.iter().for_each(|&ch| s.push(ch));
|
||||||
} else {
|
} else {
|
||||||
s.clear();
|
s.clear();
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ use crate::error::{LexError, ParseError, ParseErrorType};
|
|||||||
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
||||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||||
use crate::token::{Position, Token, TokenIterator};
|
use crate::token::{Position, Token, TokenIterator};
|
||||||
use crate::utils::EMPTY_TYPE_ID;
|
use crate::utils::{StaticVec, EMPTY_TYPE_ID};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
use crate::module::ModuleRef;
|
use crate::module::ModuleRef;
|
||||||
@ -195,7 +195,7 @@ pub struct FnDef {
|
|||||||
/// Function access mode.
|
/// Function access mode.
|
||||||
pub access: FnAccess,
|
pub access: FnAccess,
|
||||||
/// Names of function parameters.
|
/// Names of function parameters.
|
||||||
pub params: Vec<String>,
|
pub params: StaticVec<String>,
|
||||||
/// Function body.
|
/// Function body.
|
||||||
pub body: Stmt,
|
pub body: Stmt,
|
||||||
/// Position of the function definition.
|
/// Position of the function definition.
|
||||||
@ -294,7 +294,7 @@ pub enum Stmt {
|
|||||||
/// const id = expr
|
/// const id = expr
|
||||||
Const(Box<((String, Position), Expr)>),
|
Const(Box<((String, Position), Expr)>),
|
||||||
/// { stmt; ... }
|
/// { stmt; ... }
|
||||||
Block(Box<(Vec<Stmt>, Position)>),
|
Block(Box<(StaticVec<Stmt>, Position)>),
|
||||||
/// { stmt }
|
/// { stmt }
|
||||||
Expr(Box<Expr>),
|
Expr(Box<Expr>),
|
||||||
/// continue
|
/// continue
|
||||||
@ -306,7 +306,13 @@ pub enum Stmt {
|
|||||||
/// import expr as module
|
/// import expr as module
|
||||||
Import(Box<(Expr, (String, Position))>),
|
Import(Box<(Expr, (String, Position))>),
|
||||||
/// expr id as name, ...
|
/// expr id as name, ...
|
||||||
Export(Box<Vec<((String, Position), Option<(String, Position)>)>>),
|
Export(Box<StaticVec<((String, Position), Option<(String, Position)>)>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Stmt {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Noop(Default::default())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stmt {
|
impl Stmt {
|
||||||
@ -324,7 +330,7 @@ impl Stmt {
|
|||||||
Stmt::Loop(x) => x.position(),
|
Stmt::Loop(x) => x.position(),
|
||||||
Stmt::For(x) => x.2.position(),
|
Stmt::For(x) => x.2.position(),
|
||||||
Stmt::Import(x) => (x.1).1,
|
Stmt::Import(x) => (x.1).1,
|
||||||
Stmt::Export(x) => (x.get(0).unwrap().0).1,
|
Stmt::Export(x) => (x.get(0).0).1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,7 +412,7 @@ pub enum Expr {
|
|||||||
(Cow<'static, str>, Position),
|
(Cow<'static, str>, Position),
|
||||||
MRef,
|
MRef,
|
||||||
u64,
|
u64,
|
||||||
Vec<Expr>,
|
StaticVec<Expr>,
|
||||||
Option<Dynamic>,
|
Option<Dynamic>,
|
||||||
)>,
|
)>,
|
||||||
),
|
),
|
||||||
@ -417,9 +423,9 @@ pub enum Expr {
|
|||||||
/// expr[expr]
|
/// expr[expr]
|
||||||
Index(Box<(Expr, Expr, Position)>),
|
Index(Box<(Expr, Expr, Position)>),
|
||||||
/// [ expr, ... ]
|
/// [ expr, ... ]
|
||||||
Array(Box<(Vec<Expr>, Position)>),
|
Array(Box<(StaticVec<Expr>, Position)>),
|
||||||
/// #{ name:expr, ... }
|
/// #{ name:expr, ... }
|
||||||
Map(Box<(Vec<((String, Position), Expr)>, Position)>),
|
Map(Box<(StaticVec<((String, Position), Expr)>, Position)>),
|
||||||
/// lhs in rhs
|
/// lhs in rhs
|
||||||
In(Box<(Expr, Expr, Position)>),
|
In(Box<(Expr, Expr, Position)>),
|
||||||
/// lhs && rhs
|
/// lhs && rhs
|
||||||
@ -434,6 +440,12 @@ pub enum Expr {
|
|||||||
Unit(Position),
|
Unit(Position),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Expr {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Unit(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
/// Get the `Dynamic` value of a constant expression.
|
/// Get the `Dynamic` value of a constant expression.
|
||||||
///
|
///
|
||||||
@ -713,7 +725,7 @@ fn parse_call_expr<'a>(
|
|||||||
begin: Position,
|
begin: Position,
|
||||||
allow_stmt_expr: bool,
|
allow_stmt_expr: bool,
|
||||||
) -> Result<Expr, Box<ParseError>> {
|
) -> Result<Expr, Box<ParseError>> {
|
||||||
let mut args = Vec::new();
|
let mut args = StaticVec::new();
|
||||||
|
|
||||||
match input.peek().unwrap() {
|
match input.peek().unwrap() {
|
||||||
// id <EOF>
|
// id <EOF>
|
||||||
@ -733,7 +745,7 @@ fn parse_call_expr<'a>(
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let hash_fn_def = {
|
let hash_fn_def = {
|
||||||
if let Some(modules) = modules.as_mut() {
|
if let Some(modules) = modules.as_mut() {
|
||||||
modules.set_index(stack.find_module(&modules.get_ref(0).0));
|
modules.set_index(stack.find_module(&modules.get(0).0));
|
||||||
|
|
||||||
// Rust functions are indexed in two steps:
|
// Rust functions are indexed in two steps:
|
||||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||||
@ -774,7 +786,7 @@ fn parse_call_expr<'a>(
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
let hash_fn_def = {
|
let hash_fn_def = {
|
||||||
if let Some(modules) = modules.as_mut() {
|
if let Some(modules) = modules.as_mut() {
|
||||||
modules.set_index(stack.find_module(&modules.get_ref(0).0));
|
modules.set_index(stack.find_module(&modules.get(0).0));
|
||||||
|
|
||||||
// Rust functions are indexed in two steps:
|
// Rust functions are indexed in two steps:
|
||||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||||
@ -1013,7 +1025,7 @@ fn parse_array_literal<'a>(
|
|||||||
pos: Position,
|
pos: Position,
|
||||||
allow_stmt_expr: bool,
|
allow_stmt_expr: bool,
|
||||||
) -> Result<Expr, Box<ParseError>> {
|
) -> Result<Expr, Box<ParseError>> {
|
||||||
let mut arr = Vec::new();
|
let mut arr = StaticVec::new();
|
||||||
|
|
||||||
if !match_token(input, Token::RightBracket)? {
|
if !match_token(input, Token::RightBracket)? {
|
||||||
while !input.peek().unwrap().0.is_eof() {
|
while !input.peek().unwrap().0.is_eof() {
|
||||||
@ -1056,7 +1068,7 @@ fn parse_map_literal<'a>(
|
|||||||
pos: Position,
|
pos: Position,
|
||||||
allow_stmt_expr: bool,
|
allow_stmt_expr: bool,
|
||||||
) -> Result<Expr, Box<ParseError>> {
|
) -> Result<Expr, Box<ParseError>> {
|
||||||
let mut map = Vec::new();
|
let mut map = StaticVec::new();
|
||||||
|
|
||||||
if !match_token(input, Token::RightBrace)? {
|
if !match_token(input, Token::RightBrace)? {
|
||||||
while !input.peek().unwrap().0.is_eof() {
|
while !input.peek().unwrap().0.is_eof() {
|
||||||
@ -1239,7 +1251,7 @@ fn parse_primary<'a>(
|
|||||||
|
|
||||||
// Qualifiers + variable name
|
// Qualifiers + variable name
|
||||||
*hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, empty());
|
*hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, empty());
|
||||||
modules.set_index(stack.find_module(&modules.get_ref(0).0));
|
modules.set_index(stack.find_module(&modules.get(0).0));
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
@ -1296,15 +1308,17 @@ fn parse_unary<'a>(
|
|||||||
Expr::FloatConstant(x) => Ok(Expr::FloatConstant(Box::new((-x.0, x.1)))),
|
Expr::FloatConstant(x) => Ok(Expr::FloatConstant(Box::new((-x.0, x.1)))),
|
||||||
|
|
||||||
// Call negative function
|
// Call negative function
|
||||||
e => {
|
expr => {
|
||||||
let op = "-";
|
let op = "-";
|
||||||
let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2));
|
let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2));
|
||||||
|
let mut args = StaticVec::new();
|
||||||
|
args.push(expr);
|
||||||
|
|
||||||
Ok(Expr::FnCall(Box::new((
|
Ok(Expr::FnCall(Box::new((
|
||||||
(op.into(), pos),
|
(op.into(), pos),
|
||||||
None,
|
None,
|
||||||
hash,
|
hash,
|
||||||
vec![e],
|
args,
|
||||||
None,
|
None,
|
||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
@ -1318,7 +1332,8 @@ fn parse_unary<'a>(
|
|||||||
// !expr
|
// !expr
|
||||||
(Token::Bang, _) => {
|
(Token::Bang, _) => {
|
||||||
let pos = eat_token(input, Token::Bang);
|
let pos = eat_token(input, Token::Bang);
|
||||||
let expr = vec![parse_primary(input, stack, allow_stmt_expr)?];
|
let mut args = StaticVec::new();
|
||||||
|
args.push(parse_primary(input, stack, allow_stmt_expr)?);
|
||||||
|
|
||||||
let op = "!";
|
let op = "!";
|
||||||
let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2));
|
let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2));
|
||||||
@ -1327,7 +1342,7 @@ fn parse_unary<'a>(
|
|||||||
(op.into(), pos),
|
(op.into(), pos),
|
||||||
None,
|
None,
|
||||||
hash,
|
hash,
|
||||||
expr,
|
args,
|
||||||
Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false
|
Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false
|
||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
@ -1412,9 +1427,13 @@ fn parse_op_assignment_stmt<'a>(
|
|||||||
let rhs = parse_expr(input, stack, allow_stmt_expr)?;
|
let rhs = parse_expr(input, stack, allow_stmt_expr)?;
|
||||||
|
|
||||||
// lhs op= rhs -> lhs = op(lhs, rhs)
|
// lhs op= rhs -> lhs = op(lhs, rhs)
|
||||||
let args = vec![lhs_copy, rhs];
|
let mut args = StaticVec::new();
|
||||||
|
args.push(lhs_copy);
|
||||||
|
args.push(rhs);
|
||||||
|
|
||||||
let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(args.len()));
|
let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(args.len()));
|
||||||
let rhs_expr = Expr::FnCall(Box::new(((op.into(), pos), None, hash, args, None)));
|
let rhs_expr = Expr::FnCall(Box::new(((op.into(), pos), None, hash, args, None)));
|
||||||
|
|
||||||
make_assignment_stmt(stack, lhs, rhs_expr, pos)
|
make_assignment_stmt(stack, lhs, rhs_expr, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1452,7 +1471,7 @@ fn make_dot_expr(
|
|||||||
#[cfg(feature = "no_module")]
|
#[cfg(feature = "no_module")]
|
||||||
unreachable!();
|
unreachable!();
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get_ref(0).1));
|
return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get(0).1));
|
||||||
}
|
}
|
||||||
// lhs.dot_lhs.dot_rhs
|
// lhs.dot_lhs.dot_rhs
|
||||||
(lhs, Expr::Dot(x)) => {
|
(lhs, Expr::Dot(x)) => {
|
||||||
@ -1695,7 +1714,10 @@ fn parse_binary_op<'a>(
|
|||||||
let cmp_def = Some(false.into());
|
let cmp_def = Some(false.into());
|
||||||
let op = op_token.syntax();
|
let op = op_token.syntax();
|
||||||
let hash = calc_fn_hash(empty(), &op, repeat(EMPTY_TYPE_ID()).take(2));
|
let hash = calc_fn_hash(empty(), &op, repeat(EMPTY_TYPE_ID()).take(2));
|
||||||
let mut args = vec![current_lhs, rhs];
|
|
||||||
|
let mut args = StaticVec::new();
|
||||||
|
args.push(current_lhs);
|
||||||
|
args.push(rhs);
|
||||||
|
|
||||||
current_lhs = match op_token {
|
current_lhs = match op_token {
|
||||||
Token::Plus => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
|
Token::Plus => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
|
||||||
@ -1721,13 +1743,13 @@ fn parse_binary_op<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Token::Or => {
|
Token::Or => {
|
||||||
let rhs = args.pop().unwrap();
|
let rhs = args.pop();
|
||||||
let current_lhs = args.pop().unwrap();
|
let current_lhs = args.pop();
|
||||||
Expr::Or(Box::new((current_lhs, rhs, pos)))
|
Expr::Or(Box::new((current_lhs, rhs, pos)))
|
||||||
}
|
}
|
||||||
Token::And => {
|
Token::And => {
|
||||||
let rhs = args.pop().unwrap();
|
let rhs = args.pop();
|
||||||
let current_lhs = args.pop().unwrap();
|
let current_lhs = args.pop();
|
||||||
Expr::And(Box::new((current_lhs, rhs, pos)))
|
Expr::And(Box::new((current_lhs, rhs, pos)))
|
||||||
}
|
}
|
||||||
Token::Ampersand => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
|
Token::Ampersand => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
|
||||||
@ -1735,15 +1757,15 @@ fn parse_binary_op<'a>(
|
|||||||
Token::XOr => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
|
Token::XOr => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))),
|
||||||
|
|
||||||
Token::In => {
|
Token::In => {
|
||||||
let rhs = args.pop().unwrap();
|
let rhs = args.pop();
|
||||||
let current_lhs = args.pop().unwrap();
|
let current_lhs = args.pop();
|
||||||
make_in_expr(current_lhs, rhs, pos)?
|
make_in_expr(current_lhs, rhs, pos)?
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Token::Period => {
|
Token::Period => {
|
||||||
let mut rhs = args.pop().unwrap();
|
let mut rhs = args.pop();
|
||||||
let current_lhs = args.pop().unwrap();
|
let current_lhs = args.pop();
|
||||||
|
|
||||||
match &mut rhs {
|
match &mut rhs {
|
||||||
// current_lhs.rhs(...) - method call
|
// current_lhs.rhs(...) - method call
|
||||||
@ -2025,7 +2047,7 @@ fn parse_import<'a>(
|
|||||||
fn parse_export<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Box<ParseError>> {
|
fn parse_export<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Box<ParseError>> {
|
||||||
eat_token(input, Token::Export);
|
eat_token(input, Token::Export);
|
||||||
|
|
||||||
let mut exports = Vec::new();
|
let mut exports = StaticVec::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (id, id_pos) = match input.next().unwrap() {
|
let (id, id_pos) = match input.next().unwrap() {
|
||||||
@ -2098,7 +2120,7 @@ fn parse_block<'a>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut statements = Vec::new();
|
let mut statements = StaticVec::new();
|
||||||
let prev_len = stack.len();
|
let prev_len = stack.len();
|
||||||
|
|
||||||
while !match_token(input, Token::RightBrace)? {
|
while !match_token(input, Token::RightBrace)? {
|
||||||
|
@ -79,8 +79,14 @@ pub enum EvalAltResult {
|
|||||||
ErrorDotExpr(String, Position),
|
ErrorDotExpr(String, Position),
|
||||||
/// Arithmetic error encountered. Wrapped value is the error message.
|
/// Arithmetic error encountered. Wrapped value is the error message.
|
||||||
ErrorArithmetic(String, Position),
|
ErrorArithmetic(String, Position),
|
||||||
|
/// Number of operations over maximum limit.
|
||||||
|
ErrorTooManyOperations(Position),
|
||||||
|
/// Modules over maximum limit.
|
||||||
|
ErrorTooManyModules(Position),
|
||||||
/// Call stack over maximum limit.
|
/// Call stack over maximum limit.
|
||||||
ErrorStackOverflow(Position),
|
ErrorStackOverflow(Position),
|
||||||
|
/// The script is prematurely terminated.
|
||||||
|
ErrorTerminated(Position),
|
||||||
/// Run-time error encountered. Wrapped value is the error message.
|
/// Run-time error encountered. Wrapped value is the error message.
|
||||||
ErrorRuntime(String, Position),
|
ErrorRuntime(String, Position),
|
||||||
|
|
||||||
@ -137,7 +143,10 @@ impl EvalAltResult {
|
|||||||
Self::ErrorInExpr(_) => "Malformed 'in' expression",
|
Self::ErrorInExpr(_) => "Malformed 'in' expression",
|
||||||
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
||||||
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
||||||
|
Self::ErrorTooManyOperations(_) => "Too many operations",
|
||||||
|
Self::ErrorTooManyModules(_) => "Too many modules imported",
|
||||||
Self::ErrorStackOverflow(_) => "Stack overflow",
|
Self::ErrorStackOverflow(_) => "Stack overflow",
|
||||||
|
Self::ErrorTerminated(_) => "Script terminated.",
|
||||||
Self::ErrorRuntime(_, _) => "Runtime error",
|
Self::ErrorRuntime(_, _) => "Runtime error",
|
||||||
Self::ErrorLoopBreak(true, _) => "Break statement not inside a loop",
|
Self::ErrorLoopBreak(true, _) => "Break statement not inside a loop",
|
||||||
Self::ErrorLoopBreak(false, _) => "Continue statement not inside a loop",
|
Self::ErrorLoopBreak(false, _) => "Continue statement not inside a loop",
|
||||||
@ -183,7 +192,10 @@ impl fmt::Display for EvalAltResult {
|
|||||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||||
| Self::ErrorInExpr(pos)
|
| Self::ErrorInExpr(pos)
|
||||||
| Self::ErrorDotExpr(_, pos)
|
| Self::ErrorDotExpr(_, pos)
|
||||||
| Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos),
|
| Self::ErrorTooManyOperations(pos)
|
||||||
|
| Self::ErrorTooManyModules(pos)
|
||||||
|
| Self::ErrorStackOverflow(pos)
|
||||||
|
| Self::ErrorTerminated(pos) => write!(f, "{} ({})", desc, pos),
|
||||||
|
|
||||||
Self::ErrorRuntime(s, pos) => {
|
Self::ErrorRuntime(s, pos) => {
|
||||||
write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos)
|
write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos)
|
||||||
@ -299,7 +311,10 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorInExpr(pos)
|
| Self::ErrorInExpr(pos)
|
||||||
| Self::ErrorDotExpr(_, pos)
|
| Self::ErrorDotExpr(_, pos)
|
||||||
| Self::ErrorArithmetic(_, pos)
|
| Self::ErrorArithmetic(_, pos)
|
||||||
|
| Self::ErrorTooManyOperations(pos)
|
||||||
|
| Self::ErrorTooManyModules(pos)
|
||||||
| Self::ErrorStackOverflow(pos)
|
| Self::ErrorStackOverflow(pos)
|
||||||
|
| Self::ErrorTerminated(pos)
|
||||||
| Self::ErrorRuntime(_, pos)
|
| Self::ErrorRuntime(_, pos)
|
||||||
| Self::ErrorLoopBreak(_, pos)
|
| Self::ErrorLoopBreak(_, pos)
|
||||||
| Self::Return(_, pos) => *pos,
|
| Self::Return(_, pos) => *pos,
|
||||||
@ -335,7 +350,10 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorInExpr(pos)
|
| Self::ErrorInExpr(pos)
|
||||||
| Self::ErrorDotExpr(_, pos)
|
| Self::ErrorDotExpr(_, pos)
|
||||||
| Self::ErrorArithmetic(_, pos)
|
| Self::ErrorArithmetic(_, pos)
|
||||||
|
| Self::ErrorTooManyOperations(pos)
|
||||||
|
| Self::ErrorTooManyModules(pos)
|
||||||
| Self::ErrorStackOverflow(pos)
|
| Self::ErrorStackOverflow(pos)
|
||||||
|
| Self::ErrorTerminated(pos)
|
||||||
| Self::ErrorRuntime(_, pos)
|
| Self::ErrorRuntime(_, pos)
|
||||||
| Self::ErrorLoopBreak(_, pos)
|
| Self::ErrorLoopBreak(_, pos)
|
||||||
| Self::Return(_, pos) => *pos = new_position,
|
| Self::Return(_, pos) => *pos = new_position,
|
||||||
|
@ -36,7 +36,7 @@ pub struct Entry<'a> {
|
|||||||
pub expr: Option<Box<Expr>>,
|
pub expr: Option<Box<Expr>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type containing information about the current scope.
|
/// Type containing information about the current scope.
|
||||||
/// Useful for keeping state between `Engine` evaluation runs.
|
/// Useful for keeping state between `Engine` evaluation runs.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use crate::error::LexError;
|
use crate::error::LexError;
|
||||||
use crate::parser::INT;
|
use crate::parser::INT;
|
||||||
|
use crate::utils::StaticVec;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
use crate::parser::FLOAT;
|
use crate::parser::FLOAT;
|
||||||
@ -425,7 +426,7 @@ pub struct TokenIterator<'a> {
|
|||||||
/// Current position.
|
/// Current position.
|
||||||
pos: Position,
|
pos: Position,
|
||||||
/// The input character streams.
|
/// The input character streams.
|
||||||
streams: Vec<Peekable<Chars<'a>>>,
|
streams: StaticVec<Peekable<Chars<'a>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TokenIterator<'a> {
|
impl<'a> TokenIterator<'a> {
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
use crate::any::Variant;
|
use crate::any::Variant;
|
||||||
use crate::engine::State;
|
use crate::engine::State;
|
||||||
use crate::utils::StaticVec;
|
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
@ -10,7 +9,6 @@ use crate::stdlib::{
|
|||||||
boxed::Box,
|
boxed::Box,
|
||||||
mem, ptr,
|
mem, ptr,
|
||||||
string::ToString,
|
string::ToString,
|
||||||
vec::Vec,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Cast a type into another type.
|
/// Cast a type into another type.
|
||||||
@ -49,20 +47,17 @@ pub fn unsafe_cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, B
|
|||||||
/// current `Scope` without cloning the variable name. Doing this is safe because all local
|
/// current `Scope` without cloning the variable name. Doing this is safe because all local
|
||||||
/// variables in the `Scope` are cleared out before existing the block.
|
/// variables in the `Scope` are cleared out before existing the block.
|
||||||
///
|
///
|
||||||
/// Force-casting a local variable lifetime to the current `Scope`'s larger lifetime saves
|
/// Force-casting a local variable's lifetime to the current `Scope`'s larger lifetime saves
|
||||||
/// on allocations and string cloning, thus avoids us having to maintain a chain of `Scope`'s.
|
/// on allocations and string cloning, thus avoids us having to maintain a chain of `Scope`'s.
|
||||||
pub fn unsafe_cast_var_name<'s>(name: &str, state: &State) -> Cow<'s, str> {
|
pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str, state: &State) -> Cow<'s, str> {
|
||||||
// If not at global level, we can force-cast
|
// If not at global level, we can force-cast
|
||||||
if state.scope_level > 0 {
|
if state.scope_level > 0 {
|
||||||
// WARNING - force-cast the variable name into the scope's lifetime to avoid cloning it
|
// WARNING - force-cast the variable name into the scope's lifetime to avoid cloning it
|
||||||
// this is safe because all local variables are cleared at the end of the block
|
// this is safe because all local variables are cleared at the end of the block
|
||||||
unsafe { mem::transmute::<_, &'s str>(name) }.into()
|
unsafe { mem::transmute::<_, &'s str>(name) }.into()
|
||||||
} else {
|
} else {
|
||||||
|
// The variable is introduced at global (top) level and may persist after the script run.
|
||||||
|
// Therefore, clone the variable name.
|
||||||
name.to_string().into()
|
name.to_string().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provide a type instance that is memory-zeroed.
|
|
||||||
pub fn unsafe_zeroed<T>() -> T {
|
|
||||||
unsafe { mem::MaybeUninit::zeroed().assume_init() }
|
|
||||||
}
|
|
||||||
|
446
src/utils.rs
446
src/utils.rs
@ -1,6 +1,8 @@
|
|||||||
//! Module containing various utility types and functions.
|
//! Module containing various utility types and functions.
|
||||||
|
//!
|
||||||
use crate::r#unsafe::unsafe_zeroed;
|
//! # Safety
|
||||||
|
//!
|
||||||
|
//! The `StaticVec` type has some `unsafe` blocks to handle conversions between `MaybeUninit` and regular types.
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
@ -8,6 +10,8 @@ use crate::stdlib::{
|
|||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
iter::FromIterator,
|
iter::FromIterator,
|
||||||
mem,
|
mem,
|
||||||
|
mem::MaybeUninit,
|
||||||
|
ops::{Drop, Index, IndexMut},
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,29 +51,107 @@ pub fn calc_fn_spec<'a>(
|
|||||||
s.finish()
|
s.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type to hold a number of values in static storage for speed, and any spill-overs in a `Vec`.
|
/// A type to hold a number of values in static storage for no-allocation, quick access.
|
||||||
|
/// If too many items are stored, it converts into using a `Vec`.
|
||||||
///
|
///
|
||||||
/// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate.
|
/// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate.
|
||||||
/// This simplified implementation here is to avoid pulling in another crate.
|
/// This simplified implementation here is to avoid pulling in another crate.
|
||||||
///
|
///
|
||||||
|
/// # Implementation
|
||||||
|
///
|
||||||
|
/// A `StaticVec` holds data in _either one_ of two storages: 1) a fixed-size array of `MAX_STATIC_VEC`
|
||||||
|
/// items, and 2) a dynamic `Vec`. At any time, either one of them (or both) must be empty, depending on the
|
||||||
|
/// total number of items.
|
||||||
|
///
|
||||||
|
/// There is a `len` field containing the total number of items held by the `StaticVec`.
|
||||||
|
///
|
||||||
|
/// The fixed-size array (`list`) is not initialized (i.e. initialized with `MaybeUninit::uninit()`).
|
||||||
|
///
|
||||||
|
/// When `len <= MAX_STATIC_VEC`, all elements are stored in the fixed-size array.
|
||||||
|
/// Array slots `>= len` are `MaybeUninit::uninit()` while slots `< len` are considered actual data.
|
||||||
|
/// In this scenario, the `Vec` (`more`) is empty.
|
||||||
|
///
|
||||||
|
/// As soon as we try to push a new item into the `StaticVec` that makes the total number exceed
|
||||||
|
/// `MAX_STATIC_VEC`, all the items in the fixed-sized array are taken out, replaced with
|
||||||
|
/// `MaybeUninit::uninit()` (via `mem::replace`) and pushed into the `Vec`.
|
||||||
|
/// Then the new item is added to the `Vec`.
|
||||||
|
///
|
||||||
|
/// Therefore, if `len > MAX_STATIC_VEC`, then the fixed-size array (`list`) is considered
|
||||||
|
/// empty and uninitialized while all data resides in the `Vec` (`more`).
|
||||||
|
///
|
||||||
|
/// When popping an item off of the `StaticVec`, the reverse is true. When `len = MAX_STATIC_VEC + 1`,
|
||||||
|
/// after popping the item, all the items residing in the `Vec` are moved back to the fixed-size array (`list`).
|
||||||
|
/// The `Vec` will then be empty.
|
||||||
|
///
|
||||||
|
/// Therefore, if `len <= MAX_STATIC_VEC`, data is in the fixed-size array (`list`).
|
||||||
|
/// Otherwise, data is in the `Vec` (`more`).
|
||||||
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// This type uses some unsafe code (mainly to zero out unused array slots) for efficiency.
|
/// This type uses some unsafe code (mainly for uninitialized/unused array slots) for efficiency.
|
||||||
//
|
//
|
||||||
// TODO - remove unsafe code
|
// TODO - remove unsafe code
|
||||||
#[derive(Clone, Hash)]
|
|
||||||
pub struct StaticVec<T> {
|
pub struct StaticVec<T> {
|
||||||
/// Total number of values held.
|
/// Total number of values held.
|
||||||
len: usize,
|
len: usize,
|
||||||
/// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection.
|
/// Fixed-size storage for fast, no-allocation access.
|
||||||
list: [T; 4],
|
list: [MaybeUninit<T>; MAX_STATIC_VEC],
|
||||||
/// Dynamic storage. For spill-overs.
|
/// Dynamic storage. For spill-overs.
|
||||||
more: Vec<T>,
|
more: Vec<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Maximum slots of fixed-size storage for a `StaticVec`.
|
||||||
|
/// 4 slots should be enough for most cases.
|
||||||
|
const MAX_STATIC_VEC: usize = 4;
|
||||||
|
|
||||||
|
impl<T> Drop for StaticVec<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for StaticVec<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
len: 0,
|
||||||
|
list: unsafe { mem::MaybeUninit::uninit().assume_init() },
|
||||||
|
more: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: PartialEq> PartialEq for StaticVec<T> {
|
impl<T: PartialEq> PartialEq for StaticVec<T> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.len == other.len && self.list == other.list && self.more == other.more
|
if self.len != other.len || self.more != other.more {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.len > MAX_STATIC_VEC {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
mem::transmute::<_, &[T; MAX_STATIC_VEC]>(&self.list)
|
||||||
|
== mem::transmute::<_, &[T; MAX_STATIC_VEC]>(&other.list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> Clone for StaticVec<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
let mut value: Self = Default::default();
|
||||||
|
value.len = self.len;
|
||||||
|
|
||||||
|
if self.is_fixed_storage() {
|
||||||
|
for x in 0..self.len {
|
||||||
|
let item: &T = unsafe { mem::transmute(self.list.get(x).unwrap()) };
|
||||||
|
value.list[x] = MaybeUninit::new(item.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value.more = self.more.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,35 +169,121 @@ impl<T> FromIterator<T> for StaticVec<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Default for StaticVec<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
len: 0,
|
|
||||||
list: unsafe_zeroed(),
|
|
||||||
more: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> StaticVec<T> {
|
impl<T> StaticVec<T> {
|
||||||
/// Create a new `StaticVec`.
|
/// Create a new `StaticVec`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
/// Empty the `StaticVec`.
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
if self.is_fixed_storage() {
|
||||||
|
for x in 0..self.len {
|
||||||
|
self.extract_from_list(x);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.more.clear();
|
||||||
|
}
|
||||||
|
self.len = 0;
|
||||||
|
}
|
||||||
|
/// Extract a `MaybeUninit` into a concrete initialized type.
|
||||||
|
fn extract(value: MaybeUninit<T>) -> T {
|
||||||
|
unsafe { value.assume_init() }
|
||||||
|
}
|
||||||
|
/// Extract an item from the fixed-size array, replacing it with `MaybeUninit::uninit()`.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if fixed-size storage is not used, or if the `index` is out of bounds.
|
||||||
|
fn extract_from_list(&mut self, index: usize) -> T {
|
||||||
|
if !self.is_fixed_storage() {
|
||||||
|
panic!("not fixed storage in StaticVec");
|
||||||
|
}
|
||||||
|
if index >= self.len {
|
||||||
|
panic!("index OOB in StaticVec");
|
||||||
|
}
|
||||||
|
Self::extract(mem::replace(
|
||||||
|
self.list.get_mut(index).unwrap(),
|
||||||
|
MaybeUninit::uninit(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
/// Set an item into the fixed-size array.
|
||||||
|
/// If `drop` is `true`, the original value is extracted then automatically dropped.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if fixed-size storage is not used, or if the `index` is out of bounds.
|
||||||
|
fn set_into_list(&mut self, index: usize, value: T, drop: bool) {
|
||||||
|
if !self.is_fixed_storage() {
|
||||||
|
panic!("not fixed storage in StaticVec");
|
||||||
|
}
|
||||||
|
// Allow setting at most one slot to the right
|
||||||
|
if index > self.len {
|
||||||
|
panic!("index OOB in StaticVec");
|
||||||
|
}
|
||||||
|
let temp = mem::replace(self.list.get_mut(index).unwrap(), MaybeUninit::new(value));
|
||||||
|
if drop {
|
||||||
|
// Extract the original value - which will drop it automatically
|
||||||
|
Self::extract(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Move item in the fixed-size array into the `Vec`.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if fixed-size storage is not used, or if the fixed-size storage is not full.
|
||||||
|
fn move_fixed_into_vec(&mut self, num: usize) {
|
||||||
|
if !self.is_fixed_storage() {
|
||||||
|
panic!("not fixed storage in StaticVec");
|
||||||
|
}
|
||||||
|
if self.len != num {
|
||||||
|
panic!("fixed storage is not full in StaticVec");
|
||||||
|
}
|
||||||
|
self.more.extend(
|
||||||
|
self.list
|
||||||
|
.iter_mut()
|
||||||
|
.take(num)
|
||||||
|
.map(|v| mem::replace(v, MaybeUninit::uninit()))
|
||||||
|
.map(Self::extract),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/// Is data stored in fixed-size storage?
|
||||||
|
fn is_fixed_storage(&self) -> bool {
|
||||||
|
self.len <= MAX_STATIC_VEC
|
||||||
|
}
|
||||||
/// Push a new value to the end of this `StaticVec`.
|
/// Push a new value to the end of this `StaticVec`.
|
||||||
pub fn push<X: Into<T>>(&mut self, value: X) {
|
pub fn push<X: Into<T>>(&mut self, value: X) {
|
||||||
if self.len == self.list.len() {
|
if self.len == MAX_STATIC_VEC {
|
||||||
// Move the fixed list to the Vec
|
self.move_fixed_into_vec(MAX_STATIC_VEC);
|
||||||
for x in 0..self.list.len() {
|
|
||||||
let def_val: T = unsafe_zeroed();
|
|
||||||
self.more
|
|
||||||
.push(mem::replace(self.list.get_mut(x).unwrap(), def_val));
|
|
||||||
}
|
|
||||||
self.more.push(value.into());
|
|
||||||
} else if self.len > self.list.len() {
|
|
||||||
self.more.push(value.into());
|
self.more.push(value.into());
|
||||||
|
} else if self.is_fixed_storage() {
|
||||||
|
self.set_into_list(self.len, value.into(), false);
|
||||||
} else {
|
} else {
|
||||||
self.list[self.len] = value.into();
|
self.more.push(value.into());
|
||||||
|
}
|
||||||
|
self.len += 1;
|
||||||
|
}
|
||||||
|
/// Insert a new value to this `StaticVec` at a particular position.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `index` is out of bounds.
|
||||||
|
pub fn insert<X: Into<T>>(&mut self, index: usize, value: X) {
|
||||||
|
if index > self.len {
|
||||||
|
panic!("index OOB in StaticVec");
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.len == MAX_STATIC_VEC {
|
||||||
|
self.move_fixed_into_vec(MAX_STATIC_VEC);
|
||||||
|
self.more.insert(index, value.into());
|
||||||
|
} else if self.is_fixed_storage() {
|
||||||
|
// Move all items one slot to the right
|
||||||
|
for x in (index..self.len).rev() {
|
||||||
|
let orig_value = self.extract_from_list(x);
|
||||||
|
self.set_into_list(x + 1, orig_value, false);
|
||||||
|
}
|
||||||
|
self.set_into_list(index, value.into(), false);
|
||||||
|
} else {
|
||||||
|
self.more.insert(index, value.into());
|
||||||
}
|
}
|
||||||
self.len += 1;
|
self.len += 1;
|
||||||
}
|
}
|
||||||
@ -125,22 +293,62 @@ impl<T> StaticVec<T> {
|
|||||||
///
|
///
|
||||||
/// Panics if the `StaticVec` is empty.
|
/// Panics if the `StaticVec` is empty.
|
||||||
pub fn pop(&mut self) -> T {
|
pub fn pop(&mut self) -> T {
|
||||||
let result = if self.len <= 0 {
|
if self.is_empty() {
|
||||||
panic!("nothing to pop!")
|
panic!("nothing to pop!");
|
||||||
} else if self.len <= self.list.len() {
|
}
|
||||||
let def_val: T = unsafe_zeroed();
|
|
||||||
mem::replace(self.list.get_mut(self.len - 1).unwrap(), def_val)
|
let result = if self.is_fixed_storage() {
|
||||||
|
self.extract_from_list(self.len - 1)
|
||||||
} else {
|
} else {
|
||||||
let r = self.more.pop().unwrap();
|
let value = self.more.pop().unwrap();
|
||||||
|
|
||||||
// Move back to the fixed list
|
// Move back to the fixed list
|
||||||
if self.more.len() == self.list.len() {
|
if self.more.len() == MAX_STATIC_VEC {
|
||||||
for x in 0..self.list.len() {
|
for index in (0..MAX_STATIC_VEC).rev() {
|
||||||
self.list[self.list.len() - 1 - x] = self.more.pop().unwrap();
|
let item = self.more.pop().unwrap();
|
||||||
|
self.set_into_list(index, item, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r
|
value
|
||||||
|
};
|
||||||
|
|
||||||
|
self.len -= 1;
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
/// Remove a value from this `StaticVec` at a particular position.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `index` is out of bounds.
|
||||||
|
pub fn remove(&mut self, index: usize) -> T {
|
||||||
|
if index >= self.len {
|
||||||
|
panic!("index OOB in StaticVec");
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = if self.is_fixed_storage() {
|
||||||
|
let value = self.extract_from_list(index);
|
||||||
|
|
||||||
|
// Move all items one slot to the left
|
||||||
|
for x in index..self.len - 1 {
|
||||||
|
let orig_value = self.extract_from_list(x + 1);
|
||||||
|
self.set_into_list(x, orig_value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
let value = self.more.remove(index);
|
||||||
|
|
||||||
|
// Move back to the fixed list
|
||||||
|
if self.more.len() == MAX_STATIC_VEC {
|
||||||
|
for index in (0..MAX_STATIC_VEC).rev() {
|
||||||
|
let item = self.more.pop().unwrap();
|
||||||
|
self.set_into_list(index, item, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value
|
||||||
};
|
};
|
||||||
|
|
||||||
self.len -= 1;
|
self.len -= 1;
|
||||||
@ -151,18 +359,24 @@ impl<T> StaticVec<T> {
|
|||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.len
|
self.len
|
||||||
}
|
}
|
||||||
|
/// Is this `StaticVec` empty?
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len == 0
|
||||||
|
}
|
||||||
/// Get a reference to the item at a particular index.
|
/// Get a reference to the item at a particular index.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the index is out of bounds.
|
/// Panics if `index` is out of bounds.
|
||||||
pub fn get_ref(&self, index: usize) -> &T {
|
pub fn get(&self, index: usize) -> &T {
|
||||||
if index >= self.len {
|
if index >= self.len {
|
||||||
panic!("index OOB in StaticVec");
|
panic!("index OOB in StaticVec");
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.len < self.list.len() {
|
let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) };
|
||||||
self.list.get(index).unwrap()
|
|
||||||
|
if self.is_fixed_storage() {
|
||||||
|
list.get(index).unwrap()
|
||||||
} else {
|
} else {
|
||||||
self.more.get(index).unwrap()
|
self.more.get(index).unwrap()
|
||||||
}
|
}
|
||||||
@ -171,52 +385,106 @@ impl<T> StaticVec<T> {
|
|||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the index is out of bounds.
|
/// Panics if `index` is out of bounds.
|
||||||
pub fn get_mut(&mut self, index: usize) -> &mut T {
|
pub fn get_mut(&mut self, index: usize) -> &mut T {
|
||||||
if index >= self.len {
|
if index >= self.len {
|
||||||
panic!("index OOB in StaticVec");
|
panic!("index OOB in StaticVec");
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.len < self.list.len() {
|
let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) };
|
||||||
self.list.get_mut(index).unwrap()
|
|
||||||
|
if self.is_fixed_storage() {
|
||||||
|
list.get_mut(index).unwrap()
|
||||||
} else {
|
} else {
|
||||||
self.more.get_mut(index).unwrap()
|
self.more.get_mut(index).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get an iterator to entries in the `StaticVec`.
|
/// Get an iterator to entries in the `StaticVec`.
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||||
if self.len > self.list.len() {
|
let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) };
|
||||||
self.more.iter()
|
|
||||||
|
if self.is_fixed_storage() {
|
||||||
|
list[..self.len].iter()
|
||||||
} else {
|
} else {
|
||||||
self.list[..self.len].iter()
|
self.more.iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get a mutable iterator to entries in the `StaticVec`.
|
/// Get a mutable iterator to entries in the `StaticVec`.
|
||||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
|
||||||
if self.len > self.list.len() {
|
let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) };
|
||||||
self.more.iter_mut()
|
|
||||||
|
if self.is_fixed_storage() {
|
||||||
|
list[..self.len].iter_mut()
|
||||||
} else {
|
} else {
|
||||||
self.list[..self.len].iter_mut()
|
self.more.iter_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Copy> StaticVec<T> {
|
impl<T: 'static> StaticVec<T> {
|
||||||
/// Get the item at a particular index.
|
/// Get a mutable iterator to entries in the `StaticVec`.
|
||||||
|
pub fn into_iter(mut self) -> Box<dyn Iterator<Item = T>> {
|
||||||
|
if self.is_fixed_storage() {
|
||||||
|
let mut it = FixedStorageIterator {
|
||||||
|
data: unsafe { mem::MaybeUninit::uninit().assume_init() },
|
||||||
|
index: 0,
|
||||||
|
limit: self.len,
|
||||||
|
};
|
||||||
|
|
||||||
|
for x in 0..self.len {
|
||||||
|
it.data[x] = mem::replace(self.list.get_mut(x).unwrap(), MaybeUninit::uninit());
|
||||||
|
}
|
||||||
|
self.len = 0;
|
||||||
|
|
||||||
|
Box::new(it)
|
||||||
|
} else {
|
||||||
|
Box::new(Vec::from(self).into_iter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator that takes control of the fixed-size storage of a `StaticVec` and returns its values.
|
||||||
|
struct FixedStorageIterator<T> {
|
||||||
|
data: [MaybeUninit<T>; MAX_STATIC_VEC],
|
||||||
|
index: usize,
|
||||||
|
limit: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Iterator for FixedStorageIterator<T> {
|
||||||
|
type Item = T;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.index >= self.limit {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.index += 1;
|
||||||
|
|
||||||
|
let value = mem::replace(
|
||||||
|
self.data.get_mut(self.index - 1).unwrap(),
|
||||||
|
MaybeUninit::uninit(),
|
||||||
|
);
|
||||||
|
|
||||||
|
unsafe { Some(value.assume_init()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Default> StaticVec<T> {
|
||||||
|
/// Get the item at a particular index, replacing it with the default.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the index is out of bounds.
|
/// Panics if `index` is out of bounds.
|
||||||
pub fn get(&self, index: usize) -> T {
|
pub fn take(&mut self, index: usize) -> T {
|
||||||
if index >= self.len {
|
if index >= self.len {
|
||||||
panic!("index OOB in StaticVec");
|
panic!("index OOB in StaticVec");
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.len < self.list.len() {
|
mem::take(if self.is_fixed_storage() {
|
||||||
*self.list.get(index).unwrap()
|
unsafe { mem::transmute(self.list.get_mut(index).unwrap()) }
|
||||||
} else {
|
} else {
|
||||||
*self.more.get(index).unwrap()
|
self.more.get_mut(index).unwrap()
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,20 +498,68 @@ impl<T: fmt::Debug> fmt::Debug for StaticVec<T> {
|
|||||||
|
|
||||||
impl<T> AsRef<[T]> for StaticVec<T> {
|
impl<T> AsRef<[T]> for StaticVec<T> {
|
||||||
fn as_ref(&self) -> &[T] {
|
fn as_ref(&self) -> &[T] {
|
||||||
if self.len > self.list.len() {
|
let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) };
|
||||||
&self.more[..]
|
|
||||||
|
if self.is_fixed_storage() {
|
||||||
|
&list[..self.len]
|
||||||
} else {
|
} else {
|
||||||
&self.list[..self.len]
|
&self.more[..]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> AsMut<[T]> for StaticVec<T> {
|
impl<T> AsMut<[T]> for StaticVec<T> {
|
||||||
fn as_mut(&mut self) -> &mut [T] {
|
fn as_mut(&mut self) -> &mut [T] {
|
||||||
if self.len > self.list.len() {
|
let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) };
|
||||||
&mut self.more[..]
|
|
||||||
|
if self.is_fixed_storage() {
|
||||||
|
&mut list[..self.len]
|
||||||
} else {
|
} else {
|
||||||
&mut self.list[..self.len]
|
&mut self.more[..]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Index<usize> for StaticVec<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
self.get(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IndexMut<usize> for StaticVec<T> {
|
||||||
|
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||||
|
self.get_mut(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<StaticVec<T>> for Vec<T> {
|
||||||
|
fn from(mut value: StaticVec<T>) -> Self {
|
||||||
|
if value.len <= MAX_STATIC_VEC {
|
||||||
|
value.move_fixed_into_vec(value.len);
|
||||||
|
}
|
||||||
|
value.len = 0;
|
||||||
|
|
||||||
|
let mut arr = Self::new();
|
||||||
|
arr.append(&mut value.more);
|
||||||
|
arr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<Vec<T>> for StaticVec<T> {
|
||||||
|
fn from(mut value: Vec<T>) -> Self {
|
||||||
|
let mut arr: Self = Default::default();
|
||||||
|
arr.len = value.len();
|
||||||
|
|
||||||
|
if arr.len <= MAX_STATIC_VEC {
|
||||||
|
for x in (0..arr.len).rev() {
|
||||||
|
arr.set_into_list(x, value.pop().unwrap(), false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
arr.more = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
arr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -43,20 +43,20 @@ fn test_get_set() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.register_fn("new_ts", TestStruct::new);
|
engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
engine.register_indexer(|value: &mut TestStruct, index: INT| value.array[index as usize]);
|
engine.register_indexer(|value: &mut TestStruct, index: String| value.array[index.len()]);
|
||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x = 500; a.x")?, 500);
|
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x = 500; a.x")?, 500);
|
||||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42);
|
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42);
|
||||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.y.add(); a.y")?, 0);
|
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.y.add(); a.y")?, 0);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a[3]")?, 4);
|
assert_eq!(engine.eval::<INT>(r#"let a = new_ts(); a["abc"]"#)?, 4);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_big_get_set() -> Result<(), Box<EvalAltResult>> {
|
fn test_get_set_chain() -> Result<(), Box<EvalAltResult>> {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TestChild {
|
struct TestChild {
|
||||||
x: INT,
|
x: INT,
|
||||||
|
@ -72,13 +72,72 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r#"
|
r#"
|
||||||
import "hello" as h;
|
import "hello" as h1;
|
||||||
h::answer
|
import "hello" as h2;
|
||||||
|
h2::answer
|
||||||
"#
|
"#
|
||||||
)?,
|
)?,
|
||||||
42
|
42
|
||||||
);
|
);
|
||||||
|
|
||||||
|
engine.set_max_modules(5);
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
*engine
|
||||||
|
.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let x = 0;
|
||||||
|
|
||||||
|
for x in range(0, 10) {
|
||||||
|
import "hello" as h;
|
||||||
|
x += h::answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorTooManyModules(_)
|
||||||
|
));
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
assert!(matches!(
|
||||||
|
*engine
|
||||||
|
.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
let x = 0;
|
||||||
|
|
||||||
|
fn foo() {
|
||||||
|
import "hello" as h;
|
||||||
|
x += h::answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
for x in range(0, 10) {
|
||||||
|
foo();
|
||||||
|
}
|
||||||
|
|
||||||
|
x
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorInFunctionCall(fn_name, _, _) if fn_name == "foo"
|
||||||
|
));
|
||||||
|
|
||||||
|
engine.set_max_modules(0);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
engine.eval::<()>(
|
||||||
|
r#"
|
||||||
|
fn foo() {
|
||||||
|
import "hello" as h;
|
||||||
|
}
|
||||||
|
|
||||||
|
for x in range(0, 10) {
|
||||||
|
foo();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
113
tests/operations.rs
Normal file
113
tests/operations.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
#![cfg(not(feature = "unchecked"))]
|
||||||
|
use rhai::{Engine, EvalAltResult};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_max_operations() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
engine.set_max_operations(500);
|
||||||
|
|
||||||
|
engine.on_progress(|count| {
|
||||||
|
if count % 100 == 0 {
|
||||||
|
println!("{}", count);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.eval::<()>("let x = 0; while x < 20 { x += 1; }")?;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
*engine
|
||||||
|
.eval::<()>("for x in range(0, 500) {}")
|
||||||
|
.expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorTooManyOperations(_)
|
||||||
|
));
|
||||||
|
|
||||||
|
engine.set_max_operations(0);
|
||||||
|
|
||||||
|
engine.eval::<()>("for x in range(0, 10000) {}")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_max_operations_functions() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
engine.set_max_operations(500);
|
||||||
|
|
||||||
|
engine.on_progress(|count| {
|
||||||
|
if count % 100 == 0 {
|
||||||
|
println!("{}", count);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.eval::<()>(
|
||||||
|
r#"
|
||||||
|
print("Test1");
|
||||||
|
let x = 0;
|
||||||
|
|
||||||
|
while x < 28 {
|
||||||
|
print(x);
|
||||||
|
x += 1;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
engine.eval::<()>(
|
||||||
|
r#"
|
||||||
|
print("Test2");
|
||||||
|
fn inc(x) { x + 1 }
|
||||||
|
let x = 0;
|
||||||
|
while x < 20 { x = inc(x); }
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
assert!(matches!(
|
||||||
|
*engine
|
||||||
|
.eval::<()>(
|
||||||
|
r#"
|
||||||
|
print("Test3");
|
||||||
|
fn inc(x) { x + 1 }
|
||||||
|
let x = 0;
|
||||||
|
|
||||||
|
while x < 28 {
|
||||||
|
print(x);
|
||||||
|
x = inc(x);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorTooManyOperations(_)
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_max_operations_eval() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
engine.set_max_operations(500);
|
||||||
|
|
||||||
|
engine.on_progress(|count| {
|
||||||
|
if count % 100 == 0 {
|
||||||
|
println!("{}", count);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
*engine
|
||||||
|
.eval::<()>(
|
||||||
|
r#"
|
||||||
|
let script = "for x in range(0, 500) {}";
|
||||||
|
eval(script);
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.expect_err("should error"),
|
||||||
|
EvalAltResult::ErrorTooManyOperations(_)
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -15,6 +15,7 @@ fn test_stack_overflow() -> Result<(), Box<EvalAltResult>> {
|
|||||||
325
|
325
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "unchecked"))]
|
||||||
match engine.eval::<()>(
|
match engine.eval::<()>(
|
||||||
r"
|
r"
|
||||||
fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } }
|
fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } }
|
||||||
|
Loading…
Reference in New Issue
Block a user