Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-05-21 09:30:03 +08:00
commit 39cd1c8413
46 changed files with 3465 additions and 2268 deletions

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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