Merge branch 'master' into plugins
This commit is contained in:
commit
39cd1c8413
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rhai"
|
||||
version = "0.14.1"
|
||||
version = "0.14.2"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
|
||||
description = "Embedded scripting for Rust"
|
||||
|
365
README.md
365
README.md
@ -13,24 +13,30 @@ to add scripting to any application.
|
||||
|
||||
Rhai's current features set:
|
||||
|
||||
* Easy-to-use language similar to JS+Rust
|
||||
* Easy integration with Rust [native functions](#working-with-functions) and [types](#custom-types-and-methods),
|
||||
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 variables/constants into a script via an external [`Scope`]
|
||||
* 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 script runner app)
|
||||
* [`no-std`](#optional-features) support
|
||||
* Support for [function overloading](#function-overloading)
|
||||
* Support for [operator overloading](#operator-overloading)
|
||||
* Support for loading external [modules]
|
||||
* Compiled script is [optimized](#script-optimization) for repeat evaluations
|
||||
* Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features)
|
||||
* Easy-to-use language similar to JS+Rust with dynamic typing but _no_ garbage collector.
|
||||
* 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).
|
||||
* Freely pass Rust variables/constants into a script via an external [`Scope`].
|
||||
* Easily [call a script-defined function](#calling-rhai-functions-from-rust) from Rust.
|
||||
* Low compile-time overhead (~0.6 sec debug/~3 sec release for `rhai_runner` sample 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, and most `unsafe` code is limited to
|
||||
one single source file, all with names starting with `"unsafe_"`).
|
||||
* Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature).
|
||||
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment without explicit permission.
|
||||
* Rugged (protection against [stack-overflow](#maximum-call-stack-depth) and [runaway scripts](#maximum-number-of-operations) etc.).
|
||||
* Track script evaluation [progress](#tracking-progress) and manually terminate a script run.
|
||||
* [`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/)
|
||||
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`.
|
||||
|
||||
**Note:** Currently, the version is 0.14.1, so the language and API's may change before they stabilize.
|
||||
**Note:** Currently, the version is 0.14.2, so the language and API's may change before they stabilize.
|
||||
|
||||
Installation
|
||||
------------
|
||||
@ -39,7 +45,7 @@ Install the Rhai crate by adding this line to `dependencies`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rhai = "0.14.1"
|
||||
rhai = "0.14.2"
|
||||
```
|
||||
|
||||
Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/):
|
||||
@ -62,23 +68,24 @@ Beware that in order to use pre-releases (e.g. alpha and beta), the exact versio
|
||||
Optional features
|
||||
-----------------
|
||||
|
||||
| Feature | Description |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
|
||||
| `no_function` | Disable script-defined functions if not needed. |
|
||||
| `no_index` | Disable [arrays] and indexing features if not needed. |
|
||||
| `no_object` | Disable support for custom types and objects. |
|
||||
| `no_float` | Disable floating-point numbers and math if not needed. |
|
||||
| `no_optimize` | Disable the script optimizer. |
|
||||
| `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_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. |
|
||||
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Engine`], [`Scope`] and `AST` are all `Send + Sync`. |
|
||||
| Feature | Description |
|
||||
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `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. |
|
||||
| `no_index` | Disable [arrays] and indexing features. |
|
||||
| `no_object` | Disable support for custom types and object maps. |
|
||||
| `no_float` | Disable floating-point numbers and math. |
|
||||
| `no_optimize` | Disable the script optimizer. |
|
||||
| `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_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. |
|
||||
| `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.
|
||||
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
|
||||
[`no_index`]: #optional-features
|
||||
@ -104,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
|
||||
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
|
||||
|
||||
@ -113,6 +120,8 @@ the correct linker flags are used in `cargo.toml`:
|
||||
|
||||
```toml
|
||||
[profile.release]
|
||||
lto = "fat" # turn on Link-Time Optimizations
|
||||
codegen-units = 1 # trade compile time with maximum optimization
|
||||
opt-level = "z" # optimize for size
|
||||
```
|
||||
|
||||
@ -126,7 +135,8 @@ Disable script-defined functions (`no_function`) only when the feature is not ne
|
||||
|
||||
[`Engine::new_raw`](#raw-engine) creates a _raw_ engine which does not register _any_ utility functions.
|
||||
This makes the scripting language quite useless as even basic arithmetic operators are not supported.
|
||||
Selectively include the necessary operators by loading specific [packages](#packages) while minimizing the code footprint.
|
||||
Selectively include the necessary functionalities by loading specific [packages] to minimize the footprint.
|
||||
Packages are sharable (even across threads via the [`sync`] feature), so they only have to be created once.
|
||||
|
||||
Related
|
||||
-------
|
||||
@ -267,7 +277,7 @@ let ast = engine.compile_file("hello_world.rhai".into())?;
|
||||
|
||||
### 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]).
|
||||
|
||||
```rust
|
||||
@ -370,8 +380,11 @@ Use `Engine::new_raw` to create a _raw_ `Engine`, in which _nothing_ is added, n
|
||||
|
||||
### Packages
|
||||
|
||||
Rhai functional features are provided in different _packages_ that can be loaded via a call to `load_package`.
|
||||
Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package` must be imported in order for
|
||||
[package]: #packages
|
||||
[packages]: #packages
|
||||
|
||||
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 to be used.
|
||||
|
||||
```rust
|
||||
@ -382,7 +395,7 @@ use rhai::packages::CorePackage; // the 'core' package contains b
|
||||
let mut engine = Engine::new_raw(); // create a 'raw' Engine
|
||||
let package = CorePackage::new(); // create a package - can be shared among multiple `Engine` instances
|
||||
|
||||
engine.load_package(package.get()); // load the package manually
|
||||
engine.load_package(package.get()); // load the package manually. 'get' returns a reference to the shared package
|
||||
```
|
||||
|
||||
The follow packages are available:
|
||||
@ -398,9 +411,24 @@ The follow packages are available:
|
||||
| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes |
|
||||
| `BasicArrayPackage` | Basic [array] functions | No | Yes |
|
||||
| `BasicMapPackage` | Basic [object map] functions | No | Yes |
|
||||
| `EvalPackage` | Disable [`eval`] | No | No |
|
||||
| `CorePackage` | Basic essentials | | |
|
||||
| `StandardPackage` | Standard library | | |
|
||||
|
||||
Packages typically contain Rust functions that are callable within a Rhai script.
|
||||
All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers).
|
||||
Once a package is created (e.g. via `new`), it can be _shared_ (via `get`) among multiple instances of [`Engine`],
|
||||
even across threads (if the [`sync`] feature is turned on).
|
||||
Therefore, a package only has to be created _once_.
|
||||
|
||||
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
|
||||
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
|
||||
the `import` statement).
|
||||
|
||||
Custom packages can also be created. See the macro [`def_package!`](https://docs.rs/rhai/0.13.0/rhai/macro.def_package.html).
|
||||
|
||||
Evaluate expressions only
|
||||
-------------------------
|
||||
|
||||
@ -743,7 +771,7 @@ Because they [_short-circuit_](#boolean-operators), `&&` and `||` are handled sp
|
||||
overriding them has no effect at all.
|
||||
|
||||
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.
|
||||
|
||||
```rust
|
||||
@ -762,11 +790,17 @@ println!("result: {}", result); // prints 42
|
||||
let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded
|
||||
|
||||
println!("result: {}", result); // prints 1.0
|
||||
|
||||
fn mixed_add(a: i64, b: f64) -> f64 { (a as f64) + b }
|
||||
|
||||
engine.register_fn("+", mixed_add); // register '+' operator for an integer and a float
|
||||
|
||||
let result: i64 = engine.eval("1 + 1.0"); // prints 2.0 (normally an error)
|
||||
```
|
||||
|
||||
Use operator overloading for custom types (described below) only. Be very careful when overloading built-in operators because
|
||||
script writers expect standard operators to behave in a consistent and predictable manner, and will be annoyed if a calculation
|
||||
for '+' turns into a subtraction, for example.
|
||||
Use operator overloading for custom types (described below) only.
|
||||
Be very careful when overloading built-in operators because script writers expect standard operators to behave in a
|
||||
consistent and predictable manner, and will be annoyed if a calculation for '`+`' turns into a subtraction, for example.
|
||||
|
||||
Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`].
|
||||
See the [relevant section](#script-optimization) for more details.
|
||||
@ -941,7 +975,7 @@ Indexers
|
||||
--------
|
||||
|
||||
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).
|
||||
|
||||
```rust
|
||||
@ -1036,12 +1070,13 @@ fn main() -> Result<(), Box<EvalAltResult>>
|
||||
Engine configuration options
|
||||
---------------------------
|
||||
|
||||
| Method | Description |
|
||||
| ------------------------ | ---------------------------------------------------------------------------------------- |
|
||||
| `set_optimization_level` | Set the amount of script _optimizations_ performed. See [`script optimization`]. |
|
||||
| `set_max_call_levels` | Set the maximum number of function call levels (default 50) to avoid infinite recursion. |
|
||||
|
||||
[`script optimization`]: #script-optimization
|
||||
| Method | Description |
|
||||
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `set_optimization_level` | Set the amount of script _optimizations_ performed. See [script optimization]. |
|
||||
| `set_max_expr_depths` | Set the maximum nesting levels of an expression/statement. See [maximum statement depth](#maximum-statement-depth). |
|
||||
| `set_max_call_levels` | Set the maximum number of function call levels (default 50) to avoid infinite recursion. See [maximum call stack depth](#maximum-call-stack-depth). |
|
||||
| `set_max_operations` | Set the maximum number of _operations_ that a script is allowed to consume. See [maximum number of operations](#maximum-number-of-operations). |
|
||||
| `set_max_modules` | Set the maximum number of [modules] that a script is allowed to load. See [maximum number of modules](#maximum-number-of-modules). |
|
||||
|
||||
-------
|
||||
|
||||
@ -1328,7 +1363,7 @@ The following standard methods (defined in the [`MoreStringPackage`](#packages)
|
||||
| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
|
||||
| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
|
||||
| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
|
||||
| `replace` | target sub-string, replacement string | replaces a sub-string with another |
|
||||
| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another |
|
||||
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
|
||||
|
||||
### Examples
|
||||
@ -2021,7 +2056,7 @@ debug("world!"); // prints "world!" to stdout using debug formatting
|
||||
### Overriding `print` and `debug` with callback functions
|
||||
|
||||
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
|
||||
// Any function or closure that takes an '&str' argument can be used to override
|
||||
@ -2048,21 +2083,21 @@ for entry in logbook.read().unwrap().iter() {
|
||||
}
|
||||
```
|
||||
|
||||
Using external modules
|
||||
----------------------
|
||||
Modules
|
||||
-------
|
||||
|
||||
[module]: #using-external-modules
|
||||
[modules]: #using-external-modules
|
||||
[module]: #modules
|
||||
[modules]: #modules
|
||||
|
||||
Rhai allows organizing code (functions and variables) into _modules_.
|
||||
Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_.
|
||||
Modules can be disabled via the [`no_module`] feature.
|
||||
|
||||
### Exporting variables and functions
|
||||
### Exporting variables and functions from modules
|
||||
|
||||
A module is a single script (or pre-compiled `AST`) containing global variables and functions.
|
||||
A _module_ is a single script (or pre-compiled `AST`) containing global variables and functions.
|
||||
The `export` statement, which can only be at global level, exposes selected variables as members of a module.
|
||||
Variables not exported are private and invisible to the outside.
|
||||
All functions are automatically exported, unless it is prefixed with `private`.
|
||||
Variables not exported are _private_ and invisible to the outside.
|
||||
On the other hand, all functions are automatically exported, _unless_ it is explicitly opt-out with the `private` prefix.
|
||||
Functions declared `private` are invisible to the outside.
|
||||
|
||||
Everything exported from a module is **constant** (**read-only**).
|
||||
@ -2070,11 +2105,11 @@ Everything exported from a module is **constant** (**read-only**).
|
||||
```rust
|
||||
// This is a module script.
|
||||
|
||||
fn inc(x) { x + 1 } // public function
|
||||
fn inc(x) { x + 1 } // script-defined function - default public
|
||||
|
||||
private fn foo() {} // private function - invisible to outside
|
||||
|
||||
let private = 123; // variable not exported - invisible to outside
|
||||
let private = 123; // variable not exported - default invisible to outside
|
||||
let x = 42; // this will be exported below
|
||||
|
||||
export x; // the variable 'x' is exported under its own name
|
||||
@ -2085,19 +2120,27 @@ export x as answer; // the variable 'x' is exported under the alias 'ans
|
||||
|
||||
### Importing modules
|
||||
|
||||
A module can be _imported_ via the `import` statement, and its members accessed via '`::`' similar to C++.
|
||||
[`import`]: #importing-modules
|
||||
|
||||
A module can be _imported_ via the `import` statement, and its members are accessed via '`::`' similar to C++.
|
||||
|
||||
```rust
|
||||
import "crypto" as crypto; // import the script file 'crypto.rhai' as a module
|
||||
|
||||
crypto::encrypt(secret); // use functions defined under the module via '::'
|
||||
|
||||
crypto::hash::sha256(key); // sub-modules are also supported
|
||||
|
||||
print(crypto::status); // module variables are constants
|
||||
|
||||
crypto::hash::sha256(key); // sub-modules are also supported
|
||||
crypto::status = "off"; // <- runtime error - cannot modify a constant
|
||||
```
|
||||
|
||||
`import` statements are _scoped_, meaning that they are only accessible inside the scope that they're imported.
|
||||
They can appear anywhere a normal statement can be, but in the vast majority of cases `import` statements are
|
||||
group at the beginning of a script. It is not advised to deviate from this common practice unless there is
|
||||
a _Very Good Reason™_. Especially, do not place an `import` statement within a loop; doing so will repeatedly
|
||||
re-load the same module during every iteration of the loop!
|
||||
|
||||
```rust
|
||||
let mod = "crypto";
|
||||
@ -2110,9 +2153,15 @@ if secured { // new block scope
|
||||
|
||||
crypto::encrypt(others); // <- this causes a run-time error because the 'crypto' module
|
||||
// is no longer available!
|
||||
|
||||
for x in range(0, 1000) {
|
||||
import "crypto" as c; // <- importing a module inside a loop is a Very Bad Idea™
|
||||
|
||||
c.encrypt(something);
|
||||
}
|
||||
```
|
||||
|
||||
### Creating custom modules from Rust
|
||||
### Creating custom modules with Rust
|
||||
|
||||
To load a custom module (written in Rust) into an [`Engine`], first create a `Module` type, add variables/functions into it,
|
||||
then finally push it into a custom [`Scope`]. This has the equivalent effect of putting an `import` statement
|
||||
@ -2141,8 +2190,9 @@ engine.eval_expression_with_scope::<i64>(&scope, "question::inc(question::answer
|
||||
|
||||
### Creating a module from an `AST`
|
||||
|
||||
It is easy to convert a pre-compiled `AST` into a module, just use `Module::eval_ast_as_new`.
|
||||
Don't forget the `export` statement, otherwise there will be nothing inside the module!
|
||||
It is easy to convert a pre-compiled `AST` into a module: just use `Module::eval_ast_as_new`.
|
||||
Don't forget the `export` statement, otherwise there will be no variables exposed by the module
|
||||
other than non-`private` functions (unless that's intentional).
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Module};
|
||||
@ -2202,7 +2252,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. |
|
||||
| `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
|
||||
// Use the 'StaticModuleResolver'
|
||||
@ -2213,9 +2263,186 @@ engine.set_module_resolver(Some(resolver));
|
||||
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.
|
||||
It may also create a large [array] or [objecct map] literal that exhausts all memory during parsing.
|
||||
* **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.
|
||||
Alternatively, it may create a degenerated deep expression with so many levels that the parser exhausts the call stack
|
||||
when parsing the expression; or even deeply-nested statement blocks, if nested deep enough.
|
||||
* **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 call stack depth
|
||||
|
||||
Rhai by default limits function calls to a maximum depth of 128 levels (16 levels in debug build).
|
||||
This limit may be changed via the `Engine::set_max_call_levels` method.
|
||||
|
||||
When setting this limit, care must be also taken to the evaluation depth of each _statement_
|
||||
within the function. It is entirely possible for a malignant script to embed an recursive call deep
|
||||
inside a nested expression or statement block (see [maximum statement depth](#maximum-statement-depth)).
|
||||
|
||||
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.
|
||||
|
||||
### Maximum statement depth
|
||||
|
||||
Rhai by default limits statements and expressions nesting to a maximum depth of 128
|
||||
(which should be plenty) when they are at _global_ level, but only a depth of 32
|
||||
when they are within function bodies. For debug builds, these limits are set further
|
||||
downwards to 32 and 16 respectively.
|
||||
|
||||
That is because it is possible to overflow the [`Engine`]'s stack when it tries to
|
||||
recursively parse an extremely deeply-nested code stream.
|
||||
|
||||
```rust
|
||||
// The following, if long enough, can easily cause stack overflow during parsing.
|
||||
let a = (1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(...)+1)))))))))));
|
||||
```
|
||||
|
||||
This limit may be changed via the `Engine::set_max_expr_depths` method. There are two limits to set,
|
||||
one for the maximum depth at global level, and the other for function bodies.
|
||||
|
||||
```rust
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.set_max_expr_depths(50, 5); // allow nesting up to 50 layers of expressions/statements
|
||||
// at global level, but only 5 inside functions
|
||||
```
|
||||
|
||||
Beware that there may be multiple layers for a simple language construct, even though it may correspond
|
||||
to only one AST node. That is because the Rhai _parser_ internally runs a recursive chain of function calls
|
||||
and it is important that a malignant script does not panic the parser in the first place.
|
||||
|
||||
Functions are placed under stricter limits because of the multiplicative effect of recursion.
|
||||
A script can effectively call itself while deep inside an expression chain within the function body,
|
||||
thereby overflowing the stack even when the level of recursion is within limit.
|
||||
|
||||
Make sure that `C x ( 5 + F ) + S` layered calls do not cause a stack overflow, where:
|
||||
|
||||
* `C` = maximum call stack depth,
|
||||
* `F` = maximum statement depth for functions,
|
||||
* `S` = maximum statement depth at global level.
|
||||
|
||||
A script exceeding the maximum nesting depths will terminate with a parsing error.
|
||||
The malignant `AST` will not be able to get past parsing in the first place.
|
||||
|
||||
The limits can be disabled via the [`unchecked`] feature for higher performance
|
||||
(but higher risks as well).
|
||||
|
||||
### Checked arithmetic
|
||||
|
||||
By default, 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]: #script-optimization
|
||||
|
||||
Rhai includes an _optimizer_ that tries to optimize a script after parsing.
|
||||
This can reduce resource utilization and increase execution speed.
|
||||
Script optimization can be turned off via the [`no_optimize`] feature.
|
||||
@ -2323,7 +2550,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.
|
||||
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
|
||||
// Turn on aggressive optimizations
|
||||
@ -2446,6 +2673,8 @@ engine.set_optimization_level(rhai::OptimizationLevel::None);
|
||||
`eval` - or "How to Shoot Yourself in the Foot even Easier"
|
||||
---------------------------------------------------------
|
||||
|
||||
[`eval`]: #eval---or-how-to-shoot-yourself-in-the-foot-even-easier
|
||||
|
||||
Saving the best for last: in addition to script optimizations, there is the ever-dreaded... `eval` function!
|
||||
|
||||
```rust
|
||||
@ -2501,9 +2730,11 @@ let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run
|
||||
Or override it from 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())
|
||||
}
|
||||
|
||||
engine.register_result_fn("eval", alt_eval);
|
||||
```
|
||||
|
||||
There is even a [package] named `EvalPackage` which implements the disabling override.
|
||||
|
80
RELEASES.md
Normal file
80
RELEASES.md
Normal file
@ -0,0 +1,80 @@
|
||||
Rhai Release Notes
|
||||
==================
|
||||
|
||||
Version 0.14.2
|
||||
==============
|
||||
|
||||
Regression
|
||||
----------
|
||||
|
||||
* Do not optimize script with `eval_expression` - it is assumed to be one-off and short.
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
* Set limits on maximum level of nesting expressions and statements to avoid panics during parsing.
|
||||
|
||||
|
||||
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.
|
@ -41,3 +41,67 @@ fn bench_eval_expression_number_operators(bench: &mut Bencher) {
|
||||
|
||||
bench.iter(|| engine.consume_ast(&ast).unwrap());
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_eval_expression_optimized_simple(bench: &mut Bencher) {
|
||||
let script = r#"
|
||||
2 > 1 &&
|
||||
"something" != "nothing" ||
|
||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||
"#;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.set_optimization_level(OptimizationLevel::Simple);
|
||||
let ast = engine.compile_expression(script).unwrap();
|
||||
|
||||
bench.iter(|| engine.consume_ast(&ast).unwrap());
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_eval_expression_optimized_full(bench: &mut Bencher) {
|
||||
let script = r#"
|
||||
2 > 1 &&
|
||||
"something" != "nothing" ||
|
||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||
"#;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.set_optimization_level(OptimizationLevel::Full);
|
||||
let ast = engine.compile_expression(script).unwrap();
|
||||
|
||||
bench.iter(|| engine.consume_ast(&ast).unwrap());
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_eval_call_expression(bench: &mut Bencher) {
|
||||
let script = r#"
|
||||
2 > 1 &&
|
||||
"something" != "nothing" ||
|
||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||
"#;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
bench.iter(|| engine.eval_expression::<bool>(script).unwrap());
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_eval_call(bench: &mut Bencher) {
|
||||
let script = r#"
|
||||
2 > 1 &&
|
||||
"something" != "nothing" ||
|
||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||
"#;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
bench.iter(|| engine.eval::<bool>(script).unwrap());
|
||||
}
|
||||
|
@ -102,3 +102,35 @@ fn bench_parse_primes(bench: &mut Bencher) {
|
||||
|
||||
bench.iter(|| engine.compile(script).unwrap());
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_parse_optimize_simple(bench: &mut Bencher) {
|
||||
let script = r#"
|
||||
2 > 1 &&
|
||||
"something" != "nothing" ||
|
||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||
"#;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.set_optimization_level(OptimizationLevel::Simple);
|
||||
|
||||
bench.iter(|| engine.compile_expression(script).unwrap());
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_parse_optimize_full(bench: &mut Bencher) {
|
||||
let script = r#"
|
||||
2 > 1 &&
|
||||
"something" != "nothing" ||
|
||||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
|
||||
[array, with, spaces].len() <= #{prop:name}.len() &&
|
||||
modifierTest + 1000 / 2 > (80 * 100 % 2)
|
||||
"#;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.set_optimization_level(OptimizationLevel::Full);
|
||||
|
||||
bench.iter(|| engine.compile_expression(script).unwrap());
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
use rhai::{packages::*, Engine, EvalAltResult, INT};
|
||||
use std::rc::Rc;
|
||||
|
||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new_raw();
|
||||
|
@ -1,12 +1,9 @@
|
||||
use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST, INT};
|
||||
use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST};
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
use rhai::OptimizationLevel;
|
||||
|
||||
use std::{
|
||||
io::{stdin, stdout, Write},
|
||||
iter,
|
||||
};
|
||||
use std::io::{stdin, stdout, Write};
|
||||
|
||||
fn print_error(input: &str, err: EvalAltResult) {
|
||||
let lines: Vec<_> = input.trim().split('\n').collect();
|
||||
|
@ -3,7 +3,7 @@ use rhai::{Engine, EvalAltResult};
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
use rhai::OptimizationLevel;
|
||||
|
||||
use std::{env, fs::File, io::Read, iter, process::exit};
|
||||
use std::{env, fs::File, io::Read, process::exit};
|
||||
|
||||
fn eprint_error(input: &str, err: EvalAltResult) {
|
||||
fn eprint_line(lines: &[&str], line: usize, pos: usize, err: &str) {
|
||||
|
@ -3,8 +3,6 @@
|
||||
|
||||
const target = 30;
|
||||
|
||||
let now = timestamp();
|
||||
|
||||
fn fib(n) {
|
||||
if n < 2 {
|
||||
n
|
||||
@ -15,8 +13,14 @@ fn fib(n) {
|
||||
|
||||
print("Ready... Go!");
|
||||
|
||||
let now = timestamp();
|
||||
|
||||
let result = fib(target);
|
||||
|
||||
print("Finished. Run time = " + now.elapsed() + " seconds.");
|
||||
|
||||
print("Fibonacci number #" + target + " = " + result);
|
||||
|
||||
print("Finished. Run time = " + now.elapsed() + " seconds.");
|
||||
if result != 832_040 {
|
||||
print("The answer is WRONG! Should be 832,040!");
|
||||
}
|
@ -27,3 +27,7 @@ for p in range(2, MAX_NUMBER_TO_CHECK) {
|
||||
|
||||
print("Total " + total_primes_found + " primes <= " + MAX_NUMBER_TO_CHECK);
|
||||
print("Run time = " + now.elapsed() + " seconds.");
|
||||
|
||||
if total_primes_found != 9_592 {
|
||||
print("The answer is WRONG! Should be 9,592!");
|
||||
}
|
94
src/any.rs
94
src/any.rs
@ -1,10 +1,11 @@
|
||||
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
|
||||
|
||||
use crate::parser::INT;
|
||||
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
use crate::module::Module;
|
||||
|
||||
use crate::parser::INT;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::parser::FLOAT;
|
||||
|
||||
@ -18,7 +19,7 @@ use crate::stdlib::{
|
||||
any::{type_name, Any, TypeId},
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
fmt, mem, ptr,
|
||||
fmt,
|
||||
string::String,
|
||||
vec::Vec,
|
||||
};
|
||||
@ -26,7 +27,7 @@ use crate::stdlib::{
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
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.
|
||||
/// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`.
|
||||
@ -80,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`),
|
||||
/// `bool`, `String`, `char`, `Vec<T>` (into `Array`) and `HashMap<String, T>` (into `Map`).
|
||||
@ -93,7 +94,7 @@ pub trait Variant: Any + Send + Sync {
|
||||
fn as_mut_any(&mut self) -> &mut dyn Any;
|
||||
|
||||
/// Convert this `Variant` trait object to an `Any` trait object.
|
||||
fn as_box_any(self) -> Box<dyn Any>;
|
||||
fn as_box_any(self: Box<Self>) -> Box<dyn Any>;
|
||||
|
||||
/// Get the name of this type.
|
||||
fn type_name(&self) -> &'static str;
|
||||
@ -141,7 +142,7 @@ impl dyn Variant {
|
||||
}
|
||||
}
|
||||
|
||||
/// A dynamic type containing any value.
|
||||
/// Dynamic type containing any value.
|
||||
pub struct Dynamic(pub(crate) Union);
|
||||
|
||||
/// Internal `Dynamic` representation.
|
||||
@ -298,37 +299,6 @@ impl Default for Dynamic {
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast a type into another type.
|
||||
fn try_cast<A: Any, B: Any>(a: A) -> Option<B> {
|
||||
if TypeId::of::<B>() == a.type_id() {
|
||||
// SAFETY: Just checked we have the right type. We explicitly forget the
|
||||
// value immediately after moving out, removing any chance of a destructor
|
||||
// running or value otherwise being used again.
|
||||
unsafe {
|
||||
let ret: B = ptr::read(&a as *const _ as *const B);
|
||||
mem::forget(a);
|
||||
Some(ret)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast a Boxed type into another type.
|
||||
fn cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, Box<X>> {
|
||||
// Only allow casting to the exact same type
|
||||
if TypeId::of::<X>() == TypeId::of::<T>() {
|
||||
// SAFETY: just checked whether we are pointing to the correct type
|
||||
unsafe {
|
||||
let raw: *mut dyn Any = Box::into_raw(item as Box<dyn Any>);
|
||||
Ok(Box::from_raw(raw as *mut T))
|
||||
}
|
||||
} else {
|
||||
// Return the consumed item for chaining.
|
||||
Err(item)
|
||||
}
|
||||
}
|
||||
|
||||
impl Dynamic {
|
||||
/// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is.
|
||||
///
|
||||
@ -383,17 +353,17 @@ impl Dynamic {
|
||||
|
||||
let mut var = Box::new(value);
|
||||
|
||||
var = match cast_box::<_, Dynamic>(var) {
|
||||
var = match unsafe_cast_box::<_, Dynamic>(var) {
|
||||
Ok(d) => return *d,
|
||||
Err(var) => var,
|
||||
};
|
||||
var = match cast_box::<_, String>(var) {
|
||||
var = match unsafe_cast_box::<_, String>(var) {
|
||||
Ok(s) => return Self(Union::Str(s)),
|
||||
Err(var) => var,
|
||||
};
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
{
|
||||
var = match cast_box::<_, Array>(var) {
|
||||
var = match unsafe_cast_box::<_, Array>(var) {
|
||||
Ok(array) => return Self(Union::Array(array)),
|
||||
Err(var) => var,
|
||||
};
|
||||
@ -401,7 +371,7 @@ impl Dynamic {
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
var = match cast_box::<_, Map>(var) {
|
||||
var = match unsafe_cast_box::<_, Map>(var) {
|
||||
Ok(map) => return Self(Union::Map(map)),
|
||||
Err(var) => var,
|
||||
}
|
||||
@ -426,23 +396,23 @@ impl Dynamic {
|
||||
/// ```
|
||||
pub fn try_cast<T: Variant>(self) -> Option<T> {
|
||||
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
|
||||
return cast_box::<_, T>(Box::new(self)).ok().map(|v| *v);
|
||||
return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v);
|
||||
}
|
||||
|
||||
match self.0 {
|
||||
Union::Unit(value) => try_cast(value),
|
||||
Union::Bool(value) => try_cast(value),
|
||||
Union::Str(value) => cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
Union::Char(value) => try_cast(value),
|
||||
Union::Int(value) => try_cast(value),
|
||||
Union::Unit(value) => unsafe_try_cast(value),
|
||||
Union::Bool(value) => unsafe_try_cast(value),
|
||||
Union::Str(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
Union::Char(value) => unsafe_try_cast(value),
|
||||
Union::Int(value) => unsafe_try_cast(value),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Union::Float(value) => try_cast(value),
|
||||
Union::Float(value) => unsafe_try_cast(value),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(value) => cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(value) => cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
Union::Module(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
|
||||
}
|
||||
}
|
||||
@ -467,23 +437,23 @@ impl Dynamic {
|
||||
//self.try_cast::<T>().unwrap()
|
||||
|
||||
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
|
||||
return *cast_box::<_, T>(Box::new(self)).unwrap();
|
||||
return *unsafe_cast_box::<_, T>(Box::new(self)).unwrap();
|
||||
}
|
||||
|
||||
match self.0 {
|
||||
Union::Unit(value) => try_cast(value).unwrap(),
|
||||
Union::Bool(value) => try_cast(value).unwrap(),
|
||||
Union::Str(value) => *cast_box::<_, T>(value).unwrap(),
|
||||
Union::Char(value) => try_cast(value).unwrap(),
|
||||
Union::Int(value) => try_cast(value).unwrap(),
|
||||
Union::Unit(value) => unsafe_try_cast(value).unwrap(),
|
||||
Union::Bool(value) => unsafe_try_cast(value).unwrap(),
|
||||
Union::Str(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
|
||||
Union::Char(value) => unsafe_try_cast(value).unwrap(),
|
||||
Union::Int(value) => unsafe_try_cast(value).unwrap(),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Union::Float(value) => try_cast(value).unwrap(),
|
||||
Union::Float(value) => unsafe_try_cast(value).unwrap(),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(value) => *cast_box::<_, T>(value).unwrap(),
|
||||
Union::Array(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => *cast_box::<_, T>(value).unwrap(),
|
||||
Union::Map(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(value) => *cast_box::<_, T>(value).unwrap(),
|
||||
Union::Module(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
|
||||
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).unwrap(),
|
||||
}
|
||||
}
|
||||
|
280
src/api.rs
280
src/api.rs
@ -4,6 +4,7 @@ use crate::any::{Dynamic, Variant};
|
||||
use crate::engine::{make_getter, make_setter, Engine, State, FUNC_INDEXER};
|
||||
use crate::error::ParseError;
|
||||
use crate::fn_call::FuncArgs;
|
||||
use crate::fn_native::{IteratorFn, ObjectGetCallback, ObjectIndexerCallback, ObjectSetCallback};
|
||||
use crate::fn_register::RegisterFn;
|
||||
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
||||
use crate::parser::{parse, parse_global_expr, AST};
|
||||
@ -18,60 +19,13 @@ use crate::engine::Map;
|
||||
use crate::stdlib::{
|
||||
any::{type_name, TypeId},
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
mem,
|
||||
string::{String, ToString},
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf};
|
||||
|
||||
// Define callback function types
|
||||
#[cfg(feature = "sync")]
|
||||
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + Send + Sync + 'static {}
|
||||
#[cfg(feature = "sync")]
|
||||
impl<F: Fn(&mut T) -> U + Send + Sync + 'static, T, U> ObjectGetCallback<T, U> for F {}
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + 'static {}
|
||||
#[cfg(not(feature = "sync"))]
|
||||
impl<F: Fn(&mut T) -> U + 'static, T, U> ObjectGetCallback<T, U> for F {}
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
pub trait ObjectSetCallback<T, U>: Fn(&mut T, U) + Send + Sync + 'static {}
|
||||
#[cfg(feature = "sync")]
|
||||
impl<F: Fn(&mut T, U) + Send + Sync + 'static, T, U> ObjectSetCallback<T, U> for F {}
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub trait ObjectSetCallback<T, U>: Fn(&mut T, U) + 'static {}
|
||||
#[cfg(not(feature = "sync"))]
|
||||
impl<F: Fn(&mut T, U) + 'static, T, U> ObjectSetCallback<T, U> for F {}
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
pub trait ObjectIndexerCallback<T, X, U>: Fn(&mut T, X) -> U + Send + Sync + 'static {}
|
||||
#[cfg(feature = "sync")]
|
||||
impl<F: Fn(&mut T, X) -> U + Send + Sync + 'static, T, X, U> ObjectIndexerCallback<T, X, U> for F {}
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub trait ObjectIndexerCallback<T, X, U>: Fn(&mut T, X) -> U + 'static {}
|
||||
#[cfg(not(feature = "sync"))]
|
||||
impl<F: Fn(&mut T, X) -> U + 'static, T, X, U> ObjectIndexerCallback<T, X, U> for F {}
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
pub trait IteratorCallback:
|
||||
Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync + 'static
|
||||
{
|
||||
}
|
||||
#[cfg(feature = "sync")]
|
||||
impl<F: Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + Send + Sync + 'static> IteratorCallback
|
||||
for F
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub trait IteratorCallback: Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static {}
|
||||
#[cfg(not(feature = "sync"))]
|
||||
impl<F: Fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static> IteratorCallback for F {}
|
||||
|
||||
/// Engine public API
|
||||
impl Engine {
|
||||
/// Register a custom type for use with the `Engine`.
|
||||
@ -167,10 +121,8 @@ impl Engine {
|
||||
|
||||
/// Register an iterator adapter for a type with the `Engine`.
|
||||
/// This is an advanced feature.
|
||||
pub fn register_iterator<T: Variant + Clone, F: IteratorCallback>(&mut self, f: F) {
|
||||
self.base_package
|
||||
.type_iterators
|
||||
.insert(TypeId::of::<T>(), Box::new(f));
|
||||
pub fn register_iterator<T: Variant + Clone>(&mut self, f: IteratorFn) {
|
||||
self.global_module.set_iter(TypeId::of::<T>(), f);
|
||||
}
|
||||
|
||||
/// Register a getter function for a member of a registered type with the `Engine`.
|
||||
@ -387,6 +339,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
/// Compile a string into an `AST` using own scope, which can be used later for evaluation.
|
||||
///
|
||||
/// The scope is useful for passing constants into the script for optimization
|
||||
/// when using `OptimizationLevel::Full`.
|
||||
///
|
||||
@ -424,19 +377,79 @@ impl Engine {
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result<AST, Box<ParseError>> {
|
||||
self.compile_with_scope_and_optimization_level(scope, script, self.optimization_level)
|
||||
self.compile_scripts_with_scope(scope, &[script])
|
||||
}
|
||||
|
||||
/// Compile a string into an `AST` using own scope at a specific optimization level.
|
||||
/// When passed a list of strings, first join the strings into one large script,
|
||||
/// and then compile them into an `AST` using own scope, which can be used later for evaluation.
|
||||
///
|
||||
/// The scope is useful for passing constants into the script for optimization
|
||||
/// when using `OptimizationLevel::Full`.
|
||||
///
|
||||
/// ## Note
|
||||
///
|
||||
/// All strings are simply parsed one after another with nothing inserted in between, not even
|
||||
/// a newline or space.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # #[cfg(not(feature = "no_optimize"))]
|
||||
/// # {
|
||||
/// use rhai::{Engine, Scope, OptimizationLevel};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Set optimization level to 'Full' so the Engine can fold constants
|
||||
/// // into function calls and operators.
|
||||
/// engine.set_optimization_level(OptimizationLevel::Full);
|
||||
///
|
||||
/// // Create initialized scope
|
||||
/// let mut scope = Scope::new();
|
||||
/// scope.push_constant("x", 42_i64); // 'x' is a constant
|
||||
///
|
||||
/// // Compile a script made up of script segments to an AST and store it for later evaluation.
|
||||
/// // Notice that `Full` optimization is on, so constants are folded
|
||||
/// // into function calls and operators.
|
||||
/// let ast = engine.compile_scripts_with_scope(&mut scope, &[
|
||||
/// "if x > 40", // all 'x' are replaced with 42
|
||||
/// "{ x } el",
|
||||
/// "se { 0 }" // segments do not need to be valid scripts!
|
||||
/// ])?;
|
||||
///
|
||||
/// // Normally this would have failed because no scope is passed into the 'eval_ast'
|
||||
/// // call and so the variable 'x' does not exist. Here, it passes because the script
|
||||
/// // has been optimized and all references to 'x' are already gone.
|
||||
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
|
||||
/// # }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn compile_scripts_with_scope(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
scripts: &[&str],
|
||||
) -> Result<AST, Box<ParseError>> {
|
||||
self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level)
|
||||
}
|
||||
|
||||
/// Join a list of strings and compile into an `AST` using own scope at a specific optimization level.
|
||||
pub(crate) fn compile_with_scope_and_optimization_level(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
script: &str,
|
||||
scripts: &[&str],
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> Result<AST, Box<ParseError>> {
|
||||
let scripts = [script];
|
||||
let stream = lex(&scripts);
|
||||
parse(&mut stream.peekable(), self, scope, optimization_level)
|
||||
let stream = lex(scripts);
|
||||
|
||||
parse(
|
||||
&mut stream.peekable(),
|
||||
self,
|
||||
scope,
|
||||
optimization_level,
|
||||
(self.max_expr_depth, self.max_function_expr_depth),
|
||||
)
|
||||
}
|
||||
|
||||
/// Read the contents of a file into a string.
|
||||
@ -489,6 +502,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
/// Compile a script file into an `AST` using own scope, which can be used later for evaluation.
|
||||
///
|
||||
/// The scope is useful for passing constants into the script for optimization
|
||||
/// when using `OptimizationLevel::Full`.
|
||||
///
|
||||
@ -562,6 +576,7 @@ impl Engine {
|
||||
self,
|
||||
&scope,
|
||||
OptimizationLevel::None,
|
||||
self.max_expr_depth,
|
||||
)?;
|
||||
|
||||
// Handle null - map to ()
|
||||
@ -643,7 +658,16 @@ impl Engine {
|
||||
let scripts = [script];
|
||||
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,
|
||||
self.max_expr_depth,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a script file.
|
||||
@ -739,9 +763,11 @@ impl Engine {
|
||||
scope: &mut Scope,
|
||||
script: &str,
|
||||
) -> 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(scope, script, OptimizationLevel::None)?;
|
||||
let ast = self.compile_with_scope_and_optimization_level(
|
||||
scope,
|
||||
&[script],
|
||||
self.optimization_level,
|
||||
)?;
|
||||
self.eval_ast_with_scope(scope, &ast)
|
||||
}
|
||||
|
||||
@ -791,8 +817,15 @@ impl Engine {
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
let scripts = [script];
|
||||
let stream = lex(&scripts);
|
||||
// Since the AST will be thrown away afterwards, don't bother to optimize it
|
||||
let ast = parse_global_expr(&mut stream.peekable(), self, scope, OptimizationLevel::None)?;
|
||||
|
||||
let ast = parse_global_expr(
|
||||
&mut stream.peekable(),
|
||||
self,
|
||||
scope,
|
||||
OptimizationLevel::None, // No need to optimize a lone expression
|
||||
self.max_expr_depth,
|
||||
)?;
|
||||
|
||||
self.eval_ast_with_scope(scope, &ast)
|
||||
}
|
||||
|
||||
@ -852,13 +885,13 @@ impl Engine {
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
) -> 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());
|
||||
|
||||
return result.try_cast::<T>().ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||
return_type.to_string(),
|
||||
return_type.into(),
|
||||
Position::none(),
|
||||
))
|
||||
});
|
||||
@ -868,7 +901,7 @@ impl Engine {
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
|
||||
let mut state = State::new(ast.fn_lib());
|
||||
|
||||
ast.statements()
|
||||
@ -880,6 +913,7 @@ impl Engine {
|
||||
EvalAltResult::Return(out, _) => Ok(out),
|
||||
_ => Err(err),
|
||||
})
|
||||
.map(|v| (v, state.operations))
|
||||
}
|
||||
|
||||
/// Evaluate a file, but throw away the result and only return error (if any).
|
||||
@ -916,8 +950,13 @@ impl Engine {
|
||||
let scripts = [script];
|
||||
let stream = lex(&scripts);
|
||||
|
||||
// Since the AST will be thrown away afterwards, don't bother to optimize it
|
||||
let ast = parse(&mut stream.peekable(), self, scope, OptimizationLevel::None)?;
|
||||
let ast = parse(
|
||||
&mut stream.peekable(),
|
||||
self,
|
||||
scope,
|
||||
self.optimization_level,
|
||||
(self.max_expr_depth, self.max_function_expr_depth),
|
||||
)?;
|
||||
self.consume_ast_with_scope(scope, &ast)
|
||||
}
|
||||
|
||||
@ -1000,11 +1039,12 @@ impl Engine {
|
||||
|
||||
let fn_def = fn_lib
|
||||
.get_function_by_signature(name, args.len(), true)
|
||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.to_string(), pos)))?;
|
||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.into(), pos)))?;
|
||||
|
||||
let state = State::new(fn_lib);
|
||||
let args = args.as_mut();
|
||||
|
||||
let result = self.call_script_fn(Some(scope), &state, fn_def, args.as_mut(), pos, 0)?;
|
||||
let (result, _) = self.call_script_fn(Some(scope), state, name, fn_def, args, pos, 0)?;
|
||||
|
||||
let return_type = self.map_type_name(result.type_name());
|
||||
|
||||
@ -1044,6 +1084,84 @@ impl Engine {
|
||||
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!`)
|
||||
///
|
||||
/// # Example
|
||||
@ -1078,21 +1196,21 @@ impl Engine {
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # use std::sync::RwLock;
|
||||
/// # use std::sync::Arc;
|
||||
/// # use std::cell::RefCell;
|
||||
/// # use std::rc::Rc;
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let result = Arc::new(RwLock::new(String::from("")));
|
||||
/// let result = Rc::new(RefCell::new(String::from("")));
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Override action of 'print' function
|
||||
/// 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);")?;
|
||||
///
|
||||
/// assert_eq!(*result.read().unwrap(), "42");
|
||||
/// assert_eq!(*result.borrow(), "42");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
@ -1135,21 +1253,21 @@ impl Engine {
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// # use std::sync::RwLock;
|
||||
/// # use std::sync::Arc;
|
||||
/// # use std::cell::RefCell;
|
||||
/// # use std::rc::Rc;
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let result = Arc::new(RwLock::new(String::from("")));
|
||||
/// let result = Rc::new(RefCell::new(String::from("")));
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Override action of 'print' function
|
||||
/// 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");"#)?;
|
||||
///
|
||||
/// assert_eq!(*result.read().unwrap(), r#""hello""#);
|
||||
/// assert_eq!(*result.borrow(), r#""hello""#);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
|
986
src/engine.rs
986
src/engine.rs
File diff suppressed because it is too large
Load Diff
@ -110,6 +110,10 @@ pub enum ParseErrorType {
|
||||
AssignmentToCopy,
|
||||
/// Assignment to an a constant variable.
|
||||
AssignmentToConstant(String),
|
||||
/// Expression exceeding the maximum levels of complexity.
|
||||
///
|
||||
/// Never appears under the `unchecked` feature.
|
||||
ExprTooDeep,
|
||||
/// Break statement not inside a loop.
|
||||
LoopBreak,
|
||||
}
|
||||
@ -122,7 +126,7 @@ impl ParseErrorType {
|
||||
}
|
||||
|
||||
/// Error when parsing a script.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub struct ParseError(pub(crate) ParseErrorType, pub(crate) Position);
|
||||
|
||||
impl ParseError {
|
||||
@ -158,7 +162,8 @@ impl ParseError {
|
||||
ParseErrorType::DuplicatedExport(_) => "Duplicated variable/function in export statement",
|
||||
ParseErrorType::WrongExport => "Export statement can only appear at global level",
|
||||
ParseErrorType::AssignmentToCopy => "Only a copy of the value is change with this assignment",
|
||||
ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant value.",
|
||||
ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant value",
|
||||
ParseErrorType::ExprTooDeep => "Expression exceeds maximum complexity",
|
||||
ParseErrorType::LoopBreak => "Break statement should only be used inside a loop"
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use crate::utils::StaticVec;
|
||||
/// Any data type that can be converted into a `Vec<Dynamic>` can be used
|
||||
/// as arguments to a function call.
|
||||
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>;
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ use crate::scope::Scope;
|
||||
|
||||
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> {
|
||||
type Output;
|
||||
|
||||
@ -92,15 +92,14 @@ macro_rules! def_anonymous_fn {
|
||||
{
|
||||
#[cfg(feature = "sync")]
|
||||
type Output = Box<dyn Fn($($par),*) -> Result<RET, Box<EvalAltResult>> + Send + Sync>;
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
type Output = Box<dyn Fn($($par),*) -> Result<RET, Box<EvalAltResult>>>;
|
||||
|
||||
fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output {
|
||||
let name = entry_point.to_string();
|
||||
let fn_name = entry_point.to_string();
|
||||
|
||||
Box::new(move |$($par: $par),*| {
|
||||
self.call_fn(&mut Scope::new(), &ast, &name, ($($par,)*))
|
||||
self.call_fn(&mut Scope::new(), &ast, &fn_name, ($($par,)*))
|
||||
})
|
||||
}
|
||||
|
||||
|
151
src/fn_native.rs
Normal file
151
src/fn_native.rs
Normal file
@ -0,0 +1,151 @@
|
||||
use crate::any::Dynamic;
|
||||
use crate::parser::FnDef;
|
||||
use crate::result::EvalAltResult;
|
||||
|
||||
use crate::stdlib::{boxed::Box, rc::Rc, sync::Arc};
|
||||
|
||||
pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
|
||||
|
||||
pub type IteratorFn = fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
pub type PrintCallback = dyn Fn(&str) + Send + Sync + 'static;
|
||||
#[cfg(not(feature = "sync"))]
|
||||
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
|
||||
#[cfg(feature = "sync")]
|
||||
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + Send + Sync + 'static {}
|
||||
#[cfg(feature = "sync")]
|
||||
impl<F: Fn(&mut T) -> U + Send + Sync + 'static, T, U> ObjectGetCallback<T, U> for F {}
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + 'static {}
|
||||
#[cfg(not(feature = "sync"))]
|
||||
impl<F: Fn(&mut T) -> U + 'static, T, U> ObjectGetCallback<T, U> for F {}
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
pub trait ObjectSetCallback<T, U>: Fn(&mut T, U) + Send + Sync + 'static {}
|
||||
#[cfg(feature = "sync")]
|
||||
impl<F: Fn(&mut T, U) + Send + Sync + 'static, T, U> ObjectSetCallback<T, U> for F {}
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub trait ObjectSetCallback<T, U>: Fn(&mut T, U) + 'static {}
|
||||
#[cfg(not(feature = "sync"))]
|
||||
impl<F: Fn(&mut T, U) + 'static, T, U> ObjectSetCallback<T, U> for F {}
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
pub trait ObjectIndexerCallback<T, X, U>: Fn(&mut T, X) -> U + Send + Sync + 'static {}
|
||||
#[cfg(feature = "sync")]
|
||||
impl<F: Fn(&mut T, X) -> U + Send + Sync + 'static, T, X, U> ObjectIndexerCallback<T, X, U> for F {}
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub trait ObjectIndexerCallback<T, X, U>: Fn(&mut T, X) -> U + 'static {}
|
||||
#[cfg(not(feature = "sync"))]
|
||||
impl<F: Fn(&mut T, X) -> U + 'static, T, X, U> ObjectIndexerCallback<T, X, U> for F {}
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub type SharedNativeFunction = Rc<FnAny>;
|
||||
#[cfg(feature = "sync")]
|
||||
pub type SharedNativeFunction = Arc<FnAny>;
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
pub type SharedFnDef = Arc<FnDef>;
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub type SharedFnDef = Rc<FnDef>;
|
||||
|
||||
/// A type encapsulating a function callable by Rhai.
|
||||
#[derive(Clone)]
|
||||
pub enum CallableFunction {
|
||||
/// A pure native Rust function with all arguments passed by value.
|
||||
Pure(SharedNativeFunction),
|
||||
/// A native Rust object method with the first argument passed by reference,
|
||||
/// and the rest passed by value.
|
||||
Method(SharedNativeFunction),
|
||||
/// An iterator function.
|
||||
Iterator(IteratorFn),
|
||||
/// A script-defined function.
|
||||
Script(SharedFnDef),
|
||||
}
|
||||
|
||||
impl CallableFunction {
|
||||
/// Is this a pure native Rust function?
|
||||
pub fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
Self::Pure(_) => true,
|
||||
Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false,
|
||||
}
|
||||
}
|
||||
/// Is this a pure native Rust method-call?
|
||||
pub fn is_method(&self) -> bool {
|
||||
match self {
|
||||
Self::Method(_) => true,
|
||||
Self::Pure(_) | Self::Iterator(_) | Self::Script(_) => false,
|
||||
}
|
||||
}
|
||||
/// Is this an iterator function?
|
||||
pub fn is_iter(&self) -> bool {
|
||||
match self {
|
||||
Self::Iterator(_) => true,
|
||||
Self::Pure(_) | Self::Method(_) | Self::Script(_) => false,
|
||||
}
|
||||
}
|
||||
/// Is this a Rhai-scripted function?
|
||||
pub fn is_script(&self) -> bool {
|
||||
match self {
|
||||
Self::Script(_) => true,
|
||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false,
|
||||
}
|
||||
}
|
||||
/// Get a reference to a native Rust function.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the `CallableFunction` is not `Pure` or `Method`.
|
||||
pub fn get_native_fn(&self) -> &FnAny {
|
||||
match self {
|
||||
Self::Pure(f) | Self::Method(f) => f.as_ref(),
|
||||
Self::Iterator(_) | Self::Script(_) => panic!(),
|
||||
}
|
||||
}
|
||||
/// Get a reference to a script-defined function definition.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the `CallableFunction` is not `Script`.
|
||||
pub fn get_fn_def(&self) -> &FnDef {
|
||||
match self {
|
||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => panic!(),
|
||||
Self::Script(f) => f,
|
||||
}
|
||||
}
|
||||
/// Get a reference to an iterator function.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the `CallableFunction` is not `Iterator`.
|
||||
pub fn get_iter_fn(&self) -> IteratorFn {
|
||||
match self {
|
||||
Self::Iterator(f) => *f,
|
||||
Self::Pure(_) | Self::Method(_) | Self::Script(_) => panic!(),
|
||||
}
|
||||
}
|
||||
/// Create a new `CallableFunction::Pure`.
|
||||
pub fn from_pure(func: Box<FnAny>) -> Self {
|
||||
Self::Pure(func.into())
|
||||
}
|
||||
/// Create a new `CallableFunction::Method`.
|
||||
pub fn from_method(func: Box<FnAny>) -> Self {
|
||||
Self::Method(func.into())
|
||||
}
|
||||
}
|
@ -3,12 +3,12 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::engine::{Engine, FnCallArgs};
|
||||
use crate::engine::Engine;
|
||||
use crate::fn_native::{CallableFunction, FnAny, FnCallArgs};
|
||||
use crate::parser::FnAccess;
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::token::Position;
|
||||
use crate::utils::calc_fn_spec;
|
||||
|
||||
use crate::stdlib::{any::TypeId, boxed::Box, iter::empty, mem, string::ToString};
|
||||
use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString};
|
||||
|
||||
/// A trait to register custom plugins with the `Engine`.
|
||||
///
|
||||
@ -53,7 +53,7 @@ pub trait RegisterPlugin<PL: Plugin> {
|
||||
fn register_plugin(&mut self, plugin: PL);
|
||||
}
|
||||
|
||||
/// A trait to register custom functions with the `Engine`.
|
||||
/// Trait to register custom functions with the `Engine`.
|
||||
pub trait RegisterFn<FN, ARGS, RET> {
|
||||
/// Register a custom function with the `Engine`.
|
||||
///
|
||||
@ -85,7 +85,7 @@ pub trait RegisterFn<FN, ARGS, RET> {
|
||||
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> {
|
||||
/// Register a custom function returning `Dynamic` values with the `Engine`.
|
||||
///
|
||||
@ -112,7 +112,7 @@ pub trait RegisterDynamicFn<FN, ARGS> {
|
||||
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> {
|
||||
/// Register a custom fallible function with the `Engine`.
|
||||
///
|
||||
@ -169,15 +169,15 @@ pub struct Mut<T>(T);
|
||||
//pub struct Ref<T>(T);
|
||||
|
||||
/// Dereference into &mut.
|
||||
#[inline]
|
||||
pub fn by_ref<T: Clone + 'static>(data: &mut Dynamic) -> &mut T {
|
||||
#[inline(always)]
|
||||
pub fn by_ref<T: Variant + Clone>(data: &mut Dynamic) -> &mut T {
|
||||
// Directly cast the &mut Dynamic into &mut T to access the underlying data.
|
||||
data.downcast_mut::<T>().unwrap()
|
||||
}
|
||||
|
||||
/// Dereference into value.
|
||||
#[inline]
|
||||
pub fn by_value<T: Clone + 'static>(data: &mut Dynamic) -> T {
|
||||
#[inline(always)]
|
||||
pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
|
||||
// We consume the argument and then replace it with () - the argument is not supposed to be used again.
|
||||
// This way, we avoid having to clone the argument again, because it is already a clone when passed here.
|
||||
mem::take(data).cast::<T>()
|
||||
@ -198,20 +198,14 @@ macro_rules! count_args {
|
||||
|
||||
/// This macro creates a closure wrapping a registered function.
|
||||
macro_rules! make_func {
|
||||
($fn_name:ident : $fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => {
|
||||
// ^ function name
|
||||
// ^ function pointer
|
||||
// ^ result mapping function
|
||||
// ^ function parameter generic type name (A, B, C etc.)
|
||||
// ^ dereferencing function
|
||||
($fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => {
|
||||
// ^ function pointer
|
||||
// ^ result mapping function
|
||||
// ^ function parameter generic type name (A, B, C etc.)
|
||||
// ^ dereferencing function
|
||||
|
||||
move |args: &mut FnCallArgs, pos: Position| {
|
||||
// Check for length at the beginning to avoid per-element bound checks.
|
||||
const NUM_ARGS: usize = count_args!($($par)*);
|
||||
|
||||
if args.len() != NUM_ARGS {
|
||||
return Err(Box::new(EvalAltResult::ErrorFunctionArgsMismatch($fn_name.clone(), NUM_ARGS, args.len(), pos)));
|
||||
}
|
||||
Box::new(move |args: &mut FnCallArgs| {
|
||||
// The arguments are assumed to be of the correct number and types!
|
||||
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
let mut drain = args.iter_mut();
|
||||
@ -225,45 +219,41 @@ macro_rules! make_func {
|
||||
let r = $fn($($par),*);
|
||||
|
||||
// Map the result
|
||||
$map(r, pos)
|
||||
};
|
||||
$map(r)
|
||||
}) as Box<FnAny>
|
||||
};
|
||||
}
|
||||
|
||||
/// To Dynamic mapping function.
|
||||
#[inline]
|
||||
pub fn map_dynamic<T: Variant + Clone>(
|
||||
data: T,
|
||||
_pos: Position,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
#[inline(always)]
|
||||
pub fn map_dynamic<T: Variant + Clone>(data: T) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
Ok(data.into_dynamic())
|
||||
}
|
||||
|
||||
/// To Dynamic mapping function.
|
||||
#[inline]
|
||||
pub fn map_identity(data: Dynamic, _pos: Position) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
#[inline(always)]
|
||||
pub fn map_identity(data: Dynamic) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// To `Result<Dynamic, Box<EvalAltResult>>` mapping function.
|
||||
#[inline]
|
||||
#[inline(always)]
|
||||
pub fn map_result<T: Variant + Clone>(
|
||||
data: Result<T, Box<EvalAltResult>>,
|
||||
pos: Position,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
data.map(|v| v.into_dynamic())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
}
|
||||
|
||||
macro_rules! def_register {
|
||||
() => {
|
||||
def_register!(imp);
|
||||
def_register!(imp from_pure :);
|
||||
};
|
||||
(imp $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => {
|
||||
// ^ function parameter generic type name (A, B, C etc.)
|
||||
// ^ function parameter marker type (T, Ref<T> or Mut<T>)
|
||||
// ^ function parameter actual type (T, &T or &mut T)
|
||||
// ^ dereferencing function
|
||||
(imp $abi:ident : $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => {
|
||||
// ^ function ABI type
|
||||
// ^ function parameter generic type name (A, B, C etc.)
|
||||
// ^ function parameter marker type (T, Ref<T> or Mut<T>)
|
||||
// ^ function parameter actual type (T, &T or &mut T)
|
||||
// ^ dereferencing function
|
||||
impl<
|
||||
$($par: Variant + Clone,)*
|
||||
|
||||
@ -277,10 +267,10 @@ macro_rules! def_register {
|
||||
> RegisterFn<FN, ($($mark,)*), RET> for Engine
|
||||
{
|
||||
fn register_fn(&mut self, name: &str, f: FN) {
|
||||
let fn_name = name.to_string();
|
||||
let func = make_func!(fn_name : f : map_dynamic ; $($par => $clone),*);
|
||||
let hash = calc_fn_spec(empty(), name, [$(TypeId::of::<$par>()),*].iter().cloned());
|
||||
self.base_package.functions.insert(hash, Box::new(func));
|
||||
self.global_module.set_fn(name.to_string(), FnAccess::Public,
|
||||
&[$(TypeId::of::<$par>()),*],
|
||||
CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $clone),*))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,10 +285,10 @@ macro_rules! def_register {
|
||||
> RegisterDynamicFn<FN, ($($mark,)*)> for Engine
|
||||
{
|
||||
fn register_dynamic_fn(&mut self, name: &str, f: FN) {
|
||||
let fn_name = name.to_string();
|
||||
let func = make_func!(fn_name : f : map_identity ; $($par => $clone),*);
|
||||
let hash = calc_fn_spec(empty(), name, [$(TypeId::of::<$par>()),*].iter().cloned());
|
||||
self.base_package.functions.insert(hash, Box::new(func));
|
||||
self.global_module.set_fn(name.to_string(), FnAccess::Public,
|
||||
&[$(TypeId::of::<$par>()),*],
|
||||
CallableFunction::$abi(make_func!(f : map_identity ; $($par => $clone),*))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -314,20 +304,21 @@ macro_rules! def_register {
|
||||
> RegisterResultFn<FN, ($($mark,)*), RET> for Engine
|
||||
{
|
||||
fn register_result_fn(&mut self, name: &str, f: FN) {
|
||||
let fn_name = name.to_string();
|
||||
let func = make_func!(fn_name : f : map_result ; $($par => $clone),*);
|
||||
let hash = calc_fn_spec(empty(), name, [$(TypeId::of::<$par>()),*].iter().cloned());
|
||||
self.base_package.functions.insert(hash, Box::new(func));
|
||||
self.global_module.set_fn(name.to_string(), FnAccess::Public,
|
||||
&[$(TypeId::of::<$par>()),*],
|
||||
CallableFunction::$abi(make_func!(f : map_result ; $($par => $clone),*))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//def_register!(imp_pop $($par => $mark => $param),*);
|
||||
};
|
||||
($p0:ident $(, $p:ident)*) => {
|
||||
def_register!(imp $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => by_value)*);
|
||||
def_register!(imp $p0 => Mut<$p0> => &mut $p0 => by_ref $(, $p => $p => $p => by_value)*);
|
||||
// handle the first parameter ^ first parameter passed through
|
||||
// ^ others passed by value (by_value)
|
||||
def_register!(imp from_pure : $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => by_value)*);
|
||||
def_register!(imp from_method : $p0 => Mut<$p0> => &mut $p0 => by_ref $(, $p => $p => $p => by_value)*);
|
||||
// ^ CallableFunction
|
||||
// handle the first parameter ^ first parameter passed through
|
||||
// ^ others passed by value (by_value)
|
||||
|
||||
// Currently does not support first argument which is a reference, as there will be
|
||||
// conflicting implementations since &T: Any and T: Any cannot be distinguished
|
||||
|
@ -75,6 +75,7 @@ mod engine;
|
||||
mod error;
|
||||
mod fn_call;
|
||||
mod fn_func;
|
||||
mod fn_native;
|
||||
mod fn_register;
|
||||
mod module;
|
||||
mod optimize;
|
||||
@ -84,15 +85,16 @@ mod result;
|
||||
mod scope;
|
||||
mod stdlib;
|
||||
mod token;
|
||||
mod r#unsafe;
|
||||
mod utils;
|
||||
|
||||
pub use any::Dynamic;
|
||||
pub use engine::Engine;
|
||||
pub use error::{ParseError, ParseErrorType};
|
||||
pub use fn_call::FuncArgs;
|
||||
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
||||
#[cfg(feature = "plugins")]
|
||||
pub use fn_register::{Plugin, RegisterPlugin};
|
||||
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
||||
pub use module::Module;
|
||||
pub use parser::{AST, INT};
|
||||
pub use result::EvalAltResult;
|
||||
pub use scope::Scope;
|
||||
@ -112,8 +114,9 @@ pub use engine::Map;
|
||||
pub use parser::FLOAT;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub use module::{Module, ModuleResolver};
|
||||
pub use module::ModuleResolver;
|
||||
|
||||
/// Module containing all built-in _module resolvers_ available to Rhai.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub mod module_resolvers {
|
||||
pub use crate::module::resolvers::*;
|
||||
|
477
src/module.rs
477
src/module.rs
@ -1,51 +1,41 @@
|
||||
//! Module defining external-loaded modules for Rhai.
|
||||
#![cfg(not(feature = "no_module"))]
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::calc_fn_hash;
|
||||
use crate::engine::{Engine, FnAny, FnCallArgs, FunctionsLib, NativeFunction, ScriptedFunction};
|
||||
use crate::parser::{FnAccess, FnDef, AST};
|
||||
use crate::engine::{Engine, FunctionsLib};
|
||||
use crate::fn_native::{CallableFunction as CF, FnCallArgs, IteratorFn};
|
||||
use crate::parser::{
|
||||
FnAccess,
|
||||
FnAccess::{Private, Public},
|
||||
AST,
|
||||
};
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
||||
use crate::token::{Position, Token};
|
||||
use crate::utils::{StaticVec, EMPTY_TYPE_ID};
|
||||
use crate::utils::StaticVec;
|
||||
|
||||
use crate::stdlib::{
|
||||
any::TypeId,
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
iter::{empty, repeat},
|
||||
iter::empty,
|
||||
mem,
|
||||
num::NonZeroUsize,
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
string::{String, ToString},
|
||||
sync::Arc,
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
/// A trait that encapsulates a module resolution service.
|
||||
pub trait ModuleResolver {
|
||||
/// Resolve a module based on a path string.
|
||||
fn resolve(
|
||||
&self,
|
||||
engine: &Engine,
|
||||
scope: Scope,
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> Result<Module, Box<EvalAltResult>>;
|
||||
}
|
||||
|
||||
/// Return type of module-level Rust function.
|
||||
type FuncReturn<T> = Result<T, Box<EvalAltResult>>;
|
||||
pub type FuncReturn<T> = Result<T, Box<EvalAltResult>>;
|
||||
|
||||
/// An imported module, which may contain variables, sub-modules,
|
||||
/// external Rust functions, and script-defined functions.
|
||||
///
|
||||
/// Not available under the `no_module` feature.
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Module {
|
||||
/// Sub-modules.
|
||||
modules: HashMap<String, Module>,
|
||||
@ -57,16 +47,17 @@ pub struct Module {
|
||||
all_variables: HashMap<u64, Dynamic>,
|
||||
|
||||
/// External Rust functions.
|
||||
functions: HashMap<u64, (String, FnAccess, Vec<TypeId>, NativeFunction)>,
|
||||
|
||||
/// Flattened collection of all external Rust functions, including those in sub-modules.
|
||||
all_functions: HashMap<u64, NativeFunction>,
|
||||
functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, CF)>,
|
||||
|
||||
/// Script-defined functions.
|
||||
fn_lib: FunctionsLib,
|
||||
|
||||
/// Flattened collection of all script-defined functions, including those in sub-modules.
|
||||
all_fn_lib: FunctionsLib,
|
||||
/// Iterator functions, keyed by the type producing the iterator.
|
||||
type_iterators: HashMap<TypeId, IteratorFn>,
|
||||
|
||||
/// Flattened collection of all external Rust functions, native or scripted,
|
||||
/// including those in sub-modules.
|
||||
all_functions: HashMap<u64, CF>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Module {
|
||||
@ -97,6 +88,24 @@ impl Module {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Create a new module with a specified capacity for native Rust functions.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// module.set_var("answer", 42_i64);
|
||||
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
|
||||
/// ```
|
||||
pub fn new_with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
functions: HashMap::with_capacity(capacity),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Does a variable exist in the module?
|
||||
///
|
||||
/// # Examples
|
||||
@ -155,8 +164,8 @@ impl Module {
|
||||
/// module.set_var("answer", 42_i64);
|
||||
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
|
||||
/// ```
|
||||
pub fn set_var<K: Into<String>, T: Into<Dynamic>>(&mut self, name: K, value: T) {
|
||||
self.variables.insert(name.into(), value.into());
|
||||
pub fn set_var<K: Into<String>, T: Variant + Clone>(&mut self, name: K, value: T) {
|
||||
self.variables.insert(name.into(), Dynamic::from(value));
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a modules-qualified variable.
|
||||
@ -165,11 +174,11 @@ impl Module {
|
||||
pub(crate) fn get_qualified_var_mut(
|
||||
&mut self,
|
||||
name: &str,
|
||||
hash: u64,
|
||||
hash_var: u64,
|
||||
pos: Position,
|
||||
) -> Result<&mut Dynamic, Box<EvalAltResult>> {
|
||||
self.all_variables
|
||||
.get_mut(&hash)
|
||||
.get_mut(&hash_var)
|
||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.to_string(), pos)))
|
||||
}
|
||||
|
||||
@ -253,30 +262,22 @@ impl Module {
|
||||
/// let hash = module.set_fn_0("calc", || Ok(42_i64));
|
||||
/// assert!(module.contains_fn(hash));
|
||||
/// ```
|
||||
pub fn contains_fn(&self, hash: u64) -> bool {
|
||||
self.functions.contains_key(&hash)
|
||||
pub fn contains_fn(&self, hash_fn: u64) -> bool {
|
||||
self.functions.contains_key(&hash_fn)
|
||||
}
|
||||
|
||||
/// Set a Rust function into the module, returning a hash key.
|
||||
///
|
||||
/// If there is an existing Rust function of the same hash, it is replaced.
|
||||
pub fn set_fn(
|
||||
&mut self,
|
||||
fn_name: String,
|
||||
access: FnAccess,
|
||||
params: Vec<TypeId>,
|
||||
func: Box<FnAny>,
|
||||
) -> u64 {
|
||||
let hash = calc_fn_hash(empty(), &fn_name, params.iter().cloned());
|
||||
pub fn set_fn(&mut self, name: String, access: FnAccess, params: &[TypeId], func: CF) -> u64 {
|
||||
let hash_fn = calc_fn_hash(empty(), &name, params.len(), params.iter().cloned());
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
self.functions
|
||||
.insert(hash, (fn_name, access, params, Rc::new(func)));
|
||||
#[cfg(feature = "sync")]
|
||||
self.functions
|
||||
.insert(hash, (fn_name, access, params, Arc::new(func)));
|
||||
let params = params.into_iter().cloned().collect();
|
||||
|
||||
hash
|
||||
self.functions
|
||||
.insert(hash_fn, (name, access, params, func.into()));
|
||||
|
||||
hash_fn
|
||||
}
|
||||
|
||||
/// Set a Rust function taking no parameters into the module, returning a hash key.
|
||||
@ -292,19 +293,15 @@ impl Module {
|
||||
/// let hash = module.set_fn_0("calc", || Ok(42_i64));
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
/// ```
|
||||
pub fn set_fn_0<K: Into<String>, T: Into<Dynamic>>(
|
||||
pub fn set_fn_0<K: Into<String>, T: Variant + Clone>(
|
||||
&mut self,
|
||||
fn_name: K,
|
||||
name: K,
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn<T> + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn() -> FuncReturn<T> + Send + Sync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |_: &mut FnCallArgs, pos| {
|
||||
func()
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
};
|
||||
let arg_types = vec![];
|
||||
self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f))
|
||||
let f = move |_: &mut FnCallArgs| func().map(Dynamic::from);
|
||||
let args = [];
|
||||
self.set_fn(name.into(), Public, &args, CF::from_pure(Box::new(f)))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking one parameter into the module, returning a hash key.
|
||||
@ -320,19 +317,16 @@ impl Module {
|
||||
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
/// ```
|
||||
pub fn set_fn_1<K: Into<String>, A: Variant + Clone, T: Into<Dynamic>>(
|
||||
pub fn set_fn_1<K: Into<String>, A: Variant + Clone, T: Variant + Clone>(
|
||||
&mut self,
|
||||
fn_name: K,
|
||||
name: K,
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn<T> + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn<T> + Send + Sync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs, pos| {
|
||||
func(mem::take(args[0]).cast::<A>())
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
};
|
||||
let arg_types = vec![TypeId::of::<A>()];
|
||||
self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f))
|
||||
let f =
|
||||
move |args: &mut FnCallArgs| func(mem::take(args[0]).cast::<A>()).map(Dynamic::from);
|
||||
let args = [TypeId::of::<A>()];
|
||||
self.set_fn(name.into(), Public, &args, CF::from_pure(Box::new(f)))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking one mutable parameter into the module, returning a hash key.
|
||||
@ -348,19 +342,17 @@ impl Module {
|
||||
/// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) });
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
/// ```
|
||||
pub fn set_fn_1_mut<K: Into<String>, A: Variant + Clone, T: Into<Dynamic>>(
|
||||
pub fn set_fn_1_mut<K: Into<String>, A: Variant + Clone, T: Variant + Clone>(
|
||||
&mut self,
|
||||
fn_name: K,
|
||||
name: K,
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn<T> + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn<T> + Send + Sync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs, pos| {
|
||||
func(args[0].downcast_mut::<A>().unwrap())
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
let f = move |args: &mut FnCallArgs| {
|
||||
func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from)
|
||||
};
|
||||
let arg_types = vec![TypeId::of::<A>()];
|
||||
self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f))
|
||||
let args = [TypeId::of::<A>()];
|
||||
self.set_fn(name.into(), Public, &args, CF::from_method(Box::new(f)))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking two parameters into the module, returning a hash key.
|
||||
@ -378,22 +370,20 @@ impl Module {
|
||||
/// });
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
/// ```
|
||||
pub fn set_fn_2<K: Into<String>, A: Variant + Clone, B: Variant + Clone, T: Into<Dynamic>>(
|
||||
pub fn set_fn_2<K: Into<String>, A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
|
||||
&mut self,
|
||||
fn_name: K,
|
||||
name: K,
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> FuncReturn<T> + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(A, B) -> FuncReturn<T> + Send + Sync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs, pos| {
|
||||
let f = move |args: &mut FnCallArgs| {
|
||||
let a = mem::take(args[0]).cast::<A>();
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
|
||||
func(a, b)
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
func(a, b).map(Dynamic::from)
|
||||
};
|
||||
let arg_types = vec![TypeId::of::<A>(), TypeId::of::<B>()];
|
||||
self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f))
|
||||
let args = [TypeId::of::<A>(), TypeId::of::<B>()];
|
||||
self.set_fn(name.into(), Public, &args, CF::from_pure(Box::new(f)))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking two parameters (the first one mutable) into the module,
|
||||
@ -414,23 +404,21 @@ impl Module {
|
||||
K: Into<String>,
|
||||
A: Variant + Clone,
|
||||
B: Variant + Clone,
|
||||
T: Into<Dynamic>,
|
||||
T: Variant + Clone,
|
||||
>(
|
||||
&mut self,
|
||||
fn_name: K,
|
||||
name: K,
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<T> + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<T> + Send + Sync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs, pos| {
|
||||
let f = move |args: &mut FnCallArgs| {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let a = args[0].downcast_mut::<A>().unwrap();
|
||||
|
||||
func(a, b)
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
func(a, b).map(Dynamic::from)
|
||||
};
|
||||
let arg_types = vec![TypeId::of::<A>(), TypeId::of::<B>()];
|
||||
self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f))
|
||||
let args = [TypeId::of::<A>(), TypeId::of::<B>()];
|
||||
self.set_fn(name.into(), Public, &args, CF::from_method(Box::new(f)))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking three parameters into the module, returning a hash key.
|
||||
@ -453,24 +441,22 @@ impl Module {
|
||||
A: Variant + Clone,
|
||||
B: Variant + Clone,
|
||||
C: Variant + Clone,
|
||||
T: Into<Dynamic>,
|
||||
T: Variant + Clone,
|
||||
>(
|
||||
&mut self,
|
||||
fn_name: K,
|
||||
name: K,
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> FuncReturn<T> + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(A, B, C) -> FuncReturn<T> + Send + Sync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs, pos| {
|
||||
let f = move |args: &mut FnCallArgs| {
|
||||
let a = mem::take(args[0]).cast::<A>();
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
|
||||
func(a, b, c)
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
func(a, b, c).map(Dynamic::from)
|
||||
};
|
||||
let arg_types = vec![TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
|
||||
self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f))
|
||||
let args = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
|
||||
self.set_fn(name.into(), Public, &args, CF::from_pure(Box::new(f)))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking three parameters (the first one mutable) into the module,
|
||||
@ -494,24 +480,22 @@ impl Module {
|
||||
A: Variant + Clone,
|
||||
B: Variant + Clone,
|
||||
C: Variant + Clone,
|
||||
T: Into<Dynamic>,
|
||||
T: Variant + Clone,
|
||||
>(
|
||||
&mut self,
|
||||
fn_name: K,
|
||||
name: K,
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + Send + Sync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs, pos| {
|
||||
let f = move |args: &mut FnCallArgs| {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
let a = args[0].downcast_mut::<A>().unwrap();
|
||||
|
||||
func(a, b, c)
|
||||
.map(|v| v.into())
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
func(a, b, c).map(Dynamic::from)
|
||||
};
|
||||
let arg_types = vec![TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
|
||||
self.set_fn(fn_name.into(), FnAccess::Public, arg_types, Box::new(f))
|
||||
let args = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
|
||||
self.set_fn(name.into(), Public, &args, CF::from_method(Box::new(f)))
|
||||
}
|
||||
|
||||
/// Get a Rust function.
|
||||
@ -528,8 +512,8 @@ impl Module {
|
||||
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
|
||||
/// assert!(module.get_fn(hash).is_some());
|
||||
/// ```
|
||||
pub fn get_fn(&self, hash: u64) -> Option<&Box<FnAny>> {
|
||||
self.functions.get(&hash).map(|(_, _, _, v)| v.as_ref())
|
||||
pub fn get_fn(&self, hash_fn: u64) -> Option<&CF> {
|
||||
self.functions.get(&hash_fn).map(|(_, _, _, v)| v)
|
||||
}
|
||||
|
||||
/// Get a modules-qualified function.
|
||||
@ -539,34 +523,14 @@ impl Module {
|
||||
pub(crate) fn get_qualified_fn(
|
||||
&mut self,
|
||||
name: &str,
|
||||
hash: u64,
|
||||
pos: Position,
|
||||
) -> Result<&Box<FnAny>, Box<EvalAltResult>> {
|
||||
self.all_functions
|
||||
.get(&hash)
|
||||
.map(|f| f.as_ref())
|
||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.to_string(), pos)))
|
||||
}
|
||||
|
||||
/// Get the script-defined functions.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Module;
|
||||
///
|
||||
/// let mut module = Module::new();
|
||||
/// assert_eq!(module.get_fn_lib().len(), 0);
|
||||
/// ```
|
||||
pub fn get_fn_lib(&self) -> &FunctionsLib {
|
||||
&self.fn_lib
|
||||
}
|
||||
|
||||
/// Get a modules-qualified script-defined functions.
|
||||
///
|
||||
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
|
||||
pub(crate) fn get_qualified_scripted_fn(&mut self, hash: u64) -> Option<&FnDef> {
|
||||
self.all_fn_lib.get_function(hash)
|
||||
hash_fn_native: u64,
|
||||
) -> Result<&CF, Box<EvalAltResult>> {
|
||||
self.all_functions.get(&hash_fn_native).ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorFunctionNotFound(
|
||||
name.to_string(),
|
||||
Position::none(),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new `Module` by evaluating an `AST`.
|
||||
@ -585,6 +549,7 @@ impl Module {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub fn eval_ast_as_new(mut scope: Scope, ast: &AST, engine: &Engine) -> FuncReturn<Self> {
|
||||
// Run the script
|
||||
engine.eval_ast_with_scope_raw(&mut scope, &ast)?;
|
||||
@ -623,84 +588,175 @@ impl Module {
|
||||
pub(crate) fn index_all_sub_modules(&mut self) {
|
||||
// Collect a particular module.
|
||||
fn index_module<'a>(
|
||||
module: &'a mut Module,
|
||||
module: &'a Module,
|
||||
qualifiers: &mut Vec<&'a str>,
|
||||
variables: &mut Vec<(u64, Dynamic)>,
|
||||
functions: &mut Vec<(u64, NativeFunction)>,
|
||||
fn_lib: &mut Vec<(u64, ScriptedFunction)>,
|
||||
functions: &mut Vec<(u64, CF)>,
|
||||
) {
|
||||
for (name, m) in module.modules.iter_mut() {
|
||||
for (name, m) in &module.modules {
|
||||
// Index all the sub-modules first.
|
||||
qualifiers.push(name);
|
||||
index_module(m, qualifiers, variables, functions, fn_lib);
|
||||
index_module(m, qualifiers, variables, functions);
|
||||
qualifiers.pop();
|
||||
}
|
||||
|
||||
// Index all variables
|
||||
for (var_name, value) in module.variables.iter() {
|
||||
for (var_name, value) in &module.variables {
|
||||
// Qualifiers + variable name
|
||||
let hash = calc_fn_hash(qualifiers.iter().map(|v| *v), var_name, empty());
|
||||
variables.push((hash, value.clone()));
|
||||
let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0, empty());
|
||||
variables.push((hash_var, value.clone()));
|
||||
}
|
||||
// Index all Rust functions
|
||||
for (fn_name, access, params, func) in module.functions.values() {
|
||||
for (name, access, params, func) in module.functions.values() {
|
||||
match access {
|
||||
// Private functions are not exported
|
||||
FnAccess::Private => continue,
|
||||
FnAccess::Public => (),
|
||||
Private => continue,
|
||||
Public => (),
|
||||
}
|
||||
// Rust functions are indexed in two steps:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
// i.e. qualifiers + function name + dummy parameter types (one for each parameter).
|
||||
let hash_fn_def = calc_fn_hash(
|
||||
qualifiers.iter().map(|v| *v),
|
||||
fn_name,
|
||||
repeat(EMPTY_TYPE_ID()).take(params.len()),
|
||||
);
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name, and
|
||||
// the actual list of parameter `TypeId`'.s
|
||||
let hash_fn_args = calc_fn_hash(empty(), "", params.iter().cloned());
|
||||
// i.e. qualifiers + function name + number of arguments.
|
||||
let hash_fn_def =
|
||||
calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name,
|
||||
// zero number of arguments, and the actual list of argument `TypeId`'.s
|
||||
let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
|
||||
// 3) The final hash is the XOR of the two hashes.
|
||||
let hash = hash_fn_def ^ hash_fn_args;
|
||||
let hash_fn_native = hash_fn_def ^ hash_fn_args;
|
||||
|
||||
functions.push((hash, func.clone()));
|
||||
functions.push((hash_fn_native, func.clone()));
|
||||
}
|
||||
// Index all script-defined functions
|
||||
for fn_def in module.fn_lib.values() {
|
||||
match fn_def.access {
|
||||
// Private functions are not exported
|
||||
FnAccess::Private => continue,
|
||||
FnAccess::Public => (),
|
||||
Private => continue,
|
||||
Public => (),
|
||||
}
|
||||
// Qualifiers + function name + placeholders (one for each parameter)
|
||||
let hash = calc_fn_hash(
|
||||
qualifiers.iter().map(|v| *v),
|
||||
// Qualifiers + function name + number of arguments.
|
||||
let hash_fn_def = calc_fn_hash(
|
||||
qualifiers.iter().map(|&v| v),
|
||||
&fn_def.name,
|
||||
repeat(EMPTY_TYPE_ID()).take(fn_def.params.len()),
|
||||
fn_def.params.len(),
|
||||
empty(),
|
||||
);
|
||||
fn_lib.push((hash, fn_def.clone()));
|
||||
functions.push((hash_fn_def, CF::Script(fn_def.clone()).into()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut variables = Vec::new();
|
||||
let mut functions = Vec::new();
|
||||
let mut fn_lib = Vec::new();
|
||||
|
||||
index_module(
|
||||
self,
|
||||
&mut vec!["root"],
|
||||
&mut variables,
|
||||
&mut functions,
|
||||
&mut fn_lib,
|
||||
);
|
||||
index_module(self, &mut vec!["root"], &mut variables, &mut functions);
|
||||
|
||||
self.all_variables = variables.into_iter().collect();
|
||||
self.all_functions = functions.into_iter().collect();
|
||||
self.all_fn_lib = fn_lib.into();
|
||||
}
|
||||
|
||||
/// Does a type iterator exist in the module?
|
||||
pub fn contains_iter(&self, id: TypeId) -> bool {
|
||||
self.type_iterators.contains_key(&id)
|
||||
}
|
||||
|
||||
/// Set a type iterator into the module.
|
||||
pub fn set_iter(&mut self, typ: TypeId, func: IteratorFn) {
|
||||
self.type_iterators.insert(typ, func);
|
||||
}
|
||||
|
||||
/// Get the specified type iterator.
|
||||
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
|
||||
self.type_iterators.get(&id).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
/// A chain of module names to qualify a variable or function call.
|
||||
/// A `u64` hash key is kept for quick search purposes.
|
||||
///
|
||||
/// 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.
|
||||
#[derive(Clone, Eq, PartialEq, Default)]
|
||||
pub struct ModuleRef(StaticVec<(String, Position)>, Option<NonZeroUsize>);
|
||||
|
||||
impl fmt::Debug for ModuleRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.0, f)?;
|
||||
|
||||
if let Some(index) = self.1 {
|
||||
write!(f, " -> {}", index)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ModuleRef {
|
||||
type Target = StaticVec<(String, Position)>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ModuleRef {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ModuleRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (m, _) in self.0.iter() {
|
||||
write!(f, "{}{}", m, Token::DoubleColon.syntax())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StaticVec<(String, Position)>> for ModuleRef {
|
||||
fn from(modules: StaticVec<(String, Position)>) -> Self {
|
||||
Self(modules, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleRef {
|
||||
pub(crate) fn index(&self) -> Option<NonZeroUsize> {
|
||||
self.1
|
||||
}
|
||||
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
|
||||
self.1 = index
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait that encapsulates a module resolution service.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub trait ModuleResolver {
|
||||
/// Resolve a module based on a path string.
|
||||
fn resolve(
|
||||
&self,
|
||||
engine: &Engine,
|
||||
scope: Scope,
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> Result<Module, Box<EvalAltResult>>;
|
||||
}
|
||||
|
||||
/// Trait that encapsulates a module resolution service.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(feature = "sync")]
|
||||
pub trait ModuleResolver: Send + Sync {
|
||||
/// Resolve a module based on a path string.
|
||||
fn resolve(
|
||||
&self,
|
||||
engine: &Engine,
|
||||
scope: Scope,
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> Result<Module, Box<EvalAltResult>>;
|
||||
}
|
||||
|
||||
/// Re-export module resolvers.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub mod resolvers {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
pub use super::file::FileModuleResolver;
|
||||
@ -708,12 +764,13 @@ pub mod resolvers {
|
||||
}
|
||||
|
||||
/// Script file-based module resolver.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
mod file {
|
||||
use super::*;
|
||||
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
|
||||
/// allow specification of a base directory with module path used as a relative path offset
|
||||
@ -733,7 +790,7 @@ mod file {
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Clone, Hash)]
|
||||
pub struct FileModuleResolver {
|
||||
path: PathBuf,
|
||||
extension: String,
|
||||
@ -838,77 +895,19 @@ mod file {
|
||||
// Compile it
|
||||
let ast = engine
|
||||
.compile_file(file_path)
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))?;
|
||||
.map_err(|err| err.new_position(pos))?;
|
||||
|
||||
Module::eval_ast_as_new(scope, &ast, engine)
|
||||
.map_err(|err| EvalAltResult::set_position(err, pos))
|
||||
Module::eval_ast_as_new(scope, &ast, engine).map_err(|err| err.new_position(pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A chain of module names to qualify a variable or function call.
|
||||
/// A `u64` hash key is kept for quick search purposes.
|
||||
///
|
||||
/// 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.
|
||||
#[derive(Clone, Hash, Default)]
|
||||
pub struct ModuleRef(StaticVec<(String, Position)>, Option<NonZeroUsize>);
|
||||
|
||||
impl fmt::Debug for ModuleRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.0, f)?;
|
||||
|
||||
if let Some(index) = self.1 {
|
||||
write!(f, " -> {}", index)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ModuleRef {
|
||||
type Target = StaticVec<(String, Position)>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ModuleRef {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ModuleRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (m, _) in self.0.iter() {
|
||||
write!(f, "{}{}", m, Token::DoubleColon.syntax())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StaticVec<(String, Position)>> for ModuleRef {
|
||||
fn from(modules: StaticVec<(String, Position)>) -> Self {
|
||||
Self(modules, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleRef {
|
||||
pub(crate) fn index(&self) -> Option<NonZeroUsize> {
|
||||
self.1
|
||||
}
|
||||
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
|
||||
self.1 = index
|
||||
}
|
||||
}
|
||||
|
||||
/// Static module resolver.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
mod stat {
|
||||
use super::*;
|
||||
|
||||
/// A module resolution service that serves modules added into it.
|
||||
/// Module resolution service that serves modules added into it.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -974,7 +973,7 @@ mod stat {
|
||||
self.0
|
||||
.get(path)
|
||||
.cloned()
|
||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(path.to_string(), pos)))
|
||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(path.into(), pos)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
use crate::any::Dynamic;
|
||||
use crate::calc_fn_hash;
|
||||
use crate::engine::{
|
||||
Engine, FnAny, FnCallArgs, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT,
|
||||
KEYWORD_TYPE_OF,
|
||||
Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||
};
|
||||
use crate::packages::{PackageStore, PackagesCollection};
|
||||
use crate::fn_native::FnCallArgs;
|
||||
use crate::module::Module;
|
||||
use crate::packages::PackagesCollection;
|
||||
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
||||
use crate::token::Position;
|
||||
use crate::utils::StaticVec;
|
||||
|
||||
use crate::stdlib::{
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
iter::empty,
|
||||
string::{String, ToString},
|
||||
vec,
|
||||
@ -95,7 +96,7 @@ impl<'a> State<'a> {
|
||||
}
|
||||
/// Add a new constant to the list.
|
||||
pub fn push_constant(&mut self, name: &str, value: Expr) {
|
||||
self.constants.push((name.to_string(), value))
|
||||
self.constants.push((name.into(), value))
|
||||
}
|
||||
/// Look up a constant from the list.
|
||||
pub fn find_constant(&self, name: &str) -> Option<&Expr> {
|
||||
@ -112,19 +113,25 @@ impl<'a> State<'a> {
|
||||
/// Call a registered function
|
||||
fn call_fn(
|
||||
packages: &PackagesCollection,
|
||||
base_package: &PackageStore,
|
||||
global_module: &Module,
|
||||
fn_name: &str,
|
||||
args: &mut FnCallArgs,
|
||||
pos: Position,
|
||||
) -> Result<Option<Dynamic>, Box<EvalAltResult>> {
|
||||
// Search built-in's and external functions
|
||||
let hash = calc_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id()));
|
||||
let hash_fn = calc_fn_hash(
|
||||
empty(),
|
||||
fn_name,
|
||||
args.len(),
|
||||
args.iter().map(|a| a.type_id()),
|
||||
);
|
||||
|
||||
base_package
|
||||
.get_function(hash)
|
||||
.or_else(|| packages.get_function(hash))
|
||||
.map(|func| func(args, pos))
|
||||
global_module
|
||||
.get_fn(hash_fn)
|
||||
.or_else(|| packages.get_fn(hash_fn))
|
||||
.map(|func| func.get_native_fn()(args))
|
||||
.transpose()
|
||||
.map_err(|err| err.new_position(pos))
|
||||
}
|
||||
|
||||
/// Optimize a statement.
|
||||
@ -139,7 +146,11 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
||||
|
||||
if preserve_result {
|
||||
// -> { 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 {
|
||||
// -> expr
|
||||
Stmt::Expr(Box::new(expr))
|
||||
@ -192,7 +203,8 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
||||
Stmt::Break(pos) => {
|
||||
// Only a single break statement - turn into running the guard expression once
|
||||
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 {
|
||||
statements.push(Stmt::Noop(pos))
|
||||
}
|
||||
@ -323,13 +335,13 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
||||
Stmt::Noop(pos)
|
||||
}
|
||||
// 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
|
||||
[_] => {
|
||||
state.set_dirty();
|
||||
result.remove(0)
|
||||
}
|
||||
_ => Stmt::Block(Box::new((result, pos))),
|
||||
_ => Stmt::Block(Box::new((result.into(), pos))),
|
||||
}
|
||||
}
|
||||
// expr;
|
||||
@ -392,11 +404,12 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
Expr::Dot(x) => match (x.0, x.1) {
|
||||
// map.string
|
||||
(Expr::Map(m), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
|
||||
let ((prop, _, _), _) = p.as_ref();
|
||||
// Map literal where everything is pure - promote the indexed item.
|
||||
// All other items can be thrown away.
|
||||
state.set_dirty();
|
||||
let pos = m.1;
|
||||
m.0.into_iter().find(|((name, _), _)| name == &p.0)
|
||||
m.0.into_iter().find(|((name, _), _)| name == prop)
|
||||
.map(|(_, expr)| expr.set_position(pos))
|
||||
.unwrap_or_else(|| Expr::Unit(pos))
|
||||
}
|
||||
@ -414,7 +427,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
// Array literal where everything is pure - promote the indexed item.
|
||||
// All other items can be thrown away.
|
||||
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]
|
||||
(Expr::Map(m), Expr::StringConstant(s)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
|
||||
@ -438,14 +451,12 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
// [ items .. ]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(a) => Expr::Array(Box::new((a.0
|
||||
.into_iter()
|
||||
.map(|expr| optimize_expr(expr, state))
|
||||
.collect(), a.1))),
|
||||
.into_iter().map(|expr| optimize_expr(expr, state))
|
||||
.collect(), a.1))),
|
||||
// [ items .. ]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Map(m) => Expr::Map(Box::new((m.0
|
||||
.into_iter()
|
||||
.map(|((key, pos), expr)| ((key, pos), optimize_expr(expr, state)))
|
||||
.into_iter().map(|((key, pos), expr)| ((key, pos), optimize_expr(expr, state)))
|
||||
.collect(), m.1))),
|
||||
// lhs in rhs
|
||||
Expr::In(x) => match (x.0, x.1) {
|
||||
@ -544,8 +555,8 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
return Expr::FnCall(x);
|
||||
}
|
||||
|
||||
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
|
||||
let mut call_args: Vec<_> = arg_values.iter_mut().collect();
|
||||
let mut arg_values: StaticVec<_> = args.iter().map(Expr::get_constant_value).collect();
|
||||
let mut call_args: StaticVec<_> = arg_values.iter_mut().collect();
|
||||
|
||||
// Save the typename of the first argument if it is `type_of()`
|
||||
// This is to avoid `call_args` being passed into the closure
|
||||
@ -555,7 +566,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
""
|
||||
};
|
||||
|
||||
call_fn(&state.engine.packages, &state.engine.base_package, 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|
|
||||
result.or_else(|| {
|
||||
if !arg_for_type_of.is_empty() {
|
||||
@ -695,11 +706,14 @@ pub fn optimize_into_ast(
|
||||
const level: OptimizationLevel = OptimizationLevel::None;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let fn_lib: Vec<_> = functions
|
||||
let fn_lib_values: StaticVec<_> = functions
|
||||
.iter()
|
||||
.map(|fn_def| (fn_def.name.as_str(), fn_def.params.len()))
|
||||
.collect();
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let fn_lib = fn_lib_values.as_ref();
|
||||
|
||||
#[cfg(feature = "no_function")]
|
||||
const fn_lib: &[(&str, usize)] = &[];
|
||||
|
||||
@ -709,7 +723,7 @@ pub fn optimize_into_ast(
|
||||
let pos = fn_def.body.position();
|
||||
|
||||
// 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
|
||||
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
|
||||
@ -735,7 +749,7 @@ pub fn optimize_into_ast(
|
||||
match level {
|
||||
OptimizationLevel::None => statements,
|
||||
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
||||
optimize(statements, engine, &scope, &fn_lib, level)
|
||||
optimize(statements, engine, &scope, fn_lib, level)
|
||||
}
|
||||
},
|
||||
lib,
|
||||
|
@ -1,7 +1,5 @@
|
||||
use super::{reg_binary, reg_unary};
|
||||
|
||||
use crate::def_package;
|
||||
use crate::fn_register::{map_dynamic as map, map_result as result};
|
||||
use crate::module::FuncReturn;
|
||||
use crate::parser::INT;
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::token::Position;
|
||||
@ -22,7 +20,7 @@ use crate::stdlib::{
|
||||
};
|
||||
|
||||
// Checked add
|
||||
fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, Box<EvalAltResult>> {
|
||||
fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
|
||||
x.checked_add(&y).ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorArithmetic(
|
||||
format!("Addition overflow: {} + {}", x, y),
|
||||
@ -31,7 +29,7 @@ fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, Box<EvalAltResult>> {
|
||||
})
|
||||
}
|
||||
// Checked subtract
|
||||
fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, Box<EvalAltResult>> {
|
||||
fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
|
||||
x.checked_sub(&y).ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorArithmetic(
|
||||
format!("Subtraction underflow: {} - {}", x, y),
|
||||
@ -40,7 +38,7 @@ fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, Box<EvalAltResult>> {
|
||||
})
|
||||
}
|
||||
// Checked multiply
|
||||
fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, Box<EvalAltResult>> {
|
||||
fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
|
||||
x.checked_mul(&y).ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorArithmetic(
|
||||
format!("Multiplication overflow: {} * {}", x, y),
|
||||
@ -49,7 +47,7 @@ fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, Box<EvalAltResult>> {
|
||||
})
|
||||
}
|
||||
// Checked divide
|
||||
fn div<T>(x: T, y: T) -> Result<T, Box<EvalAltResult>>
|
||||
fn div<T>(x: T, y: T) -> FuncReturn<T>
|
||||
where
|
||||
T: Display + CheckedDiv + PartialEq + Zero,
|
||||
{
|
||||
@ -69,7 +67,7 @@ where
|
||||
})
|
||||
}
|
||||
// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
|
||||
fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, Box<EvalAltResult>> {
|
||||
fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
|
||||
x.checked_neg().ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorArithmetic(
|
||||
format!("Negation overflow: -{}", x),
|
||||
@ -78,7 +76,7 @@ fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, Box<EvalAltResult>> {
|
||||
})
|
||||
}
|
||||
// Checked absolute
|
||||
fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, Box<EvalAltResult>> {
|
||||
fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> FuncReturn<T> {
|
||||
// FIX - We don't use Signed::abs() here because, contrary to documentation, it panics
|
||||
// when the number is ::MIN instead of returning ::MIN itself.
|
||||
if x >= <T as Zero>::zero() {
|
||||
@ -93,49 +91,49 @@ fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, Box<EvalA
|
||||
}
|
||||
}
|
||||
// Unchecked add - may panic on overflow
|
||||
fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output {
|
||||
x + y
|
||||
fn add_u<T: Add>(x: T, y: T) -> FuncReturn<<T as Add>::Output> {
|
||||
Ok(x + y)
|
||||
}
|
||||
// Unchecked subtract - may panic on underflow
|
||||
fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output {
|
||||
x - y
|
||||
fn sub_u<T: Sub>(x: T, y: T) -> FuncReturn<<T as Sub>::Output> {
|
||||
Ok(x - y)
|
||||
}
|
||||
// Unchecked multiply - may panic on overflow
|
||||
fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output {
|
||||
x * y
|
||||
fn mul_u<T: Mul>(x: T, y: T) -> FuncReturn<<T as Mul>::Output> {
|
||||
Ok(x * y)
|
||||
}
|
||||
// Unchecked divide - may panic when dividing by zero
|
||||
fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output {
|
||||
x / y
|
||||
fn div_u<T: Div>(x: T, y: T) -> FuncReturn<<T as Div>::Output> {
|
||||
Ok(x / y)
|
||||
}
|
||||
// Unchecked negative - may panic on overflow
|
||||
fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output {
|
||||
-x
|
||||
fn neg_u<T: Neg>(x: T) -> FuncReturn<<T as Neg>::Output> {
|
||||
Ok(-x)
|
||||
}
|
||||
// Unchecked absolute - may panic on overflow
|
||||
fn abs_u<T>(x: T) -> <T as Neg>::Output
|
||||
fn abs_u<T>(x: T) -> FuncReturn<<T as Neg>::Output>
|
||||
where
|
||||
T: Neg + PartialOrd + Default + Into<<T as Neg>::Output>,
|
||||
{
|
||||
// Numbers should default to zero
|
||||
if x < Default::default() {
|
||||
-x
|
||||
Ok(-x)
|
||||
} else {
|
||||
x.into()
|
||||
Ok(x.into())
|
||||
}
|
||||
}
|
||||
// Bit operators
|
||||
fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output {
|
||||
x & y
|
||||
fn binary_and<T: BitAnd>(x: T, y: T) -> FuncReturn<<T as BitAnd>::Output> {
|
||||
Ok(x & y)
|
||||
}
|
||||
fn binary_or<T: BitOr>(x: T, y: T) -> <T as BitOr>::Output {
|
||||
x | y
|
||||
fn binary_or<T: BitOr>(x: T, y: T) -> FuncReturn<<T as BitOr>::Output> {
|
||||
Ok(x | y)
|
||||
}
|
||||
fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output {
|
||||
x ^ y
|
||||
fn binary_xor<T: BitXor>(x: T, y: T) -> FuncReturn<<T as BitXor>::Output> {
|
||||
Ok(x ^ y)
|
||||
}
|
||||
// Checked left-shift
|
||||
fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, Box<EvalAltResult>> {
|
||||
fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
|
||||
// Cannot shift by a negative number of bits
|
||||
if y < 0 {
|
||||
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||
@ -152,7 +150,7 @@ fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, Box<EvalAltResult>> {
|
||||
})
|
||||
}
|
||||
// Checked right-shift
|
||||
fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, Box<EvalAltResult>> {
|
||||
fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
|
||||
// Cannot shift by a negative number of bits
|
||||
if y < 0 {
|
||||
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||
@ -169,15 +167,15 @@ fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, Box<EvalAltResult>> {
|
||||
})
|
||||
}
|
||||
// Unchecked left-shift - may panic if shifting by a negative number of bits
|
||||
fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output {
|
||||
x.shl(y)
|
||||
fn shl_u<T: Shl<T>>(x: T, y: T) -> FuncReturn<<T as Shl<T>>::Output> {
|
||||
Ok(x.shl(y))
|
||||
}
|
||||
// Unchecked right-shift - may panic if shifting by a negative number of bits
|
||||
fn shr_u<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output {
|
||||
x.shr(y)
|
||||
fn shr_u<T: Shr<T>>(x: T, y: T) -> FuncReturn<<T as Shr<T>>::Output> {
|
||||
Ok(x.shr(y))
|
||||
}
|
||||
// Checked modulo
|
||||
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, Box<EvalAltResult>> {
|
||||
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> {
|
||||
x.checked_rem(&y).ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorArithmetic(
|
||||
format!("Modulo division by zero or overflow: {} % {}", x, y),
|
||||
@ -186,11 +184,11 @@ fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, Box<EvalAltResult>>
|
||||
})
|
||||
}
|
||||
// Unchecked modulo - may panic if dividing by zero
|
||||
fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output {
|
||||
x % y
|
||||
fn modulo_u<T: Rem>(x: T, y: T) -> FuncReturn<<T as Rem>::Output> {
|
||||
Ok(x % y)
|
||||
}
|
||||
// Checked power
|
||||
fn pow_i_i(x: INT, y: INT) -> Result<INT, Box<EvalAltResult>> {
|
||||
fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
{
|
||||
if y > (u32::MAX as INT) {
|
||||
@ -231,17 +229,17 @@ fn pow_i_i(x: INT, y: INT) -> Result<INT, Box<EvalAltResult>> {
|
||||
}
|
||||
}
|
||||
// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
|
||||
fn pow_i_i_u(x: INT, y: INT) -> INT {
|
||||
x.pow(y as u32)
|
||||
fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> {
|
||||
Ok(x.pow(y as u32))
|
||||
}
|
||||
// Floating-point power - always well-defined
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT {
|
||||
x.powf(y)
|
||||
fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn<FLOAT> {
|
||||
Ok(x.powf(y))
|
||||
}
|
||||
// Checked power
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
fn pow_f_i(x: FLOAT, y: INT) -> Result<FLOAT, Box<EvalAltResult>> {
|
||||
fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
|
||||
// Raise to power that is larger than an i32
|
||||
if y > (i32::MAX as INT) {
|
||||
return Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||
@ -255,39 +253,37 @@ fn pow_f_i(x: FLOAT, y: INT) -> Result<FLOAT, Box<EvalAltResult>> {
|
||||
// Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
|
||||
#[cfg(feature = "unchecked")]
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
fn pow_f_i_u(x: FLOAT, y: INT) -> FLOAT {
|
||||
x.powi(y as i32)
|
||||
fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
|
||||
Ok(x.powi(y as i32))
|
||||
}
|
||||
|
||||
macro_rules! reg_unary_x { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$(reg_unary($lib, $op, $func::<$par>, result);)* };
|
||||
macro_rules! reg_unary {
|
||||
($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$( $lib.set_fn_1($op, $func::<$par>); )*
|
||||
};
|
||||
}
|
||||
macro_rules! reg_unary { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$(reg_unary($lib, $op, $func::<$par>, map);)* };
|
||||
}
|
||||
macro_rules! reg_op_x { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$(reg_binary($lib, $op, $func::<$par>, result);)* };
|
||||
}
|
||||
macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$(reg_binary($lib, $op, $func::<$par>, map);)* };
|
||||
macro_rules! reg_op {
|
||||
($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$( $lib.set_fn_2($op, $func::<$par>); )*
|
||||
};
|
||||
}
|
||||
|
||||
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
|
||||
// Checked basic arithmetic
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
reg_op_x!(lib, "+", add, INT);
|
||||
reg_op_x!(lib, "-", sub, INT);
|
||||
reg_op_x!(lib, "*", mul, INT);
|
||||
reg_op_x!(lib, "/", div, INT);
|
||||
reg_op!(lib, "+", add, INT);
|
||||
reg_op!(lib, "-", sub, INT);
|
||||
reg_op!(lib, "*", mul, INT);
|
||||
reg_op!(lib, "/", div, INT);
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
{
|
||||
reg_op_x!(lib, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
reg_op_x!(lib, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
reg_op_x!(lib, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
reg_op_x!(lib, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
reg_op!(lib, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
reg_op!(lib, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
}
|
||||
}
|
||||
|
||||
@ -334,16 +330,16 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
|
||||
// Checked bit shifts
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
reg_op_x!(lib, "<<", shl, INT);
|
||||
reg_op_x!(lib, ">>", shr, INT);
|
||||
reg_op_x!(lib, "%", modulo, INT);
|
||||
reg_op!(lib, "<<", shl, INT);
|
||||
reg_op!(lib, ">>", shr, INT);
|
||||
reg_op!(lib, "%", modulo, INT);
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
{
|
||||
reg_op_x!(lib, "<<", shl, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
reg_op_x!(lib, ">>", shr, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
reg_op_x!(lib, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
}
|
||||
}
|
||||
|
||||
@ -366,39 +362,39 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
|
||||
// Checked power
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
reg_binary(lib, "~", pow_i_i, result);
|
||||
lib.set_fn_2("~", pow_i_i);
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
reg_binary(lib, "~", pow_f_i, result);
|
||||
lib.set_fn_2("~", pow_f_i);
|
||||
}
|
||||
|
||||
// Unchecked power
|
||||
#[cfg(feature = "unchecked")]
|
||||
{
|
||||
reg_binary(lib, "~", pow_i_i_u, map);
|
||||
lib.set_fn_2("~", pow_i_i_u);
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
reg_binary(lib, "~", pow_f_i_u, map);
|
||||
lib.set_fn_2("~", pow_f_i_u);
|
||||
}
|
||||
|
||||
// Floating-point modulo and power
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
reg_op!(lib, "%", modulo_u, f32, f64);
|
||||
reg_binary(lib, "~", pow_f_f, map);
|
||||
lib.set_fn_2("~", pow_f_f);
|
||||
}
|
||||
|
||||
// Checked unary
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
reg_unary_x!(lib, "-", neg, INT);
|
||||
reg_unary_x!(lib, "abs", abs, INT);
|
||||
reg_unary!(lib, "-", neg, INT);
|
||||
reg_unary!(lib, "abs", abs, INT);
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
{
|
||||
reg_unary_x!(lib, "-", neg, i8, i16, i32, i64, i128);
|
||||
reg_unary_x!(lib, "abs", abs, i8, i16, i32, i64, i128);
|
||||
reg_unary!(lib, "-", neg, i8, i16, i32, i64, i128);
|
||||
reg_unary!(lib, "abs", abs, i8, i16, i32, i64, i128);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,41 +1,46 @@
|
||||
#![cfg(not(feature = "no_index"))]
|
||||
|
||||
use super::{reg_binary, reg_binary_mut, reg_trinary_mut, reg_unary_mut};
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::def_package;
|
||||
use crate::engine::Array;
|
||||
use crate::fn_register::{map_dynamic as map, map_identity as pass};
|
||||
use crate::module::FuncReturn;
|
||||
use crate::parser::INT;
|
||||
|
||||
use crate::stdlib::{any::TypeId, boxed::Box, string::String};
|
||||
|
||||
// Register array utility functions
|
||||
fn push<T: Variant + Clone>(list: &mut Array, item: T) {
|
||||
fn push<T: Variant + Clone>(list: &mut Array, item: T) -> FuncReturn<()> {
|
||||
list.push(Dynamic::from(item));
|
||||
Ok(())
|
||||
}
|
||||
fn ins<T: Variant + Clone>(list: &mut Array, position: INT, item: T) {
|
||||
fn ins<T: Variant + Clone>(list: &mut Array, position: INT, item: T) -> FuncReturn<()> {
|
||||
if position <= 0 {
|
||||
list.insert(0, Dynamic::from(item));
|
||||
} else if (position as usize) >= list.len() - 1 {
|
||||
push(list, item);
|
||||
push(list, item)?;
|
||||
} else {
|
||||
list.insert(position as usize, Dynamic::from(item));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn pad<T: Variant + Clone>(list: &mut Array, len: INT, item: T) {
|
||||
fn pad<T: Variant + Clone>(list: &mut Array, len: INT, item: T) -> FuncReturn<()> {
|
||||
if len >= 0 {
|
||||
while list.len() < len as usize {
|
||||
push(list, item.clone());
|
||||
push(list, item.clone())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$(reg_binary_mut($lib, $op, $func::<$par>, map);)* };
|
||||
macro_rules! reg_op {
|
||||
($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$( $lib.set_fn_2_mut($op, $func::<$par>); )*
|
||||
};
|
||||
}
|
||||
macro_rules! reg_tri { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$(reg_trinary_mut($lib, $op, $func::<$par>, map);)* };
|
||||
macro_rules! reg_tri {
|
||||
($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$( $lib.set_fn_3_mut($op, $func::<$par>); )*
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
@ -44,15 +49,16 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
|
||||
reg_tri!(lib, "pad", pad, INT, bool, char, String, Array, ());
|
||||
reg_tri!(lib, "insert", ins, INT, bool, char, String, Array, ());
|
||||
|
||||
reg_binary_mut(lib, "append", |x: &mut Array, y: Array| x.extend(y), map);
|
||||
reg_binary(
|
||||
lib,
|
||||
lib.set_fn_2_mut("append", |x: &mut Array, y: Array| {
|
||||
x.extend(y);
|
||||
Ok(())
|
||||
});
|
||||
lib.set_fn_2(
|
||||
"+",
|
||||
|mut x: Array, y: Array| {
|
||||
x.extend(y);
|
||||
x
|
||||
Ok(x)
|
||||
},
|
||||
map,
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
@ -70,40 +76,36 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
|
||||
reg_tri!(lib, "insert", ins, f32, f64);
|
||||
}
|
||||
|
||||
reg_unary_mut(
|
||||
lib,
|
||||
lib.set_fn_1_mut(
|
||||
"pop",
|
||||
|list: &mut Array| list.pop().unwrap_or_else(|| ().into()),
|
||||
pass,
|
||||
|list: &mut Array| Ok(list.pop().unwrap_or_else(|| ().into())),
|
||||
);
|
||||
reg_unary_mut(
|
||||
lib,
|
||||
lib.set_fn_1_mut(
|
||||
"shift",
|
||||
|list: &mut Array| {
|
||||
if list.is_empty() {
|
||||
Ok(if list.is_empty() {
|
||||
().into()
|
||||
} else {
|
||||
list.remove(0)
|
||||
}
|
||||
})
|
||||
},
|
||||
pass,
|
||||
);
|
||||
reg_binary_mut(
|
||||
lib,
|
||||
lib.set_fn_2_mut(
|
||||
"remove",
|
||||
|list: &mut Array, len: INT| {
|
||||
if len < 0 || (len as usize) >= list.len() {
|
||||
Ok(if len < 0 || (len as usize) >= list.len() {
|
||||
().into()
|
||||
} else {
|
||||
list.remove(len as usize)
|
||||
}
|
||||
})
|
||||
},
|
||||
pass,
|
||||
);
|
||||
reg_unary_mut(lib, "len", |list: &mut Array| list.len() as INT, map);
|
||||
reg_unary_mut(lib, "clear", |list: &mut Array| list.clear(), map);
|
||||
reg_binary_mut(
|
||||
lib,
|
||||
lib.set_fn_1_mut("len", |list: &mut Array| Ok(list.len() as INT));
|
||||
lib.set_fn_1_mut("clear", |list: &mut Array| {
|
||||
list.clear();
|
||||
Ok(())
|
||||
});
|
||||
lib.set_fn_2_mut(
|
||||
"truncate",
|
||||
|list: &mut Array, len: INT| {
|
||||
if len >= 0 {
|
||||
@ -111,16 +113,13 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
|
||||
} else {
|
||||
list.clear();
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
map,
|
||||
);
|
||||
|
||||
// Register array iterator
|
||||
lib.type_iterators.insert(
|
||||
lib.set_iter(
|
||||
TypeId::of::<Array>(),
|
||||
Box::new(|a: Dynamic| {
|
||||
Box::new(a.cast::<Array>().into_iter())
|
||||
as Box<dyn Iterator<Item = Dynamic>>
|
||||
}),
|
||||
|arr| Box::new(arr.cast::<Array>().into_iter()) as Box<dyn Iterator<Item = Dynamic>>,
|
||||
);
|
||||
});
|
||||
|
12
src/packages/eval.rs
Normal file
12
src/packages/eval.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use crate::def_package;
|
||||
use crate::module::FuncReturn;
|
||||
use crate::stdlib::string::String;
|
||||
|
||||
def_package!(crate:EvalPackage:"Disable 'eval'.", lib, {
|
||||
lib.set_fn_1_mut(
|
||||
"eval",
|
||||
|_: &mut String| -> FuncReturn<()> {
|
||||
Err("eval is evil!".into())
|
||||
},
|
||||
);
|
||||
});
|
@ -1,8 +1,6 @@
|
||||
use super::{reg_binary, reg_trinary, PackageStore};
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::def_package;
|
||||
use crate::fn_register::map_dynamic as map;
|
||||
use crate::module::{FuncReturn, Module};
|
||||
use crate::parser::INT;
|
||||
|
||||
use crate::stdlib::{
|
||||
@ -12,17 +10,18 @@ use crate::stdlib::{
|
||||
};
|
||||
|
||||
// Register range function
|
||||
fn reg_range<T: Variant + Clone>(lib: &mut PackageStore)
|
||||
fn reg_range<T: Variant + Clone>(lib: &mut Module)
|
||||
where
|
||||
Range<T>: Iterator<Item = T>,
|
||||
{
|
||||
lib.type_iterators.insert(
|
||||
TypeId::of::<Range<T>>(),
|
||||
Box::new(|source: Dynamic| {
|
||||
Box::new(source.cast::<Range<T>>().map(|x| x.into_dynamic()))
|
||||
as Box<dyn Iterator<Item = Dynamic>>
|
||||
}),
|
||||
);
|
||||
lib.set_iter(TypeId::of::<Range<T>>(), |source| {
|
||||
Box::new(source.cast::<Range<T>>().map(|x| x.into_dynamic()))
|
||||
as Box<dyn Iterator<Item = Dynamic>>
|
||||
});
|
||||
}
|
||||
|
||||
fn get_range<T: Variant + Clone>(from: T, to: T) -> FuncReturn<Range<T>> {
|
||||
Ok(from..to)
|
||||
}
|
||||
|
||||
// Register range function with step
|
||||
@ -50,37 +49,38 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn reg_step<T>(lib: &mut PackageStore)
|
||||
fn reg_step<T>(lib: &mut Module)
|
||||
where
|
||||
for<'a> &'a T: Add<&'a T, Output = T>,
|
||||
T: Variant + Clone + PartialOrd,
|
||||
StepRange<T>: Iterator<Item = T>,
|
||||
{
|
||||
lib.type_iterators.insert(
|
||||
TypeId::of::<StepRange<T>>(),
|
||||
Box::new(|source: Dynamic| {
|
||||
Box::new(source.cast::<StepRange<T>>().map(|x| x.into_dynamic()))
|
||||
as Box<dyn Iterator<Item = Dynamic>>
|
||||
}),
|
||||
);
|
||||
lib.set_iter(TypeId::of::<StepRange<T>>(), |source| {
|
||||
Box::new(source.cast::<StepRange<T>>().map(|x| x.into_dynamic()))
|
||||
as Box<dyn Iterator<Item = Dynamic>>
|
||||
});
|
||||
}
|
||||
|
||||
fn get_step_range<T>(from: T, to: T, step: T) -> FuncReturn<StepRange<T>>
|
||||
where
|
||||
for<'a> &'a T: Add<&'a T, Output = T>,
|
||||
T: Variant + Clone + PartialOrd,
|
||||
{
|
||||
Ok(StepRange::<T>(from, to, step))
|
||||
}
|
||||
|
||||
def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
|
||||
fn get_range<T>(from: T, to: T) -> Range<T> {
|
||||
from..to
|
||||
}
|
||||
|
||||
reg_range::<INT>(lib);
|
||||
reg_binary(lib, "range", get_range::<INT>, map);
|
||||
lib.set_fn_2("range", get_range::<INT>);
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
{
|
||||
macro_rules! reg_range {
|
||||
($self:expr, $x:expr, $( $y:ty ),*) => (
|
||||
($lib:expr, $x:expr, $( $y:ty ),*) => (
|
||||
$(
|
||||
reg_range::<$y>($self);
|
||||
reg_binary($self, $x, get_range::<$y>, map);
|
||||
reg_range::<$y>($lib);
|
||||
$lib.set_fn_2($x, get_range::<$y>);
|
||||
)*
|
||||
)
|
||||
}
|
||||
@ -89,16 +89,16 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
|
||||
}
|
||||
|
||||
reg_step::<INT>(lib);
|
||||
reg_trinary(lib, "range", StepRange::<INT>, map);
|
||||
lib.set_fn_3("range", get_step_range::<INT>);
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
{
|
||||
macro_rules! reg_step {
|
||||
($self:expr, $x:expr, $( $y:ty ),*) => (
|
||||
($lib:expr, $x:expr, $( $y:ty ),*) => (
|
||||
$(
|
||||
reg_step::<$y>($self);
|
||||
reg_trinary($self, $x, StepRange::<$y>, map);
|
||||
reg_step::<$y>($lib);
|
||||
$lib.set_fn_3($x, get_step_range::<$y>);
|
||||
)*
|
||||
)
|
||||
}
|
||||
|
@ -1,44 +1,44 @@
|
||||
use super::{reg_binary, reg_binary_mut, reg_unary};
|
||||
|
||||
use crate::def_package;
|
||||
use crate::fn_register::map_dynamic as map;
|
||||
use crate::module::FuncReturn;
|
||||
use crate::parser::INT;
|
||||
|
||||
use crate::stdlib::string::String;
|
||||
|
||||
// Comparison operators
|
||||
pub fn lt<T: PartialOrd>(x: T, y: T) -> bool {
|
||||
x < y
|
||||
pub fn lt<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
|
||||
Ok(x < y)
|
||||
}
|
||||
pub fn lte<T: PartialOrd>(x: T, y: T) -> bool {
|
||||
x <= y
|
||||
pub fn lte<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
|
||||
Ok(x <= y)
|
||||
}
|
||||
pub fn gt<T: PartialOrd>(x: T, y: T) -> bool {
|
||||
x > y
|
||||
pub fn gt<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
|
||||
Ok(x > y)
|
||||
}
|
||||
pub fn gte<T: PartialOrd>(x: T, y: T) -> bool {
|
||||
x >= y
|
||||
pub fn gte<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
|
||||
Ok(x >= y)
|
||||
}
|
||||
pub fn eq<T: PartialEq>(x: T, y: T) -> bool {
|
||||
x == y
|
||||
pub fn eq<T: PartialEq>(x: T, y: T) -> FuncReturn<bool> {
|
||||
Ok(x == y)
|
||||
}
|
||||
pub fn ne<T: PartialEq>(x: T, y: T) -> bool {
|
||||
x != y
|
||||
pub fn ne<T: PartialEq>(x: T, y: T) -> FuncReturn<bool> {
|
||||
Ok(x != y)
|
||||
}
|
||||
|
||||
// Logic operators
|
||||
fn and(x: bool, y: bool) -> bool {
|
||||
x && y
|
||||
fn and(x: bool, y: bool) -> FuncReturn<bool> {
|
||||
Ok(x && y)
|
||||
}
|
||||
fn or(x: bool, y: bool) -> bool {
|
||||
x || y
|
||||
fn or(x: bool, y: bool) -> FuncReturn<bool> {
|
||||
Ok(x || y)
|
||||
}
|
||||
fn not(x: bool) -> bool {
|
||||
!x
|
||||
fn not(x: bool) -> FuncReturn<bool> {
|
||||
Ok(!x)
|
||||
}
|
||||
|
||||
macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$(reg_binary($lib, $op, $func::<$par>, map);)* };
|
||||
macro_rules! reg_op {
|
||||
($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$( $lib.set_fn_2($op, $func::<$par>); )*
|
||||
};
|
||||
}
|
||||
|
||||
def_package!(crate:LogicPackage:"Logical operators.", lib, {
|
||||
@ -50,14 +50,12 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, {
|
||||
reg_op!(lib, "!=", ne, INT, char, bool, ());
|
||||
|
||||
// Special versions for strings - at least avoid copying the first string
|
||||
// use super::utils::reg_test;
|
||||
// reg_test(lib, "<", |x: &mut String, y: String| *x < y, |v| v, map);
|
||||
reg_binary_mut(lib, "<", |x: &mut String, y: String| *x < y, map);
|
||||
reg_binary_mut(lib, "<=", |x: &mut String, y: String| *x <= y, map);
|
||||
reg_binary_mut(lib, ">", |x: &mut String, y: String| *x > y, map);
|
||||
reg_binary_mut(lib, ">=", |x: &mut String, y: String| *x >= y, map);
|
||||
reg_binary_mut(lib, "==", |x: &mut String, y: String| *x == y, map);
|
||||
reg_binary_mut(lib, "!=", |x: &mut String, y: String| *x != y, map);
|
||||
lib.set_fn_2_mut("<", |x: &mut String, y: String| Ok(*x < y));
|
||||
lib.set_fn_2_mut("<=", |x: &mut String, y: String| Ok(*x <= y));
|
||||
lib.set_fn_2_mut(">", |x: &mut String, y: String| Ok(*x > y));
|
||||
lib.set_fn_2_mut(">=", |x: &mut String, y: String| Ok(*x >= y));
|
||||
lib.set_fn_2_mut("==", |x: &mut String, y: String| Ok(*x == y));
|
||||
lib.set_fn_2_mut("!=", |x: &mut String, y: String| Ok(*x != y));
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
@ -85,7 +83,7 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, {
|
||||
//reg_op!(lib, "||", or, bool);
|
||||
//reg_op!(lib, "&&", and, bool);
|
||||
|
||||
reg_binary(lib, "|", or, map);
|
||||
reg_binary(lib, "&", and, map);
|
||||
reg_unary(lib, "!", not, map);
|
||||
lib.set_fn_2("|", or);
|
||||
lib.set_fn_2("&", and);
|
||||
lib.set_fn_1("!", not);
|
||||
});
|
||||
|
@ -1,11 +1,9 @@
|
||||
#![cfg(not(feature = "no_object"))]
|
||||
|
||||
use super::{reg_binary, reg_binary_mut, reg_unary_mut};
|
||||
|
||||
use crate::any::Dynamic;
|
||||
use crate::def_package;
|
||||
use crate::engine::Map;
|
||||
use crate::fn_register::map_dynamic as map;
|
||||
use crate::module::FuncReturn;
|
||||
use crate::parser::INT;
|
||||
|
||||
use crate::stdlib::{
|
||||
@ -13,55 +11,51 @@ use crate::stdlib::{
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
fn map_get_keys(map: &mut Map) -> Vec<Dynamic> {
|
||||
map.iter().map(|(k, _)| k.to_string().into()).collect()
|
||||
fn map_get_keys(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
|
||||
Ok(map.iter().map(|(k, _)| k.to_string().into()).collect())
|
||||
}
|
||||
fn map_get_values(map: &mut Map) -> Vec<Dynamic> {
|
||||
map.iter().map(|(_, v)| v.clone()).collect()
|
||||
fn map_get_values(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
|
||||
Ok(map.iter().map(|(_, v)| v.clone()).collect())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
|
||||
reg_binary_mut(
|
||||
lib,
|
||||
lib.set_fn_2_mut(
|
||||
"has",
|
||||
|map: &mut Map, prop: String| map.contains_key(&prop),
|
||||
map,
|
||||
|map: &mut Map, prop: String| Ok(map.contains_key(&prop)),
|
||||
);
|
||||
reg_unary_mut(lib, "len", |map: &mut Map| map.len() as INT, map);
|
||||
reg_unary_mut(lib, "clear", |map: &mut Map| map.clear(), map);
|
||||
reg_binary_mut(
|
||||
lib,
|
||||
lib.set_fn_1_mut("len", |map: &mut Map| Ok(map.len() as INT));
|
||||
lib.set_fn_1_mut("clear", |map: &mut Map| {
|
||||
map.clear();
|
||||
Ok(())
|
||||
});
|
||||
lib.set_fn_2_mut(
|
||||
"remove",
|
||||
|x: &mut Map, name: String| x.remove(&name).unwrap_or_else(|| ().into()),
|
||||
map,
|
||||
|x: &mut Map, name: String| Ok(x.remove(&name).unwrap_or_else(|| ().into())),
|
||||
);
|
||||
reg_binary_mut(
|
||||
lib,
|
||||
lib.set_fn_2_mut(
|
||||
"mixin",
|
||||
|map1: &mut Map, map2: Map| {
|
||||
map2.into_iter().for_each(|(key, value)| {
|
||||
map1.insert(key, value);
|
||||
});
|
||||
Ok(())
|
||||
},
|
||||
map,
|
||||
);
|
||||
reg_binary(
|
||||
lib,
|
||||
lib.set_fn_2(
|
||||
"+",
|
||||
|mut map1: Map, map2: Map| {
|
||||
map2.into_iter().for_each(|(key, value)| {
|
||||
map1.insert(key, value);
|
||||
});
|
||||
map1
|
||||
Ok(map1)
|
||||
},
|
||||
map,
|
||||
);
|
||||
|
||||
// Register map access functions
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
reg_unary_mut(lib, "keys", map_get_keys, map);
|
||||
lib.set_fn_1_mut("keys", map_get_keys);
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
reg_unary_mut(lib, "values", map_get_values, map);
|
||||
lib.set_fn_1_mut("values", map_get_values);
|
||||
});
|
||||
|
@ -1,7 +1,4 @@
|
||||
use super::{reg_binary, reg_unary};
|
||||
|
||||
use crate::def_package;
|
||||
use crate::fn_register::{map_dynamic as map, map_result as result};
|
||||
use crate::parser::INT;
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::token::Position;
|
||||
@ -20,78 +17,77 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
// Advanced math functions
|
||||
reg_unary(lib, "sin", |x: FLOAT| x.to_radians().sin(), map);
|
||||
reg_unary(lib, "cos", |x: FLOAT| x.to_radians().cos(), map);
|
||||
reg_unary(lib, "tan", |x: FLOAT| x.to_radians().tan(), map);
|
||||
reg_unary(lib, "sinh", |x: FLOAT| x.to_radians().sinh(), map);
|
||||
reg_unary(lib, "cosh", |x: FLOAT| x.to_radians().cosh(), map);
|
||||
reg_unary(lib, "tanh", |x: FLOAT| x.to_radians().tanh(), map);
|
||||
reg_unary(lib, "asin", |x: FLOAT| x.asin().to_degrees(), map);
|
||||
reg_unary(lib, "acos", |x: FLOAT| x.acos().to_degrees(), map);
|
||||
reg_unary(lib, "atan", |x: FLOAT| x.atan().to_degrees(), map);
|
||||
reg_unary(lib, "asinh", |x: FLOAT| x.asinh().to_degrees(), map);
|
||||
reg_unary(lib, "acosh", |x: FLOAT| x.acosh().to_degrees(), map);
|
||||
reg_unary(lib, "atanh", |x: FLOAT| x.atanh().to_degrees(), map);
|
||||
reg_unary(lib, "sqrt", |x: FLOAT| x.sqrt(), map);
|
||||
reg_unary(lib, "exp", |x: FLOAT| x.exp(), map);
|
||||
reg_unary(lib, "ln", |x: FLOAT| x.ln(), map);
|
||||
reg_binary(lib, "log", |x: FLOAT, base: FLOAT| x.log(base), map);
|
||||
reg_unary(lib, "log10", |x: FLOAT| x.log10(), map);
|
||||
reg_unary(lib, "floor", |x: FLOAT| x.floor(), map);
|
||||
reg_unary(lib, "ceiling", |x: FLOAT| x.ceil(), map);
|
||||
reg_unary(lib, "round", |x: FLOAT| x.ceil(), map);
|
||||
reg_unary(lib, "int", |x: FLOAT| x.trunc(), map);
|
||||
reg_unary(lib, "fraction", |x: FLOAT| x.fract(), map);
|
||||
reg_unary(lib, "is_nan", |x: FLOAT| x.is_nan(), map);
|
||||
reg_unary(lib, "is_finite", |x: FLOAT| x.is_finite(), map);
|
||||
reg_unary(lib, "is_infinite", |x: FLOAT| x.is_infinite(), map);
|
||||
lib.set_fn_1("sin", |x: FLOAT| Ok(x.to_radians().sin()));
|
||||
lib.set_fn_1("cos", |x: FLOAT| Ok(x.to_radians().cos()));
|
||||
lib.set_fn_1("tan", |x: FLOAT| Ok(x.to_radians().tan()));
|
||||
lib.set_fn_1("sinh", |x: FLOAT| Ok(x.to_radians().sinh()));
|
||||
lib.set_fn_1("cosh", |x: FLOAT| Ok(x.to_radians().cosh()));
|
||||
lib.set_fn_1("tanh", |x: FLOAT| Ok(x.to_radians().tanh()));
|
||||
lib.set_fn_1("asin", |x: FLOAT| Ok(x.asin().to_degrees()));
|
||||
lib.set_fn_1("acos", |x: FLOAT| Ok(x.acos().to_degrees()));
|
||||
lib.set_fn_1("atan", |x: FLOAT| Ok(x.atan().to_degrees()));
|
||||
lib.set_fn_1("asinh", |x: FLOAT| Ok(x.asinh().to_degrees()));
|
||||
lib.set_fn_1("acosh", |x: FLOAT| Ok(x.acosh().to_degrees()));
|
||||
lib.set_fn_1("atanh", |x: FLOAT| Ok(x.atanh().to_degrees()));
|
||||
lib.set_fn_1("sqrt", |x: FLOAT| Ok(x.sqrt()));
|
||||
lib.set_fn_1("exp", |x: FLOAT| Ok(x.exp()));
|
||||
lib.set_fn_1("ln", |x: FLOAT| Ok(x.ln()));
|
||||
lib.set_fn_2("log", |x: FLOAT, base: FLOAT| Ok(x.log(base)));
|
||||
lib.set_fn_1("log10", |x: FLOAT| Ok(x.log10()));
|
||||
lib.set_fn_1("floor", |x: FLOAT| Ok(x.floor()));
|
||||
lib.set_fn_1("ceiling", |x: FLOAT| Ok(x.ceil()));
|
||||
lib.set_fn_1("round", |x: FLOAT| Ok(x.ceil()));
|
||||
lib.set_fn_1("int", |x: FLOAT| Ok(x.trunc()));
|
||||
lib.set_fn_1("fraction", |x: FLOAT| Ok(x.fract()));
|
||||
lib.set_fn_1("is_nan", |x: FLOAT| Ok(x.is_nan()));
|
||||
lib.set_fn_1("is_finite", |x: FLOAT| Ok(x.is_finite()));
|
||||
lib.set_fn_1("is_infinite", |x: FLOAT| Ok(x.is_infinite()));
|
||||
|
||||
// Register conversion functions
|
||||
reg_unary(lib, "to_float", |x: INT| x as FLOAT, map);
|
||||
reg_unary(lib, "to_float", |x: f32| x as FLOAT, map);
|
||||
lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT));
|
||||
lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT));
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
{
|
||||
reg_unary(lib, "to_float", |x: i8| x as FLOAT, map);
|
||||
reg_unary(lib, "to_float", |x: u8| x as FLOAT, map);
|
||||
reg_unary(lib, "to_float", |x: i16| x as FLOAT, map);
|
||||
reg_unary(lib, "to_float", |x: u16| x as FLOAT, map);
|
||||
reg_unary(lib, "to_float", |x: i32| x as FLOAT, map);
|
||||
reg_unary(lib, "to_float", |x: u32| x as FLOAT, map);
|
||||
reg_unary(lib, "to_float", |x: i64| x as FLOAT, map);
|
||||
reg_unary(lib, "to_float", |x: u64| x as FLOAT, map);
|
||||
reg_unary(lib, "to_float", |x: i128| x as FLOAT, map);
|
||||
reg_unary(lib, "to_float", |x: u128| x as FLOAT, map);
|
||||
lib.set_fn_1("to_float", |x: i8| Ok(x as FLOAT));
|
||||
lib.set_fn_1("to_float", |x: u8| Ok(x as FLOAT));
|
||||
lib.set_fn_1("to_float", |x: i16| Ok(x as FLOAT));
|
||||
lib.set_fn_1("to_float", |x: u16| Ok(x as FLOAT));
|
||||
lib.set_fn_1("to_float", |x: i32| Ok(x as FLOAT));
|
||||
lib.set_fn_1("to_float", |x: u32| Ok(x as FLOAT));
|
||||
lib.set_fn_1("to_float", |x: i64| Ok(x as FLOAT));
|
||||
lib.set_fn_1("to_float", |x: u64| Ok(x as FLOAT));
|
||||
lib.set_fn_1("to_float", |x: i128| Ok(x as FLOAT));
|
||||
lib.set_fn_1("to_float", |x: u128| Ok(x as FLOAT));
|
||||
}
|
||||
}
|
||||
|
||||
reg_unary(lib, "to_int", |ch: char| ch as INT, map);
|
||||
lib.set_fn_1("to_int", |ch: char| Ok(ch as INT));
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
{
|
||||
reg_unary(lib, "to_int", |x: i8| x as INT, map);
|
||||
reg_unary(lib, "to_int", |x: u8| x as INT, map);
|
||||
reg_unary(lib, "to_int", |x: i16| x as INT, map);
|
||||
reg_unary(lib, "to_int", |x: u16| x as INT, map);
|
||||
lib.set_fn_1("to_int", |x: i8| Ok(x as INT));
|
||||
lib.set_fn_1("to_int", |x: u8| Ok(x as INT));
|
||||
lib.set_fn_1("to_int", |x: i16| Ok(x as INT));
|
||||
lib.set_fn_1("to_int", |x: u16| Ok(x as INT));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
{
|
||||
reg_unary(lib, "to_int", |x: i32| x as INT, map);
|
||||
reg_unary(lib, "to_int", |x: u64| x as INT, map);
|
||||
lib.set_fn_1("to_int", |x: i32| Ok(x as INT));
|
||||
lib.set_fn_1("to_int", |x: u64| Ok(x as INT));
|
||||
|
||||
#[cfg(feature = "only_i64")]
|
||||
reg_unary(lib, "to_int", |x: u32| x as INT, map);
|
||||
lib.set_fn_1("to_int", |x: u32| Ok(x as INT));
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
reg_unary(
|
||||
lib,
|
||||
lib.set_fn_1(
|
||||
"to_int",
|
||||
|x: f32| {
|
||||
if x > (MAX_INT as f32) {
|
||||
@ -103,10 +99,8 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
|
||||
|
||||
Ok(x.trunc() as INT)
|
||||
},
|
||||
result,
|
||||
);
|
||||
reg_unary(
|
||||
lib,
|
||||
lib.set_fn_1(
|
||||
"to_int",
|
||||
|x: FLOAT| {
|
||||
if x > (MAX_INT as FLOAT) {
|
||||
@ -118,14 +112,13 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
|
||||
|
||||
Ok(x.trunc() as INT)
|
||||
},
|
||||
result,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "unchecked")]
|
||||
{
|
||||
reg_unary(lib, "to_int", |x: f32| x as INT, map);
|
||||
reg_unary(lib, "to_int", |x: f64| x as INT, map);
|
||||
lib.set_fn_1("to_int", |x: f32| Ok(x as INT));
|
||||
lib.set_fn_1("to_int", |x: f64| Ok(x as INT));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,11 +1,14 @@
|
||||
//! 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::engine::{FnAny, IteratorFn};
|
||||
use crate::fn_native::{CallableFunction, IteratorFn};
|
||||
use crate::module::Module;
|
||||
use crate::utils::StaticVec;
|
||||
|
||||
use crate::stdlib::{any::TypeId, boxed::Box, collections::HashMap, rc::Rc, sync::Arc, vec::Vec};
|
||||
|
||||
mod arithmetic;
|
||||
mod array_basic;
|
||||
mod eval;
|
||||
mod iter_basic;
|
||||
mod logic;
|
||||
mod map_basic;
|
||||
@ -15,11 +18,11 @@ mod pkg_std;
|
||||
mod string_basic;
|
||||
mod string_more;
|
||||
mod time_basic;
|
||||
mod utils;
|
||||
|
||||
pub use arithmetic::ArithmeticPackage;
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub use array_basic::BasicArrayPackage;
|
||||
pub use eval::EvalPackage;
|
||||
pub use iter_basic::BasicIteratorPackage;
|
||||
pub use logic::LogicPackage;
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
@ -32,74 +35,29 @@ pub use string_more::MoreStringPackage;
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
pub use time_basic::BasicTimePackage;
|
||||
|
||||
pub use utils::*;
|
||||
|
||||
const NUM_NATIVE_FUNCTIONS: usize = 512;
|
||||
|
||||
/// Trait that all packages must implement.
|
||||
pub trait Package {
|
||||
/// Register all the functions in a package into a store.
|
||||
fn init(lib: &mut PackageStore);
|
||||
fn init(lib: &mut Module);
|
||||
|
||||
/// Retrieve the generic package library from this package.
|
||||
fn get(&self) -> PackageLibrary;
|
||||
}
|
||||
|
||||
/// Type to store all functions in the package.
|
||||
pub struct PackageStore {
|
||||
/// All functions, keyed by a hash created from the function name and parameter types.
|
||||
pub functions: HashMap<u64, Box<FnAny>>,
|
||||
|
||||
/// All iterator functions, keyed by the type producing the iterator.
|
||||
pub type_iterators: HashMap<TypeId, Box<IteratorFn>>,
|
||||
}
|
||||
|
||||
impl PackageStore {
|
||||
/// Create a new `PackageStore`.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
/// Does the specified function hash key exist in the `PackageStore`?
|
||||
pub fn contains_function(&self, hash: u64) -> bool {
|
||||
self.functions.contains_key(&hash)
|
||||
}
|
||||
/// Get specified function via its hash key.
|
||||
pub fn get_function(&self, hash: u64) -> Option<&Box<FnAny>> {
|
||||
self.functions.get(&hash)
|
||||
}
|
||||
/// Does the specified TypeId iterator exist in the `PackageStore`?
|
||||
pub fn contains_iterator(&self, id: TypeId) -> bool {
|
||||
self.type_iterators.contains_key(&id)
|
||||
}
|
||||
/// Get the specified TypeId iterator.
|
||||
pub fn get_iterator(&self, id: TypeId) -> Option<&Box<IteratorFn>> {
|
||||
self.type_iterators.get(&id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PackageStore {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
functions: HashMap::with_capacity(NUM_NATIVE_FUNCTIONS),
|
||||
type_iterators: HashMap::with_capacity(4),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type which `Rc`-wraps a `PackageStore` to facilitate sharing library instances.
|
||||
/// Type which `Rc`-wraps a `Module` to facilitate sharing library instances.
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub type PackageLibrary = Rc<PackageStore>;
|
||||
pub type PackageLibrary = Rc<Module>;
|
||||
|
||||
/// Type which `Arc`-wraps a `PackageStore` to facilitate sharing library instances.
|
||||
/// Type which `Arc`-wraps a `Module` to facilitate sharing library instances.
|
||||
#[cfg(feature = "sync")]
|
||||
pub type PackageLibrary = Arc<PackageStore>;
|
||||
pub type PackageLibrary = Arc<Module>;
|
||||
|
||||
/// Type containing a collection of `PackageLibrary` instances.
|
||||
/// All function and type iterator keys in the loaded packages are indexed for fast access.
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct PackagesCollection {
|
||||
/// Collection of `PackageLibrary` instances.
|
||||
packages: Vec<PackageLibrary>,
|
||||
packages: StaticVec<PackageLibrary>,
|
||||
}
|
||||
|
||||
impl PackagesCollection {
|
||||
@ -109,27 +67,75 @@ impl PackagesCollection {
|
||||
self.packages.insert(0, package);
|
||||
}
|
||||
/// Does the specified function hash key exist in the `PackagesCollection`?
|
||||
pub fn contains_function(&self, hash: u64) -> bool {
|
||||
self.packages.iter().any(|p| p.contains_function(hash))
|
||||
pub fn contains_fn(&self, hash: u64) -> bool {
|
||||
self.packages.iter().any(|p| p.contains_fn(hash))
|
||||
}
|
||||
/// Get specified function via its hash key.
|
||||
pub fn get_function(&self, hash: u64) -> Option<&Box<FnAny>> {
|
||||
pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
|
||||
self.packages
|
||||
.iter()
|
||||
.map(|p| p.get_function(hash))
|
||||
.map(|p| p.get_fn(hash))
|
||||
.find(|f| f.is_some())
|
||||
.flatten()
|
||||
}
|
||||
/// Does the specified TypeId iterator exist in the `PackagesCollection`?
|
||||
pub fn contains_iterator(&self, id: TypeId) -> bool {
|
||||
self.packages.iter().any(|p| p.contains_iterator(id))
|
||||
pub fn contains_iter(&self, id: TypeId) -> bool {
|
||||
self.packages.iter().any(|p| p.contains_iter(id))
|
||||
}
|
||||
/// Get the specified TypeId iterator.
|
||||
pub fn get_iterator(&self, id: TypeId) -> Option<&Box<IteratorFn>> {
|
||||
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
|
||||
self.packages
|
||||
.iter()
|
||||
.map(|p| p.get_iterator(id))
|
||||
.map(|p| p.get_iter(id))
|
||||
.find(|f| f.is_some())
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
/// Macro that makes it easy to define a _package_ (which is basically a shared module)
|
||||
/// and register functions into it.
|
||||
///
|
||||
/// Functions can be added to the package using the standard module methods such as
|
||||
/// `set_fn_2`, `set_fn_3_mut`, `set_fn_0` etc.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::{Dynamic, EvalAltResult};
|
||||
/// use rhai::def_package;
|
||||
///
|
||||
/// fn add(x: i64, y: i64) -> Result<i64, Box<EvalAltResult>> { Ok(x + y) }
|
||||
///
|
||||
/// def_package!(rhai:MyPackage:"My super-duper package", lib,
|
||||
/// {
|
||||
/// // Load a binary function with all value parameters.
|
||||
/// lib.set_fn_2("my_add", add);
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// The above defines a package named 'MyPackage' with a single function named 'my_add'.
|
||||
#[macro_export]
|
||||
macro_rules! def_package {
|
||||
($root:ident : $package:ident : $comment:expr , $lib:ident , $block:stmt) => {
|
||||
#[doc=$comment]
|
||||
pub struct $package($root::packages::PackageLibrary);
|
||||
|
||||
impl $root::packages::Package for $package {
|
||||
fn get(&self) -> $root::packages::PackageLibrary {
|
||||
self.0.clone()
|
||||
}
|
||||
|
||||
fn init($lib: &mut $root::Module) {
|
||||
$block
|
||||
}
|
||||
}
|
||||
|
||||
impl $package {
|
||||
pub fn new() -> Self {
|
||||
let mut module = $root::Module::new_with_capacity(512);
|
||||
<Self as $root::packages::Package>::init(&mut module);
|
||||
Self(module.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
use super::{reg_binary, reg_binary_mut, reg_none, reg_unary, reg_unary_mut};
|
||||
|
||||
use crate::def_package;
|
||||
use crate::engine::{FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT};
|
||||
use crate::fn_register::map_dynamic as map;
|
||||
use crate::module::FuncReturn;
|
||||
use crate::parser::INT;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
@ -18,31 +16,33 @@ use crate::stdlib::{
|
||||
};
|
||||
|
||||
// Register print and debug
|
||||
fn to_debug<T: Debug>(x: &mut T) -> String {
|
||||
format!("{:?}", x)
|
||||
fn to_debug<T: Debug>(x: &mut T) -> FuncReturn<String> {
|
||||
Ok(format!("{:?}", x))
|
||||
}
|
||||
fn to_string<T: Display>(x: &mut T) -> String {
|
||||
format!("{}", x)
|
||||
fn to_string<T: Display>(x: &mut T) -> FuncReturn<String> {
|
||||
Ok(format!("{}", x))
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
fn format_map(x: &mut Map) -> String {
|
||||
format!("#{:?}", x)
|
||||
fn format_map(x: &mut Map) -> FuncReturn<String> {
|
||||
Ok(format!("#{:?}", x))
|
||||
}
|
||||
|
||||
macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$(reg_unary_mut($lib, $op, $func::<$par>, map);)* };
|
||||
macro_rules! reg_op {
|
||||
($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$( $lib.set_fn_1_mut($op, $func::<$par>); )*
|
||||
};
|
||||
}
|
||||
|
||||
def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, {
|
||||
reg_op!(lib, KEYWORD_PRINT, to_string, INT, bool, char);
|
||||
reg_op!(lib, FUNC_TO_STRING, to_string, INT, bool, char);
|
||||
|
||||
reg_none(lib, KEYWORD_PRINT, || "".to_string(), map);
|
||||
reg_unary(lib, KEYWORD_PRINT, |_: ()| "".to_string(), map);
|
||||
reg_unary(lib, FUNC_TO_STRING, |_: ()| "".to_string(), map);
|
||||
lib.set_fn_0(KEYWORD_PRINT, || Ok("".to_string()));
|
||||
lib.set_fn_1(KEYWORD_PRINT, |_: ()| Ok("".to_string()));
|
||||
lib.set_fn_1(FUNC_TO_STRING, |_: ()| Ok("".to_string()));
|
||||
|
||||
reg_unary_mut(lib, KEYWORD_PRINT, |s: &mut String| s.clone(), map);
|
||||
reg_unary_mut(lib, FUNC_TO_STRING, |s: &mut String| s.clone(), map);
|
||||
lib.set_fn_1_mut(KEYWORD_PRINT, |s: &mut String| Ok(s.clone()));
|
||||
lib.set_fn_1_mut(FUNC_TO_STRING, |s: &mut String| Ok(s.clone()));
|
||||
|
||||
reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, String);
|
||||
|
||||
@ -73,34 +73,34 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
reg_unary_mut(lib, KEYWORD_PRINT, format_map, map);
|
||||
reg_unary_mut(lib, FUNC_TO_STRING, format_map, map);
|
||||
reg_unary_mut(lib, KEYWORD_DEBUG, format_map, map);
|
||||
lib.set_fn_1_mut(KEYWORD_PRINT, format_map);
|
||||
lib.set_fn_1_mut(FUNC_TO_STRING, format_map);
|
||||
lib.set_fn_1_mut(KEYWORD_DEBUG, format_map);
|
||||
}
|
||||
|
||||
reg_binary(
|
||||
lib,
|
||||
lib.set_fn_2(
|
||||
"+",
|
||||
|mut s: String, ch: char| {
|
||||
s.push(ch);
|
||||
s
|
||||
Ok(s)
|
||||
},
|
||||
map,
|
||||
);
|
||||
reg_binary(
|
||||
lib,
|
||||
lib.set_fn_2(
|
||||
"+",
|
||||
|mut s: String, s2: String| {
|
||||
s.push_str(&s2);
|
||||
s
|
||||
Ok(s)
|
||||
},
|
||||
map,
|
||||
);
|
||||
reg_binary_mut(lib, "append", |s: &mut String, ch: char| s.push(ch), map);
|
||||
reg_binary_mut(
|
||||
lib,
|
||||
lib.set_fn_2_mut("append", |s: &mut String, ch: char| {
|
||||
s.push(ch);
|
||||
Ok(())
|
||||
});
|
||||
lib.set_fn_2_mut(
|
||||
"append",
|
||||
|s: &mut String, s2: String| s.push_str(&s2),
|
||||
map,
|
||||
|s: &mut String, s2: String| {
|
||||
s.push_str(&s2);
|
||||
Ok(())
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -1,8 +1,7 @@
|
||||
use super::{reg_binary, reg_binary_mut, reg_trinary_mut, reg_unary_mut};
|
||||
|
||||
use crate::def_package;
|
||||
use crate::fn_register::map_dynamic as map;
|
||||
use crate::module::FuncReturn;
|
||||
use crate::parser::INT;
|
||||
use crate::utils::StaticVec;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::engine::Array;
|
||||
@ -14,24 +13,24 @@ use crate::stdlib::{
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
fn prepend<T: Display>(x: T, y: String) -> String {
|
||||
format!("{}{}", x, y)
|
||||
fn prepend<T: Display>(x: T, y: String) -> FuncReturn<String> {
|
||||
Ok(format!("{}{}", x, y))
|
||||
}
|
||||
fn append<T: Display>(x: String, y: T) -> String {
|
||||
format!("{}{}", x, y)
|
||||
fn append<T: Display>(x: String, y: T) -> FuncReturn<String> {
|
||||
Ok(format!("{}{}", x, y))
|
||||
}
|
||||
fn sub_string(s: &mut String, start: INT, len: INT) -> String {
|
||||
fn sub_string(s: &mut String, start: INT, len: INT) -> FuncReturn<String> {
|
||||
let offset = if s.is_empty() || len <= 0 {
|
||||
return "".to_string();
|
||||
return Ok("".to_string());
|
||||
} else if start < 0 {
|
||||
0
|
||||
} else if (start as usize) >= s.chars().count() {
|
||||
return "".to_string();
|
||||
return Ok("".to_string());
|
||||
} else {
|
||||
start as usize
|
||||
};
|
||||
|
||||
let chars: Vec<_> = s.chars().collect();
|
||||
let chars: StaticVec<_> = s.chars().collect();
|
||||
|
||||
let len = if offset + (len as usize) > chars.len() {
|
||||
chars.len() - offset
|
||||
@ -39,22 +38,22 @@ fn sub_string(s: &mut String, start: INT, len: INT) -> String {
|
||||
len as usize
|
||||
};
|
||||
|
||||
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) {
|
||||
fn crop_string(s: &mut String, start: INT, len: INT) -> FuncReturn<()> {
|
||||
let offset = if s.is_empty() || len <= 0 {
|
||||
s.clear();
|
||||
return;
|
||||
return Ok(());
|
||||
} else if start < 0 {
|
||||
0
|
||||
} else if (start as usize) >= s.chars().count() {
|
||||
s.clear();
|
||||
return;
|
||||
return Ok(());
|
||||
} else {
|
||||
start as usize
|
||||
};
|
||||
|
||||
let chars: Vec<_> = s.chars().collect();
|
||||
let chars: StaticVec<_> = s.chars().collect();
|
||||
|
||||
let len = if offset + (len as usize) > chars.len() {
|
||||
chars.len() - offset
|
||||
@ -64,21 +63,27 @@ fn crop_string(s: &mut String, start: INT, len: INT) {
|
||||
|
||||
s.clear();
|
||||
|
||||
chars[offset..][..len]
|
||||
.into_iter()
|
||||
chars
|
||||
.iter()
|
||||
.skip(offset)
|
||||
.take(len)
|
||||
.for_each(|&ch| s.push(ch));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
macro_rules! reg_op { ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$(reg_binary($lib, $op, $func::<$par>, map);)* };
|
||||
macro_rules! reg_op {
|
||||
($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$( $lib.set_fn_2($op, $func::<$par>); )*
|
||||
};
|
||||
}
|
||||
|
||||
def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, {
|
||||
reg_op!(lib, "+", append, INT, bool, char);
|
||||
reg_binary_mut(lib, "+", |x: &mut String, _: ()| x.clone(), map);
|
||||
lib.set_fn_2_mut( "+", |x: &mut String, _: ()| Ok(x.clone()));
|
||||
|
||||
reg_op!(lib, "+", prepend, INT, bool, char);
|
||||
reg_binary(lib, "+", |_: (), y: String| y, map);
|
||||
lib.set_fn_2("+", |_: (), y: String| Ok(y));
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
@ -95,139 +100,153 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
{
|
||||
reg_binary(lib, "+", |x: String, y: Array| format!("{}{:?}", x, y), map);
|
||||
reg_binary(lib, "+", |x: Array, y: String| format!("{:?}{}", x, y), map);
|
||||
lib.set_fn_2("+", |x: String, y: Array| Ok(format!("{}{:?}", x, y)));
|
||||
lib.set_fn_2("+", |x: Array, y: String| Ok(format!("{:?}{}", x, y)));
|
||||
}
|
||||
|
||||
reg_unary_mut(lib, "len", |s: &mut String| s.chars().count() as INT, map);
|
||||
reg_binary_mut(
|
||||
lib,
|
||||
lib.set_fn_1_mut("len", |s: &mut String| Ok(s.chars().count() as INT));
|
||||
lib.set_fn_2_mut(
|
||||
"contains",
|
||||
|s: &mut String, ch: char| s.contains(ch),
|
||||
map,
|
||||
|s: &mut String, ch: char| Ok(s.contains(ch)),
|
||||
);
|
||||
reg_binary_mut(
|
||||
lib,
|
||||
lib.set_fn_2_mut(
|
||||
"contains",
|
||||
|s: &mut String, find: String| s.contains(&find),
|
||||
map,
|
||||
|s: &mut String, find: String| Ok(s.contains(&find)),
|
||||
);
|
||||
reg_trinary_mut(
|
||||
lib,
|
||||
lib.set_fn_3_mut(
|
||||
"index_of",
|
||||
|s: &mut String, ch: char, start: INT| {
|
||||
let start = if start < 0 {
|
||||
0
|
||||
} else if (start as usize) >= s.chars().count() {
|
||||
return -1 as INT;
|
||||
return Ok(-1 as INT);
|
||||
} else {
|
||||
s.chars().take(start as usize).collect::<String>().len()
|
||||
};
|
||||
|
||||
s[start..]
|
||||
Ok(s[start..]
|
||||
.find(ch)
|
||||
.map(|index| s[0..start + index].chars().count() as INT)
|
||||
.unwrap_or(-1 as INT)
|
||||
.unwrap_or(-1 as INT))
|
||||
},
|
||||
map,
|
||||
);
|
||||
reg_binary_mut(
|
||||
lib,
|
||||
lib.set_fn_2_mut(
|
||||
"index_of",
|
||||
|s: &mut String, ch: char| {
|
||||
s.find(ch)
|
||||
Ok(s.find(ch)
|
||||
.map(|index| s[0..index].chars().count() as INT)
|
||||
.unwrap_or(-1 as INT)
|
||||
.unwrap_or(-1 as INT))
|
||||
},
|
||||
map,
|
||||
);
|
||||
reg_trinary_mut(
|
||||
lib,
|
||||
lib.set_fn_3_mut(
|
||||
"index_of",
|
||||
|s: &mut String, find: String, start: INT| {
|
||||
let start = if start < 0 {
|
||||
0
|
||||
} else if (start as usize) >= s.chars().count() {
|
||||
return -1 as INT;
|
||||
return Ok(-1 as INT);
|
||||
} else {
|
||||
s.chars().take(start as usize).collect::<String>().len()
|
||||
};
|
||||
|
||||
s[start..]
|
||||
Ok(s[start..]
|
||||
.find(&find)
|
||||
.map(|index| s[0..start + index].chars().count() as INT)
|
||||
.unwrap_or(-1 as INT)
|
||||
.unwrap_or(-1 as INT))
|
||||
},
|
||||
map,
|
||||
);
|
||||
reg_binary_mut(
|
||||
lib,
|
||||
lib.set_fn_2_mut(
|
||||
"index_of",
|
||||
|s: &mut String, find: String| {
|
||||
s.find(&find)
|
||||
Ok(s.find(&find)
|
||||
.map(|index| s[0..index].chars().count() as INT)
|
||||
.unwrap_or(-1 as INT)
|
||||
.unwrap_or(-1 as INT))
|
||||
},
|
||||
map,
|
||||
);
|
||||
reg_unary_mut(lib, "clear", |s: &mut String| s.clear(), map);
|
||||
reg_binary_mut(lib, "append", |s: &mut String, ch: char| s.push(ch), map);
|
||||
reg_binary_mut(
|
||||
lib,
|
||||
lib.set_fn_1_mut("clear", |s: &mut String| {
|
||||
s.clear();
|
||||
Ok(())
|
||||
});
|
||||
lib.set_fn_2_mut( "append", |s: &mut String, ch: char| {
|
||||
s.push(ch);
|
||||
Ok(())
|
||||
});
|
||||
lib.set_fn_2_mut(
|
||||
"append",
|
||||
|s: &mut String, add: String| s.push_str(&add),
|
||||
map,
|
||||
|s: &mut String, add: String| {
|
||||
s.push_str(&add);
|
||||
Ok(())
|
||||
}
|
||||
);
|
||||
reg_trinary_mut(lib, "sub_string", sub_string, map);
|
||||
reg_binary_mut(
|
||||
lib,
|
||||
lib.set_fn_3_mut( "sub_string", sub_string);
|
||||
lib.set_fn_2_mut(
|
||||
"sub_string",
|
||||
|s: &mut String, start: INT| sub_string(s, start, s.len() as INT),
|
||||
map,
|
||||
);
|
||||
reg_trinary_mut(lib, "crop", crop_string, map);
|
||||
reg_binary_mut(
|
||||
lib,
|
||||
lib.set_fn_3_mut( "crop", crop_string);
|
||||
lib.set_fn_2_mut(
|
||||
"crop",
|
||||
|s: &mut String, start: INT| crop_string(s, start, s.len() as INT),
|
||||
map,
|
||||
);
|
||||
reg_binary_mut(
|
||||
lib,
|
||||
lib.set_fn_2_mut(
|
||||
"truncate",
|
||||
|s: &mut String, len: INT| {
|
||||
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();
|
||||
chars.into_iter().for_each(|ch| s.push(ch));
|
||||
chars.iter().for_each(|&ch| s.push(ch));
|
||||
} else {
|
||||
s.clear();
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
map,
|
||||
);
|
||||
reg_trinary_mut(
|
||||
lib,
|
||||
lib.set_fn_3_mut(
|
||||
"pad",
|
||||
|s: &mut String, len: INT, ch: char| {
|
||||
for _ in 0..s.chars().count() - len as usize {
|
||||
s.push(ch);
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
map,
|
||||
);
|
||||
reg_trinary_mut(
|
||||
lib,
|
||||
lib.set_fn_3_mut(
|
||||
"replace",
|
||||
|s: &mut String, find: String, sub: String| {
|
||||
let new_str = s.replace(&find, &sub);
|
||||
s.clear();
|
||||
s.push_str(&new_str);
|
||||
Ok(())
|
||||
},
|
||||
map,
|
||||
);
|
||||
reg_unary_mut(
|
||||
lib,
|
||||
lib.set_fn_3_mut(
|
||||
"replace",
|
||||
|s: &mut String, find: String, sub: char| {
|
||||
let new_str = s.replace(&find, &sub.to_string());
|
||||
s.clear();
|
||||
s.push_str(&new_str);
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
lib.set_fn_3_mut(
|
||||
"replace",
|
||||
|s: &mut String, find: char, sub: String| {
|
||||
let new_str = s.replace(&find.to_string(), &sub);
|
||||
s.clear();
|
||||
s.push_str(&new_str);
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
lib.set_fn_3_mut(
|
||||
"replace",
|
||||
|s: &mut String, find: char, sub: char| {
|
||||
let new_str = s.replace(&find.to_string(), &sub.to_string());
|
||||
s.clear();
|
||||
s.push_str(&new_str);
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
lib.set_fn_1_mut(
|
||||
"trim",
|
||||
|s: &mut String| {
|
||||
let trimmed = s.trim();
|
||||
@ -235,7 +254,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|
||||
if trimmed.len() < s.len() {
|
||||
*s = trimmed.to_string();
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
map,
|
||||
);
|
||||
});
|
||||
|
@ -1,9 +1,8 @@
|
||||
use super::logic::{eq, gt, gte, lt, lte, ne};
|
||||
use super::math_basic::MAX_INT;
|
||||
use super::{reg_binary, reg_none, reg_unary};
|
||||
|
||||
use crate::def_package;
|
||||
use crate::fn_register::{map_dynamic as map, map_result as result};
|
||||
use crate::module::FuncReturn;
|
||||
use crate::parser::INT;
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::token::Position;
|
||||
@ -14,10 +13,9 @@ use crate::stdlib::time::Instant;
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
||||
// Register date/time functions
|
||||
reg_none(lib, "timestamp", || Instant::now(), map);
|
||||
lib.set_fn_0("timestamp", || Ok(Instant::now()));
|
||||
|
||||
reg_binary(
|
||||
lib,
|
||||
lib.set_fn_2(
|
||||
"-",
|
||||
|ts1: Instant, ts2: Instant| {
|
||||
if ts2 > ts1 {
|
||||
@ -63,18 +61,16 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
||||
}
|
||||
}
|
||||
},
|
||||
result,
|
||||
);
|
||||
|
||||
reg_binary(lib, "<", lt::<Instant>, map);
|
||||
reg_binary(lib, "<=", lte::<Instant>, map);
|
||||
reg_binary(lib, ">", gt::<Instant>, map);
|
||||
reg_binary(lib, ">=", gte::<Instant>, map);
|
||||
reg_binary(lib, "==", eq::<Instant>, map);
|
||||
reg_binary(lib, "!=", ne::<Instant>, map);
|
||||
lib.set_fn_2("<", lt::<Instant>);
|
||||
lib.set_fn_2("<=", lte::<Instant>);
|
||||
lib.set_fn_2(">", gt::<Instant>);
|
||||
lib.set_fn_2(">=", gte::<Instant>);
|
||||
lib.set_fn_2("==", eq::<Instant>);
|
||||
lib.set_fn_2("!=", ne::<Instant>);
|
||||
|
||||
reg_unary(
|
||||
lib,
|
||||
lib.set_fn_1(
|
||||
"elapsed",
|
||||
|timestamp: Instant| {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
@ -96,6 +92,5 @@ def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
|
||||
return Ok(seconds as INT);
|
||||
}
|
||||
},
|
||||
result,
|
||||
);
|
||||
});
|
||||
|
@ -1,451 +0,0 @@
|
||||
use super::PackageStore;
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::calc_fn_hash;
|
||||
use crate::engine::FnCallArgs;
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::token::Position;
|
||||
|
||||
use crate::stdlib::{
|
||||
any::TypeId,
|
||||
boxed::Box,
|
||||
iter::empty,
|
||||
mem,
|
||||
string::{String, ToString},
|
||||
};
|
||||
|
||||
/// This macro makes it easy to define a _package_ and register functions into it.
|
||||
///
|
||||
/// Functions can be added to the package using a number of helper functions under the `packages` module,
|
||||
/// such as `reg_unary`, `reg_binary_mut`, `reg_trinary_mut` etc.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Dynamic;
|
||||
/// use rhai::def_package;
|
||||
/// use rhai::packages::reg_binary;
|
||||
///
|
||||
/// fn add(x: i64, y: i64) -> i64 { x + y }
|
||||
///
|
||||
/// def_package!(rhai:MyPackage:"My super-duper package", lib,
|
||||
/// {
|
||||
/// reg_binary(lib, "my_add", add, |v, _| Ok(v.into()));
|
||||
/// // ^^^^^^^^^^^^^^^^^^^
|
||||
/// // map into Result<Dynamic, Box<EvalAltResult>>
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// The above defines a package named 'MyPackage' with a single function named 'my_add'.
|
||||
#[macro_export]
|
||||
macro_rules! def_package {
|
||||
($root:ident : $package:ident : $comment:expr , $lib:ident , $block:stmt) => {
|
||||
#[doc=$comment]
|
||||
pub struct $package($root::packages::PackageLibrary);
|
||||
|
||||
impl $root::packages::Package for $package {
|
||||
fn get(&self) -> $root::packages::PackageLibrary {
|
||||
self.0.clone()
|
||||
}
|
||||
|
||||
fn init($lib: &mut $root::packages::PackageStore) {
|
||||
$block
|
||||
}
|
||||
}
|
||||
|
||||
impl $package {
|
||||
pub fn new() -> Self {
|
||||
let mut pkg = $root::packages::PackageStore::new();
|
||||
<Self as $root::packages::Package>::init(&mut pkg);
|
||||
Self(pkg.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Check whether the correct number of arguments is passed to the function.
|
||||
fn check_num_args(
|
||||
name: &str,
|
||||
num_args: usize,
|
||||
args: &mut FnCallArgs,
|
||||
pos: Position,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
if args.len() != num_args {
|
||||
Err(Box::new(EvalAltResult::ErrorFunctionArgsMismatch(
|
||||
name.to_string(),
|
||||
num_args,
|
||||
args.len(),
|
||||
pos,
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a function with no parameters to the package.
|
||||
///
|
||||
/// `map_result` is a function that maps the return type of the function to `Result<Dynamic, EvalAltResult>`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Dynamic;
|
||||
/// use rhai::def_package;
|
||||
/// use rhai::packages::reg_none;
|
||||
///
|
||||
/// fn get_answer() -> i64 { 42 }
|
||||
///
|
||||
/// def_package!(rhai:MyPackage:"My super-duper package", lib,
|
||||
/// {
|
||||
/// reg_none(lib, "my_answer", get_answer, |v, _| Ok(v.into()));
|
||||
/// // ^^^^^^^^^^^^^^^^^^^
|
||||
/// // map into Result<Dynamic, Box<EvalAltResult>>
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// The above defines a package named 'MyPackage' with a single function named 'my_add_1'.
|
||||
pub fn reg_none<R>(
|
||||
lib: &mut PackageStore,
|
||||
fn_name: &'static str,
|
||||
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn() -> R + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn() -> R + Send + Sync + 'static,
|
||||
|
||||
#[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ 'static,
|
||||
#[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) {
|
||||
let hash = calc_fn_hash(empty(), fn_name, ([] as [TypeId; 0]).iter().cloned());
|
||||
|
||||
let f = Box::new(move |args: &mut FnCallArgs, pos: Position| {
|
||||
check_num_args(fn_name, 0, args, pos)?;
|
||||
|
||||
let r = func();
|
||||
map_result(r, pos)
|
||||
});
|
||||
|
||||
lib.functions.insert(hash, f);
|
||||
}
|
||||
|
||||
/// Add a function with one parameter to the package.
|
||||
///
|
||||
/// `map_result` is a function that maps the return type of the function to `Result<Dynamic, EvalAltResult>`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Dynamic;
|
||||
/// use rhai::def_package;
|
||||
/// use rhai::packages::reg_unary;
|
||||
///
|
||||
/// fn add_1(x: i64) -> i64 { x + 1 }
|
||||
///
|
||||
/// def_package!(rhai:MyPackage:"My super-duper package", lib,
|
||||
/// {
|
||||
/// reg_unary(lib, "my_add_1", add_1, |v, _| Ok(v.into()));
|
||||
/// // ^^^^^^^^^^^^^^^^^^^
|
||||
/// // map into Result<Dynamic, Box<EvalAltResult>>
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// The above defines a package named 'MyPackage' with a single function named 'my_add_1'.
|
||||
pub fn reg_unary<T: Variant + Clone, R>(
|
||||
lib: &mut PackageStore,
|
||||
fn_name: &'static str,
|
||||
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(T) -> R + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(T) -> R + Send + Sync + 'static,
|
||||
|
||||
#[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ 'static,
|
||||
#[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) {
|
||||
//println!("register {}({})", fn_name, crate::std::any::type_name::<T>());
|
||||
|
||||
let hash = calc_fn_hash(empty(), fn_name, [TypeId::of::<T>()].iter().cloned());
|
||||
|
||||
let f = Box::new(move |args: &mut FnCallArgs, pos: Position| {
|
||||
check_num_args(fn_name, 1, args, pos)?;
|
||||
|
||||
let mut drain = args.iter_mut();
|
||||
let x = mem::take(*drain.next().unwrap()).cast::<T>();
|
||||
|
||||
let r = func(x);
|
||||
map_result(r, pos)
|
||||
});
|
||||
|
||||
lib.functions.insert(hash, f);
|
||||
}
|
||||
|
||||
/// Add a function with one mutable reference parameter to the package.
|
||||
///
|
||||
/// `map_result` is a function that maps the return type of the function to `Result<Dynamic, EvalAltResult>`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::{Dynamic, EvalAltResult};
|
||||
/// use rhai::def_package;
|
||||
/// use rhai::packages::reg_unary_mut;
|
||||
///
|
||||
/// fn inc(x: &mut i64) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
/// if *x == 0 {
|
||||
/// return Err("boo! zero cannot be incremented!".into())
|
||||
/// }
|
||||
/// *x += 1;
|
||||
/// Ok(().into())
|
||||
/// }
|
||||
///
|
||||
/// def_package!(rhai:MyPackage:"My super-duper package", lib,
|
||||
/// {
|
||||
/// reg_unary_mut(lib, "try_inc", inc, |r, _| r);
|
||||
/// // ^^^^^^^^
|
||||
/// // map into Result<Dynamic, Box<EvalAltResult>>
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// The above defines a package named 'MyPackage' with a single fallible function named 'try_inc'
|
||||
/// which takes a first argument of `&mut`, return a `Result<Dynamic, Box<EvalAltResult>>`.
|
||||
pub fn reg_unary_mut<T: Variant + Clone, R>(
|
||||
lib: &mut PackageStore,
|
||||
fn_name: &'static str,
|
||||
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(&mut T) -> R + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(&mut T) -> R + Send + Sync + 'static,
|
||||
|
||||
#[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ 'static,
|
||||
#[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) {
|
||||
//println!("register {}(&mut {})", fn_name, crate::std::any::type_name::<T>());
|
||||
|
||||
let hash = calc_fn_hash(empty(), fn_name, [TypeId::of::<T>()].iter().cloned());
|
||||
|
||||
let f = Box::new(move |args: &mut FnCallArgs, pos: Position| {
|
||||
check_num_args(fn_name, 1, args, pos)?;
|
||||
|
||||
let mut drain = args.iter_mut();
|
||||
let x: &mut T = drain.next().unwrap().downcast_mut().unwrap();
|
||||
|
||||
let r = func(x);
|
||||
map_result(r, pos)
|
||||
});
|
||||
|
||||
lib.functions.insert(hash, f);
|
||||
}
|
||||
|
||||
/// Add a function with two parameters to the package.
|
||||
///
|
||||
/// `map_result` is a function that maps the return type of the function to `Result<Dynamic, EvalAltResult>`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Dynamic;
|
||||
/// use rhai::def_package;
|
||||
/// use rhai::packages::reg_binary;
|
||||
///
|
||||
/// fn add(x: i64, y: i64) -> i64 { x + y }
|
||||
///
|
||||
/// def_package!(rhai:MyPackage:"My super-duper package", lib,
|
||||
/// {
|
||||
/// reg_binary(lib, "my_add", add, |v, _| Ok(v.into()));
|
||||
/// // ^^^^^^^^^^^^^^^^^^^
|
||||
/// // map into Result<Dynamic, Box<EvalAltResult>>
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// The above defines a package named 'MyPackage' with a single function named 'my_add'.
|
||||
pub fn reg_binary<A: Variant + Clone, B: Variant + Clone, R>(
|
||||
lib: &mut PackageStore,
|
||||
fn_name: &'static str,
|
||||
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> R + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(A, B) -> R + Send + Sync + 'static,
|
||||
|
||||
#[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ 'static,
|
||||
#[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) {
|
||||
//println!("register {}({}, {})", fn_name, crate::std::any::type_name::<A>(), crate::std::any::type_name::<B>());
|
||||
|
||||
let hash = calc_fn_hash(
|
||||
empty(),
|
||||
fn_name,
|
||||
[TypeId::of::<A>(), TypeId::of::<B>()].iter().cloned(),
|
||||
);
|
||||
|
||||
let f = Box::new(move |args: &mut FnCallArgs, pos: Position| {
|
||||
check_num_args(fn_name, 2, args, pos)?;
|
||||
|
||||
let mut drain = args.iter_mut();
|
||||
let x = mem::take(*drain.next().unwrap()).cast::<A>();
|
||||
let y = mem::take(*drain.next().unwrap()).cast::<B>();
|
||||
|
||||
let r = func(x, y);
|
||||
map_result(r, pos)
|
||||
});
|
||||
|
||||
lib.functions.insert(hash, f);
|
||||
}
|
||||
|
||||
/// Add a function with two parameters (the first one being a mutable reference) to the package.
|
||||
///
|
||||
/// `map_result` is a function that maps the return type of the function to `Result<Dynamic, EvalAltResult>`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::{Dynamic, EvalAltResult};
|
||||
/// use rhai::def_package;
|
||||
/// use rhai::packages::reg_binary_mut;
|
||||
///
|
||||
/// fn add(x: &mut i64, y: i64) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
/// if y == 0 {
|
||||
/// return Err("boo! cannot add zero!".into())
|
||||
/// }
|
||||
/// *x += y;
|
||||
/// Ok(().into())
|
||||
/// }
|
||||
///
|
||||
/// def_package!(rhai:MyPackage:"My super-duper package", lib,
|
||||
/// {
|
||||
/// reg_binary_mut(lib, "try_add", add, |r, _| r);
|
||||
/// // ^^^^^^^^
|
||||
/// // map into Result<Dynamic, Box<EvalAltResult>>
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// The above defines a package named 'MyPackage' with a single fallible function named 'try_add'
|
||||
/// which takes a first argument of `&mut`, return a `Result<Dynamic, Box<EvalAltResult>>`.
|
||||
pub fn reg_binary_mut<A: Variant + Clone, B: Variant + Clone, R>(
|
||||
lib: &mut PackageStore,
|
||||
fn_name: &'static str,
|
||||
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> R + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> R + Send + Sync + 'static,
|
||||
|
||||
#[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ 'static,
|
||||
#[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) {
|
||||
//println!("register {}(&mut {}, {})", fn_name, crate::std::any::type_name::<A>(), crate::std::any::type_name::<B>());
|
||||
|
||||
let hash = calc_fn_hash(
|
||||
empty(),
|
||||
fn_name,
|
||||
[TypeId::of::<A>(), TypeId::of::<B>()].iter().cloned(),
|
||||
);
|
||||
|
||||
let f = Box::new(move |args: &mut FnCallArgs, pos: Position| {
|
||||
check_num_args(fn_name, 2, args, pos)?;
|
||||
|
||||
let mut drain = args.iter_mut();
|
||||
let x: &mut A = drain.next().unwrap().downcast_mut().unwrap();
|
||||
let y = mem::take(*drain.next().unwrap()).cast::<B>();
|
||||
|
||||
let r = func(x, y);
|
||||
map_result(r, pos)
|
||||
});
|
||||
|
||||
lib.functions.insert(hash, f);
|
||||
}
|
||||
|
||||
/// Add a function with three parameters to the package.
|
||||
///
|
||||
/// `map_result` is a function that maps the return type of the function to `Result<Dynamic, EvalAltResult>`.
|
||||
pub fn reg_trinary<A: Variant + Clone, B: Variant + Clone, C: Variant + Clone, R>(
|
||||
lib: &mut PackageStore,
|
||||
fn_name: &'static str,
|
||||
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> R + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(A, B, C) -> R + Send + Sync + 'static,
|
||||
|
||||
#[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ 'static,
|
||||
#[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) {
|
||||
//println!("register {}({}, {}, {})", fn_name, crate::std::any::type_name::<A>(), crate::std::any::type_name::<B>(), crate::std::any::type_name::<C>());
|
||||
|
||||
let hash = calc_fn_hash(
|
||||
empty(),
|
||||
fn_name,
|
||||
[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]
|
||||
.iter()
|
||||
.cloned(),
|
||||
);
|
||||
|
||||
let f = Box::new(move |args: &mut FnCallArgs, pos: Position| {
|
||||
check_num_args(fn_name, 3, args, pos)?;
|
||||
|
||||
let mut drain = args.iter_mut();
|
||||
let x = mem::take(*drain.next().unwrap()).cast::<A>();
|
||||
let y = mem::take(*drain.next().unwrap()).cast::<B>();
|
||||
let z = mem::take(*drain.next().unwrap()).cast::<C>();
|
||||
|
||||
let r = func(x, y, z);
|
||||
map_result(r, pos)
|
||||
});
|
||||
|
||||
lib.functions.insert(hash, f);
|
||||
}
|
||||
|
||||
/// Add a function with three parameters (the first one is a mutable reference) to the package.
|
||||
///
|
||||
/// `map_result` is a function that maps the return type of the function to `Result<Dynamic, EvalAltResult>`.
|
||||
pub fn reg_trinary_mut<A: Variant + Clone, B: Variant + Clone, C: Variant + Clone, R>(
|
||||
lib: &mut PackageStore,
|
||||
fn_name: &'static str,
|
||||
|
||||
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> R + 'static,
|
||||
#[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> R + Send + Sync + 'static,
|
||||
|
||||
#[cfg(not(feature = "sync"))] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ 'static,
|
||||
#[cfg(feature = "sync")] map_result: impl Fn(R, Position) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) {
|
||||
//println!("register {}(&mut {}, {}, {})", fn_name, crate::std::any::type_name::<A>(), crate::std::any::type_name::<B>(), crate::std::any::type_name::<C>());
|
||||
|
||||
let hash = calc_fn_hash(
|
||||
empty(),
|
||||
fn_name,
|
||||
[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]
|
||||
.iter()
|
||||
.cloned(),
|
||||
);
|
||||
|
||||
let f = Box::new(move |args: &mut FnCallArgs, pos: Position| {
|
||||
check_num_args(fn_name, 3, args, pos)?;
|
||||
|
||||
let mut drain = args.iter_mut();
|
||||
let x: &mut A = drain.next().unwrap().downcast_mut().unwrap();
|
||||
let y = mem::take(*drain.next().unwrap()).cast::<B>();
|
||||
let z = mem::take(*drain.next().unwrap()).cast::<C>();
|
||||
|
||||
let r = func(x, y, z);
|
||||
map_result(r, pos)
|
||||
});
|
||||
|
||||
lib.functions.insert(hash, f);
|
||||
}
|
672
src/parser.rs
672
src/parser.rs
File diff suppressed because it is too large
Load Diff
@ -33,10 +33,9 @@ pub enum EvalAltResult {
|
||||
|
||||
/// Call to an unknown function. Wrapped value is the name of the function.
|
||||
ErrorFunctionNotFound(String, Position),
|
||||
/// Function call has incorrect number of arguments.
|
||||
/// Wrapped values are the name of the function, the number of parameters required
|
||||
/// and the actual number of arguments passed.
|
||||
ErrorFunctionArgsMismatch(String, usize, usize, Position),
|
||||
/// An error has occurred inside a called function.
|
||||
/// Wrapped values re the name of the function and the interior error.
|
||||
ErrorInFunctionCall(String, Box<EvalAltResult>, Position),
|
||||
/// Non-boolean operand encountered for boolean operator. Wrapped value is the operator.
|
||||
ErrorBooleanArgMismatch(String, Position),
|
||||
/// Non-character value encountered where a character is required.
|
||||
@ -76,8 +75,14 @@ pub enum EvalAltResult {
|
||||
ErrorDotExpr(String, Position),
|
||||
/// Arithmetic error encountered. Wrapped value is the error message.
|
||||
ErrorArithmetic(String, Position),
|
||||
/// Number of operations over maximum limit.
|
||||
ErrorTooManyOperations(Position),
|
||||
/// Modules over maximum limit.
|
||||
ErrorTooManyModules(Position),
|
||||
/// Call stack over maximum limit.
|
||||
ErrorStackOverflow(Position),
|
||||
/// The script is prematurely terminated.
|
||||
ErrorTerminated(Position),
|
||||
/// Run-time error encountered. Wrapped value is the error message.
|
||||
ErrorRuntime(String, Position),
|
||||
|
||||
@ -97,10 +102,8 @@ impl EvalAltResult {
|
||||
Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file",
|
||||
|
||||
Self::ErrorParsing(p) => p.desc(),
|
||||
Self::ErrorInFunctionCall(_, _, _) => "Error in called function",
|
||||
Self::ErrorFunctionNotFound(_, _) => "Function not found",
|
||||
Self::ErrorFunctionArgsMismatch(_, _, _, _) => {
|
||||
"Function call with wrong number of arguments"
|
||||
}
|
||||
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
|
||||
Self::ErrorCharMismatch(_) => "Character expected",
|
||||
Self::ErrorNumericIndexExpr(_) => {
|
||||
@ -133,7 +136,10 @@ impl EvalAltResult {
|
||||
Self::ErrorInExpr(_) => "Malformed 'in' expression",
|
||||
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
||||
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
||||
Self::ErrorTooManyOperations(_) => "Too many operations",
|
||||
Self::ErrorTooManyModules(_) => "Too many modules imported",
|
||||
Self::ErrorStackOverflow(_) => "Stack overflow",
|
||||
Self::ErrorTerminated(_) => "Script terminated.",
|
||||
Self::ErrorRuntime(_, _) => "Runtime error",
|
||||
Self::ErrorLoopBreak(true, _) => "Break statement not inside a loop",
|
||||
Self::ErrorLoopBreak(false, _) => "Continue statement not inside a loop",
|
||||
@ -160,6 +166,10 @@ impl fmt::Display for EvalAltResult {
|
||||
|
||||
Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p),
|
||||
|
||||
Self::ErrorInFunctionCall(s, err, pos) => {
|
||||
write!(f, "Error in call to function '{}' ({}): {}", s, pos, err)
|
||||
}
|
||||
|
||||
Self::ErrorFunctionNotFound(s, pos)
|
||||
| Self::ErrorVariableNotFound(s, pos)
|
||||
| Self::ErrorModuleNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
|
||||
@ -175,7 +185,10 @@ impl fmt::Display for EvalAltResult {
|
||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||
| Self::ErrorInExpr(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) => {
|
||||
write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos)
|
||||
@ -188,21 +201,6 @@ impl fmt::Display for EvalAltResult {
|
||||
Self::ErrorLoopBreak(_, pos) => write!(f, "{} ({})", desc, pos),
|
||||
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
|
||||
|
||||
Self::ErrorFunctionArgsMismatch(fn_name, 0, n, pos) => write!(
|
||||
f,
|
||||
"Function '{}' expects no argument but {} found ({})",
|
||||
fn_name, n, pos
|
||||
),
|
||||
Self::ErrorFunctionArgsMismatch(fn_name, 1, n, pos) => write!(
|
||||
f,
|
||||
"Function '{}' expects one argument but {} found ({})",
|
||||
fn_name, n, pos
|
||||
),
|
||||
Self::ErrorFunctionArgsMismatch(fn_name, need, n, pos) => write!(
|
||||
f,
|
||||
"Function '{}' expects {} argument(s) but {} found ({})",
|
||||
fn_name, need, n, pos
|
||||
),
|
||||
Self::ErrorBooleanArgMismatch(op, pos) => {
|
||||
write!(f, "{} operator expects boolean operands ({})", op, pos)
|
||||
}
|
||||
@ -262,6 +260,7 @@ impl<T: AsRef<str>> From<T> for Box<EvalAltResult> {
|
||||
}
|
||||
|
||||
impl EvalAltResult {
|
||||
/// Get the `Position` of this error.
|
||||
pub fn position(&self) -> Position {
|
||||
match self {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
@ -270,7 +269,7 @@ impl EvalAltResult {
|
||||
Self::ErrorParsing(err) => err.position(),
|
||||
|
||||
Self::ErrorFunctionNotFound(_, pos)
|
||||
| Self::ErrorFunctionArgsMismatch(_, _, _, pos)
|
||||
| Self::ErrorInFunctionCall(_, _, pos)
|
||||
| Self::ErrorBooleanArgMismatch(_, pos)
|
||||
| Self::ErrorCharMismatch(pos)
|
||||
| Self::ErrorArrayBounds(_, _, pos)
|
||||
@ -289,24 +288,26 @@ impl EvalAltResult {
|
||||
| Self::ErrorInExpr(pos)
|
||||
| Self::ErrorDotExpr(_, pos)
|
||||
| Self::ErrorArithmetic(_, pos)
|
||||
| Self::ErrorTooManyOperations(pos)
|
||||
| Self::ErrorTooManyModules(pos)
|
||||
| Self::ErrorStackOverflow(pos)
|
||||
| Self::ErrorTerminated(pos)
|
||||
| Self::ErrorRuntime(_, pos)
|
||||
| Self::ErrorLoopBreak(_, pos)
|
||||
| Self::Return(_, pos) => *pos,
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the current `EvalAltResult` and return a new one
|
||||
/// with the specified `Position`.
|
||||
pub(crate) fn set_position(mut err: Box<Self>, new_position: Position) -> Box<Self> {
|
||||
match err.as_mut() {
|
||||
/// Override the `Position` of this error.
|
||||
pub fn set_position(&mut self, new_position: Position) {
|
||||
match self {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position,
|
||||
|
||||
Self::ErrorParsing(err) => err.1 = new_position,
|
||||
|
||||
Self::ErrorFunctionNotFound(_, pos)
|
||||
| Self::ErrorFunctionArgsMismatch(_, _, _, pos)
|
||||
| Self::ErrorInFunctionCall(_, _, pos)
|
||||
| Self::ErrorBooleanArgMismatch(_, pos)
|
||||
| Self::ErrorCharMismatch(pos)
|
||||
| Self::ErrorArrayBounds(_, _, pos)
|
||||
@ -325,12 +326,20 @@ impl EvalAltResult {
|
||||
| Self::ErrorInExpr(pos)
|
||||
| Self::ErrorDotExpr(_, pos)
|
||||
| Self::ErrorArithmetic(_, pos)
|
||||
| Self::ErrorTooManyOperations(pos)
|
||||
| Self::ErrorTooManyModules(pos)
|
||||
| Self::ErrorStackOverflow(pos)
|
||||
| Self::ErrorTerminated(pos)
|
||||
| Self::ErrorRuntime(_, pos)
|
||||
| Self::ErrorLoopBreak(_, pos)
|
||||
| Self::Return(_, pos) => *pos = new_position,
|
||||
}
|
||||
}
|
||||
|
||||
err
|
||||
/// Consume the current `EvalAltResult` and return a new one
|
||||
/// with the specified `Position`.
|
||||
pub(crate) fn new_position(mut self: Box<Self>, new_position: Position) -> Box<Self> {
|
||||
self.set_position(new_position);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ pub enum EntryType {
|
||||
}
|
||||
|
||||
/// An entry in the Scope.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Entry<'a> {
|
||||
/// Name of the entry.
|
||||
pub name: Cow<'a, str>,
|
||||
@ -36,7 +36,7 @@ pub struct Entry<'a> {
|
||||
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.
|
||||
///
|
||||
/// # Example
|
||||
@ -64,7 +64,7 @@ pub struct Entry<'a> {
|
||||
/// allowing for automatic _shadowing_.
|
||||
///
|
||||
/// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Scope<'a>(Vec<Entry<'a>>);
|
||||
|
||||
impl<'a> Scope<'a> {
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
use crate::error::LexError;
|
||||
use crate::parser::INT;
|
||||
use crate::utils::StaticVec;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::parser::FLOAT;
|
||||
@ -425,7 +426,7 @@ pub struct TokenIterator<'a> {
|
||||
/// Current position.
|
||||
pos: Position,
|
||||
/// The input character streams.
|
||||
streams: Vec<Peekable<Chars<'a>>>,
|
||||
streams: StaticVec<Peekable<Chars<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a> TokenIterator<'a> {
|
||||
|
63
src/unsafe.rs
Normal file
63
src/unsafe.rs
Normal file
@ -0,0 +1,63 @@
|
||||
//! A module containing all unsafe code.
|
||||
|
||||
use crate::any::Variant;
|
||||
use crate::engine::State;
|
||||
|
||||
use crate::stdlib::{
|
||||
any::{Any, TypeId},
|
||||
borrow::Cow,
|
||||
boxed::Box,
|
||||
mem, ptr,
|
||||
string::ToString,
|
||||
};
|
||||
|
||||
/// Cast a type into another type.
|
||||
pub fn unsafe_try_cast<A: Any, B: Any>(a: A) -> Option<B> {
|
||||
if TypeId::of::<B>() == a.type_id() {
|
||||
// SAFETY: Just checked we have the right type. We explicitly forget the
|
||||
// value immediately after moving out, removing any chance of a destructor
|
||||
// running or value otherwise being used again.
|
||||
unsafe {
|
||||
let ret: B = ptr::read(&a as *const _ as *const B);
|
||||
mem::forget(a);
|
||||
Some(ret)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast a Boxed type into another type.
|
||||
pub fn unsafe_cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, Box<X>> {
|
||||
// Only allow casting to the exact same type
|
||||
if TypeId::of::<X>() == TypeId::of::<T>() {
|
||||
// SAFETY: just checked whether we are pointing to the correct type
|
||||
unsafe {
|
||||
let raw: *mut dyn Any = Box::into_raw(item as Box<dyn Any>);
|
||||
Ok(Box::from_raw(raw as *mut T))
|
||||
}
|
||||
} else {
|
||||
// Return the consumed item for chaining.
|
||||
Err(item)
|
||||
}
|
||||
}
|
||||
|
||||
/// A dangerous function that blindly casts a `&str` from one lifetime to a `Cow<str>` of
|
||||
/// another lifetime. This is mainly used to let us push a block-local variable into the
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
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 state.scope_level > 0 {
|
||||
// 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
|
||||
unsafe { mem::transmute::<_, &'s str>(name) }.into()
|
||||
} 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()
|
||||
}
|
||||
}
|
442
src/utils.rs
442
src/utils.rs
@ -1,6 +1,8 @@
|
||||
//! Module containing various utility types and functions.
|
||||
//
|
||||
// TODO - remove unsafe code
|
||||
//!
|
||||
//! # Safety
|
||||
//!
|
||||
//! The `StaticVec` type has some `unsafe` blocks to handle conversions between `MaybeUninit` and regular types.
|
||||
|
||||
use crate::stdlib::{
|
||||
any::TypeId,
|
||||
@ -8,6 +10,8 @@ use crate::stdlib::{
|
||||
hash::{Hash, Hasher},
|
||||
iter::FromIterator,
|
||||
mem,
|
||||
mem::MaybeUninit,
|
||||
ops::{Drop, Index, IndexMut},
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
@ -17,10 +21,6 @@ use crate::stdlib::collections::hash_map::DefaultHasher;
|
||||
#[cfg(feature = "no_std")]
|
||||
use ahash::AHasher;
|
||||
|
||||
pub fn EMPTY_TYPE_ID() -> TypeId {
|
||||
TypeId::of::<()>()
|
||||
}
|
||||
|
||||
/// Calculate a `u64` hash key from a module-qualified function name and parameter types.
|
||||
///
|
||||
/// Module names are passed in via `&str` references from an iterator.
|
||||
@ -32,6 +32,7 @@ pub fn EMPTY_TYPE_ID() -> TypeId {
|
||||
pub fn calc_fn_spec<'a>(
|
||||
modules: impl Iterator<Item = &'a str>,
|
||||
fn_name: &str,
|
||||
num: usize,
|
||||
params: impl Iterator<Item = TypeId>,
|
||||
) -> u64 {
|
||||
#[cfg(feature = "no_std")]
|
||||
@ -42,38 +43,117 @@ pub fn calc_fn_spec<'a>(
|
||||
// We always skip the first module
|
||||
modules.skip(1).for_each(|m| m.hash(&mut s));
|
||||
s.write(fn_name.as_bytes());
|
||||
s.write_usize(num);
|
||||
params.for_each(|t| t.hash(&mut s));
|
||||
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 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
|
||||
///
|
||||
/// This type uses some unsafe code (mainly to zero out unused array slots) for efficiency.
|
||||
#[derive(Clone, Hash)]
|
||||
/// This type uses some unsafe code (mainly for uninitialized/unused array slots) for efficiency.
|
||||
//
|
||||
// TODO - remove unsafe code
|
||||
pub struct StaticVec<T> {
|
||||
/// Total number of values held.
|
||||
len: usize,
|
||||
/// Static storage. 4 slots should be enough for most cases - i.e. four levels of indirection.
|
||||
list: [T; 4],
|
||||
/// Fixed-size storage for fast, no-allocation access.
|
||||
list: [MaybeUninit<T>; MAX_STATIC_VEC],
|
||||
/// Dynamic storage. For spill-overs.
|
||||
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::zeroed().assume_init() },
|
||||
list: unsafe { mem::MaybeUninit::uninit().assume_init() },
|
||||
more: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialEq for StaticVec<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq> Eq for StaticVec<T> {}
|
||||
|
||||
impl<T> FromIterator<T> for StaticVec<T> {
|
||||
fn from_iter<X: IntoIterator<Item = T>>(iter: X) -> Self {
|
||||
let mut vec = StaticVec::new();
|
||||
@ -91,20 +171,116 @@ impl<T> StaticVec<T> {
|
||||
pub fn new() -> Self {
|
||||
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`.
|
||||
pub fn push<X: Into<T>>(&mut self, value: X) {
|
||||
if self.len == self.list.len() {
|
||||
// Move the fixed list to the Vec
|
||||
for x in 0..self.list.len() {
|
||||
let def_val: T = unsafe { mem::MaybeUninit::zeroed().assume_init() };
|
||||
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() {
|
||||
if self.len == MAX_STATIC_VEC {
|
||||
self.move_fixed_into_vec(MAX_STATIC_VEC);
|
||||
self.more.push(value.into());
|
||||
} else if self.is_fixed_storage() {
|
||||
self.set_into_list(self.len, value.into(), false);
|
||||
} 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;
|
||||
}
|
||||
@ -114,22 +290,62 @@ impl<T> StaticVec<T> {
|
||||
///
|
||||
/// Panics if the `StaticVec` is empty.
|
||||
pub fn pop(&mut self) -> T {
|
||||
let result = if self.len <= 0 {
|
||||
panic!("nothing to pop!")
|
||||
} else if self.len <= self.list.len() {
|
||||
let def_val: T = unsafe { mem::MaybeUninit::zeroed().assume_init() };
|
||||
mem::replace(self.list.get_mut(self.len - 1).unwrap(), def_val)
|
||||
if self.is_empty() {
|
||||
panic!("nothing to pop!");
|
||||
}
|
||||
|
||||
let result = if self.is_fixed_storage() {
|
||||
self.extract_from_list(self.len - 1)
|
||||
} else {
|
||||
let r = self.more.pop().unwrap();
|
||||
let value = self.more.pop().unwrap();
|
||||
|
||||
// Move back to the fixed list
|
||||
if self.more.len() == self.list.len() {
|
||||
for x in 0..self.list.len() {
|
||||
self.list[self.list.len() - 1 - x] = self.more.pop().unwrap();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@ -140,18 +356,24 @@ impl<T> StaticVec<T> {
|
||||
pub fn len(&self) -> usize {
|
||||
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.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the index is out of bounds.
|
||||
pub fn get_ref(&self, index: usize) -> &T {
|
||||
/// Panics if `index` is out of bounds.
|
||||
pub fn get(&self, index: usize) -> &T {
|
||||
if index >= self.len {
|
||||
panic!("index OOB in StaticVec");
|
||||
}
|
||||
|
||||
if self.len < self.list.len() {
|
||||
self.list.get(index).unwrap()
|
||||
let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) };
|
||||
|
||||
if self.is_fixed_storage() {
|
||||
list.get(index).unwrap()
|
||||
} else {
|
||||
self.more.get(index).unwrap()
|
||||
}
|
||||
@ -160,52 +382,106 @@ impl<T> StaticVec<T> {
|
||||
///
|
||||
/// # 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 {
|
||||
if index >= self.len {
|
||||
panic!("index OOB in StaticVec");
|
||||
}
|
||||
|
||||
if self.len < self.list.len() {
|
||||
self.list.get_mut(index).unwrap()
|
||||
let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) };
|
||||
|
||||
if self.is_fixed_storage() {
|
||||
list.get_mut(index).unwrap()
|
||||
} else {
|
||||
self.more.get_mut(index).unwrap()
|
||||
}
|
||||
}
|
||||
/// Get an iterator to entries in the `StaticVec`.
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
if self.len > self.list.len() {
|
||||
self.more.iter()
|
||||
let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) };
|
||||
|
||||
if self.is_fixed_storage() {
|
||||
list[..self.len].iter()
|
||||
} else {
|
||||
self.list[..self.len].iter()
|
||||
self.more.iter()
|
||||
}
|
||||
}
|
||||
/// Get a mutable iterator to entries in the `StaticVec`.
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
|
||||
if self.len > self.list.len() {
|
||||
self.more.iter_mut()
|
||||
let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) };
|
||||
|
||||
if self.is_fixed_storage() {
|
||||
list[..self.len].iter_mut()
|
||||
} else {
|
||||
self.list[..self.len].iter_mut()
|
||||
self.more.iter_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> StaticVec<T> {
|
||||
/// Get the item at a particular index.
|
||||
impl<T: 'static> StaticVec<T> {
|
||||
/// 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 if the index is out of bounds.
|
||||
pub fn get(&self, index: usize) -> T {
|
||||
/// Panics if `index` is out of bounds.
|
||||
pub fn take(&mut self, index: usize) -> T {
|
||||
if index >= self.len {
|
||||
panic!("index OOB in StaticVec");
|
||||
}
|
||||
|
||||
if self.len < self.list.len() {
|
||||
*self.list.get(index).unwrap()
|
||||
mem::take(if self.is_fixed_storage() {
|
||||
unsafe { mem::transmute(self.list.get_mut(index).unwrap()) }
|
||||
} else {
|
||||
*self.more.get(index).unwrap()
|
||||
}
|
||||
self.more.get_mut(index).unwrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,20 +495,68 @@ impl<T: fmt::Debug> fmt::Debug for StaticVec<T> {
|
||||
|
||||
impl<T> AsRef<[T]> for StaticVec<T> {
|
||||
fn as_ref(&self) -> &[T] {
|
||||
if self.len > self.list.len() {
|
||||
&self.more[..]
|
||||
let list: &[T; MAX_STATIC_VEC] = unsafe { mem::transmute(&self.list) };
|
||||
|
||||
if self.is_fixed_storage() {
|
||||
&list[..self.len]
|
||||
} else {
|
||||
&self.list[..self.len]
|
||||
&self.more[..]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<[T]> for StaticVec<T> {
|
||||
fn as_mut(&mut self) -> &mut [T] {
|
||||
if self.len > self.list.len() {
|
||||
&mut self.more[..]
|
||||
let list: &mut [T; MAX_STATIC_VEC] = unsafe { mem::transmute(&mut self.list) };
|
||||
|
||||
if self.is_fixed_storage() {
|
||||
&mut list[..self.len]
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
@ -41,13 +41,13 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
|
||||
",
|
||||
)?;
|
||||
|
||||
let r: i64 = engine.call_fn(&mut scope, &ast, "hello", (42 as INT, 123 as INT))?;
|
||||
let r: INT = engine.call_fn(&mut scope, &ast, "hello", (42 as INT, 123 as INT))?;
|
||||
assert_eq!(r, 165);
|
||||
|
||||
let r: i64 = engine.call_fn(&mut scope, &ast, "hello", (123 as INT,))?;
|
||||
let r: INT = engine.call_fn(&mut scope, &ast, "hello", (123 as INT,))?;
|
||||
assert_eq!(r, 5166);
|
||||
|
||||
let r: i64 = engine.call_fn(&mut scope, &ast, "hello", ())?;
|
||||
let r: INT = engine.call_fn(&mut scope, &ast, "hello", ())?;
|
||||
assert_eq!(r, 42);
|
||||
|
||||
assert_eq!(
|
||||
@ -60,6 +60,27 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_fn_private() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
let ast = engine.compile("fn add(x, n) { x + n }")?;
|
||||
|
||||
let r: INT = engine.call_fn(&mut scope, &ast, "add", (40 as INT, 2 as INT))?;
|
||||
assert_eq!(r, 42);
|
||||
|
||||
let ast = engine.compile("private fn add(x, n) { x + n }")?;
|
||||
|
||||
assert!(matches!(
|
||||
*engine.call_fn::<_, INT>(&mut scope, &ast, "add", (40 as INT, 2 as INT))
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "add"
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
|
||||
let calc_func = Func::<(INT, INT, INT), INT>::create_from_script(
|
||||
@ -70,5 +91,16 @@ fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
assert_eq!(calc_func(42, 123, 9)?, 1485);
|
||||
|
||||
let calc_func = Func::<(INT, INT, INT), INT>::create_from_script(
|
||||
Engine::new(),
|
||||
"private fn calc(x, y, z) { (x + y) * z }",
|
||||
"calc",
|
||||
)?;
|
||||
|
||||
assert!(matches!(
|
||||
*calc_func(42, 123, 9).expect_err("should error"),
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "calc"
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
25
tests/functions.rs
Normal file
25
tests/functions.rs
Normal file
@ -0,0 +1,25 @@
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_functions() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<INT>("fn add(x, n) { x + n } add(40, 2)")?, 42);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>("fn add(x, n) { x + n } let x = 40; x.add(2)")?,
|
||||
42
|
||||
);
|
||||
|
||||
assert_eq!(engine.eval::<INT>("fn mul2(x) { x * 2 } mul2(21)")?, 42);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert_eq!(
|
||||
engine.eval::<INT>("fn mul2(x) { x * 2 } let x = 21; x.mul2()")?,
|
||||
42
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
@ -43,20 +43,20 @@ fn test_get_set() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.register_fn("new_ts", TestStruct::new);
|
||||
|
||||
#[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.add(); a.x")?, 42);
|
||||
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.y.add(); a.y")?, 0);
|
||||
|
||||
#[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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_big_get_set() -> Result<(), Box<EvalAltResult>> {
|
||||
fn test_get_set_chain() -> Result<(), Box<EvalAltResult>> {
|
||||
#[derive(Clone)]
|
||||
struct TestChild {
|
||||
x: INT,
|
||||
|
@ -10,8 +10,8 @@ fn test_method_call() -> Result<(), Box<EvalAltResult>> {
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
fn update(&mut self) {
|
||||
self.x += 1000;
|
||||
fn update(&mut self, n: INT) {
|
||||
self.x += n;
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
@ -27,14 +27,23 @@ fn test_method_call() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.register_fn("new_ts", TestStruct::new);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?,
|
||||
engine.eval::<TestStruct>("let x = new_ts(); x.update(1000); x")?,
|
||||
TestStruct { x: 1001 }
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<TestStruct>("let x = new_ts(); update(x); x")?,
|
||||
engine.eval::<TestStruct>("let x = new_ts(); update(x, 1000); x")?,
|
||||
TestStruct { x: 1 }
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_method_call_style() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<INT>("let x = -123; x.abs(); x")?, -123);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -72,17 +72,77 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
import "hello" as h;
|
||||
h::answer
|
||||
import "hello" as h1;
|
||||
import "hello" as h2;
|
||||
h2::answer
|
||||
"#
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
engine.set_max_modules(5);
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<INT>(
|
||||
r#"
|
||||
let sum = 0;
|
||||
|
||||
for x in range(0, 10) {
|
||||
import "hello" as h;
|
||||
sum += h::answer;
|
||||
}
|
||||
|
||||
sum
|
||||
"#
|
||||
)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorTooManyModules(_)
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<INT>(
|
||||
r#"
|
||||
let sum = 0;
|
||||
|
||||
fn foo() {
|
||||
import "hello" as h;
|
||||
sum += h::answer;
|
||||
}
|
||||
|
||||
for x in range(0, 10) {
|
||||
foo();
|
||||
}
|
||||
|
||||
sum
|
||||
"#
|
||||
)
|
||||
.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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
@ -100,7 +160,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
|
||||
x + 1
|
||||
}
|
||||
fn add_len(x, y) {
|
||||
x + y.len()
|
||||
x + len(y)
|
||||
}
|
||||
private fn hidden() {
|
||||
throw "you shouldn't see me!";
|
||||
|
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(())
|
||||
}
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user