Merge pull request #206 from schungx/master

Closures.
This commit is contained in:
Stephen Chung 2020-08-04 17:24:47 +08:00 committed by GitHub
commit 178393e4bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 3026 additions and 2019 deletions

29
.github/workflows/benchmark.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Benchmark
on:
push:
branches:
- master
jobs:
benchmark:
name: Run Rust benchmark
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: rustup toolchain update nightly && rustup default nightly
- name: Run benchmark
run: cargo +nightly bench | tee output.txt
- name: Store benchmark result
uses: rhysd/github-action-benchmark@v1
with:
name: Rust Benchmark
tool: 'cargo'
output-file-path: output.txt
# Use personal access token instead of GITHUB_TOKEN due to https://github.community/t5/GitHub-Actions/Github-action-not-triggering-gh-pages-upon-push/td-p/26869/highlight/false
github-token: ${{ secrets.RHAI }}
auto-push: true
# Show alert with commit comment on detecting possible performance regression
alert-threshold: '200%'
comment-on-alert: true
fail-on-alert: true
alert-comment-cc-users: '@schungx'

View File

@ -4,6 +4,7 @@ on:
push:
branches:
- master
- closures
pull_request: {}
jobs:
@ -29,6 +30,7 @@ jobs:
- "--features no_object"
- "--features no_function"
- "--features no_module"
- "--features no_closure"
- "--features unicode-xid-ident"
toolchain: [stable]
experimental: [false]

View File

@ -14,15 +14,16 @@ include = [
"Cargo.toml"
]
keywords = [ "scripting" ]
categories = [ "no-std", "embedded", "parser-implementations" ]
categories = [ "no-std", "embedded", "wasm", "parser-implementations" ]
[dependencies]
num-traits = { version = "0.2.11", default-features = false }
smallvec = { version = "1.4.1", default-features = false }
[features]
#default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"]
default = []
plugins = []
plugins = [] # custom plugins support
unchecked = [] # unchecked arithmetic
sync = [] # restrict to only types that implement Send + Sync
no_optimize = [] # no script optimizer
@ -32,12 +33,13 @@ only_i64 = [] # set INT=i64 (default) and disable support for all other in
no_index = [] # no arrays and indexing
no_object = [] # no custom objects
no_function = [] # no script-defined functions
no_closure = [] # no automatic sharing and capture of anonymous functions to external variables
no_module = [] # no modules
internals = [] # expose internal data structures
unicode-xid-ident = ["unicode-xid"] # allow unicode-xid for identifiers.
unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers.
# compiling for no-std
no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]
no_std = [ "no_closure", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]
[profile.release]
lto = "fat"

View File

@ -27,12 +27,11 @@ Standard features
* Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust.
* Fairly low compile-time overhead.
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
* 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_"`).
* Relatively little `unsafe` code (yes there are some for performance reasons).
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
* [Function overloading](https://schungx.github.io/rhai/language/overload.html).
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html).
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html).
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html) with additional support for [currying](https://schungx.github.io/rhai/language/fn-curry.html).
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
* Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html).
* Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature).

View File

@ -6,25 +6,41 @@ Version 0.18.0
This version adds:
* Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions.
* Binding the `this` pointer in a function pointer `call`.
* Anonymous functions (in Rust closure syntax). Simplifies creation of single-use ad-hoc functions.
* Currying of function pointers.
* Closures - auto-currying of anonymous functions to capture shared variables from the external scope.
* Capturing call scope via `func!(...)` syntax.
New features
------------
* `call` can now be called function-call style for function pointers - this is to handle builds with `no_object`.
* Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc.
* Reserve language keywords, such as `print`, `eval`, `call`, `this` etc.
* `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`.
* Anonymous functions in the syntax of a closure, e.g. `|x, y, z| x + y - z`.
* Anonymous functions are supported in the syntax of a Rust closure, e.g. `|x, y, z| x + y - z`.
* Custom syntax now works even without the `internals` feature.
* Currying of function pointers is supported via the `curry` keyword.
* Currying of function pointers is supported via the new `curry` keyword.
* Automatic currying of anonymous functions to capture shared variables from the external scope.
* Capturing of the calling scope for function call via the `func!(...)` syntax.
* `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`.
* New `unicode-xid-ident` feature to allow unicode-xid for identifiers.
* New `unicode-xid-ident` feature to allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) for identifiers.
* `Scope::iter_raw` returns an iterator with a reference to the underlying `Dynamic` value (which may be shared).
Breaking changes
----------------
* Language keywords are now _reserved_ (even when disabled) and they can no longer be used as variable names.
* Function signature for defining custom syntax is simplified.
* `Engine::register_raw_fn_XXX` API shortcuts are removed.
* `PackagesCollection::get_fn`, `PackagesCollection::contains_fn`, `Module::get_fn` and `Module::contains_fn` now take an additional `public_only` parameter indicating whether only public functions are accepted.
* The iterator returned by `Scope::iter` now contains a clone of the `Dynamic` value (unshared).
* `Engine::load_package` takes any type that is `Into<PackageLibrary>`.
Housekeeping
------------
* Most compilation warnings are eliminated via feature gates.
Version 0.17.0
@ -58,7 +74,7 @@ New features
* `Engine::disable_symbol` to surgically disable keywords and/or operators.
* `Engine::register_custom_operator` to define a custom operator.
* `Engine::register_custom_syntax` to define a custom syntax.
* New low-level API `Engine::register_raw_fn` and `Engine::register_raw_fn_XXX`.
* New low-level API `Engine::register_raw_fn`.
* New low-level API `Module::set_raw_fn` mirroring `Engine::register_raw_fn`.
* `AST::clone_functions_only`, `AST::clone_functions_only_filtered` and `AST::clone_statements_only` to clone only part of an `AST`.
* The boolean `^` (XOR) operator is added.

View File

@ -48,7 +48,7 @@ fn bench_eval_expression_optimized_simple(bench: &mut Bencher) {
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
[array, has, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
@ -65,7 +65,7 @@ fn bench_eval_expression_optimized_full(bench: &mut Bencher) {
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
[array, has, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
@ -82,7 +82,7 @@ fn bench_eval_call_expression(bench: &mut Bencher) {
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
[array, has, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
@ -97,7 +97,7 @@ fn bench_eval_call(bench: &mut Bencher) {
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
[array, has, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;

View File

@ -32,7 +32,7 @@ fn bench_parse_full(bench: &mut Bencher) {
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
[array, has, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
@ -109,7 +109,7 @@ fn bench_parse_optimize_simple(bench: &mut Bencher) {
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
[array, has, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
@ -125,7 +125,7 @@ fn bench_parse_optimize_full(bench: &mut Bencher) {
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len <= #{prop:name}.len &&
[array, has, spaces].len <= #{prop:name}.len &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;

View File

@ -79,6 +79,7 @@ The Rhai Scripting Language
4. [Function Pointers](language/fn-ptr.md)
5. [Anonymous Functions](language/fn-anon.md)
6. [Currying](language/fn-curry.md)
7. [Closures](language/fn-closure.md)
16. [Print and Debug](language/print-debug.md)
17. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
@ -99,21 +100,22 @@ The Rhai Scripting Language
8. [Maximum Call Stack Depth](safety/max-call-stack.md)
9. [Maximum Statement Depth](safety/max-stmt-depth.md)
7. [Advanced Topics](advanced.md)
1. [Object-Oriented Programming (OOP)](language/oop.md)
2. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md)
3. [Script Optimization](engine/optimize/index.md)
1. [Capture Scope for Function Call](language/fn-capture.md)
2. [Object-Oriented Programming (OOP)](language/oop.md)
3. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md)
4. [Script Optimization](engine/optimize/index.md)
1. [Optimization Levels](engine/optimize/optimize-levels.md)
2. [Re-Optimize an AST](engine/optimize/reoptimize.md)
3. [Eager Function Evaluation](engine/optimize/eager.md)
4. [Side-Effect Considerations](engine/optimize/side-effects.md)
5. [Volatility Considerations](engine/optimize/volatility.md)
6. [Subtle Semantic Changes](engine/optimize/semantics.md)
4. [Low-Level API](rust/register-raw.md)
5. [Use as DSL](engine/dsl.md)
5. [Low-Level API](rust/register-raw.md)
6. [Use as DSL](engine/dsl.md)
1. [Disable Keywords and/or Operators](engine/disable.md)
2. [Custom Operators](engine/custom-op.md)
3. [Extending with Custom Syntax](engine/custom-syntax.md)
6. [Eval Statement](language/eval.md)
7. [Eval Statement](language/eval.md)
8. [Appendix](appendix/index.md)
1. [Keywords](appendix/keywords.md)
2. [Operators and Symbols](appendix/operators.md)

View File

@ -14,7 +14,7 @@ Easy
* Easily [call a script-defined function]({{rootUrl}}/engine/call-fn.md) from Rust.
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) to do checked arithmetic operations);
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) to do checked arithmetic operations, and [`smallvec`](https://crates.io/crates/smallvec/));
for [`no-std`] builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`.
Fast
@ -35,23 +35,22 @@ Dynamic
* Organize code base with dynamically-loadable [modules].
* Dynamic dispatch via [function pointers].
* Dynamic dispatch via [function pointers] with additional support for [currying].
* Closures via [automatic currying] with capturing shared variables from the external scope.
* Some support for [object-oriented programming (OOP)][OOP].
* Serialization/deserialization support via [`serde`].
Safe
----
* 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_"`).
* Relatively little `unsafe` code (yes there are some for performance reasons).
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
Rugged
------
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
* Protected against malicious attacks (such as [stack-overflow][maximum call stack depth], [over-sized data][maximum length of strings], and [runaway scripts][maximum number of operations] etc.) that may come from untrusted third-party user-land scripts.
* Track script evaluation [progress] and manually terminate a script run.
@ -61,6 +60,8 @@ Flexible
* Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature).
* Serialization/deserialization support via [`serde`](https://crates.io/crates/serde).
* Support for [minimal builds] by excluding unneeded language [features].
* Supports [most build targets](targets.md) including `no-std` and [WASM].

View File

@ -5,6 +5,8 @@ Advanced Topics
This section covers advanced features such as:
* [Capture the calling scope]({{rootUrl}}/language/fn-capture.md) in a function call.
* Simulated [Object Oriented Programming (OOP)][OOP].
* [`serde`] integration.

View File

@ -3,32 +3,69 @@ Keywords List
{{#include ../links.md}}
| Keyword | Description | Not available under |
| :-------------------: | ---------------------------------------- | :-----------------: |
| `true` | Boolean true literal | |
| `false` | Boolean false literal | |
| `let` | Variable declaration | |
| `const` | Constant declaration | |
| `if` | If statement | |
| `else` | else block of if statement | |
| `while` | While loop | |
| `loop` | Infinite loop | |
| `for` | For loop | |
| `in` | Containment test, part of for loop | |
| `continue` | Continue a loop at the next iteration | |
| `break` | Loop breaking | |
| `return` | Return value | |
| `throw` | Throw exception | |
| `import` | Import module | [`no_module`] |
| `export` | Export variable | [`no_module`] |
| `as` | Alias for variable export | [`no_module`] |
| `private` | Mark function private | [`no_function`] |
| `fn` (lower-case `f`) | Function definition | [`no_function`] |
| `Fn` (capital `F`) | Function to create a [function pointer] | |
| `call` | Call a [function pointer] | |
| `curry` | Curry a [function pointer] | |
| `this` | Reference to base object for method call | [`no_function`] |
| `type_of` | Get type name of value | |
| `print` | Print value | |
| `debug` | Print value in debug format | |
| `eval` | Evaluate script | |
| Keyword | Description | Inactive under | Overloadable |
| :-------------------: | ---------------------------------------- | :-------------: | :----------: |
| `true` | Boolean true literal | | No |
| `false` | Boolean false literal | | No |
| `let` | Variable declaration | | No |
| `const` | Constant declaration | | No |
| `is_shared` | Is a value shared? | | No |
| `if` | If statement | | No |
| `else` | else block of if statement | | No |
| `while` | While loop | | No |
| `loop` | Infinite loop | | No |
| `for` | For loop | | No |
| `in` | Containment test, part of for loop | | No |
| `continue` | Continue a loop at the next iteration | | No |
| `break` | Loop breaking | | No |
| `return` | Return value | | No |
| `throw` | Throw exception | | No |
| `import` | Import module | [`no_module`] | No |
| `export` | Export variable | [`no_module`] | No |
| `as` | Alias for variable export | [`no_module`] | No |
| `private` | Mark function private | [`no_function`] | No |
| `fn` (lower-case `f`) | Function definition | [`no_function`] | No |
| `Fn` (capital `F`) | Function to create a [function pointer] | | Yes |
| `call` | Call a [function pointer] | | No |
| `curry` | Curry a [function pointer] | | No |
| `this` | Reference to base object for method call | [`no_function`] | No |
| `type_of` | Get type name of value | | Yes |
| `print` | Print value | | Yes |
| `debug` | Print value in debug format | | Yes |
| `eval` | Evaluate script | | Yes |
Reserved Keywords
-----------------
| Keyword | Potential usage |
| --------- | --------------------- |
| `var` | Variable declaration |
| `static` | Variable declaration |
| `shared` | Share value |
| `do` | Looping |
| `each` | Looping |
| `then` | Control flow |
| `goto` | Control flow |
| `exit` | Control flow |
| `switch` | Matching |
| `match` | Matching |
| `case` | Matching |
| `public` | Function/field access |
| `new` | Constructor |
| `try` | Trap exception |
| `catch` | Catch exception |
| `use` | Import namespace |
| `with` | Scope |
| `module` | Module |
| `package` | Package |
| `spawn` | Threading |
| `go` | Threading |
| `await` | Async |
| `async` | Async |
| `sync` | Async |
| `yield` | Async |
| `default` | Special value |
| `void` | Special value |
| `null` | Special value |
| `nil` | Special value |

View File

@ -114,10 +114,7 @@ Any custom syntax must include an _implementation_ of it.
The function signature of an implementation is:
```rust
Fn(engine: &Engine, context: &mut EvalContext, scope: &mut Scope, inputs: &[Expression])
-> Result<Dynamic, Box<EvalAltResult>>
```
> `Fn(engine: &Engine, context: &mut EvalContext, scope: &mut Scope, inputs: &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>`
where:

View File

@ -15,6 +15,17 @@ The [`Engine::eval_expression_XXX`][`eval_expression`] API can be used to restri
a script to expressions only.
Unicode Standard Annex #31 Identifiers
-------------------------------------
Variable names and other identifiers do not necessarily need to be ASCII-only.
The [`unicode-xid-ident`] feature, when turned on, causes Rhai to allow variable names and identifiers
that follow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/).
This is sometimes useful in a non-English DSL.
Disable Keywords and/or Operators
--------------------------------

View File

@ -22,7 +22,7 @@ fn print_obj() { print(this.data); }
```
The above can be replaced by using _anonymous functions_ which have the same syntax as Rust's closures
(but they are **NOT** closures, merely syntactic sugar):
(but they are **NOT** real closures, merely syntactic sugar):
```rust
let obj = #{
@ -49,9 +49,12 @@ fn anon_fn_1001(x) { this.data -= x; }
fn anon_fn_1002() { print this.data; }
```
WARNING - NOT Closures
----------------------
WARNING - NOT Real Closures
--------------------------
Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves
**not** closures. In particular, they do not capture their running environment. They are more like
Rust's function pointers.
**not** real closures.
In particular, they capture their execution environment via [automatic currying]
(disabled via [`no_closure`]).

View File

@ -0,0 +1,67 @@
Capture The Calling Scope for Function Call
==========================================
{{#include ../links.md}}
Peeking Out of The Pure Box
---------------------------
Rhai functions are _pure_, meaning that they depend on on their arguments and have no
access to the calling environment.
When a function accesses a variable that is not defined within that function's scope,
it raises an evaluation error.
It is possible, through a special syntax, to capture the calling scope - i.e. the scope
that makes the function call - and access variables defined there.
Capturing can be disabled via the [`no_closure`] feature.
```rust
fn foo(y) { // function accesses 'x' and 'y', but 'x' is not defined
x += y; // 'x' is modified in this function
x
}
let x = 1;
foo(41); // error: variable 'x' not found
// Calling a function with a '!' causes it to capture the calling scope
foo!(41) == 42; // the function can access the value of 'x', but cannot change it
x == 1; // 'x' is still the original value
x.method!(); // <- syntax error: capturing is not allowed in method-call style
// Capturing also works for function pointers
let f = Fn("foo");
call!(f, 41) == 42; // must use function-call style
f.call!(41); // <- syntax error: capturing is not allowed in method-call style
```
No Mutations
------------
Variables in the calling scope are captured as copies.
Changes to them do not reflect back to the calling scope.
Rhai functions remain _pure_ in the sense that they can never mutate their environment.
Caveat Emptor
-------------
Functions relying on the calling scope is often a _Very Bad Idea™_ because it makes code
almost impossible to reason and maintain, as their behaviors are volatile and unpredictable.
They behave more like macros that are expanded inline than actual function calls, thus the
syntax is also similar to Rust's macro invocations.
This usage should be at the last resort. YOU HAVE BEEN WARNED.

View File

@ -0,0 +1,186 @@
Simulating Closures
===================
{{#include ../links.md}}
Capture External Variables via Automatic Currying
------------------------------------------------
Since [anonymous functions] de-sugar to standard function definitions, they retain all the behaviors of
Rhai functions, including being _pure_, having no access to external variables.
The anonymous function syntax, however, automatically _captures_ variables that are not defined within
the current scope, but are defined in the external scope - i.e. the scope where the anonymous function
is created.
Variables that are accessible during the time the [anonymous function] is created can be captured,
as long as they are not shadowed by local variables defined within the function's scope.
The captured variables are automatically converted into **reference-counted shared values**
(`Rc<RefCell<Dynamic>>` in normal builds, `Arc<RwLock<Dynamic>>` in [`sync`] builds).
Therefore, similar to closures in many languages, these captured shared values persist through
reference counting, and may be read or modified even after the variables that hold them
go out of scope and no longer exist.
Use the `is_shared` function to check whether a particular value is a shared value.
Automatic currying can be turned off via the [`no_closure`] feature.
Actual Implementation
---------------------
The actual implementation de-sugars to:
1. Keeping track of what variables are accessed inside the anonymous function,
2. If a variable is not defined within the anonymous function's scope, it is looked up _outside_ the function and in the current execution scope - where the anonymous function is created.
3. The variable is added to the parameters list of the anonymous function, at the front.
4. The variable is then converted into a **reference-counted shared value**.
An [anonymous function] which captures an external variable is the only way to create a reference-counted shared value in Rhai.
5. The shared value is then [curried][currying] into the [function pointer] itself, essentially carrying a reference to that shared value and inserting it into future calls of the function.
This process is called _Automatic Currying_, and is the mechanism through which Rhai simulates normal closures.
Examples
--------
```rust
let x = 1; // a normal variable
let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f'
x.is_shared() == true; // 'x' is now a shared value!
x = 40; // changing 'x'...
f.call(2) == 42; // the value of 'x' is 40 because 'x' is shared
// The above de-sugars into this:
fn anon$1001(x, y) { x + y } // parameter 'x' is inserted
make_shared(x); // convert variable 'x' into a shared value
let f = Fn("anon$1001").curry(x); // shared 'x' is curried
f.call(2) == 42;
```
Beware: Captured Variables are Truly Shared
------------------------------------------
The example below is a typical tutorial sample for many languages to illustrate the traps
that may accompany capturing external scope variables in closures.
It prints `9`, `9`, `9`, ... `9`, `9`, not `0`, `1`, `2`, ... `8`, `9`, because there is
ever only one captured variable, and all ten closures capture the _same_ variable.
```rust
let funcs = [];
for i in range(0, 10) {
funcs.push(|| print(i)); // the for loop variable 'i' is captured
}
funcs.len() == 10; // 10 closures stored in the array
funcs[0].type_of() == "Fn"; // make sure these are closures
for f in funcs {
f.call(); // all the references to 'i' are the same variable!
}
```
Therefore - Be Careful to Prevent Data Races
-------------------------------------------
Rust does not have data races, but that doesn't mean Rhai doesn't.
Avoid performing a method call on a captured shared variable (which essentially takes a
mutable reference to the shared object) while using that same variable as a parameter
in the method call - this is a sure-fire way to generate a data race error.
If a shared value is used as the `this` pointer in a method call to a closure function,
then the same shared value _must not_ be captured inside that function, or a data race
will occur and the script will terminate with an error.
```rust
let x = 20;
let f = |a| this += x + a; // 'x' is captured in this closure
x.is_shared() == true; // now 'x' is shared
x.call(f, 2); // <- error: data race detected on 'x'
```
Data Races in `sync` Builds Can Become Deadlocks
-----------------------------------------------
Under the [`sync`] feature, shared values are guarded with a `RwLock`, meaning that data race
conditions no longer raise an error.
Instead, they wait endlessly for the `RwLock` to be freed, and thus can become deadlocks.
On the other hand, since the same thread (i.e. the [`Engine`] thread) that is holding the lock
is attempting to read it again, this may also [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1)
depending on the O/S.
```rust
let x = 20;
let f = |a| this += x + a; // 'x' is captured in this closure
// Under `sync`, the following may wait forever, or may panic,
// because 'x' is locked as the `this` pointer but also accessed
// via a captured shared value.
x.call(f, 2);
```
TL;DR
-----
### Q: Why are closures implemented as automatic currying?
In concept, a closure _closes_ over captured variables from the outer scope - that's why
they are called _closures_. When this happen, a typical language implementation hoists
those variables that are captured away from the stack frame and into heap-allocated storage.
This is because those variables may be needed after the stack frame goes away.
These heap-allocated captured variables only go away when all the closures that need them
are finished with them. A garbage collector makes this trivial to implement - they are
automatically collected as soon as all closures needing them are destroyed.
In Rust, this can be done by reference counting instead, with the potential pitfall of creating
reference loops that will prevent those variables from being deallocated forever.
Rhai avoids this by clone-copying most data values, so reference loops are hard to create.
Rhai does the hoisting of captured variables into the heap by converting those values
into reference-counted locked values, also allocated on the heap. The process is identical.
Closures are usually implemented as a data structure containing two items:
1) A function pointer to the function body of the closure,
2) A data structure containing references to the captured shared variables on the heap.
Usually a language implementation passes the structure containing references to captured
shared variables into the function pointer, the function body taking this data structure
as an additional parameter.
This is essentially what Rhai does, except that Rhai passes each variable individually
as separate parameters to the function, instead of creating a structure and passing that
structure as a single parameter. This is the only difference.
Therefore, in most languages, essentially all closures are implemented as automatic currying of
shared variables hoisted into the heap, automatically passing those variables as parameters into
the function. Rhai just brings this directly up to the front.

View File

@ -28,3 +28,12 @@ let curried = curry(func, 21); // function-call style also works
curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)'
// only one argument is now required
```
Automatic Currying
------------------
[Anonymous functions] defined via a closure syntax _capture_ external variables
that are not shadowed inside the function's scope.
This is accomplished via [automatic currying].

View File

@ -69,17 +69,22 @@ The only practical way to ensure that a function is a correct one is to use [mod
i.e. define the function in a separate module and then [`import`] it:
```rust
message.rhai:
----------------
| message.rhai |
----------------
fn message() { "Hello!" }
fn message() { "Hello!" }
script.rhai:
fn say_hello() {
import "message" as msg;
print(msg::message());
}
say_hello();
---------------
| script.rhai |
---------------
fn say_hello() {
import "message" as msg;
print(msg::message());
}
say_hello();
```
@ -94,18 +99,23 @@ defined _within the script_. When called later, those functions will be searche
current global namespace and may not be found.
```rust
greeting.rhai:
-----------------
| greeting.rhai |
-----------------
fn message() { "Hello!" };
fn message() { "Hello!" };
fn say_hello() { print(message()); }
fn say_hello() { print(message()); }
say_hello(); // 'message' is looked up in the global namespace
say_hello(); // 'message' is looked up in the global namespace
script.rhai:
import "greeting" as g;
g::say_hello(); // <- error: function not found - 'message'
---------------
| script.rhai |
---------------
import "greeting" as g;
g::say_hello(); // <- error: function not found - 'message'
```
In the example above, although the module `greeting.rhai` loads fine (`"Hello!"` is printed),
@ -118,24 +128,29 @@ as possible and avoid cross-calling them from each other. A [function pointer]
to call another function within a module-defined function:
```rust
greeting.rhai:
-----------------
| greeting.rhai |
-----------------
fn message() { "Hello!" };
fn message() { "Hello!" };
fn say_hello(msg_func) { // 'msg_func' is a function pointer
print(msg_func.call()); // call via the function pointer
}
fn say_hello(msg_func) { // 'msg_func' is a function pointer
print(msg_func.call()); // call via the function pointer
}
say_hello(); // 'message' is looked up in the global namespace
say_hello(); // 'message' is looked up in the global namespace
script.rhai:
import "greeting" as g;
---------------
| script.rhai |
---------------
fn my_msg() {
import "greeting" as g; // <- must import again here...
g::message() // <- ... otherwise will not find module 'g'
}
import "greeting" as g;
g::say_hello(Fn("my_msg")); // prints 'Hello!'
fn my_msg() {
import "greeting" as g; // <- must import again here...
g::message() // <- ... otherwise will not find module 'g'
}
g::say_hello(Fn("my_msg")); // prints 'Hello!'
```

View File

@ -16,7 +16,7 @@ fn sub(x, y,) { // trailing comma in parameters list is OK
add(2, 3) == 5;
sub(2, 3,) == -1; // trailing comma in arguments list is OK
sub(2, 3,) == -1; // trailing comma in arguments list is OK
```
@ -127,5 +127,5 @@ x.change(); // call 'change' in method-call style, 'this' binds to 'x'
x == 42; // 'x' is changed!
change(); // <- error: `this` is unbounded
change(); // <- error: `this` is unbound
```

View File

@ -5,18 +5,21 @@ Keywords
The following are reserved keywords in Rhai:
| Keywords | Usage | Not available under feature |
| ------------------------------------------------- | --------------------- | :-------------------------: |
| `true`, `false` | Boolean constants | |
| `let`, `const` | Variable declarations | |
| `if`, `else` | Control flow | |
| `while`, `loop`, `for`, `in`, `continue`, `break` | Looping | |
| `fn`, `private` | Functions | [`no_function`] |
| `return` | Return values | |
| `throw` | throw exceptions | |
| `import`, `export`, `as` | Modules | [`no_module`] |
| `Fn`, `call` | Function pointers | |
| `type_of`, `print`, `debug`, `eval` | Special functions | |
| Active keywords | Reserved keywords | Usage | Inactive under feature |
| ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: |
| `true`, `false` | | Boolean constants | |
| `let`, `const` | `var`, `static` | Variable declarations | |
| `is_shared` | | Shared values | [`no_closure`] |
| `if`, `else` | `then`, `goto`, `exit` | Control flow | |
| | `switch`, `match`, `case` | Matching | |
| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | |
| `fn`, `private` | `public`, `new` | Functions | [`no_function`] |
| `return` | | Return values | |
| `throw` | `try`, `catch` | Throw exceptions | |
| `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] |
| `Fn`, `call`, `curry` | | Function pointers | |
| | `spawn`, `go`, `sync`, `async`, `await`, `yield` | Threading/async | |
| `type_of`, `print`, `debug`, `eval` | | Special functions | |
| | `default`, `void`, `null`, `nil` | Special values | |
Keywords cannot be the name of a [function] or [variable], unless the relevant feature is enabled.
For example, `fn` is a valid variable name under [`no_function`].
Keywords cannot become the name of a [function] or [variable], even when they are disabled.

View File

@ -37,12 +37,30 @@ array[0].update(); // <- call in method-call style will update 'a'
```
`&mut` is Efficient
------------------
Number of Parameters
--------------------
Native Rust methods registered with an [`Engine`] take _one additional parameter_ more than
an equivalent method coded in script, where the object is accessed via the `this` pointer instead.
The following table illustrates the differences:
| Function type | Parameters | Object reference | Function signature |
| :-----------: | :--------: | :--------------------: | :-----------------------------------------------------: |
| Native Rust | _n_ + 1 | First `&mut` parameter | `fn method<T, U, V>`<br/>`(obj: &mut T, x: U, y: V) {}` |
| Rhai script | _n_ | `this` | `fn method(x, y) {}` |
`&mut` is Efficient (Except for `ImmutableString`)
------------------------------------------------
Using a `&mut` first parameter is highly encouraged when using types that are expensive to clone,
even when the intention is not to mutate that argument, because it avoids cloning that argument value.
For example, the `len` method of an [array] has the signature: `Fn(&mut Array) -> INT`.
The array itself is not modified in any way, but using a `&mut` parameter avoids a cloning that would
otherwise have happened if the signature were `Fn(Array) -> INT`.
For primary types that are cheap to clone (e.g. those that implement `Copy`),
including `ImmutableString`, this is not necessary.

View File

@ -63,7 +63,9 @@ cause a stack overflow in the [`Engine`], unless stopped by setting a limit for
For instance, importing itself always causes an infinite recursion:
```rust
// This file is 'hello.rhai'
--------------
| hello.rhai |
--------------
import "hello" as foo; // import itself - infinite recursion!
@ -73,11 +75,18 @@ foo::do_something();
Modules cross-referencing also cause infinite recursion:
```rust
// This file is 'hello.rhai' - references 'world.rhai'
--------------
| hello.rhai |
--------------
import "world" as foo;
foo::do_something();
// This file is 'world.rhai' - references 'hello.rhai'
--------------
| world.rhai |
--------------
import "hello" as bar;
bar::do_something_else();
```

View File

@ -21,23 +21,42 @@ When a property of an [object map] is called like a method function, and if it h
a valid [function pointer] (perhaps defined via an [anonymous function]), then the call will be
dispatched to the actual function with `this` binding to the [object map] itself.
Use Anonymous Functions to Define Methods
----------------------------------------
[Anonymous functions] defined as values for [object map] properties take on a syntactic shape
that resembles very closely that of class methods in an OOP language.
Anonymous functions can also _capture_ variables from the defining environment, which is a very
common OOP pattern. Capturing is accomplished via a feature called _[automatic currying]_ and
can be turned off via the [`no_closure`] feature.
Examples
--------
```rust
let factor = 1;
// Define the object
let obj =
#{
data: 0,
increment: |x| this.data += x, // when called, 'this' binds to 'obj'
update: |x| this.data = x, // when called, 'this' binds to 'obj'
action: || print(this.data) // when called, 'this' binds to 'obj'
increment: |x| this.data += x, // 'this' binds to 'obj'
update: |x| this.data = x * factor, // 'this' binds to 'obj', 'factor' is captured
action: || print(this.data) // 'this' binds to 'obj'
};
// Use the object
obj.increment(1);
obj.action(); // prints 1
obj.action(); // prints 1
obj.update(42);
obj.action(); // prints 42
obj.action(); // prints 42
factor = 2;
obj.update(42);
obj.action(); // prints 84
```

View File

@ -9,15 +9,15 @@ and _number_ of parameters, but not parameter _types_ since all parameters are t
New definitions _overwrite_ previous definitions of the same name and number of parameters.
```rust
fn foo(x,y,z) { print("Three!!! " + x + "," + y + "," + z) }
fn foo(x,y,z) { print("Three!!! " + x + "," + y + "," + z); }
fn foo(x) { print("One! " + x) }
fn foo(x) { print("One! " + x); }
fn foo(x,y) { print("Two! " + x + "," + y) }
fn foo(x,y) { print("Two! " + x + "," + y); }
fn foo() { print("None.") }
fn foo() { print("None."); }
fn foo(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition
fn foo(x) { print("HA! NEW ONE! " + x); } // overwrites previous definition
foo(1,2,3); // prints "Three!!! 1,2,3"

View File

@ -5,21 +5,22 @@ Values and Types
The following primitive types are supported natively:
| Category | Equivalent Rust types | [`type_of()`] | `to_string()` |
| --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | ----------------------- |
| **Integer number** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. |
| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. |
| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`) | `"string"` | `"hello"` etc. |
| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` |
| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | _not supported_ |
| **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` |
| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ |
| Category | Equivalent Rust types | [`type_of()`] | `to_string()` |
| -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | ----------------------- |
| **Integer number** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. |
| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. |
| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`) | `"string"` | `"hello"` etc. |
| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` |
| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | `"<timestamp>"` |
| **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` |
| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
| **Shared value** (a reference-counted, shared [`Dynamic`] value, created via [automatic currying], disabled with [`no_closure`]) | | _the actual type_ | _actual value_ |
| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ |
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different -
they even cannot be added together. This is very similar to Rust.

View File

@ -21,6 +21,11 @@ Variable names are case _sensitive_.
Variable names also cannot be the same as a [keyword].
### Unicode Standard Annex #31 Identifiers
The [`unicode-xid-ident`] feature expands the allowed characters for variable names to the set defined by
[Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/).
Declare a Variable
------------------

View File

@ -9,6 +9,7 @@
[`no_object`]: {{rootUrl}}/start/features.md
[`no_function`]: {{rootUrl}}/start/features.md
[`no_module`]: {{rootUrl}}/start/features.md
[`no_closure`]: {{rootUrl}}/start/features.md
[`no_std`]: {{rootUrl}}/start/features.md
[`no-std`]: {{rootUrl}}/start/features.md
[`internals`]: {{rootUrl}}/start/features.md
@ -78,6 +79,10 @@
[function pointer]: {{rootUrl}}/language/fn-ptr.md
[function pointers]: {{rootUrl}}/language/fn-ptr.md
[currying]: {{rootUrl}}/language/fn-curry.md
[capture]: {{rootUrl}}/language/fn-capture.md
[automatic currying]: {{rootUrl}}/language/fn-closure.md
[closure]: {{rootUrl}}/language/fn-closure.md
[closures]: {{rootUrl}}/language/fn-closure.md
[function namespace]: {{rootUrl}}/language/fn-namespaces.md
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md
[anonymous function]: {{rootUrl}}/language/fn-anon.md

View File

@ -35,12 +35,12 @@ engine.register_raw_fn(
// Therefore, get a '&mut' reference to the first argument _last_.
// Alternatively, use `args.split_at_mut(1)` etc. to split the slice first.
let y: i64 = *args[1].downcast_ref::<i64>() // get a reference to the second argument
let y: i64 = *args[1].read_lock::<i64>() // get a reference to the second argument
.unwrap(); // then copying it because it is a primary type
let y: i64 = std::mem::take(args[1]).cast::<i64>(); // alternatively, directly 'consume' it
let x: &mut i64 = args[0].downcast_mut::<i64>() // get a '&mut' reference to the
let x: &mut i64 = args[0].write_lock::<i64>() // get a '&mut' reference to the
.unwrap(); // first argument
*x += y; // perform the action
@ -55,27 +55,12 @@ engine.register_fn("increment_by", |x: &mut i64, y: i64| x += y);
```
Shortcuts
---------
Function Signature
------------------
As usual with Rhai, there are shortcuts. For functions of zero to four parameters, which should be
the majority, use one of the `Engine::register_raw_fn_n` (where `n = 0..4`) methods:
The function signature passed to `Engine::register_raw_fn` takes the following form:
```rust
// Specify parameter types as generics
engine.register_raw_fn_2::<i64, i64>(
"increment_by",
|engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { ... }
);
```
Closure Signature
-----------------
The closure passed to `Engine::register_raw_fn` takes the following form:
`Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result<T, Box<EvalAltResult>> + 'static`
> `Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result<T, Box<EvalAltResult>> + 'static`
where:
@ -99,12 +84,12 @@ Extract Arguments
To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following:
| Argument type | Access (`n` = argument position) | Result |
| ------------------------------ | -------------------------------------- | ---------------------------------------------------------- |
| [Primary type][standard types] | `args[n].clone().cast::<T>()` | Copy of value. |
| Custom type | `args[n].downcast_ref::<T>().unwrap()` | Immutable reference to value. |
| Custom type (consumed) | `std::mem::take(args[n]).cast::<T>()` | The _consumed_ value.<br/>The original value becomes `()`. |
| `this` object | `args[0].downcast_mut::<T>().unwrap()` | Mutable reference to value. |
| Argument type | Access (`n` = argument position) | Result |
| ------------------------------ | ------------------------------------- | ---------------------------------------------------------- |
| [Primary type][standard types] | `args[n].clone().cast::<T>()` | Copy of value. |
| Custom type | `args[n].read_lock::<T>().unwrap()` | Immutable reference to value. |
| Custom type (consumed) | `std::mem::take(args[n]).cast::<T>()` | The _consumed_ value.<br/>The original value becomes `()`. |
| `this` object | `args[0].write_lock::<T>().unwrap()` | Mutable reference to value. |
When there is a mutable reference to the `this` object (i.e. the first argument),
there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain.
@ -113,6 +98,12 @@ there can be no other immutable references to `args`, otherwise the Rust borrow
Example - Passing a Function Pointer to a Rust Function
------------------------------------------------------
The low-level API is useful when there is a need to interact with the scripting [`Engine`] within a function.
The following example registers a function that takes a [function pointer] as an argument,
then calls it within the same [`Engine`]. This way, a _callback_ function can be provided
to a native Rust function.
```rust
use rhai::{Engine, Module, Dynamic, FnPtr};
@ -133,11 +124,9 @@ engine.register_raw_fn(
let value = args[2].clone(); // 3rd argument - function argument
let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer
// Use 'call_fn_dynamic' to call the function name.
// Pass 'lib' as the current global library of functions.
engine.call_fn_dynamic(&mut Scope::new(), lib, fp.fn_name(), Some(this_ptr), [value])?;
Ok(())
// Use 'FnPtr::call_dynamic' to call the function pointer.
// Beware, private script-defined functions will not be found.
fp.call_dynamic(engine, lib, Some(this_ptr), [value])
},
);
@ -167,5 +156,5 @@ let this_ptr = first[0].downcast_mut::<A>().unwrap();
// Immutable reference to the second value parameter
// This can be mutable but there is no point because the parameter is passed by value
let value = rest[0].downcast_ref::<B>().unwrap();
let value_ref = rest[0].read_lock::<B>().unwrap();
```

View File

@ -11,22 +11,23 @@ Notice that this deviates from Rust norm where features are _additive_.
Excluding unneeded functionalities can result in smaller, faster builds as well as
more control over what a script can (or cannot) do.
| Feature | Description |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unchecked` | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.<br/>Beware that a bad script may panic the entire system! |
| `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`. |
| `no_optimize` | Disable [script optimization]. |
| `no_float` | Disable floating-point numbers and math. |
| `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_index` | Disable [arrays] and indexing features. |
| `no_object` | Disable support for [custom types] and [object maps]. |
| `no_function` | Disable script-defined [functions]. |
| `no_module` | Disable loading external [modules]. |
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
| `serde` | Enable serialization/deserialization via [`serde`]. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. |
| `unicode-xid-ident` | Allow unicode-xid for identifiers. |
| Feature | Description |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unchecked` | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.<br/>Beware that a bad script may panic the entire system! |
| `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`. |
| `no_optimize` | Disable [script optimization]. |
| `no_float` | Disable floating-point numbers and math. |
| `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_index` | Disable [arrays] and indexing features. |
| `no_object` | Disable support for [custom types] and [object maps]. |
| `no_function` | Disable script-defined [functions]. |
| `no_module` | Disable loading external [modules]. |
| `no_closure` | Disable [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. |
| `no_std` | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. |
| `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. |
| `unicode-xid-ident` | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. |
Example

View File

@ -72,15 +72,17 @@ fn main() {
println!("==============");
print_help();
loop {
'main_loop: loop {
print!("rhai> ");
stdout().flush().expect("couldn't flush stdout");
input.clear();
loop {
if let Err(err) = stdin().read_line(&mut input) {
panic!("input error: {}", err);
match stdin().read_line(&mut input) {
Ok(0) => break 'main_loop,
Ok(_) => (),
Err(err) => panic!("input error: {}", err),
}
let line = input.as_str().trim_end();
@ -112,10 +114,15 @@ fn main() {
}
"exit" | "quit" => break, // quit
"scope" => {
scope
.iter()
.enumerate()
.for_each(|(i, (name, value))| println!("[{}] {} = {:?}", i + 1, name, value));
scope.iter_raw().enumerate().for_each(|(i, (name, value))| {
println!(
"[{}] {}{} = {:?}",
i + 1,
name,
if value.is_shared() { " (shared)" } else { "" },
*value.read_lock::<Dynamic>().unwrap(),
)
});
continue;
}
"astu" => {

View File

@ -4,6 +4,9 @@ use crate::fn_native::{FnPtr, SendSync};
use crate::parser::{ImmutableString, INT};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
#[cfg(not(feature = "no_closure"))]
use crate::fn_native::SharedMut;
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
@ -17,9 +20,21 @@ use crate::stdlib::{
any::{type_name, Any, TypeId},
boxed::Box,
fmt,
ops::{Deref, DerefMut},
string::String,
};
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
use crate::stdlib::{
cell::{Ref, RefCell, RefMut},
rc::Rc,
};
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
use crate::stdlib::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
#[cfg(not(feature = "no_object"))]
use crate::stdlib::collections::HashMap;
@ -144,6 +159,92 @@ pub enum Union {
Map(Box<Map>),
FnPtr(Box<FnPtr>),
Variant(Box<Box<dyn Variant>>),
#[cfg(not(feature = "no_closure"))]
Shared(SharedMut<Dynamic>),
}
/// Underlying `Variant` read guard for `Dynamic`.
///
/// This data structure provides transparent interoperability between
/// normal `Dynamic` and shared Dynamic values.
#[derive(Debug)]
pub struct DynamicReadLock<'d, T: Variant + Clone>(DynamicReadLockInner<'d, T>);
/// Different types of read guards for `DynamicReadLock`.
#[derive(Debug)]
enum DynamicReadLockInner<'d, T: Variant + Clone> {
/// A simple reference to a non-shared value.
Reference(&'d T),
/// A read guard to a shared `RefCell`.
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
Guard(Ref<'d, Dynamic>),
/// A read guard to a shared `RwLock`.
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
Guard(RwLockReadGuard<'d, Dynamic>),
}
impl<'d, T: Variant + Clone> Deref for DynamicReadLock<'d, T> {
type Target = T;
#[inline(always)]
fn deref(&self) -> &Self::Target {
match &self.0 {
DynamicReadLockInner::Reference(reference) => *reference,
// Unwrapping is safe because all checking is already done in its constructor
#[cfg(not(feature = "no_closure"))]
DynamicReadLockInner::Guard(guard) => guard.downcast_ref().unwrap(),
}
}
}
/// Underlying `Variant` write guard for `Dynamic`.
///
/// This data structure provides transparent interoperability between
/// normal `Dynamic` and shared Dynamic values.
#[derive(Debug)]
pub struct DynamicWriteLock<'d, T: Variant + Clone>(DynamicWriteLockInner<'d, T>);
/// Different types of write guards for `DynamicReadLock`.
#[derive(Debug)]
enum DynamicWriteLockInner<'d, T: Variant + Clone> {
/// A simple mutable reference to a non-shared value.
Reference(&'d mut T),
/// A write guard to a shared `RefCell`.
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
Guard(RefMut<'d, Dynamic>),
/// A write guard to a shared `RwLock`.
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
Guard(RwLockWriteGuard<'d, Dynamic>),
}
impl<'d, T: Variant + Clone> Deref for DynamicWriteLock<'d, T> {
type Target = T;
#[inline(always)]
fn deref(&self) -> &Self::Target {
match &self.0 {
DynamicWriteLockInner::Reference(reference) => *reference,
// Unwrapping is safe because all checking is already done in its constructor
#[cfg(not(feature = "no_closure"))]
DynamicWriteLockInner::Guard(guard) => guard.downcast_ref().unwrap(),
}
}
}
impl<'d, T: Variant + Clone> DerefMut for DynamicWriteLock<'d, T> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
match &mut self.0 {
DynamicWriteLockInner::Reference(reference) => *reference,
// Unwrapping is safe because all checking is already done in its constructor
#[cfg(not(feature = "no_closure"))]
DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().unwrap(),
}
}
}
impl Dynamic {
@ -156,16 +257,36 @@ impl Dynamic {
}
}
/// Does this `Dynamic` hold a shared data type
/// instead of one of the supported system primitive types?
pub fn is_shared(&self) -> bool {
match self.0 {
#[cfg(not(feature = "no_closure"))]
Union::Shared(_) => true,
_ => false,
}
}
/// Is the value held by this `Dynamic` a particular type?
///
/// If the `Dynamic` is a Shared variant checking is performed on
/// top of it's internal value.
pub fn is<T: Variant + Clone>(&self) -> bool {
self.type_id() == TypeId::of::<T>()
|| match self.0 {
Union::Str(_) => TypeId::of::<String>() == TypeId::of::<T>(),
_ => false,
}
let mut target_type_id = TypeId::of::<T>();
if target_type_id == TypeId::of::<String>() {
target_type_id = TypeId::of::<ImmutableString>();
}
self.type_id() == target_type_id
}
/// Get the TypeId of the value held by this `Dynamic`.
///
/// # Panics or Deadlocks When Value is Shared
///
/// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1).
/// Otherwise, this call panics if the data is currently borrowed for write.
pub fn type_id(&self) -> TypeId {
match &self.0 {
Union::Unit(_) => TypeId::of::<()>(),
@ -181,10 +302,21 @@ impl Dynamic {
Union::Map(_) => TypeId::of::<Map>(),
Union::FnPtr(_) => TypeId::of::<FnPtr>(),
Union::Variant(value) => (***value).type_id(),
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
Union::Shared(cell) => (*cell.borrow()).type_id(),
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
Union::Shared(cell) => (*cell.read().unwrap()).type_id(),
}
}
/// Get the name of the type of the value held by this `Dynamic`.
///
/// # Panics or Deadlocks When Value is Shared
///
/// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1).
/// Otherwise, this call panics if the data is currently borrowed for write.
pub fn type_name(&self) -> &'static str {
match &self.0 {
Union::Unit(_) => "()",
@ -203,6 +335,15 @@ impl Dynamic {
#[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => "timestamp",
Union::Variant(value) => (***value).type_name(),
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
Union::Shared(cell) => cell
.try_borrow()
.map(|v| (*v).type_name())
.unwrap_or("<shared>"),
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
Union::Shared(cell) => (*cell.read().unwrap()).type_name(),
}
}
}
@ -256,8 +397,20 @@ impl fmt::Display for Dynamic {
Union::FnPtr(value) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
Union::Variant(value) => write!(f, "{}", (*value).type_name()),
Union::Variant(value) if value.is::<Instant>() => f.write_str("<timestamp>"),
Union::Variant(value) => f.write_str((*value).type_name()),
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
Union::Shared(cell) => {
if let Ok(v) = cell.try_borrow() {
fmt::Display::fmt(&*v, f)
} else {
f.write_str("<shared>")
}
}
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
Union::Shared(cell) => fmt::Display::fmt(*cell.read_lock().unwrap(), f),
}
}
}
@ -284,6 +437,18 @@ impl fmt::Debug for Dynamic {
#[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
Union::Variant(value) => write!(f, "{}", (*value).type_name()),
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
Union::Shared(cell) => {
if let Ok(v) = cell.try_borrow() {
write!(f, "{:?} (shared)", *v)
} else {
f.write_str("<shared>")
}
}
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
Union::Shared(cell) => fmt::Display::fmt(*cell.read_lock().unwrap(), f),
}
}
}
@ -304,6 +469,8 @@ impl Clone for Dynamic {
Union::Map(ref value) => Self(Union::Map(value.clone())),
Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())),
Union::Variant(ref value) => (***value).clone_into_dynamic(),
#[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell) => Self(Union::Shared(cell.clone())),
}
}
}
@ -407,6 +574,11 @@ impl Dynamic {
}
}
boxed = match unsafe_cast_box::<_, FnPtr>(boxed) {
Ok(fn_ptr) => return (*fn_ptr).into(),
Err(val) => val,
};
boxed = match unsafe_cast_box::<_, Dynamic>(boxed) {
Ok(d) => return *d,
Err(val) => val,
@ -415,10 +587,46 @@ impl Dynamic {
Self(Union::Variant(Box::new(boxed)))
}
/// Get a copy of the `Dynamic` value as a specific type.
/// Casting to a `Dynamic` just returns as is.
/// Turn the `Dynamic` value into a shared `Dynamic` value backed by an `Rc<RefCell<Dynamic>>`
/// or `Arc<RwLock<Dynamic>>` depending on the `sync` feature.
///
/// Returns an error with the name of the value's actual type when the cast fails.
/// Shared `Dynamic` values are relatively cheap to clone as they simply increment the
/// reference counts.
///
/// Shared `Dynamic` values can be converted seamlessly to and from ordinary `Dynamic` values.
///
/// If the `Dynamic` value is already shared, this method returns itself.
///
/// # Panics
///
/// Panics under the `no_closure` feature.
pub fn into_shared(self) -> Self {
#[cfg(not(feature = "no_closure"))]
return match self.0 {
Union::Shared(..) => self,
#[cfg(not(feature = "sync"))]
_ => Self(Union::Shared(Rc::new(RefCell::new(self)))),
#[cfg(feature = "sync")]
_ => Self(Union::Shared(Arc::new(RwLock::new(self)))),
};
#[cfg(feature = "no_closure")]
unimplemented!()
}
/// Convert the `Dynamic` value into specific type.
///
/// Casting to a `Dynamic` just returns as is, but if it contains a shared value,
/// it is cloned into a `Dynamic` with a normal value.
///
/// Returns `None` if types mismatched.
///
/// # Panics or Deadlocks
///
/// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1).
/// Otherwise, this call panics if the data is currently borrowed for write.
///
/// These normally shouldn't occur since most operations in Rhai is single-threaded.
///
/// # Example
///
@ -433,12 +641,28 @@ impl Dynamic {
pub fn try_cast<T: Variant>(self) -> Option<T> {
let type_id = TypeId::of::<T>();
match self.0 {
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
Union::Shared(cell) => return cell.borrow().clone().try_cast(),
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
Union::Shared(cell) => return cell.read().unwrap().clone().try_cast(),
_ => (),
}
if type_id == TypeId::of::<Dynamic>() {
return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v);
}
if type_id == TypeId::of::<INT>() {
return match self.0 {
Union::Int(value) => unsafe_try_cast(value),
_ => None,
};
}
#[cfg(not(feature = "no_float"))]
if type_id == TypeId::of::<FLOAT>() {
return match self.0 {
@ -446,30 +670,35 @@ impl Dynamic {
_ => None,
};
}
if type_id == TypeId::of::<bool>() {
return match self.0 {
Union::Bool(value) => unsafe_try_cast(value),
_ => None,
};
}
if type_id == TypeId::of::<ImmutableString>() {
return match self.0 {
Union::Str(value) => unsafe_try_cast(value),
_ => None,
};
}
if type_id == TypeId::of::<String>() {
return match self.0 {
Union::Str(value) => unsafe_try_cast(value.into_owned()),
_ => None,
};
}
if type_id == TypeId::of::<char>() {
return match self.0 {
Union::Char(value) => unsafe_try_cast(value),
_ => None,
};
}
#[cfg(not(feature = "no_index"))]
if type_id == TypeId::of::<Array>() {
return match self.0 {
@ -477,6 +706,7 @@ impl Dynamic {
_ => None,
};
}
#[cfg(not(feature = "no_object"))]
if type_id == TypeId::of::<Map>() {
return match self.0 {
@ -484,34 +714,45 @@ impl Dynamic {
_ => None,
};
}
if type_id == TypeId::of::<FnPtr>() {
return match self.0 {
Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None,
};
}
if type_id == TypeId::of::<()>() {
return match self.0 {
Union::Unit(value) => unsafe_try_cast(value),
_ => None,
};
}
if type_id == TypeId::of::<Dynamic>() {
return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v);
}
match self.0 {
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
#[cfg(not(feature = "no_closure"))]
Union::Shared(_) => unreachable!(),
_ => None,
}
}
/// Get a copy of the `Dynamic` value as a specific type.
/// Casting to a `Dynamic` just returns as is.
/// Convert the `Dynamic` value into a specific type.
///
/// # Panics
/// Casting to a `Dynamic` just returns as is, but if it contains a shared value,
/// it is cloned into a `Dynamic` with a normal value.
///
/// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type).
/// Returns `None` if types mismatched.
///
/// # Panics or Deadlocks
///
/// Panics if the cast fails (e.g. the type of the actual value is not the
/// same as the specified type).
///
/// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1).
/// Otherwise, this call panics if the data is currently borrowed for write.
///
/// These normally shouldn't occur since most operations in Rhai is single-threaded.
///
/// # Example
///
@ -527,11 +768,125 @@ impl Dynamic {
self.try_cast::<T>().unwrap()
}
/// Get a reference of a specific type to the `Dynamic`.
/// Casting to `Dynamic` just returns a reference to it.
/// Get a copy of the `Dynamic` as a specific type.
///
/// If the `Dynamic` is not a shared value, it returns a cloned copy of the value.
///
/// If the `Dynamic` is a shared value, it returns a cloned copy of the shared value.
///
/// Returns `None` if the cast fails.
#[inline(always)]
pub fn downcast_ref<T: Variant + Clone>(&self) -> Option<&T> {
pub fn clone_inner_data<T: Variant + Clone>(self) -> Option<T> {
match self.0 {
#[cfg(not(feature = "no_closure"))]
Union::Shared(cell) => {
#[cfg(not(feature = "sync"))]
return Some(cell.borrow().downcast_ref::<T>().unwrap().clone());
#[cfg(feature = "sync")]
return Some(cell.read().unwrap().downcast_ref::<T>().unwrap().clone());
}
_ => self.try_cast(),
}
}
/// Is the `Dynamic` a shared value that is locked?
///
/// ## Note
///
/// Under the `sync` feature, shared values use `RwLock` and they are never locked.
/// Access just waits until the `RwLock` is released.
/// So this method always returns `false` under `sync`.
#[inline(always)]
pub fn is_locked(&self) -> bool {
match self.0 {
#[cfg(not(feature = "no_closure"))]
Union::Shared(ref _cell) => {
#[cfg(not(feature = "sync"))]
return _cell.try_borrow().is_err();
#[cfg(feature = "sync")]
return false;
}
_ => false,
}
}
/// Get a reference of a specific type to the `Dynamic`.
/// Casting to `Dynamic` just returns a reference to it.
///
/// Returns `None` if the cast fails.
///
/// # Panics or Deadlocks When Value is Shared
///
/// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1).
/// Otherwise, this call panics if the data is currently borrowed for write.
#[inline(always)]
pub fn read_lock<T: Variant + Clone>(&self) -> Option<DynamicReadLock<T>> {
match self.0 {
#[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell) => {
#[cfg(not(feature = "sync"))]
let data = cell.borrow();
#[cfg(feature = "sync")]
let data = cell.read().unwrap();
let type_id = (*data).type_id();
println!("Type = {}", (*data).type_name());
if type_id != TypeId::of::<T>() && TypeId::of::<Dynamic>() != TypeId::of::<T>() {
None
} else {
Some(DynamicReadLock(DynamicReadLockInner::Guard(data)))
}
}
_ => self
.downcast_ref()
.map(|r| DynamicReadLock(DynamicReadLockInner::Reference(r))),
}
}
/// Get a mutable reference of a specific type to the `Dynamic`.
/// Casting to `Dynamic` just returns a mutable reference to it.
///
/// Returns `None` if the cast fails.
///
/// # Panics or Deadlocks When Value is Shared
///
/// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1).
/// Otherwise, this call panics if the data is currently borrowed for write.
#[inline(always)]
pub fn write_lock<T: Variant + Clone>(&mut self) -> Option<DynamicWriteLock<T>> {
match self.0 {
#[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell) => {
#[cfg(not(feature = "sync"))]
let data = cell.borrow_mut();
#[cfg(feature = "sync")]
let data = cell.write().unwrap();
let type_id = (*data).type_id();
if type_id != TypeId::of::<T>() && TypeId::of::<Dynamic>() != TypeId::of::<T>() {
None
} else {
Some(DynamicWriteLock(DynamicWriteLockInner::Guard(data)))
}
}
_ => self
.downcast_mut()
.map(|r| DynamicWriteLock(DynamicWriteLockInner::Reference(r))),
}
}
/// Get a reference of a specific type to the `Dynamic`.
/// Casting to `Dynamic` just returns a reference to it.
///
/// Returns `None` if the cast fails, or if the value is shared.
#[inline(always)]
pub(crate) fn downcast_ref<T: Variant + Clone>(&self) -> Option<&T> {
let type_id = TypeId::of::<T>();
if type_id == TypeId::of::<INT>() {
@ -603,15 +958,18 @@ impl Dynamic {
match &self.0 {
Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(),
#[cfg(not(feature = "no_closure"))]
Union::Shared(_) => None,
_ => None,
}
}
/// Get a mutable reference of a specific type to the `Dynamic`.
/// Casting to `Dynamic` just returns a mutable reference to it.
/// Returns `None` if the cast fails.
///
/// Returns `None` if the cast fails, or if the value is shared.
#[inline(always)]
pub fn downcast_mut<T: Variant + Clone>(&mut self) -> Option<&mut T> {
pub(crate) fn downcast_mut<T: Variant + Clone>(&mut self) -> Option<&mut T> {
let type_id = TypeId::of::<T>();
if type_id == TypeId::of::<INT>() {
@ -677,6 +1035,8 @@ impl Dynamic {
match &mut self.0 {
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(),
#[cfg(not(feature = "no_closure"))]
Union::Shared(_) => None,
_ => None,
}
}
@ -686,6 +1046,8 @@ impl Dynamic {
pub fn as_int(&self) -> Result<INT, &'static str> {
match self.0 {
Union::Int(n) => Ok(n),
#[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
_ => Err(self.type_name()),
}
}
@ -696,6 +1058,8 @@ impl Dynamic {
pub fn as_float(&self) -> Result<FLOAT, &'static str> {
match self.0 {
Union::Float(n) => Ok(n),
#[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
_ => Err(self.type_name()),
}
}
@ -705,6 +1069,8 @@ impl Dynamic {
pub fn as_bool(&self) -> Result<bool, &'static str> {
match self.0 {
Union::Bool(b) => Ok(b),
#[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
_ => Err(self.type_name()),
}
}
@ -714,12 +1080,16 @@ impl Dynamic {
pub fn as_char(&self) -> Result<char, &'static str> {
match self.0 {
Union::Char(n) => Ok(n),
#[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
_ => Err(self.type_name()),
}
}
/// Cast the `Dynamic` as a string and return the string slice.
/// Returns the name of the actual type if the cast fails.
///
/// Cast is failing if `self` is Shared Dynamic
pub fn as_str(&self) -> Result<&str, &'static str> {
match &self.0 {
Union::Str(s) => Ok(s),
@ -741,6 +1111,27 @@ impl Dynamic {
match self.0 {
Union::Str(s) => Ok(s),
Union::FnPtr(f) => Ok(f.take_data().0),
#[cfg(not(feature = "no_closure"))]
Union::Shared(cell) => {
#[cfg(not(feature = "sync"))]
{
let inner = cell.borrow();
match &inner.0 {
Union::Str(s) => Ok(s.clone()),
Union::FnPtr(f) => Ok(f.clone().take_data().0),
_ => Err((*inner).type_name()),
}
}
#[cfg(feature = "sync")]
{
let inner = cell.read().unwrap();
match &inner.0 {
Union::Str(s) => Ok(s.clone()),
Union::FnPtr(f) => Ok(f.clone().take_data().0),
_ => Err((*inner).type_name()),
}
}
}
_ => Err(self.type_name()),
}
}
@ -806,7 +1197,7 @@ impl<K: Into<ImmutableString>, T: Variant + Clone> From<HashMap<K, T>> for Dynam
}
impl From<FnPtr> for Dynamic {
fn from(value: FnPtr) -> Self {
Box::new(value).into()
Self(Union::FnPtr(Box::new(value)))
}
}
impl From<Box<FnPtr>> for Dynamic {

View File

@ -3,6 +3,7 @@
use crate::any::{Dynamic, Variant};
use crate::engine::{Engine, Imports, State};
use crate::error::ParseError;
use crate::fn_call::ensure_no_data_race;
use crate::fn_native::{IteratorFn, SendSync};
use crate::module::{FuncReturn, Module};
use crate::optimize::{optimize_into_ast, OptimizationLevel};
@ -19,6 +20,7 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET};
use crate::{
engine::{make_getter, make_setter, Map},
fn_register::RegisterFn,
token::Token,
};
#[cfg(not(feature = "no_function"))]
@ -62,147 +64,6 @@ impl Engine {
self
}
/// Register a function of no parameters with the `Engine`.
///
/// ## WARNING - Low Level API
///
/// This function is very low level.
#[deprecated(note = "this function is volatile and may change")]
pub fn register_raw_fn_0<T: Variant + Clone>(
&mut self,
name: &str,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
) -> &mut Self {
self.global_module.set_raw_fn(name, &[], func);
self
}
/// Register a function of one parameter with the `Engine`.
///
/// ## WARNING - Low Level API
///
/// This function is very low level.
///
/// Arguments are simply passed in as a mutable array of `&mut Dynamic`,
/// which is guaranteed to contain enough arguments of the correct types.
///
/// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::<T>()`
///
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// Notice that this will _consume_ the argument, replacing it with `()`.
///
/// To access the first mutable parameter, use `args.get_mut(0).unwrap()`
#[deprecated(note = "this function is volatile and may change")]
pub fn register_raw_fn_1<A: Variant + Clone, T: Variant + Clone>(
&mut self,
name: &str,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
) -> &mut Self {
self.global_module
.set_raw_fn(name, &[TypeId::of::<A>()], func);
self
}
/// Register a function of two parameters with the `Engine`.
///
/// ## WARNING - Low Level API
///
/// This function is very low level.
///
/// Arguments are simply passed in as a mutable array of `&mut Dynamic`,
/// which is guaranteed to contain enough arguments of the correct types.
///
/// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::<T>()`
///
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// Notice that this will _consume_ the argument, replacing it with `()`.
///
/// To access the first mutable parameter, use `args.get_mut(0).unwrap()`
#[deprecated(note = "this function is volatile and may change")]
pub fn register_raw_fn_2<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self,
name: &str,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
) -> &mut Self {
self.global_module
.set_raw_fn(name, &[TypeId::of::<A>(), TypeId::of::<B>()], func);
self
}
/// Register a function of three parameters with the `Engine`.
///
/// ## WARNING - Low Level API
///
/// This function is very low level.
///
/// Arguments are simply passed in as a mutable array of `&mut Dynamic`,
/// which is guaranteed to contain enough arguments of the correct types.
///
/// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::<T>()`
///
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// Notice that this will _consume_ the argument, replacing it with `()`.
///
/// To access the first mutable parameter, use `args.get_mut(0).unwrap()`
#[deprecated(note = "this function is volatile and may change")]
pub fn register_raw_fn_3<
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
T: Variant + Clone,
>(
&mut self,
name: &str,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
) -> &mut Self {
self.global_module.set_raw_fn(
name,
&[TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()],
func,
);
self
}
/// Register a function of four parameters with the `Engine`.
///
/// ## WARNING - Low Level API
///
/// This function is very low level.
///
/// Arguments are simply passed in as a mutable array of `&mut Dynamic`,
/// which is guaranteed to contain enough arguments of the correct types.
///
/// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::<T>()`
///
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
/// Notice that this will _consume_ the argument, replacing it with `()`.
///
/// To access the first mutable parameter, use `args.get_mut(0).unwrap()`
#[deprecated(note = "this function is volatile and may change")]
pub fn register_raw_fn_4<
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
D: Variant + Clone,
T: Variant + Clone,
>(
&mut self,
name: &str,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
) -> &mut Self {
self.global_module.set_raw_fn(
name,
&[
TypeId::of::<A>(),
TypeId::of::<B>(),
TypeId::of::<C>(),
TypeId::of::<D>(),
],
func,
);
self
}
/// Register a custom type for use with the `Engine`.
/// The type must implement `Clone`.
///
@ -731,7 +592,7 @@ impl Engine {
scripts: &[&str],
optimization_level: OptimizationLevel,
) -> Result<AST, ParseError> {
let stream = lex(scripts, self);
let stream = lex(scripts, None, self);
self.parse(&mut stream.peekable(), scope, optimization_level)
}
@ -856,7 +717,19 @@ impl Engine {
// Trims the JSON string and add a '#' in front
let scripts = ["#", json.trim()];
let stream = lex(&scripts, self);
let stream = lex(
&scripts,
if has_null {
Some(Box::new(|token| match token {
// If `null` is present, make sure `null` is treated as a variable
Token::Reserved(s) if s == "null" => Token::Identifier(s),
_ => token,
}))
} else {
None
},
self,
);
let ast =
self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?;
@ -937,7 +810,7 @@ impl Engine {
script: &str,
) -> Result<AST, ParseError> {
let scripts = [script];
let stream = lex(&scripts, self);
let stream = lex(&scripts, None, self);
{
let mut peekable = stream.peekable();
self.parse_global_expr(&mut peekable, scope, self.optimization_level)
@ -1092,7 +965,7 @@ impl Engine {
script: &str,
) -> Result<T, Box<EvalAltResult>> {
let scripts = [script];
let stream = lex(&scripts, self);
let stream = lex(&scripts, None, self);
// No need to optimize a lone expression
let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?;
@ -1225,7 +1098,7 @@ impl Engine {
script: &str,
) -> Result<(), Box<EvalAltResult>> {
let scripts = [script];
let stream = lex(&scripts, self);
let stream = lex(&scripts, None, self);
let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?;
self.consume_ast_with_scope(scope, &ast)
}
@ -1410,6 +1283,11 @@ impl Engine {
let mut mods = Imports::new();
let args = args.as_mut();
// Check for data race.
if cfg!(not(feature = "no_closure")) {
ensure_no_data_race(name, args, false)?;
}
self.call_script_fn(
scope, &mut mods, &mut state, lib, this_ptr, name, fn_def, args, 0,
)
@ -1433,16 +1311,15 @@ impl Engine {
mut ast: AST,
optimization_level: OptimizationLevel,
) -> AST {
#[cfg(not(feature = "no_function"))]
let lib = ast
.lib()
.iter_fn()
.filter(|(_, _, _, f)| f.is_script())
.map(|(_, _, _, f)| f.get_fn_def().clone())
.collect();
#[cfg(feature = "no_function")]
let lib = Default::default();
let lib = if cfg!(not(feature = "no_function")) {
ast.lib()
.iter_fn()
.filter(|(_, _, _, f)| f.is_script())
.map(|(_, _, _, f)| f.get_fn_def().clone())
.collect()
} else {
Default::default()
};
let stmt = mem::take(ast.statements_mut());
optimize_into_ast(self, scope, stmt, lib, optimization_level)

View File

@ -19,7 +19,7 @@ use crate::utils::StaticVec;
use crate::any::Variant;
#[cfg(not(feature = "no_function"))]
use crate::parser::{FnAccess, ScriptFnDef};
use crate::parser::ScriptFnDef;
#[cfg(not(feature = "no_module"))]
use crate::module::ModuleResolver;
@ -31,12 +31,17 @@ use crate::module::resolvers;
#[cfg(any(not(feature = "no_object"), not(feature = "no_module")))]
use crate::utils::ImmutableString;
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
use crate::any::DynamicWriteLock;
use crate::stdlib::{
borrow::Cow,
boxed::Box,
collections::{HashMap, HashSet},
fmt, format,
iter::{empty, once},
ops::DerefMut,
string::{String, ToString},
vec::Vec,
};
@ -44,6 +49,9 @@ use crate::stdlib::{
#[cfg(not(feature = "no_index"))]
use crate::stdlib::any::TypeId;
#[cfg(not(feature = "no_closure"))]
use crate::stdlib::mem;
/// Variable-sized array of `Dynamic` values.
///
/// Not available under the `no_index` feature.
@ -91,6 +99,7 @@ pub const KEYWORD_EVAL: &str = "eval";
pub const KEYWORD_FN_PTR: &str = "Fn";
pub const KEYWORD_FN_PTR_CALL: &str = "call";
pub const KEYWORD_FN_PTR_CURRY: &str = "curry";
pub const KEYWORD_IS_SHARED: &str = "is_shared";
pub const KEYWORD_THIS: &str = "this";
pub const FN_TO_STRING: &str = "to_string";
#[cfg(not(feature = "no_object"))]
@ -122,6 +131,11 @@ pub enum ChainType {
pub enum Target<'a> {
/// The target is a mutable reference to a `Dynamic` value somewhere.
Ref(&'a mut Dynamic),
/// The target is a mutable reference to a Shared `Dynamic` value.
/// It holds both the access guard and the original shared value.
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
LockGuard((DynamicWriteLock<'a, Dynamic>, Dynamic)),
/// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects).
Value(Dynamic),
/// The target is a character inside a String.
@ -136,6 +150,9 @@ impl Target<'_> {
pub fn is_ref(&self) -> bool {
match self {
Self::Ref(_) => true,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard(_) => true,
Self::Value(_) => false,
#[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, _) => false,
@ -145,16 +162,34 @@ impl Target<'_> {
pub fn is_value(&self) -> bool {
match self {
Self::Ref(_) => false,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard(_) => false,
Self::Value(_) => true,
#[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, _) => false,
}
}
/// Is the `Target` a shared value?
pub fn is_shared(&self) -> bool {
match self {
Self::Ref(r) => r.is_shared(),
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard(_) => true,
Self::Value(r) => r.is_shared(),
#[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, _) => false,
}
}
/// Is the `Target` a specific type?
#[allow(dead_code)]
pub fn is<T: Variant + Clone>(&self) -> bool {
match self {
Target::Ref(r) => r.is::<T>(),
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Target::LockGuard((r, _)) => r.is::<T>(),
Target::Value(r) => r.is::<T>(),
#[cfg(not(feature = "no_index"))]
Target::StringChar(_, _, _) => TypeId::of::<T>() == TypeId::of::<char>(),
@ -164,6 +199,9 @@ impl Target<'_> {
pub fn clone_into_dynamic(self) -> Dynamic {
match self {
Self::Ref(r) => r.clone(), // Referenced value is cloned
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard((_, orig)) => orig, // Original value is simply taken
Self::Value(v) => v, // Owned value is simply taken
#[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, ch) => ch, // Character is taken
@ -173,6 +211,9 @@ impl Target<'_> {
pub fn as_mut(&mut self) -> &mut Dynamic {
match self {
Self::Ref(r) => *r,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard((r, _)) => r.deref_mut(),
Self::Value(ref mut r) => r,
#[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, ref mut r) => r,
@ -183,13 +224,18 @@ impl Target<'_> {
pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> {
match self {
Self::Ref(r) => **r = new_val,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard((r, _)) => **r = new_val,
Self::Value(_) => {
return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(
Position::none(),
)))
}
#[cfg(not(feature = "no_index"))]
Self::StringChar(Dynamic(Union::Str(ref mut s)), index, _) => {
Self::StringChar(string, index, _) if string.is::<ImmutableString>() => {
let mut s = string.write_lock::<ImmutableString>().unwrap();
// Replace the character at the specified index position
let new_ch = new_val
.as_char()
@ -215,9 +261,18 @@ impl Target<'_> {
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl<'a> From<&'a mut Dynamic> for Target<'a> {
fn from(value: &'a mut Dynamic) -> Self {
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
if value.is_shared() {
// Cloning is cheap for a shared value
let container = value.clone();
return Self::LockGuard((value.write_lock::<Dynamic>().unwrap(), container));
}
Self::Ref(value)
}
}
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl<T: Into<Dynamic>> From<T> for Target<'_> {
fn from(value: T) -> Self {
@ -228,11 +283,6 @@ impl<T: Into<Dynamic>> From<T> for Target<'_> {
/// [INTERNALS] A type that holds all the current states of the Engine.
/// Exported under the `internals` feature only.
///
/// # Safety
///
/// This type uses some unsafe code, mainly for avoiding cloning of local variable names via
/// direct lifetime casting.
///
/// ## WARNING
///
/// This type is volatile and may change.
@ -264,19 +314,15 @@ pub fn get_script_function_by_signature<'a>(
module: &'a Module,
name: &str,
params: usize,
public_only: bool,
pub_only: bool,
) -> Option<&'a ScriptFnDef> {
// Qualifiers (none) + function name + number of arguments.
let hash_script = calc_fn_hash(empty(), name, params, empty());
let func = module.get_fn(hash_script)?;
if !func.is_script() {
return None;
}
let fn_def = func.get_fn_def();
match fn_def.access {
FnAccess::Private if public_only => None,
FnAccess::Private | FnAccess::Public => Some(&fn_def),
let func = module.get_fn(hash_script, pub_only)?;
if func.is_script() {
Some(func.get_fn_def())
} else {
None
}
}
@ -401,11 +447,11 @@ impl Default for Engine {
progress: None,
// optimization level
#[cfg(feature = "no_optimize")]
optimization_level: OptimizationLevel::None,
#[cfg(not(feature = "no_optimize"))]
optimization_level: OptimizationLevel::Simple,
optimization_level: if cfg!(feature = "no_optimize") {
OptimizationLevel::None
} else {
OptimizationLevel::Simple
},
#[cfg(not(feature = "unchecked"))]
limits: Limits {
@ -454,7 +500,7 @@ pub fn search_imports<'s>(
state: &mut State,
modules: &Box<ModuleRef>,
) -> Result<&'s Module, Box<EvalAltResult>> {
let (root, root_pos) = modules.get(0);
let (root, root_pos) = &modules[0];
// Qualified - check if the root module is directly indexed
let index = if state.always_search {
@ -487,7 +533,7 @@ pub fn search_imports_mut<'s>(
state: &mut State,
modules: &Box<ModuleRef>,
) -> Result<&'s mut Module, Box<EvalAltResult>> {
let (root, root_pos) = modules.get(0);
let (root, root_pos) = &modules[0];
// Qualified - check if the root module is directly indexed
let index = if state.always_search {
@ -513,7 +559,8 @@ pub fn search_imports_mut<'s>(
})
}
/// Search for a variable within the scope and imports
/// Search for a variable within the scope or within imports,
/// depending on whether the variable name is qualified.
pub fn search_namespace<'s, 'a>(
scope: &'s mut Scope,
mods: &'s mut Imports,
@ -565,7 +612,7 @@ pub fn search_scope_only<'s, 'a>(
if let Some(val) = this_ptr {
return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos));
} else {
return Err(Box::new(EvalAltResult::ErrorUnboundedThis(*pos)));
return Err(Box::new(EvalAltResult::ErrorUnboundThis(*pos)));
}
}
@ -583,6 +630,13 @@ pub fn search_scope_only<'s, 'a>(
};
let (val, typ) = scope.get_mut(index);
// Check for data race - probably not necessary because the only place it should conflict is in a method call
// when the object variable is also used as a parameter.
// if cfg!(not(feature = "no_closure")) && val.is_locked() {
// return Err(Box::new(EvalAltResult::ErrorDataRace(name.into(), *pos)));
// }
Ok((val, name, typ, *pos))
}
@ -613,11 +667,11 @@ impl Engine {
debug: Box::new(|_| {}),
progress: None,
#[cfg(feature = "no_optimize")]
optimization_level: OptimizationLevel::None,
#[cfg(not(feature = "no_optimize"))]
optimization_level: OptimizationLevel::Simple,
optimization_level: if cfg!(feature = "no_optimize") {
OptimizationLevel::None
} else {
OptimizationLevel::Simple
},
#[cfg(not(feature = "unchecked"))]
limits: Limits {
@ -661,7 +715,7 @@ impl Engine {
};
// Pop the last index value
let idx_val = idx_values.pop();
let idx_val = idx_values.pop().unwrap();
match chain_type {
#[cfg(not(feature = "no_index"))]
@ -673,8 +727,9 @@ impl Engine {
Expr::Dot(x) | Expr::Index(x) => {
let (idx, expr, pos) = x.as_ref();
let idx_pos = idx.position();
let obj_ptr = &mut self
.get_indexed_mut(state, lib, target, idx_val, idx_pos, false, level)?;
let obj_ptr = &mut self.get_indexed_mut(
state, lib, target, idx_val, idx_pos, false, true, level,
)?;
self.eval_dot_index_chain_helper(
state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level,
@ -684,54 +739,58 @@ impl Engine {
}
// xxx[rhs] = new_val
_ if _new_val.is_some() => {
let mut new_val = _new_val.unwrap();
let mut idx_val2 = idx_val.clone();
match self.get_indexed_mut(state, lib, target, idx_val, pos, true, level) {
// Indexed value is an owned value - the only possibility is an indexer
// Try to call an index setter
#[cfg(not(feature = "no_index"))]
Ok(obj_ptr) if obj_ptr.is_value() => {
let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val];
self.exec_fn_call(
state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None,
level,
)
.or_else(|err| match *err {
// If there is no index setter, no need to set it back because the indexer is read-only
EvalAltResult::ErrorFunctionNotFound(_, _) => {
Ok(Default::default())
}
_ => Err(err),
})?;
}
// `call_setter` is introduced to bypass double mutable borrowing of target
let _call_setter = match self
.get_indexed_mut(state, lib, target, idx_val, pos, true, false, level)
{
// Indexed value is a reference - update directly
Ok(ref mut obj_ptr) => {
obj_ptr
.set_value(new_val)
.set_value(_new_val.unwrap())
.map_err(|err| err.new_position(rhs.position()))?;
None
}
Err(err) => match *err {
// No index getter - try to call an index setter
#[cfg(not(feature = "no_index"))]
EvalAltResult::ErrorIndexingType(_, _) => {
let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val];
self.exec_fn_call(
state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None,
level,
)?;
// Raise error if there is no index getter nor setter
Some(_new_val.unwrap())
}
// Error
// Any other error - return
err => return Err(Box::new(err)),
},
};
#[cfg(not(feature = "no_index"))]
if let Some(mut new_val) = _call_setter {
let val = target.as_mut();
let val_type_name = val.type_name();
let args = &mut [val, &mut idx_val2, &mut new_val];
self.exec_fn_call(
state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, None,
level,
)
.map_err(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(_, _) => {
EvalAltResult::ErrorIndexingType(
self.map_type_name(val_type_name).into(),
Position::none(),
)
}
err => err,
})?;
}
Ok(Default::default())
}
// xxx[rhs]
_ => self
.get_indexed_mut(state, lib, target, idx_val, pos, false, level)
.get_indexed_mut(state, lib, target, idx_val, pos, false, true, level)
.map(|v| (v.clone_into_dynamic(), false)),
}
}
@ -741,7 +800,12 @@ impl Engine {
match rhs {
// xxx.fn_name(arg_expr_list)
Expr::FnCall(x) if x.1.is_none() => {
self.make_method_call(state, lib, target, rhs, idx_val, level)
let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
self.make_method_call(
state, lib, name, *hash, target, idx_val, *def_val, *native, false,
level,
)
.map_err(|err| err.new_position(*pos))
}
// xxx.module::fn_name(...) - syntax error
Expr::FnCall(_) => unreachable!(),
@ -749,8 +813,8 @@ impl Engine {
Expr::Property(x) if target.is::<Map>() && _new_val.is_some() => {
let ((prop, _, _), pos) = x.as_ref();
let index = prop.clone().into();
let mut val =
self.get_indexed_mut(state, lib, target, index, *pos, true, level)?;
let mut val = self
.get_indexed_mut(state, lib, target, index, *pos, true, false, level)?;
val.set_value(_new_val.unwrap())
.map_err(|err| err.new_position(rhs.position()))?;
@ -760,8 +824,9 @@ impl Engine {
Expr::Property(x) if target.is::<Map>() => {
let ((prop, _, _), pos) = x.as_ref();
let index = prop.clone().into();
let val =
self.get_indexed_mut(state, lib, target, index, *pos, false, level)?;
let val = self.get_indexed_mut(
state, lib, target, index, *pos, false, false, level,
)?;
Ok((val.clone_into_dynamic(), false))
}
@ -770,7 +835,8 @@ impl Engine {
let ((_, _, setter), pos) = x.as_ref();
let mut args = [target.as_mut(), _new_val.as_mut().unwrap()];
self.exec_fn_call(
state, lib, setter, true, 0, &mut args, is_ref, true, None, level,
state, lib, setter, 0, &mut args, is_ref, true, false, None, None,
level,
)
.map(|(v, _)| (v, true))
.map_err(|err| err.new_position(*pos))
@ -780,7 +846,8 @@ impl Engine {
let ((_, getter, _), pos) = x.as_ref();
let mut args = [target.as_mut()];
self.exec_fn_call(
state, lib, getter, true, 0, &mut args, is_ref, true, None, level,
state, lib, getter, 0, &mut args, is_ref, true, false, None, None,
level,
)
.map(|(v, _)| (v, false))
.map_err(|err| err.new_position(*pos))
@ -791,15 +858,21 @@ impl Engine {
let mut val = match sub_lhs {
Expr::Property(p) => {
let ((prop, _, _), _) = p.as_ref();
let ((prop, _, _), pos) = p.as_ref();
let index = prop.clone().into();
self.get_indexed_mut(state, lib, target, index, *pos, false, level)?
self.get_indexed_mut(
state, lib, target, index, *pos, false, true, level,
)?
}
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
Expr::FnCall(x) if x.1.is_none() => {
let (val, _) = self.make_method_call(
state, lib, target, sub_lhs, idx_val, level,
)?;
let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
let (val, _) = self
.make_method_call(
state, lib, name, *hash, target, idx_val, *def_val,
*native, false, level,
)
.map_err(|err| err.new_position(*pos))?;
val.into()
}
// {xxx:map}.module::fn_name(...) - syntax error
@ -816,28 +889,34 @@ impl Engine {
}
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr
Expr::Index(x) | Expr::Dot(x) => {
let (sub_lhs, expr, pos) = x.as_ref();
let (sub_lhs, expr, _) = x.as_ref();
match sub_lhs {
// xxx.prop[expr] | xxx.prop.expr
Expr::Property(p) => {
let ((_, getter, setter), _) = p.as_ref();
let ((_, getter, setter), pos) = p.as_ref();
let arg_values = &mut [target.as_mut(), &mut Default::default()];
let args = &mut arg_values[..1];
let (mut val, updated) = self
.exec_fn_call(
state, lib, getter, true, 0, args, is_ref, true, None,
level,
state, lib, getter, 0, args, is_ref, true, false, None,
None, level,
)
.map_err(|err| err.new_position(*pos))?;
let val = &mut val;
let target = &mut val.into();
let (result, may_be_changed) = self
.eval_dot_index_chain_helper(
state, lib, this_ptr, target, expr, idx_values, next_chain,
level, _new_val,
state,
lib,
this_ptr,
&mut val.into(),
expr,
idx_values,
next_chain,
level,
_new_val,
)
.map_err(|err| err.new_position(*pos))?;
@ -846,8 +925,8 @@ impl Engine {
// Re-use args because the first &mut parameter will not be consumed
arg_values[1] = val;
self.exec_fn_call(
state, lib, setter, true, 0, arg_values, is_ref, true,
None, level,
state, lib, setter, 0, arg_values, is_ref, true, false,
None, None, level,
)
.or_else(
|err| match *err {
@ -864,9 +943,13 @@ impl Engine {
}
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
Expr::FnCall(x) if x.1.is_none() => {
let (mut val, _) = self.make_method_call(
state, lib, target, sub_lhs, idx_val, level,
)?;
let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
let (mut val, _) = self
.make_method_call(
state, lib, name, *hash, target, idx_val, *def_val,
*native, false, level,
)
.map_err(|err| err.new_position(*pos))?;
let val = &mut val;
let target = &mut val.into();
@ -1001,7 +1084,7 @@ impl Engine {
idx_values.push(Dynamic::from(arg_values));
}
Expr::FnCall(_) => unreachable!(),
Expr::Property(_) => idx_values.push(()), // Store a placeholder - no need to copy the property name
Expr::Property(_) => idx_values.push(().into()), // Store a placeholder - no need to copy the property name
Expr::Index(x) | Expr::Dot(x) => {
let (lhs, rhs, _) = x.as_ref();
@ -1052,6 +1135,7 @@ impl Engine {
mut _idx: Dynamic,
idx_pos: Position,
_create: bool,
_indexers: bool,
_level: usize,
) -> Result<Target<'a>, Box<EvalAltResult>> {
self.inc_operations(state)?;
@ -1096,10 +1180,10 @@ impl Engine {
map.entry(index).or_insert(Default::default()).into()
} else {
let index = _idx
.downcast_ref::<ImmutableString>()
.read_lock::<ImmutableString>()
.ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?;
map.get_mut(index.as_str())
map.get_mut(&*index)
.map(Target::from)
.unwrap_or_else(|| Target::from(()))
})
@ -1128,11 +1212,11 @@ impl Engine {
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
_ => {
_ if _indexers => {
let type_name = val.type_name();
let args = &mut [val, &mut _idx];
self.exec_fn_call(
state, _lib, FN_IDX_GET, true, 0, args, is_ref, true, None, _level,
state, _lib, FN_IDX_GET, 0, args, is_ref, true, false, None, None, _level,
)
.map(|(v, _)| v.into())
.map_err(|err| match *err {
@ -1143,7 +1227,6 @@ impl Engine {
})
}
#[cfg(any(feature = "no_index", feature = "no_object"))]
_ => Err(Box::new(EvalAltResult::ErrorIndexingType(
self.map_type_name(val.type_name()).into(),
Position::none(),
@ -1173,26 +1256,23 @@ impl Engine {
#[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(mut rhs_value)) => {
let op = "==";
let mut scope = Scope::new();
// Call the `==` operator to compare each value
for value in rhs_value.iter_mut() {
let def_value = Some(false);
let args = &mut [&mut lhs_value.clone(), value];
let hashes = (
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())),
0,
);
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
let hash =
calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id()));
let (r, _) = self
.call_fn_raw(
&mut scope, mods, state, lib, op, hashes, args, false, false,
def_value, level,
)
.map_err(|err| err.new_position(rhs.position()))?;
if r.as_bool().unwrap_or(false) {
if self
.call_native_fn(state, lib, op, hash, args, false, false, def_value)
.map_err(|err| err.new_position(rhs.position()))?
.0
.as_bool()
.unwrap_or(false)
{
return Ok(true.into());
}
}
@ -1202,10 +1282,8 @@ impl Engine {
#[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(rhs_value)) => match lhs_value {
// Only allows String or char
Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(s.as_str()).into()),
Dynamic(Union::Char(c)) => {
Ok(rhs_value.contains_key(c.to_string().as_str()).into())
}
Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(&s).into()),
Dynamic(Union::Char(c)) => Ok(rhs_value.contains_key(&c.to_string()).into()),
_ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))),
},
Dynamic(Union::Str(rhs_value)) => match lhs_value {
@ -1245,7 +1323,7 @@ impl Engine {
if let Some(val) = this_ptr {
Ok(val.clone())
} else {
Err(Box::new(EvalAltResult::ErrorUnboundedThis((x.0).1)))
Err(Box::new(EvalAltResult::ErrorUnboundThis((x.0).1)))
}
}
Expr::Variable(_) => {
@ -1274,7 +1352,12 @@ impl Engine {
)),
// Normal assignment
ScopeEntryType::Normal if op.is_empty() => {
*lhs_ptr = rhs_val;
let rhs_val = rhs_val.clone_inner_data().unwrap();
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
*lhs_ptr.write_lock::<Dynamic>().unwrap() = rhs_val;
} else {
*lhs_ptr = rhs_val;
}
Ok(Default::default())
}
// Op-assignment - in order of precedence:
@ -1289,25 +1372,39 @@ impl Engine {
if let Some(CallableFunction::Method(func)) = self
.global_module
.get_fn(hash_fn)
.or_else(|| self.packages.get_fn(hash_fn))
.get_fn(hash_fn, false)
.or_else(|| self.packages.get_fn(hash_fn, false))
{
// Overriding exact implementation
func(self, lib, &mut [lhs_ptr, &mut rhs_val])?;
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
let mut lock_guard = lhs_ptr.write_lock::<Dynamic>().unwrap();
let lhs_ptr_inner = lock_guard.deref_mut();
// Overriding exact implementation
func(self, lib, &mut [lhs_ptr_inner, &mut rhs_val])?;
} else {
// Overriding exact implementation
func(self, lib, &mut [lhs_ptr, &mut rhs_val])?;
}
} else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() {
// Not built in, map to `var = var op rhs`
let op = &op[..op.len() - 1]; // extract operator without =
let hash = calc_fn_hash(empty(), op, 2, empty());
// Clone the LHS value
let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val];
// Run function
let (value, _) = self
.exec_fn_call(
state, lib, op, true, hash, args, false, false, None, level,
state, lib, op, 0, args, false, false, false, None, None, level,
)
.map_err(|err| err.new_position(*op_pos))?;
// Set value to LHS
*lhs_ptr = value;
let value = value.clone_inner_data().unwrap();
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
*lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
} else {
*lhs_ptr = value;
}
}
Ok(Default::default())
}
@ -1326,14 +1423,15 @@ impl Engine {
} else {
// Op-assignment - always map to `lhs = lhs op rhs`
let op = &op[..op.len() - 1]; // extract operator without =
let hash = calc_fn_hash(empty(), op, 2, empty());
let args = &mut [
&mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?,
&mut rhs_val,
];
self.exec_fn_call(state, lib, op, true, hash, args, false, false, None, level)
.map(|(v, _)| v)
.map_err(|err| err.new_position(*op_pos))?
self.exec_fn_call(
state, lib, op, 0, args, false, false, false, None, None, level,
)
.map(|(v, _)| v)
.map_err(|err| err.new_position(*op_pos))?
});
match lhs_expr {
@ -1400,20 +1498,20 @@ impl Engine {
// Normal function call
Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref();
let ((name, native, capture, pos), _, hash, args_expr, def_val) = x.as_ref();
self.make_function_call(
scope, mods, state, lib, this_ptr, name, args_expr, *def_val, *hash, *native,
level,
false, *capture, level,
)
.map_err(|err| err.new_position(*pos))
}
// Module-qualified function call
Expr::FnCall(x) if x.1.is_some() => {
let ((name, _, pos), modules, hash, args_expr, def_val) = x.as_ref();
let ((name, _, capture, pos), modules, hash, args_expr, def_val) = x.as_ref();
self.make_qualified_function_call(
scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash,
level,
*capture, level,
)
.map_err(|err| err.new_position(*pos))
}
@ -1481,6 +1579,12 @@ impl Engine {
}
/// Evaluate a statement
///
///
/// # Safety
///
/// This method uses some unsafe code, mainly for avoiding cloning of local variable names via
/// direct lifetime casting.
pub(crate) fn eval_stmt(
&self,
scope: &mut Scope,
@ -1524,7 +1628,7 @@ impl Engine {
// If-else statement
Stmt::IfThenElse(x) => {
let (expr, if_block, else_block) = x.as_ref();
let (expr, if_block, else_block, _) = x.as_ref();
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.as_bool()
@ -1542,7 +1646,7 @@ impl Engine {
// While loop
Stmt::While(x) => loop {
let (expr, body) = x.as_ref();
let (expr, body, _) = x.as_ref();
match self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
@ -1568,8 +1672,8 @@ impl Engine {
},
// Loop statement
Stmt::Loop(body) => loop {
match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) {
Stmt::Loop(x) => loop {
match self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level) {
Ok(_) => (),
Err(err) => match *err {
EvalAltResult::ErrorLoopBreak(false, _) => (),
@ -1581,7 +1685,7 @@ impl Engine {
// For loop
Stmt::For(x) => {
let (name, expr, stmt) = x.as_ref();
let (name, expr, stmt, _) = x.as_ref();
let iter_type = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let tid = iter_type.type_id();
@ -1597,7 +1701,15 @@ impl Engine {
state.scope_level += 1;
for loop_var in func(iter_type) {
*scope.get_mut(index).0 = loop_var;
let for_var = scope.get_mut(index).0;
let value = loop_var.clone_inner_data().unwrap();
if cfg!(not(feature = "no_closure")) && for_var.is_shared() {
*for_var.write_lock().unwrap() = value;
} else {
*for_var = value;
}
self.inc_operations(state)
.map_err(|err| err.new_position(stmt.position()))?;
@ -1627,16 +1739,9 @@ impl Engine {
// Return value
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
let expr = x.1.as_ref().unwrap();
Err(Box::new(EvalAltResult::Return(
self.eval_expr(
scope,
mods,
state,
lib,
this_ptr,
x.1.as_ref().unwrap(),
level,
)?,
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?,
(x.0).1,
)))
}
@ -1648,15 +1753,8 @@ impl Engine {
// Throw value
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => {
let val = self.eval_expr(
scope,
mods,
state,
lib,
this_ptr,
x.1.as_ref().unwrap(),
level,
)?;
let expr = x.1.as_ref().unwrap();
let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
Err(Box::new(EvalAltResult::ErrorRuntime(
val.take_string().unwrap_or_else(|_| "".into()),
(x.0).1,
@ -1672,23 +1770,19 @@ impl Engine {
// Let statement
Stmt::Let(x) if x.1.is_some() => {
let ((var_name, _), expr) = x.as_ref();
let val = self.eval_expr(
scope,
mods,
state,
lib,
this_ptr,
expr.as_ref().unwrap(),
level,
)?;
let ((var_name, _), expr, _) = x.as_ref();
let expr = expr.as_ref().unwrap();
let val = self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.clone_inner_data()
.unwrap();
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false);
Ok(Default::default())
}
Stmt::Let(x) => {
let ((var_name, _), _) = x.as_ref();
let ((var_name, _), _, _) = x.as_ref();
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
scope.push(var_name, ());
Ok(Default::default())
@ -1696,8 +1790,11 @@ impl Engine {
// Const statement
Stmt::Const(x) if x.1.is_constant() => {
let ((var_name, _), expr) = x.as_ref();
let val = self.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?;
let ((var_name, _), expr, _) = x.as_ref();
let val = self
.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?
.clone_inner_data()
.unwrap();
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true);
Ok(Default::default())
@ -1709,7 +1806,7 @@ impl Engine {
// Import statement
#[cfg(not(feature = "no_module"))]
Stmt::Import(x) => {
let (expr, (name, _pos)) = x.as_ref();
let (expr, (name, _pos), _) = x.as_ref();
// Guard against too many modules
#[cfg(not(feature = "unchecked"))]
@ -1742,8 +1839,8 @@ impl Engine {
// Export statement
#[cfg(not(feature = "no_module"))]
Stmt::Export(list) => {
for ((id, id_pos), rename) in list.iter() {
Stmt::Export(x) => {
for ((id, id_pos), rename) in x.0.iter() {
// Mark scope variables as public
if let Some(index) = scope.get_index(id).map(|(i, _)| i) {
let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id);
@ -1757,19 +1854,40 @@ impl Engine {
}
Ok(Default::default())
}
// Share statement
#[cfg(not(feature = "no_closure"))]
Stmt::Share(x) => {
let (var_name, _) = x.as_ref();
match scope.get_index(var_name) {
Some((index, ScopeEntryType::Normal)) => {
let (val, _) = scope.get_mut(index);
if !val.is_shared() {
// Replace the variable with a shared value.
*val = mem::take(val).into_shared();
}
}
_ => (),
}
Ok(Default::default())
}
};
self.check_data_size(result)
.map_err(|err| err.new_position(stmt.position()))
}
/// Check a result to ensure that the data size is within allowable limit.
/// Position in `EvalAltResult` may be None and should be set afterwards.
#[cfg(feature = "unchecked")]
#[inline(always)]
fn check_data_size(
&self,
result: Result<Dynamic, Box<EvalAltResult>>,
) -> Result<Dynamic, Box<EvalAltResult>> {
return result;
result
}
/// Check a result to ensure that the data size is within allowable limit.
@ -1779,9 +1897,6 @@ impl Engine {
&self,
result: Result<Dynamic, Box<EvalAltResult>>,
) -> Result<Dynamic, Box<EvalAltResult>> {
#[cfg(feature = "unchecked")]
return result;
// If no data size limits, just return
if self.limits.max_string_size + self.limits.max_array_size + self.limits.max_map_size == 0
{

View File

@ -91,6 +91,10 @@ pub enum ParseErrorType {
///
/// Never appears under the `no_object` and `no_index` features combination.
MalformedInExpr(String),
/// A capturing has syntax error. Wrapped value is the error description (if any).
///
/// Never appears under the `no_closure` feature.
MalformedCapture(String),
/// A map definition has duplicated property names. Wrapped value is the property name.
///
/// Never appears under the `no_object` feature.
@ -166,13 +170,14 @@ impl ParseErrorType {
Self::MalformedCallExpr(_) => "Invalid expression in function call arguments",
Self::MalformedIndexExpr(_) => "Invalid index in indexing expression",
Self::MalformedInExpr(_) => "Invalid 'in' expression",
Self::MalformedCapture(_) => "Invalid capturing",
Self::DuplicatedProperty(_) => "Duplicated property in object map literal",
Self::ForbiddenConstantExpr(_) => "Expecting a constant",
Self::PropertyExpected => "Expecting name of a property",
Self::VariableExpected => "Expecting name of a variable",
Self::Reserved(_) => "Invalid use of reserved keyword",
Self::ExprExpected(_) => "Expecting an expression",
Self::FnMissingName => "Expecting name in function declaration",
Self::FnMissingName => "Expecting function name in function declaration",
Self::FnMissingParams(_) => "Expecting parameters in function declaration",
Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
Self::FnMissingBody(_) => "Expecting body statement block for function declaration",
@ -199,9 +204,9 @@ impl fmt::Display for ParseErrorType {
}
Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s),
Self::MalformedIndexExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }),
Self::MalformedInExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }),
Self::MalformedIndexExpr(s) | Self::MalformedInExpr(s) | Self::MalformedCapture(s) => {
f.write_str(if s.is_empty() { self.desc() } else { s })
}
Self::DuplicatedProperty(s) => {
write!(f, "Duplicated property '{}' for object map literal", s)

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,18 @@
//! Module defining interfaces to native-Rust functions.
use crate::any::Dynamic;
use crate::calc_fn_hash;
use crate::engine::Engine;
use crate::module::Module;
use crate::parser::FnAccess;
use crate::result::EvalAltResult;
use crate::token::{is_valid_identifier, Position};
use crate::utils::ImmutableString;
#[cfg(not(feature = "no_function"))]
use crate::{module::FuncReturn, parser::ScriptFnDef, scope::Scope, utils::StaticVec};
use crate::{module::FuncReturn, parser::ScriptFnDef, utils::StaticVec};
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, string::String, vec::Vec};
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, string::String, vec::Vec};
#[cfg(not(feature = "no_function"))]
use crate::stdlib::mem;
@ -20,23 +22,43 @@ use crate::stdlib::rc::Rc;
#[cfg(feature = "sync")]
use crate::stdlib::sync::Arc;
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
use crate::stdlib::cell::RefCell;
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
use crate::stdlib::sync::RwLock;
/// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(feature = "sync")]
pub trait SendSync: Send + Sync {}
/// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(feature = "sync")]
impl<T: Send + Sync> SendSync for T {}
/// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(not(feature = "sync"))]
pub trait SendSync {}
/// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(not(feature = "sync"))]
impl<T> SendSync for T {}
/// Immutable reference-counted container
#[cfg(not(feature = "sync"))]
pub type Shared<T> = Rc<T>;
/// Immutable reference-counted container
#[cfg(feature = "sync")]
pub type Shared<T> = Arc<T>;
/// Mutable reference-counted container (read-write lock)
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
pub type SharedMut<T> = Shared<RefCell<T>>;
/// Mutable reference-counted container (read-write lock)
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
pub type SharedMut<T> = Shared<RwLock<T>>;
/// Consume a `Shared` resource and return a mutable reference to the wrapped value.
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
@ -89,9 +111,7 @@ impl FnPtr {
/// Call the function pointer with curried arguments (if any).
///
/// The function must be a script-defined function. It cannot be a Rust function.
///
/// To call a Rust function, just call it directly in Rust!
/// If this function is a script-defined function, it must not be marked private.
///
/// ## WARNING
///
@ -107,14 +127,39 @@ impl FnPtr {
this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>,
) -> FuncReturn<Dynamic> {
let args = self
let mut args_data = self
.1
.iter()
.cloned()
.chain(arg_values.as_mut().iter_mut().map(|v| mem::take(v)))
.collect::<StaticVec<_>>();
engine.call_fn_dynamic(&mut Scope::new(), lib, self.0.as_str(), this_ptr, args)
let has_this = this_ptr.is_some();
let args_len = args_data.len();
let mut args = args_data.iter_mut().collect::<StaticVec<_>>();
if let Some(obj) = this_ptr {
args.insert(0, obj);
}
let fn_name = self.0.as_str();
let hash_script = calc_fn_hash(empty(), fn_name, args_len, empty());
engine
.exec_fn_call(
&mut Default::default(),
lib.as_ref(),
fn_name,
hash_script,
args.as_mut(),
has_this,
has_this,
true,
None,
None,
0,
)
.map(|(v, _)| v)
}
}
@ -255,6 +300,23 @@ impl CallableFunction {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false,
}
}
/// Is this a native Rust function?
pub fn is_native(&self) -> bool {
match self {
Self::Pure(_) | Self::Method(_) => true,
Self::Iterator(_) => true,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
}
}
/// Get the access mode.
pub fn access(&self) -> FnAccess {
match self {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => FnAccess::Public,
Self::Script(f) => f.access,
}
}
/// Get a reference to a native Rust function.
///
/// # Panics

View File

@ -2,7 +2,7 @@
#![allow(non_snake_case)]
use crate::any::{Dynamic, Variant};
use crate::any::{Dynamic, Variant, DynamicWriteLock};
use crate::engine::Engine;
use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync};
use crate::module::Module;
@ -15,7 +15,7 @@ use crate::stdlib::{
any::TypeId,
boxed::Box,
mem,
string::{String, ToString},
string::String,
};
/// Trait to register custom functions with the `Engine`.
@ -97,11 +97,11 @@ pub trait RegisterResultFn<FN, ARGS> {
pub struct Mut<T>(T);
//pub struct Ref<T>(T);
/// Dereference into &mut.
/// Dereference into DynamicWriteLock
#[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()
pub fn by_ref<T: Variant + Clone>(data: &mut Dynamic) -> DynamicWriteLock<T> {
// Directly cast the &mut Dynamic into DynamicWriteLock to access the underlying data.
data.write_lock::<T>().unwrap()
}
/// Dereference into value.
@ -114,7 +114,7 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
ref_T.clone()
} else if TypeId::of::<T>() == TypeId::of::<String>() {
// If T is String, data must be ImmutableString, so map directly to it
*unsafe_cast_box(Box::new(data.as_str().unwrap().to_string())).unwrap()
*unsafe_cast_box(Box::new(data.clone().take_string().unwrap())).unwrap()
} else {
// 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.
@ -124,24 +124,23 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
/// This macro creates a closure wrapping a registered function.
macro_rules! make_func {
($fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => {
($fn:ident : $map:expr ; $($par:ident => $let:stmt => $convert:expr => $arg:expr),*) => {
// ^ function pointer
// ^ result mapping function
// ^ function parameter generic type name (A, B, C etc.)
// ^ dereferencing function
// ^ argument let statement(e.g. let mut A ...)
// ^ dereferencing function
// ^ argument reference expression(like A, *B, &mut C etc)
Box::new(move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
// The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut();
$(
// Downcast every element, panic in case of a type mismatch (which shouldn't happen).
// Call the user-supplied function using ($convert) to access it either by value or by reference.
let $par = ($convert)(_drain.next().unwrap());
)*
$($let)*
$($par = ($convert)(_drain.next().unwrap()); )*
// Call the function with each parameter value
let r = $fn($($par),*);
let r = $fn($($arg),*);
// Map the result
$map(r)
@ -181,12 +180,13 @@ macro_rules! def_register {
() => {
def_register!(imp from_pure :);
};
(imp $abi:ident : $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => {
(imp $abi:ident : $($par:ident => $arg:expr => $mark:ty => $param:ty => $let:stmt => $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
// ^ call argument(like A, *B, &mut C etc)
// ^ function parameter marker type (T, Ref<T> or Mut<T>)
// ^ function parameter actual type (T, &T or &mut T)
// ^ argument let statement
impl<
$($par: Variant + Clone,)*
FN: Fn($($param),*) -> RET + SendSync + 'static,
@ -196,7 +196,7 @@ macro_rules! def_register {
fn register_fn(&mut self, name: &str, f: FN) -> &mut Self {
self.global_module.set_fn(name, FnAccess::Public,
&[$(map_type_id::<$par>()),*],
CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $clone),*))
CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $let => $clone => $arg),*))
);
self
}
@ -210,7 +210,7 @@ macro_rules! def_register {
fn register_result_fn(&mut self, name: &str, f: FN) -> &mut Self {
self.global_module.set_fn(name, FnAccess::Public,
&[$(map_type_id::<$par>()),*],
CallableFunction::$abi(make_func!(f : map_result ; $($par => $clone),*))
CallableFunction::$abi(make_func!(f : map_result ; $($par => $let => $clone => $arg),*))
);
self
}
@ -219,11 +219,11 @@ macro_rules! def_register {
//def_register!(imp_pop $($par => $mark => $param),*);
};
($p0:ident $(, $p:ident)*) => {
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)*);
def_register!(imp from_pure : $p0 => $p0 => $p0 => $p0 => let $p0 => by_value $(, $p => $p => $p => $p => let $p => by_value)*);
def_register!(imp from_method : $p0 => &mut $p0 => Mut<$p0> => &mut $p0 => let mut $p0 => by_ref $(, $p => $p => $p => $p => let $p => by_value)*);
// ^ CallableFunction
// handle the first parameter ^ first parameter passed through
// ^ others passed by value (by_value)
// 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

@ -48,23 +48,7 @@
//! }
//! ```
//!
//! ## 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_module` | Disable loading external modules 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. |
//! | `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`. |
//! | `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
//! | `internals` | Expose internal data structures (beware they may be volatile from version to version). |
//! # Documentation
//!
//! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language.

View File

@ -355,10 +355,20 @@ impl Module {
///
/// let mut module = Module::new();
/// let hash = module.set_fn_0("calc", || Ok(42_i64));
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn contains_fn(&self, hash_fn: u64) -> bool {
self.functions.contains_key(&hash_fn)
pub fn contains_fn(&self, hash_fn: u64, public_only: bool) -> bool {
if public_only {
self.functions
.get(&hash_fn)
.map(|(_, access, _, _)| match access {
FnAccess::Public => true,
FnAccess::Private => false,
})
.unwrap_or(false)
} else {
self.functions.contains_key(&hash_fn)
}
}
/// Set a Rust function into the module, returning a hash key.
@ -422,7 +432,7 @@ impl Module {
/// let mut module = Module::new();
/// let hash = module.set_raw_fn("double_or_not",
/// // Pass parameter types via a slice with TypeId's
/// &[std::any::TypeId::of::<i64>(), std::any::TypeId::of::<bool>() ],
/// &[std::any::TypeId::of::<i64>(), std::any::TypeId::of::<bool>()],
/// // Fixed closure signature
/// |engine, lib, args| {
/// // 'args' is guaranteed to be the right length and of the correct types
@ -432,7 +442,7 @@ impl Module {
/// // Since it is a primary type, it can also be cheaply copied
/// let double = args[1].clone().cast::<bool>();
/// // Get a mutable reference to the first argument.
/// let x = args[0].downcast_mut::<i64>().unwrap();
/// let mut x = args[0].write_lock::<i64>().unwrap();
///
/// let orig = *x;
///
@ -443,7 +453,7 @@ impl Module {
/// Ok(orig) // return Result<T, Box<EvalAltResult>>
/// });
///
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_raw_fn<T: Variant + Clone>(
&mut self,
@ -468,7 +478,7 @@ impl Module {
///
/// let mut module = Module::new();
/// let hash = module.set_fn_0("calc", || Ok(42_i64));
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_0<T: Variant + Clone>(
&mut self,
@ -491,7 +501,7 @@ impl Module {
///
/// let mut module = Module::new();
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_1<A: Variant + Clone, T: Variant + Clone>(
&mut self,
@ -516,7 +526,7 @@ impl Module {
///
/// let mut module = Module::new();
/// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_1_mut<A: Variant + Clone, T: Variant + Clone>(
&mut self,
@ -524,7 +534,7 @@ impl Module {
func: impl Fn(&mut A) -> FuncReturn<T> + SendSync + 'static,
) -> u64 {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from)
func(&mut args[0].write_lock::<A>().unwrap()).map(Dynamic::from)
};
let arg_types = [TypeId::of::<A>()];
self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f)))
@ -541,7 +551,7 @@ impl Module {
///
/// let mut module = Module::new();
/// let hash = module.set_getter_fn("value", |x: &mut i64| { Ok(*x) });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
#[cfg(not(feature = "no_object"))]
pub fn set_getter_fn<A: Variant + Clone, T: Variant + Clone>(
@ -565,7 +575,7 @@ impl Module {
/// let hash = module.set_fn_2("calc", |x: i64, y: ImmutableString| {
/// Ok(x + y.len() as i64)
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_2<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self,
@ -596,7 +606,7 @@ impl Module {
/// let hash = module.set_fn_2_mut("calc", |x: &mut i64, y: ImmutableString| {
/// *x += y.len() as i64; Ok(*x)
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_2_mut<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self,
@ -605,9 +615,9 @@ impl Module {
) -> u64 {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>();
let a = args[0].downcast_mut::<A>().unwrap();
let mut a = args[0].write_lock::<A>().unwrap();
func(a, b).map(Dynamic::from)
func(&mut a, b).map(Dynamic::from)
};
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f)))
@ -628,7 +638,7 @@ impl Module {
/// *x = y.len() as i64;
/// Ok(())
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
#[cfg(not(feature = "no_object"))]
pub fn set_setter_fn<A: Variant + Clone, B: Variant + Clone>(
@ -653,7 +663,7 @@ impl Module {
/// let hash = module.set_indexer_get_fn(|x: &mut i64, y: ImmutableString| {
/// Ok(*x + y.len() as i64)
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
@ -677,7 +687,7 @@ impl Module {
/// let hash = module.set_fn_3("calc", |x: i64, y: ImmutableString, z: i64| {
/// Ok(x + y.len() as i64 + z)
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_3<
A: Variant + Clone,
@ -714,7 +724,7 @@ impl Module {
/// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: ImmutableString, z: i64| {
/// *x += y.len() as i64 + z; Ok(*x)
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_3_mut<
A: Variant + Clone,
@ -729,9 +739,9 @@ impl Module {
let f = move |_: &Engine, _: &Module, 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();
let mut a = args[0].write_lock::<A>().unwrap();
func(a, b, c).map(Dynamic::from)
func(&mut a, b, c).map(Dynamic::from)
};
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f)))
@ -752,7 +762,7 @@ impl Module {
/// *x = y.len() as i64 + value;
/// Ok(())
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
@ -763,9 +773,9 @@ impl Module {
let f = move |_: &Engine, _: &Module, 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();
let mut a = args[0].write_lock::<A>().unwrap();
func(a, b, c).map(Dynamic::from)
func(&mut a, b, c).map(Dynamic::from)
};
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(
@ -796,8 +806,8 @@ impl Module {
/// Ok(())
/// }
/// );
/// assert!(module.contains_fn(hash_get));
/// assert!(module.contains_fn(hash_set));
/// assert!(module.contains_fn(hash_get, true));
/// assert!(module.contains_fn(hash_set, true));
/// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
@ -825,7 +835,7 @@ impl Module {
/// let hash = module.set_fn_4("calc", |x: i64, y: ImmutableString, z: i64, _w: ()| {
/// Ok(x + y.len() as i64 + z)
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_4<
A: Variant + Clone,
@ -869,7 +879,7 @@ impl Module {
/// let hash = module.set_fn_4_mut("calc", |x: &mut i64, y: ImmutableString, z: i64, _w: ()| {
/// *x += y.len() as i64 + z; Ok(*x)
/// });
/// assert!(module.contains_fn(hash));
/// assert!(module.contains_fn(hash, true));
/// ```
pub fn set_fn_4_mut<
A: Variant + Clone,
@ -886,9 +896,9 @@ impl Module {
let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>();
let d = mem::take(args[3]).cast::<D>();
let a = args[0].downcast_mut::<A>().unwrap();
let mut a = args[0].write_lock::<A>().unwrap();
func(a, b, c, d).map(Dynamic::from)
func(&mut a, b, c, d).map(Dynamic::from)
};
let arg_types = [
TypeId::of::<A>(),
@ -903,8 +913,14 @@ impl Module {
///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
/// It is also returned by the `set_fn_XXX` calls.
pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&Func> {
self.functions.get(&hash_fn).map(|(_, _, _, v)| v)
pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&Func> {
self.functions
.get(&hash_fn)
.and_then(|(_, access, _, f)| match access {
_ if !public_only => Some(f),
FnAccess::Public => Some(f),
FnAccess::Private => None,
})
}
/// Get a modules-qualified function.
@ -912,16 +928,8 @@ impl Module {
///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match
/// the hash calculated by `index_all_sub_modules`.
pub(crate) fn get_qualified_fn(
&self,
hash_qualified_fn: u64,
) -> Result<&Func, Box<EvalAltResult>> {
self.all_functions.get(&hash_qualified_fn).ok_or_else(|| {
Box::new(EvalAltResult::ErrorFunctionNotFound(
String::new(),
Position::none(),
))
})
pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&Func> {
self.all_functions.get(&hash_qualified_fn)
}
/// Merge another module into this module.

View File

@ -3,12 +3,12 @@
use crate::any::Dynamic;
use crate::calc_fn_hash;
use crate::engine::{
Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
};
use crate::fn_native::FnPtr;
use crate::module::Module;
use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST};
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::token::is_valid_identifier;
use crate::utils::StaticVec;
#[cfg(not(feature = "no_function"))]
@ -19,6 +19,7 @@ use crate::parser::CustomExpr;
use crate::stdlib::{
boxed::Box,
convert::TryFrom,
iter::empty,
string::{String, ToString},
vec,
@ -131,21 +132,18 @@ fn call_fn_with_constant_arguments(
state
.engine
.call_fn_raw(
&mut Scope::new(),
&mut Imports::new(),
.call_native_fn(
&mut Default::default(),
state.lib,
fn_name,
(hash_fn, 0),
hash_fn,
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
false,
false,
true,
None,
0,
)
.map(|(v, _)| Some(v))
.unwrap_or_else(|_| None)
.ok()
.map(|(v, _)| v)
}
/// Optimize a statement.
@ -184,6 +182,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
optimize_expr(expr, state),
optimize_stmt(x.1, state, true),
None,
x.3,
))),
},
// if expr { if_block } else { else_block }
@ -200,6 +199,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
Stmt::Noop(_) => None, // Noop -> no else block
stmt => Some(stmt),
},
x.3,
))),
},
// while expr { block }
@ -210,7 +210,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
Stmt::Noop(pos)
}
// while true { block } -> loop { block }
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(x.1, state, false))),
Expr::True(_) => Stmt::Loop(Box::new((optimize_stmt(x.1, state, false), x.2))),
// while expr { block }
expr => match optimize_stmt(x.1, state, false) {
// while expr { break; } -> { expr; }
@ -225,11 +225,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
Stmt::Block(Box::new((statements, pos)))
}
// while expr { block }
stmt => Stmt::While(Box::new((optimize_expr(expr, state), stmt))),
stmt => Stmt::While(Box::new((optimize_expr(expr, state), stmt, x.2))),
},
},
// loop { block }
Stmt::Loop(block) => match optimize_stmt(*block, state, false) {
Stmt::Loop(x) => match optimize_stmt(x.0, state, false) {
// loop { break; } -> Noop
Stmt::Break(pos) => {
// Only a single break statement
@ -237,23 +237,26 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
Stmt::Noop(pos)
}
// loop { block }
stmt => Stmt::Loop(Box::new(stmt)),
stmt => Stmt::Loop(Box::new((stmt, x.1))),
},
// for id in expr { block }
Stmt::For(x) => Stmt::For(Box::new((
x.0,
optimize_expr(x.1, state),
optimize_stmt(x.2, state, false),
x.3,
))),
// let id = expr;
Stmt::Let(x) if x.1.is_some() => {
Stmt::Let(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state)))))
}
Stmt::Let(x) if x.1.is_some() => Stmt::Let(Box::new((
x.0,
Some(optimize_expr(x.1.unwrap(), state)),
x.2,
))),
// let id;
stmt @ Stmt::Let(_) => stmt,
// import expr as id;
#[cfg(not(feature = "no_module"))]
Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1))),
Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1, x.2))),
// { block }
Stmt::Block(x) => {
let orig_len = x.0.len(); // Original number of statements in the block, for change detection
@ -266,7 +269,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
.map(|stmt| match stmt {
// Add constant into the state
Stmt::Const(v) => {
let ((name, pos), expr) = *v;
let ((name, pos), expr, _) = *v;
state.push_constant(&name, expr);
state.set_dirty();
Stmt::Noop(pos) // No need to keep constants
@ -366,9 +369,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
// expr;
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
// return expr;
Stmt::ReturnWithVal(x) if x.1.is_some() => {
Stmt::ReturnWithVal(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state)))))
}
Stmt::ReturnWithVal(x) if x.1.is_some() => Stmt::ReturnWithVal(Box::new((
x.0,
Some(optimize_expr(x.1.unwrap(), state)),
x.2,
))),
// All other statements - skip
stmt => stmt,
}
@ -410,8 +415,8 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// All other items can be thrown away.
state.set_dirty();
let pos = m.1;
m.0.into_iter().find(|((name, _), _)| name.as_str() == prop.as_str())
.map(|(_, expr)| expr.set_position(pos))
m.0.into_iter().find(|((name, _), _)| name == prop)
.map(|(_, mut expr)| { expr.set_position(pos); expr })
.unwrap_or_else(|| Expr::Unit(pos))
}
// lhs.rhs
@ -428,7 +433,9 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// Array literal where everything is pure - promote the indexed item.
// All other items can be thrown away.
state.set_dirty();
a.0.take(i.0 as usize).set_position(a.1)
let mut expr = a.0.remove(i.0 as usize);
expr.set_position(a.1);
expr
}
// map[string]
(Expr::Map(m), Expr::StringConstant(s)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
@ -437,7 +444,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
state.set_dirty();
let pos = m.1;
m.0.into_iter().find(|((name, _), _)| *name == s.0)
.map(|(_, expr)| expr.set_position(pos))
.map(|(_, mut expr)| { expr.set_position(pos); expr })
.unwrap_or_else(|| Expr::Unit(pos))
}
// string[int]
@ -485,7 +492,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
state.set_dirty();
let ch = a.0.to_string();
if b.0.iter().find(|((name, _), _)| name.as_str() == ch.as_str()).is_some() {
if b.0.iter().find(|((name, _), _)| name == &ch).is_some() {
Expr::True(a.1)
} else {
Expr::False(a.1)
@ -543,14 +550,19 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// Fn("...")
Expr::FnCall(x)
if x.1.is_none()
&& (x.0).0 == KEYWORD_FN_PTR
&& x.3.len() == 1
&& matches!(x.3[0], Expr::StringConstant(_))
if x.1.is_none()
&& (x.0).0 == KEYWORD_FN_PTR
&& x.3.len() == 1
&& matches!(x.3[0], Expr::StringConstant(_))
=> {
match &x.3[0] {
Expr::StringConstant(s) if is_valid_identifier(s.0.chars()) => Expr::FnPointer(s.clone()),
_ => Expr::FnCall(x)
if let Expr::StringConstant(s) = &x.3[0] {
if let Ok(fn_ptr) = FnPtr::try_from(s.0.as_str()) {
Expr::FnPointer(Box::new((fn_ptr.take_data().0, s.1)))
} else {
Expr::FnCall(x)
}
} else {
unreachable!()
}
}
@ -560,21 +572,17 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
&& state.optimization_level == OptimizationLevel::Full // full optimizations
&& x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants
=> {
let ((name, _, pos), _, _, args, def_value) = x.as_mut();
let ((name, _, _, pos), _, _, args, def_value) = x.as_mut();
// First search in functions lib (can override built-in)
// Cater for both normal function call style and method call style (one additional arguments)
#[cfg(not(feature = "no_function"))]
let _has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| {
let has_script_fn = cfg!(not(feature = "no_function")) && state.lib.iter_fn().find(|(_, _, _, f)| {
if !f.is_script() { return false; }
let fn_def = f.get_fn_def();
fn_def.name.as_str() == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
}).is_some();
#[cfg(feature = "no_function")]
let _has_script_fn: bool = false;
if _has_script_fn {
if has_script_fn {
// A script-defined function overrides the built-in function - do not make the call
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
return Expr::FnCall(x);
@ -624,7 +632,9 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
state.set_dirty();
// Replace constant with value
state.find_constant(&name).unwrap().clone().set_position(pos)
let mut expr = state.find_constant(&name).unwrap().clone();
expr.set_position(pos);
expr
}
// Custom syntax
@ -686,7 +696,7 @@ fn optimize(
match &stmt {
Stmt::Const(v) => {
// Load constants
let ((name, _), expr) = v.as_ref();
let ((name, _), expr, _) = v.as_ref();
state.push_constant(&name, expr.clone());
stmt // Keep it in the global scope
}
@ -734,11 +744,13 @@ pub fn optimize_into_ast(
_functions: Vec<ScriptFnDef>,
level: OptimizationLevel,
) -> AST {
#[cfg(feature = "no_optimize")]
const level: OptimizationLevel = OptimizationLevel::None;
let level = if cfg!(feature = "no_optimize") {
OptimizationLevel::None
} else {
level
};
#[cfg(not(feature = "no_function"))]
let lib = {
let lib = if cfg!(not(feature = "no_function")) {
let mut module = Module::new();
if !level.is_none() {
@ -753,6 +765,8 @@ pub fn optimize_into_ast(
access: fn_def.access,
body: Default::default(),
params: fn_def.params.clone(),
#[cfg(not(feature = "no_closure"))]
externals: fn_def.externals.clone(),
pos: fn_def.pos,
}
.into()
@ -798,11 +812,10 @@ pub fn optimize_into_ast(
}
module
} else {
Default::default()
};
#[cfg(feature = "no_function")]
let lib = Default::default();
AST::new(
match level {
OptimizationLevel::None => statements,

View File

@ -2,36 +2,29 @@ use crate::def_package;
use crate::module::FuncReturn;
use crate::parser::INT;
#[cfg(not(feature = "unchecked"))]
use crate::{result::EvalAltResult, token::Position};
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
#[cfg(not(feature = "no_float"))]
#[cfg(feature = "no_std")]
use num_traits::*;
#[cfg(not(feature = "unchecked"))]
use num_traits::{
identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl,
CheckedShr, CheckedSub,
};
use crate::stdlib::ops::{BitAnd, BitOr, BitXor};
#[cfg(feature = "no_std")]
#[cfg(not(feature = "no_float"))]
use num_traits::float::Float;
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
use crate::stdlib::ops::{Add, Div, Mul, Neg, Rem, Sub};
#[cfg(feature = "unchecked")]
use crate::stdlib::ops::{Shl, Shr};
#[cfg(not(feature = "unchecked"))]
use crate::stdlib::{boxed::Box, fmt::Display, format};
use crate::stdlib::{
boxed::Box,
fmt::Display,
format,
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub},
};
// Checked add
#[cfg(not(feature = "unchecked"))]
pub(crate) fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
pub fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
x.checked_add(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Addition overflow: {} + {}", x, y),
@ -40,8 +33,7 @@ pub(crate) fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
})
}
// Checked subtract
#[cfg(not(feature = "unchecked"))]
pub(crate) fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
pub fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
x.checked_sub(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Subtraction underflow: {} - {}", x, y),
@ -50,8 +42,7 @@ pub(crate) fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
})
}
// Checked multiply
#[cfg(not(feature = "unchecked"))]
pub(crate) fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
pub fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
x.checked_mul(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Multiplication overflow: {} * {}", x, y),
@ -60,8 +51,7 @@ pub(crate) fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
})
}
// Checked divide
#[cfg(not(feature = "unchecked"))]
pub(crate) fn div<T>(x: T, y: T) -> FuncReturn<T>
pub fn div<T>(x: T, y: T) -> FuncReturn<T>
where
T: Display + CheckedDiv + PartialEq + Zero,
{
@ -81,8 +71,7 @@ where
})
}
// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
#[cfg(not(feature = "unchecked"))]
pub(crate) fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
pub fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
x.checked_neg().ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Negation overflow: -{}", x),
@ -91,8 +80,7 @@ pub(crate) fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
})
}
// Checked absolute
#[cfg(not(feature = "unchecked"))]
pub(crate) fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> FuncReturn<T> {
pub 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() {
@ -107,32 +95,26 @@ pub(crate) fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> FuncRetu
}
}
// Unchecked add - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn add_u<T: Add>(x: T, y: T) -> FuncReturn<<T as Add>::Output> {
Ok(x + y)
}
// Unchecked subtract - may panic on underflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn sub_u<T: Sub>(x: T, y: T) -> FuncReturn<<T as Sub>::Output> {
Ok(x - y)
}
// Unchecked multiply - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
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
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn div_u<T: Div>(x: T, y: T) -> FuncReturn<<T as Div>::Output> {
Ok(x / y)
}
// Unchecked negative - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn neg_u<T: Neg>(x: T) -> FuncReturn<<T as Neg>::Output> {
Ok(-x)
}
// Unchecked absolute - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn abs_u<T>(x: T) -> FuncReturn<<T as Neg>::Output>
where
T: Neg + PartialOrd + Default + Into<<T as Neg>::Output>,
@ -155,8 +137,7 @@ fn binary_xor<T: BitXor>(x: T, y: T) -> FuncReturn<<T as BitXor>::Output> {
Ok(x ^ y)
}
// Checked left-shift
#[cfg(not(feature = "unchecked"))]
pub(crate) fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
pub fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
// Cannot shift by a negative number of bits
if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -173,8 +154,7 @@ pub(crate) fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
})
}
// Checked right-shift
#[cfg(not(feature = "unchecked"))]
pub(crate) fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
pub fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
// Cannot shift by a negative number of bits
if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -191,18 +171,15 @@ pub(crate) fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
})
}
// Unchecked left-shift - may panic if shifting by a negative number of bits
#[cfg(feature = "unchecked")]
pub(crate) fn shl_u<T: Shl<T>>(x: T, y: T) -> FuncReturn<<T as Shl<T>>::Output> {
pub 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
#[cfg(feature = "unchecked")]
pub(crate) fn shr_u<T: Shr<T>>(x: T, y: T) -> FuncReturn<<T as Shr<T>>::Output> {
pub fn shr_u<T: Shr<T>>(x: T, y: T) -> FuncReturn<<T as Shr<T>>::Output> {
Ok(x.shr(y))
}
// Checked modulo
#[cfg(not(feature = "unchecked"))]
pub(crate) fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> {
pub fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> {
x.checked_rem(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Modulo division by zero or overflow: {} % {}", x, y),
@ -211,62 +188,58 @@ pub(crate) fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> {
})
}
// Unchecked modulo - may panic if dividing by zero
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn modulo_u<T: Rem>(x: T, y: T) -> FuncReturn<<T as Rem>::Output> {
Ok(x % y)
}
// Checked power
#[cfg(not(feature = "unchecked"))]
pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
#[cfg(not(feature = "only_i32"))]
if y > (u32::MAX as INT) {
Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Integer raised to too large an index: {} ~ {}", x, y),
Position::none(),
)))
} else if y < 0 {
Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Integer raised to a negative index: {} ~ {}", x, y),
Position::none(),
)))
} else {
x.checked_pow(y as u32).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y),
pub fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
if cfg!(not(feature = "only_i32")) {
if y > (u32::MAX as INT) {
Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Integer raised to too large an index: {} ~ {}", x, y),
Position::none(),
))
})
}
#[cfg(feature = "only_i32")]
if y < 0 {
Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Integer raised to a negative index: {} ~ {}", x, y),
Position::none(),
)))
} else {
x.checked_pow(y as u32).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y),
)))
} else if y < 0 {
Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Integer raised to a negative index: {} ~ {}", x, y),
Position::none(),
))
})
)))
} else {
x.checked_pow(y as u32).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y),
Position::none(),
))
})
}
} else {
if y < 0 {
Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Integer raised to a negative index: {} ~ {}", x, y),
Position::none(),
)))
} else {
x.checked_pow(y as u32).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y),
Position::none(),
))
})
}
}
}
// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
#[cfg(feature = "unchecked")]
pub(crate) fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> {
pub 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"))]
pub(crate) fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn<FLOAT> {
pub fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn<FLOAT> {
Ok(x.powf(y))
}
// Checked power
#[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "unchecked"))]
pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
pub fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
// Raise to power that is larger than an i32
if y > (i32::MAX as INT) {
return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -278,9 +251,8 @@ pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
Ok(x.powi(y as i32))
}
// Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
#[cfg(feature = "unchecked")]
#[cfg(not(feature = "no_float"))]
pub(crate) fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
pub fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
Ok(x.powi(y as i32))
}
@ -309,11 +281,8 @@ macro_rules! reg_sign {
}
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
#[cfg(not(feature = "unchecked"))]
{
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
if cfg!(not(feature = "unchecked")) {
// Checked basic arithmetic
reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64);
@ -324,8 +293,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
{
if cfg!(not(target_arch = "wasm32")) {
reg_op!(lib, "+", add, i128, u128);
reg_op!(lib, "-", sub, i128, u128);
reg_op!(lib, "*", mul, i128, u128);
@ -337,8 +305,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
}
}
#[cfg(feature = "unchecked")]
{
if cfg!(feature = "unchecked") {
// Unchecked basic arithmetic
reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64);
@ -349,8 +316,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
{
if cfg!(not(target_arch = "wasm32")) {
reg_op!(lib, "+", add_u, i128, u128);
reg_op!(lib, "-", sub_u, i128, u128);
reg_op!(lib, "*", mul_u, i128, u128);
@ -364,13 +330,13 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
reg_sign!(lib, "sign", INT, i8, i16, i32, i64);
#[cfg(not(target_arch = "wasm32"))]
reg_sign!(lib, "sign", INT, i128);
if cfg!(not(target_arch = "wasm32")) {
reg_sign!(lib, "sign", INT, i128);
}
}
// Basic arithmetic for floating-point - no need to check
#[cfg(not(feature = "no_float"))]
{
if cfg!(not(feature = "no_float")) {
reg_op!(lib, "+", add_u, f32);
reg_op!(lib, "-", sub_u, f32);
reg_op!(lib, "*", mul_u, f32);
@ -379,15 +345,12 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
reg_sign!(lib, "sign", f64, f64);
}
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
{
if cfg!(not(target_arch = "wasm32")) {
reg_op!(lib, "|", binary_or, i128, u128);
reg_op!(lib, "&", binary_and, i128, u128);
reg_op!(lib, "^", binary_xor, i128, u128);
@ -397,12 +360,11 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
#[cfg(not(feature = "no_float"))]
{
// Checked power
#[cfg(not(feature = "unchecked"))]
lib.set_fn_2("~", pow_f_i);
// Unchecked power
#[cfg(feature = "unchecked")]
lib.set_fn_2("~", pow_f_i_u);
if cfg!(not(feature = "unchecked")) {
lib.set_fn_2("~", pow_f_i);
} else {
lib.set_fn_2("~", pow_f_i_u);
}
// Floating-point modulo and power
reg_op!(lib, "%", modulo_u, f32);
@ -413,19 +375,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
}
// Checked unary
#[cfg(not(feature = "unchecked"))]
{
if cfg!(not(feature = "unchecked")) {
reg_unary!(lib, "-", neg, INT);
reg_unary!(lib, "abs", abs, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
reg_unary!(lib, "-", neg, i8, i16, i32, i64);
reg_unary!(lib, "abs", abs, i8, i16, i32, i64);
#[cfg(not(target_arch = "wasm32"))]
{
if cfg!(not(target_arch = "wasm32")) {
reg_unary!(lib, "-", neg, i128);
reg_unary!(lib, "abs", abs, i128);
}
@ -433,19 +391,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
}
// Unchecked unary
#[cfg(feature = "unchecked")]
{
if cfg!(feature = "unchecked") {
reg_unary!(lib, "-", neg_u, INT);
reg_unary!(lib, "abs", abs_u, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
reg_unary!(lib, "-", neg_u, i8, i16, i32, i64);
reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64);
#[cfg(not(target_arch = "wasm32"))]
{
if cfg!(not(target_arch = "wasm32")) {
reg_unary!(lib, "-", neg_u, i128);
reg_unary!(lib, "abs", abs_u, i128);
}

View File

@ -3,6 +3,7 @@
use crate::any::{Dynamic, Variant};
use crate::def_package;
use crate::engine::{Array, Engine};
use crate::fn_native::FnPtr;
use crate::module::{FuncReturn, Module};
use crate::parser::{ImmutableString, INT};
@ -34,7 +35,7 @@ fn pad<T: Variant + Clone>(
_: &Module,
args: &mut [&mut Dynamic],
) -> FuncReturn<()> {
let len = *args[1].downcast_ref::<INT>().unwrap();
let len = *args[1].read_lock::<INT>().unwrap();
// Check if array will be over max size limit
#[cfg(not(feature = "unchecked"))]
@ -52,7 +53,7 @@ fn pad<T: Variant + Clone>(
if len > 0 {
let item = args[2].clone();
let list = args[0].downcast_mut::<Array>().unwrap();
let mut list = args[0].write_lock::<Array>().unwrap();
if len as usize > list.len() {
list.resize(len as usize, item);
@ -82,11 +83,10 @@ macro_rules! reg_pad {
};
}
#[cfg(not(feature = "no_index"))]
def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ());
reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ());
reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, Array, ());
reg_op!(lib, "push", push, INT, bool, char, ImmutableString, FnPtr, Array, ());
reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, FnPtr, Array, ());
reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, FnPtr, Array, ());
lib.set_fn_2_mut("append", |x: &mut Array, y: Array| {
x.extend(y);
@ -104,15 +104,12 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
},
);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
reg_op!(lib, "push", push, i8, u8, i16, u16, i32, i64, u32, u64);
reg_pad!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64);
reg_tri!(lib, "insert", ins, i8, u8, i16, u16, i32, i64, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
{
if cfg!(not(target_arch = "wasm32")) {
reg_op!(lib, "push", push, i128, u128);
reg_pad!(lib, "pad", pad, i128, u128);
reg_tri!(lib, "insert", ins, i128, u128);

View File

@ -73,9 +73,7 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
reg_range::<INT>(lib);
lib.set_fn_2("range", get_range::<INT>);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
macro_rules! reg_range {
($lib:expr, $x:expr, $( $y:ty ),*) => (
$(
@ -87,16 +85,15 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
reg_range!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
reg_range!(lib, "range", i128, u128);
if cfg!(not(target_arch = "wasm32")) {
reg_range!(lib, "range", i128, u128);
}
}
reg_step::<INT>(lib);
lib.set_fn_3("range", get_step_range::<INT>);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
macro_rules! reg_step {
($lib:expr, $x:expr, $( $y:ty ),*) => (
$(
@ -108,7 +105,8 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
reg_step!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
reg_step!(lib, "range", i128, u128);
if cfg!(not(target_arch = "wasm32")) {
reg_step!(lib, "range", i128, u128);
}
}
});

View File

@ -33,9 +33,7 @@ macro_rules! reg_op {
}
def_package!(crate:LogicPackage:"Logical operators.", lib, {
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64);
@ -43,8 +41,7 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, {
reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
{
if cfg!(not(target_arch = "wasm32")) {
reg_op!(lib, "<", lt, i128, u128);
reg_op!(lib, "<=", lte, i128, u128);
reg_op!(lib, ">", gt, i128, u128);

View File

@ -1,29 +1,24 @@
#![cfg(not(feature = "no_object"))]
use crate::any::Dynamic;
use crate::def_package;
use crate::engine::Map;
use crate::module::FuncReturn;
use crate::parser::{ImmutableString, INT};
#[cfg(not(feature = "no_index"))]
use crate::{any::Dynamic, module::FuncReturn};
#[cfg(not(feature = "no_index"))]
use crate::stdlib::vec::Vec;
#[cfg(not(feature = "no_index"))]
fn map_get_keys(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
Ok(map.iter().map(|(k, _)| k.clone().into()).collect())
}
#[cfg(not(feature = "no_index"))]
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, {
lib.set_fn_2_mut(
"has",
|map: &mut Map, prop: ImmutableString| Ok(map.contains_key(prop.as_str())),
|map: &mut Map, prop: ImmutableString| Ok(map.contains_key(&prop)),
);
lib.set_fn_1_mut("len", |map: &mut Map| Ok(map.len() as INT));
lib.set_fn_1_mut("clear", |map: &mut Map| {
@ -32,7 +27,7 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
});
lib.set_fn_2_mut(
"remove",
|x: &mut Map, name: ImmutableString| Ok(x.remove(name.as_str()).unwrap_or_else(|| ().into())),
|x: &mut Map, name: ImmutableString| Ok(x.remove(&name).unwrap_or_else(|| ().into())),
);
lib.set_fn_2_mut(
"mixin",
@ -47,7 +42,7 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
"fill_with",
|map1: &mut Map, map2: Map| {
map2.into_iter().for_each(|(key, value)| {
if !map1.contains_key(key.as_str()) {
if !map1.contains_key(&key) {
map1.insert(key, value);
}
});
@ -74,9 +69,11 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
);
// Register map access functions
#[cfg(not(feature = "no_index"))]
lib.set_fn_1_mut("keys", map_get_keys);
if cfg!(not(feature = "no_index")) {
lib.set_fn_1_mut("keys", map_get_keys);
}
#[cfg(not(feature = "no_index"))]
lib.set_fn_1_mut("values", map_get_values);
if cfg!(not(feature = "no_index")) {
lib.set_fn_1_mut("values", map_get_values);
}
});

View File

@ -5,22 +5,20 @@ use crate::parser::INT;
use crate::parser::FLOAT;
#[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "unchecked"))]
use crate::{result::EvalAltResult, token::Position};
#[cfg(not(feature = "no_float"))]
#[cfg(feature = "no_std")]
use num_traits::*;
#[cfg(not(feature = "no_float"))]
use num_traits::float::Float;
#[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "unchecked"))]
use crate::stdlib::{boxed::Box, format};
#[allow(dead_code)]
#[cfg(feature = "only_i32")]
#[cfg(not(feature = "unchecked"))]
pub const MAX_INT: INT = i32::MAX;
#[allow(dead_code)]
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "unchecked"))]
pub const MAX_INT: INT = i64::MAX;
def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
@ -69,9 +67,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
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"))]
{
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
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));
@ -81,8 +77,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
lib.set_fn_1("to_float", |x: i64| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: u64| Ok(x as FLOAT));
#[cfg(not(target_arch = "wasm32"))]
{
if cfg!(not(target_arch = "wasm32")) {
lib.set_fn_1("to_float", |x: i128| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: u128| Ok(x as FLOAT));
}
@ -91,28 +86,25 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
lib.set_fn_1("to_int", |ch: char| Ok(ch as INT));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
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"))]
{
if cfg!(not(feature = "only_i32")) {
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")]
lib.set_fn_1("to_int", |x: u32| Ok(x as INT));
if cfg!(feature = "only_i64") {
lib.set_fn_1("to_int", |x: u32| Ok(x as INT));
}
}
#[cfg(not(feature = "no_float"))]
{
#[cfg(not(feature = "unchecked"))]
{
if cfg!(not(feature = "unchecked")) {
lib.set_fn_1(
"to_int",
|x: f32| {
@ -141,8 +133,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
);
}
#[cfg(feature = "unchecked")]
{
if cfg!(feature = "unchecked") {
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

@ -61,14 +61,15 @@ impl PackagesCollection {
self.0.insert(0, package);
}
/// Does the specified function hash key exist in the `PackagesCollection`?
pub fn contains_fn(&self, hash: u64) -> bool {
self.0.iter().any(|p| p.contains_fn(hash))
#[allow(dead_code)]
pub fn contains_fn(&self, hash: u64, public_only: bool) -> bool {
self.0.iter().any(|p| p.contains_fn(hash, public_only))
}
/// Get specified function via its hash key.
pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
pub fn get_fn(&self, hash: u64, public_only: bool) -> Option<&CallableFunction> {
self.0
.iter()
.map(|p| p.get_fn(hash))
.map(|p| p.get_fn(hash, public_only))
.find(|f| f.is_some())
.flatten()
}

View File

@ -21,7 +21,7 @@ fn to_debug<T: Debug>(x: &mut T) -> FuncReturn<ImmutableString> {
Ok(format!("{:?}", x).into())
}
fn to_string<T: Display>(x: &mut T) -> FuncReturn<ImmutableString> {
Ok(format!("{}", x).into())
Ok(x.to_string().into())
}
#[cfg(not(feature = "no_object"))]
fn format_map(x: &mut Map) -> FuncReturn<ImmutableString> {
@ -48,9 +48,7 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, ImmutableString);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
reg_op!(lib, KEYWORD_PRINT, to_string, i8, u8, i16, u16, i32, u32);
reg_op!(lib, FN_TO_STRING, to_string, i8, u8, i16, u16, i32, u32);
reg_op!(lib, KEYWORD_DEBUG, to_debug, i8, u8, i16, u16, i32, u32);
@ -58,8 +56,7 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
reg_op!(lib, FN_TO_STRING, to_string, i64, u64);
reg_op!(lib, KEYWORD_DEBUG, to_debug, i64, u64);
#[cfg(not(target_arch = "wasm32"))]
{
if cfg!(not(target_arch = "wasm32")) {
reg_op!(lib, KEYWORD_PRINT, to_string, i128, u128);
reg_op!(lib, FN_TO_STRING, to_string, i128, u128);
reg_op!(lib, KEYWORD_DEBUG, to_debug, i128, u128);

View File

@ -1,6 +1,7 @@
use crate::any::Dynamic;
use crate::def_package;
use crate::engine::Engine;
use crate::fn_native::FnPtr;
use crate::module::{FuncReturn, Module};
use crate::parser::{ImmutableString, INT};
use crate::utils::StaticVec;
@ -15,7 +16,7 @@ use crate::stdlib::{
any::TypeId,
boxed::Box,
fmt::Display,
format,
format, mem,
string::{String, ToString},
vec::Vec,
};
@ -88,20 +89,17 @@ macro_rules! reg_op {
}
def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, {
reg_op!(lib, "+", append, INT, bool, char);
reg_op!(lib, "+", append, INT, bool, char, FnPtr);
lib.set_fn_2( "+", |x: ImmutableString, _: ()| Ok(x));
reg_op!(lib, "+", prepend, INT, bool, char);
reg_op!(lib, "+", prepend, INT, bool, char, FnPtr);
lib.set_fn_2("+", |_: (), y: ImmutableString| Ok(y));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
reg_op!(lib, "+", append, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op!(lib, "+", prepend, i8, u8, i16, u16, i32, i64, u32, u64);
#[cfg(not(target_arch = "wasm32"))]
{
if cfg!(not(target_arch = "wasm32")) {
reg_op!(lib, "+", append, i128, u128);
reg_op!(lib, "+", prepend, i128, u128);
}
@ -228,7 +226,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
"pad",
&[TypeId::of::<ImmutableString>(), TypeId::of::<INT>(), TypeId::of::<char>()],
|_engine: &Engine, _: &Module, args: &mut [&mut Dynamic]| {
let len = *args[1].downcast_ref::< INT>().unwrap();
let len = *args[1].read_lock::< INT>().unwrap();
// Check if string will be over max size limit
#[cfg(not(feature = "unchecked"))]
@ -242,8 +240,8 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
}
if len > 0 {
let ch = *args[2].downcast_ref::< char>().unwrap();
let s = args[0].downcast_mut::<ImmutableString>().unwrap();
let ch = mem::take(args[2]).cast::<char>();
let mut s = args[0].write_lock::<ImmutableString>().unwrap();
let orig_len = s.chars().count();

File diff suppressed because it is too large Load Diff

View File

@ -39,8 +39,8 @@ pub enum EvalAltResult {
/// An error has occurred inside a called function.
/// Wrapped values are the name of the function and the interior error.
ErrorInFunctionCall(String, Box<EvalAltResult>, Position),
/// Access to `this` that is not bounded.
ErrorUnboundedThis(Position),
/// Access to `this` that is not bound.
ErrorUnboundThis(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.
@ -69,6 +69,8 @@ pub enum EvalAltResult {
ErrorVariableNotFound(String, Position),
/// Usage of an unknown module. Wrapped value is the name of the module.
ErrorModuleNotFound(String, Position),
/// Data race detected when accessing a variable. Wrapped value is the name of the variable.
ErrorDataRace(String, Position),
/// Assignment to an inappropriate LHS (left-hand-side) expression.
ErrorAssignmentToUnknownLHS(Position),
/// Assignment to a constant variable.
@ -112,7 +114,7 @@ impl EvalAltResult {
Self::ErrorParsing(p, _) => p.desc(),
Self::ErrorInFunctionCall(_, _, _) => "Error in called function",
Self::ErrorFunctionNotFound(_, _) => "Function not found",
Self::ErrorUnboundedThis(_) => "'this' is not bounded",
Self::ErrorUnboundThis(_) => "'this' is not bound",
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
Self::ErrorCharMismatch(_) => "Character expected",
Self::ErrorNumericIndexExpr(_) => {
@ -136,7 +138,8 @@ impl EvalAltResult {
Self::ErrorLogicGuard(_) => "Boolean value expected",
Self::ErrorFor(_) => "For loop expects an array, object map, or range",
Self::ErrorVariableNotFound(_, _) => "Variable not found",
Self::ErrorModuleNotFound(_, _) => "module not found",
Self::ErrorModuleNotFound(_, _) => "Module not found",
Self::ErrorDataRace(_, _) => "Data race detected when accessing variable",
Self::ErrorAssignmentToUnknownLHS(_) => {
"Assignment to an unsupported left-hand side expression"
}
@ -180,14 +183,15 @@ impl fmt::Display for EvalAltResult {
Self::ErrorFunctionNotFound(s, _)
| Self::ErrorVariableNotFound(s, _)
| Self::ErrorDataRace(s, _)
| Self::ErrorModuleNotFound(s, _) => write!(f, "{}: '{}'", desc, s)?,
Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{} {}", desc, s)?,
Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{}", s)?,
Self::ErrorIndexingType(_, _)
| Self::ErrorNumericIndexExpr(_)
| Self::ErrorStringIndexExpr(_)
| Self::ErrorUnboundedThis(_)
| Self::ErrorUnboundThis(_)
| Self::ErrorImportExpr(_)
| Self::ErrorLogicGuard(_)
| Self::ErrorFor(_)
@ -276,7 +280,7 @@ impl EvalAltResult {
Self::ErrorParsing(_, pos)
| Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorUnboundedThis(pos)
| Self::ErrorUnboundThis(pos)
| Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos)
| Self::ErrorArrayBounds(_, _, pos)
@ -289,6 +293,7 @@ impl EvalAltResult {
| Self::ErrorFor(pos)
| Self::ErrorVariableNotFound(_, pos)
| Self::ErrorModuleNotFound(_, pos)
| Self::ErrorDataRace(_, pos)
| Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorAssignmentToConstant(_, pos)
| Self::ErrorMismatchOutputType(_, _, pos)
@ -316,7 +321,7 @@ impl EvalAltResult {
Self::ErrorParsing(_, pos)
| Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorUnboundedThis(pos)
| Self::ErrorUnboundThis(pos)
| Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos)
| Self::ErrorArrayBounds(_, _, pos)
@ -329,6 +334,7 @@ impl EvalAltResult {
| Self::ErrorFor(pos)
| Self::ErrorVariableNotFound(_, pos)
| Self::ErrorModuleNotFound(_, pos)
| Self::ErrorDataRace(_, pos)
| Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorAssignmentToConstant(_, pos)
| Self::ErrorMismatchOutputType(_, _, pos)

View File

@ -316,6 +316,14 @@ impl<'a> Scope<'a> {
})
}
/// Get an entry in the Scope, starting from the last.
pub(crate) fn get_entry(&self, name: &str) -> Option<&Entry> {
self.0
.iter()
.rev()
.find(|Entry { name: key, .. }| name == key)
}
/// Get the value of an entry in the Scope, starting from the last.
///
/// # Examples
@ -329,11 +337,8 @@ impl<'a> Scope<'a> {
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ```
pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
self.0
.iter()
.rev()
.find(|Entry { name: key, .. }| name == key)
.and_then(|Entry { value, .. }| value.downcast_ref::<T>().cloned())
self.get_entry(name)
.and_then(|Entry { value, .. }| value.clone().clone_inner_data::<T>())
}
/// Update the value of the named entry.
@ -384,13 +389,30 @@ impl<'a> Scope<'a> {
self
}
/// Clone the Scope, keeping only the last instances of each variable name.
/// Shadowed variables are omitted in the copy.
pub(crate) fn flatten_clone(&self) -> Self {
let mut entries: Vec<Entry> = Default::default();
self.0.iter().rev().for_each(|entry| {
if entries
.iter()
.find(|Entry { name, .. }| &entry.name == name)
.is_none()
{
entries.push(entry.clone());
}
});
Self(entries)
}
/// Get an iterator to entries in the Scope.
#[cfg(not(feature = "no_module"))]
pub(crate) fn into_iter(self) -> impl Iterator<Item = Entry<'a>> {
self.0.into_iter()
}
/// Get an iterator to entries in the Scope.
/// Get an iterator to entries in the Scope in reverse order.
pub(crate) fn to_iter(&self) -> impl Iterator<Item = &Entry> {
self.0.iter().rev() // Always search a Scope in reverse order
}
@ -411,13 +433,20 @@ impl<'a> Scope<'a> {
///
/// let (name, value) = iter.next().unwrap();
/// assert_eq!(name, "x");
/// assert_eq!(value.clone().cast::<i64>(), 42);
/// assert_eq!(value.cast::<i64>(), 42);
///
/// let (name, value) = iter.next().unwrap();
/// assert_eq!(name, "foo");
/// assert_eq!(value.clone().cast::<String>(), "hello");
/// assert_eq!(value.cast::<String>(), "hello");
/// ```
pub fn iter(&self) -> impl Iterator<Item = (&str, &Dynamic)> {
pub fn iter(&self) -> impl Iterator<Item = (&str, Dynamic)> {
self.iter_raw()
.map(|(name, value)| (name, value.clone().clone_inner_data().unwrap()))
}
/// Get an iterator to entries in the Scope.
/// Shared values are not expanded.
pub fn iter_raw(&self) -> impl Iterator<Item = (&str, &Dynamic)> {
self.0
.iter()
.map(|Entry { name, value, .. }| (name.as_ref(), value))

View File

@ -177,6 +177,9 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
Union::Variant(value) if value.is::<u128>() => self.deserialize_u128(visitor),
Union::Variant(_) => self.type_error(),
#[cfg(not(feature = "no_closure"))]
Union::Shared(_) => self.type_error(),
}
}

View File

@ -18,9 +18,9 @@ impl Engine {
///
/// When searching for functions, packages loaded later are preferred.
/// In other words, loaded packages are searched in reverse order.
pub fn load_package(&mut self, package: PackageLibrary) -> &mut Self {
pub fn load_package(&mut self, package: impl Into<PackageLibrary>) -> &mut Self {
// Push the package to the top - packages are searched in reverse order
self.packages.push(package);
self.packages.push(package.into());
self
}

View File

@ -2,7 +2,7 @@
use crate::engine::{
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY,
KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
KEYWORD_IS_SHARED, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
};
use crate::error::LexError;
@ -21,7 +21,6 @@ use crate::stdlib::{
iter::Peekable,
str::{Chars, FromStr},
string::{String, ToString},
vec::Vec,
};
type LERR = LexError;
@ -30,8 +29,11 @@ pub type TokenStream<'a, 't> = Peekable<TokenIterator<'a, 't>>;
/// A location (line number + character position) in the input script.
///
/// In order to keep footprint small, both line number and character position have 16-bit unsigned resolution,
/// meaning they go up to a maximum of 65,535 lines and characters per line.
/// # Limitations
///
/// In order to keep footprint small, both line number and character position have 16-bit resolution,
/// meaning they go up to a maximum of 65,535 lines and 65,535 characters per line.
///
/// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position {
@ -43,6 +45,13 @@ pub struct Position {
impl Position {
/// Create a new `Position`.
///
/// `line` must not be zero.
/// If `position` is zero, then it is at the beginning of a line.
///
/// # Panics
///
/// Panics if `line` is zero.
pub fn new(line: u16, position: u16) -> Self {
assert!(line != 0, "line cannot be zero");
@ -52,7 +61,7 @@ impl Position {
}
}
/// Get the line number (1-based), or `None` if no position.
/// Get the line number (1-based), or `None` if there is no position.
pub fn line(&self) -> Option<usize> {
if self.is_none() {
None
@ -85,7 +94,6 @@ impl Position {
/// # Panics
///
/// Panics if already at beginning of a line - cannot rewind to a previous line.
///
pub(crate) fn rewind(&mut self) {
assert!(!self.is_none(), "cannot rewind Position::none");
assert!(self.pos > 0, "cannot rewind at position 0");
@ -104,7 +112,7 @@ impl Position {
}
/// Create a `Position` representing no position.
pub(crate) fn none() -> Self {
pub fn none() -> Self {
Self { line: 0, pos: 0 }
}
@ -146,9 +154,9 @@ impl fmt::Debug for Position {
pub enum Token {
/// An `INT` constant.
IntegerConstant(INT),
/// A `FLOAT` constaint.
/// A `FLOAT` constant.
///
/// Never appears under the `no_float` feature.
/// Reserved under the `no_float` feature.
#[cfg(not(feature = "no_float"))]
FloatConstant(FLOAT),
/// An identifier.
@ -249,7 +257,7 @@ pub enum Token {
And,
/// `fn`
///
/// Never appears under the `no_function` feature.
/// Reserved under the `no_function` feature.
#[cfg(not(feature = "no_function"))]
Fn,
/// `continue`
@ -284,22 +292,22 @@ pub enum Token {
PowerOfAssign,
/// `private`
///
/// Never appears under the `no_function` feature.
/// Reserved under the `no_function` feature.
#[cfg(not(feature = "no_function"))]
Private,
/// `import`
///
/// Never appears under the `no_module` feature.
/// Reserved under the `no_module` feature.
#[cfg(not(feature = "no_module"))]
Import,
/// `export`
///
/// Never appears under the `no_module` feature.
/// Reserved under the `no_module` feature.
#[cfg(not(feature = "no_module"))]
Export,
/// `as`
///
/// Never appears under the `no_module` feature.
/// Reserved under the `no_module` feature.
#[cfg(not(feature = "no_module"))]
As,
/// A lexer error.
@ -492,11 +500,16 @@ impl Token {
#[cfg(feature = "no_module")]
"import" | "export" | "as" => Reserved(syntax.into()),
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" => {
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public"
| "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with"
| "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try"
| "catch" | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async"
| "await" | "yield" => Reserved(syntax.into()),
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_SHARED | KEYWORD_THIS => {
Reserved(syntax.into())
}
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()),
_ => return None,
})
@ -640,7 +653,7 @@ impl Token {
}
}
/// Is this token a standard keyword?
/// Is this token an active standard keyword?
pub fn is_keyword(&self) -> bool {
use Token::*;
@ -666,6 +679,15 @@ impl Token {
}
}
/// Convert a token into a function name, if possible.
pub(crate) fn into_function_name_for_override(self) -> Result<String, Self> {
match self {
Self::Reserved(s) if can_override_keyword(&s) => Ok(s),
Self::Custom(s) | Self::Identifier(s) if is_valid_identifier(s.chars()) => Ok(s),
_ => Err(self),
}
}
/// Is this token a custom keyword?
pub fn is_custom(&self) -> bool {
match self {
@ -726,8 +748,8 @@ pub fn parse_string_literal(
pos: &mut Position,
enclosing_char: char,
) -> Result<String, (LexError, Position)> {
let mut result = Vec::new();
let mut escape = String::with_capacity(12);
let mut result: StaticVec<char> = Default::default();
let mut escape: StaticVec<char> = Default::default();
loop {
let next_char = stream.get_next().ok_or((LERR::UnterminatedString, *pos))?;
@ -766,8 +788,8 @@ pub fn parse_string_literal(
// \x??, \u????, \U????????
ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => {
let mut seq = escape.clone();
seq.push(ch);
escape.clear();
seq.push(ch);
let mut out_val: u32 = 0;
let len = match ch {
@ -778,23 +800,31 @@ pub fn parse_string_literal(
};
for _ in 0..len {
let c = stream
.get_next()
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?;
let c = stream.get_next().ok_or_else(|| {
(
LERR::MalformedEscapeSequence(seq.iter().cloned().collect()),
*pos,
)
})?;
seq.push(c);
pos.advance();
out_val *= 16;
out_val += c
.to_digit(16)
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?;
out_val += c.to_digit(16).ok_or_else(|| {
(
LERR::MalformedEscapeSequence(seq.iter().cloned().collect()),
*pos,
)
})?;
}
result.push(
char::from_u32(out_val)
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq), *pos))?,
);
result.push(char::from_u32(out_val).ok_or_else(|| {
(
LERR::MalformedEscapeSequence(seq.into_iter().collect()),
*pos,
)
})?);
}
// \{enclosing_char} - escaped
@ -807,7 +837,12 @@ pub fn parse_string_literal(
ch if enclosing_char == ch && escape.is_empty() => break,
// Unknown escape sequence
_ if !escape.is_empty() => return Err((LERR::MalformedEscapeSequence(escape), *pos)),
_ if !escape.is_empty() => {
return Err((
LERR::MalformedEscapeSequence(escape.into_iter().collect()),
*pos,
))
}
// Cannot have new-lines inside string literals
'\n' => {
@ -962,7 +997,7 @@ fn get_next_token_inner(
// digit ...
('0'..='9', _) => {
let mut result = Vec::new();
let mut result: StaticVec<char> = Default::default();
let mut radix_base: Option<u32> = None;
result.push(c);
@ -1083,7 +1118,7 @@ fn get_next_token_inner(
|err| (Token::LexError(Box::new(err.0)), err.1),
|result| {
let mut chars = result.chars();
let first = chars.next();
let first = chars.next().unwrap();
if chars.next().is_some() {
(
@ -1091,10 +1126,7 @@ fn get_next_token_inner(
start_pos,
)
} else {
(
Token::CharConstant(first.expect("should be Some")),
start_pos,
)
(Token::CharConstant(first), start_pos)
}
},
))
@ -1367,7 +1399,7 @@ fn get_identifier(
start_pos: Position,
first_char: char,
) -> Option<(Token, Position)> {
let mut result = Vec::new();
let mut result: StaticVec<_> = Default::default();
result.push(first_char);
while let Some(next_char) = stream.peek_next() {
@ -1382,7 +1414,7 @@ fn get_identifier(
let is_valid_identifier = is_valid_identifier(result.iter().cloned());
let identifier: String = result.into_iter().collect();
let identifier = result.into_iter().collect();
if !is_valid_identifier {
return Some((
@ -1397,6 +1429,27 @@ fn get_identifier(
));
}
/// Is this keyword allowed as a function?
#[inline(always)]
pub fn is_keyword_function(name: &str) -> bool {
match name {
#[cfg(not(feature = "no_closure"))]
KEYWORD_IS_SHARED => true,
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => true,
_ => false,
}
}
/// Can this keyword be overridden as a function?
#[inline(always)]
pub fn can_override_keyword(name: &str) -> bool {
match name {
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR => true,
_ => false,
}
}
pub fn is_valid_identifier(name: impl Iterator<Item = char>) -> bool {
let mut first_alphabetic = false;
@ -1414,22 +1467,25 @@ pub fn is_valid_identifier(name: impl Iterator<Item = char>) -> bool {
}
#[cfg(feature = "unicode-xid-ident")]
#[inline(always)]
fn is_id_first_alphabetic(x: char) -> bool {
unicode_xid::UnicodeXID::is_xid_start(x)
}
#[cfg(feature = "unicode-xid-ident")]
#[inline(always)]
fn is_id_continue(x: char) -> bool {
unicode_xid::UnicodeXID::is_xid_continue(x)
}
#[cfg(not(feature = "unicode-xid-ident"))]
#[inline(always)]
fn is_id_first_alphabetic(x: char) -> bool {
x.is_ascii_alphabetic()
}
#[cfg(not(feature = "unicode-xid-ident"))]
#[inline(always)]
fn is_id_continue(x: char) -> bool {
x.is_ascii_alphanumeric() || x == '_'
}
@ -1486,13 +1542,15 @@ pub struct TokenIterator<'a, 'e> {
pos: Position,
/// Input character stream.
stream: MultiInputsStream<'a>,
/// A processor function (if any) that maps a token to another.
map: Option<Box<dyn Fn(Token) -> Token>>,
}
impl<'a> Iterator for TokenIterator<'a, '_> {
type Item = (Token, Position);
fn next(&mut self) -> Option<Self::Item> {
match (
let token = match (
get_next_token(&mut self.stream, &mut self.state, &mut self.pos),
self.engine.disabled_symbols.as_ref(),
self.engine.custom_keywords.as_ref(),
@ -1574,12 +1632,27 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
Some((Token::Reserved(token.syntax().into()), pos))
}
(r, _, _) => r,
};
match token {
None => None,
Some((token, pos)) => {
if let Some(ref map) = self.map {
Some((map(token), pos))
} else {
Some((token, pos))
}
}
}
}
}
/// Tokenize an input text stream.
pub fn lex<'a, 'e>(input: &'a [&'a str], engine: &'e Engine) -> TokenIterator<'a, 'e> {
pub fn lex<'a, 'e>(
input: &'a [&'a str],
map: Option<Box<dyn Fn(Token) -> Token>>,
engine: &'e Engine,
) -> TokenIterator<'a, 'e> {
TokenIterator {
engine,
state: TokenizeState {
@ -1597,5 +1670,6 @@ pub fn lex<'a, 'e>(input: &'a [&'a str], engine: &'e Engine) -> TokenIterator<'a
streams: input.iter().map(|s| s.chars().peekable()).collect(),
index: 0,
},
map,
}
}

View File

@ -1,8 +1,4 @@
//! Module containing various utility types and functions.
//!
//! # Safety
//!
//! The `StaticVec` type has some `unsafe` blocks to handle conversions between `MaybeUninit` and regular types.
use crate::fn_native::{shared_make_mut, shared_take, Shared};
@ -10,15 +6,13 @@ use crate::stdlib::{
any::TypeId,
borrow::Borrow,
boxed::Box,
cmp::Ordering,
fmt,
hash::{BuildHasher, Hash, Hasher},
iter::FromIterator,
mem,
mem::MaybeUninit,
ops::{Add, AddAssign, Deref, DerefMut, Drop, Index, IndexMut},
ops::{Add, AddAssign, Deref},
str::FromStr,
string::{String, ToString},
vec::Vec,
};
#[cfg(not(feature = "no_std"))]
@ -27,6 +21,8 @@ use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(feature = "no_std")]
use ahash::AHasher;
use smallvec::SmallVec;
/// A hasher that only takes one single `u64` and returns it as a hash key.
///
/// # Panics
@ -92,549 +88,10 @@ pub fn calc_fn_spec<'a>(
s.finish()
}
/// [INTERNALS] An array-like type that holds a number of values in static storage for no-allocation, quick access.
/// [INTERNALS] Alias to [`smallvec::SmallVec<[T; 4]>`](https://crates.io/crates/smallvec),
/// which is a specialized `Vec` backed by a small, fixed-size array when there are <= 4 items stored.
/// Exported under the `internals` feature only.
///
/// 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 for uninitialized/unused array slots) for efficiency.
///
/// ## WARNING
///
/// This type is volatile and may change.
//
// TODO - remove unsafe code
pub struct StaticVec<T> {
/// Total number of values held.
len: usize,
/// 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: Hash> Hash for StaticVec<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.iter().for_each(|x| x.hash(state));
}
}
impl<T> Default for StaticVec<T> {
fn default() -> Self {
Self {
len: 0,
list: unsafe { mem::MaybeUninit::uninit().assume_init() },
more: Vec::new(),
}
}
}
impl<T: PartialEq> PartialEq for StaticVec<T> {
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 = self.list.get(x).unwrap();
let item_value = unsafe { mem::transmute::<_, &T>(item) };
value.list[x] = MaybeUninit::new(item_value.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();
for x in iter {
vec.push(x);
}
vec
}
}
impl<T: 'static> IntoIterator for StaticVec<T> {
type Item = T;
type IntoIter = Box<dyn Iterator<Item = T>>;
fn into_iter(self) -> Self::IntoIter {
self.into_iter()
}
}
impl<T> StaticVec<T> {
/// Create a new `StaticVec`.
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 == 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.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;
}
/// Pop a value from the end of this `StaticVec`.
///
/// # Panics
///
/// Panics if the `StaticVec` is empty.
pub fn pop(&mut self) -> T {
if self.is_empty() {
panic!("nothing to pop!");
}
if self.is_fixed_storage() {
let value = self.extract_from_list(self.len - 1);
self.len -= 1;
value
} else {
let value = self.more.pop().unwrap();
self.len -= 1;
// 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
}
}
/// 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");
}
if self.is_fixed_storage() {
let value = self.extract_from_list(index);
// Move all items one slot to the left
for x in index + 1..self.len {
let orig_value = self.extract_from_list(x);
self.set_into_list(x - 1, orig_value, false);
}
self.len -= 1;
value
} else {
let value = self.more.remove(index);
self.len -= 1;
// 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
}
}
/// Get the number of items in this `StaticVec`.
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 `index` is out of bounds.
pub fn get(&self, index: usize) -> &T {
if index >= self.len {
panic!("index OOB in StaticVec");
}
let list = unsafe { mem::transmute::<_, &[T; MAX_STATIC_VEC]>(&self.list) };
if self.is_fixed_storage() {
list.get(index).unwrap()
} else {
self.more.get(index).unwrap()
}
}
/// Get a mutable reference to the item at a particular index.
///
/// # Panics
///
/// 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");
}
let list = unsafe { mem::transmute::<_, &mut [T; MAX_STATIC_VEC]>(&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> {
let list = unsafe { mem::transmute::<_, &[T; MAX_STATIC_VEC]>(&self.list) };
if self.is_fixed_storage() {
list[..self.len].iter()
} else {
self.more.iter()
}
}
/// Get a mutable iterator to entries in the `StaticVec`.
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
let list = unsafe { mem::transmute::<_, &mut [T; MAX_STATIC_VEC]>(&mut self.list) };
if self.is_fixed_storage() {
list[..self.len].iter_mut()
} else {
self.more.iter_mut()
}
}
}
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 `index` is out of bounds.
pub fn take(&mut self, index: usize) -> T {
if index >= self.len {
panic!("index OOB in StaticVec");
}
mem::take(if self.is_fixed_storage() {
unsafe { mem::transmute(self.list.get_mut(index).unwrap()) }
} else {
self.more.get_mut(index).unwrap()
})
}
}
impl<T: fmt::Debug> fmt::Debug for StaticVec<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.iter().collect::<Vec<_>>(), f)
}
}
impl<T> AsRef<[T]> for StaticVec<T> {
fn as_ref(&self) -> &[T] {
let list = unsafe { mem::transmute::<_, &[T; MAX_STATIC_VEC]>(&self.list) };
if self.is_fixed_storage() {
&list[..self.len]
} else {
&self.more[..]
}
}
}
impl<T> AsMut<[T]> for StaticVec<T> {
fn as_mut(&mut self) -> &mut [T] {
let list = unsafe { mem::transmute::<_, &mut [T; MAX_STATIC_VEC]>(&mut self.list) };
if self.is_fixed_storage() {
&mut list[..self.len]
} else {
&mut self.more[..]
}
}
}
impl<T> Deref for StaticVec<T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl<T> DerefMut for StaticVec<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut()
}
}
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
}
}
pub type StaticVec<T> = SmallVec<[T; 4]>;
/// The system immutable string type.
///
@ -685,6 +142,12 @@ impl AsRef<String> for ImmutableString {
}
}
impl Borrow<String> for ImmutableString {
fn borrow(&self) -> &String {
&self.0
}
}
impl Borrow<str> for ImmutableString {
fn borrow(&self) -> &str {
self.0.as_str()
@ -801,6 +264,18 @@ impl AddAssign<&ImmutableString> for ImmutableString {
}
}
impl AddAssign<ImmutableString> for ImmutableString {
fn add_assign(&mut self, rhs: ImmutableString) {
if !rhs.is_empty() {
if self.is_empty() {
self.0 = rhs.0;
} else {
self.make_mut().push_str(rhs.0.as_str());
}
}
}
}
impl Add<&str> for ImmutableString {
type Output = Self;
@ -892,6 +367,42 @@ impl AddAssign<char> for ImmutableString {
}
}
impl<S: AsRef<str>> PartialEq<S> for ImmutableString {
fn eq(&self, other: &S) -> bool {
self.as_str().eq(other.as_ref())
}
}
impl PartialEq<ImmutableString> for str {
fn eq(&self, other: &ImmutableString) -> bool {
self.eq(other.as_str())
}
}
impl PartialEq<ImmutableString> for String {
fn eq(&self, other: &ImmutableString) -> bool {
self.eq(other.as_str())
}
}
impl<S: AsRef<str>> PartialOrd<S> for ImmutableString {
fn partial_cmp(&self, other: &S) -> Option<Ordering> {
self.as_str().partial_cmp(other.as_ref())
}
}
impl PartialOrd<ImmutableString> for str {
fn partial_cmp(&self, other: &ImmutableString) -> Option<Ordering> {
self.partial_cmp(other.as_str())
}
}
impl PartialOrd<ImmutableString> for String {
fn partial_cmp(&self, other: &ImmutableString) -> Option<Ordering> {
self.as_str().partial_cmp(other.as_str())
}
}
impl ImmutableString {
/// Consume the `ImmutableString` and convert it into a `String`.
/// If there are other references to the same string, a cloned copy is returned.

View File

@ -1,6 +1,7 @@
#![cfg(not(feature = "no_function"))]
use rhai::{
Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT,
Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, RegisterFn,
Scope, INT,
};
use std::any::TypeId;
@ -82,6 +83,70 @@ fn test_call_fn_private() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
#[cfg(not(feature = "no_object"))]
fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
#[allow(deprecated)]
engine
.register_fn("mul", |x: &mut INT, y: INT| *x *= y)
.register_raw_fn(
"bar",
&[
TypeId::of::<INT>(),
TypeId::of::<FnPtr>(),
TypeId::of::<INT>(),
],
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
let fp = std::mem::take(args[1]).cast::<FnPtr>();
let value = args[2].clone();
let this_ptr = args.get_mut(0).unwrap();
fp.call_dynamic(engine, lib, Some(this_ptr), [value])
},
);
assert_eq!(
engine.eval::<INT>(
r#"
fn foo(x) { this += x; }
let x = 41;
x.bar(Fn("foo"), 1);
x
"#
)?,
42
);
assert!(matches!(
*engine.eval::<INT>(
r#"
private fn foo(x) { this += x; }
let x = 41;
x.bar(Fn("foo"), 1);
x
"#
).expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(x, _) if x.starts_with("foo (")
));
assert_eq!(
engine.eval::<INT>(
r#"
let x = 21;
x.bar(Fn("mul"), 2);
x
"#
)?,
42
);
Ok(())
}
#[test]
fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
let calc_func = Func::<(INT, INT, INT), INT>::create_from_script(
@ -113,80 +178,3 @@ fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
#[cfg(not(feature = "no_object"))]
fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.register_raw_fn(
"bar",
&[
TypeId::of::<INT>(),
TypeId::of::<FnPtr>(),
TypeId::of::<INT>(),
],
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
let fp = std::mem::take(args[1]).cast::<FnPtr>();
let value = args[2].clone();
let this_ptr = args.get_mut(0).unwrap();
engine.call_fn_dynamic(
&mut Scope::new(),
lib,
fp.fn_name(),
Some(this_ptr),
[value],
)?;
Ok(())
},
);
assert_eq!(
engine.eval::<INT>(
r#"
fn foo(x) { this += x; }
let x = 41;
x.bar(Fn("foo"), 1);
x
"#
)?,
42
);
Ok(())
}
#[test]
fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
let mut module = Module::new();
module.set_raw_fn(
"call_with_arg",
&[TypeId::of::<FnPtr>(), TypeId::of::<INT>()],
|engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
let fn_ptr = std::mem::take(args[0]).cast::<FnPtr>();
fn_ptr.call_dynamic(engine, lib, None, [std::mem::take(args[1])])
},
);
let mut engine = Engine::new();
engine.load_package(module.into());
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>(
r#"
let addition = |x, y| { x + y };
let curried = addition.curry(2);
call_with_arg(curried, 40)
"#
)?,
42
);
Ok(())
}

117
tests/closures.rs Normal file
View File

@ -0,0 +1,117 @@
#![cfg(not(feature = "no_function"))]
use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Module, INT};
use std::any::TypeId;
#[test]
fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
let mut module = Module::new();
module.set_raw_fn(
"call_with_arg",
&[TypeId::of::<FnPtr>(), TypeId::of::<INT>()],
|engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
let fn_ptr = std::mem::take(args[0]).cast::<FnPtr>();
fn_ptr.call_dynamic(engine, lib, None, [std::mem::take(args[1])])
},
);
let mut engine = Engine::new();
engine.load_package(module);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>(
r#"
let addition = |x, y| { x + y };
let curried = addition.curry(2);
call_with_arg(curried, 40)
"#
)?,
42
);
Ok(())
}
#[test]
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
fn test_closures() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r#"
let x = 8;
let res = |y, z| {
let w = 12;
return (|| x + y + z + w).call();
}.curry(15).call(2);
res + (|| x - 3).call()
"#
)?,
42
);
assert_eq!(
engine.eval::<INT>(
r#"
let a = 41;
let foo = |x| { a += x };
foo.call(1);
a
"#
)?,
42
);
assert!(engine.eval::<bool>(
r#"
let a = 41;
let foo = |x| { a += x };
a.is_shared()
"#
)?);
Ok(())
}
#[test]
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
fn test_closures_data_race() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r#"
let a = 1;
let b = 40;
let foo = |x| { this += a + x };
b.call(foo, 1);
b
"#
)?,
42
);
assert!(matches!(
*engine
.eval::<INT>(
r#"
let a = 20;
let foo = |x| { this += a + x };
a.call(foo, 1);
a
"#
)
.expect_err("should error"),
EvalAltResult::ErrorDataRace(_, _)
));
Ok(())
}

View File

@ -73,7 +73,7 @@ fn test_fn_ptr() -> Result<(), Box<EvalAltResult>> {
"#
)
.expect_err("should error"),
EvalAltResult::ErrorInFunctionCall(fn_name, err, _) if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundedThis(_))
EvalAltResult::ErrorInFunctionCall(fn_name, err, _) if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(_))
));
Ok(())

View File

@ -1,5 +1,5 @@
#![cfg(not(feature = "no_function"))]
use rhai::{Engine, EvalAltResult, INT};
use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, INT};
#[test]
fn test_functions() -> Result<(), Box<EvalAltResult>> {
@ -120,3 +120,53 @@ fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
#[cfg(not(feature = "no_closure"))]
fn test_function_captures() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r#"
fn foo(y) { x += y; x }
let x = 41;
let y = 999;
foo!(1) + x
"#
)?,
83
);
assert!(engine
.eval::<INT>(
r#"
fn foo(y) { x += y; x }
let x = 41;
let y = 999;
foo(1) + x
"#
)
.is_err());
#[cfg(not(feature = "no_object"))]
assert!(matches!(
engine.compile(
r#"
fn foo() { this += x; }
let x = 41;
let y = 999;
y.foo!();
"#
).expect_err("should error"),
ParseError(err, _) if matches!(*err, ParseErrorType::MalformedCapture(_))
));
Ok(())
}

View File

@ -35,7 +35,7 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
let m2 = m.get_sub_module("universe").unwrap();
assert!(m2.contains_var("answer"));
assert!(m2.contains_fn(hash_inc));
assert!(m2.contains_fn(hash_inc, false));
assert_eq!(m2.get_var_value::<INT>("answer").unwrap(), 41);

View File

@ -1,11 +1,14 @@
#![cfg(not(feature = "no_std"))]
#![cfg(not(target_arch = "wasm32"))]
use rhai::{Engine, EvalAltResult, INT};
use rhai::{Engine, EvalAltResult};
#[cfg(not(feature = "no_float"))]
use rhai::FLOAT;
#[cfg(feature = "no_float")]
use rhai::INT;
#[test]
fn test_timestamp() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();

View File

@ -57,9 +57,9 @@ fn test_tokens_unicode_xid_ident() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let result = engine.eval::<INT>(
r"
fn () { 42 }
()
",
fn () { 42 }
()
",
);
#[cfg(feature = "unicode-xid-ident")]
assert_eq!(result?, 42);
@ -69,9 +69,9 @@ fn test_tokens_unicode_xid_ident() -> Result<(), Box<EvalAltResult>> {
let result = engine.eval::<INT>(
r"
fn _1() { 1 }
_1()
",
fn _1() { 1 }
_1()
",
);
assert!(result.is_err());