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

View File

@ -14,15 +14,16 @@ include = [
"Cargo.toml" "Cargo.toml"
] ]
keywords = [ "scripting" ] keywords = [ "scripting" ]
categories = [ "no-std", "embedded", "parser-implementations" ] categories = [ "no-std", "embedded", "wasm", "parser-implementations" ]
[dependencies] [dependencies]
num-traits = { version = "0.2.11", default-features = false } num-traits = { version = "0.2.11", default-features = false }
smallvec = { version = "1.4.1", default-features = false }
[features] [features]
#default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"]
default = [] default = []
plugins = [] plugins = [] # custom plugins support
unchecked = [] # unchecked arithmetic unchecked = [] # unchecked arithmetic
sync = [] # restrict to only types that implement Send + Sync sync = [] # restrict to only types that implement Send + Sync
no_optimize = [] # no script optimizer 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_index = [] # no arrays and indexing
no_object = [] # no custom objects no_object = [] # no custom objects
no_function = [] # no script-defined functions no_function = [] # no script-defined functions
no_closure = [] # no automatic sharing and capture of anonymous functions to external variables
no_module = [] # no modules no_module = [] # no modules
internals = [] # expose internal data structures internals = [] # expose internal data structures
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 # 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] [profile.release]
lto = "fat" 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. * Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust.
* Fairly low compile-time overhead. * Fairly low compile-time overhead.
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM). * 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 * Relatively little `unsafe` code (yes there are some for performance reasons).
one single source file, all with names starting with `"unsafe_"`).
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
* [Function overloading](https://schungx.github.io/rhai/language/overload.html). * [Function overloading](https://schungx.github.io/rhai/language/overload.html).
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html). * [Operator overloading](https://schungx.github.io/rhai/rust/operators.html).
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html). * 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). * Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
* Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html). * Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html).
* Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature).

View File

@ -6,25 +6,41 @@ Version 0.18.0
This version adds: 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. * 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 New features
------------ ------------
* `call` can now be called function-call style for function pointers - this is to handle builds with `no_object`. * `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`. * `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. * 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`. * `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 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. * 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 Version 0.17.0
@ -58,7 +74,7 @@ New features
* `Engine::disable_symbol` to surgically disable keywords and/or operators. * `Engine::disable_symbol` to surgically disable keywords and/or operators.
* `Engine::register_custom_operator` to define a custom operator. * `Engine::register_custom_operator` to define a custom operator.
* `Engine::register_custom_syntax` to define a custom syntax. * `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`. * 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`. * `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. * The boolean `^` (XOR) operator is added.

View File

@ -48,7 +48,7 @@ fn bench_eval_expression_optimized_simple(bench: &mut Bencher) {
2 > 1 && 2 > 1 &&
"something" != "nothing" || "something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && "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) modifierTest + 1000 / 2 > (80 * 100 % 2)
"#; "#;
@ -65,7 +65,7 @@ fn bench_eval_expression_optimized_full(bench: &mut Bencher) {
2 > 1 && 2 > 1 &&
"something" != "nothing" || "something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && "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) modifierTest + 1000 / 2 > (80 * 100 % 2)
"#; "#;
@ -82,7 +82,7 @@ fn bench_eval_call_expression(bench: &mut Bencher) {
2 > 1 && 2 > 1 &&
"something" != "nothing" || "something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && "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) modifierTest + 1000 / 2 > (80 * 100 % 2)
"#; "#;
@ -97,7 +97,7 @@ fn bench_eval_call(bench: &mut Bencher) {
2 > 1 && 2 > 1 &&
"something" != "nothing" || "something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && "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) modifierTest + 1000 / 2 > (80 * 100 % 2)
"#; "#;

View File

@ -32,7 +32,7 @@ fn bench_parse_full(bench: &mut Bencher) {
2 > 1 && 2 > 1 &&
"something" != "nothing" || "something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && "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) modifierTest + 1000 / 2 > (80 * 100 % 2)
"#; "#;
@ -109,7 +109,7 @@ fn bench_parse_optimize_simple(bench: &mut Bencher) {
2 > 1 && 2 > 1 &&
"something" != "nothing" || "something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && "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) modifierTest + 1000 / 2 > (80 * 100 % 2)
"#; "#;
@ -125,7 +125,7 @@ fn bench_parse_optimize_full(bench: &mut Bencher) {
2 > 1 && 2 > 1 &&
"something" != "nothing" || "something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && "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) modifierTest + 1000 / 2 > (80 * 100 % 2)
"#; "#;

View File

@ -79,6 +79,7 @@ The Rhai Scripting Language
4. [Function Pointers](language/fn-ptr.md) 4. [Function Pointers](language/fn-ptr.md)
5. [Anonymous Functions](language/fn-anon.md) 5. [Anonymous Functions](language/fn-anon.md)
6. [Currying](language/fn-curry.md) 6. [Currying](language/fn-curry.md)
7. [Closures](language/fn-closure.md)
16. [Print and Debug](language/print-debug.md) 16. [Print and Debug](language/print-debug.md)
17. [Modules](language/modules/index.md) 17. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.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) 8. [Maximum Call Stack Depth](safety/max-call-stack.md)
9. [Maximum Statement Depth](safety/max-stmt-depth.md) 9. [Maximum Statement Depth](safety/max-stmt-depth.md)
7. [Advanced Topics](advanced.md) 7. [Advanced Topics](advanced.md)
1. [Object-Oriented Programming (OOP)](language/oop.md) 1. [Capture Scope for Function Call](language/fn-capture.md)
2. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md) 2. [Object-Oriented Programming (OOP)](language/oop.md)
3. [Script Optimization](engine/optimize/index.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) 1. [Optimization Levels](engine/optimize/optimize-levels.md)
2. [Re-Optimize an AST](engine/optimize/reoptimize.md) 2. [Re-Optimize an AST](engine/optimize/reoptimize.md)
3. [Eager Function Evaluation](engine/optimize/eager.md) 3. [Eager Function Evaluation](engine/optimize/eager.md)
4. [Side-Effect Considerations](engine/optimize/side-effects.md) 4. [Side-Effect Considerations](engine/optimize/side-effects.md)
5. [Volatility Considerations](engine/optimize/volatility.md) 5. [Volatility Considerations](engine/optimize/volatility.md)
6. [Subtle Semantic Changes](engine/optimize/semantics.md) 6. [Subtle Semantic Changes](engine/optimize/semantics.md)
4. [Low-Level API](rust/register-raw.md) 5. [Low-Level API](rust/register-raw.md)
5. [Use as DSL](engine/dsl.md) 6. [Use as DSL](engine/dsl.md)
1. [Disable Keywords and/or Operators](engine/disable.md) 1. [Disable Keywords and/or Operators](engine/disable.md)
2. [Custom Operators](engine/custom-op.md) 2. [Custom Operators](engine/custom-op.md)
3. [Extending with Custom Syntax](engine/custom-syntax.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) 8. [Appendix](appendix/index.md)
1. [Keywords](appendix/keywords.md) 1. [Keywords](appendix/keywords.md)
2. [Operators and Symbols](appendix/operators.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. * 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`. for [`no-std`] builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`.
Fast Fast
@ -35,23 +35,22 @@ Dynamic
* Organize code base with dynamically-loadable [modules]. * 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]. * Some support for [object-oriented programming (OOP)][OOP].
* Serialization/deserialization support via [`serde`].
Safe Safe
---- ----
* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to * Relatively little `unsafe` code (yes there are some for performance reasons).
one single source file, all with names starting with `"unsafe_"`).
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
Rugged 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. * 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. * 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). * 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]. * Support for [minimal builds] by excluding unneeded language [features].
* Supports [most build targets](targets.md) including `no-std` and [WASM]. * 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: 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]. * Simulated [Object Oriented Programming (OOP)][OOP].
* [`serde`] integration. * [`serde`] integration.

View File

@ -3,32 +3,69 @@ Keywords List
{{#include ../links.md}} {{#include ../links.md}}
| Keyword | Description | Not available under | | Keyword | Description | Inactive under | Overloadable |
| :-------------------: | ---------------------------------------- | :-----------------: | | :-------------------: | ---------------------------------------- | :-------------: | :----------: |
| `true` | Boolean true literal | | | `true` | Boolean true literal | | No |
| `false` | Boolean false literal | | | `false` | Boolean false literal | | No |
| `let` | Variable declaration | | | `let` | Variable declaration | | No |
| `const` | Constant declaration | | | `const` | Constant declaration | | No |
| `if` | If statement | | | `is_shared` | Is a value shared? | | No |
| `else` | else block of if statement | | | `if` | If statement | | No |
| `while` | While loop | | | `else` | else block of if statement | | No |
| `loop` | Infinite loop | | | `while` | While loop | | No |
| `for` | For loop | | | `loop` | Infinite loop | | No |
| `in` | Containment test, part of for loop | | | `for` | For loop | | No |
| `continue` | Continue a loop at the next iteration | | | `in` | Containment test, part of for loop | | No |
| `break` | Loop breaking | | | `continue` | Continue a loop at the next iteration | | No |
| `return` | Return value | | | `break` | Loop breaking | | No |
| `throw` | Throw exception | | | `return` | Return value | | No |
| `import` | Import module | [`no_module`] | | `throw` | Throw exception | | No |
| `export` | Export variable | [`no_module`] | | `import` | Import module | [`no_module`] | No |
| `as` | Alias for variable export | [`no_module`] | | `export` | Export variable | [`no_module`] | No |
| `private` | Mark function private | [`no_function`] | | `as` | Alias for variable export | [`no_module`] | No |
| `fn` (lower-case `f`) | Function definition | [`no_function`] | | `private` | Mark function private | [`no_function`] | No |
| `Fn` (capital `F`) | Function to create a [function pointer] | | | `fn` (lower-case `f`) | Function definition | [`no_function`] | No |
| `call` | Call a [function pointer] | | | `Fn` (capital `F`) | Function to create a [function pointer] | | Yes |
| `curry` | Curry a [function pointer] | | | `call` | Call a [function pointer] | | No |
| `this` | Reference to base object for method call | [`no_function`] | | `curry` | Curry a [function pointer] | | No |
| `type_of` | Get type name of value | | | `this` | Reference to base object for method call | [`no_function`] | No |
| `print` | Print value | | | `type_of` | Get type name of value | | Yes |
| `debug` | Print value in debug format | | | `print` | Print value | | Yes |
| `eval` | Evaluate script | | | `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: 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: 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. 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 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 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 ```rust
let obj = #{ let obj = #{
@ -49,9 +49,12 @@ fn anon_fn_1001(x) { this.data -= x; }
fn anon_fn_1002() { print this.data; } 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 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 **not** real closures.
Rust's function pointers.
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)' curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)'
// only one argument is now required // 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: i.e. define the function in a separate module and then [`import`] it:
```rust ```rust
message.rhai: ----------------
| message.rhai |
----------------
fn message() { "Hello!" } fn message() { "Hello!" }
script.rhai:
fn say_hello() { ---------------
import "message" as msg; | script.rhai |
print(msg::message()); ---------------
}
say_hello(); 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. current global namespace and may not be found.
```rust ```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), 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: to call another function within a module-defined function:
```rust ```rust
greeting.rhai: -----------------
| greeting.rhai |
-----------------
fn message() { "Hello!" }; fn message() { "Hello!" };
fn say_hello(msg_func) { // 'msg_func' is a function pointer fn say_hello(msg_func) { // 'msg_func' is a function pointer
print(msg_func.call()); // call via the 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;
import "greeting" as g; // <- must import again here...
g::message() // <- ... otherwise will not find module '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; 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! 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: The following are reserved keywords in Rhai:
| Keywords | Usage | Not available under feature | | Active keywords | Reserved keywords | Usage | Inactive under feature |
| ------------------------------------------------- | --------------------- | :-------------------------: | | ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: |
| `true`, `false` | Boolean constants | | | `true`, `false` | | Boolean constants | |
| `let`, `const` | Variable declarations | | | `let`, `const` | `var`, `static` | Variable declarations | |
| `if`, `else` | Control flow | | | `is_shared` | | Shared values | [`no_closure`] |
| `while`, `loop`, `for`, `in`, `continue`, `break` | Looping | | | `if`, `else` | `then`, `goto`, `exit` | Control flow | |
| `fn`, `private` | Functions | [`no_function`] | | | `switch`, `match`, `case` | Matching | |
| `return` | Return values | | | `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | |
| `throw` | throw exceptions | | | `fn`, `private` | `public`, `new` | Functions | [`no_function`] |
| `import`, `export`, `as` | Modules | [`no_module`] | | `return` | | Return values | |
| `Fn`, `call` | Function pointers | | | `throw` | `try`, `catch` | Throw exceptions | |
| `type_of`, `print`, `debug`, `eval` | Special functions | | | `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. Keywords cannot become the name of a [function] or [variable], even when they are disabled.
For example, `fn` is a valid variable name under [`no_function`].

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, 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. 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`), For primary types that are cheap to clone (e.g. those that implement `Copy`),
including `ImmutableString`, this is not necessary. 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: For instance, importing itself always causes an infinite recursion:
```rust ```rust
// This file is 'hello.rhai' --------------
| hello.rhai |
--------------
import "hello" as foo; // import itself - infinite recursion! import "hello" as foo; // import itself - infinite recursion!
@ -73,11 +75,18 @@ foo::do_something();
Modules cross-referencing also cause infinite recursion: Modules cross-referencing also cause infinite recursion:
```rust ```rust
// This file is 'hello.rhai' - references 'world.rhai' --------------
| hello.rhai |
--------------
import "world" as foo; import "world" as foo;
foo::do_something(); foo::do_something();
// This file is 'world.rhai' - references 'hello.rhai'
--------------
| world.rhai |
--------------
import "hello" as bar; import "hello" as bar;
bar::do_something_else(); 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 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. 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 Examples
-------- --------
```rust ```rust
let factor = 1;
// Define the object // Define the object
let obj = let obj =
#{ #{
data: 0, data: 0,
increment: |x| this.data += x, // when called, 'this' binds to 'obj' increment: |x| this.data += x, // 'this' binds to 'obj'
update: |x| this.data = x, // when called, 'this' binds to 'obj' update: |x| this.data = x * factor, // 'this' binds to 'obj', 'factor' is captured
action: || print(this.data) // when called, 'this' binds to 'obj' action: || print(this.data) // 'this' binds to 'obj'
}; };
// Use the object // Use the object
obj.increment(1); obj.increment(1);
obj.action(); // prints 1 obj.action(); // prints 1
obj.update(42); 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. New definitions _overwrite_ previous definitions of the same name and number of parameters.
```rust ```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" 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: The following primitive types are supported natively:
| Category | Equivalent Rust types | [`type_of()`] | `to_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. | | **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. | | **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. |
| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` | | **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. | | **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`) | `"string"` | `"hello"` etc. | | **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`) | `"string"` | `"hello"` etc. |
| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | | **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | | **[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_ | | **[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)"` | | **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` |
| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | | **[`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. | | **Shared value** (a reference-counted, shared [`Dynamic`] value, created via [automatic currying], disabled with [`no_closure`]) | | _the actual type_ | _actual value_ |
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | | **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ | | **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 - 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. 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]. 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 Declare a Variable
------------------ ------------------

View File

@ -9,6 +9,7 @@
[`no_object`]: {{rootUrl}}/start/features.md [`no_object`]: {{rootUrl}}/start/features.md
[`no_function`]: {{rootUrl}}/start/features.md [`no_function`]: {{rootUrl}}/start/features.md
[`no_module`]: {{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
[`no-std`]: {{rootUrl}}/start/features.md [`no-std`]: {{rootUrl}}/start/features.md
[`internals`]: {{rootUrl}}/start/features.md [`internals`]: {{rootUrl}}/start/features.md
@ -78,6 +79,10 @@
[function pointer]: {{rootUrl}}/language/fn-ptr.md [function pointer]: {{rootUrl}}/language/fn-ptr.md
[function pointers]: {{rootUrl}}/language/fn-ptr.md [function pointers]: {{rootUrl}}/language/fn-ptr.md
[currying]: {{rootUrl}}/language/fn-curry.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 namespace]: {{rootUrl}}/language/fn-namespaces.md
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md [function namespaces]: {{rootUrl}}/language/fn-namespaces.md
[anonymous function]: {{rootUrl}}/language/fn-anon.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_. // Therefore, get a '&mut' reference to the first argument _last_.
// Alternatively, use `args.split_at_mut(1)` etc. to split the slice first. // 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 .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 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 .unwrap(); // first argument
*x += y; // perform the action *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 function signature passed to `Engine::register_raw_fn` takes the following form:
the majority, use one of the `Engine::register_raw_fn_n` (where `n = 0..4`) methods:
```rust > `Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result<T, Box<EvalAltResult>> + 'static`
// 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`
where: where:
@ -99,12 +84,12 @@ Extract Arguments
To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following: To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following:
| Argument type | Access (`n` = argument position) | Result | | Argument type | Access (`n` = argument position) | Result |
| ------------------------------ | -------------------------------------- | ---------------------------------------------------------- | | ------------------------------ | ------------------------------------- | ---------------------------------------------------------- |
| [Primary type][standard types] | `args[n].clone().cast::<T>()` | Copy of value. | | [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 | `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 `()`. | | 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. | | `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), 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. 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 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 ```rust
use rhai::{Engine, Module, Dynamic, FnPtr}; use rhai::{Engine, Module, Dynamic, FnPtr};
@ -133,11 +124,9 @@ engine.register_raw_fn(
let value = args[2].clone(); // 3rd argument - function argument let value = args[2].clone(); // 3rd argument - function argument
let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer
// Use 'call_fn_dynamic' to call the function name. // Use 'FnPtr::call_dynamic' to call the function pointer.
// Pass 'lib' as the current global library of functions. // Beware, private script-defined functions will not be found.
engine.call_fn_dynamic(&mut Scope::new(), lib, fp.fn_name(), Some(this_ptr), [value])?; fp.call_dynamic(engine, lib, Some(this_ptr), [value])
Ok(())
}, },
); );
@ -167,5 +156,5 @@ let this_ptr = first[0].downcast_mut::<A>().unwrap();
// Immutable reference to the second value parameter // Immutable reference to the second value parameter
// This can be mutable but there is no point because the parameter is passed by value // 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 Excluding unneeded functionalities can result in smaller, faster builds as well as
more control over what a script can (or cannot) do. more control over what a script can (or cannot) do.
| Feature | Description | | 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! | | `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`. | | `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_optimize` | Disable [script optimization]. |
| `no_float` | Disable floating-point numbers and math. | | `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_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
| `no_index` | Disable [arrays] and indexing features. | | `no_index` | Disable [arrays] and indexing features. |
| `no_object` | Disable support for [custom types] and [object maps]. | | `no_object` | Disable support for [custom types] and [object maps]. |
| `no_function` | Disable script-defined [functions]. | | `no_function` | Disable script-defined [functions]. |
| `no_module` | Disable loading external [modules]. | | `no_module` | Disable loading external [modules]. |
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | | `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. |
| `serde` | Enable serialization/deserialization via [`serde`]. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | | `no_std` | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. |
| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | | `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. |
| `unicode-xid-ident` | Allow unicode-xid for identifiers. | | `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 Example

View File

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

View File

@ -4,6 +4,9 @@ use crate::fn_native::{FnPtr, SendSync};
use crate::parser::{ImmutableString, INT}; use crate::parser::{ImmutableString, INT};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; 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"))] #[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT; use crate::parser::FLOAT;
@ -17,9 +20,21 @@ use crate::stdlib::{
any::{type_name, Any, TypeId}, any::{type_name, Any, TypeId},
boxed::Box, boxed::Box,
fmt, fmt,
ops::{Deref, DerefMut},
string::String, 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"))] #[cfg(not(feature = "no_object"))]
use crate::stdlib::collections::HashMap; use crate::stdlib::collections::HashMap;
@ -144,6 +159,92 @@ pub enum Union {
Map(Box<Map>), Map(Box<Map>),
FnPtr(Box<FnPtr>), FnPtr(Box<FnPtr>),
Variant(Box<Box<dyn Variant>>), 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 { 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? /// 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 { pub fn is<T: Variant + Clone>(&self) -> bool {
self.type_id() == TypeId::of::<T>() let mut target_type_id = TypeId::of::<T>();
|| match self.0 {
Union::Str(_) => TypeId::of::<String>() == TypeId::of::<T>(), if target_type_id == TypeId::of::<String>() {
_ => false, target_type_id = TypeId::of::<ImmutableString>();
} }
self.type_id() == target_type_id
} }
/// Get the TypeId of the value held by this `Dynamic`. /// 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 { pub fn type_id(&self) -> TypeId {
match &self.0 { match &self.0 {
Union::Unit(_) => TypeId::of::<()>(), Union::Unit(_) => TypeId::of::<()>(),
@ -181,10 +302,21 @@ impl Dynamic {
Union::Map(_) => TypeId::of::<Map>(), Union::Map(_) => TypeId::of::<Map>(),
Union::FnPtr(_) => TypeId::of::<FnPtr>(), Union::FnPtr(_) => TypeId::of::<FnPtr>(),
Union::Variant(value) => (***value).type_id(), 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`. /// 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 { pub fn type_name(&self) -> &'static str {
match &self.0 { match &self.0 {
Union::Unit(_) => "()", Union::Unit(_) => "()",
@ -203,6 +335,15 @@ impl Dynamic {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => "timestamp", Union::Variant(value) if value.is::<Instant>() => "timestamp",
Union::Variant(value) => (***value).type_name(), 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), Union::FnPtr(value) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"), Union::Variant(value) if value.is::<Instant>() => f.write_str("<timestamp>"),
Union::Variant(value) => write!(f, "{}", (*value).type_name()), 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"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"), Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
Union::Variant(value) => write!(f, "{}", (*value).type_name()), 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::Map(ref value) => Self(Union::Map(value.clone())),
Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())), Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())),
Union::Variant(ref value) => (***value).clone_into_dynamic(), 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) { boxed = match unsafe_cast_box::<_, Dynamic>(boxed) {
Ok(d) => return *d, Ok(d) => return *d,
Err(val) => val, Err(val) => val,
@ -415,10 +587,46 @@ impl Dynamic {
Self(Union::Variant(Box::new(boxed))) Self(Union::Variant(Box::new(boxed)))
} }
/// Get a copy of the `Dynamic` value as a specific type. /// Turn the `Dynamic` value into a shared `Dynamic` value backed by an `Rc<RefCell<Dynamic>>`
/// Casting to a `Dynamic` just returns as is. /// 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 /// # Example
/// ///
@ -433,12 +641,28 @@ impl Dynamic {
pub fn try_cast<T: Variant>(self) -> Option<T> { pub fn try_cast<T: Variant>(self) -> Option<T> {
let type_id = TypeId::of::<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>() { if type_id == TypeId::of::<INT>() {
return match self.0 { return match self.0 {
Union::Int(value) => unsafe_try_cast(value), Union::Int(value) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
if type_id == TypeId::of::<FLOAT>() { if type_id == TypeId::of::<FLOAT>() {
return match self.0 { return match self.0 {
@ -446,30 +670,35 @@ impl Dynamic {
_ => None, _ => None,
}; };
} }
if type_id == TypeId::of::<bool>() { if type_id == TypeId::of::<bool>() {
return match self.0 { return match self.0 {
Union::Bool(value) => unsafe_try_cast(value), Union::Bool(value) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
if type_id == TypeId::of::<ImmutableString>() { if type_id == TypeId::of::<ImmutableString>() {
return match self.0 { return match self.0 {
Union::Str(value) => unsafe_try_cast(value), Union::Str(value) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
if type_id == TypeId::of::<String>() { if type_id == TypeId::of::<String>() {
return match self.0 { return match self.0 {
Union::Str(value) => unsafe_try_cast(value.into_owned()), Union::Str(value) => unsafe_try_cast(value.into_owned()),
_ => None, _ => None,
}; };
} }
if type_id == TypeId::of::<char>() { if type_id == TypeId::of::<char>() {
return match self.0 { return match self.0 {
Union::Char(value) => unsafe_try_cast(value), Union::Char(value) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if type_id == TypeId::of::<Array>() { if type_id == TypeId::of::<Array>() {
return match self.0 { return match self.0 {
@ -477,6 +706,7 @@ impl Dynamic {
_ => None, _ => None,
}; };
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if type_id == TypeId::of::<Map>() { if type_id == TypeId::of::<Map>() {
return match self.0 { return match self.0 {
@ -484,34 +714,45 @@ impl Dynamic {
_ => None, _ => None,
}; };
} }
if type_id == TypeId::of::<FnPtr>() { if type_id == TypeId::of::<FnPtr>() {
return match self.0 { return match self.0 {
Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None, _ => None,
}; };
} }
if type_id == TypeId::of::<()>() { if type_id == TypeId::of::<()>() {
return match self.0 { return match self.0 {
Union::Unit(value) => unsafe_try_cast(value), Union::Unit(value) => unsafe_try_cast(value),
_ => None, _ => None,
}; };
} }
if type_id == TypeId::of::<Dynamic>() {
return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v);
}
match self.0 { match self.0 {
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
#[cfg(not(feature = "no_closure"))]
Union::Shared(_) => unreachable!(),
_ => None, _ => None,
} }
} }
/// Get a copy of the `Dynamic` value as a specific type. /// Convert the `Dynamic` value into a specific type.
/// Casting to a `Dynamic` just returns as is.
/// ///
/// # 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 /// # Example
/// ///
@ -527,11 +768,125 @@ impl Dynamic {
self.try_cast::<T>().unwrap() self.try_cast::<T>().unwrap()
} }
/// Get a reference of a specific type to the `Dynamic`. /// Get a copy of the `Dynamic` as a specific type.
/// Casting to `Dynamic` just returns a reference to it. ///
/// 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. /// Returns `None` if the cast fails.
#[inline(always)] #[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>(); let type_id = TypeId::of::<T>();
if type_id == TypeId::of::<INT>() { if type_id == TypeId::of::<INT>() {
@ -603,15 +958,18 @@ impl Dynamic {
match &self.0 { match &self.0 {
Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(), Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(),
#[cfg(not(feature = "no_closure"))]
Union::Shared(_) => None,
_ => None, _ => None,
} }
} }
/// Get a mutable reference of a specific type to the `Dynamic`. /// Get a mutable reference of a specific type to the `Dynamic`.
/// Casting to `Dynamic` just returns a mutable reference to it. /// 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)] #[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>(); let type_id = TypeId::of::<T>();
if type_id == TypeId::of::<INT>() { if type_id == TypeId::of::<INT>() {
@ -677,6 +1035,8 @@ impl Dynamic {
match &mut self.0 { match &mut self.0 {
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(), Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(),
#[cfg(not(feature = "no_closure"))]
Union::Shared(_) => None,
_ => None, _ => None,
} }
} }
@ -686,6 +1046,8 @@ impl Dynamic {
pub fn as_int(&self) -> Result<INT, &'static str> { pub fn as_int(&self) -> Result<INT, &'static str> {
match self.0 { match self.0 {
Union::Int(n) => Ok(n), 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()), _ => Err(self.type_name()),
} }
} }
@ -696,6 +1058,8 @@ impl Dynamic {
pub fn as_float(&self) -> Result<FLOAT, &'static str> { pub fn as_float(&self) -> Result<FLOAT, &'static str> {
match self.0 { match self.0 {
Union::Float(n) => Ok(n), 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()), _ => Err(self.type_name()),
} }
} }
@ -705,6 +1069,8 @@ impl Dynamic {
pub fn as_bool(&self) -> Result<bool, &'static str> { pub fn as_bool(&self) -> Result<bool, &'static str> {
match self.0 { match self.0 {
Union::Bool(b) => Ok(b), 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()), _ => Err(self.type_name()),
} }
} }
@ -714,12 +1080,16 @@ impl Dynamic {
pub fn as_char(&self) -> Result<char, &'static str> { pub fn as_char(&self) -> Result<char, &'static str> {
match self.0 { match self.0 {
Union::Char(n) => Ok(n), 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()), _ => Err(self.type_name()),
} }
} }
/// Cast the `Dynamic` as a string and return the string slice. /// Cast the `Dynamic` as a string and return the string slice.
/// Returns the name of the actual type if the cast fails. /// 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> { pub fn as_str(&self) -> Result<&str, &'static str> {
match &self.0 { match &self.0 {
Union::Str(s) => Ok(s), Union::Str(s) => Ok(s),
@ -741,6 +1111,27 @@ impl Dynamic {
match self.0 { match self.0 {
Union::Str(s) => Ok(s), Union::Str(s) => Ok(s),
Union::FnPtr(f) => Ok(f.take_data().0), 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()), _ => 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 { impl From<FnPtr> for Dynamic {
fn from(value: FnPtr) -> Self { fn from(value: FnPtr) -> Self {
Box::new(value).into() Self(Union::FnPtr(Box::new(value)))
} }
} }
impl From<Box<FnPtr>> for Dynamic { impl From<Box<FnPtr>> for Dynamic {

View File

@ -3,6 +3,7 @@
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::engine::{Engine, Imports, State}; use crate::engine::{Engine, Imports, State};
use crate::error::ParseError; use crate::error::ParseError;
use crate::fn_call::ensure_no_data_race;
use crate::fn_native::{IteratorFn, SendSync}; use crate::fn_native::{IteratorFn, SendSync};
use crate::module::{FuncReturn, Module}; use crate::module::{FuncReturn, Module};
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::{optimize_into_ast, OptimizationLevel};
@ -19,6 +20,7 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET};
use crate::{ use crate::{
engine::{make_getter, make_setter, Map}, engine::{make_getter, make_setter, Map},
fn_register::RegisterFn, fn_register::RegisterFn,
token::Token,
}; };
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -62,147 +64,6 @@ impl Engine {
self 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`. /// Register a custom type for use with the `Engine`.
/// The type must implement `Clone`. /// The type must implement `Clone`.
/// ///
@ -731,7 +592,7 @@ impl Engine {
scripts: &[&str], scripts: &[&str],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let stream = lex(scripts, self); let stream = lex(scripts, None, self);
self.parse(&mut stream.peekable(), scope, optimization_level) self.parse(&mut stream.peekable(), scope, optimization_level)
} }
@ -856,7 +717,19 @@ impl Engine {
// Trims the JSON string and add a '#' in front // Trims the JSON string and add a '#' in front
let scripts = ["#", json.trim()]; 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 = let ast =
self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?; self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?;
@ -937,7 +810,7 @@ impl Engine {
script: &str, script: &str,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts, self); let stream = lex(&scripts, None, self);
{ {
let mut peekable = stream.peekable(); let mut peekable = stream.peekable();
self.parse_global_expr(&mut peekable, scope, self.optimization_level) self.parse_global_expr(&mut peekable, scope, self.optimization_level)
@ -1092,7 +965,7 @@ impl Engine {
script: &str, script: &str,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts, self); let stream = lex(&scripts, None, self);
// No need to optimize a lone expression // No need to optimize a lone expression
let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?; let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?;
@ -1225,7 +1098,7 @@ impl Engine {
script: &str, script: &str,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let scripts = [script]; 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)?; let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?;
self.consume_ast_with_scope(scope, &ast) self.consume_ast_with_scope(scope, &ast)
} }
@ -1410,6 +1283,11 @@ impl Engine {
let mut mods = Imports::new(); let mut mods = Imports::new();
let args = args.as_mut(); 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( self.call_script_fn(
scope, &mut mods, &mut state, lib, this_ptr, name, fn_def, args, 0, scope, &mut mods, &mut state, lib, this_ptr, name, fn_def, args, 0,
) )
@ -1433,16 +1311,15 @@ impl Engine {
mut ast: AST, mut ast: AST,
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> AST { ) -> AST {
#[cfg(not(feature = "no_function"))] let lib = if cfg!(not(feature = "no_function")) {
let lib = ast ast.lib()
.lib() .iter_fn()
.iter_fn() .filter(|(_, _, _, f)| f.is_script())
.filter(|(_, _, _, f)| f.is_script()) .map(|(_, _, _, f)| f.get_fn_def().clone())
.map(|(_, _, _, f)| f.get_fn_def().clone()) .collect()
.collect(); } else {
Default::default()
#[cfg(feature = "no_function")] };
let lib = Default::default();
let stmt = mem::take(ast.statements_mut()); let stmt = mem::take(ast.statements_mut());
optimize_into_ast(self, scope, stmt, lib, optimization_level) optimize_into_ast(self, scope, stmt, lib, optimization_level)

View File

@ -19,7 +19,7 @@ use crate::utils::StaticVec;
use crate::any::Variant; use crate::any::Variant;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::parser::{FnAccess, ScriptFnDef}; use crate::parser::ScriptFnDef;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
use crate::module::ModuleResolver; use crate::module::ModuleResolver;
@ -31,12 +31,17 @@ use crate::module::resolvers;
#[cfg(any(not(feature = "no_object"), not(feature = "no_module")))] #[cfg(any(not(feature = "no_object"), not(feature = "no_module")))]
use crate::utils::ImmutableString; use crate::utils::ImmutableString;
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
use crate::any::DynamicWriteLock;
use crate::stdlib::{ use crate::stdlib::{
borrow::Cow, borrow::Cow,
boxed::Box, boxed::Box,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
fmt, format, fmt, format,
iter::{empty, once}, iter::{empty, once},
ops::DerefMut,
string::{String, ToString}, string::{String, ToString},
vec::Vec, vec::Vec,
}; };
@ -44,6 +49,9 @@ use crate::stdlib::{
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::stdlib::any::TypeId; use crate::stdlib::any::TypeId;
#[cfg(not(feature = "no_closure"))]
use crate::stdlib::mem;
/// Variable-sized array of `Dynamic` values. /// Variable-sized array of `Dynamic` values.
/// ///
/// Not available under the `no_index` feature. /// 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: &str = "Fn";
pub const KEYWORD_FN_PTR_CALL: &str = "call"; pub const KEYWORD_FN_PTR_CALL: &str = "call";
pub const KEYWORD_FN_PTR_CURRY: &str = "curry"; pub const KEYWORD_FN_PTR_CURRY: &str = "curry";
pub const KEYWORD_IS_SHARED: &str = "is_shared";
pub const KEYWORD_THIS: &str = "this"; pub const KEYWORD_THIS: &str = "this";
pub const FN_TO_STRING: &str = "to_string"; pub const FN_TO_STRING: &str = "to_string";
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -122,6 +131,11 @@ pub enum ChainType {
pub enum Target<'a> { pub enum Target<'a> {
/// The target is a mutable reference to a `Dynamic` value somewhere. /// The target is a mutable reference to a `Dynamic` value somewhere.
Ref(&'a mut Dynamic), Ref(&'a mut Dynamic),
/// The target is a 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). /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects).
Value(Dynamic), Value(Dynamic),
/// The target is a character inside a String. /// The target is a character inside a String.
@ -136,6 +150,9 @@ impl Target<'_> {
pub fn is_ref(&self) -> bool { pub fn is_ref(&self) -> bool {
match self { match self {
Self::Ref(_) => true, Self::Ref(_) => true,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard(_) => true,
Self::Value(_) => false, Self::Value(_) => false,
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, _) => false, Self::StringChar(_, _, _) => false,
@ -145,16 +162,34 @@ impl Target<'_> {
pub fn is_value(&self) -> bool { pub fn is_value(&self) -> bool {
match self { match self {
Self::Ref(_) => false, Self::Ref(_) => false,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard(_) => false,
Self::Value(_) => true, Self::Value(_) => true,
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, _) => false, 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? /// Is the `Target` a specific type?
#[allow(dead_code)] #[allow(dead_code)]
pub fn is<T: Variant + Clone>(&self) -> bool { pub fn is<T: Variant + Clone>(&self) -> bool {
match self { match self {
Target::Ref(r) => r.is::<T>(), 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>(), Target::Value(r) => r.is::<T>(),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Target::StringChar(_, _, _) => TypeId::of::<T>() == TypeId::of::<char>(), Target::StringChar(_, _, _) => TypeId::of::<T>() == TypeId::of::<char>(),
@ -164,6 +199,9 @@ impl Target<'_> {
pub fn clone_into_dynamic(self) -> Dynamic { pub fn clone_into_dynamic(self) -> Dynamic {
match self { match self {
Self::Ref(r) => r.clone(), // Referenced value is cloned 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 Self::Value(v) => v, // Owned value is simply taken
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, ch) => ch, // Character is taken Self::StringChar(_, _, ch) => ch, // Character is taken
@ -173,6 +211,9 @@ impl Target<'_> {
pub fn as_mut(&mut self) -> &mut Dynamic { pub fn as_mut(&mut self) -> &mut Dynamic {
match self { match self {
Self::Ref(r) => *r, 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, Self::Value(ref mut r) => r,
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, ref mut r) => r, Self::StringChar(_, _, ref mut r) => r,
@ -183,13 +224,18 @@ impl Target<'_> {
pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> { pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> {
match self { match self {
Self::Ref(r) => **r = new_val, Self::Ref(r) => **r = new_val,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard((r, _)) => **r = new_val,
Self::Value(_) => { Self::Value(_) => {
return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(
Position::none(), Position::none(),
))) )))
} }
#[cfg(not(feature = "no_index"))] #[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 // Replace the character at the specified index position
let new_ch = new_val let new_ch = new_val
.as_char() .as_char()
@ -215,9 +261,18 @@ impl Target<'_> {
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl<'a> From<&'a mut Dynamic> for Target<'a> { impl<'a> From<&'a mut Dynamic> for Target<'a> {
fn from(value: &'a mut Dynamic) -> Self { 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) Self::Ref(value)
} }
} }
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl<T: Into<Dynamic>> From<T> for Target<'_> { impl<T: Into<Dynamic>> From<T> for Target<'_> {
fn from(value: T) -> Self { fn from(value: T) -> Self {
@ -228,11 +283,6 @@ impl<T: Into<Dynamic>> From<T> for Target<'_> {
/// [INTERNALS] A type that holds all the current states of the Engine. /// [INTERNALS] A type that holds all the current states of the Engine.
/// Exported under the `internals` feature only. /// 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 /// ## WARNING
/// ///
/// This type is volatile and may change. /// This type is volatile and may change.
@ -264,19 +314,15 @@ pub fn get_script_function_by_signature<'a>(
module: &'a Module, module: &'a Module,
name: &str, name: &str,
params: usize, params: usize,
public_only: bool, pub_only: bool,
) -> Option<&'a ScriptFnDef> { ) -> Option<&'a ScriptFnDef> {
// Qualifiers (none) + function name + number of arguments. // Qualifiers (none) + function name + number of arguments.
let hash_script = calc_fn_hash(empty(), name, params, empty()); let hash_script = calc_fn_hash(empty(), name, params, empty());
let func = module.get_fn(hash_script)?; let func = module.get_fn(hash_script, pub_only)?;
if !func.is_script() { if func.is_script() {
return None; Some(func.get_fn_def())
} } else {
let fn_def = func.get_fn_def(); None
match fn_def.access {
FnAccess::Private if public_only => None,
FnAccess::Private | FnAccess::Public => Some(&fn_def),
} }
} }
@ -401,11 +447,11 @@ impl Default for Engine {
progress: None, progress: None,
// optimization level // optimization level
#[cfg(feature = "no_optimize")] optimization_level: if cfg!(feature = "no_optimize") {
optimization_level: OptimizationLevel::None, OptimizationLevel::None
} else {
#[cfg(not(feature = "no_optimize"))] OptimizationLevel::Simple
optimization_level: OptimizationLevel::Simple, },
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
limits: Limits { limits: Limits {
@ -454,7 +500,7 @@ pub fn search_imports<'s>(
state: &mut State, state: &mut State,
modules: &Box<ModuleRef>, modules: &Box<ModuleRef>,
) -> Result<&'s Module, Box<EvalAltResult>> { ) -> 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 // Qualified - check if the root module is directly indexed
let index = if state.always_search { let index = if state.always_search {
@ -487,7 +533,7 @@ pub fn search_imports_mut<'s>(
state: &mut State, state: &mut State,
modules: &Box<ModuleRef>, modules: &Box<ModuleRef>,
) -> Result<&'s mut Module, Box<EvalAltResult>> { ) -> 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 // Qualified - check if the root module is directly indexed
let index = if state.always_search { 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>( pub fn search_namespace<'s, 'a>(
scope: &'s mut Scope, scope: &'s mut Scope,
mods: &'s mut Imports, mods: &'s mut Imports,
@ -565,7 +612,7 @@ pub fn search_scope_only<'s, 'a>(
if let Some(val) = this_ptr { if let Some(val) = this_ptr {
return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos)); return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos));
} else { } else {
return Err(Box::new(EvalAltResult::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); 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)) Ok((val, name, typ, *pos))
} }
@ -613,11 +667,11 @@ impl Engine {
debug: Box::new(|_| {}), debug: Box::new(|_| {}),
progress: None, progress: None,
#[cfg(feature = "no_optimize")] optimization_level: if cfg!(feature = "no_optimize") {
optimization_level: OptimizationLevel::None, OptimizationLevel::None
} else {
#[cfg(not(feature = "no_optimize"))] OptimizationLevel::Simple
optimization_level: OptimizationLevel::Simple, },
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
limits: Limits { limits: Limits {
@ -661,7 +715,7 @@ impl Engine {
}; };
// Pop the last index value // Pop the last index value
let idx_val = idx_values.pop(); let idx_val = idx_values.pop().unwrap();
match chain_type { match chain_type {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -673,8 +727,9 @@ impl Engine {
Expr::Dot(x) | Expr::Index(x) => { Expr::Dot(x) | Expr::Index(x) => {
let (idx, expr, pos) = x.as_ref(); let (idx, expr, pos) = x.as_ref();
let idx_pos = idx.position(); let idx_pos = idx.position();
let obj_ptr = &mut self let obj_ptr = &mut self.get_indexed_mut(
.get_indexed_mut(state, lib, target, idx_val, idx_pos, false, level)?; state, lib, target, idx_val, idx_pos, false, true, level,
)?;
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level, state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level,
@ -684,54 +739,58 @@ impl Engine {
} }
// xxx[rhs] = new_val // xxx[rhs] = new_val
_ if _new_val.is_some() => { _ if _new_val.is_some() => {
let mut new_val = _new_val.unwrap();
let mut idx_val2 = idx_val.clone(); let mut idx_val2 = idx_val.clone();
match self.get_indexed_mut(state, lib, target, idx_val, pos, true, level) { // `call_setter` is introduced to bypass double mutable borrowing of target
// Indexed value is an owned value - the only possibility is an indexer let _call_setter = match self
// Try to call an index setter .get_indexed_mut(state, lib, target, idx_val, pos, true, false, level)
#[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),
})?;
}
// Indexed value is a reference - update directly // Indexed value is a reference - update directly
Ok(ref mut obj_ptr) => { Ok(ref mut obj_ptr) => {
obj_ptr obj_ptr
.set_value(new_val) .set_value(_new_val.unwrap())
.map_err(|err| err.new_position(rhs.position()))?; .map_err(|err| err.new_position(rhs.position()))?;
None
} }
Err(err) => match *err { Err(err) => match *err {
// No index getter - try to call an index setter // No index getter - try to call an index setter
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
EvalAltResult::ErrorIndexingType(_, _) => { EvalAltResult::ErrorIndexingType(_, _) => {
let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; // Raise error if there is no index getter nor setter
Some(_new_val.unwrap())
self.exec_fn_call(
state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None,
level,
)?;
} }
// Error // Any other error - return
err => return Err(Box::new(err)), 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()) Ok(Default::default())
} }
// xxx[rhs] // xxx[rhs]
_ => self _ => 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)), .map(|v| (v.clone_into_dynamic(), false)),
} }
} }
@ -741,7 +800,12 @@ impl Engine {
match rhs { match rhs {
// xxx.fn_name(arg_expr_list) // xxx.fn_name(arg_expr_list)
Expr::FnCall(x) if x.1.is_none() => { 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 // xxx.module::fn_name(...) - syntax error
Expr::FnCall(_) => unreachable!(), Expr::FnCall(_) => unreachable!(),
@ -749,8 +813,8 @@ impl Engine {
Expr::Property(x) if target.is::<Map>() && _new_val.is_some() => { Expr::Property(x) if target.is::<Map>() && _new_val.is_some() => {
let ((prop, _, _), pos) = x.as_ref(); let ((prop, _, _), pos) = x.as_ref();
let index = prop.clone().into(); let index = prop.clone().into();
let mut val = let mut val = self
self.get_indexed_mut(state, lib, target, index, *pos, true, level)?; .get_indexed_mut(state, lib, target, index, *pos, true, false, level)?;
val.set_value(_new_val.unwrap()) val.set_value(_new_val.unwrap())
.map_err(|err| err.new_position(rhs.position()))?; .map_err(|err| err.new_position(rhs.position()))?;
@ -760,8 +824,9 @@ impl Engine {
Expr::Property(x) if target.is::<Map>() => { Expr::Property(x) if target.is::<Map>() => {
let ((prop, _, _), pos) = x.as_ref(); let ((prop, _, _), pos) = x.as_ref();
let index = prop.clone().into(); let index = prop.clone().into();
let val = let val = self.get_indexed_mut(
self.get_indexed_mut(state, lib, target, index, *pos, false, level)?; state, lib, target, index, *pos, false, false, level,
)?;
Ok((val.clone_into_dynamic(), false)) Ok((val.clone_into_dynamic(), false))
} }
@ -770,7 +835,8 @@ impl Engine {
let ((_, _, setter), pos) = x.as_ref(); let ((_, _, setter), pos) = x.as_ref();
let mut args = [target.as_mut(), _new_val.as_mut().unwrap()]; let mut args = [target.as_mut(), _new_val.as_mut().unwrap()];
self.exec_fn_call( 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(|(v, _)| (v, true))
.map_err(|err| err.new_position(*pos)) .map_err(|err| err.new_position(*pos))
@ -780,7 +846,8 @@ impl Engine {
let ((_, getter, _), pos) = x.as_ref(); let ((_, getter, _), pos) = x.as_ref();
let mut args = [target.as_mut()]; let mut args = [target.as_mut()];
self.exec_fn_call( 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(|(v, _)| (v, false))
.map_err(|err| err.new_position(*pos)) .map_err(|err| err.new_position(*pos))
@ -791,15 +858,21 @@ impl Engine {
let mut val = match sub_lhs { let mut val = match sub_lhs {
Expr::Property(p) => { Expr::Property(p) => {
let ((prop, _, _), _) = p.as_ref(); let ((prop, _, _), pos) = p.as_ref();
let index = prop.clone().into(); 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 // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
Expr::FnCall(x) if x.1.is_none() => { Expr::FnCall(x) if x.1.is_none() => {
let (val, _) = self.make_method_call( let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
state, lib, target, sub_lhs, idx_val, level, 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() val.into()
} }
// {xxx:map}.module::fn_name(...) - syntax error // {xxx:map}.module::fn_name(...) - syntax error
@ -816,28 +889,34 @@ impl Engine {
} }
// xxx.sub_lhs[expr] | xxx.sub_lhs.expr // xxx.sub_lhs[expr] | xxx.sub_lhs.expr
Expr::Index(x) | Expr::Dot(x) => { Expr::Index(x) | Expr::Dot(x) => {
let (sub_lhs, expr, pos) = x.as_ref(); let (sub_lhs, expr, _) = x.as_ref();
match sub_lhs { match sub_lhs {
// xxx.prop[expr] | xxx.prop.expr // xxx.prop[expr] | xxx.prop.expr
Expr::Property(p) => { 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 arg_values = &mut [target.as_mut(), &mut Default::default()];
let args = &mut arg_values[..1]; let args = &mut arg_values[..1];
let (mut val, updated) = self let (mut val, updated) = self
.exec_fn_call( .exec_fn_call(
state, lib, getter, true, 0, args, is_ref, true, None, state, lib, getter, 0, args, is_ref, true, false, None,
level, None, level,
) )
.map_err(|err| err.new_position(*pos))?; .map_err(|err| err.new_position(*pos))?;
let val = &mut val; let val = &mut val;
let target = &mut val.into();
let (result, may_be_changed) = self let (result, may_be_changed) = self
.eval_dot_index_chain_helper( .eval_dot_index_chain_helper(
state, lib, this_ptr, target, expr, idx_values, next_chain, state,
level, _new_val, lib,
this_ptr,
&mut val.into(),
expr,
idx_values,
next_chain,
level,
_new_val,
) )
.map_err(|err| err.new_position(*pos))?; .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 // Re-use args because the first &mut parameter will not be consumed
arg_values[1] = val; arg_values[1] = val;
self.exec_fn_call( self.exec_fn_call(
state, lib, setter, true, 0, arg_values, is_ref, true, state, lib, setter, 0, arg_values, is_ref, true, false,
None, level, None, None, level,
) )
.or_else( .or_else(
|err| match *err { |err| match *err {
@ -864,9 +943,13 @@ impl Engine {
} }
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
Expr::FnCall(x) if x.1.is_none() => { Expr::FnCall(x) if x.1.is_none() => {
let (mut val, _) = self.make_method_call( let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
state, lib, target, sub_lhs, idx_val, level, 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 val = &mut val;
let target = &mut val.into(); let target = &mut val.into();
@ -1001,7 +1084,7 @@ impl Engine {
idx_values.push(Dynamic::from(arg_values)); idx_values.push(Dynamic::from(arg_values));
} }
Expr::FnCall(_) => unreachable!(), 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) => { Expr::Index(x) | Expr::Dot(x) => {
let (lhs, rhs, _) = x.as_ref(); let (lhs, rhs, _) = x.as_ref();
@ -1052,6 +1135,7 @@ impl Engine {
mut _idx: Dynamic, mut _idx: Dynamic,
idx_pos: Position, idx_pos: Position,
_create: bool, _create: bool,
_indexers: bool,
_level: usize, _level: usize,
) -> Result<Target<'a>, Box<EvalAltResult>> { ) -> Result<Target<'a>, Box<EvalAltResult>> {
self.inc_operations(state)?; self.inc_operations(state)?;
@ -1096,10 +1180,10 @@ impl Engine {
map.entry(index).or_insert(Default::default()).into() map.entry(index).or_insert(Default::default()).into()
} else { } else {
let index = _idx let index = _idx
.downcast_ref::<ImmutableString>() .read_lock::<ImmutableString>()
.ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; .ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?;
map.get_mut(index.as_str()) map.get_mut(&*index)
.map(Target::from) .map(Target::from)
.unwrap_or_else(|| Target::from(())) .unwrap_or_else(|| Target::from(()))
}) })
@ -1128,11 +1212,11 @@ impl Engine {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
_ => { _ if _indexers => {
let type_name = val.type_name(); let type_name = val.type_name();
let args = &mut [val, &mut _idx]; let args = &mut [val, &mut _idx];
self.exec_fn_call( 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(|(v, _)| v.into())
.map_err(|err| match *err { .map_err(|err| match *err {
@ -1143,7 +1227,6 @@ impl Engine {
}) })
} }
#[cfg(any(feature = "no_index", feature = "no_object"))]
_ => Err(Box::new(EvalAltResult::ErrorIndexingType( _ => Err(Box::new(EvalAltResult::ErrorIndexingType(
self.map_type_name(val.type_name()).into(), self.map_type_name(val.type_name()).into(),
Position::none(), Position::none(),
@ -1173,26 +1256,23 @@ impl Engine {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(mut rhs_value)) => { Dynamic(Union::Array(mut rhs_value)) => {
let op = "=="; let op = "==";
let mut scope = Scope::new();
// Call the `==` operator to compare each value // Call the `==` operator to compare each value
for value in rhs_value.iter_mut() { for value in rhs_value.iter_mut() {
let def_value = Some(false); let def_value = Some(false);
let args = &mut [&mut lhs_value.clone(), value]; let args = &mut [&mut lhs_value.clone(), value];
let hashes = ( // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
// 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())), calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id()));
0,
);
let (r, _) = self if self
.call_fn_raw( .call_native_fn(state, lib, op, hash, args, false, false, def_value)
&mut scope, mods, state, lib, op, hashes, args, false, false, .map_err(|err| err.new_position(rhs.position()))?
def_value, level, .0
) .as_bool()
.map_err(|err| err.new_position(rhs.position()))?; .unwrap_or(false)
if r.as_bool().unwrap_or(false) { {
return Ok(true.into()); return Ok(true.into());
} }
} }
@ -1202,10 +1282,8 @@ impl Engine {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(rhs_value)) => match lhs_value { Dynamic(Union::Map(rhs_value)) => match lhs_value {
// Only allows String or char // Only allows String or char
Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(s.as_str()).into()), Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(&s).into()),
Dynamic(Union::Char(c)) => { Dynamic(Union::Char(c)) => Ok(rhs_value.contains_key(&c.to_string()).into()),
Ok(rhs_value.contains_key(c.to_string().as_str()).into())
}
_ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))), _ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))),
}, },
Dynamic(Union::Str(rhs_value)) => match lhs_value { Dynamic(Union::Str(rhs_value)) => match lhs_value {
@ -1245,7 +1323,7 @@ impl Engine {
if let Some(val) = this_ptr { if let Some(val) = this_ptr {
Ok(val.clone()) Ok(val.clone())
} else { } else {
Err(Box::new(EvalAltResult::ErrorUnboundedThis((x.0).1))) Err(Box::new(EvalAltResult::ErrorUnboundThis((x.0).1)))
} }
} }
Expr::Variable(_) => { Expr::Variable(_) => {
@ -1274,7 +1352,12 @@ impl Engine {
)), )),
// Normal assignment // Normal assignment
ScopeEntryType::Normal if op.is_empty() => { 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()) Ok(Default::default())
} }
// Op-assignment - in order of precedence: // Op-assignment - in order of precedence:
@ -1289,25 +1372,39 @@ impl Engine {
if let Some(CallableFunction::Method(func)) = self if let Some(CallableFunction::Method(func)) = self
.global_module .global_module
.get_fn(hash_fn) .get_fn(hash_fn, false)
.or_else(|| self.packages.get_fn(hash_fn)) .or_else(|| self.packages.get_fn(hash_fn, false))
{ {
// Overriding exact implementation if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; 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() { } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() {
// Not built in, map to `var = var op rhs` // Not built in, map to `var = var op rhs`
let op = &op[..op.len() - 1]; // extract operator without = let op = &op[..op.len() - 1]; // extract operator without =
let hash = calc_fn_hash(empty(), op, 2, empty());
// Clone the LHS value // Clone the LHS value
let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val]; let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val];
// Run function // Run function
let (value, _) = self let (value, _) = self
.exec_fn_call( .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))?; .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()) Ok(Default::default())
} }
@ -1326,14 +1423,15 @@ impl Engine {
} else { } else {
// Op-assignment - always map to `lhs = lhs op rhs` // Op-assignment - always map to `lhs = lhs op rhs`
let op = &op[..op.len() - 1]; // extract operator without = let op = &op[..op.len() - 1]; // extract operator without =
let hash = calc_fn_hash(empty(), op, 2, empty());
let args = &mut [ let args = &mut [
&mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?, &mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?,
&mut rhs_val, &mut rhs_val,
]; ];
self.exec_fn_call(state, lib, op, true, hash, args, false, false, None, level) self.exec_fn_call(
.map(|(v, _)| v) state, lib, op, 0, args, false, false, false, None, None, level,
.map_err(|err| err.new_position(*op_pos))? )
.map(|(v, _)| v)
.map_err(|err| err.new_position(*op_pos))?
}); });
match lhs_expr { match lhs_expr {
@ -1400,20 +1498,20 @@ impl Engine {
// Normal function call // Normal function call
Expr::FnCall(x) if x.1.is_none() => { 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( self.make_function_call(
scope, mods, state, lib, this_ptr, name, args_expr, *def_val, *hash, *native, scope, mods, state, lib, this_ptr, name, args_expr, *def_val, *hash, *native,
level, false, *capture, level,
) )
.map_err(|err| err.new_position(*pos)) .map_err(|err| err.new_position(*pos))
} }
// Module-qualified function call // Module-qualified function call
Expr::FnCall(x) if x.1.is_some() => { 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( self.make_qualified_function_call(
scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash, scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash,
level, *capture, level,
) )
.map_err(|err| err.new_position(*pos)) .map_err(|err| err.new_position(*pos))
} }
@ -1481,6 +1579,12 @@ impl Engine {
} }
/// Evaluate a statement /// 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( pub(crate) fn eval_stmt(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -1524,7 +1628,7 @@ impl Engine {
// If-else statement // If-else statement
Stmt::IfThenElse(x) => { 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)? self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.as_bool() .as_bool()
@ -1542,7 +1646,7 @@ impl Engine {
// While loop // While loop
Stmt::While(x) => loop { Stmt::While(x) => loop {
let (expr, body) = x.as_ref(); let (expr, body, _) = x.as_ref();
match self match self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
@ -1568,8 +1672,8 @@ impl Engine {
}, },
// Loop statement // Loop statement
Stmt::Loop(body) => loop { Stmt::Loop(x) => loop {
match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) { match self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level) {
Ok(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
EvalAltResult::ErrorLoopBreak(false, _) => (), EvalAltResult::ErrorLoopBreak(false, _) => (),
@ -1581,7 +1685,7 @@ impl Engine {
// For loop // For loop
Stmt::For(x) => { 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 iter_type = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let tid = iter_type.type_id(); let tid = iter_type.type_id();
@ -1597,7 +1701,15 @@ impl Engine {
state.scope_level += 1; state.scope_level += 1;
for loop_var in func(iter_type) { 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) self.inc_operations(state)
.map_err(|err| err.new_position(stmt.position()))?; .map_err(|err| err.new_position(stmt.position()))?;
@ -1627,16 +1739,9 @@ impl Engine {
// Return value // Return value
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => { Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
let expr = x.1.as_ref().unwrap();
Err(Box::new(EvalAltResult::Return( Err(Box::new(EvalAltResult::Return(
self.eval_expr( self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?,
scope,
mods,
state,
lib,
this_ptr,
x.1.as_ref().unwrap(),
level,
)?,
(x.0).1, (x.0).1,
))) )))
} }
@ -1648,15 +1753,8 @@ impl Engine {
// Throw value // Throw value
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => { Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => {
let val = self.eval_expr( let expr = x.1.as_ref().unwrap();
scope, let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
mods,
state,
lib,
this_ptr,
x.1.as_ref().unwrap(),
level,
)?;
Err(Box::new(EvalAltResult::ErrorRuntime( Err(Box::new(EvalAltResult::ErrorRuntime(
val.take_string().unwrap_or_else(|_| "".into()), val.take_string().unwrap_or_else(|_| "".into()),
(x.0).1, (x.0).1,
@ -1672,23 +1770,19 @@ impl Engine {
// Let statement // Let statement
Stmt::Let(x) if x.1.is_some() => { Stmt::Let(x) if x.1.is_some() => {
let ((var_name, _), expr) = x.as_ref(); let ((var_name, _), expr, _) = x.as_ref();
let val = self.eval_expr( let expr = expr.as_ref().unwrap();
scope, let val = self
mods, .eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
state, .clone_inner_data()
lib, .unwrap();
this_ptr,
expr.as_ref().unwrap(),
level,
)?;
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false); scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false);
Ok(Default::default()) Ok(Default::default())
} }
Stmt::Let(x) => { Stmt::Let(x) => {
let ((var_name, _), _) = x.as_ref(); let ((var_name, _), _, _) = x.as_ref();
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
scope.push(var_name, ()); scope.push(var_name, ());
Ok(Default::default()) Ok(Default::default())
@ -1696,8 +1790,11 @@ impl Engine {
// Const statement // Const statement
Stmt::Const(x) if x.1.is_constant() => { Stmt::Const(x) if x.1.is_constant() => {
let ((var_name, _), expr) = x.as_ref(); let ((var_name, _), expr, _) = x.as_ref();
let val = self.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?; 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); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true); scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true);
Ok(Default::default()) Ok(Default::default())
@ -1709,7 +1806,7 @@ impl Engine {
// Import statement // Import statement
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Import(x) => { Stmt::Import(x) => {
let (expr, (name, _pos)) = x.as_ref(); let (expr, (name, _pos), _) = x.as_ref();
// Guard against too many modules // Guard against too many modules
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -1742,8 +1839,8 @@ impl Engine {
// Export statement // Export statement
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Export(list) => { Stmt::Export(x) => {
for ((id, id_pos), rename) in list.iter() { for ((id, id_pos), rename) in x.0.iter() {
// Mark scope variables as public // Mark scope variables as public
if let Some(index) = scope.get_index(id).map(|(i, _)| i) { if let Some(index) = scope.get_index(id).map(|(i, _)| i) {
let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id); let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id);
@ -1757,19 +1854,40 @@ impl Engine {
} }
Ok(Default::default()) 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) self.check_data_size(result)
.map_err(|err| err.new_position(stmt.position())) .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")] #[cfg(feature = "unchecked")]
#[inline(always)] #[inline(always)]
fn check_data_size( fn check_data_size(
&self, &self,
result: Result<Dynamic, Box<EvalAltResult>>, result: Result<Dynamic, Box<EvalAltResult>>,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
return result; result
} }
/// Check a result to ensure that the data size is within allowable limit. /// Check a result to ensure that the data size is within allowable limit.
@ -1779,9 +1897,6 @@ impl Engine {
&self, &self,
result: Result<Dynamic, Box<EvalAltResult>>, result: Result<Dynamic, Box<EvalAltResult>>,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
#[cfg(feature = "unchecked")]
return result;
// If no data size limits, just return // If no data size limits, just return
if self.limits.max_string_size + self.limits.max_array_size + self.limits.max_map_size == 0 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. /// Never appears under the `no_object` and `no_index` features combination.
MalformedInExpr(String), 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. /// A map definition has duplicated property names. Wrapped value is the property name.
/// ///
/// Never appears under the `no_object` feature. /// Never appears under the `no_object` feature.
@ -166,13 +170,14 @@ impl ParseErrorType {
Self::MalformedCallExpr(_) => "Invalid expression in function call arguments", Self::MalformedCallExpr(_) => "Invalid expression in function call arguments",
Self::MalformedIndexExpr(_) => "Invalid index in indexing expression", Self::MalformedIndexExpr(_) => "Invalid index in indexing expression",
Self::MalformedInExpr(_) => "Invalid 'in' expression", Self::MalformedInExpr(_) => "Invalid 'in' expression",
Self::MalformedCapture(_) => "Invalid capturing",
Self::DuplicatedProperty(_) => "Duplicated property in object map literal", Self::DuplicatedProperty(_) => "Duplicated property in object map literal",
Self::ForbiddenConstantExpr(_) => "Expecting a constant", Self::ForbiddenConstantExpr(_) => "Expecting a constant",
Self::PropertyExpected => "Expecting name of a property", Self::PropertyExpected => "Expecting name of a property",
Self::VariableExpected => "Expecting name of a variable", Self::VariableExpected => "Expecting name of a variable",
Self::Reserved(_) => "Invalid use of reserved keyword", Self::Reserved(_) => "Invalid use of reserved keyword",
Self::ExprExpected(_) => "Expecting an expression", 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::FnMissingParams(_) => "Expecting parameters in function declaration",
Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration", Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
Self::FnMissingBody(_) => "Expecting body statement block for 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::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s),
Self::MalformedIndexExpr(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::MalformedInExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }), }
Self::DuplicatedProperty(s) => { Self::DuplicatedProperty(s) => {
write!(f, "Duplicated property '{}' for object map literal", 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. //! Module defining interfaces to native-Rust functions.
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::calc_fn_hash;
use crate::engine::Engine; use crate::engine::Engine;
use crate::module::Module; use crate::module::Module;
use crate::parser::FnAccess;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::{is_valid_identifier, Position}; use crate::token::{is_valid_identifier, Position};
use crate::utils::ImmutableString; use crate::utils::ImmutableString;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::{module::FuncReturn, parser::ScriptFnDef, 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"))] #[cfg(not(feature = "no_function"))]
use crate::stdlib::mem; use crate::stdlib::mem;
@ -20,23 +22,43 @@ use crate::stdlib::rc::Rc;
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
use crate::stdlib::sync::Arc; 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. /// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub trait SendSync: Send + Sync {} pub trait SendSync: Send + Sync {}
/// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
impl<T: Send + Sync> SendSync for T {} impl<T: Send + Sync> SendSync for T {}
/// Trait that maps to `Send + Sync` only under the `sync` feature. /// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub trait SendSync {} pub trait SendSync {}
/// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
impl<T> SendSync for T {} impl<T> SendSync for T {}
/// Immutable reference-counted container
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type Shared<T> = Rc<T>; pub type Shared<T> = Rc<T>;
/// Immutable reference-counted container
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type Shared<T> = Arc<T>; 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. /// 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. /// 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 { 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). /// Call the function pointer with curried arguments (if any).
/// ///
/// The function must be a script-defined function. It cannot be a Rust function. /// If this function is a script-defined function, it must not be marked private.
///
/// To call a Rust function, just call it directly in Rust!
/// ///
/// ## WARNING /// ## WARNING
/// ///
@ -107,14 +127,39 @@ impl FnPtr {
this_ptr: Option<&mut Dynamic>, this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>, mut arg_values: impl AsMut<[Dynamic]>,
) -> FuncReturn<Dynamic> { ) -> FuncReturn<Dynamic> {
let args = self let mut args_data = self
.1 .1
.iter() .iter()
.cloned() .cloned()
.chain(arg_values.as_mut().iter_mut().map(|v| mem::take(v))) .chain(arg_values.as_mut().iter_mut().map(|v| mem::take(v)))
.collect::<StaticVec<_>>(); .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, 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. /// Get a reference to a native Rust function.
/// ///
/// # Panics /// # Panics

View File

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

View File

@ -48,23 +48,7 @@
//! } //! }
//! ``` //! ```
//! //!
//! ## Optional features //! # Documentation
//!
//! | 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). |
//! //!
//! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language. //! 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 mut module = Module::new();
/// let hash = module.set_fn_0("calc", || Ok(42_i64)); /// 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 { pub fn contains_fn(&self, hash_fn: u64, public_only: bool) -> bool {
self.functions.contains_key(&hash_fn) 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. /// Set a Rust function into the module, returning a hash key.
@ -422,7 +432,7 @@ impl Module {
/// let mut module = Module::new(); /// let mut module = Module::new();
/// let hash = module.set_raw_fn("double_or_not", /// let hash = module.set_raw_fn("double_or_not",
/// // Pass parameter types via a slice with TypeId's /// // 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 /// // Fixed closure signature
/// |engine, lib, args| { /// |engine, lib, args| {
/// // 'args' is guaranteed to be the right length and of the correct types /// // '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 /// // Since it is a primary type, it can also be cheaply copied
/// let double = args[1].clone().cast::<bool>(); /// let double = args[1].clone().cast::<bool>();
/// // Get a mutable reference to the first argument. /// // 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; /// let orig = *x;
/// ///
@ -443,7 +453,7 @@ impl Module {
/// Ok(orig) // return Result<T, Box<EvalAltResult>> /// 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>( pub fn set_raw_fn<T: Variant + Clone>(
&mut self, &mut self,
@ -468,7 +478,7 @@ impl Module {
/// ///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// let hash = module.set_fn_0("calc", || Ok(42_i64)); /// 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>( pub fn set_fn_0<T: Variant + Clone>(
&mut self, &mut self,
@ -491,7 +501,7 @@ impl Module {
/// ///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); /// 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>( pub fn set_fn_1<A: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
@ -516,7 +526,7 @@ impl Module {
/// ///
/// let mut module = Module::new(); /// let mut module = Module::new();
/// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) }); /// 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>( pub fn set_fn_1_mut<A: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
@ -524,7 +534,7 @@ impl Module {
func: impl Fn(&mut A) -> FuncReturn<T> + SendSync + 'static, func: impl Fn(&mut A) -> FuncReturn<T> + SendSync + 'static,
) -> u64 { ) -> u64 {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { 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>()]; let arg_types = [TypeId::of::<A>()];
self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) 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 mut module = Module::new();
/// let hash = module.set_getter_fn("value", |x: &mut i64| { Ok(*x) }); /// 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"))] #[cfg(not(feature = "no_object"))]
pub fn set_getter_fn<A: Variant + Clone, T: Variant + Clone>( 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| { /// let hash = module.set_fn_2("calc", |x: i64, y: ImmutableString| {
/// Ok(x + y.len() as i64) /// 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>( pub fn set_fn_2<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
@ -596,7 +606,7 @@ impl Module {
/// let hash = module.set_fn_2_mut("calc", |x: &mut i64, y: ImmutableString| { /// let hash = module.set_fn_2_mut("calc", |x: &mut i64, y: ImmutableString| {
/// *x += y.len() as i64; Ok(*x) /// *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>( pub fn set_fn_2_mut<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
@ -605,9 +615,9 @@ impl Module {
) -> u64 { ) -> u64 {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); 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>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f)))
@ -628,7 +638,7 @@ impl Module {
/// *x = y.len() as i64; /// *x = y.len() as i64;
/// Ok(()) /// Ok(())
/// }); /// });
/// assert!(module.contains_fn(hash)); /// assert!(module.contains_fn(hash, true));
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn set_setter_fn<A: Variant + Clone, B: Variant + Clone>( 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| { /// let hash = module.set_indexer_get_fn(|x: &mut i64, y: ImmutableString| {
/// Ok(*x + y.len() as i64) /// 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_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -677,7 +687,7 @@ impl Module {
/// let hash = module.set_fn_3("calc", |x: i64, y: ImmutableString, z: i64| { /// let hash = module.set_fn_3("calc", |x: i64, y: ImmutableString, z: i64| {
/// Ok(x + y.len() as i64 + z) /// Ok(x + y.len() as i64 + z)
/// }); /// });
/// assert!(module.contains_fn(hash)); /// assert!(module.contains_fn(hash, true));
/// ``` /// ```
pub fn set_fn_3< pub fn set_fn_3<
A: Variant + Clone, A: Variant + Clone,
@ -714,7 +724,7 @@ impl Module {
/// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: ImmutableString, z: i64| { /// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: ImmutableString, z: i64| {
/// *x += y.len() as i64 + z; Ok(*x) /// *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< pub fn set_fn_3_mut<
A: Variant + Clone, A: Variant + Clone,
@ -729,9 +739,9 @@ impl Module {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>(); 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>()]; 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))) 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; /// *x = y.len() as i64 + value;
/// Ok(()) /// Ok(())
/// }); /// });
/// assert!(module.contains_fn(hash)); /// assert!(module.contains_fn(hash, true));
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -763,9 +773,9 @@ impl Module {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>(); 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>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn( self.set_fn(
@ -796,8 +806,8 @@ impl Module {
/// Ok(()) /// Ok(())
/// } /// }
/// ); /// );
/// assert!(module.contains_fn(hash_get)); /// assert!(module.contains_fn(hash_get, true));
/// assert!(module.contains_fn(hash_set)); /// assert!(module.contains_fn(hash_set, true));
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[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: ()| { /// let hash = module.set_fn_4("calc", |x: i64, y: ImmutableString, z: i64, _w: ()| {
/// Ok(x + y.len() as i64 + z) /// Ok(x + y.len() as i64 + z)
/// }); /// });
/// assert!(module.contains_fn(hash)); /// assert!(module.contains_fn(hash, true));
/// ``` /// ```
pub fn set_fn_4< pub fn set_fn_4<
A: Variant + Clone, 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: ()| { /// let hash = module.set_fn_4_mut("calc", |x: &mut i64, y: ImmutableString, z: i64, _w: ()| {
/// *x += y.len() as i64 + z; Ok(*x) /// *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< pub fn set_fn_4_mut<
A: Variant + Clone, A: Variant + Clone,
@ -886,9 +896,9 @@ impl Module {
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>(); let c = mem::take(args[2]).cast::<C>();
let d = mem::take(args[3]).cast::<D>(); 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 = [ let arg_types = [
TypeId::of::<A>(), TypeId::of::<A>(),
@ -903,8 +913,14 @@ impl Module {
/// ///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`. /// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
/// It is also returned by the `set_fn_XXX` calls. /// It is also returned by the `set_fn_XXX` calls.
pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&Func> { pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&Func> {
self.functions.get(&hash_fn).map(|(_, _, _, v)| v) 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. /// 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 `u64` hash is calculated by the function `crate::calc_fn_hash` and must match
/// the hash calculated by `index_all_sub_modules`. /// the hash calculated by `index_all_sub_modules`.
pub(crate) fn get_qualified_fn( pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&Func> {
&self, self.all_functions.get(&hash_qualified_fn)
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(),
))
})
} }
/// Merge another module into this module. /// Merge another module into this module.

View File

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

View File

@ -2,36 +2,29 @@ use crate::def_package;
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::parser::INT; use crate::parser::INT;
#[cfg(not(feature = "unchecked"))]
use crate::{result::EvalAltResult, token::Position}; use crate::{result::EvalAltResult, token::Position};
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT; use crate::parser::FLOAT;
#[cfg(not(feature = "no_float"))]
#[cfg(feature = "no_std")]
use num_traits::*;
#[cfg(not(feature = "unchecked"))]
use num_traits::{ use num_traits::{
identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl,
CheckedShr, CheckedSub, 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::{
use crate::stdlib::ops::{Add, Div, Mul, Neg, Rem, Sub}; boxed::Box,
fmt::Display,
#[cfg(feature = "unchecked")] format,
use crate::stdlib::ops::{Shl, Shr}; ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub},
};
#[cfg(not(feature = "unchecked"))]
use crate::stdlib::{boxed::Box, fmt::Display, format};
// Checked add // Checked add
#[cfg(not(feature = "unchecked"))] pub fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
pub(crate) fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
x.checked_add(&y).ok_or_else(|| { x.checked_add(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Addition overflow: {} + {}", x, y), format!("Addition overflow: {} + {}", x, y),
@ -40,8 +33,7 @@ pub(crate) fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
}) })
} }
// Checked subtract // Checked subtract
#[cfg(not(feature = "unchecked"))] pub fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
pub(crate) fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
x.checked_sub(&y).ok_or_else(|| { x.checked_sub(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Subtraction underflow: {} - {}", x, y), format!("Subtraction underflow: {} - {}", x, y),
@ -50,8 +42,7 @@ pub(crate) fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
}) })
} }
// Checked multiply // Checked multiply
#[cfg(not(feature = "unchecked"))] pub fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
pub(crate) fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
x.checked_mul(&y).ok_or_else(|| { x.checked_mul(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Multiplication overflow: {} * {}", x, y), format!("Multiplication overflow: {} * {}", x, y),
@ -60,8 +51,7 @@ pub(crate) fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
}) })
} }
// Checked divide // Checked divide
#[cfg(not(feature = "unchecked"))] pub fn div<T>(x: T, y: T) -> FuncReturn<T>
pub(crate) fn div<T>(x: T, y: T) -> FuncReturn<T>
where where
T: Display + CheckedDiv + PartialEq + Zero, T: Display + CheckedDiv + PartialEq + Zero,
{ {
@ -81,8 +71,7 @@ where
}) })
} }
// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX // Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
#[cfg(not(feature = "unchecked"))] pub fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
pub(crate) fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
x.checked_neg().ok_or_else(|| { x.checked_neg().ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Negation overflow: -{}", x), format!("Negation overflow: -{}", x),
@ -91,8 +80,7 @@ pub(crate) fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
}) })
} }
// Checked absolute // Checked absolute
#[cfg(not(feature = "unchecked"))] pub fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> FuncReturn<T> {
pub(crate) 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 // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics
// when the number is ::MIN instead of returning ::MIN itself. // when the number is ::MIN instead of returning ::MIN itself.
if x >= <T as Zero>::zero() { 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 // 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> { fn add_u<T: Add>(x: T, y: T) -> FuncReturn<<T as Add>::Output> {
Ok(x + y) Ok(x + y)
} }
// Unchecked subtract - may panic on underflow // 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> { fn sub_u<T: Sub>(x: T, y: T) -> FuncReturn<<T as Sub>::Output> {
Ok(x - y) Ok(x - y)
} }
// Unchecked multiply - may panic on overflow // 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> { fn mul_u<T: Mul>(x: T, y: T) -> FuncReturn<<T as Mul>::Output> {
Ok(x * y) Ok(x * y)
} }
// Unchecked divide - may panic when dividing by zero // 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> { fn div_u<T: Div>(x: T, y: T) -> FuncReturn<<T as Div>::Output> {
Ok(x / y) Ok(x / y)
} }
// Unchecked negative - may panic on overflow // 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> { fn neg_u<T: Neg>(x: T) -> FuncReturn<<T as Neg>::Output> {
Ok(-x) Ok(-x)
} }
// Unchecked absolute - may panic on overflow // 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> fn abs_u<T>(x: T) -> FuncReturn<<T as Neg>::Output>
where where
T: Neg + PartialOrd + Default + Into<<T as Neg>::Output>, 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) Ok(x ^ y)
} }
// Checked left-shift // Checked left-shift
#[cfg(not(feature = "unchecked"))] pub fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
pub(crate) fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
// Cannot shift by a negative number of bits // Cannot shift by a negative number of bits
if y < 0 { if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return 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 // Checked right-shift
#[cfg(not(feature = "unchecked"))] pub fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
pub(crate) fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
// Cannot shift by a negative number of bits // Cannot shift by a negative number of bits
if y < 0 { if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return 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 // Unchecked left-shift - may panic if shifting by a negative number of bits
#[cfg(feature = "unchecked")] pub fn shl_u<T: Shl<T>>(x: T, y: T) -> FuncReturn<<T as Shl<T>>::Output> {
pub(crate) fn shl_u<T: Shl<T>>(x: T, y: T) -> FuncReturn<<T as Shl<T>>::Output> {
Ok(x.shl(y)) Ok(x.shl(y))
} }
// Unchecked right-shift - may panic if shifting by a negative number of bits // Unchecked right-shift - may panic if shifting by a negative number of bits
#[cfg(feature = "unchecked")] pub fn shr_u<T: Shr<T>>(x: T, y: T) -> FuncReturn<<T as Shr<T>>::Output> {
pub(crate) fn shr_u<T: Shr<T>>(x: T, y: T) -> FuncReturn<<T as Shr<T>>::Output> {
Ok(x.shr(y)) Ok(x.shr(y))
} }
// Checked modulo // Checked modulo
#[cfg(not(feature = "unchecked"))] pub fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> {
pub(crate) fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> {
x.checked_rem(&y).ok_or_else(|| { x.checked_rem(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Modulo division by zero or overflow: {} % {}", x, y), 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 // 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> { fn modulo_u<T: Rem>(x: T, y: T) -> FuncReturn<<T as Rem>::Output> {
Ok(x % y) Ok(x % y)
} }
// Checked power // Checked power
#[cfg(not(feature = "unchecked"))] pub fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> { if cfg!(not(feature = "only_i32")) {
#[cfg(not(feature = "only_i32"))] if y > (u32::MAX as INT) {
if y > (u32::MAX as INT) { Err(Box::new(EvalAltResult::ErrorArithmetic(
Err(Box::new(EvalAltResult::ErrorArithmetic( format!("Integer raised to too large an index: {} ~ {}", x, y),
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),
Position::none(), Position::none(),
)) )))
}) } else if y < 0 {
} Err(Box::new(EvalAltResult::ErrorArithmetic(
format!("Integer raised to a negative index: {} ~ {}", x, y),
#[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),
Position::none(), 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) // Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
#[cfg(feature = "unchecked")] pub fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> {
pub(crate) fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> {
Ok(x.pow(y as u32)) Ok(x.pow(y as u32))
} }
// Floating-point power - always well-defined // Floating-point power - always well-defined
#[cfg(not(feature = "no_float"))] #[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)) Ok(x.powf(y))
} }
// Checked power // Checked power
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "unchecked"))] pub fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
// Raise to power that is larger than an i32 // Raise to power that is larger than an i32
if y > (i32::MAX as INT) { if y > (i32::MAX as INT) {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return 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)) Ok(x.powi(y as i32))
} }
// Unchecked power - may be incorrect if the power index is too high (> i32::MAX) // Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
#[cfg(feature = "unchecked")]
#[cfg(not(feature = "no_float"))] #[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)) Ok(x.powi(y as i32))
} }
@ -309,11 +281,8 @@ macro_rules! reg_sign {
} }
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
#[cfg(not(feature = "only_i32"))] if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
#[cfg(not(feature = "only_i64"))] if cfg!(not(feature = "unchecked")) {
{
#[cfg(not(feature = "unchecked"))]
{
// Checked basic arithmetic // Checked basic arithmetic
reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "-", sub, 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, ">>", shr, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "%", modulo, 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, "+", add, i128, u128);
reg_op!(lib, "-", sub, i128, u128); reg_op!(lib, "-", sub, i128, u128);
reg_op!(lib, "*", mul, 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 // Unchecked basic arithmetic
reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "-", sub_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, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "%", modulo_u, 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, "+", add_u, i128, u128);
reg_op!(lib, "-", sub_u, i128, u128); reg_op!(lib, "-", sub_u, i128, u128);
reg_op!(lib, "*", mul_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); reg_sign!(lib, "sign", INT, i8, i16, i32, i64);
#[cfg(not(target_arch = "wasm32"))] if cfg!(not(target_arch = "wasm32")) {
reg_sign!(lib, "sign", INT, i128); reg_sign!(lib, "sign", INT, i128);
}
} }
// Basic arithmetic for floating-point - no need to check // 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, "+", add_u, f32);
reg_op!(lib, "-", sub_u, f32); reg_op!(lib, "-", sub_u, f32);
reg_op!(lib, "*", mul_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); reg_sign!(lib, "sign", f64, f64);
} }
#[cfg(not(feature = "only_i32"))] if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64); 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_and, i8, u8, i16, u16, i32, u32, u64);
reg_op!(lib, "^", binary_xor, 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_or, i128, u128);
reg_op!(lib, "&", binary_and, i128, u128); reg_op!(lib, "&", binary_and, i128, u128);
reg_op!(lib, "^", binary_xor, 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"))] #[cfg(not(feature = "no_float"))]
{ {
// Checked power // Checked power
#[cfg(not(feature = "unchecked"))] if cfg!(not(feature = "unchecked")) {
lib.set_fn_2("~", pow_f_i); lib.set_fn_2("~", pow_f_i);
} else {
// Unchecked power lib.set_fn_2("~", pow_f_i_u);
#[cfg(feature = "unchecked")] }
lib.set_fn_2("~", pow_f_i_u);
// Floating-point modulo and power // Floating-point modulo and power
reg_op!(lib, "%", modulo_u, f32); reg_op!(lib, "%", modulo_u, f32);
@ -413,19 +375,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
} }
// Checked unary // Checked unary
#[cfg(not(feature = "unchecked"))] if cfg!(not(feature = "unchecked")) {
{
reg_unary!(lib, "-", neg, INT); reg_unary!(lib, "-", neg, INT);
reg_unary!(lib, "abs", abs, INT); reg_unary!(lib, "abs", abs, INT);
#[cfg(not(feature = "only_i32"))] if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
#[cfg(not(feature = "only_i64"))]
{
reg_unary!(lib, "-", neg, i8, i16, i32, i64); reg_unary!(lib, "-", neg, i8, i16, i32, i64);
reg_unary!(lib, "abs", abs, 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, "-", neg, i128);
reg_unary!(lib, "abs", abs, i128); reg_unary!(lib, "abs", abs, i128);
} }
@ -433,19 +391,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
} }
// Unchecked unary // Unchecked unary
#[cfg(feature = "unchecked")] if cfg!(feature = "unchecked") {
{
reg_unary!(lib, "-", neg_u, INT); reg_unary!(lib, "-", neg_u, INT);
reg_unary!(lib, "abs", abs_u, INT); reg_unary!(lib, "abs", abs_u, INT);
#[cfg(not(feature = "only_i32"))] if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
#[cfg(not(feature = "only_i64"))]
{
reg_unary!(lib, "-", neg_u, i8, i16, i32, i64); reg_unary!(lib, "-", neg_u, i8, i16, i32, i64);
reg_unary!(lib, "abs", abs_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, "-", neg_u, i128);
reg_unary!(lib, "abs", abs_u, i128); reg_unary!(lib, "abs", abs_u, i128);
} }

View File

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

View File

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

View File

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

View File

@ -5,22 +5,20 @@ use crate::parser::INT;
use crate::parser::FLOAT; use crate::parser::FLOAT;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "unchecked"))]
use crate::{result::EvalAltResult, token::Position}; use crate::{result::EvalAltResult, token::Position};
#[cfg(not(feature = "no_float"))]
#[cfg(feature = "no_std")] #[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 = "no_float"))]
#[cfg(not(feature = "unchecked"))]
use crate::stdlib::{boxed::Box, format}; use crate::stdlib::{boxed::Box, format};
#[allow(dead_code)]
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]
#[cfg(not(feature = "unchecked"))]
pub const MAX_INT: INT = i32::MAX; pub const MAX_INT: INT = i32::MAX;
#[allow(dead_code)]
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "unchecked"))]
pub const MAX_INT: INT = i64::MAX; pub const MAX_INT: INT = i64::MAX;
def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { 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: INT| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT));
#[cfg(not(feature = "only_i32"))] if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
#[cfg(not(feature = "only_i64"))]
{
lib.set_fn_1("to_float", |x: i8| Ok(x as FLOAT)); 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: u8| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: i16| 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: i64| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: u64| 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: i128| Ok(x as FLOAT));
lib.set_fn_1("to_float", |x: u128| 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)); lib.set_fn_1("to_int", |ch: char| Ok(ch as INT));
#[cfg(not(feature = "only_i32"))] if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
#[cfg(not(feature = "only_i64"))]
{
lib.set_fn_1("to_int", |x: i8| Ok(x as INT)); 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: u8| Ok(x as INT));
lib.set_fn_1("to_int", |x: i16| 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)); 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: i32| Ok(x as INT));
lib.set_fn_1("to_int", |x: u64| Ok(x as INT)); lib.set_fn_1("to_int", |x: u64| Ok(x as INT));
#[cfg(feature = "only_i64")] if cfg!(feature = "only_i64") {
lib.set_fn_1("to_int", |x: u32| Ok(x as INT)); lib.set_fn_1("to_int", |x: u32| Ok(x as INT));
}
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
#[cfg(not(feature = "unchecked"))] if cfg!(not(feature = "unchecked")) {
{
lib.set_fn_1( lib.set_fn_1(
"to_int", "to_int",
|x: f32| { |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: f32| Ok(x as INT));
lib.set_fn_1("to_int", |x: f64| 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); self.0.insert(0, package);
} }
/// Does the specified function hash key exist in the `PackagesCollection`? /// Does the specified function hash key exist in the `PackagesCollection`?
pub fn contains_fn(&self, hash: u64) -> bool { #[allow(dead_code)]
self.0.iter().any(|p| p.contains_fn(hash)) 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. /// 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 self.0
.iter() .iter()
.map(|p| p.get_fn(hash)) .map(|p| p.get_fn(hash, public_only))
.find(|f| f.is_some()) .find(|f| f.is_some())
.flatten() .flatten()
} }

View File

@ -21,7 +21,7 @@ fn to_debug<T: Debug>(x: &mut T) -> FuncReturn<ImmutableString> {
Ok(format!("{:?}", x).into()) Ok(format!("{:?}", x).into())
} }
fn to_string<T: Display>(x: &mut T) -> FuncReturn<ImmutableString> { fn to_string<T: Display>(x: &mut T) -> FuncReturn<ImmutableString> {
Ok(format!("{}", x).into()) Ok(x.to_string().into())
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
fn format_map(x: &mut Map) -> FuncReturn<ImmutableString> { 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); reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, ImmutableString);
#[cfg(not(feature = "only_i32"))] if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, KEYWORD_PRINT, to_string, i8, u8, i16, u16, i32, u32); 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, FN_TO_STRING, to_string, i8, u8, i16, u16, i32, u32);
reg_op!(lib, KEYWORD_DEBUG, to_debug, 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, FN_TO_STRING, to_string, i64, u64);
reg_op!(lib, KEYWORD_DEBUG, to_debug, 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, KEYWORD_PRINT, to_string, i128, u128);
reg_op!(lib, FN_TO_STRING, to_string, i128, u128); reg_op!(lib, FN_TO_STRING, to_string, i128, u128);
reg_op!(lib, KEYWORD_DEBUG, to_debug, i128, u128); reg_op!(lib, KEYWORD_DEBUG, to_debug, i128, u128);

View File

@ -1,6 +1,7 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::def_package; use crate::def_package;
use crate::engine::Engine; use crate::engine::Engine;
use crate::fn_native::FnPtr;
use crate::module::{FuncReturn, Module}; use crate::module::{FuncReturn, Module};
use crate::parser::{ImmutableString, INT}; use crate::parser::{ImmutableString, INT};
use crate::utils::StaticVec; use crate::utils::StaticVec;
@ -15,7 +16,7 @@ use crate::stdlib::{
any::TypeId, any::TypeId,
boxed::Box, boxed::Box,
fmt::Display, fmt::Display,
format, format, mem,
string::{String, ToString}, string::{String, ToString},
vec::Vec, vec::Vec,
}; };
@ -88,20 +89,17 @@ macro_rules! reg_op {
} }
def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { 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)); 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)); lib.set_fn_2("+", |_: (), y: ImmutableString| Ok(y));
#[cfg(not(feature = "only_i32"))] if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) {
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "+", append, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(lib, "+", append, i8, u8, i16, u16, i32, i64, u32, u64);
reg_op!(lib, "+", prepend, 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, "+", append, i128, u128);
reg_op!(lib, "+", prepend, i128, u128); reg_op!(lib, "+", prepend, i128, u128);
} }
@ -228,7 +226,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
"pad", "pad",
&[TypeId::of::<ImmutableString>(), TypeId::of::<INT>(), TypeId::of::<char>()], &[TypeId::of::<ImmutableString>(), TypeId::of::<INT>(), TypeId::of::<char>()],
|_engine: &Engine, _: &Module, args: &mut [&mut Dynamic]| { |_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 // Check if string will be over max size limit
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -242,8 +240,8 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
} }
if len > 0 { if len > 0 {
let ch = *args[2].downcast_ref::< char>().unwrap(); let ch = mem::take(args[2]).cast::<char>();
let s = args[0].downcast_mut::<ImmutableString>().unwrap(); let mut s = args[0].write_lock::<ImmutableString>().unwrap();
let orig_len = s.chars().count(); 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. /// An error has occurred inside a called function.
/// Wrapped values are the name of the function and the interior error. /// Wrapped values are the name of the function and the interior error.
ErrorInFunctionCall(String, Box<EvalAltResult>, Position), ErrorInFunctionCall(String, Box<EvalAltResult>, Position),
/// Access to `this` that is not bounded. /// Access to `this` that is not bound.
ErrorUnboundedThis(Position), ErrorUnboundThis(Position),
/// Non-boolean operand encountered for boolean operator. Wrapped value is the operator. /// Non-boolean operand encountered for boolean operator. Wrapped value is the operator.
ErrorBooleanArgMismatch(String, Position), ErrorBooleanArgMismatch(String, Position),
/// Non-character value encountered where a character is required. /// Non-character value encountered where a character is required.
@ -69,6 +69,8 @@ pub enum EvalAltResult {
ErrorVariableNotFound(String, Position), ErrorVariableNotFound(String, Position),
/// Usage of an unknown module. Wrapped value is the name of the module. /// Usage of an unknown module. Wrapped value is the name of the module.
ErrorModuleNotFound(String, Position), 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. /// Assignment to an inappropriate LHS (left-hand-side) expression.
ErrorAssignmentToUnknownLHS(Position), ErrorAssignmentToUnknownLHS(Position),
/// Assignment to a constant variable. /// Assignment to a constant variable.
@ -112,7 +114,7 @@ impl EvalAltResult {
Self::ErrorParsing(p, _) => p.desc(), Self::ErrorParsing(p, _) => p.desc(),
Self::ErrorInFunctionCall(_, _, _) => "Error in called function", Self::ErrorInFunctionCall(_, _, _) => "Error in called function",
Self::ErrorFunctionNotFound(_, _) => "Function not found", 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::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
Self::ErrorCharMismatch(_) => "Character expected", Self::ErrorCharMismatch(_) => "Character expected",
Self::ErrorNumericIndexExpr(_) => { Self::ErrorNumericIndexExpr(_) => {
@ -136,7 +138,8 @@ impl EvalAltResult {
Self::ErrorLogicGuard(_) => "Boolean value expected", Self::ErrorLogicGuard(_) => "Boolean value expected",
Self::ErrorFor(_) => "For loop expects an array, object map, or range", Self::ErrorFor(_) => "For loop expects an array, object map, or range",
Self::ErrorVariableNotFound(_, _) => "Variable not found", 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(_) => { Self::ErrorAssignmentToUnknownLHS(_) => {
"Assignment to an unsupported left-hand side expression" "Assignment to an unsupported left-hand side expression"
} }
@ -180,14 +183,15 @@ impl fmt::Display for EvalAltResult {
Self::ErrorFunctionNotFound(s, _) Self::ErrorFunctionNotFound(s, _)
| Self::ErrorVariableNotFound(s, _) | Self::ErrorVariableNotFound(s, _)
| Self::ErrorDataRace(s, _)
| Self::ErrorModuleNotFound(s, _) => write!(f, "{}: '{}'", desc, 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::ErrorIndexingType(_, _)
| Self::ErrorNumericIndexExpr(_) | Self::ErrorNumericIndexExpr(_)
| Self::ErrorStringIndexExpr(_) | Self::ErrorStringIndexExpr(_)
| Self::ErrorUnboundedThis(_) | Self::ErrorUnboundThis(_)
| Self::ErrorImportExpr(_) | Self::ErrorImportExpr(_)
| Self::ErrorLogicGuard(_) | Self::ErrorLogicGuard(_)
| Self::ErrorFor(_) | Self::ErrorFor(_)
@ -276,7 +280,7 @@ impl EvalAltResult {
Self::ErrorParsing(_, pos) Self::ErrorParsing(_, pos)
| Self::ErrorFunctionNotFound(_, pos) | Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos) | Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorUnboundedThis(pos) | Self::ErrorUnboundThis(pos)
| Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos) | Self::ErrorCharMismatch(pos)
| Self::ErrorArrayBounds(_, _, pos) | Self::ErrorArrayBounds(_, _, pos)
@ -289,6 +293,7 @@ impl EvalAltResult {
| Self::ErrorFor(pos) | Self::ErrorFor(pos)
| Self::ErrorVariableNotFound(_, pos) | Self::ErrorVariableNotFound(_, pos)
| Self::ErrorModuleNotFound(_, pos) | Self::ErrorModuleNotFound(_, pos)
| Self::ErrorDataRace(_, pos)
| Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorAssignmentToConstant(_, pos)
| Self::ErrorMismatchOutputType(_, _, pos) | Self::ErrorMismatchOutputType(_, _, pos)
@ -316,7 +321,7 @@ impl EvalAltResult {
Self::ErrorParsing(_, pos) Self::ErrorParsing(_, pos)
| Self::ErrorFunctionNotFound(_, pos) | Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorInFunctionCall(_, _, pos) | Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorUnboundedThis(pos) | Self::ErrorUnboundThis(pos)
| Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos) | Self::ErrorCharMismatch(pos)
| Self::ErrorArrayBounds(_, _, pos) | Self::ErrorArrayBounds(_, _, pos)
@ -329,6 +334,7 @@ impl EvalAltResult {
| Self::ErrorFor(pos) | Self::ErrorFor(pos)
| Self::ErrorVariableNotFound(_, pos) | Self::ErrorVariableNotFound(_, pos)
| Self::ErrorModuleNotFound(_, pos) | Self::ErrorModuleNotFound(_, pos)
| Self::ErrorDataRace(_, pos)
| Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToUnknownLHS(pos)
| Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorAssignmentToConstant(_, pos)
| Self::ErrorMismatchOutputType(_, _, 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. /// Get the value of an entry in the Scope, starting from the last.
/// ///
/// # Examples /// # Examples
@ -329,11 +337,8 @@ impl<'a> Scope<'a> {
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42); /// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ``` /// ```
pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> { pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
self.0 self.get_entry(name)
.iter() .and_then(|Entry { value, .. }| value.clone().clone_inner_data::<T>())
.rev()
.find(|Entry { name: key, .. }| name == key)
.and_then(|Entry { value, .. }| value.downcast_ref::<T>().cloned())
} }
/// Update the value of the named entry. /// Update the value of the named entry.
@ -384,13 +389,30 @@ impl<'a> Scope<'a> {
self 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. /// Get an iterator to entries in the Scope.
#[cfg(not(feature = "no_module"))]
pub(crate) fn into_iter(self) -> impl Iterator<Item = Entry<'a>> { pub(crate) fn into_iter(self) -> impl Iterator<Item = Entry<'a>> {
self.0.into_iter() 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> { pub(crate) fn to_iter(&self) -> impl Iterator<Item = &Entry> {
self.0.iter().rev() // Always search a Scope in reverse order 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(); /// let (name, value) = iter.next().unwrap();
/// assert_eq!(name, "x"); /// assert_eq!(name, "x");
/// assert_eq!(value.clone().cast::<i64>(), 42); /// assert_eq!(value.cast::<i64>(), 42);
/// ///
/// let (name, value) = iter.next().unwrap(); /// let (name, value) = iter.next().unwrap();
/// assert_eq!(name, "foo"); /// 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 self.0
.iter() .iter()
.map(|Entry { name, value, .. }| (name.as_ref(), value)) .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(value) if value.is::<u128>() => self.deserialize_u128(visitor),
Union::Variant(_) => self.type_error(), 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. /// When searching for functions, packages loaded later are preferred.
/// In other words, loaded packages are searched in reverse order. /// 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 // Push the package to the top - packages are searched in reverse order
self.packages.push(package); self.packages.push(package.into());
self self
} }

View File

@ -2,7 +2,7 @@
use crate::engine::{ use crate::engine::{
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, 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; use crate::error::LexError;
@ -21,7 +21,6 @@ use crate::stdlib::{
iter::Peekable, iter::Peekable,
str::{Chars, FromStr}, str::{Chars, FromStr},
string::{String, ToString}, string::{String, ToString},
vec::Vec,
}; };
type LERR = LexError; 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. /// 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, /// # Limitations
/// meaning they go up to a maximum of 65,535 lines and characters per line. ///
/// 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. /// 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)] #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position { pub struct Position {
@ -43,6 +45,13 @@ pub struct Position {
impl Position { impl Position {
/// Create a new `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 { pub fn new(line: u16, position: u16) -> Self {
assert!(line != 0, "line cannot be zero"); 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> { pub fn line(&self) -> Option<usize> {
if self.is_none() { if self.is_none() {
None None
@ -85,7 +94,6 @@ impl Position {
/// # Panics /// # Panics
/// ///
/// Panics if already at beginning of a line - cannot rewind to a previous line. /// Panics if already at beginning of a line - cannot rewind to a previous line.
///
pub(crate) fn rewind(&mut self) { pub(crate) fn rewind(&mut self) {
assert!(!self.is_none(), "cannot rewind Position::none"); assert!(!self.is_none(), "cannot rewind Position::none");
assert!(self.pos > 0, "cannot rewind at position 0"); assert!(self.pos > 0, "cannot rewind at position 0");
@ -104,7 +112,7 @@ impl Position {
} }
/// Create a `Position` representing no position. /// Create a `Position` representing no position.
pub(crate) fn none() -> Self { pub fn none() -> Self {
Self { line: 0, pos: 0 } Self { line: 0, pos: 0 }
} }
@ -146,9 +154,9 @@ impl fmt::Debug for Position {
pub enum Token { pub enum Token {
/// An `INT` constant. /// An `INT` constant.
IntegerConstant(INT), 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"))] #[cfg(not(feature = "no_float"))]
FloatConstant(FLOAT), FloatConstant(FLOAT),
/// An identifier. /// An identifier.
@ -249,7 +257,7 @@ pub enum Token {
And, And,
/// `fn` /// `fn`
/// ///
/// Never appears under the `no_function` feature. /// Reserved under the `no_function` feature.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Fn, Fn,
/// `continue` /// `continue`
@ -284,22 +292,22 @@ pub enum Token {
PowerOfAssign, PowerOfAssign,
/// `private` /// `private`
/// ///
/// Never appears under the `no_function` feature. /// Reserved under the `no_function` feature.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Private, Private,
/// `import` /// `import`
/// ///
/// Never appears under the `no_module` feature. /// Reserved under the `no_module` feature.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Import, Import,
/// `export` /// `export`
/// ///
/// Never appears under the `no_module` feature. /// Reserved under the `no_module` feature.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Export, Export,
/// `as` /// `as`
/// ///
/// Never appears under the `no_module` feature. /// Reserved under the `no_module` feature.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
As, As,
/// A lexer error. /// A lexer error.
@ -492,11 +500,16 @@ impl Token {
#[cfg(feature = "no_module")] #[cfg(feature = "no_module")]
"import" | "export" | "as" => Reserved(syntax.into()), "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()) 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, _ => 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 { pub fn is_keyword(&self) -> bool {
use Token::*; 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? /// Is this token a custom keyword?
pub fn is_custom(&self) -> bool { pub fn is_custom(&self) -> bool {
match self { match self {
@ -726,8 +748,8 @@ pub fn parse_string_literal(
pos: &mut Position, pos: &mut Position,
enclosing_char: char, enclosing_char: char,
) -> Result<String, (LexError, Position)> { ) -> Result<String, (LexError, Position)> {
let mut result = Vec::new(); let mut result: StaticVec<char> = Default::default();
let mut escape = String::with_capacity(12); let mut escape: StaticVec<char> = Default::default();
loop { loop {
let next_char = stream.get_next().ok_or((LERR::UnterminatedString, *pos))?; let next_char = stream.get_next().ok_or((LERR::UnterminatedString, *pos))?;
@ -766,8 +788,8 @@ pub fn parse_string_literal(
// \x??, \u????, \U???????? // \x??, \u????, \U????????
ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => { ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => {
let mut seq = escape.clone(); let mut seq = escape.clone();
seq.push(ch);
escape.clear(); escape.clear();
seq.push(ch);
let mut out_val: u32 = 0; let mut out_val: u32 = 0;
let len = match ch { let len = match ch {
@ -778,23 +800,31 @@ pub fn parse_string_literal(
}; };
for _ in 0..len { for _ in 0..len {
let c = stream let c = stream.get_next().ok_or_else(|| {
.get_next() (
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?; LERR::MalformedEscapeSequence(seq.iter().cloned().collect()),
*pos,
)
})?;
seq.push(c); seq.push(c);
pos.advance(); pos.advance();
out_val *= 16; out_val *= 16;
out_val += c out_val += c.to_digit(16).ok_or_else(|| {
.to_digit(16) (
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?; LERR::MalformedEscapeSequence(seq.iter().cloned().collect()),
*pos,
)
})?;
} }
result.push( result.push(char::from_u32(out_val).ok_or_else(|| {
char::from_u32(out_val) (
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq), *pos))?, LERR::MalformedEscapeSequence(seq.into_iter().collect()),
); *pos,
)
})?);
} }
// \{enclosing_char} - escaped // \{enclosing_char} - escaped
@ -807,7 +837,12 @@ pub fn parse_string_literal(
ch if enclosing_char == ch && escape.is_empty() => break, ch if enclosing_char == ch && escape.is_empty() => break,
// Unknown escape sequence // 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 // Cannot have new-lines inside string literals
'\n' => { '\n' => {
@ -962,7 +997,7 @@ fn get_next_token_inner(
// digit ... // digit ...
('0'..='9', _) => { ('0'..='9', _) => {
let mut result = Vec::new(); let mut result: StaticVec<char> = Default::default();
let mut radix_base: Option<u32> = None; let mut radix_base: Option<u32> = None;
result.push(c); result.push(c);
@ -1083,7 +1118,7 @@ fn get_next_token_inner(
|err| (Token::LexError(Box::new(err.0)), err.1), |err| (Token::LexError(Box::new(err.0)), err.1),
|result| { |result| {
let mut chars = result.chars(); let mut chars = result.chars();
let first = chars.next(); let first = chars.next().unwrap();
if chars.next().is_some() { if chars.next().is_some() {
( (
@ -1091,10 +1126,7 @@ fn get_next_token_inner(
start_pos, start_pos,
) )
} else { } else {
( (Token::CharConstant(first), start_pos)
Token::CharConstant(first.expect("should be Some")),
start_pos,
)
} }
}, },
)) ))
@ -1367,7 +1399,7 @@ fn get_identifier(
start_pos: Position, start_pos: Position,
first_char: char, first_char: char,
) -> Option<(Token, Position)> { ) -> Option<(Token, Position)> {
let mut result = Vec::new(); let mut result: StaticVec<_> = Default::default();
result.push(first_char); result.push(first_char);
while let Some(next_char) = stream.peek_next() { 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 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 { if !is_valid_identifier {
return Some(( 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 { pub fn is_valid_identifier(name: impl Iterator<Item = char>) -> bool {
let mut first_alphabetic = false; 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")] #[cfg(feature = "unicode-xid-ident")]
#[inline(always)]
fn is_id_first_alphabetic(x: char) -> bool { fn is_id_first_alphabetic(x: char) -> bool {
unicode_xid::UnicodeXID::is_xid_start(x) unicode_xid::UnicodeXID::is_xid_start(x)
} }
#[cfg(feature = "unicode-xid-ident")] #[cfg(feature = "unicode-xid-ident")]
#[inline(always)]
fn is_id_continue(x: char) -> bool { fn is_id_continue(x: char) -> bool {
unicode_xid::UnicodeXID::is_xid_continue(x) unicode_xid::UnicodeXID::is_xid_continue(x)
} }
#[cfg(not(feature = "unicode-xid-ident"))] #[cfg(not(feature = "unicode-xid-ident"))]
#[inline(always)]
fn is_id_first_alphabetic(x: char) -> bool { fn is_id_first_alphabetic(x: char) -> bool {
x.is_ascii_alphabetic() x.is_ascii_alphabetic()
} }
#[cfg(not(feature = "unicode-xid-ident"))] #[cfg(not(feature = "unicode-xid-ident"))]
#[inline(always)]
fn is_id_continue(x: char) -> bool { fn is_id_continue(x: char) -> bool {
x.is_ascii_alphanumeric() || x == '_' x.is_ascii_alphanumeric() || x == '_'
} }
@ -1486,13 +1542,15 @@ pub struct TokenIterator<'a, 'e> {
pos: Position, pos: Position,
/// Input character stream. /// Input character stream.
stream: MultiInputsStream<'a>, 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, '_> { impl<'a> Iterator for TokenIterator<'a, '_> {
type Item = (Token, Position); type Item = (Token, Position);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
match ( let token = match (
get_next_token(&mut self.stream, &mut self.state, &mut self.pos), get_next_token(&mut self.stream, &mut self.state, &mut self.pos),
self.engine.disabled_symbols.as_ref(), self.engine.disabled_symbols.as_ref(),
self.engine.custom_keywords.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)) Some((Token::Reserved(token.syntax().into()), pos))
} }
(r, _, _) => r, (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. /// 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 { TokenIterator {
engine, engine,
state: TokenizeState { 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(), streams: input.iter().map(|s| s.chars().peekable()).collect(),
index: 0, index: 0,
}, },
map,
} }
} }

View File

@ -1,8 +1,4 @@
//! Module containing various utility types and functions. //! 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}; use crate::fn_native::{shared_make_mut, shared_take, Shared};
@ -10,15 +6,13 @@ use crate::stdlib::{
any::TypeId, any::TypeId,
borrow::Borrow, borrow::Borrow,
boxed::Box, boxed::Box,
cmp::Ordering,
fmt, fmt,
hash::{BuildHasher, Hash, Hasher}, hash::{BuildHasher, Hash, Hasher},
iter::FromIterator, iter::FromIterator,
mem, ops::{Add, AddAssign, Deref},
mem::MaybeUninit,
ops::{Add, AddAssign, Deref, DerefMut, Drop, Index, IndexMut},
str::FromStr, str::FromStr,
string::{String, ToString}, string::{String, ToString},
vec::Vec,
}; };
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
@ -27,6 +21,8 @@ use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use ahash::AHasher; use ahash::AHasher;
use smallvec::SmallVec;
/// A hasher that only takes one single `u64` and returns it as a hash key. /// A hasher that only takes one single `u64` and returns it as a hash key.
/// ///
/// # Panics /// # Panics
@ -92,549 +88,10 @@ pub fn calc_fn_spec<'a>(
s.finish() 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. /// Exported under the `internals` feature only.
/// pub type StaticVec<T> = SmallVec<[T; 4]>;
/// 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
}
}
/// The system immutable string type. /// 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 { impl Borrow<str> for ImmutableString {
fn borrow(&self) -> &str { fn borrow(&self) -> &str {
self.0.as_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 { impl Add<&str> for ImmutableString {
type Output = Self; 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 { impl ImmutableString {
/// Consume the `ImmutableString` and convert it into a `String`. /// Consume the `ImmutableString` and convert it into a `String`.
/// If there are other references to the same string, a cloned copy is returned. /// 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"))] #![cfg(not(feature = "no_function"))]
use rhai::{ 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; use std::any::TypeId;
@ -82,6 +83,70 @@ fn test_call_fn_private() -> Result<(), Box<EvalAltResult>> {
Ok(()) 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] #[test]
fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> { fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
let calc_func = Func::<(INT, INT, INT), INT>::create_from_script( let calc_func = Func::<(INT, INT, INT), INT>::create_from_script(
@ -113,80 +178,3 @@ fn test_anonymous_fn() -> Result<(), Box<EvalAltResult>> {
Ok(()) 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"), .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(()) Ok(())

View File

@ -1,5 +1,5 @@
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, INT};
#[test] #[test]
fn test_functions() -> Result<(), Box<EvalAltResult>> { fn test_functions() -> Result<(), Box<EvalAltResult>> {
@ -120,3 +120,53 @@ fn test_function_pointers() -> Result<(), Box<EvalAltResult>> {
Ok(()) 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(); let m2 = m.get_sub_module("universe").unwrap();
assert!(m2.contains_var("answer")); 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); assert_eq!(m2.get_var_value::<INT>("answer").unwrap(), 41);

View File

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

View File

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