diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..df310705 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -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' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1370e03e..11216cc3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - closures pull_request: {} jobs: @@ -29,6 +30,7 @@ jobs: - "--features no_object" - "--features no_function" - "--features no_module" + - "--features no_closure" - "--features unicode-xid-ident" toolchain: [stable] experimental: [false] diff --git a/Cargo.toml b/Cargo.toml index 66bdba28..f6ffdea4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,15 +14,16 @@ include = [ "Cargo.toml" ] keywords = [ "scripting" ] -categories = [ "no-std", "embedded", "parser-implementations" ] +categories = [ "no-std", "embedded", "wasm", "parser-implementations" ] [dependencies] num-traits = { version = "0.2.11", default-features = false } +smallvec = { version = "1.4.1", default-features = false } [features] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"] default = [] -plugins = [] +plugins = [] # custom plugins support unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync no_optimize = [] # no script optimizer @@ -32,12 +33,13 @@ only_i64 = [] # set INT=i64 (default) and disable support for all other in no_index = [] # no arrays and indexing no_object = [] # no custom objects no_function = [] # no script-defined functions +no_closure = [] # no automatic sharing and capture of anonymous functions to external variables no_module = [] # no modules internals = [] # expose internal data structures -unicode-xid-ident = ["unicode-xid"] # allow unicode-xid for identifiers. +unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. # compiling for no-std -no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] +no_std = [ "no_closure", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] [profile.release] lto = "fat" diff --git a/README.md b/README.md index 1516abcc..7aef538c 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,11 @@ Standard features * Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust. * Fairly low compile-time overhead. * Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM). -* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to - one single source file, all with names starting with `"unsafe_"`). +* Relatively little `unsafe` code (yes there are some for performance reasons). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * [Function overloading](https://schungx.github.io/rhai/language/overload.html). * [Operator overloading](https://schungx.github.io/rhai/rust/operators.html). -* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html). +* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html) with additional support for [currying](https://schungx.github.io/rhai/language/fn-curry.html). * Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html). * Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html). * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). diff --git a/RELEASES.md b/RELEASES.md index 364e9b10..1d2a842b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -6,25 +6,41 @@ Version 0.18.0 This version adds: -* Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions. +* Binding the `this` pointer in a function pointer `call`. +* Anonymous functions (in Rust closure syntax). Simplifies creation of single-use ad-hoc functions. * Currying of function pointers. +* Closures - auto-currying of anonymous functions to capture shared variables from the external scope. +* Capturing call scope via `func!(...)` syntax. New features ------------ * `call` can now be called function-call style for function pointers - this is to handle builds with `no_object`. -* Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc. +* Reserve language keywords, such as `print`, `eval`, `call`, `this` etc. * `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`. -* Anonymous functions in the syntax of a closure, e.g. `|x, y, z| x + y - z`. +* Anonymous functions are supported in the syntax of a Rust closure, e.g. `|x, y, z| x + y - z`. * Custom syntax now works even without the `internals` feature. -* Currying of function pointers is supported via the `curry` keyword. +* Currying of function pointers is supported via the new `curry` keyword. +* Automatic currying of anonymous functions to capture shared variables from the external scope. +* Capturing of the calling scope for function call via the `func!(...)` syntax. * `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`. -* New `unicode-xid-ident` feature to allow unicode-xid for identifiers. +* New `unicode-xid-ident` feature to allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) for identifiers. +* `Scope::iter_raw` returns an iterator with a reference to the underlying `Dynamic` value (which may be shared). Breaking changes ---------------- +* Language keywords are now _reserved_ (even when disabled) and they can no longer be used as variable names. * Function signature for defining custom syntax is simplified. +* `Engine::register_raw_fn_XXX` API shortcuts are removed. +* `PackagesCollection::get_fn`, `PackagesCollection::contains_fn`, `Module::get_fn` and `Module::contains_fn` now take an additional `public_only` parameter indicating whether only public functions are accepted. +* The iterator returned by `Scope::iter` now contains a clone of the `Dynamic` value (unshared). +* `Engine::load_package` takes any type that is `Into`. + +Housekeeping +------------ + +* Most compilation warnings are eliminated via feature gates. Version 0.17.0 @@ -58,7 +74,7 @@ New features * `Engine::disable_symbol` to surgically disable keywords and/or operators. * `Engine::register_custom_operator` to define a custom operator. * `Engine::register_custom_syntax` to define a custom syntax. -* New low-level API `Engine::register_raw_fn` and `Engine::register_raw_fn_XXX`. +* New low-level API `Engine::register_raw_fn`. * New low-level API `Module::set_raw_fn` mirroring `Engine::register_raw_fn`. * `AST::clone_functions_only`, `AST::clone_functions_only_filtered` and `AST::clone_statements_only` to clone only part of an `AST`. * The boolean `^` (XOR) operator is added. diff --git a/benches/eval_expression.rs b/benches/eval_expression.rs index ceaaaa17..82fd1b78 100644 --- a/benches/eval_expression.rs +++ b/benches/eval_expression.rs @@ -48,7 +48,7 @@ fn bench_eval_expression_optimized_simple(bench: &mut Bencher) { 2 > 1 && "something" != "nothing" || "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && - [array, with, spaces].len <= #{prop:name}.len && + [array, has, spaces].len <= #{prop:name}.len && modifierTest + 1000 / 2 > (80 * 100 % 2) "#; @@ -65,7 +65,7 @@ fn bench_eval_expression_optimized_full(bench: &mut Bencher) { 2 > 1 && "something" != "nothing" || "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && - [array, with, spaces].len <= #{prop:name}.len && + [array, has, spaces].len <= #{prop:name}.len && modifierTest + 1000 / 2 > (80 * 100 % 2) "#; @@ -82,7 +82,7 @@ fn bench_eval_call_expression(bench: &mut Bencher) { 2 > 1 && "something" != "nothing" || "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && - [array, with, spaces].len <= #{prop:name}.len && + [array, has, spaces].len <= #{prop:name}.len && modifierTest + 1000 / 2 > (80 * 100 % 2) "#; @@ -97,7 +97,7 @@ fn bench_eval_call(bench: &mut Bencher) { 2 > 1 && "something" != "nothing" || "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && - [array, with, spaces].len <= #{prop:name}.len && + [array, has, spaces].len <= #{prop:name}.len && modifierTest + 1000 / 2 > (80 * 100 % 2) "#; diff --git a/benches/parsing.rs b/benches/parsing.rs index 8e84bd8d..6bde9dd1 100644 --- a/benches/parsing.rs +++ b/benches/parsing.rs @@ -32,7 +32,7 @@ fn bench_parse_full(bench: &mut Bencher) { 2 > 1 && "something" != "nothing" || "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && - [array, with, spaces].len <= #{prop:name}.len && + [array, has, spaces].len <= #{prop:name}.len && modifierTest + 1000 / 2 > (80 * 100 % 2) "#; @@ -109,7 +109,7 @@ fn bench_parse_optimize_simple(bench: &mut Bencher) { 2 > 1 && "something" != "nothing" || "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && - [array, with, spaces].len <= #{prop:name}.len && + [array, has, spaces].len <= #{prop:name}.len && modifierTest + 1000 / 2 > (80 * 100 % 2) "#; @@ -125,7 +125,7 @@ fn bench_parse_optimize_full(bench: &mut Bencher) { 2 > 1 && "something" != "nothing" || "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && - [array, with, spaces].len <= #{prop:name}.len && + [array, has, spaces].len <= #{prop:name}.len && modifierTest + 1000 / 2 > (80 * 100 % 2) "#; diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 3592dbc5..3b23b321 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -79,6 +79,7 @@ The Rhai Scripting Language 4. [Function Pointers](language/fn-ptr.md) 5. [Anonymous Functions](language/fn-anon.md) 6. [Currying](language/fn-curry.md) + 7. [Closures](language/fn-closure.md) 16. [Print and Debug](language/print-debug.md) 17. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) @@ -99,21 +100,22 @@ The Rhai Scripting Language 8. [Maximum Call Stack Depth](safety/max-call-stack.md) 9. [Maximum Statement Depth](safety/max-stmt-depth.md) 7. [Advanced Topics](advanced.md) - 1. [Object-Oriented Programming (OOP)](language/oop.md) - 2. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md) - 3. [Script Optimization](engine/optimize/index.md) + 1. [Capture Scope for Function Call](language/fn-capture.md) + 2. [Object-Oriented Programming (OOP)](language/oop.md) + 3. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md) + 4. [Script Optimization](engine/optimize/index.md) 1. [Optimization Levels](engine/optimize/optimize-levels.md) 2. [Re-Optimize an AST](engine/optimize/reoptimize.md) 3. [Eager Function Evaluation](engine/optimize/eager.md) 4. [Side-Effect Considerations](engine/optimize/side-effects.md) 5. [Volatility Considerations](engine/optimize/volatility.md) 6. [Subtle Semantic Changes](engine/optimize/semantics.md) - 4. [Low-Level API](rust/register-raw.md) - 5. [Use as DSL](engine/dsl.md) + 5. [Low-Level API](rust/register-raw.md) + 6. [Use as DSL](engine/dsl.md) 1. [Disable Keywords and/or Operators](engine/disable.md) 2. [Custom Operators](engine/custom-op.md) 3. [Extending with Custom Syntax](engine/custom-syntax.md) - 6. [Eval Statement](language/eval.md) + 7. [Eval Statement](language/eval.md) 8. [Appendix](appendix/index.md) 1. [Keywords](appendix/keywords.md) 2. [Operators and Symbols](appendix/operators.md) diff --git a/doc/src/about/features.md b/doc/src/about/features.md index 13b2e4c1..6c2e3965 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -14,7 +14,7 @@ Easy * Easily [call a script-defined function]({{rootUrl}}/engine/call-fn.md) from Rust. -* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) to do checked arithmetic operations); +* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) to do checked arithmetic operations, and [`smallvec`](https://crates.io/crates/smallvec/)); for [`no-std`] builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. Fast @@ -35,23 +35,22 @@ Dynamic * Organize code base with dynamically-loadable [modules]. -* Dynamic dispatch via [function pointers]. +* Dynamic dispatch via [function pointers] with additional support for [currying]. + +* Closures via [automatic currying] with capturing shared variables from the external scope. * Some support for [object-oriented programming (OOP)][OOP]. -* Serialization/deserialization support via [`serde`]. - Safe ---- -* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to - one single source file, all with names starting with `"unsafe_"`). +* Relatively little `unsafe` code (yes there are some for performance reasons). + +* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`). Rugged ------ -* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`). - * Protected against malicious attacks (such as [stack-overflow][maximum call stack depth], [over-sized data][maximum length of strings], and [runaway scripts][maximum number of operations] etc.) that may come from untrusted third-party user-land scripts. * Track script evaluation [progress] and manually terminate a script run. @@ -61,6 +60,8 @@ Flexible * Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature). +* Serialization/deserialization support via [`serde`](https://crates.io/crates/serde). + * Support for [minimal builds] by excluding unneeded language [features]. * Supports [most build targets](targets.md) including `no-std` and [WASM]. diff --git a/doc/src/advanced.md b/doc/src/advanced.md index cd958c73..6b79fe70 100644 --- a/doc/src/advanced.md +++ b/doc/src/advanced.md @@ -5,6 +5,8 @@ Advanced Topics This section covers advanced features such as: +* [Capture the calling scope]({{rootUrl}}/language/fn-capture.md) in a function call. + * Simulated [Object Oriented Programming (OOP)][OOP]. * [`serde`] integration. diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index 7c8dbaff..d896739a 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -3,32 +3,69 @@ Keywords List {{#include ../links.md}} -| Keyword | Description | Not available under | -| :-------------------: | ---------------------------------------- | :-----------------: | -| `true` | Boolean true literal | | -| `false` | Boolean false literal | | -| `let` | Variable declaration | | -| `const` | Constant declaration | | -| `if` | If statement | | -| `else` | else block of if statement | | -| `while` | While loop | | -| `loop` | Infinite loop | | -| `for` | For loop | | -| `in` | Containment test, part of for loop | | -| `continue` | Continue a loop at the next iteration | | -| `break` | Loop breaking | | -| `return` | Return value | | -| `throw` | Throw exception | | -| `import` | Import module | [`no_module`] | -| `export` | Export variable | [`no_module`] | -| `as` | Alias for variable export | [`no_module`] | -| `private` | Mark function private | [`no_function`] | -| `fn` (lower-case `f`) | Function definition | [`no_function`] | -| `Fn` (capital `F`) | Function to create a [function pointer] | | -| `call` | Call a [function pointer] | | -| `curry` | Curry a [function pointer] | | -| `this` | Reference to base object for method call | [`no_function`] | -| `type_of` | Get type name of value | | -| `print` | Print value | | -| `debug` | Print value in debug format | | -| `eval` | Evaluate script | | +| Keyword | Description | Inactive under | Overloadable | +| :-------------------: | ---------------------------------------- | :-------------: | :----------: | +| `true` | Boolean true literal | | No | +| `false` | Boolean false literal | | No | +| `let` | Variable declaration | | No | +| `const` | Constant declaration | | No | +| `is_shared` | Is a value shared? | | No | +| `if` | If statement | | No | +| `else` | else block of if statement | | No | +| `while` | While loop | | No | +| `loop` | Infinite loop | | No | +| `for` | For loop | | No | +| `in` | Containment test, part of for loop | | No | +| `continue` | Continue a loop at the next iteration | | No | +| `break` | Loop breaking | | No | +| `return` | Return value | | No | +| `throw` | Throw exception | | No | +| `import` | Import module | [`no_module`] | No | +| `export` | Export variable | [`no_module`] | No | +| `as` | Alias for variable export | [`no_module`] | No | +| `private` | Mark function private | [`no_function`] | No | +| `fn` (lower-case `f`) | Function definition | [`no_function`] | No | +| `Fn` (capital `F`) | Function to create a [function pointer] | | Yes | +| `call` | Call a [function pointer] | | No | +| `curry` | Curry a [function pointer] | | No | +| `this` | Reference to base object for method call | [`no_function`] | No | +| `type_of` | Get type name of value | | Yes | +| `print` | Print value | | Yes | +| `debug` | Print value in debug format | | Yes | +| `eval` | Evaluate script | | Yes | + + +Reserved Keywords +----------------- + +| Keyword | Potential usage | +| --------- | --------------------- | +| `var` | Variable declaration | +| `static` | Variable declaration | +| `shared` | Share value | +| `do` | Looping | +| `each` | Looping | +| `then` | Control flow | +| `goto` | Control flow | +| `exit` | Control flow | +| `switch` | Matching | +| `match` | Matching | +| `case` | Matching | +| `public` | Function/field access | +| `new` | Constructor | +| `try` | Trap exception | +| `catch` | Catch exception | +| `use` | Import namespace | +| `with` | Scope | +| `module` | Module | +| `package` | Package | +| `spawn` | Threading | +| `go` | Threading | +| `await` | Async | +| `async` | Async | +| `sync` | Async | +| `yield` | Async | +| `default` | Special value | +| `void` | Special value | +| `null` | Special value | +| `nil` | Special value | diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md index 89fdf792..8f308106 100644 --- a/doc/src/engine/custom-syntax.md +++ b/doc/src/engine/custom-syntax.md @@ -114,10 +114,7 @@ Any custom syntax must include an _implementation_ of it. The function signature of an implementation is: -```rust -Fn(engine: &Engine, context: &mut EvalContext, scope: &mut Scope, inputs: &[Expression]) - -> Result> -``` +> `Fn(engine: &Engine, context: &mut EvalContext, scope: &mut Scope, inputs: &[Expression]) -> Result>` where: diff --git a/doc/src/engine/dsl.md b/doc/src/engine/dsl.md index 5b381d4a..8f326ed1 100644 --- a/doc/src/engine/dsl.md +++ b/doc/src/engine/dsl.md @@ -15,6 +15,17 @@ The [`Engine::eval_expression_XXX`][`eval_expression`] API can be used to restri a script to expressions only. +Unicode Standard Annex #31 Identifiers +------------------------------------- + +Variable names and other identifiers do not necessarily need to be ASCII-only. + +The [`unicode-xid-ident`] feature, when turned on, causes Rhai to allow variable names and identifiers +that follow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/). + +This is sometimes useful in a non-English DSL. + + Disable Keywords and/or Operators -------------------------------- diff --git a/doc/src/language/fn-anon.md b/doc/src/language/fn-anon.md index 21ceb7a3..8b514509 100644 --- a/doc/src/language/fn-anon.md +++ b/doc/src/language/fn-anon.md @@ -22,7 +22,7 @@ fn print_obj() { print(this.data); } ``` The above can be replaced by using _anonymous functions_ which have the same syntax as Rust's closures -(but they are **NOT** closures, merely syntactic sugar): +(but they are **NOT** real closures, merely syntactic sugar): ```rust let obj = #{ @@ -49,9 +49,12 @@ fn anon_fn_1001(x) { this.data -= x; } fn anon_fn_1002() { print this.data; } ``` -WARNING - NOT Closures ----------------------- + +WARNING - NOT Real Closures +-------------------------- Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves -**not** closures. In particular, they do not capture their running environment. They are more like -Rust's function pointers. +**not** real closures. + +In particular, they capture their execution environment via [automatic currying] +(disabled via [`no_closure`]). diff --git a/doc/src/language/fn-capture.md b/doc/src/language/fn-capture.md new file mode 100644 index 00000000..08052826 --- /dev/null +++ b/doc/src/language/fn-capture.md @@ -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. diff --git a/doc/src/language/fn-closure.md b/doc/src/language/fn-closure.md new file mode 100644 index 00000000..2f6ce2c5 --- /dev/null +++ b/doc/src/language/fn-closure.md @@ -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>` in normal builds, `Arc>` 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. diff --git a/doc/src/language/fn-curry.md b/doc/src/language/fn-curry.md index 8ee103a9..c223d8cd 100644 --- a/doc/src/language/fn-curry.md +++ b/doc/src/language/fn-curry.md @@ -28,3 +28,12 @@ let curried = curry(func, 21); // function-call style also works curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)' // only one argument is now required ``` + + +Automatic Currying +------------------ + +[Anonymous functions] defined via a closure syntax _capture_ external variables +that are not shadowed inside the function's scope. + +This is accomplished via [automatic currying]. diff --git a/doc/src/language/fn-namespaces.md b/doc/src/language/fn-namespaces.md index 5bd8e16f..9516f1e6 100644 --- a/doc/src/language/fn-namespaces.md +++ b/doc/src/language/fn-namespaces.md @@ -69,17 +69,22 @@ The only practical way to ensure that a function is a correct one is to use [mod i.e. define the function in a separate module and then [`import`] it: ```rust -message.rhai: +---------------- +| message.rhai | +---------------- - fn message() { "Hello!" } +fn message() { "Hello!" } -script.rhai: - fn say_hello() { - import "message" as msg; - print(msg::message()); - } - say_hello(); +--------------- +| script.rhai | +--------------- + +fn say_hello() { + import "message" as msg; + print(msg::message()); +} +say_hello(); ``` @@ -94,18 +99,23 @@ defined _within the script_. When called later, those functions will be searche current global namespace and may not be found. ```rust -greeting.rhai: +----------------- +| greeting.rhai | +----------------- - fn message() { "Hello!" }; +fn message() { "Hello!" }; - fn say_hello() { print(message()); } +fn say_hello() { print(message()); } - say_hello(); // 'message' is looked up in the global namespace +say_hello(); // 'message' is looked up in the global namespace -script.rhai: - import "greeting" as g; - g::say_hello(); // <- error: function not found - 'message' +--------------- +| script.rhai | +--------------- + +import "greeting" as g; +g::say_hello(); // <- error: function not found - 'message' ``` In the example above, although the module `greeting.rhai` loads fine (`"Hello!"` is printed), @@ -118,24 +128,29 @@ as possible and avoid cross-calling them from each other. A [function pointer] to call another function within a module-defined function: ```rust -greeting.rhai: +----------------- +| greeting.rhai | +----------------- - fn message() { "Hello!" }; +fn message() { "Hello!" }; - fn say_hello(msg_func) { // 'msg_func' is a function pointer - print(msg_func.call()); // call via the function pointer - } +fn say_hello(msg_func) { // 'msg_func' is a function pointer + print(msg_func.call()); // call via the function pointer +} - say_hello(); // 'message' is looked up in the global namespace +say_hello(); // 'message' is looked up in the global namespace -script.rhai: - import "greeting" as g; +--------------- +| script.rhai | +--------------- - fn my_msg() { - import "greeting" as g; // <- must import again here... - g::message() // <- ... otherwise will not find module 'g' - } +import "greeting" as g; - g::say_hello(Fn("my_msg")); // prints 'Hello!' +fn my_msg() { + import "greeting" as g; // <- must import again here... + g::message() // <- ... otherwise will not find module 'g' +} + +g::say_hello(Fn("my_msg")); // prints 'Hello!' ``` diff --git a/doc/src/language/functions.md b/doc/src/language/functions.md index b6c0a85f..3bd15cd0 100644 --- a/doc/src/language/functions.md +++ b/doc/src/language/functions.md @@ -16,7 +16,7 @@ fn sub(x, y,) { // trailing comma in parameters list is OK add(2, 3) == 5; -sub(2, 3,) == -1; // trailing comma in arguments list is OK +sub(2, 3,) == -1; // trailing comma in arguments list is OK ``` @@ -127,5 +127,5 @@ x.change(); // call 'change' in method-call style, 'this' binds to 'x' x == 42; // 'x' is changed! -change(); // <- error: `this` is unbounded +change(); // <- error: `this` is unbound ``` diff --git a/doc/src/language/keywords.md b/doc/src/language/keywords.md index 836e9b0c..4ddaae8f 100644 --- a/doc/src/language/keywords.md +++ b/doc/src/language/keywords.md @@ -5,18 +5,21 @@ Keywords The following are reserved keywords in Rhai: -| Keywords | Usage | Not available under feature | -| ------------------------------------------------- | --------------------- | :-------------------------: | -| `true`, `false` | Boolean constants | | -| `let`, `const` | Variable declarations | | -| `if`, `else` | Control flow | | -| `while`, `loop`, `for`, `in`, `continue`, `break` | Looping | | -| `fn`, `private` | Functions | [`no_function`] | -| `return` | Return values | | -| `throw` | throw exceptions | | -| `import`, `export`, `as` | Modules | [`no_module`] | -| `Fn`, `call` | Function pointers | | -| `type_of`, `print`, `debug`, `eval` | Special functions | | +| Active keywords | Reserved keywords | Usage | Inactive under feature | +| ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: | +| `true`, `false` | | Boolean constants | | +| `let`, `const` | `var`, `static` | Variable declarations | | +| `is_shared` | | Shared values | [`no_closure`] | +| `if`, `else` | `then`, `goto`, `exit` | Control flow | | +| | `switch`, `match`, `case` | Matching | | +| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | +| `fn`, `private` | `public`, `new` | Functions | [`no_function`] | +| `return` | | Return values | | +| `throw` | `try`, `catch` | Throw exceptions | | +| `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] | +| `Fn`, `call`, `curry` | | Function pointers | | +| | `spawn`, `go`, `sync`, `async`, `await`, `yield` | Threading/async | | +| `type_of`, `print`, `debug`, `eval` | | Special functions | | +| | `default`, `void`, `null`, `nil` | Special values | | -Keywords cannot be the name of a [function] or [variable], unless the relevant feature is enabled. -For example, `fn` is a valid variable name under [`no_function`]. +Keywords cannot become the name of a [function] or [variable], even when they are disabled. diff --git a/doc/src/language/method.md b/doc/src/language/method.md index c2e9e99e..290d1a06 100644 --- a/doc/src/language/method.md +++ b/doc/src/language/method.md @@ -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`
`(obj: &mut T, x: U, y: V) {}` | +| Rhai script | _n_ | `this` | `fn method(x, y) {}` | + + +`&mut` is Efficient (Except for `ImmutableString`) +------------------------------------------------ Using a `&mut` first parameter is highly encouraged when using types that are expensive to clone, even when the intention is not to mutate that argument, because it avoids cloning that argument value. +For example, the `len` method of an [array] has the signature: `Fn(&mut Array) -> INT`. +The array itself is not modified in any way, but using a `&mut` parameter avoids a cloning that would +otherwise have happened if the signature were `Fn(Array) -> INT`. + For primary types that are cheap to clone (e.g. those that implement `Copy`), including `ImmutableString`, this is not necessary. diff --git a/doc/src/language/modules/import.md b/doc/src/language/modules/import.md index dc94b5a8..5e0a377a 100644 --- a/doc/src/language/modules/import.md +++ b/doc/src/language/modules/import.md @@ -63,7 +63,9 @@ cause a stack overflow in the [`Engine`], unless stopped by setting a limit for For instance, importing itself always causes an infinite recursion: ```rust -// This file is 'hello.rhai' +-------------- +| hello.rhai | +-------------- import "hello" as foo; // import itself - infinite recursion! @@ -73,11 +75,18 @@ foo::do_something(); Modules cross-referencing also cause infinite recursion: ```rust -// This file is 'hello.rhai' - references 'world.rhai' +-------------- +| hello.rhai | +-------------- + import "world" as foo; foo::do_something(); -// This file is 'world.rhai' - references 'hello.rhai' + +-------------- +| world.rhai | +-------------- + import "hello" as bar; bar::do_something_else(); ``` diff --git a/doc/src/language/oop.md b/doc/src/language/oop.md index e34a8ab2..017c4932 100644 --- a/doc/src/language/oop.md +++ b/doc/src/language/oop.md @@ -21,23 +21,42 @@ When a property of an [object map] is called like a method function, and if it h a valid [function pointer] (perhaps defined via an [anonymous function]), then the call will be dispatched to the actual function with `this` binding to the [object map] itself. + +Use Anonymous Functions to Define Methods +---------------------------------------- + +[Anonymous functions] defined as values for [object map] properties take on a syntactic shape +that resembles very closely that of class methods in an OOP language. + +Anonymous functions can also _capture_ variables from the defining environment, which is a very +common OOP pattern. Capturing is accomplished via a feature called _[automatic currying]_ and +can be turned off via the [`no_closure`] feature. + + Examples -------- ```rust +let factor = 1; + // Define the object let obj = #{ data: 0, - increment: |x| this.data += x, // when called, 'this' binds to 'obj' - update: |x| this.data = x, // when called, 'this' binds to 'obj' - action: || print(this.data) // when called, 'this' binds to 'obj' + increment: |x| this.data += x, // 'this' binds to 'obj' + update: |x| this.data = x * factor, // 'this' binds to 'obj', 'factor' is captured + action: || print(this.data) // 'this' binds to 'obj' }; // Use the object obj.increment(1); -obj.action(); // prints 1 +obj.action(); // prints 1 obj.update(42); -obj.action(); // prints 42 +obj.action(); // prints 42 + +factor = 2; + +obj.update(42); +obj.action(); // prints 84 ``` diff --git a/doc/src/language/overload.md b/doc/src/language/overload.md index bb291ca3..0b6b4ea8 100644 --- a/doc/src/language/overload.md +++ b/doc/src/language/overload.md @@ -9,15 +9,15 @@ and _number_ of parameters, but not parameter _types_ since all parameters are t New definitions _overwrite_ previous definitions of the same name and number of parameters. ```rust -fn foo(x,y,z) { print("Three!!! " + x + "," + y + "," + z) } +fn foo(x,y,z) { print("Three!!! " + x + "," + y + "," + z); } -fn foo(x) { print("One! " + x) } +fn foo(x) { print("One! " + x); } -fn foo(x,y) { print("Two! " + x + "," + y) } +fn foo(x,y) { print("Two! " + x + "," + y); } -fn foo() { print("None.") } +fn foo() { print("None."); } -fn foo(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition +fn foo(x) { print("HA! NEW ONE! " + x); } // overwrites previous definition foo(1,2,3); // prints "Three!!! 1,2,3" diff --git a/doc/src/language/values-and-types.md b/doc/src/language/values-and-types.md index 11426e84..f7446913 100644 --- a/doc/src/language/values-and-types.md +++ b/doc/src/language/values-and-types.md @@ -5,21 +5,22 @@ Values and Types The following primitive types are supported natively: -| Category | Equivalent Rust types | [`type_of()`] | `to_string()` | -| --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | ----------------------- | -| **Integer number** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`]),
`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. | -| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. | -| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` | -| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. | -| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`) | `"string"` | `"hello"` etc. | -| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | -| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | -| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | _not supported_ | -| **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` | -| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | -| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | -| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | -| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ | +| Category | Equivalent Rust types | [`type_of()`] | `to_string()` | +| -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | ----------------------- | +| **Integer number** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`]),
`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. | +| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. | +| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` | +| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. | +| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`) | `"string"` | `"hello"` etc. | +| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | +| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | +| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | `""` | +| **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` | +| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | +| **Shared value** (a reference-counted, shared [`Dynamic`] value, created via [automatic currying], disabled with [`no_closure`]) | | _the actual type_ | _actual value_ | +| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | +| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | +| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ | All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust. diff --git a/doc/src/language/variables.md b/doc/src/language/variables.md index be4abbce..455c075b 100644 --- a/doc/src/language/variables.md +++ b/doc/src/language/variables.md @@ -21,6 +21,11 @@ Variable names are case _sensitive_. Variable names also cannot be the same as a [keyword]. +### Unicode Standard Annex #31 Identifiers + +The [`unicode-xid-ident`] feature expands the allowed characters for variable names to the set defined by +[Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/). + Declare a Variable ------------------ diff --git a/doc/src/links.md b/doc/src/links.md index 936c714d..11b16796 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -9,6 +9,7 @@ [`no_object`]: {{rootUrl}}/start/features.md [`no_function`]: {{rootUrl}}/start/features.md [`no_module`]: {{rootUrl}}/start/features.md +[`no_closure`]: {{rootUrl}}/start/features.md [`no_std`]: {{rootUrl}}/start/features.md [`no-std`]: {{rootUrl}}/start/features.md [`internals`]: {{rootUrl}}/start/features.md @@ -78,6 +79,10 @@ [function pointer]: {{rootUrl}}/language/fn-ptr.md [function pointers]: {{rootUrl}}/language/fn-ptr.md [currying]: {{rootUrl}}/language/fn-curry.md +[capture]: {{rootUrl}}/language/fn-capture.md +[automatic currying]: {{rootUrl}}/language/fn-closure.md +[closure]: {{rootUrl}}/language/fn-closure.md +[closures]: {{rootUrl}}/language/fn-closure.md [function namespace]: {{rootUrl}}/language/fn-namespaces.md [function namespaces]: {{rootUrl}}/language/fn-namespaces.md [anonymous function]: {{rootUrl}}/language/fn-anon.md diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index 6d3f22c5..b666d588 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -35,12 +35,12 @@ engine.register_raw_fn( // Therefore, get a '&mut' reference to the first argument _last_. // Alternatively, use `args.split_at_mut(1)` etc. to split the slice first. - let y: i64 = *args[1].downcast_ref::() // get a reference to the second argument + let y: i64 = *args[1].read_lock::() // get a reference to the second argument .unwrap(); // then copying it because it is a primary type let y: i64 = std::mem::take(args[1]).cast::(); // alternatively, directly 'consume' it - let x: &mut i64 = args[0].downcast_mut::() // get a '&mut' reference to the + let x: &mut i64 = args[0].write_lock::() // get a '&mut' reference to the .unwrap(); // first argument *x += y; // perform the action @@ -55,27 +55,12 @@ engine.register_fn("increment_by", |x: &mut i64, y: i64| x += y); ``` -Shortcuts ---------- +Function Signature +------------------ -As usual with Rhai, there are shortcuts. For functions of zero to four parameters, which should be -the majority, use one of the `Engine::register_raw_fn_n` (where `n = 0..4`) methods: +The function signature passed to `Engine::register_raw_fn` takes the following form: -```rust -// Specify parameter types as generics -engine.register_raw_fn_2::( - "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> + 'static` +> `Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result> + 'static` where: @@ -99,12 +84,12 @@ Extract Arguments To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following: -| Argument type | Access (`n` = argument position) | Result | -| ------------------------------ | -------------------------------------- | ---------------------------------------------------------- | -| [Primary type][standard types] | `args[n].clone().cast::()` | Copy of value. | -| Custom type | `args[n].downcast_ref::().unwrap()` | Immutable reference to value. | -| Custom type (consumed) | `std::mem::take(args[n]).cast::()` | The _consumed_ value.
The original value becomes `()`. | -| `this` object | `args[0].downcast_mut::().unwrap()` | Mutable reference to value. | +| Argument type | Access (`n` = argument position) | Result | +| ------------------------------ | ------------------------------------- | ---------------------------------------------------------- | +| [Primary type][standard types] | `args[n].clone().cast::()` | Copy of value. | +| Custom type | `args[n].read_lock::().unwrap()` | Immutable reference to value. | +| Custom type (consumed) | `std::mem::take(args[n]).cast::()` | The _consumed_ value.
The original value becomes `()`. | +| `this` object | `args[0].write_lock::().unwrap()` | Mutable reference to value. | When there is a mutable reference to the `this` object (i.e. the first argument), there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain. @@ -113,6 +98,12 @@ there can be no other immutable references to `args`, otherwise the Rust borrow Example - Passing a Function Pointer to a Rust Function ------------------------------------------------------ +The low-level API is useful when there is a need to interact with the scripting [`Engine`] within a function. + +The following example registers a function that takes a [function pointer] as an argument, +then calls it within the same [`Engine`]. This way, a _callback_ function can be provided +to a native Rust function. + ```rust use rhai::{Engine, Module, Dynamic, FnPtr}; @@ -133,11 +124,9 @@ engine.register_raw_fn( let value = args[2].clone(); // 3rd argument - function argument let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer - // Use 'call_fn_dynamic' to call the function name. - // Pass 'lib' as the current global library of functions. - engine.call_fn_dynamic(&mut Scope::new(), lib, fp.fn_name(), Some(this_ptr), [value])?; - - Ok(()) + // Use 'FnPtr::call_dynamic' to call the function pointer. + // Beware, private script-defined functions will not be found. + fp.call_dynamic(engine, lib, Some(this_ptr), [value]) }, ); @@ -167,5 +156,5 @@ let this_ptr = first[0].downcast_mut::().unwrap(); // Immutable reference to the second value parameter // This can be mutable but there is no point because the parameter is passed by value -let value = rest[0].downcast_ref::().unwrap(); +let value_ref = rest[0].read_lock::().unwrap(); ``` diff --git a/doc/src/start/features.md b/doc/src/start/features.md index 9f9f3808..3201aa07 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -11,22 +11,23 @@ Notice that this deviates from Rust norm where features are _additive_. Excluding unneeded functionalities can result in smaller, faster builds as well as more control over what a script can (or cannot) do. -| Feature | Description | -| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `unchecked` | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.
Beware that a bad script may panic the entire system! | -| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync`. | -| `no_optimize` | Disable [script optimization]. | -| `no_float` | Disable floating-point numbers and math. | -| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | -| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | -| `no_index` | Disable [arrays] and indexing features. | -| `no_object` | Disable support for [custom types] and [object maps]. | -| `no_function` | Disable script-defined [functions]. | -| `no_module` | Disable loading external [modules]. | -| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | -| `serde` | Enable serialization/deserialization via [`serde`]. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | -| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | -| `unicode-xid-ident` | Allow unicode-xid for identifiers. | +| Feature | Description | +| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `unchecked` | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.
Beware that a bad script may panic the entire system! | +| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync`. | +| `no_optimize` | Disable [script optimization]. | +| `no_float` | Disable floating-point numbers and math. | +| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | +| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | +| `no_index` | Disable [arrays] and indexing features. | +| `no_object` | Disable support for [custom types] and [object maps]. | +| `no_function` | Disable script-defined [functions]. | +| `no_module` | Disable loading external [modules]. | +| `no_closure` | Disable [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | +| `no_std` | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. | +| `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | +| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | +| `unicode-xid-ident` | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. | Example diff --git a/examples/repl.rs b/examples/repl.rs index cf255986..ee73c193 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -72,15 +72,17 @@ fn main() { println!("=============="); print_help(); - loop { + 'main_loop: loop { print!("rhai> "); stdout().flush().expect("couldn't flush stdout"); input.clear(); loop { - if let Err(err) = stdin().read_line(&mut input) { - panic!("input error: {}", err); + match stdin().read_line(&mut input) { + Ok(0) => break 'main_loop, + Ok(_) => (), + Err(err) => panic!("input error: {}", err), } let line = input.as_str().trim_end(); @@ -112,10 +114,15 @@ fn main() { } "exit" | "quit" => break, // quit "scope" => { - scope - .iter() - .enumerate() - .for_each(|(i, (name, value))| println!("[{}] {} = {:?}", i + 1, name, value)); + scope.iter_raw().enumerate().for_each(|(i, (name, value))| { + println!( + "[{}] {}{} = {:?}", + i + 1, + name, + if value.is_shared() { " (shared)" } else { "" }, + *value.read_lock::().unwrap(), + ) + }); continue; } "astu" => { diff --git a/src/any.rs b/src/any.rs index 9d28c768..5efcad16 100644 --- a/src/any.rs +++ b/src/any.rs @@ -4,6 +4,9 @@ use crate::fn_native::{FnPtr, SendSync}; use crate::parser::{ImmutableString, INT}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; +#[cfg(not(feature = "no_closure"))] +use crate::fn_native::SharedMut; + #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; @@ -17,9 +20,21 @@ use crate::stdlib::{ any::{type_name, Any, TypeId}, boxed::Box, fmt, + ops::{Deref, DerefMut}, string::String, }; +#[cfg(not(feature = "no_closure"))] +#[cfg(not(feature = "sync"))] +use crate::stdlib::{ + cell::{Ref, RefCell, RefMut}, + rc::Rc, +}; + +#[cfg(not(feature = "no_closure"))] +#[cfg(feature = "sync")] +use crate::stdlib::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; + #[cfg(not(feature = "no_object"))] use crate::stdlib::collections::HashMap; @@ -144,6 +159,92 @@ pub enum Union { Map(Box), FnPtr(Box), Variant(Box>), + #[cfg(not(feature = "no_closure"))] + Shared(SharedMut), +} + +/// Underlying `Variant` read guard for `Dynamic`. +/// +/// This data structure provides transparent interoperability between +/// normal `Dynamic` and shared Dynamic values. +#[derive(Debug)] +pub struct DynamicReadLock<'d, T: Variant + Clone>(DynamicReadLockInner<'d, T>); + +/// Different types of read guards for `DynamicReadLock`. +#[derive(Debug)] +enum DynamicReadLockInner<'d, T: Variant + Clone> { + /// A simple reference to a non-shared value. + Reference(&'d T), + /// A read guard to a shared `RefCell`. + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "sync"))] + Guard(Ref<'d, Dynamic>), + /// A read guard to a shared `RwLock`. + #[cfg(not(feature = "no_closure"))] + #[cfg(feature = "sync")] + Guard(RwLockReadGuard<'d, Dynamic>), +} + +impl<'d, T: Variant + Clone> Deref for DynamicReadLock<'d, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + match &self.0 { + DynamicReadLockInner::Reference(reference) => *reference, + // Unwrapping is safe because all checking is already done in its constructor + #[cfg(not(feature = "no_closure"))] + DynamicReadLockInner::Guard(guard) => guard.downcast_ref().unwrap(), + } + } +} + +/// Underlying `Variant` write guard for `Dynamic`. +/// +/// This data structure provides transparent interoperability between +/// normal `Dynamic` and shared Dynamic values. +#[derive(Debug)] +pub struct DynamicWriteLock<'d, T: Variant + Clone>(DynamicWriteLockInner<'d, T>); + +/// Different types of write guards for `DynamicReadLock`. +#[derive(Debug)] +enum DynamicWriteLockInner<'d, T: Variant + Clone> { + /// A simple mutable reference to a non-shared value. + Reference(&'d mut T), + /// A write guard to a shared `RefCell`. + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "sync"))] + Guard(RefMut<'d, Dynamic>), + /// A write guard to a shared `RwLock`. + #[cfg(not(feature = "no_closure"))] + #[cfg(feature = "sync")] + Guard(RwLockWriteGuard<'d, Dynamic>), +} + +impl<'d, T: Variant + Clone> Deref for DynamicWriteLock<'d, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + match &self.0 { + DynamicWriteLockInner::Reference(reference) => *reference, + // Unwrapping is safe because all checking is already done in its constructor + #[cfg(not(feature = "no_closure"))] + DynamicWriteLockInner::Guard(guard) => guard.downcast_ref().unwrap(), + } + } +} + +impl<'d, T: Variant + Clone> DerefMut for DynamicWriteLock<'d, T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + match &mut self.0 { + DynamicWriteLockInner::Reference(reference) => *reference, + // Unwrapping is safe because all checking is already done in its constructor + #[cfg(not(feature = "no_closure"))] + DynamicWriteLockInner::Guard(guard) => guard.downcast_mut().unwrap(), + } + } } impl Dynamic { @@ -156,16 +257,36 @@ impl Dynamic { } } + /// Does this `Dynamic` hold a shared data type + /// instead of one of the supported system primitive types? + pub fn is_shared(&self) -> bool { + match self.0 { + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => true, + _ => false, + } + } + /// Is the value held by this `Dynamic` a particular type? + /// + /// If the `Dynamic` is a Shared variant checking is performed on + /// top of it's internal value. pub fn is(&self) -> bool { - self.type_id() == TypeId::of::() - || match self.0 { - Union::Str(_) => TypeId::of::() == TypeId::of::(), - _ => false, - } + let mut target_type_id = TypeId::of::(); + + if target_type_id == TypeId::of::() { + target_type_id = TypeId::of::(); + } + + self.type_id() == target_type_id } /// Get the TypeId of the value held by this `Dynamic`. + /// + /// # Panics or Deadlocks When Value is Shared + /// + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). + /// Otherwise, this call panics if the data is currently borrowed for write. pub fn type_id(&self) -> TypeId { match &self.0 { Union::Unit(_) => TypeId::of::<()>(), @@ -181,10 +302,21 @@ impl Dynamic { Union::Map(_) => TypeId::of::(), Union::FnPtr(_) => TypeId::of::(), Union::Variant(value) => (***value).type_id(), + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "sync"))] + Union::Shared(cell) => (*cell.borrow()).type_id(), + #[cfg(not(feature = "no_closure"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => (*cell.read().unwrap()).type_id(), } } /// Get the name of the type of the value held by this `Dynamic`. + /// + /// # Panics or Deadlocks When Value is Shared + /// + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). + /// Otherwise, this call panics if the data is currently borrowed for write. pub fn type_name(&self) -> &'static str { match &self.0 { Union::Unit(_) => "()", @@ -203,6 +335,15 @@ impl Dynamic { #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => "timestamp", Union::Variant(value) => (***value).type_name(), + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "sync"))] + Union::Shared(cell) => cell + .try_borrow() + .map(|v| (*v).type_name()) + .unwrap_or(""), + #[cfg(not(feature = "no_closure"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => (*cell.read().unwrap()).type_name(), } } } @@ -256,8 +397,20 @@ impl fmt::Display for Dynamic { Union::FnPtr(value) => fmt::Display::fmt(value, f), #[cfg(not(feature = "no_std"))] - Union::Variant(value) if value.is::() => write!(f, ""), - Union::Variant(value) => write!(f, "{}", (*value).type_name()), + Union::Variant(value) if value.is::() => f.write_str(""), + 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("") + } + } + #[cfg(not(feature = "no_closure"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => fmt::Display::fmt(*cell.read_lock().unwrap(), f), } } } @@ -284,6 +437,18 @@ impl fmt::Debug for Dynamic { #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => write!(f, ""), 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("") + } + } + #[cfg(not(feature = "no_closure"))] + #[cfg(feature = "sync")] + Union::Shared(cell) => fmt::Display::fmt(*cell.read_lock().unwrap(), f), } } } @@ -304,6 +469,8 @@ impl Clone for Dynamic { Union::Map(ref value) => Self(Union::Map(value.clone())), Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())), Union::Variant(ref value) => (***value).clone_into_dynamic(), + #[cfg(not(feature = "no_closure"))] + Union::Shared(ref cell) => Self(Union::Shared(cell.clone())), } } } @@ -407,6 +574,11 @@ impl Dynamic { } } + boxed = match unsafe_cast_box::<_, FnPtr>(boxed) { + Ok(fn_ptr) => return (*fn_ptr).into(), + Err(val) => val, + }; + boxed = match unsafe_cast_box::<_, Dynamic>(boxed) { Ok(d) => return *d, Err(val) => val, @@ -415,10 +587,46 @@ impl Dynamic { Self(Union::Variant(Box::new(boxed))) } - /// Get a copy of the `Dynamic` value as a specific type. - /// Casting to a `Dynamic` just returns as is. + /// Turn the `Dynamic` value into a shared `Dynamic` value backed by an `Rc>` + /// or `Arc>` depending on the `sync` feature. /// - /// Returns an error with the name of the value's actual type when the cast fails. + /// Shared `Dynamic` values are relatively cheap to clone as they simply increment the + /// reference counts. + /// + /// Shared `Dynamic` values can be converted seamlessly to and from ordinary `Dynamic` values. + /// + /// If the `Dynamic` value is already shared, this method returns itself. + /// + /// # Panics + /// + /// Panics under the `no_closure` feature. + pub fn into_shared(self) -> Self { + #[cfg(not(feature = "no_closure"))] + return match self.0 { + Union::Shared(..) => self, + #[cfg(not(feature = "sync"))] + _ => Self(Union::Shared(Rc::new(RefCell::new(self)))), + #[cfg(feature = "sync")] + _ => Self(Union::Shared(Arc::new(RwLock::new(self)))), + }; + + #[cfg(feature = "no_closure")] + unimplemented!() + } + + /// Convert the `Dynamic` value into specific type. + /// + /// Casting to a `Dynamic` just returns as is, but if it contains a shared value, + /// it is cloned into a `Dynamic` with a normal value. + /// + /// Returns `None` if types mismatched. + /// + /// # Panics or Deadlocks + /// + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). + /// Otherwise, this call panics if the data is currently borrowed for write. + /// + /// These normally shouldn't occur since most operations in Rhai is single-threaded. /// /// # Example /// @@ -433,12 +641,28 @@ impl Dynamic { pub fn try_cast(self) -> Option { let type_id = TypeId::of::(); + 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::() { + return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); + } + if type_id == TypeId::of::() { return match self.0 { Union::Int(value) => unsafe_try_cast(value), _ => None, }; } + #[cfg(not(feature = "no_float"))] if type_id == TypeId::of::() { return match self.0 { @@ -446,30 +670,35 @@ impl Dynamic { _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::Bool(value) => unsafe_try_cast(value), _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::Str(value) => unsafe_try_cast(value), _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::Str(value) => unsafe_try_cast(value.into_owned()), _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::Char(value) => unsafe_try_cast(value), _ => None, }; } + #[cfg(not(feature = "no_index"))] if type_id == TypeId::of::() { return match self.0 { @@ -477,6 +706,7 @@ impl Dynamic { _ => None, }; } + #[cfg(not(feature = "no_object"))] if type_id == TypeId::of::() { return match self.0 { @@ -484,34 +714,45 @@ impl Dynamic { _ => None, }; } + if type_id == TypeId::of::() { return match self.0 { Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), _ => None, }; } + if type_id == TypeId::of::<()>() { return match self.0 { Union::Unit(value) => unsafe_try_cast(value), _ => None, }; } - if type_id == TypeId::of::() { - return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); - } match self.0 { Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => unreachable!(), _ => None, } } - /// Get a copy of the `Dynamic` value as a specific type. - /// Casting to a `Dynamic` just returns as is. + /// Convert the `Dynamic` value into a specific type. /// - /// # Panics + /// Casting to a `Dynamic` just returns as is, but if it contains a shared value, + /// it is cloned into a `Dynamic` with a normal value. /// - /// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type). + /// Returns `None` if types mismatched. + /// + /// # Panics or Deadlocks + /// + /// Panics if the cast fails (e.g. the type of the actual value is not the + /// same as the specified type). + /// + /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). + /// Otherwise, this call panics if the data is currently borrowed for write. + /// + /// These normally shouldn't occur since most operations in Rhai is single-threaded. /// /// # Example /// @@ -527,11 +768,125 @@ impl Dynamic { self.try_cast::().unwrap() } - /// Get a reference of a specific type to the `Dynamic`. - /// Casting to `Dynamic` just returns a reference to it. + /// Get a copy of the `Dynamic` as a specific type. + /// + /// If the `Dynamic` is not a shared value, it returns a cloned copy of the value. + /// + /// If the `Dynamic` is a shared value, it returns a cloned copy of the shared value. + /// /// Returns `None` if the cast fails. #[inline(always)] - pub fn downcast_ref(&self) -> Option<&T> { + pub fn clone_inner_data(self) -> Option { + match self.0 { + #[cfg(not(feature = "no_closure"))] + Union::Shared(cell) => { + #[cfg(not(feature = "sync"))] + return Some(cell.borrow().downcast_ref::().unwrap().clone()); + + #[cfg(feature = "sync")] + return Some(cell.read().unwrap().downcast_ref::().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(&self) -> Option> { + 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::() && TypeId::of::() != TypeId::of::() { + 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(&mut self) -> Option> { + 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::() && TypeId::of::() != TypeId::of::() { + 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(&self) -> Option<&T> { let type_id = TypeId::of::(); if type_id == TypeId::of::() { @@ -603,15 +958,18 @@ impl Dynamic { match &self.0 { Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::(), + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => None, _ => None, } } /// Get a mutable reference of a specific type to the `Dynamic`. /// Casting to `Dynamic` just returns a mutable reference to it. - /// Returns `None` if the cast fails. + /// + /// Returns `None` if the cast fails, or if the value is shared. #[inline(always)] - pub fn downcast_mut(&mut self) -> Option<&mut T> { + pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { let type_id = TypeId::of::(); if type_id == TypeId::of::() { @@ -677,6 +1035,8 @@ impl Dynamic { match &mut self.0 { Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::(), + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => None, _ => None, } } @@ -686,6 +1046,8 @@ impl Dynamic { pub fn as_int(&self) -> Result { match self.0 { Union::Int(n) => Ok(n), + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -696,6 +1058,8 @@ impl Dynamic { pub fn as_float(&self) -> Result { match self.0 { Union::Float(n) => Ok(n), + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -705,6 +1069,8 @@ impl Dynamic { pub fn as_bool(&self) -> Result { match self.0 { Union::Bool(b) => Ok(b), + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } @@ -714,12 +1080,16 @@ impl Dynamic { pub fn as_char(&self) -> Result { match self.0 { Union::Char(n) => Ok(n), + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), } } /// Cast the `Dynamic` as a string and return the string slice. /// Returns the name of the actual type if the cast fails. + /// + /// Cast is failing if `self` is Shared Dynamic pub fn as_str(&self) -> Result<&str, &'static str> { match &self.0 { Union::Str(s) => Ok(s), @@ -741,6 +1111,27 @@ impl Dynamic { match self.0 { Union::Str(s) => Ok(s), Union::FnPtr(f) => Ok(f.take_data().0), + #[cfg(not(feature = "no_closure"))] + Union::Shared(cell) => { + #[cfg(not(feature = "sync"))] + { + let inner = cell.borrow(); + match &inner.0 { + Union::Str(s) => Ok(s.clone()), + Union::FnPtr(f) => Ok(f.clone().take_data().0), + _ => Err((*inner).type_name()), + } + } + #[cfg(feature = "sync")] + { + let inner = cell.read().unwrap(); + match &inner.0 { + Union::Str(s) => Ok(s.clone()), + Union::FnPtr(f) => Ok(f.clone().take_data().0), + _ => Err((*inner).type_name()), + } + } + } _ => Err(self.type_name()), } } @@ -806,7 +1197,7 @@ impl, T: Variant + Clone> From> for Dynam } impl From for Dynamic { fn from(value: FnPtr) -> Self { - Box::new(value).into() + Self(Union::FnPtr(Box::new(value))) } } impl From> for Dynamic { diff --git a/src/api.rs b/src/api.rs index b7a1bb74..43aaf6d5 100644 --- a/src/api.rs +++ b/src/api.rs @@ -3,6 +3,7 @@ use crate::any::{Dynamic, Variant}; use crate::engine::{Engine, Imports, State}; use crate::error::ParseError; +use crate::fn_call::ensure_no_data_race; use crate::fn_native::{IteratorFn, SendSync}; use crate::module::{FuncReturn, Module}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; @@ -19,6 +20,7 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET}; use crate::{ engine::{make_getter, make_setter, Map}, fn_register::RegisterFn, + token::Token, }; #[cfg(not(feature = "no_function"))] @@ -62,147 +64,6 @@ impl Engine { self } - /// Register a function of no parameters with the `Engine`. - /// - /// ## WARNING - Low Level API - /// - /// This function is very low level. - #[deprecated(note = "this function is volatile and may change")] - pub fn register_raw_fn_0( - &mut self, - name: &str, - func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + 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::()` - /// - /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. - /// 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( - &mut self, - name: &str, - func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, - ) -> &mut Self { - self.global_module - .set_raw_fn(name, &[TypeId::of::
()], 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::()` - /// - /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. - /// 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( - &mut self, - name: &str, - func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, - ) -> &mut Self { - self.global_module - .set_raw_fn(name, &[TypeId::of::(), TypeId::of::()], 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::()` - /// - /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. - /// 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 + SendSync + 'static, - ) -> &mut Self { - self.global_module.set_raw_fn( - name, - &[TypeId::of::(), TypeId::of::(), TypeId::of::()], - 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::()` - /// - /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. - /// 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 + SendSync + 'static, - ) -> &mut Self { - self.global_module.set_raw_fn( - name, - &[ - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - ], - func, - ); - self - } - /// Register a custom type for use with the `Engine`. /// The type must implement `Clone`. /// @@ -731,7 +592,7 @@ impl Engine { scripts: &[&str], optimization_level: OptimizationLevel, ) -> Result { - let stream = lex(scripts, self); + let stream = lex(scripts, None, self); self.parse(&mut stream.peekable(), scope, optimization_level) } @@ -856,7 +717,19 @@ impl Engine { // Trims the JSON string and add a '#' in front let scripts = ["#", json.trim()]; - let stream = lex(&scripts, self); + let stream = lex( + &scripts, + if has_null { + Some(Box::new(|token| match token { + // If `null` is present, make sure `null` is treated as a variable + Token::Reserved(s) if s == "null" => Token::Identifier(s), + _ => token, + })) + } else { + None + }, + self, + ); let ast = self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?; @@ -937,7 +810,7 @@ impl Engine { script: &str, ) -> Result { let scripts = [script]; - let stream = lex(&scripts, self); + let stream = lex(&scripts, None, self); { let mut peekable = stream.peekable(); self.parse_global_expr(&mut peekable, scope, self.optimization_level) @@ -1092,7 +965,7 @@ impl Engine { script: &str, ) -> Result> { let scripts = [script]; - let stream = lex(&scripts, self); + let stream = lex(&scripts, None, self); // No need to optimize a lone expression let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?; @@ -1225,7 +1098,7 @@ impl Engine { script: &str, ) -> Result<(), Box> { let scripts = [script]; - let stream = lex(&scripts, self); + let stream = lex(&scripts, None, self); let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?; self.consume_ast_with_scope(scope, &ast) } @@ -1410,6 +1283,11 @@ impl Engine { let mut mods = Imports::new(); let args = args.as_mut(); + // Check for data race. + if cfg!(not(feature = "no_closure")) { + ensure_no_data_race(name, args, false)?; + } + self.call_script_fn( scope, &mut mods, &mut state, lib, this_ptr, name, fn_def, args, 0, ) @@ -1433,16 +1311,15 @@ impl Engine { mut ast: AST, optimization_level: OptimizationLevel, ) -> AST { - #[cfg(not(feature = "no_function"))] - let lib = ast - .lib() - .iter_fn() - .filter(|(_, _, _, f)| f.is_script()) - .map(|(_, _, _, f)| f.get_fn_def().clone()) - .collect(); - - #[cfg(feature = "no_function")] - let lib = Default::default(); + let lib = if cfg!(not(feature = "no_function")) { + ast.lib() + .iter_fn() + .filter(|(_, _, _, f)| f.is_script()) + .map(|(_, _, _, f)| f.get_fn_def().clone()) + .collect() + } else { + Default::default() + }; let stmt = mem::take(ast.statements_mut()); optimize_into_ast(self, scope, stmt, lib, optimization_level) diff --git a/src/engine.rs b/src/engine.rs index f4435698..65228a8f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -19,7 +19,7 @@ use crate::utils::StaticVec; use crate::any::Variant; #[cfg(not(feature = "no_function"))] -use crate::parser::{FnAccess, ScriptFnDef}; +use crate::parser::ScriptFnDef; #[cfg(not(feature = "no_module"))] use crate::module::ModuleResolver; @@ -31,12 +31,17 @@ use crate::module::resolvers; #[cfg(any(not(feature = "no_object"), not(feature = "no_module")))] use crate::utils::ImmutableString; +#[cfg(not(feature = "no_closure"))] +#[cfg(not(feature = "no_object"))] +use crate::any::DynamicWriteLock; + use crate::stdlib::{ borrow::Cow, boxed::Box, collections::{HashMap, HashSet}, fmt, format, iter::{empty, once}, + ops::DerefMut, string::{String, ToString}, vec::Vec, }; @@ -44,6 +49,9 @@ use crate::stdlib::{ #[cfg(not(feature = "no_index"))] use crate::stdlib::any::TypeId; +#[cfg(not(feature = "no_closure"))] +use crate::stdlib::mem; + /// Variable-sized array of `Dynamic` values. /// /// Not available under the `no_index` feature. @@ -91,6 +99,7 @@ pub const KEYWORD_EVAL: &str = "eval"; pub const KEYWORD_FN_PTR: &str = "Fn"; pub const KEYWORD_FN_PTR_CALL: &str = "call"; pub const KEYWORD_FN_PTR_CURRY: &str = "curry"; +pub const KEYWORD_IS_SHARED: &str = "is_shared"; pub const KEYWORD_THIS: &str = "this"; pub const FN_TO_STRING: &str = "to_string"; #[cfg(not(feature = "no_object"))] @@ -122,6 +131,11 @@ pub enum ChainType { pub enum Target<'a> { /// The target is a mutable reference to a `Dynamic` value somewhere. Ref(&'a mut Dynamic), + /// The target is a mutable reference to a Shared `Dynamic` value. + /// It holds both the access guard and the original shared value. + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "no_object"))] + LockGuard((DynamicWriteLock<'a, Dynamic>, Dynamic)), /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). Value(Dynamic), /// The target is a character inside a String. @@ -136,6 +150,9 @@ impl Target<'_> { pub fn is_ref(&self) -> bool { match self { Self::Ref(_) => true, + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "no_object"))] + Self::LockGuard(_) => true, Self::Value(_) => false, #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, _) => false, @@ -145,16 +162,34 @@ impl Target<'_> { pub fn is_value(&self) -> bool { match self { Self::Ref(_) => false, + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "no_object"))] + Self::LockGuard(_) => false, Self::Value(_) => true, #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, _) => false, } } + /// Is the `Target` a shared value? + pub fn is_shared(&self) -> bool { + match self { + Self::Ref(r) => r.is_shared(), + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "no_object"))] + Self::LockGuard(_) => true, + Self::Value(r) => r.is_shared(), + #[cfg(not(feature = "no_index"))] + Self::StringChar(_, _, _) => false, + } + } /// Is the `Target` a specific type? #[allow(dead_code)] pub fn is(&self) -> bool { match self { Target::Ref(r) => r.is::(), + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "no_object"))] + Target::LockGuard((r, _)) => r.is::(), Target::Value(r) => r.is::(), #[cfg(not(feature = "no_index"))] Target::StringChar(_, _, _) => TypeId::of::() == TypeId::of::(), @@ -164,6 +199,9 @@ impl Target<'_> { pub fn clone_into_dynamic(self) -> Dynamic { match self { Self::Ref(r) => r.clone(), // Referenced value is cloned + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "no_object"))] + Self::LockGuard((_, orig)) => orig, // Original value is simply taken Self::Value(v) => v, // Owned value is simply taken #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ch) => ch, // Character is taken @@ -173,6 +211,9 @@ impl Target<'_> { pub fn as_mut(&mut self) -> &mut Dynamic { match self { Self::Ref(r) => *r, + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "no_object"))] + Self::LockGuard((r, _)) => r.deref_mut(), Self::Value(ref mut r) => r, #[cfg(not(feature = "no_index"))] Self::StringChar(_, _, ref mut r) => r, @@ -183,13 +224,18 @@ impl Target<'_> { pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box> { match self { Self::Ref(r) => **r = new_val, + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "no_object"))] + Self::LockGuard((r, _)) => **r = new_val, Self::Value(_) => { return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( Position::none(), ))) } #[cfg(not(feature = "no_index"))] - Self::StringChar(Dynamic(Union::Str(ref mut s)), index, _) => { + Self::StringChar(string, index, _) if string.is::() => { + let mut s = string.write_lock::().unwrap(); + // Replace the character at the specified index position let new_ch = new_val .as_char() @@ -215,9 +261,18 @@ impl Target<'_> { #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] impl<'a> From<&'a mut Dynamic> for Target<'a> { fn from(value: &'a mut Dynamic) -> Self { + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "no_object"))] + if value.is_shared() { + // Cloning is cheap for a shared value + let container = value.clone(); + return Self::LockGuard((value.write_lock::().unwrap(), container)); + } + Self::Ref(value) } } + #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] impl> From for Target<'_> { fn from(value: T) -> Self { @@ -228,11 +283,6 @@ impl> From for Target<'_> { /// [INTERNALS] A type that holds all the current states of the Engine. /// Exported under the `internals` feature only. /// -/// # Safety -/// -/// This type uses some unsafe code, mainly for avoiding cloning of local variable names via -/// direct lifetime casting. -/// /// ## WARNING /// /// This type is volatile and may change. @@ -264,19 +314,15 @@ pub fn get_script_function_by_signature<'a>( module: &'a Module, name: &str, params: usize, - public_only: bool, + pub_only: bool, ) -> Option<&'a ScriptFnDef> { // Qualifiers (none) + function name + number of arguments. let hash_script = calc_fn_hash(empty(), name, params, empty()); - let func = module.get_fn(hash_script)?; - if !func.is_script() { - return None; - } - let fn_def = func.get_fn_def(); - - match fn_def.access { - FnAccess::Private if public_only => None, - FnAccess::Private | FnAccess::Public => Some(&fn_def), + let func = module.get_fn(hash_script, pub_only)?; + if func.is_script() { + Some(func.get_fn_def()) + } else { + None } } @@ -401,11 +447,11 @@ impl Default for Engine { progress: None, // optimization level - #[cfg(feature = "no_optimize")] - optimization_level: OptimizationLevel::None, - - #[cfg(not(feature = "no_optimize"))] - optimization_level: OptimizationLevel::Simple, + optimization_level: if cfg!(feature = "no_optimize") { + OptimizationLevel::None + } else { + OptimizationLevel::Simple + }, #[cfg(not(feature = "unchecked"))] limits: Limits { @@ -454,7 +500,7 @@ pub fn search_imports<'s>( state: &mut State, modules: &Box, ) -> Result<&'s Module, Box> { - let (root, root_pos) = modules.get(0); + let (root, root_pos) = &modules[0]; // Qualified - check if the root module is directly indexed let index = if state.always_search { @@ -487,7 +533,7 @@ pub fn search_imports_mut<'s>( state: &mut State, modules: &Box, ) -> Result<&'s mut Module, Box> { - let (root, root_pos) = modules.get(0); + let (root, root_pos) = &modules[0]; // Qualified - check if the root module is directly indexed let index = if state.always_search { @@ -513,7 +559,8 @@ pub fn search_imports_mut<'s>( }) } -/// Search for a variable within the scope and imports +/// Search for a variable within the scope or within imports, +/// depending on whether the variable name is qualified. pub fn search_namespace<'s, 'a>( scope: &'s mut Scope, mods: &'s mut Imports, @@ -565,7 +612,7 @@ pub fn search_scope_only<'s, 'a>( if let Some(val) = this_ptr { return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos)); } else { - return Err(Box::new(EvalAltResult::ErrorUnboundedThis(*pos))); + return Err(Box::new(EvalAltResult::ErrorUnboundThis(*pos))); } } @@ -583,6 +630,13 @@ pub fn search_scope_only<'s, 'a>( }; let (val, typ) = scope.get_mut(index); + + // Check for data race - probably not necessary because the only place it should conflict is in a method call + // when the object variable is also used as a parameter. + // if cfg!(not(feature = "no_closure")) && val.is_locked() { + // return Err(Box::new(EvalAltResult::ErrorDataRace(name.into(), *pos))); + // } + Ok((val, name, typ, *pos)) } @@ -613,11 +667,11 @@ impl Engine { debug: Box::new(|_| {}), progress: None, - #[cfg(feature = "no_optimize")] - optimization_level: OptimizationLevel::None, - - #[cfg(not(feature = "no_optimize"))] - optimization_level: OptimizationLevel::Simple, + optimization_level: if cfg!(feature = "no_optimize") { + OptimizationLevel::None + } else { + OptimizationLevel::Simple + }, #[cfg(not(feature = "unchecked"))] limits: Limits { @@ -661,7 +715,7 @@ impl Engine { }; // Pop the last index value - let idx_val = idx_values.pop(); + let idx_val = idx_values.pop().unwrap(); match chain_type { #[cfg(not(feature = "no_index"))] @@ -673,8 +727,9 @@ impl Engine { Expr::Dot(x) | Expr::Index(x) => { let (idx, expr, pos) = x.as_ref(); let idx_pos = idx.position(); - let obj_ptr = &mut self - .get_indexed_mut(state, lib, target, idx_val, idx_pos, false, level)?; + let obj_ptr = &mut self.get_indexed_mut( + state, lib, target, idx_val, idx_pos, false, true, level, + )?; self.eval_dot_index_chain_helper( state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level, @@ -684,54 +739,58 @@ impl Engine { } // xxx[rhs] = new_val _ if _new_val.is_some() => { - let mut new_val = _new_val.unwrap(); let mut idx_val2 = idx_val.clone(); - match self.get_indexed_mut(state, lib, target, idx_val, pos, true, level) { - // Indexed value is an owned value - the only possibility is an indexer - // Try to call an index setter - #[cfg(not(feature = "no_index"))] - Ok(obj_ptr) if obj_ptr.is_value() => { - let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; - - self.exec_fn_call( - state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None, - level, - ) - .or_else(|err| match *err { - // If there is no index setter, no need to set it back because the indexer is read-only - EvalAltResult::ErrorFunctionNotFound(_, _) => { - Ok(Default::default()) - } - _ => Err(err), - })?; - } + // `call_setter` is introduced to bypass double mutable borrowing of target + let _call_setter = match self + .get_indexed_mut(state, lib, target, idx_val, pos, true, false, level) + { // Indexed value is a reference - update directly Ok(ref mut obj_ptr) => { obj_ptr - .set_value(new_val) + .set_value(_new_val.unwrap()) .map_err(|err| err.new_position(rhs.position()))?; + + None } Err(err) => match *err { // No index getter - try to call an index setter #[cfg(not(feature = "no_index"))] EvalAltResult::ErrorIndexingType(_, _) => { - let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; - - self.exec_fn_call( - state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None, - level, - )?; + // Raise error if there is no index getter nor setter + Some(_new_val.unwrap()) } - // Error + // Any other error - return err => return Err(Box::new(err)), }, + }; + + #[cfg(not(feature = "no_index"))] + if let Some(mut new_val) = _call_setter { + let val = target.as_mut(); + let val_type_name = val.type_name(); + let args = &mut [val, &mut idx_val2, &mut new_val]; + + self.exec_fn_call( + state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, None, + level, + ) + .map_err(|err| match *err { + EvalAltResult::ErrorFunctionNotFound(_, _) => { + EvalAltResult::ErrorIndexingType( + self.map_type_name(val_type_name).into(), + Position::none(), + ) + } + err => err, + })?; } + Ok(Default::default()) } // xxx[rhs] _ => self - .get_indexed_mut(state, lib, target, idx_val, pos, false, level) + .get_indexed_mut(state, lib, target, idx_val, pos, false, true, level) .map(|v| (v.clone_into_dynamic(), false)), } } @@ -741,7 +800,12 @@ impl Engine { match rhs { // xxx.fn_name(arg_expr_list) Expr::FnCall(x) if x.1.is_none() => { - self.make_method_call(state, lib, target, rhs, idx_val, level) + let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); + self.make_method_call( + state, lib, name, *hash, target, idx_val, *def_val, *native, false, + level, + ) + .map_err(|err| err.new_position(*pos)) } // xxx.module::fn_name(...) - syntax error Expr::FnCall(_) => unreachable!(), @@ -749,8 +813,8 @@ impl Engine { Expr::Property(x) if target.is::() && _new_val.is_some() => { let ((prop, _, _), pos) = x.as_ref(); let index = prop.clone().into(); - let mut val = - self.get_indexed_mut(state, lib, target, index, *pos, true, level)?; + let mut val = self + .get_indexed_mut(state, lib, target, index, *pos, true, false, level)?; val.set_value(_new_val.unwrap()) .map_err(|err| err.new_position(rhs.position()))?; @@ -760,8 +824,9 @@ impl Engine { Expr::Property(x) if target.is::() => { let ((prop, _, _), pos) = x.as_ref(); let index = prop.clone().into(); - let val = - self.get_indexed_mut(state, lib, target, index, *pos, false, level)?; + let val = self.get_indexed_mut( + state, lib, target, index, *pos, false, false, level, + )?; Ok((val.clone_into_dynamic(), false)) } @@ -770,7 +835,8 @@ impl Engine { let ((_, _, setter), pos) = x.as_ref(); let mut args = [target.as_mut(), _new_val.as_mut().unwrap()]; self.exec_fn_call( - state, lib, setter, true, 0, &mut args, is_ref, true, None, level, + state, lib, setter, 0, &mut args, is_ref, true, false, None, None, + level, ) .map(|(v, _)| (v, true)) .map_err(|err| err.new_position(*pos)) @@ -780,7 +846,8 @@ impl Engine { let ((_, getter, _), pos) = x.as_ref(); let mut args = [target.as_mut()]; self.exec_fn_call( - state, lib, getter, true, 0, &mut args, is_ref, true, None, level, + state, lib, getter, 0, &mut args, is_ref, true, false, None, None, + level, ) .map(|(v, _)| (v, false)) .map_err(|err| err.new_position(*pos)) @@ -791,15 +858,21 @@ impl Engine { let mut val = match sub_lhs { Expr::Property(p) => { - let ((prop, _, _), _) = p.as_ref(); + let ((prop, _, _), pos) = p.as_ref(); let index = prop.clone().into(); - self.get_indexed_mut(state, lib, target, index, *pos, false, level)? + self.get_indexed_mut( + state, lib, target, index, *pos, false, true, level, + )? } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { - let (val, _) = self.make_method_call( - state, lib, target, sub_lhs, idx_val, level, - )?; + let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); + let (val, _) = self + .make_method_call( + state, lib, name, *hash, target, idx_val, *def_val, + *native, false, level, + ) + .map_err(|err| err.new_position(*pos))?; val.into() } // {xxx:map}.module::fn_name(...) - syntax error @@ -816,28 +889,34 @@ impl Engine { } // xxx.sub_lhs[expr] | xxx.sub_lhs.expr Expr::Index(x) | Expr::Dot(x) => { - let (sub_lhs, expr, pos) = x.as_ref(); + let (sub_lhs, expr, _) = x.as_ref(); match sub_lhs { // xxx.prop[expr] | xxx.prop.expr Expr::Property(p) => { - let ((_, getter, setter), _) = p.as_ref(); + let ((_, getter, setter), pos) = p.as_ref(); let arg_values = &mut [target.as_mut(), &mut Default::default()]; let args = &mut arg_values[..1]; let (mut val, updated) = self .exec_fn_call( - state, lib, getter, true, 0, args, is_ref, true, None, - level, + state, lib, getter, 0, args, is_ref, true, false, None, + None, level, ) .map_err(|err| err.new_position(*pos))?; let val = &mut val; - let target = &mut val.into(); let (result, may_be_changed) = self .eval_dot_index_chain_helper( - state, lib, this_ptr, target, expr, idx_values, next_chain, - level, _new_val, + state, + lib, + this_ptr, + &mut val.into(), + expr, + idx_values, + next_chain, + level, + _new_val, ) .map_err(|err| err.new_position(*pos))?; @@ -846,8 +925,8 @@ impl Engine { // Re-use args because the first &mut parameter will not be consumed arg_values[1] = val; self.exec_fn_call( - state, lib, setter, true, 0, arg_values, is_ref, true, - None, level, + state, lib, setter, 0, arg_values, is_ref, true, false, + None, None, level, ) .or_else( |err| match *err { @@ -864,9 +943,13 @@ impl Engine { } // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::FnCall(x) if x.1.is_none() => { - let (mut val, _) = self.make_method_call( - state, lib, target, sub_lhs, idx_val, level, - )?; + let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref(); + let (mut val, _) = self + .make_method_call( + state, lib, name, *hash, target, idx_val, *def_val, + *native, false, level, + ) + .map_err(|err| err.new_position(*pos))?; let val = &mut val; let target = &mut val.into(); @@ -1001,7 +1084,7 @@ impl Engine { idx_values.push(Dynamic::from(arg_values)); } Expr::FnCall(_) => unreachable!(), - Expr::Property(_) => idx_values.push(()), // Store a placeholder - no need to copy the property name + Expr::Property(_) => idx_values.push(().into()), // Store a placeholder - no need to copy the property name Expr::Index(x) | Expr::Dot(x) => { let (lhs, rhs, _) = x.as_ref(); @@ -1052,6 +1135,7 @@ impl Engine { mut _idx: Dynamic, idx_pos: Position, _create: bool, + _indexers: bool, _level: usize, ) -> Result, Box> { self.inc_operations(state)?; @@ -1096,10 +1180,10 @@ impl Engine { map.entry(index).or_insert(Default::default()).into() } else { let index = _idx - .downcast_ref::() + .read_lock::() .ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; - map.get_mut(index.as_str()) + map.get_mut(&*index) .map(Target::from) .unwrap_or_else(|| Target::from(())) }) @@ -1128,11 +1212,11 @@ impl Engine { #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] - _ => { + _ if _indexers => { let type_name = val.type_name(); let args = &mut [val, &mut _idx]; self.exec_fn_call( - state, _lib, FN_IDX_GET, true, 0, args, is_ref, true, None, _level, + state, _lib, FN_IDX_GET, 0, args, is_ref, true, false, None, None, _level, ) .map(|(v, _)| v.into()) .map_err(|err| match *err { @@ -1143,7 +1227,6 @@ impl Engine { }) } - #[cfg(any(feature = "no_index", feature = "no_object"))] _ => Err(Box::new(EvalAltResult::ErrorIndexingType( self.map_type_name(val.type_name()).into(), Position::none(), @@ -1173,26 +1256,23 @@ impl Engine { #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(mut rhs_value)) => { let op = "=="; - let mut scope = Scope::new(); // Call the `==` operator to compare each value for value in rhs_value.iter_mut() { let def_value = Some(false); let args = &mut [&mut lhs_value.clone(), value]; - let hashes = ( - // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. - calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())), - 0, - ); + // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. + let hash = + calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())); - let (r, _) = self - .call_fn_raw( - &mut scope, mods, state, lib, op, hashes, args, false, false, - def_value, level, - ) - .map_err(|err| err.new_position(rhs.position()))?; - if r.as_bool().unwrap_or(false) { + if self + .call_native_fn(state, lib, op, hash, args, false, false, def_value) + .map_err(|err| err.new_position(rhs.position()))? + .0 + .as_bool() + .unwrap_or(false) + { return Ok(true.into()); } } @@ -1202,10 +1282,8 @@ impl Engine { #[cfg(not(feature = "no_object"))] Dynamic(Union::Map(rhs_value)) => match lhs_value { // Only allows String or char - Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(s.as_str()).into()), - Dynamic(Union::Char(c)) => { - Ok(rhs_value.contains_key(c.to_string().as_str()).into()) - } + Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(&s).into()), + Dynamic(Union::Char(c)) => Ok(rhs_value.contains_key(&c.to_string()).into()), _ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))), }, Dynamic(Union::Str(rhs_value)) => match lhs_value { @@ -1245,7 +1323,7 @@ impl Engine { if let Some(val) = this_ptr { Ok(val.clone()) } else { - Err(Box::new(EvalAltResult::ErrorUnboundedThis((x.0).1))) + Err(Box::new(EvalAltResult::ErrorUnboundThis((x.0).1))) } } Expr::Variable(_) => { @@ -1274,7 +1352,12 @@ impl Engine { )), // Normal assignment ScopeEntryType::Normal if op.is_empty() => { - *lhs_ptr = rhs_val; + let rhs_val = rhs_val.clone_inner_data().unwrap(); + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + *lhs_ptr.write_lock::().unwrap() = rhs_val; + } else { + *lhs_ptr = rhs_val; + } Ok(Default::default()) } // Op-assignment - in order of precedence: @@ -1289,25 +1372,39 @@ impl Engine { if let Some(CallableFunction::Method(func)) = self .global_module - .get_fn(hash_fn) - .or_else(|| self.packages.get_fn(hash_fn)) + .get_fn(hash_fn, false) + .or_else(|| self.packages.get_fn(hash_fn, false)) { - // Overriding exact implementation - func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + let mut lock_guard = lhs_ptr.write_lock::().unwrap(); + let lhs_ptr_inner = lock_guard.deref_mut(); + + // Overriding exact implementation + func(self, lib, &mut [lhs_ptr_inner, &mut rhs_val])?; + } else { + // Overriding exact implementation + func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; + } } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = - let hash = calc_fn_hash(empty(), op, 2, empty()); + // Clone the LHS value let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val]; + // Run function let (value, _) = self .exec_fn_call( - state, lib, op, true, hash, args, false, false, None, level, + state, lib, op, 0, args, false, false, false, None, None, level, ) .map_err(|err| err.new_position(*op_pos))?; - // Set value to LHS - *lhs_ptr = value; + + let value = value.clone_inner_data().unwrap(); + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + *lhs_ptr.write_lock::().unwrap() = value; + } else { + *lhs_ptr = value; + } } Ok(Default::default()) } @@ -1326,14 +1423,15 @@ impl Engine { } else { // Op-assignment - always map to `lhs = lhs op rhs` let op = &op[..op.len() - 1]; // extract operator without = - let hash = calc_fn_hash(empty(), op, 2, empty()); let args = &mut [ &mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?, &mut rhs_val, ]; - self.exec_fn_call(state, lib, op, true, hash, args, false, false, None, level) - .map(|(v, _)| v) - .map_err(|err| err.new_position(*op_pos))? + self.exec_fn_call( + state, lib, op, 0, args, false, false, false, None, None, level, + ) + .map(|(v, _)| v) + .map_err(|err| err.new_position(*op_pos))? }); match lhs_expr { @@ -1400,20 +1498,20 @@ impl Engine { // Normal function call Expr::FnCall(x) if x.1.is_none() => { - let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref(); + let ((name, native, capture, pos), _, hash, args_expr, def_val) = x.as_ref(); self.make_function_call( scope, mods, state, lib, this_ptr, name, args_expr, *def_val, *hash, *native, - level, + false, *capture, level, ) .map_err(|err| err.new_position(*pos)) } // Module-qualified function call Expr::FnCall(x) if x.1.is_some() => { - let ((name, _, pos), modules, hash, args_expr, def_val) = x.as_ref(); + let ((name, _, capture, pos), modules, hash, args_expr, def_val) = x.as_ref(); self.make_qualified_function_call( scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash, - level, + *capture, level, ) .map_err(|err| err.new_position(*pos)) } @@ -1481,6 +1579,12 @@ impl Engine { } /// Evaluate a statement + /// + /// + /// # Safety + /// + /// This method uses some unsafe code, mainly for avoiding cloning of local variable names via + /// direct lifetime casting. pub(crate) fn eval_stmt( &self, scope: &mut Scope, @@ -1524,7 +1628,7 @@ impl Engine { // If-else statement Stmt::IfThenElse(x) => { - let (expr, if_block, else_block) = x.as_ref(); + let (expr, if_block, else_block, _) = x.as_ref(); self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .as_bool() @@ -1542,7 +1646,7 @@ impl Engine { // While loop Stmt::While(x) => loop { - let (expr, body) = x.as_ref(); + let (expr, body, _) = x.as_ref(); match self .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? @@ -1568,8 +1672,8 @@ impl Engine { }, // Loop statement - Stmt::Loop(body) => loop { - match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) { + Stmt::Loop(x) => loop { + match self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level) { Ok(_) => (), Err(err) => match *err { EvalAltResult::ErrorLoopBreak(false, _) => (), @@ -1581,7 +1685,7 @@ impl Engine { // For loop Stmt::For(x) => { - let (name, expr, stmt) = x.as_ref(); + let (name, expr, stmt, _) = x.as_ref(); let iter_type = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let tid = iter_type.type_id(); @@ -1597,7 +1701,15 @@ impl Engine { state.scope_level += 1; for loop_var in func(iter_type) { - *scope.get_mut(index).0 = loop_var; + let for_var = scope.get_mut(index).0; + let value = loop_var.clone_inner_data().unwrap(); + + if cfg!(not(feature = "no_closure")) && for_var.is_shared() { + *for_var.write_lock().unwrap() = value; + } else { + *for_var = value; + } + self.inc_operations(state) .map_err(|err| err.new_position(stmt.position()))?; @@ -1627,16 +1739,9 @@ impl Engine { // Return value Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => { + let expr = x.1.as_ref().unwrap(); Err(Box::new(EvalAltResult::Return( - self.eval_expr( - scope, - mods, - state, - lib, - this_ptr, - x.1.as_ref().unwrap(), - level, - )?, + self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?, (x.0).1, ))) } @@ -1648,15 +1753,8 @@ impl Engine { // Throw value Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => { - let val = self.eval_expr( - scope, - mods, - state, - lib, - this_ptr, - x.1.as_ref().unwrap(), - level, - )?; + let expr = x.1.as_ref().unwrap(); + let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; Err(Box::new(EvalAltResult::ErrorRuntime( val.take_string().unwrap_or_else(|_| "".into()), (x.0).1, @@ -1672,23 +1770,19 @@ impl Engine { // Let statement Stmt::Let(x) if x.1.is_some() => { - let ((var_name, _), expr) = x.as_ref(); - let val = self.eval_expr( - scope, - mods, - state, - lib, - this_ptr, - expr.as_ref().unwrap(), - level, - )?; + let ((var_name, _), expr, _) = x.as_ref(); + let expr = expr.as_ref().unwrap(); + let val = self + .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .clone_inner_data() + .unwrap(); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false); Ok(Default::default()) } Stmt::Let(x) => { - let ((var_name, _), _) = x.as_ref(); + let ((var_name, _), _, _) = x.as_ref(); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push(var_name, ()); Ok(Default::default()) @@ -1696,8 +1790,11 @@ impl Engine { // Const statement Stmt::Const(x) if x.1.is_constant() => { - let ((var_name, _), expr) = x.as_ref(); - let val = self.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?; + let ((var_name, _), expr, _) = x.as_ref(); + let val = self + .eval_expr(scope, mods, state, lib, this_ptr, &expr, level)? + .clone_inner_data() + .unwrap(); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true); Ok(Default::default()) @@ -1709,7 +1806,7 @@ impl Engine { // Import statement #[cfg(not(feature = "no_module"))] Stmt::Import(x) => { - let (expr, (name, _pos)) = x.as_ref(); + let (expr, (name, _pos), _) = x.as_ref(); // Guard against too many modules #[cfg(not(feature = "unchecked"))] @@ -1742,8 +1839,8 @@ impl Engine { // Export statement #[cfg(not(feature = "no_module"))] - Stmt::Export(list) => { - for ((id, id_pos), rename) in list.iter() { + Stmt::Export(x) => { + for ((id, id_pos), rename) in x.0.iter() { // Mark scope variables as public if let Some(index) = scope.get_index(id).map(|(i, _)| i) { let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id); @@ -1757,19 +1854,40 @@ impl Engine { } Ok(Default::default()) } + + // Share statement + #[cfg(not(feature = "no_closure"))] + Stmt::Share(x) => { + let (var_name, _) = x.as_ref(); + + match scope.get_index(var_name) { + Some((index, ScopeEntryType::Normal)) => { + let (val, _) = scope.get_mut(index); + + if !val.is_shared() { + // Replace the variable with a shared value. + *val = mem::take(val).into_shared(); + } + } + _ => (), + } + Ok(Default::default()) + } }; self.check_data_size(result) .map_err(|err| err.new_position(stmt.position())) } + /// Check a result to ensure that the data size is within allowable limit. + /// Position in `EvalAltResult` may be None and should be set afterwards. #[cfg(feature = "unchecked")] #[inline(always)] fn check_data_size( &self, result: Result>, ) -> Result> { - return result; + result } /// Check a result to ensure that the data size is within allowable limit. @@ -1779,9 +1897,6 @@ impl Engine { &self, result: Result>, ) -> Result> { - #[cfg(feature = "unchecked")] - return result; - // If no data size limits, just return if self.limits.max_string_size + self.limits.max_array_size + self.limits.max_map_size == 0 { diff --git a/src/error.rs b/src/error.rs index b6901c68..1d5ea7f8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -91,6 +91,10 @@ pub enum ParseErrorType { /// /// Never appears under the `no_object` and `no_index` features combination. MalformedInExpr(String), + /// A capturing has syntax error. Wrapped value is the error description (if any). + /// + /// Never appears under the `no_closure` feature. + MalformedCapture(String), /// A map definition has duplicated property names. Wrapped value is the property name. /// /// Never appears under the `no_object` feature. @@ -166,13 +170,14 @@ impl ParseErrorType { Self::MalformedCallExpr(_) => "Invalid expression in function call arguments", Self::MalformedIndexExpr(_) => "Invalid index in indexing expression", Self::MalformedInExpr(_) => "Invalid 'in' expression", + Self::MalformedCapture(_) => "Invalid capturing", Self::DuplicatedProperty(_) => "Duplicated property in object map literal", Self::ForbiddenConstantExpr(_) => "Expecting a constant", Self::PropertyExpected => "Expecting name of a property", Self::VariableExpected => "Expecting name of a variable", Self::Reserved(_) => "Invalid use of reserved keyword", Self::ExprExpected(_) => "Expecting an expression", - Self::FnMissingName => "Expecting name in function declaration", + Self::FnMissingName => "Expecting function name in function declaration", Self::FnMissingParams(_) => "Expecting parameters in function declaration", Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration", Self::FnMissingBody(_) => "Expecting body statement block for function declaration", @@ -199,9 +204,9 @@ impl fmt::Display for ParseErrorType { } Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s), - Self::MalformedIndexExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }), - - Self::MalformedInExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }), + Self::MalformedIndexExpr(s) | Self::MalformedInExpr(s) | Self::MalformedCapture(s) => { + f.write_str(if s.is_empty() { self.desc() } else { s }) + } Self::DuplicatedProperty(s) => { write!(f, "Duplicated property '{}' for object map literal", s) diff --git a/src/fn_call.rs b/src/fn_call.rs index c55124e2..d56487e2 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -4,8 +4,8 @@ use crate::any::Dynamic; use crate::calc_fn_hash; use crate::engine::{ search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG, - KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_PRINT, - KEYWORD_TYPE_OF, + KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_SHARED, + KEYWORD_PRINT, KEYWORD_TYPE_OF, }; use crate::error::ParseErrorType; use crate::fn_native::{FnCallArgs, FnPtr}; @@ -14,6 +14,7 @@ use crate::optimize::OptimizationLevel; use crate::parser::{Expr, ImmutableString, AST, INT}; use crate::result::EvalAltResult; use crate::scope::Scope; +use crate::stdlib::ops::Deref; use crate::token::Position; use crate::utils::StaticVec; @@ -32,6 +33,9 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET}; #[cfg(not(feature = "no_object"))] use crate::engine::{Map, Target, FN_GET, FN_SET}; +#[cfg(not(feature = "no_closure"))] +use crate::scope::Entry as ScopeEntry; + use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, @@ -43,6 +47,9 @@ use crate::stdlib::{ vec::Vec, }; +#[cfg(not(feature = "no_closure"))] +use crate::stdlib::{collections::HashSet, string::String}; + /// Extract the property name from a getter function name. #[inline(always)] fn extract_prop_from_getter(_fn_name: &str) -> Option<&str> { @@ -65,48 +72,121 @@ fn extract_prop_from_setter(_fn_name: &str) -> Option<&str> { None } -/// This function replaces the first argument of a method call with a clone copy. -/// This is to prevent a pure function unintentionally consuming the first argument. -fn normalize_first_arg<'a>( - normalize: bool, - this_copy: &mut Dynamic, - old_this_ptr: &mut Option<&'a mut Dynamic>, - args: &mut FnCallArgs<'a>, -) { - // Only do it for method calls with arguments. - if !normalize || args.is_empty() { - return; - } - - // Clone the original value. - *this_copy = args[0].clone(); - - // Replace the first reference with a reference to the clone, force-casting the lifetime. - // Keep the original reference. Must remember to restore it later with `restore_first_arg_of_method_call`. - // - // # Safety - // - // Blindly casting a a reference to another lifetime saves on allocations and string cloning, - // but must be used with the utmost care. - // - // We can do this here because, at the end of this scope, we'd restore the original reference - // with `restore_first_arg_of_method_call`. Therefore this shorter lifetime does not get "out". - let this_pointer = mem::replace(args.get_mut(0).unwrap(), unsafe { - mem::transmute(this_copy) - }); - - *old_this_ptr = Some(this_pointer); +/// A type that temporarily stores a mutable reference to a `Dynamic`, +/// replacing it with a cloned copy. +#[derive(Debug, Default)] +struct ArgBackup<'a> { + orig_mut: Option<&'a mut Dynamic>, + value_copy: Dynamic, } -/// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`. -fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) { - if let Some(this_pointer) = old_this_ptr { - args[0] = this_pointer; +impl<'a> ArgBackup<'a> { + /// This function replaces the first argument of a method call with a clone copy. + /// This is to prevent a pure function unintentionally consuming the first argument. + /// + /// `restore_first_arg` must be called before the end of the scope to prevent the shorter lifetime from leaking. + /// + /// # Safety + /// + /// This method blindly casts a reference to another lifetime, which saves allocation and string cloning. + /// + /// If `restore_first_arg` is called before the end of the scope, the shorter lifetime will not leak. + fn change_first_arg_to_copy(&mut self, normalize: bool, args: &mut FnCallArgs<'a>) { + // Only do it for method calls with arguments. + if !normalize || args.is_empty() { + return; + } + + // Clone the original value. + self.value_copy = args[0].clone(); + + // Replace the first reference with a reference to the clone, force-casting the lifetime. + // Must remember to restore it later with `restore_first_arg`. + // + // # Safety + // + // Blindly casting a reference to another lifetime saves allocation and string cloning, + // but must be used with the utmost care. + // + // We can do this here because, before the end of this scope, we'd restore the original reference + // via `restore_first_arg`. Therefore this shorter lifetime does not leak. + self.orig_mut = Some(mem::replace(args.get_mut(0).unwrap(), unsafe { + mem::transmute(&mut self.value_copy) + })); } + + /// This function restores the first argument that was replaced by `change_first_arg_to_copy`. + /// + /// # Safety + /// + /// If `change_first_arg_to_copy` has been called, this function **MUST** be called _BEFORE_ exiting + /// the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak. + fn restore_first_arg(&mut self, args: &mut FnCallArgs<'a>) { + if let Some(this_pointer) = self.orig_mut.take() { + args[0] = this_pointer; + } + } +} + +impl Drop for ArgBackup<'_> { + fn drop(&mut self) { + // Panic if the shorter lifetime leaks. + assert!( + self.orig_mut.is_none(), + "MutBackup::restore has not been called prior to existing this scope" + ); + } +} + +// Add captured variables into scope +#[cfg(not(feature = "no_closure"))] +fn add_captured_variables_into_scope<'s>( + externals: &HashSet, + captured: Scope<'s>, + scope: &mut Scope<'s>, +) { + captured + .into_iter() + .filter(|ScopeEntry { name, .. }| externals.contains(name.as_ref())) + .for_each( + |ScopeEntry { + name, typ, value, .. + }| { + match typ { + ScopeEntryType::Normal => scope.push(name, value), + ScopeEntryType::Constant => scope.push_constant(name, value), + }; + }, + ); +} + +#[inline(always)] +pub fn ensure_no_data_race( + fn_name: &str, + args: &FnCallArgs, + is_ref: bool, +) -> Result<(), Box> { + if cfg!(not(feature = "no_closure")) { + let skip = if is_ref { 1 } else { 0 }; + + if let Some((n, _)) = args + .iter() + .skip(skip) + .enumerate() + .find(|(_, a)| a.is_locked()) + { + return Err(Box::new(EvalAltResult::ErrorDataRace( + format!("argument #{} of function '{}'", n + 1 + skip, fn_name), + Position::none(), + ))); + } + } + + Ok(()) } impl Engine { - /// Universal method for calling functions either registered with the `Engine` or written in Rhai. + /// Call a native Rust function registered with the `Engine`. /// Position in `EvalAltResult` is `None` and must be set afterwards. /// /// ## WARNING @@ -114,99 +194,41 @@ impl Engine { /// Function call arguments be _consumed_ when the function requires them to be passed by value. /// All function arguments not in the first position are always passed by value and thus consumed. /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! - pub(crate) fn call_fn_raw( + pub(crate) fn call_native_fn( &self, - _scope: &mut Scope, - _mods: &mut Imports, state: &mut State, lib: &Module, fn_name: &str, - (hash_fn, hash_script): (u64, u64), + hash_fn: u64, args: &mut FnCallArgs, is_ref: bool, - _is_method: bool, + pub_only: bool, def_val: Option, - _level: usize, ) -> Result<(Dynamic, bool), Box> { self.inc_operations(state)?; - let native_only = hash_script == 0; - - // Check for stack overflow - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "unchecked"))] - if _level > self.limits.max_call_stack_depth { - return Err(Box::new( - EvalAltResult::ErrorStackOverflow(Position::none()), - )); - } - - let mut this_copy: Dynamic = Default::default(); - let mut old_this_ptr: Option<&mut Dynamic> = None; - - // Search for the function - // First search in script-defined functions (can override built-in) - // Then search registered native functions (can override packages) + // Search for the native function + // First search registered functions (can override packages) // Then search packages - // NOTE: We skip script functions for global_module and packages, and native functions for lib - let func = if !native_only { - lib.get_fn(hash_script) //.or_else(|| lib.get_fn(hash_fn)) - } else { - None - } - //.or_else(|| self.global_module.get_fn(hash_script)) - .or_else(|| self.global_module.get_fn(hash_fn)) - //.or_else(|| self.packages.get_fn(hash_script)) - .or_else(|| self.packages.get_fn(hash_fn)); + let func = self + .global_module + .get_fn(hash_fn, pub_only) + .or_else(|| self.packages.get_fn(hash_fn, pub_only)); if let Some(func) = func { - #[cfg(not(feature = "no_function"))] - let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !_is_method)); - #[cfg(feature = "no_function")] - let need_normalize = is_ref && func.is_pure(); + assert!(func.is_native()); // Calling pure function but the first argument is a reference? - normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args); - - #[cfg(not(feature = "no_function"))] - if func.is_script() { - // Run scripted function - let fn_def = func.get_fn_def(); - - // Method call of script function - map first argument to `this` - return if _is_method { - let (first, rest) = args.split_at_mut(1); - Ok(( - self.call_script_fn( - _scope, - _mods, - state, - lib, - &mut Some(first[0]), - fn_name, - fn_def, - rest, - _level, - )?, - false, - )) - } else { - let result = self.call_script_fn( - _scope, _mods, state, lib, &mut None, fn_name, fn_def, args, _level, - )?; - - // Restore the original reference - restore_first_arg(old_this_ptr, args); - - Ok((result, false)) - }; - } + let mut backup: ArgBackup = Default::default(); + backup.change_first_arg_to_copy(is_ref && func.is_pure(), args); // Run external function - let result = func.get_native_fn()(self, lib, args)?; + let result = func.get_native_fn()(self, lib, args); // Restore the original reference - restore_first_arg(old_this_ptr, args); + backup.restore_first_arg(args); + + let result = result?; // See if the function match print/debug (which requires special processing) return Ok(match fn_name { @@ -252,7 +274,11 @@ impl Engine { // Getter function not found? if let Some(prop) = extract_prop_from_getter(fn_name) { return Err(Box::new(EvalAltResult::ErrorDotExpr( - format!("- property '{}' unknown or write-only", prop), + format!( + "Unknown property '{}' for {}, or it is write-only", + prop, + self.map_type_name(args[0].type_name()) + ), Position::none(), ))); } @@ -260,7 +286,11 @@ impl Engine { // Setter function not found? if let Some(prop) = extract_prop_from_setter(fn_name) { return Err(Box::new(EvalAltResult::ErrorDotExpr( - format!("- property '{}' unknown or read-only", prop), + format!( + "Unknown property '{}' for {}, or it is read-only", + prop, + self.map_type_name(args[0].type_name()) + ), Position::none(), ))); } @@ -330,6 +360,17 @@ impl Engine { args: &mut FnCallArgs, level: usize, ) -> Result> { + self.inc_operations(state)?; + + // Check for stack overflow + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "unchecked"))] + if level > self.limits.max_call_stack_depth { + return Err(Box::new( + EvalAltResult::ErrorStackOverflow(Position::none()), + )); + } + let orig_scope_level = state.scope_level; state.scope_level += 1; @@ -378,21 +419,21 @@ impl Engine { } // Has a system function an override? - fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64) -> bool { + fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64, pub_only: bool) -> bool { // NOTE: We skip script functions for global_module and packages, and native functions for lib // First check script-defined functions - lib.contains_fn(hash_script) - //|| lib.contains_fn(hash_fn) - // Then check registered functions - //|| self.global_module.contains_fn(hash_script) - || self.global_module.contains_fn(hash_fn) - // Then check packages - //|| self.packages.contains_fn(hash_script) - || self.packages.contains_fn(hash_fn) + lib.contains_fn(hash_script, pub_only) + //|| lib.contains_fn(hash_fn, pub_only) + // Then check registered functions + //|| self.global_module.contains_fn(hash_script, pub_only) + || self.global_module.contains_fn(hash_fn, pub_only) + // Then check packages + //|| self.packages.contains_fn(hash_script, pub_only) + || self.packages.contains_fn(hash_fn, pub_only) } - /// Perform an actual function call, taking care of special functions + /// Perform an actual function call, native Rust or scripted, taking care of special functions. /// Position in `EvalAltResult` is `None` and must be set afterwards. /// /// ## WARNING @@ -405,22 +446,29 @@ impl Engine { state: &mut State, lib: &Module, fn_name: &str, - native_only: bool, hash_script: u64, args: &mut FnCallArgs, is_ref: bool, is_method: bool, + pub_only: bool, + _capture: Option, def_val: Option, level: usize, ) -> Result<(Dynamic, bool), Box> { + // Check for data race. + if cfg!(not(feature = "no_closure")) { + ensure_no_data_race(fn_name, args, is_ref)?; + } + // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. let arg_types = args.iter().map(|a| a.type_id()); let hash_fn = calc_fn_hash(empty(), fn_name, args.len(), arg_types); - let hashes = (hash_fn, if native_only { 0 } else { hash_script }); match fn_name { // type_of - KEYWORD_TYPE_OF if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + KEYWORD_TYPE_OF + if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => + { Ok(( self.map_type_name(args[0].type_name()).to_string().into(), false, @@ -428,7 +476,9 @@ impl Engine { } // Fn - KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + KEYWORD_FN_PTR + if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => + { Err(Box::new(EvalAltResult::ErrorRuntime( "'Fn' should not be called in method style. Try Fn(...);".into(), Position::none(), @@ -436,22 +486,66 @@ impl Engine { } // eval - reaching this point it must be a method-style call - KEYWORD_EVAL if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1) => { + KEYWORD_EVAL + if args.len() == 1 && !self.has_override(lib, hash_fn, hash_script, pub_only) => + { Err(Box::new(EvalAltResult::ErrorRuntime( "'eval' should not be called in method style. Try eval(...);".into(), Position::none(), ))) } - // Normal function call - _ => { - let mut scope = Scope::new(); - let mut mods = Imports::new(); - self.call_fn_raw( - &mut scope, &mut mods, state, lib, fn_name, hashes, args, is_ref, is_method, - def_val, level, - ) + // Normal script function call + #[cfg(not(feature = "no_function"))] + _ if hash_script > 0 && lib.contains_fn(hash_script, pub_only) => { + // Get scripted function + let func = lib.get_fn(hash_script, pub_only).unwrap().get_fn_def(); + + let scope = &mut Scope::new(); + let mods = &mut Imports::new(); + + // Add captured variables into scope + #[cfg(not(feature = "no_closure"))] + if let Some(captured) = _capture { + add_captured_variables_into_scope(&func.externals, captured, scope); + } + + let result = if is_method { + // Method call of script function - map first argument to `this` + let (first, rest) = args.split_at_mut(1); + self.call_script_fn( + scope, + mods, + state, + lib, + &mut Some(first[0]), + fn_name, + func, + rest, + level, + )? + } else { + // Normal call of script function - map first argument to `this` + // The first argument is a reference? + let mut backup: ArgBackup = Default::default(); + backup.change_first_arg_to_copy(is_ref, args); + + let result = self.call_script_fn( + scope, mods, state, lib, &mut None, fn_name, func, args, level, + ); + + // Restore the original reference + backup.restore_first_arg(args); + + result? + }; + + Ok((result, false)) } + // Normal native function call + _ => self.call_native_fn( + state, lib, fn_name, hash_fn, args, is_ref, pub_only, def_val, + ), } } @@ -463,9 +557,21 @@ impl Engine { mods: &mut Imports, state: &mut State, lib: &Module, - script: &Dynamic, + script_expr: &Dynamic, + _level: usize, ) -> Result> { - let script = script.as_str().map_err(|typ| { + self.inc_operations(state)?; + + // Check for stack overflow + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "unchecked"))] + if _level > self.limits.max_call_stack_depth { + return Err(Box::new( + EvalAltResult::ErrorStackOverflow(Position::none()), + )); + } + + let script = script_expr.as_str().map_err(|typ| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name(type_name::()).into(), typ.into(), @@ -499,37 +605,41 @@ impl Engine { } /// Call a dot method. + /// Position in `EvalAltResult` is `None` and must be set afterwards. #[cfg(not(feature = "no_object"))] pub(crate) fn make_method_call( &self, state: &mut State, lib: &Module, + name: &str, + hash_script: u64, target: &mut Target, - expr: &Expr, idx_val: Dynamic, + def_val: Option, + native: bool, + pub_only: bool, level: usize, ) -> Result<(Dynamic, bool), Box> { - let ((name, native, pos), _, hash, _, def_val) = match expr { - Expr::FnCall(x) => x.as_ref(), - _ => unreachable!(), - }; - let is_ref = target.is_ref(); let is_value = target.is_value(); // Get a reference to the mutation target Dynamic let obj = target.as_mut(); let mut idx = idx_val.cast::>(); - let mut _fn_name = name.as_ref(); + let mut _fn_name = name; let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { // FnPtr call - let fn_ptr = obj.downcast_ref::().unwrap(); + let fn_ptr = obj.read_lock::().unwrap(); let mut curry = fn_ptr.curry().iter().cloned().collect::>(); // Redirect function name let fn_name = fn_ptr.fn_name(); // Recalculate hash - let hash = calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty()); + let hash = if native { + 0 + } else { + calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty()) + }; // Arguments are passed as-is, adding the curried arguments let mut arg_values = curry .iter_mut() @@ -539,7 +649,7 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - state, lib, fn_name, *native, hash, args, false, false, *def_val, level, + state, lib, fn_name, hash, args, false, false, pub_only, None, def_val, level, ) } else if _fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::() { // FnPtr call on object @@ -548,7 +658,11 @@ impl Engine { // Redirect function name let fn_name = fn_ptr.get_fn_name().clone(); // Recalculate hash - let hash = calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty()); + let hash = if native { + 0 + } else { + calc_fn_hash(empty(), &fn_name, curry.len() + idx.len(), empty()) + }; // Replace the first argument with the object pointer, adding the curried arguments let mut arg_values = once(obj) .chain(curry.iter_mut()) @@ -558,11 +672,11 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level, + state, lib, &fn_name, hash, args, is_ref, true, pub_only, None, def_val, level, ) } else if _fn_name == KEYWORD_FN_PTR_CURRY && obj.is::() { // Curry call - let fn_ptr = obj.downcast_ref::().unwrap(); + let fn_ptr = obj.read_lock::().unwrap(); Ok(( FnPtr::new_unchecked( fn_ptr.get_fn_name().clone(), @@ -576,16 +690,22 @@ impl Engine { .into(), false, )) + } else if cfg!(not(feature = "no_closure")) + && _fn_name == KEYWORD_IS_SHARED + && idx.is_empty() + { + // take call + Ok((target.is_shared().into(), false)) } else { #[cfg(not(feature = "no_object"))] let redirected; - let mut _hash = *hash; + let mut _hash = hash_script; // Check if it is a map method call in OOP style #[cfg(not(feature = "no_object"))] - if let Some(map) = obj.downcast_ref::() { + if let Some(map) = obj.read_lock::() { if let Some(val) = map.get(_fn_name) { - if let Some(f) = val.downcast_ref::() { + if let Some(f) = val.read_lock::() { // Remap the function name redirected = f.get_fn_name().clone(); _fn_name = &redirected; @@ -595,15 +715,18 @@ impl Engine { } }; + if native { + _hash = 0; + } + // Attached object pointer in front of the arguments let mut arg_values = once(obj).chain(idx.iter_mut()).collect::>(); let args = arg_values.as_mut(); self.exec_fn_call( - state, lib, _fn_name, *native, _hash, args, is_ref, true, *def_val, level, + state, lib, _fn_name, _hash, args, is_ref, true, pub_only, None, def_val, level, ) - } - .map_err(|err| err.new_position(*pos))?; + }?; // Feed the changed temp value back if updated && !is_ref && !is_value { @@ -626,15 +749,17 @@ impl Engine { name: &str, args_expr: &[Expr], def_val: Option, - mut hash: u64, + mut hash_script: u64, native: bool, + pub_only: bool, + capture: bool, level: usize, ) -> Result> { // Handle Fn() if name == KEYWORD_FN_PTR && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); - if !self.has_override(lib, hash_fn, hash) { + if !self.has_override(lib, hash_fn, hash_script, pub_only) { // Fn - only in function call style let expr = args_expr.get(0).unwrap(); let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; @@ -649,7 +774,8 @@ impl Engine { )) }) .and_then(|s| FnPtr::try_from(s)) - .map(Into::::into); + .map(Into::::into) + .map_err(|err| err.new_position(expr.position())); } } @@ -681,27 +807,12 @@ impl Engine { .into()); } - // Handle eval() - if name == KEYWORD_EVAL && args_expr.len() == 1 { - let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); + // Handle is_shared() + if cfg!(not(feature = "no_closure")) && name == KEYWORD_IS_SHARED && args_expr.len() == 1 { + let expr = args_expr.get(0).unwrap(); + let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - if !self.has_override(lib, hash_fn, hash) { - // eval - only in function call style - let prev_len = scope.len(); - let expr = args_expr.get(0).unwrap(); - let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; - let result = self - .eval_script_expr(scope, mods, state, lib, &script) - .map_err(|err| err.new_position(expr.position())); - - if scope.len() != prev_len { - // IMPORTANT! If the eval defines new variables in the current scope, - // all variable offsets from this point on will be mis-aligned. - state.always_search = true; - } - - return result; - } + return Ok(value.is_shared().into()); } // Handle call() - Redirect function call @@ -710,7 +821,10 @@ impl Engine { let mut curry: StaticVec<_> = Default::default(); let mut name = name; - if name == KEYWORD_FN_PTR_CALL && args_expr.len() >= 1 && !self.has_override(lib, 0, hash) { + if name == KEYWORD_FN_PTR_CALL + && args_expr.len() >= 1 + && !self.has_override(lib, 0, hash_script, pub_only) + { let expr = args_expr.get(0).unwrap(); let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; @@ -723,7 +837,7 @@ impl Engine { // Skip the first argument args_expr = &args_expr.as_ref()[1..]; // Recalculate hash - hash = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); + hash_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); } else { return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( self.map_type_name(type_name::()).into(), @@ -733,20 +847,48 @@ impl Engine { } } - // Normal function call - except for Fn and eval (handled above) + // Handle eval() + if name == KEYWORD_EVAL && args_expr.len() == 1 { + let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); + + if !self.has_override(lib, hash_fn, hash_script, pub_only) { + // eval - only in function call style + let prev_len = scope.len(); + let expr = args_expr.get(0).unwrap(); + let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; + let result = self + .eval_script_expr(scope, mods, state, lib, &script, level + 1) + .map_err(|err| err.new_position(expr.position())); + + // IMPORTANT! If the eval defines new variables in the current scope, + // all variable offsets from this point on will be mis-aligned. + if scope.len() != prev_len { + state.always_search = true; + } + + return result; + } + } + + // Normal function call - except for Fn, curry, call and eval (handled above) let mut arg_values: StaticVec<_>; let mut args: StaticVec<_>; let mut is_ref = false; + let capture = if cfg!(not(feature = "no_closure")) && capture && !scope.is_empty() { + Some(scope.flatten_clone()) + } else { + None + }; if args_expr.is_empty() && curry.is_empty() { // No arguments args = Default::default(); } else { - // See if the first argument is a variable, if so, convert to method-call style + // If the first argument is a variable, and there is no curried arguments, convert to method-call style // in order to leverage potential &mut first argument and avoid cloning the value match args_expr.get(0).unwrap() { // func(x, ...) -> x.func(...) - lhs @ Expr::Variable(_) => { + lhs @ Expr::Variable(_) if curry.is_empty() => { arg_values = args_expr .iter() .skip(1) @@ -758,12 +900,14 @@ impl Engine { self.inc_operations(state) .map_err(|err| err.new_position(pos))?; - args = once(target) - .chain(curry.iter_mut()) - .chain(arg_values.iter_mut()) - .collect(); - - is_ref = true; + // Turn it into a method call only if the object is not shared + args = if target.is_shared() { + arg_values.insert(0, target.clone().clone_inner_data().unwrap()); + arg_values.iter_mut().collect() + } else { + is_ref = true; + once(target).chain(arg_values.iter_mut()).collect() + }; } // func(..., ...) _ => { @@ -777,9 +921,11 @@ impl Engine { } } + let hash = if native { 0 } else { hash_script }; let args = args.as_mut(); + self.exec_fn_call( - state, lib, name, native, hash, args, is_ref, false, def_val, level, + state, lib, name, hash, args, is_ref, false, pub_only, capture, def_val, level, ) .map(|(v, _)| v) } @@ -798,10 +944,10 @@ impl Engine { args_expr: &[Expr], def_val: Option, hash_script: u64, + _capture: bool, level: usize, ) -> Result> { let modules = modules.as_ref().unwrap(); - let mut arg_values: StaticVec<_>; let mut args: StaticVec<_>; @@ -845,8 +991,8 @@ impl Engine { // First search in script-defined functions (can override built-in) let func = match module.get_qualified_fn(hash_script) { - Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => { - // Then search in Rust functions + // Then search in Rust functions + None => { self.inc_operations(state)?; // Qualified Rust functions are indexed in two steps: @@ -865,40 +1011,43 @@ impl Engine { match func { #[cfg(not(feature = "no_function"))] - Ok(f) if f.is_script() => { + Some(f) if f.is_script() => { let args = args.as_mut(); - let fn_def = f.get_fn_def(); - let mut scope = Scope::new(); - let mut mods = Imports::new(); - self.call_script_fn( - &mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level, - ) + let func = f.get_fn_def(); + + let scope = &mut Scope::new(); + let mods = &mut Imports::new(); + + // Add captured variables into scope + #[cfg(not(feature = "no_closure"))] + if _capture && !scope.is_empty() { + add_captured_variables_into_scope( + &func.externals, + scope.flatten_clone(), + scope, + ); + } + + self.call_script_fn(scope, mods, state, lib, &mut None, name, func, args, level) } - Ok(f) => f.get_native_fn()(self, lib, args.as_mut()), - Err(err) => match *err { - EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => { - Ok(def_val.unwrap().into()) - } - EvalAltResult::ErrorFunctionNotFound(_, pos) => { - Err(Box::new(EvalAltResult::ErrorFunctionNotFound( - format!( - "{}{} ({})", - modules, - name, - args.iter() - .map(|a| if a.is::() { - "&str | ImmutableString | String" - } else { - self.map_type_name((*a).type_name()) - }) - .collect::>() - .join(", ") - ), - pos, - ))) - } - _ => Err(err), - }, + Some(f) => f.get_native_fn()(self, lib, args.as_mut()), + None if def_val.is_some() => Ok(def_val.unwrap().into()), + None => Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + format!( + "{}{} ({})", + modules, + name, + args.iter() + .map(|a| if a.is::() { + "&str | ImmutableString | String" + } else { + self.map_type_name((*a).type_name()) + }) + .collect::>() + .join(", ") + ), + Position::none(), + ))), } } } @@ -918,33 +1067,33 @@ pub fn run_builtin_binary_op( } if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); + let x = x.clone().cast::(); + let y = y.clone().cast::(); - #[cfg(not(feature = "unchecked"))] - match op { - "+" => return add(x, y).map(Into::into).map(Some), - "-" => return sub(x, y).map(Into::into).map(Some), - "*" => return mul(x, y).map(Into::into).map(Some), - "/" => return div(x, y).map(Into::into).map(Some), - "%" => return modulo(x, y).map(Into::into).map(Some), - "~" => return pow_i_i(x, y).map(Into::into).map(Some), - ">>" => return shr(x, y).map(Into::into).map(Some), - "<<" => return shl(x, y).map(Into::into).map(Some), - _ => (), - } - - #[cfg(feature = "unchecked")] - match op { - "+" => return Ok(Some((x + y).into())), - "-" => return Ok(Some((x - y).into())), - "*" => return Ok(Some((x * y).into())), - "/" => return Ok(Some((x / y).into())), - "%" => return Ok(Some((x % y).into())), - "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), - ">>" => return shr_u(x, y).map(Into::into).map(Some), - "<<" => return shl_u(x, y).map(Into::into).map(Some), - _ => (), + if cfg!(not(feature = "unchecked")) { + match op { + "+" => return add(x, y).map(Into::into).map(Some), + "-" => return sub(x, y).map(Into::into).map(Some), + "*" => return mul(x, y).map(Into::into).map(Some), + "/" => return div(x, y).map(Into::into).map(Some), + "%" => return modulo(x, y).map(Into::into).map(Some), + "~" => return pow_i_i(x, y).map(Into::into).map(Some), + ">>" => return shr(x, y).map(Into::into).map(Some), + "<<" => return shl(x, y).map(Into::into).map(Some), + _ => (), + } + } else { + match op { + "+" => return Ok(Some((x + y).into())), + "-" => return Ok(Some((x - y).into())), + "*" => return Ok(Some((x * y).into())), + "/" => return Ok(Some((x / y).into())), + "%" => return Ok(Some((x % y).into())), + "~" => return pow_i_i_u(x, y).map(Into::into).map(Some), + ">>" => return shr_u(x, y).map(Into::into).map(Some), + "<<" => return shl_u(x, y).map(Into::into).map(Some), + _ => (), + } } match op { @@ -960,8 +1109,8 @@ pub fn run_builtin_binary_op( _ => (), } } else if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); + let x = x.clone().cast::(); + let y = y.clone().cast::(); match op { "&" => return Ok(Some((x && y).into())), @@ -972,8 +1121,8 @@ pub fn run_builtin_binary_op( _ => (), } } else if args_type == TypeId::of::() { - let x = x.downcast_ref::().unwrap(); - let y = y.downcast_ref::().unwrap(); + let x = &*x.read_lock::().unwrap(); + let y = &*y.read_lock::().unwrap(); match op { "+" => return Ok(Some((x + y).into())), @@ -986,8 +1135,8 @@ pub fn run_builtin_binary_op( _ => (), } } else if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); + let x = x.clone().cast::(); + let y = y.clone().cast::(); match op { "==" => return Ok(Some((x == y).into())), @@ -1008,8 +1157,8 @@ pub fn run_builtin_binary_op( #[cfg(not(feature = "no_float"))] if args_type == TypeId::of::() { - let x = *x.downcast_ref::().unwrap(); - let y = *y.downcast_ref::().unwrap(); + let x = x.clone().cast::(); + let y = y.clone().cast::(); match op { "+" => return Ok(Some((x + y).into())), @@ -1046,33 +1195,33 @@ pub fn run_builtin_op_assignment( } if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); - let y = *y.downcast_ref::().unwrap(); + let y = y.clone().cast::(); + let mut x = x.write_lock::().unwrap(); - #[cfg(not(feature = "unchecked"))] - match op { - "+=" => return Ok(Some(*x = add(*x, y)?)), - "-=" => return Ok(Some(*x = sub(*x, y)?)), - "*=" => return Ok(Some(*x = mul(*x, y)?)), - "/=" => return Ok(Some(*x = div(*x, y)?)), - "%=" => return Ok(Some(*x = modulo(*x, y)?)), - "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), - ">>=" => return Ok(Some(*x = shr(*x, y)?)), - "<<=" => return Ok(Some(*x = shl(*x, y)?)), - _ => (), - } - - #[cfg(feature = "unchecked")] - match op { - "+=" => return Ok(Some(*x += y)), - "-=" => return Ok(Some(*x -= y)), - "*=" => return Ok(Some(*x *= y)), - "/=" => return Ok(Some(*x /= y)), - "%=" => return Ok(Some(*x %= y)), - "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), - ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), - "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), - _ => (), + if cfg!(not(feature = "unchecked")) { + match op { + "+=" => return Ok(Some(*x = add(*x, y)?)), + "-=" => return Ok(Some(*x = sub(*x, y)?)), + "*=" => return Ok(Some(*x = mul(*x, y)?)), + "/=" => return Ok(Some(*x = div(*x, y)?)), + "%=" => return Ok(Some(*x = modulo(*x, y)?)), + "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), + ">>=" => return Ok(Some(*x = shr(*x, y)?)), + "<<=" => return Ok(Some(*x = shl(*x, y)?)), + _ => (), + } + } else { + match op { + "+=" => return Ok(Some(*x += y)), + "-=" => return Ok(Some(*x -= y)), + "*=" => return Ok(Some(*x *= y)), + "/=" => return Ok(Some(*x /= y)), + "%=" => return Ok(Some(*x %= y)), + "~=" => return Ok(Some(*x = pow_i_i_u(*x, y)?)), + ">>=" => return Ok(Some(*x = shr_u(*x, y)?)), + "<<=" => return Ok(Some(*x = shl_u(*x, y)?)), + _ => (), + } } match op { @@ -1082,8 +1231,8 @@ pub fn run_builtin_op_assignment( _ => (), } } else if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); - let y = *y.downcast_ref::().unwrap(); + let y = y.clone().cast::(); + let mut x = x.write_lock::().unwrap(); match op { "&=" => return Ok(Some(*x = *x && y)), @@ -1091,8 +1240,8 @@ pub fn run_builtin_op_assignment( _ => (), } } else if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); - let y = y.downcast_ref::().unwrap(); + let y = y.read_lock::().unwrap().deref().clone(); + let mut x = x.write_lock::().unwrap(); match op { "+=" => return Ok(Some(*x += y)), @@ -1102,8 +1251,8 @@ pub fn run_builtin_op_assignment( #[cfg(not(feature = "no_float"))] if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); - let y = *y.downcast_ref::().unwrap(); + let y = y.clone().cast::(); + let mut x = x.write_lock::().unwrap(); match op { "+=" => return Ok(Some(*x += y)), diff --git a/src/fn_native.rs b/src/fn_native.rs index bfb8b956..a972793b 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,16 +1,18 @@ //! Module defining interfaces to native-Rust functions. use crate::any::Dynamic; +use crate::calc_fn_hash; use crate::engine::Engine; use crate::module::Module; +use crate::parser::FnAccess; use crate::result::EvalAltResult; use crate::token::{is_valid_identifier, Position}; use crate::utils::ImmutableString; #[cfg(not(feature = "no_function"))] -use crate::{module::FuncReturn, parser::ScriptFnDef, scope::Scope, utils::StaticVec}; +use crate::{module::FuncReturn, parser::ScriptFnDef, utils::StaticVec}; -use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, string::String, vec::Vec}; +use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, string::String, vec::Vec}; #[cfg(not(feature = "no_function"))] use crate::stdlib::mem; @@ -20,23 +22,43 @@ use crate::stdlib::rc::Rc; #[cfg(feature = "sync")] use crate::stdlib::sync::Arc; +#[cfg(not(feature = "no_closure"))] +#[cfg(not(feature = "sync"))] +use crate::stdlib::cell::RefCell; +#[cfg(not(feature = "no_closure"))] +#[cfg(feature = "sync")] +use crate::stdlib::sync::RwLock; + /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] pub trait SendSync: Send + Sync {} +/// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] impl SendSync for T {} /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(not(feature = "sync"))] pub trait SendSync {} +/// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(not(feature = "sync"))] impl SendSync for T {} +/// Immutable reference-counted container #[cfg(not(feature = "sync"))] pub type Shared = Rc; +/// Immutable reference-counted container #[cfg(feature = "sync")] pub type Shared = Arc; +/// Mutable reference-counted container (read-write lock) +#[cfg(not(feature = "no_closure"))] +#[cfg(not(feature = "sync"))] +pub type SharedMut = Shared>; +/// Mutable reference-counted container (read-write lock) +#[cfg(not(feature = "no_closure"))] +#[cfg(feature = "sync")] +pub type SharedMut = Shared>; + /// Consume a `Shared` resource and return a mutable reference to the wrapped value. /// If the resource is shared (i.e. has other outstanding references), a cloned copy is used. pub fn shared_make_mut(value: &mut Shared) -> &mut T { @@ -89,9 +111,7 @@ impl FnPtr { /// Call the function pointer with curried arguments (if any). /// - /// The function must be a script-defined function. It cannot be a Rust function. - /// - /// To call a Rust function, just call it directly in Rust! + /// If this function is a script-defined function, it must not be marked private. /// /// ## WARNING /// @@ -107,14 +127,39 @@ impl FnPtr { this_ptr: Option<&mut Dynamic>, mut arg_values: impl AsMut<[Dynamic]>, ) -> FuncReturn { - let args = self + let mut args_data = self .1 .iter() .cloned() .chain(arg_values.as_mut().iter_mut().map(|v| mem::take(v))) .collect::>(); - 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::>(); + + if let Some(obj) = this_ptr { + args.insert(0, obj); + } + + let fn_name = self.0.as_str(); + let hash_script = calc_fn_hash(empty(), fn_name, args_len, empty()); + + engine + .exec_fn_call( + &mut Default::default(), + lib.as_ref(), + fn_name, + hash_script, + args.as_mut(), + has_this, + has_this, + true, + None, + None, + 0, + ) + .map(|(v, _)| v) } } @@ -255,6 +300,23 @@ impl CallableFunction { Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false, } } + /// Is this a native Rust function? + pub fn is_native(&self) -> bool { + match self { + Self::Pure(_) | Self::Method(_) => true, + Self::Iterator(_) => true, + + #[cfg(not(feature = "no_function"))] + Self::Script(_) => false, + } + } + /// Get the access mode. + pub fn access(&self) -> FnAccess { + match self { + Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => FnAccess::Public, + Self::Script(f) => f.access, + } + } /// Get a reference to a native Rust function. /// /// # Panics diff --git a/src/fn_register.rs b/src/fn_register.rs index c2eb5534..e01f44a1 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -2,7 +2,7 @@ #![allow(non_snake_case)] -use crate::any::{Dynamic, Variant}; +use crate::any::{Dynamic, Variant, DynamicWriteLock}; use crate::engine::Engine; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; use crate::module::Module; @@ -15,7 +15,7 @@ use crate::stdlib::{ any::TypeId, boxed::Box, mem, - string::{String, ToString}, + string::String, }; /// Trait to register custom functions with the `Engine`. @@ -97,11 +97,11 @@ pub trait RegisterResultFn { pub struct Mut(T); //pub struct Ref(T); -/// Dereference into &mut. +/// Dereference into DynamicWriteLock #[inline(always)] -pub fn by_ref(data: &mut Dynamic) -> &mut T { - // Directly cast the &mut Dynamic into &mut T to access the underlying data. - data.downcast_mut::().unwrap() +pub fn by_ref(data: &mut Dynamic) -> DynamicWriteLock { + // Directly cast the &mut Dynamic into DynamicWriteLock to access the underlying data. + data.write_lock::().unwrap() } /// Dereference into value. @@ -114,7 +114,7 @@ pub fn by_value(data: &mut Dynamic) -> T { ref_T.clone() } else if TypeId::of::() == TypeId::of::() { // If T is String, data must be ImmutableString, so map directly to it - *unsafe_cast_box(Box::new(data.as_str().unwrap().to_string())).unwrap() + *unsafe_cast_box(Box::new(data.clone().take_string().unwrap())).unwrap() } else { // We consume the argument and then replace it with () - the argument is not supposed to be used again. // This way, we avoid having to clone the argument again, because it is already a clone when passed here. @@ -124,24 +124,23 @@ pub fn by_value(data: &mut Dynamic) -> T { /// This macro creates a closure wrapping a registered function. macro_rules! make_func { - ($fn:ident : $map:expr ; $($par:ident => $convert:expr),*) => { + ($fn:ident : $map:expr ; $($par:ident => $let:stmt => $convert:expr => $arg:expr),*) => { // ^ function pointer // ^ result mapping function // ^ function parameter generic type name (A, B, C etc.) -// ^ dereferencing function +// ^ argument let statement(e.g. let mut A ...) +// ^ dereferencing function +// ^ argument reference expression(like A, *B, &mut C etc) Box::new(move |_: &Engine, _: &Module, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! let mut _drain = args.iter_mut(); - $( - // Downcast every element, panic in case of a type mismatch (which shouldn't happen). - // Call the user-supplied function using ($convert) to access it either by value or by reference. - let $par = ($convert)(_drain.next().unwrap()); - )* + $($let)* + $($par = ($convert)(_drain.next().unwrap()); )* // Call the function with each parameter value - let r = $fn($($par),*); + let r = $fn($($arg),*); // Map the result $map(r) @@ -181,12 +180,13 @@ macro_rules! def_register { () => { def_register!(imp from_pure :); }; - (imp $abi:ident : $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => { + (imp $abi:ident : $($par:ident => $arg:expr => $mark:ty => $param:ty => $let:stmt => $clone:expr),*) => { // ^ function ABI type // ^ function parameter generic type name (A, B, C etc.) - // ^ function parameter marker type (T, Ref or Mut) - // ^ function parameter actual type (T, &T or &mut T) - // ^ dereferencing function +// ^ call argument(like A, *B, &mut C etc) + // ^ function parameter marker type (T, Ref or Mut) + // ^ function parameter actual type (T, &T or &mut T) + // ^ argument let statement impl< $($par: Variant + Clone,)* FN: Fn($($param),*) -> RET + SendSync + 'static, @@ -196,7 +196,7 @@ macro_rules! def_register { fn register_fn(&mut self, name: &str, f: FN) -> &mut Self { self.global_module.set_fn(name, FnAccess::Public, &[$(map_type_id::<$par>()),*], - CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $clone),*)) + CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $let => $clone => $arg),*)) ); self } @@ -210,7 +210,7 @@ macro_rules! def_register { fn register_result_fn(&mut self, name: &str, f: FN) -> &mut Self { self.global_module.set_fn(name, FnAccess::Public, &[$(map_type_id::<$par>()),*], - CallableFunction::$abi(make_func!(f : map_result ; $($par => $clone),*)) + CallableFunction::$abi(make_func!(f : map_result ; $($par => $let => $clone => $arg),*)) ); self } @@ -219,11 +219,11 @@ macro_rules! def_register { //def_register!(imp_pop $($par => $mark => $param),*); }; ($p0:ident $(, $p:ident)*) => { - def_register!(imp from_pure : $p0 => $p0 => $p0 => by_value $(, $p => $p => $p => by_value)*); - def_register!(imp from_method : $p0 => Mut<$p0> => &mut $p0 => by_ref $(, $p => $p => $p => by_value)*); + def_register!(imp from_pure : $p0 => $p0 => $p0 => $p0 => let $p0 => by_value $(, $p => $p => $p => $p => let $p => by_value)*); + def_register!(imp from_method : $p0 => &mut $p0 => Mut<$p0> => &mut $p0 => let mut $p0 => by_ref $(, $p => $p => $p => $p => let $p => by_value)*); // ^ CallableFunction - // handle the first parameter ^ first parameter passed through - // ^ others passed by value (by_value) + // handle the first parameter ^ first parameter passed through + // ^ others passed by value (by_value) // Currently does not support first argument which is a reference, as there will be // conflicting implementations since &T: Any and T: Any cannot be distinguished diff --git a/src/lib.rs b/src/lib.rs index 1194fd67..7406b45a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,23 +48,7 @@ //! } //! ``` //! -//! ## Optional features -//! -//! | Feature | Description | -//! | ------------- | ----------------------------------------------------------------------------------------------------------------------------------| -//! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | -//! | `no_function` | Disable script-defined functions if not needed. | -//! | `no_module` | Disable loading external modules if not needed. | -//! | `no_index` | Disable arrays and indexing features if not needed. | -//! | `no_object` | Disable support for custom types and objects. | -//! | `no_float` | Disable floating-point numbers and math if not needed. | -//! | `no_optimize` | Disable the script optimizer. | -//! | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | -//! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | -//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | -//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and `AST` are all `Send + Sync`. | -//! | `serde` | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | -//! | `internals` | Expose internal data structures (beware they may be volatile from version to version). | +//! # Documentation //! //! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language. diff --git a/src/module.rs b/src/module.rs index 07430f6e..fa23abd6 100644 --- a/src/module.rs +++ b/src/module.rs @@ -355,10 +355,20 @@ impl Module { /// /// let mut module = Module::new(); /// let hash = module.set_fn_0("calc", || Ok(42_i64)); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` - pub fn contains_fn(&self, hash_fn: u64) -> bool { - self.functions.contains_key(&hash_fn) + pub fn contains_fn(&self, hash_fn: u64, public_only: bool) -> bool { + if public_only { + self.functions + .get(&hash_fn) + .map(|(_, access, _, _)| match access { + FnAccess::Public => true, + FnAccess::Private => false, + }) + .unwrap_or(false) + } else { + self.functions.contains_key(&hash_fn) + } } /// Set a Rust function into the module, returning a hash key. @@ -422,7 +432,7 @@ impl Module { /// let mut module = Module::new(); /// let hash = module.set_raw_fn("double_or_not", /// // Pass parameter types via a slice with TypeId's - /// &[std::any::TypeId::of::(), std::any::TypeId::of::() ], + /// &[std::any::TypeId::of::(), std::any::TypeId::of::()], /// // Fixed closure signature /// |engine, lib, args| { /// // 'args' is guaranteed to be the right length and of the correct types @@ -432,7 +442,7 @@ impl Module { /// // Since it is a primary type, it can also be cheaply copied /// let double = args[1].clone().cast::(); /// // Get a mutable reference to the first argument. - /// let x = args[0].downcast_mut::().unwrap(); + /// let mut x = args[0].write_lock::().unwrap(); /// /// let orig = *x; /// @@ -443,7 +453,7 @@ impl Module { /// Ok(orig) // return Result> /// }); /// - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_raw_fn( &mut self, @@ -468,7 +478,7 @@ impl Module { /// /// let mut module = Module::new(); /// let hash = module.set_fn_0("calc", || Ok(42_i64)); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_0( &mut self, @@ -491,7 +501,7 @@ impl Module { /// /// let mut module = Module::new(); /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_1( &mut self, @@ -516,7 +526,7 @@ impl Module { /// /// let mut module = Module::new(); /// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_1_mut( &mut self, @@ -524,7 +534,7 @@ impl Module { func: impl Fn(&mut A) -> FuncReturn + SendSync + 'static, ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { - func(args[0].downcast_mut::().unwrap()).map(Dynamic::from) + func(&mut args[0].write_lock::().unwrap()).map(Dynamic::from) }; let arg_types = [TypeId::of::()]; self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) @@ -541,7 +551,7 @@ impl Module { /// /// let mut module = Module::new(); /// let hash = module.set_getter_fn("value", |x: &mut i64| { Ok(*x) }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` #[cfg(not(feature = "no_object"))] pub fn set_getter_fn( @@ -565,7 +575,7 @@ impl Module { /// let hash = module.set_fn_2("calc", |x: i64, y: ImmutableString| { /// Ok(x + y.len() as i64) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_2( &mut self, @@ -596,7 +606,7 @@ impl Module { /// let hash = module.set_fn_2_mut("calc", |x: &mut i64, y: ImmutableString| { /// *x += y.len() as i64; Ok(*x) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_2_mut( &mut self, @@ -605,9 +615,9 @@ impl Module { ) -> u64 { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); - let a = args[0].downcast_mut::().unwrap(); + let mut a = args[0].write_lock::().unwrap(); - func(a, b).map(Dynamic::from) + func(&mut a, b).map(Dynamic::from) }; let arg_types = [TypeId::of::(), TypeId::of::()]; self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) @@ -628,7 +638,7 @@ impl Module { /// *x = y.len() as i64; /// Ok(()) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` #[cfg(not(feature = "no_object"))] pub fn set_setter_fn( @@ -653,7 +663,7 @@ impl Module { /// let hash = module.set_indexer_get_fn(|x: &mut i64, y: ImmutableString| { /// Ok(*x + y.len() as i64) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] @@ -677,7 +687,7 @@ impl Module { /// let hash = module.set_fn_3("calc", |x: i64, y: ImmutableString, z: i64| { /// Ok(x + y.len() as i64 + z) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_3< A: Variant + Clone, @@ -714,7 +724,7 @@ impl Module { /// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: ImmutableString, z: i64| { /// *x += y.len() as i64 + z; Ok(*x) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_3_mut< A: Variant + Clone, @@ -729,9 +739,9 @@ impl Module { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); - let a = args[0].downcast_mut::().unwrap(); + let mut a = args[0].write_lock::().unwrap(); - func(a, b, c).map(Dynamic::from) + func(&mut a, b, c).map(Dynamic::from) }; let arg_types = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) @@ -752,7 +762,7 @@ impl Module { /// *x = y.len() as i64 + value; /// Ok(()) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] @@ -763,9 +773,9 @@ impl Module { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); - let a = args[0].downcast_mut::().unwrap(); + let mut a = args[0].write_lock::().unwrap(); - func(a, b, c).map(Dynamic::from) + func(&mut a, b, c).map(Dynamic::from) }; let arg_types = [TypeId::of::(), TypeId::of::(), TypeId::of::()]; self.set_fn( @@ -796,8 +806,8 @@ impl Module { /// Ok(()) /// } /// ); - /// assert!(module.contains_fn(hash_get)); - /// assert!(module.contains_fn(hash_set)); + /// assert!(module.contains_fn(hash_get, true)); + /// assert!(module.contains_fn(hash_set, true)); /// ``` #[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_index"))] @@ -825,7 +835,7 @@ impl Module { /// let hash = module.set_fn_4("calc", |x: i64, y: ImmutableString, z: i64, _w: ()| { /// Ok(x + y.len() as i64 + z) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_4< A: Variant + Clone, @@ -869,7 +879,7 @@ impl Module { /// let hash = module.set_fn_4_mut("calc", |x: &mut i64, y: ImmutableString, z: i64, _w: ()| { /// *x += y.len() as i64 + z; Ok(*x) /// }); - /// assert!(module.contains_fn(hash)); + /// assert!(module.contains_fn(hash, true)); /// ``` pub fn set_fn_4_mut< A: Variant + Clone, @@ -886,9 +896,9 @@ impl Module { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); let d = mem::take(args[3]).cast::(); - let a = args[0].downcast_mut::().unwrap(); + let mut a = args[0].write_lock::().unwrap(); - func(a, b, c, d).map(Dynamic::from) + func(&mut a, b, c, d).map(Dynamic::from) }; let arg_types = [ TypeId::of::(), @@ -903,8 +913,14 @@ impl Module { /// /// The `u64` hash is calculated by the function `crate::calc_fn_hash`. /// It is also returned by the `set_fn_XXX` calls. - pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&Func> { - self.functions.get(&hash_fn).map(|(_, _, _, v)| v) + pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&Func> { + self.functions + .get(&hash_fn) + .and_then(|(_, access, _, f)| match access { + _ if !public_only => Some(f), + FnAccess::Public => Some(f), + FnAccess::Private => None, + }) } /// Get a modules-qualified function. @@ -912,16 +928,8 @@ impl Module { /// /// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match /// the hash calculated by `index_all_sub_modules`. - pub(crate) fn get_qualified_fn( - &self, - hash_qualified_fn: u64, - ) -> Result<&Func, Box> { - self.all_functions.get(&hash_qualified_fn).ok_or_else(|| { - Box::new(EvalAltResult::ErrorFunctionNotFound( - String::new(), - Position::none(), - )) - }) + pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&Func> { + self.all_functions.get(&hash_qualified_fn) } /// Merge another module into this module. diff --git a/src/optimize.rs b/src/optimize.rs index 0948fbd5..7503c592 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -3,12 +3,12 @@ use crate::any::Dynamic; use crate::calc_fn_hash; use crate::engine::{ - Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, + Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; +use crate::fn_native::FnPtr; use crate::module::Module; use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; -use crate::token::is_valid_identifier; use crate::utils::StaticVec; #[cfg(not(feature = "no_function"))] @@ -19,6 +19,7 @@ use crate::parser::CustomExpr; use crate::stdlib::{ boxed::Box, + convert::TryFrom, iter::empty, string::{String, ToString}, vec, @@ -131,21 +132,18 @@ fn call_fn_with_constant_arguments( state .engine - .call_fn_raw( - &mut Scope::new(), - &mut Imports::new(), + .call_native_fn( &mut Default::default(), state.lib, fn_name, - (hash_fn, 0), + hash_fn, arg_values.iter_mut().collect::>().as_mut(), false, - false, + true, None, - 0, ) - .map(|(v, _)| Some(v)) - .unwrap_or_else(|_| None) + .ok() + .map(|(v, _)| v) } /// Optimize a statement. @@ -184,6 +182,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { optimize_expr(expr, state), optimize_stmt(x.1, state, true), None, + x.3, ))), }, // if expr { if_block } else { else_block } @@ -200,6 +199,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { Stmt::Noop(_) => None, // Noop -> no else block stmt => Some(stmt), }, + x.3, ))), }, // while expr { block } @@ -210,7 +210,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { Stmt::Noop(pos) } // while true { block } -> loop { block } - Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(x.1, state, false))), + Expr::True(_) => Stmt::Loop(Box::new((optimize_stmt(x.1, state, false), x.2))), // while expr { block } expr => match optimize_stmt(x.1, state, false) { // while expr { break; } -> { expr; } @@ -225,11 +225,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { Stmt::Block(Box::new((statements, pos))) } // while expr { block } - stmt => Stmt::While(Box::new((optimize_expr(expr, state), stmt))), + stmt => Stmt::While(Box::new((optimize_expr(expr, state), stmt, x.2))), }, }, // loop { block } - Stmt::Loop(block) => match optimize_stmt(*block, state, false) { + Stmt::Loop(x) => match optimize_stmt(x.0, state, false) { // loop { break; } -> Noop Stmt::Break(pos) => { // Only a single break statement @@ -237,23 +237,26 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { Stmt::Noop(pos) } // loop { block } - stmt => Stmt::Loop(Box::new(stmt)), + stmt => Stmt::Loop(Box::new((stmt, x.1))), }, // for id in expr { block } Stmt::For(x) => Stmt::For(Box::new(( x.0, optimize_expr(x.1, state), optimize_stmt(x.2, state, false), + x.3, ))), // let id = expr; - Stmt::Let(x) if x.1.is_some() => { - Stmt::Let(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state))))) - } + Stmt::Let(x) if x.1.is_some() => Stmt::Let(Box::new(( + x.0, + Some(optimize_expr(x.1.unwrap(), state)), + x.2, + ))), // let id; stmt @ Stmt::Let(_) => stmt, // import expr as id; #[cfg(not(feature = "no_module"))] - Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1))), + Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1, x.2))), // { block } Stmt::Block(x) => { let orig_len = x.0.len(); // Original number of statements in the block, for change detection @@ -266,7 +269,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { .map(|stmt| match stmt { // Add constant into the state Stmt::Const(v) => { - let ((name, pos), expr) = *v; + let ((name, pos), expr, _) = *v; state.push_constant(&name, expr); state.set_dirty(); Stmt::Noop(pos) // No need to keep constants @@ -366,9 +369,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { // expr; Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))), // return expr; - Stmt::ReturnWithVal(x) if x.1.is_some() => { - Stmt::ReturnWithVal(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state))))) - } + Stmt::ReturnWithVal(x) if x.1.is_some() => Stmt::ReturnWithVal(Box::new(( + x.0, + Some(optimize_expr(x.1.unwrap(), state)), + x.2, + ))), // All other statements - skip stmt => stmt, } @@ -410,8 +415,8 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // All other items can be thrown away. state.set_dirty(); let pos = m.1; - m.0.into_iter().find(|((name, _), _)| name.as_str() == prop.as_str()) - .map(|(_, expr)| expr.set_position(pos)) + m.0.into_iter().find(|((name, _), _)| name == prop) + .map(|(_, mut expr)| { expr.set_position(pos); expr }) .unwrap_or_else(|| Expr::Unit(pos)) } // lhs.rhs @@ -428,7 +433,9 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // Array literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); - a.0.take(i.0 as usize).set_position(a.1) + let mut expr = a.0.remove(i.0 as usize); + expr.set_position(a.1); + expr } // map[string] (Expr::Map(m), Expr::StringConstant(s)) if m.0.iter().all(|(_, x)| x.is_pure()) => { @@ -437,7 +444,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); let pos = m.1; m.0.into_iter().find(|((name, _), _)| *name == s.0) - .map(|(_, expr)| expr.set_position(pos)) + .map(|(_, mut expr)| { expr.set_position(pos); expr }) .unwrap_or_else(|| Expr::Unit(pos)) } // string[int] @@ -485,7 +492,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); let ch = a.0.to_string(); - if b.0.iter().find(|((name, _), _)| name.as_str() == ch.as_str()).is_some() { + if b.0.iter().find(|((name, _), _)| name == &ch).is_some() { Expr::True(a.1) } else { Expr::False(a.1) @@ -543,14 +550,19 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // Fn("...") Expr::FnCall(x) - if x.1.is_none() - && (x.0).0 == KEYWORD_FN_PTR - && x.3.len() == 1 - && matches!(x.3[0], Expr::StringConstant(_)) + if x.1.is_none() + && (x.0).0 == KEYWORD_FN_PTR + && x.3.len() == 1 + && matches!(x.3[0], Expr::StringConstant(_)) => { - match &x.3[0] { - Expr::StringConstant(s) if is_valid_identifier(s.0.chars()) => Expr::FnPointer(s.clone()), - _ => Expr::FnCall(x) + if let Expr::StringConstant(s) = &x.3[0] { + if let Ok(fn_ptr) = FnPtr::try_from(s.0.as_str()) { + Expr::FnPointer(Box::new((fn_ptr.take_data().0, s.1))) + } else { + Expr::FnCall(x) + } + } else { + unreachable!() } } @@ -560,21 +572,17 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { && state.optimization_level == OptimizationLevel::Full // full optimizations && x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants => { - let ((name, _, pos), _, _, args, def_value) = x.as_mut(); + let ((name, _, _, pos), _, _, args, def_value) = x.as_mut(); // First search in functions lib (can override built-in) // Cater for both normal function call style and method call style (one additional arguments) - #[cfg(not(feature = "no_function"))] - let _has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| { + let has_script_fn = cfg!(not(feature = "no_function")) && state.lib.iter_fn().find(|(_, _, _, f)| { if !f.is_script() { return false; } let fn_def = f.get_fn_def(); - fn_def.name.as_str() == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) + fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) }).is_some(); - #[cfg(feature = "no_function")] - let _has_script_fn: bool = false; - - if _has_script_fn { + if has_script_fn { // A script-defined function overrides the built-in function - do not make the call x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); return Expr::FnCall(x); @@ -624,7 +632,9 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); // Replace constant with value - state.find_constant(&name).unwrap().clone().set_position(pos) + let mut expr = state.find_constant(&name).unwrap().clone(); + expr.set_position(pos); + expr } // Custom syntax @@ -686,7 +696,7 @@ fn optimize( match &stmt { Stmt::Const(v) => { // Load constants - let ((name, _), expr) = v.as_ref(); + let ((name, _), expr, _) = v.as_ref(); state.push_constant(&name, expr.clone()); stmt // Keep it in the global scope } @@ -734,11 +744,13 @@ pub fn optimize_into_ast( _functions: Vec, level: OptimizationLevel, ) -> AST { - #[cfg(feature = "no_optimize")] - const level: OptimizationLevel = OptimizationLevel::None; + let level = if cfg!(feature = "no_optimize") { + OptimizationLevel::None + } else { + level + }; - #[cfg(not(feature = "no_function"))] - let lib = { + let lib = if cfg!(not(feature = "no_function")) { let mut module = Module::new(); if !level.is_none() { @@ -753,6 +765,8 @@ pub fn optimize_into_ast( access: fn_def.access, body: Default::default(), params: fn_def.params.clone(), + #[cfg(not(feature = "no_closure"))] + externals: fn_def.externals.clone(), pos: fn_def.pos, } .into() @@ -798,11 +812,10 @@ pub fn optimize_into_ast( } module + } else { + Default::default() }; - #[cfg(feature = "no_function")] - let lib = Default::default(); - AST::new( match level { OptimizationLevel::None => statements, diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 61350996..d8e256cc 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -2,36 +2,29 @@ use crate::def_package; use crate::module::FuncReturn; use crate::parser::INT; -#[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; -#[cfg(not(feature = "no_float"))] -#[cfg(feature = "no_std")] -use num_traits::*; - -#[cfg(not(feature = "unchecked"))] use num_traits::{ identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, }; -use crate::stdlib::ops::{BitAnd, BitOr, BitXor}; +#[cfg(feature = "no_std")] +#[cfg(not(feature = "no_float"))] +use num_traits::float::Float; -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] -use crate::stdlib::ops::{Add, Div, Mul, Neg, Rem, Sub}; - -#[cfg(feature = "unchecked")] -use crate::stdlib::ops::{Shl, Shr}; - -#[cfg(not(feature = "unchecked"))] -use crate::stdlib::{boxed::Box, fmt::Display, format}; +use crate::stdlib::{ + boxed::Box, + fmt::Display, + format, + ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}, +}; // Checked add -#[cfg(not(feature = "unchecked"))] -pub(crate) fn add(x: T, y: T) -> FuncReturn { +pub fn add(x: T, y: T) -> FuncReturn { x.checked_add(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Addition overflow: {} + {}", x, y), @@ -40,8 +33,7 @@ pub(crate) fn add(x: T, y: T) -> FuncReturn { }) } // Checked subtract -#[cfg(not(feature = "unchecked"))] -pub(crate) fn sub(x: T, y: T) -> FuncReturn { +pub fn sub(x: T, y: T) -> FuncReturn { x.checked_sub(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Subtraction underflow: {} - {}", x, y), @@ -50,8 +42,7 @@ pub(crate) fn sub(x: T, y: T) -> FuncReturn { }) } // Checked multiply -#[cfg(not(feature = "unchecked"))] -pub(crate) fn mul(x: T, y: T) -> FuncReturn { +pub fn mul(x: T, y: T) -> FuncReturn { x.checked_mul(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Multiplication overflow: {} * {}", x, y), @@ -60,8 +51,7 @@ pub(crate) fn mul(x: T, y: T) -> FuncReturn { }) } // Checked divide -#[cfg(not(feature = "unchecked"))] -pub(crate) fn div(x: T, y: T) -> FuncReturn +pub fn div(x: T, y: T) -> FuncReturn where T: Display + CheckedDiv + PartialEq + Zero, { @@ -81,8 +71,7 @@ where }) } // Checked negative - e.g. -(i32::MIN) will overflow i32::MAX -#[cfg(not(feature = "unchecked"))] -pub(crate) fn neg(x: T) -> FuncReturn { +pub fn neg(x: T) -> FuncReturn { x.checked_neg().ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Negation overflow: -{}", x), @@ -91,8 +80,7 @@ pub(crate) fn neg(x: T) -> FuncReturn { }) } // Checked absolute -#[cfg(not(feature = "unchecked"))] -pub(crate) fn abs(x: T) -> FuncReturn { +pub fn abs(x: T) -> FuncReturn { // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics // when the number is ::MIN instead of returning ::MIN itself. if x >= ::zero() { @@ -107,32 +95,26 @@ pub(crate) fn abs(x: T) -> FuncRetu } } // Unchecked add - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn add_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x + y) } // Unchecked subtract - may panic on underflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn sub_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x - y) } // Unchecked multiply - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn mul_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x * y) } // Unchecked divide - may panic when dividing by zero -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn div_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x / y) } // Unchecked negative - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn neg_u(x: T) -> FuncReturn<::Output> { Ok(-x) } // Unchecked absolute - may panic on overflow -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn abs_u(x: T) -> FuncReturn<::Output> where T: Neg + PartialOrd + Default + Into<::Output>, @@ -155,8 +137,7 @@ fn binary_xor(x: T, y: T) -> FuncReturn<::Output> { Ok(x ^ y) } // Checked left-shift -#[cfg(not(feature = "unchecked"))] -pub(crate) fn shl(x: T, y: INT) -> FuncReturn { +pub fn shl(x: T, y: INT) -> FuncReturn { // Cannot shift by a negative number of bits if y < 0 { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -173,8 +154,7 @@ pub(crate) fn shl(x: T, y: INT) -> FuncReturn { }) } // Checked right-shift -#[cfg(not(feature = "unchecked"))] -pub(crate) fn shr(x: T, y: INT) -> FuncReturn { +pub fn shr(x: T, y: INT) -> FuncReturn { // Cannot shift by a negative number of bits if y < 0 { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -191,18 +171,15 @@ pub(crate) fn shr(x: T, y: INT) -> FuncReturn { }) } // Unchecked left-shift - may panic if shifting by a negative number of bits -#[cfg(feature = "unchecked")] -pub(crate) fn shl_u>(x: T, y: T) -> FuncReturn<>::Output> { +pub fn shl_u>(x: T, y: T) -> FuncReturn<>::Output> { Ok(x.shl(y)) } // Unchecked right-shift - may panic if shifting by a negative number of bits -#[cfg(feature = "unchecked")] -pub(crate) fn shr_u>(x: T, y: T) -> FuncReturn<>::Output> { +pub fn shr_u>(x: T, y: T) -> FuncReturn<>::Output> { Ok(x.shr(y)) } // Checked modulo -#[cfg(not(feature = "unchecked"))] -pub(crate) fn modulo(x: T, y: T) -> FuncReturn { +pub fn modulo(x: T, y: T) -> FuncReturn { x.checked_rem(&y).ok_or_else(|| { Box::new(EvalAltResult::ErrorArithmetic( format!("Modulo division by zero or overflow: {} % {}", x, y), @@ -211,62 +188,58 @@ pub(crate) fn modulo(x: T, y: T) -> FuncReturn { }) } // Unchecked modulo - may panic if dividing by zero -#[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn modulo_u(x: T, y: T) -> FuncReturn<::Output> { Ok(x % y) } // Checked power -#[cfg(not(feature = "unchecked"))] -pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn { - #[cfg(not(feature = "only_i32"))] - if y > (u32::MAX as INT) { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to too large an index: {} ~ {}", x, y), - Position::none(), - ))) - } else if y < 0 { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ))) - } else { - x.checked_pow(y as u32).ok_or_else(|| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), +pub fn pow_i_i(x: INT, y: INT) -> FuncReturn { + if cfg!(not(feature = "only_i32")) { + if y > (u32::MAX as INT) { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to too large an index: {} ~ {}", x, y), Position::none(), - )) - }) - } - - #[cfg(feature = "only_i32")] - if y < 0 { - Err(Box::new(EvalAltResult::ErrorArithmetic( - format!("Integer raised to a negative index: {} ~ {}", x, y), - Position::none(), - ))) - } else { - x.checked_pow(y as u32).ok_or_else(|| { - Box::new(EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), + ))) + } else if y < 0 { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), Position::none(), - )) - }) + ))) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + Box::new(EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + )) + }) + } + } else { + if y < 0 { + Err(Box::new(EvalAltResult::ErrorArithmetic( + format!("Integer raised to a negative index: {} ~ {}", x, y), + Position::none(), + ))) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + Box::new(EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + )) + }) + } } } // Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX) -#[cfg(feature = "unchecked")] -pub(crate) fn pow_i_i_u(x: INT, y: INT) -> FuncReturn { +pub fn pow_i_i_u(x: INT, y: INT) -> FuncReturn { Ok(x.pow(y as u32)) } // Floating-point power - always well-defined #[cfg(not(feature = "no_float"))] -pub(crate) fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn { +pub fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn { Ok(x.powf(y)) } // Checked power #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] -pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { +pub fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { // Raise to power that is larger than an i32 if y > (i32::MAX as INT) { return Err(Box::new(EvalAltResult::ErrorArithmetic( @@ -278,9 +251,8 @@ pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn { Ok(x.powi(y as i32)) } // Unchecked power - may be incorrect if the power index is too high (> i32::MAX) -#[cfg(feature = "unchecked")] #[cfg(not(feature = "no_float"))] -pub(crate) fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn { +pub fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn { Ok(x.powi(y as i32)) } @@ -309,11 +281,8 @@ macro_rules! reg_sign { } def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { + if cfg!(not(feature = "unchecked")) { // Checked basic arithmetic reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64); @@ -324,8 +293,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", add, i128, u128); reg_op!(lib, "-", sub, i128, u128); reg_op!(lib, "*", mul, i128, u128); @@ -337,8 +305,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } } - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { // Unchecked basic arithmetic reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64); @@ -349,8 +316,7 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", add_u, i128, u128); reg_op!(lib, "-", sub_u, i128, u128); reg_op!(lib, "*", mul_u, i128, u128); @@ -364,13 +330,13 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_sign!(lib, "sign", INT, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - reg_sign!(lib, "sign", INT, i128); + if cfg!(not(target_arch = "wasm32")) { + reg_sign!(lib, "sign", INT, i128); + } } // Basic arithmetic for floating-point - no need to check - #[cfg(not(feature = "no_float"))] - { + if cfg!(not(feature = "no_float")) { reg_op!(lib, "+", add_u, f32); reg_op!(lib, "-", sub_u, f32); reg_op!(lib, "*", mul_u, f32); @@ -379,15 +345,12 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_sign!(lib, "sign", f64, f64); } - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "|", binary_or, i128, u128); reg_op!(lib, "&", binary_and, i128, u128); reg_op!(lib, "^", binary_xor, i128, u128); @@ -397,12 +360,11 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { #[cfg(not(feature = "no_float"))] { // Checked power - #[cfg(not(feature = "unchecked"))] - lib.set_fn_2("~", pow_f_i); - - // Unchecked power - #[cfg(feature = "unchecked")] - lib.set_fn_2("~", pow_f_i_u); + if cfg!(not(feature = "unchecked")) { + lib.set_fn_2("~", pow_f_i); + } else { + lib.set_fn_2("~", pow_f_i_u); + } // Floating-point modulo and power reg_op!(lib, "%", modulo_u, f32); @@ -413,19 +375,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } // Checked unary - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "unchecked")) { reg_unary!(lib, "-", neg, INT); reg_unary!(lib, "abs", abs, INT); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_unary!(lib, "-", neg, i8, i16, i32, i64); reg_unary!(lib, "abs", abs, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_unary!(lib, "-", neg, i128); reg_unary!(lib, "abs", abs, i128); } @@ -433,19 +391,15 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { } // Unchecked unary - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { reg_unary!(lib, "-", neg_u, INT); reg_unary!(lib, "abs", abs_u, INT); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_unary!(lib, "-", neg_u, i8, i16, i32, i64); reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_unary!(lib, "-", neg_u, i128); reg_unary!(lib, "abs", abs_u, i128); } diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index c0bea7ee..5574a4f5 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -3,6 +3,7 @@ use crate::any::{Dynamic, Variant}; use crate::def_package; use crate::engine::{Array, Engine}; +use crate::fn_native::FnPtr; use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; @@ -34,7 +35,7 @@ fn pad( _: &Module, args: &mut [&mut Dynamic], ) -> FuncReturn<()> { - let len = *args[1].downcast_ref::().unwrap(); + let len = *args[1].read_lock::().unwrap(); // Check if array will be over max size limit #[cfg(not(feature = "unchecked"))] @@ -52,7 +53,7 @@ fn pad( if len > 0 { let item = args[2].clone(); - let list = args[0].downcast_mut::().unwrap(); + let mut list = args[0].write_lock::().unwrap(); if len as usize > list.len() { list.resize(len as usize, item); @@ -82,11 +83,10 @@ macro_rules! reg_pad { }; } -#[cfg(not(feature = "no_index"))] def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { - reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ()); - reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ()); - reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, Array, ()); + reg_op!(lib, "push", push, INT, bool, char, ImmutableString, FnPtr, Array, ()); + reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, FnPtr, Array, ()); + reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, FnPtr, Array, ()); lib.set_fn_2_mut("append", |x: &mut Array, y: Array| { x.extend(y); @@ -104,15 +104,12 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { }, ); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "push", push, i8, u8, i16, u16, i32, i64, u32, u64); reg_pad!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64); reg_tri!(lib, "insert", ins, i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "push", push, i128, u128); reg_pad!(lib, "pad", pad, i128, u128); reg_tri!(lib, "insert", ins, i128, u128); diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index cccf4b6c..8914c6a5 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -73,9 +73,7 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_range::(lib); lib.set_fn_2("range", get_range::); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { macro_rules! reg_range { ($lib:expr, $x:expr, $( $y:ty ),*) => ( $( @@ -87,16 +85,15 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_range!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - reg_range!(lib, "range", i128, u128); + if cfg!(not(target_arch = "wasm32")) { + reg_range!(lib, "range", i128, u128); + } } reg_step::(lib); lib.set_fn_3("range", get_step_range::); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { macro_rules! reg_step { ($lib:expr, $x:expr, $( $y:ty ),*) => ( $( @@ -108,7 +105,8 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { reg_step!(lib, "range", i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - reg_step!(lib, "range", i128, u128); + if cfg!(not(target_arch = "wasm32")) { + reg_step!(lib, "range", i128, u128); + } } }); diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 1cb4b437..6c70a38c 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -33,9 +33,7 @@ macro_rules! reg_op { } def_package!(crate:LogicPackage:"Logical operators.", lib, { - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64); @@ -43,8 +41,7 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, { reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, u32, u64); reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "<", lt, i128, u128); reg_op!(lib, "<=", lte, i128, u128); reg_op!(lib, ">", gt, i128, u128); diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index cc1aa000..4e2e58ad 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -1,29 +1,24 @@ #![cfg(not(feature = "no_object"))] +use crate::any::Dynamic; use crate::def_package; use crate::engine::Map; +use crate::module::FuncReturn; use crate::parser::{ImmutableString, INT}; -#[cfg(not(feature = "no_index"))] -use crate::{any::Dynamic, module::FuncReturn}; - -#[cfg(not(feature = "no_index"))] use crate::stdlib::vec::Vec; -#[cfg(not(feature = "no_index"))] fn map_get_keys(map: &mut Map) -> FuncReturn> { Ok(map.iter().map(|(k, _)| k.clone().into()).collect()) } -#[cfg(not(feature = "no_index"))] fn map_get_values(map: &mut Map) -> FuncReturn> { Ok(map.iter().map(|(_, v)| v.clone()).collect()) } -#[cfg(not(feature = "no_object"))] def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { lib.set_fn_2_mut( "has", - |map: &mut Map, prop: ImmutableString| Ok(map.contains_key(prop.as_str())), + |map: &mut Map, prop: ImmutableString| Ok(map.contains_key(&prop)), ); lib.set_fn_1_mut("len", |map: &mut Map| Ok(map.len() as INT)); lib.set_fn_1_mut("clear", |map: &mut Map| { @@ -32,7 +27,7 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { }); lib.set_fn_2_mut( "remove", - |x: &mut Map, name: ImmutableString| Ok(x.remove(name.as_str()).unwrap_or_else(|| ().into())), + |x: &mut Map, name: ImmutableString| Ok(x.remove(&name).unwrap_or_else(|| ().into())), ); lib.set_fn_2_mut( "mixin", @@ -47,7 +42,7 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { "fill_with", |map1: &mut Map, map2: Map| { map2.into_iter().for_each(|(key, value)| { - if !map1.contains_key(key.as_str()) { + if !map1.contains_key(&key) { map1.insert(key, value); } }); @@ -74,9 +69,11 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { ); // Register map access functions - #[cfg(not(feature = "no_index"))] - lib.set_fn_1_mut("keys", map_get_keys); + if cfg!(not(feature = "no_index")) { + lib.set_fn_1_mut("keys", map_get_keys); + } - #[cfg(not(feature = "no_index"))] - lib.set_fn_1_mut("values", map_get_values); + if cfg!(not(feature = "no_index")) { + lib.set_fn_1_mut("values", map_get_values); + } }); diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 55bb68a3..f8fd2d33 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -5,22 +5,20 @@ use crate::parser::INT; use crate::parser::FLOAT; #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] use crate::{result::EvalAltResult, token::Position}; -#[cfg(not(feature = "no_float"))] #[cfg(feature = "no_std")] -use num_traits::*; +#[cfg(not(feature = "no_float"))] +use num_traits::float::Float; #[cfg(not(feature = "no_float"))] -#[cfg(not(feature = "unchecked"))] use crate::stdlib::{boxed::Box, format}; +#[allow(dead_code)] #[cfg(feature = "only_i32")] -#[cfg(not(feature = "unchecked"))] pub const MAX_INT: INT = i32::MAX; +#[allow(dead_code)] #[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "unchecked"))] pub const MAX_INT: INT = i64::MAX; def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { @@ -69,9 +67,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_float", |x: INT| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: f32| Ok(x as FLOAT)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { lib.set_fn_1("to_float", |x: i8| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u8| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: i16| Ok(x as FLOAT)); @@ -81,8 +77,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_float", |x: i64| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u64| Ok(x as FLOAT)); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { lib.set_fn_1("to_float", |x: i128| Ok(x as FLOAT)); lib.set_fn_1("to_float", |x: u128| Ok(x as FLOAT)); } @@ -91,28 +86,25 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { lib.set_fn_1("to_int", |ch: char| Ok(ch as INT)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { lib.set_fn_1("to_int", |x: i8| Ok(x as INT)); lib.set_fn_1("to_int", |x: u8| Ok(x as INT)); lib.set_fn_1("to_int", |x: i16| Ok(x as INT)); lib.set_fn_1("to_int", |x: u16| Ok(x as INT)); } - #[cfg(not(feature = "only_i32"))] - { + if cfg!(not(feature = "only_i32")) { lib.set_fn_1("to_int", |x: i32| Ok(x as INT)); lib.set_fn_1("to_int", |x: u64| Ok(x as INT)); - #[cfg(feature = "only_i64")] - lib.set_fn_1("to_int", |x: u32| Ok(x as INT)); + if cfg!(feature = "only_i64") { + lib.set_fn_1("to_int", |x: u32| Ok(x as INT)); + } } #[cfg(not(feature = "no_float"))] { - #[cfg(not(feature = "unchecked"))] - { + if cfg!(not(feature = "unchecked")) { lib.set_fn_1( "to_int", |x: f32| { @@ -141,8 +133,7 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, { ); } - #[cfg(feature = "unchecked")] - { + if cfg!(feature = "unchecked") { lib.set_fn_1("to_int", |x: f32| Ok(x as INT)); lib.set_fn_1("to_int", |x: f64| Ok(x as INT)); } diff --git a/src/packages/mod.rs b/src/packages/mod.rs index ccd78add..1c6dc3a9 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -61,14 +61,15 @@ impl PackagesCollection { self.0.insert(0, package); } /// Does the specified function hash key exist in the `PackagesCollection`? - pub fn contains_fn(&self, hash: u64) -> bool { - self.0.iter().any(|p| p.contains_fn(hash)) + #[allow(dead_code)] + pub fn contains_fn(&self, hash: u64, public_only: bool) -> bool { + self.0.iter().any(|p| p.contains_fn(hash, public_only)) } /// Get specified function via its hash key. - pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> { + pub fn get_fn(&self, hash: u64, public_only: bool) -> Option<&CallableFunction> { self.0 .iter() - .map(|p| p.get_fn(hash)) + .map(|p| p.get_fn(hash, public_only)) .find(|f| f.is_some()) .flatten() } diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 2e405004..08778150 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -21,7 +21,7 @@ fn to_debug(x: &mut T) -> FuncReturn { Ok(format!("{:?}", x).into()) } fn to_string(x: &mut T) -> FuncReturn { - Ok(format!("{}", x).into()) + Ok(x.to_string().into()) } #[cfg(not(feature = "no_object"))] fn format_map(x: &mut Map) -> FuncReturn { @@ -48,9 +48,7 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, ImmutableString); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, KEYWORD_PRINT, to_string, i8, u8, i16, u16, i32, u32); reg_op!(lib, FN_TO_STRING, to_string, i8, u8, i16, u16, i32, u32); reg_op!(lib, KEYWORD_DEBUG, to_debug, i8, u8, i16, u16, i32, u32); @@ -58,8 +56,7 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin reg_op!(lib, FN_TO_STRING, to_string, i64, u64); reg_op!(lib, KEYWORD_DEBUG, to_debug, i64, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, KEYWORD_PRINT, to_string, i128, u128); reg_op!(lib, FN_TO_STRING, to_string, i128, u128); reg_op!(lib, KEYWORD_DEBUG, to_debug, i128, u128); diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 62a5fdb2..149823c4 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,6 +1,7 @@ use crate::any::Dynamic; use crate::def_package; use crate::engine::Engine; +use crate::fn_native::FnPtr; use crate::module::{FuncReturn, Module}; use crate::parser::{ImmutableString, INT}; use crate::utils::StaticVec; @@ -15,7 +16,7 @@ use crate::stdlib::{ any::TypeId, boxed::Box, fmt::Display, - format, + format, mem, string::{String, ToString}, vec::Vec, }; @@ -88,20 +89,17 @@ macro_rules! reg_op { } def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { - reg_op!(lib, "+", append, INT, bool, char); + reg_op!(lib, "+", append, INT, bool, char, FnPtr); lib.set_fn_2( "+", |x: ImmutableString, _: ()| Ok(x)); - reg_op!(lib, "+", prepend, INT, bool, char); + reg_op!(lib, "+", prepend, INT, bool, char, FnPtr); lib.set_fn_2("+", |_: (), y: ImmutableString| Ok(y)); - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { + if cfg!(not(feature = "only_i32")) && cfg!(not(feature = "only_i64")) { reg_op!(lib, "+", append, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(lib, "+", prepend, i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(not(target_arch = "wasm32")) { reg_op!(lib, "+", append, i128, u128); reg_op!(lib, "+", prepend, i128, u128); } @@ -228,7 +226,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str "pad", &[TypeId::of::(), TypeId::of::(), TypeId::of::()], |_engine: &Engine, _: &Module, args: &mut [&mut Dynamic]| { - let len = *args[1].downcast_ref::< INT>().unwrap(); + let len = *args[1].read_lock::< INT>().unwrap(); // Check if string will be over max size limit #[cfg(not(feature = "unchecked"))] @@ -242,8 +240,8 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str } if len > 0 { - let ch = *args[2].downcast_ref::< char>().unwrap(); - let s = args[0].downcast_mut::().unwrap(); + let ch = mem::take(args[2]).cast::(); + let mut s = args[0].write_lock::().unwrap(); let orig_len = s.chars().count(); diff --git a/src/parser.rs b/src/parser.rs index baf1be85..272215ac 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,14 +2,16 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; -use crate::engine::{Engine, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; +use crate::engine::{ + Engine, KEYWORD_FN_PTR_CURRY, KEYWORD_THIS, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT, +}; use crate::error::{LexError, ParseError, ParseErrorType}; -use crate::fn_native::Shared; +use crate::fn_native::{FnPtr, Shared}; use crate::module::{Module, ModuleRef}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::syntax::FnCustomSyntaxEval; -use crate::token::{is_valid_identifier, Position, Token, TokenStream}; +use crate::token::{is_keyword_function, is_valid_identifier, Position, Token, TokenStream}; use crate::utils::{StaticVec, StraightHasherBuilder}; #[cfg(not(feature = "no_function"))] @@ -26,7 +28,6 @@ use crate::stdlib::{ fmt, format, hash::{Hash, Hasher}, iter::empty, - mem, num::NonZeroUsize, ops::Add, string::{String, ToString}, @@ -38,6 +39,9 @@ use crate::stdlib::{ #[cfg(not(feature = "no_function"))] use crate::stdlib::collections::hash_map::DefaultHasher; +#[cfg(not(feature = "no_closure"))] +use crate::stdlib::collections::HashSet; + #[cfg(feature = "no_std")] #[cfg(not(feature = "no_function"))] use ahash::AHasher; @@ -353,7 +357,7 @@ impl fmt::Display for FnAccess { /// ## WARNING /// /// This type is volatile and may change. -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Clone)] pub struct ScriptFnDef { /// Function name. pub name: ImmutableString, @@ -361,6 +365,9 @@ pub struct ScriptFnDef { pub access: FnAccess, /// Names of function parameters. pub params: StaticVec, + /// Access to external variables. + #[cfg(not(feature = "no_closure"))] + pub externals: HashSet, /// Function body. pub body: Stmt, /// Position of the function definition. @@ -406,6 +413,14 @@ struct ParseState<'e> { engine: &'e Engine, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. stack: Vec<(String, ScopeEntryType)>, + /// Tracks a list of external variables (variables that are not explicitly declared in the scope). + #[cfg(not(feature = "no_closure"))] + externals: HashMap, + /// An indicator that disables variable capturing into externals one single time. + /// If set to false the next call to `access_var` will not capture the variable. + /// All consequent calls to `access_var` will not be affected + #[cfg(not(feature = "no_closure"))] + allow_capture: bool, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. modules: Vec, /// Maximum levels of expression nesting. @@ -425,30 +440,51 @@ impl<'e> ParseState<'e> { ) -> Self { Self { engine, - stack: Default::default(), - modules: Default::default(), #[cfg(not(feature = "unchecked"))] max_expr_depth, #[cfg(not(feature = "unchecked"))] max_function_expr_depth, + #[cfg(not(feature = "no_closure"))] + externals: Default::default(), + #[cfg(not(feature = "no_closure"))] + allow_capture: true, + stack: Default::default(), + modules: Default::default(), } } - /// Find a variable by name in the `ParseState`, searching in reverse. + + /// Find explicitly declared variable by name in the `ParseState`, searching in reverse order. + /// + /// If the variable is not present in the scope adds it to the list of external variables + /// /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. - /// Return zero when the variable name is not found in the `ParseState`. - pub fn find_var(&self, name: &str) -> Option { - self.stack + /// Return `None` when the variable name is not found in the `stack`. + fn access_var(&mut self, name: &str, _pos: Position) -> Option { + let index = self + .stack .iter() .rev() .enumerate() .find(|(_, (n, _))| *n == name) - .and_then(|(i, _)| NonZeroUsize::new(i + 1)) + .and_then(|(i, _)| NonZeroUsize::new(i + 1)); + + #[cfg(not(feature = "no_closure"))] + if self.allow_capture { + if index.is_none() && !self.externals.contains_key(name) { + self.externals.insert(name.to_string(), _pos); + } + } else { + self.allow_capture = true + } + + index } + /// Find a module by name in the `ParseState`, searching in reverse. /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. - /// Return zero when the variable name is not found in the `ParseState`. + /// Return `None` when the variable name is not found in the `ParseState`. pub fn find_module(&self, name: &str) -> Option { self.modules .iter() @@ -511,33 +547,41 @@ pub enum Stmt { /// No-op. Noop(Position), /// if expr { stmt } else { stmt } - IfThenElse(Box<(Expr, Stmt, Option)>), + IfThenElse(Box<(Expr, Stmt, Option, Position)>), /// while expr { stmt } - While(Box<(Expr, Stmt)>), + While(Box<(Expr, Stmt, Position)>), /// loop { stmt } - Loop(Box), + Loop(Box<(Stmt, Position)>), /// for id in expr { stmt } - For(Box<(String, Expr, Stmt)>), + For(Box<(String, Expr, Stmt, Position)>), /// let id = expr - Let(Box<((String, Position), Option)>), + Let(Box<((String, Position), Option, Position)>), /// const id = expr - Const(Box<((String, Position), Expr)>), + Const(Box<((String, Position), Expr, Position)>), /// { stmt; ... } Block(Box<(StaticVec, Position)>), - /// { stmt } + /// expr Expr(Box), /// continue Continue(Position), /// break Break(Position), /// return/throw - ReturnWithVal(Box<((ReturnType, Position), Option)>), + ReturnWithVal(Box<((ReturnType, Position), Option, Position)>), /// import expr as module #[cfg(not(feature = "no_module"))] - Import(Box<(Expr, (String, Position))>), + Import(Box<(Expr, (String, Position), Position)>), /// expr id as name, ... #[cfg(not(feature = "no_module"))] - Export(Box)>>), + Export( + Box<( + StaticVec<((String, Position), Option<(String, Position)>)>, + Position, + )>, + ), + /// Convert a variable to shared. + #[cfg(not(feature = "no_closure"))] + Share(Box<(String, Position)>), } impl Default for Stmt { @@ -555,19 +599,50 @@ impl Stmt { Stmt::Const(x) => (x.0).1, Stmt::ReturnWithVal(x) => (x.0).1, Stmt::Block(x) => x.1, - Stmt::IfThenElse(x) => x.0.position(), + Stmt::IfThenElse(x) => x.3, Stmt::Expr(x) => x.position(), - Stmt::While(x) => x.1.position(), - Stmt::Loop(x) => x.position(), - Stmt::For(x) => x.2.position(), + Stmt::While(x) => x.2, + Stmt::Loop(x) => x.1, + Stmt::For(x) => x.3, #[cfg(not(feature = "no_module"))] - Stmt::Import(x) => (x.1).1, + Stmt::Import(x) => x.2, #[cfg(not(feature = "no_module"))] - Stmt::Export(x) => (x.get(0).0).1, + Stmt::Export(x) => x.1, + + #[cfg(not(feature = "no_closure"))] + Stmt::Share(x) => x.1, } } + /// Override the `Position` of this statement. + pub fn set_position(&mut self, new_pos: Position) -> &mut Self { + match self { + Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos = new_pos, + Stmt::Let(x) => (x.0).1 = new_pos, + Stmt::Const(x) => (x.0).1 = new_pos, + Stmt::ReturnWithVal(x) => (x.0).1 = new_pos, + Stmt::Block(x) => x.1 = new_pos, + Stmt::IfThenElse(x) => x.3 = new_pos, + Stmt::Expr(x) => { + x.set_position(new_pos); + } + Stmt::While(x) => x.2 = new_pos, + Stmt::Loop(x) => x.1 = new_pos, + Stmt::For(x) => x.3 = new_pos, + + #[cfg(not(feature = "no_module"))] + Stmt::Import(x) => x.2 = new_pos, + #[cfg(not(feature = "no_module"))] + Stmt::Export(x) => x.1 = new_pos, + + #[cfg(not(feature = "no_closure"))] + Stmt::Share(x) => x.1 = new_pos, + } + + self + } + /// Is this statement self-terminated (i.e. no need for a semicolon terminator)? pub fn is_self_terminated(&self) -> bool { match self { @@ -589,6 +664,9 @@ impl Stmt { #[cfg(not(feature = "no_module"))] Stmt::Import(_) | Stmt::Export(_) => false, + + #[cfg(not(feature = "no_closure"))] + Stmt::Share(_) => false, } } @@ -602,7 +680,7 @@ impl Stmt { } Stmt::IfThenElse(x) => x.1.is_pure(), Stmt::While(x) => x.0.is_pure() && x.1.is_pure(), - Stmt::Loop(x) => x.is_pure(), + Stmt::Loop(x) => x.0.is_pure(), Stmt::For(x) => x.1.is_pure() && x.2.is_pure(), Stmt::Let(_) | Stmt::Const(_) => false, Stmt::Block(x) => x.0.iter().all(Stmt::is_pure), @@ -612,6 +690,9 @@ impl Stmt { Stmt::Import(_) => false, #[cfg(not(feature = "no_module"))] Stmt::Export(_) => false, + + #[cfg(not(feature = "no_closure"))] + Stmt::Share(_) => false, } } } @@ -695,12 +776,12 @@ pub enum Expr { Stmt(Box<(Stmt, Position)>), /// Wrapped expression - should not be optimized away. Expr(Box), - /// func(expr, ... ) - ((function name, native_only, position), optional modules, hash, arguments, optional default value) + /// func(expr, ... ) - ((function name, native_only, capture, position), optional modules, hash, arguments, optional default value) /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls /// and the function names are predictable, so no need to allocate a new `String`. FnCall( Box<( - (Cow<'static, str>, bool, Position), + (Cow<'static, str>, bool, bool, Position), Option>, u64, StaticVec, @@ -754,6 +835,10 @@ impl Expr { Self::FloatConstant(x) => x.0.into(), Self::CharConstant(x) => x.0.into(), Self::StringConstant(x) => x.0.clone().into(), + Self::FnPointer(x) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked( + x.0.clone(), + Default::default(), + )))), Self::True(_) => true.into(), Self::False(_) => false.into(), Self::Unit(_) => ().into(), @@ -818,7 +903,7 @@ impl Expr { Self::Property(x) => x.1, Self::Stmt(x) => x.1, Self::Variable(x) => (x.0).1, - Self::FnCall(x) => (x.0).2, + Self::FnCall(x) => (x.0).3, Self::Assignment(x) => x.0.position(), Self::And(x) | Self::Or(x) | Self::In(x) => x.2, @@ -832,11 +917,10 @@ impl Expr { } /// Override the `Position` of the expression. - pub(crate) fn set_position(mut self, new_pos: Position) -> Self { - match &mut self { - Self::Expr(ref mut x) => { - let expr = mem::take(x); - *x = Box::new(expr.set_position(new_pos)); + pub fn set_position(&mut self, new_pos: Position) -> &mut Self { + match self { + Self::Expr(x) => { + x.set_position(new_pos); } #[cfg(not(feature = "no_float"))] @@ -851,7 +935,7 @@ impl Expr { Self::Variable(x) => (x.0).1 = new_pos, Self::Property(x) => x.1 = new_pos, Self::Stmt(x) => x.1 = new_pos, - Self::FnCall(x) => (x.0).2 = new_pos, + Self::FnCall(x) => (x.0).3 = new_pos, Self::And(x) => x.2 = new_pos, Self::Or(x) => x.2 = new_pos, Self::In(x) => x.2 = new_pos, @@ -957,6 +1041,7 @@ impl Expr { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, Token::LeftParen => true, + Token::Bang => true, Token::DoubleColon => true, _ => false, }, @@ -1044,11 +1129,12 @@ fn parse_paren_expr( } /// Parse a function call. -fn parse_call_expr( +fn parse_fn_call( input: &mut TokenStream, state: &mut ParseState, lib: &mut FunctionsLib, id: String, + capture: bool, mut modules: Option>, settings: ParseSettings, ) -> Result { @@ -1075,7 +1161,7 @@ fn parse_call_expr( eat_token(input, Token::RightParen); let hash_script = if let Some(modules) = modules.as_mut() { - modules.set_index(state.find_module(&modules.get(0).0)); + modules.set_index(state.find_module(&modules[0].0)); // Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, @@ -1091,7 +1177,7 @@ fn parse_call_expr( }; return Ok(Expr::FnCall(Box::new(( - (id.into(), false, settings.pos), + (id.into(), false, capture, settings.pos), modules, hash_script, args, @@ -1117,7 +1203,7 @@ fn parse_call_expr( eat_token(input, Token::RightParen); let hash_script = if let Some(modules) = modules.as_mut() { - modules.set_index(state.find_module(&modules.get(0).0)); + modules.set_index(state.find_module(&modules[0].0)); // Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, @@ -1133,7 +1219,7 @@ fn parse_call_expr( }; return Ok(Expr::FnCall(Box::new(( - (id.into(), false, settings.pos), + (id.into(), false, capture, settings.pos), modules, hash_script, args, @@ -1542,6 +1628,8 @@ fn parse_primary( _ => input.next().unwrap(), }; + let (next_token, _) = input.peek().unwrap(); + let mut root_expr = match token { Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))), #[cfg(not(feature = "no_float"))] @@ -1549,15 +1637,19 @@ fn parse_primary( Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))), Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))), Token::Identifier(s) => { - let index = state.find_var(&s); + let index = state.access_var(&s, settings.pos); Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) } // Function call is allowed to have reserved keyword - Token::Reserved(s) if s != KEYWORD_THIS && input.peek().unwrap().0 == Token::LeftParen => { - Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) + Token::Reserved(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => { + if is_keyword_function(&s) { + Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) + } else { + return Err(PERR::Reserved(s).into_err(settings.pos)); + } } // Access to `this` as a variable is OK - Token::Reserved(s) if s == KEYWORD_THIS && input.peek().unwrap().0 != Token::LeftParen => { + Token::Reserved(s) if s == KEYWORD_THIS && *next_token != Token::LeftParen => { if !settings.is_function_scope { return Err( PERR::BadInput(format!("'{}' can only be used in functions", s)) @@ -1597,11 +1689,26 @@ fn parse_primary( settings.pos = token_pos; root_expr = match (root_expr, token) { + // Function call + #[cfg(not(feature = "no_closure"))] + (Expr::Variable(x), Token::Bang) => { + if !match_token(input, Token::LeftParen)? { + return Err(PERR::MissingToken( + Token::LeftParen.syntax().into(), + "to start arguments list of function call".into(), + ) + .into_err(input.peek().unwrap().1)); + } + + let ((name, pos), modules, _, _) = *x; + settings.pos = pos; + parse_fn_call(input, state, lib, name, true, modules, settings.level_up())? + } // Function call (Expr::Variable(x), Token::LeftParen) => { let ((name, pos), modules, _, _) = *x; settings.pos = pos; - parse_call_expr(input, state, lib, name, modules, settings.level_up())? + parse_fn_call(input, state, lib, name, false, modules, settings.level_up())? } (Expr::Property(_), _) => unreachable!(), // module access @@ -1646,7 +1753,7 @@ fn parse_primary( // Qualifiers + variable name *hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, 0, empty()); - modules.set_index(state.find_module(&modules.get(0).0)); + modules.set_index(state.find_module(&modules[0].0)); } _ => (), } @@ -1711,7 +1818,7 @@ fn parse_unary( args.push(expr); Ok(Expr::FnCall(Box::new(( - (op.into(), true, pos), + (op.into(), true, false, pos), None, hash, args, @@ -1736,7 +1843,7 @@ fn parse_unary( let hash = calc_fn_hash(empty(), op, 2, empty()); Ok(Expr::FnCall(Box::new(( - (op.into(), true, pos), + (op.into(), true, false, pos), None, hash, args, @@ -1746,7 +1853,7 @@ fn parse_unary( // | ... #[cfg(not(feature = "no_function"))] Token::Pipe | Token::Or => { - let mut state = ParseState::new( + let mut new_state = ParseState::new( state.engine, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, @@ -1765,7 +1872,12 @@ fn parse_unary( pos: *token_pos, }; - let (expr, func) = parse_anon_fn(input, &mut state, lib, settings)?; + let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; + + #[cfg(not(feature = "no_closure"))] + new_state.externals.iter().for_each(|(closure, pos)| { + state.access_var(closure, *pos); + }); // Qualifiers (none) + function name + number of arguments. let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); @@ -1904,7 +2016,7 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { - return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get(0).1)); + return Err(PERR::PropertyExpected.into_err(x.1.unwrap()[0].1)); } // lhs.prop (lhs, prop @ Expr::Property(_)) => Expr::Dot(Box::new((lhs, prop, op_pos))), @@ -1926,7 +2038,14 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { + return Err(PERR::MalformedCapture( + "method-call style does not support capturing".into(), + ) + .into_err((x.0).3)) + } + // lhs.func(...) (lhs, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))), // lhs.rhs (_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())), @@ -2113,6 +2232,16 @@ fn parse_binary_op( let (op_token, pos) = input.next().unwrap(); + if cfg!(not(feature = "no_object")) && op_token == Token::Period { + if let (Token::Identifier(_), _) = input.peek().unwrap() { + // prevents capturing of the object properties as vars: xxx. + #[cfg(not(feature = "no_closure"))] + { + state.allow_capture = false; + } + } + } + let rhs = parse_unary(input, state, lib, settings)?; let next_precedence = input.peek().unwrap().0.precedence(custom); @@ -2135,7 +2264,7 @@ fn parse_binary_op( let cmp_def = Some(false); let op = op_token.syntax(); let hash = calc_fn_hash(empty(), &op, 2, empty()); - let op = (op, true, pos); + let op = (op, true, false, pos); let mut args = StaticVec::new(); args.push(root); @@ -2165,25 +2294,25 @@ fn parse_binary_op( | Token::GreaterThanEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, cmp_def))), Token::Or => { - let rhs = args.pop(); - let current_lhs = args.pop(); + let rhs = args.pop().unwrap(); + let current_lhs = args.pop().unwrap(); Expr::Or(Box::new((current_lhs, rhs, pos))) } Token::And => { - let rhs = args.pop(); - let current_lhs = args.pop(); + let rhs = args.pop().unwrap(); + let current_lhs = args.pop().unwrap(); Expr::And(Box::new((current_lhs, rhs, pos))) } Token::In => { - let rhs = args.pop(); - let current_lhs = args.pop(); + let rhs = args.pop().unwrap(); + let current_lhs = args.pop().unwrap(); make_in_expr(current_lhs, rhs, pos)? } #[cfg(not(feature = "no_object"))] Token::Period => { - let rhs = args.pop(); - let current_lhs = args.pop(); + let rhs = args.pop().unwrap(); + let current_lhs = args.pop().unwrap(); make_dot_expr(current_lhs, rhs, pos)? } @@ -2196,7 +2325,7 @@ fn parse_binary_op( .unwrap_or(false) => { // Accept non-native functions for custom operators - let op = (op.0, false, op.2); + let op = (op.0, false, op.2, op.3); Expr::FnCall(Box::new((op, None, hash, args, None))) } @@ -2310,7 +2439,7 @@ fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { match input.peek().unwrap() { (Token::Equals, pos) => { - return Err(PERR::BadInput("Possibly a typo of '=='?".to_string()).into_err(*pos)) + Err(PERR::BadInput("Possibly a typo of '=='?".to_string()).into_err(*pos)) } (Token::PlusAssign, pos) | (Token::MinusAssign, pos) @@ -2322,12 +2451,10 @@ fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { | (Token::PowerOfAssign, pos) | (Token::AndAssign, pos) | (Token::OrAssign, pos) - | (Token::XOrAssign, pos) => { - return Err(PERR::BadInput( - "Expecting a boolean expression, not an assignment".to_string(), - ) - .into_err(*pos)) - } + | (Token::XOrAssign, pos) => Err(PERR::BadInput( + "Expecting a boolean expression, not an assignment".to_string(), + ) + .into_err(*pos)), _ => Ok(()), } @@ -2341,7 +2468,8 @@ fn parse_if( mut settings: ParseSettings, ) -> Result { // if ... - settings.pos = eat_token(input, Token::If); + let token_pos = eat_token(input, Token::If); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2365,7 +2493,9 @@ fn parse_if( None }; - Ok(Stmt::IfThenElse(Box::new((guard, if_body, else_body)))) + Ok(Stmt::IfThenElse(Box::new(( + guard, if_body, else_body, token_pos, + )))) } /// Parse a while loop. @@ -2376,7 +2506,8 @@ fn parse_while( mut settings: ParseSettings, ) -> Result { // while ... - settings.pos = eat_token(input, Token::While); + let token_pos = eat_token(input, Token::While); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2389,7 +2520,7 @@ fn parse_while( settings.is_breakable = true; let body = parse_block(input, state, lib, settings.level_up())?; - Ok(Stmt::While(Box::new((guard, body)))) + Ok(Stmt::While(Box::new((guard, body, token_pos)))) } /// Parse a loop statement. @@ -2400,7 +2531,8 @@ fn parse_loop( mut settings: ParseSettings, ) -> Result { // loop ... - settings.pos = eat_token(input, Token::Loop); + let token_pos = eat_token(input, Token::Loop); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2409,7 +2541,7 @@ fn parse_loop( settings.is_breakable = true; let body = parse_block(input, state, lib, settings.level_up())?; - Ok(Stmt::Loop(Box::new(body))) + Ok(Stmt::Loop(Box::new((body, token_pos)))) } /// Parse a for loop. @@ -2420,7 +2552,8 @@ fn parse_for( mut settings: ParseSettings, ) -> Result { // for ... - settings.pos = eat_token(input, Token::For); + let token_pos = eat_token(input, Token::For); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2463,7 +2596,7 @@ fn parse_for( state.stack.truncate(prev_stack_len); - Ok(Stmt::For(Box::new((name, expr, body)))) + Ok(Stmt::For(Box::new((name, expr, body, token_pos)))) } /// Parse a variable definition statement. @@ -2475,7 +2608,8 @@ fn parse_let( mut settings: ParseSettings, ) -> Result { // let/const... (specified in `var_type`) - settings.pos = input.next().unwrap().1; + let token_pos = input.next().unwrap().1; + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2499,12 +2633,16 @@ fn parse_let( // let name = expr ScopeEntryType::Normal => { state.stack.push((name.clone(), ScopeEntryType::Normal)); - Ok(Stmt::Let(Box::new(((name, pos), Some(init_value))))) + Ok(Stmt::Let(Box::new(( + (name, pos), + Some(init_value), + token_pos, + )))) } // const name = { expr:constant } ScopeEntryType::Constant if init_value.is_constant() => { state.stack.push((name.clone(), ScopeEntryType::Constant)); - Ok(Stmt::Const(Box::new(((name, pos), init_value)))) + Ok(Stmt::Const(Box::new(((name, pos), init_value, token_pos)))) } // const name = expr: error ScopeEntryType::Constant => { @@ -2516,11 +2654,15 @@ fn parse_let( match var_type { ScopeEntryType::Normal => { state.stack.push((name.clone(), ScopeEntryType::Normal)); - Ok(Stmt::Let(Box::new(((name, pos), None)))) + Ok(Stmt::Let(Box::new(((name, pos), None, token_pos)))) } ScopeEntryType::Constant => { state.stack.push((name.clone(), ScopeEntryType::Constant)); - Ok(Stmt::Const(Box::new(((name, pos), Expr::Unit(pos))))) + Ok(Stmt::Const(Box::new(( + (name, pos), + Expr::Unit(pos), + token_pos, + )))) } } } @@ -2535,7 +2677,8 @@ fn parse_import( mut settings: ParseSettings, ) -> Result { // import ... - settings.pos = eat_token(input, Token::Import); + let token_pos = eat_token(input, Token::Import); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2565,7 +2708,12 @@ fn parse_import( }; state.modules.push(name.clone()); - Ok(Stmt::Import(Box::new((expr, (name, settings.pos))))) + + Ok(Stmt::Import(Box::new(( + expr, + (name, settings.pos), + token_pos, + )))) } /// Parse an export statement. @@ -2576,7 +2724,8 @@ fn parse_export( _lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - settings.pos = eat_token(input, Token::Export); + let token_pos = eat_token(input, Token::Export); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(_state.max_expr_depth)?; @@ -2636,7 +2785,7 @@ fn parse_export( }) .map_err(|(id2, pos)| PERR::DuplicatedExport(id2.to_string()).into_err(pos))?; - Ok(Stmt::Export(Box::new(exports))) + Ok(Stmt::Export(Box::new((exports, token_pos)))) } /// Parse a statement block. @@ -2670,10 +2819,9 @@ fn parse_block( // Parse statements inside the block settings.is_global = false; - let stmt = if let Some(s) = parse_stmt(input, state, lib, settings.level_up())? { - s - } else { - continue; + let stmt = match parse_stmt(input, state, lib, settings.level_up())? { + Some(s) => s, + None => continue, }; // See if it needs a terminating semicolon @@ -2771,7 +2919,7 @@ fn parse_stmt( match input.next().unwrap() { (Token::Fn, pos) => { - let mut state = ParseState::new( + let mut new_state = ParseState::new( state.engine, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, @@ -2790,7 +2938,7 @@ fn parse_stmt( pos: pos, }; - let func = parse_fn(input, &mut state, lib, access, settings)?; + let func = parse_fn(input, &mut new_state, lib, access, settings)?; // Qualifiers (none) + function name + number of arguments. let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); @@ -2824,22 +2972,32 @@ fn parse_stmt( Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(settings.pos)), Token::Return | Token::Throw => { - let return_type = match input.next().unwrap() { - (Token::Return, _) => ReturnType::Return, - (Token::Throw, _) => ReturnType::Exception, - _ => unreachable!(), - }; + let (return_type, token_pos) = input + .next() + .map(|(token, pos)| { + ( + match token { + Token::Return => ReturnType::Return, + Token::Throw => ReturnType::Exception, + _ => unreachable!(), + }, + pos, + ) + }) + .unwrap(); match input.peek().unwrap() { // `return`/`throw` at (Token::EOF, pos) => Ok(Some(Stmt::ReturnWithVal(Box::new(( (return_type, *pos), None, + token_pos, ))))), // `return;` or `throw;` (Token::SemiColon, _) => Ok(Some(Stmt::ReturnWithVal(Box::new(( (return_type, settings.pos), None, + token_pos, ))))), // `return` or `throw` with expression (_, _) => { @@ -2849,6 +3007,7 @@ fn parse_stmt( Ok(Some(Stmt::ReturnWithVal(Box::new(( (return_type, pos), Some(expr), + token_pos, ))))) } } @@ -2882,14 +3041,14 @@ fn parse_fn( #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let name = match input.next().unwrap() { - (Token::Identifier(s), _) | (Token::Custom(s), _) | (Token::Reserved(s), _) - if s != KEYWORD_THIS && is_valid_identifier(s.chars()) => - { - s - } - (_, pos) => return Err(PERR::FnMissingName.into_err(pos)), - }; + let (token, pos) = input.next().unwrap(); + + let name = token + .into_function_name_for_override() + .map_err(|t| match t { + Token::Reserved(s) => PERR::Reserved(s).into_err(pos), + _ => PERR::FnMissingName.into_err(pos), + })?; match input.peek().unwrap() { (Token::LeftParen, _) => eat_token(input, Token::LeftParen), @@ -2953,17 +3112,85 @@ fn parse_fn( (_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), }; - let params = params.into_iter().map(|(p, _)| p).collect(); + let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); + + #[cfg(not(feature = "no_closure"))] + let externals = state + .externals + .iter() + .map(|(name, _)| name) + .filter(|name| !params.contains(name)) + .cloned() + .collect(); Ok(ScriptFnDef { name: name.into(), access, params, + #[cfg(not(feature = "no_closure"))] + externals, body, pos: settings.pos, }) } +/// Creates a curried expression from a list of external variables +fn make_curry_from_externals( + fn_expr: Expr, + externals: StaticVec<(String, Position)>, + pos: Position, +) -> Expr { + if externals.is_empty() { + return fn_expr; + } + + let num_externals = externals.len(); + let mut args: StaticVec<_> = Default::default(); + + #[cfg(not(feature = "no_closure"))] + externals.iter().for_each(|(var_name, pos)| { + args.push(Expr::Variable(Box::new(( + (var_name.into(), *pos), + None, + 0, + None, + )))); + }); + + #[cfg(feature = "no_closure")] + externals.into_iter().for_each(|(var_name, pos)| { + args.push(Expr::Variable(Box::new(((var_name, pos), None, 0, None)))); + }); + + let hash = calc_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, num_externals, empty()); + + let fn_call = Expr::FnCall(Box::new(( + (KEYWORD_FN_PTR_CURRY.into(), false, false, pos), + None, + hash, + args, + None, + ))); + + let expr = Expr::Dot(Box::new((fn_expr, fn_call, pos))); + + // If there are captured variables, convert the entire expression into a statement block, + // then insert the relevant `Share` statements. + #[cfg(not(feature = "no_closure"))] + { + // Statement block + let mut statements: StaticVec<_> = Default::default(); + // Insert `Share` statements + statements.extend(externals.into_iter().map(|x| Stmt::Share(Box::new(x)))); + // Final expression + statements.push(Stmt::Expr(Box::new(expr))); + Expr::Stmt(Box::new((Stmt::Block(Box::new((statements, pos))), pos))) + } + + #[cfg(feature = "no_closure")] + return expr; +} + /// Parse an anonymous function definition. #[cfg(not(feature = "no_function"))] fn parse_anon_fn( @@ -3031,7 +3258,31 @@ fn parse_anon_fn( let body = parse_stmt(input, state, lib, settings.level_up()) .map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?; - let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect(); + // External variables may need to be processed in a consistent order, + // so extract them into a list. + let externals: StaticVec<_> = { + #[cfg(not(feature = "no_closure"))] + { + state + .externals + .iter() + .map(|(k, &v)| (k.clone(), v)) + .collect() + } + #[cfg(feature = "no_closure")] + Default::default() + }; + + let params: StaticVec<_> = if cfg!(not(feature = "no_closure")) { + externals + .iter() + .map(|(k, _)| k) + .cloned() + .chain(params.into_iter().map(|(v, _)| v)) + .collect() + } else { + params.into_iter().map(|(v, _)| v).collect() + }; // Calculate hash #[cfg(feature = "no_std")] @@ -3045,18 +3296,27 @@ fn parse_anon_fn( let hash = s.finish(); // Create unique function name - let fn_name: ImmutableString = format!("{}{:16x}", FN_ANONYMOUS, hash).into(); + let fn_name: ImmutableString = format!("{}{:016x}", FN_ANONYMOUS, hash).into(); + // Define the function let script = ScriptFnDef { name: fn_name.clone(), access: FnAccess::Public, params, + #[cfg(not(feature = "no_closure"))] + externals: Default::default(), body, pos: settings.pos, }; let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); + let expr = if cfg!(not(feature = "no_closure")) { + make_curry_from_externals(expr, externals, settings.pos) + } else { + expr + }; + Ok((expr, script)) } @@ -3068,7 +3328,6 @@ impl Engine { optimization_level: OptimizationLevel, ) -> Result { let mut functions = Default::default(); - let mut state = ParseState::new( self, #[cfg(not(feature = "unchecked"))] @@ -3114,7 +3373,6 @@ impl Engine { ) -> Result<(Vec, Vec), ParseError> { let mut statements: Vec = Default::default(); let mut functions = Default::default(); - let mut state = ParseState::new( self, #[cfg(not(feature = "unchecked"))] @@ -3135,10 +3393,9 @@ impl Engine { pos: Position::none(), }; - let stmt = if let Some(s) = parse_stmt(input, &mut state, &mut functions, settings)? { - s - } else { - continue; + let stmt = match parse_stmt(input, &mut state, &mut functions, settings)? { + Some(s) => s, + None => continue, }; let need_semicolon = !stmt.is_self_terminated(); diff --git a/src/result.rs b/src/result.rs index 66fd9cdf..cecd14bd 100644 --- a/src/result.rs +++ b/src/result.rs @@ -39,8 +39,8 @@ pub enum EvalAltResult { /// An error has occurred inside a called function. /// Wrapped values are the name of the function and the interior error. ErrorInFunctionCall(String, Box, Position), - /// Access to `this` that is not bounded. - ErrorUnboundedThis(Position), + /// Access to `this` that is not bound. + ErrorUnboundThis(Position), /// Non-boolean operand encountered for boolean operator. Wrapped value is the operator. ErrorBooleanArgMismatch(String, Position), /// Non-character value encountered where a character is required. @@ -69,6 +69,8 @@ pub enum EvalAltResult { ErrorVariableNotFound(String, Position), /// Usage of an unknown module. Wrapped value is the name of the module. ErrorModuleNotFound(String, Position), + /// Data race detected when accessing a variable. Wrapped value is the name of the variable. + ErrorDataRace(String, Position), /// Assignment to an inappropriate LHS (left-hand-side) expression. ErrorAssignmentToUnknownLHS(Position), /// Assignment to a constant variable. @@ -112,7 +114,7 @@ impl EvalAltResult { Self::ErrorParsing(p, _) => p.desc(), Self::ErrorInFunctionCall(_, _, _) => "Error in called function", Self::ErrorFunctionNotFound(_, _) => "Function not found", - Self::ErrorUnboundedThis(_) => "'this' is not bounded", + Self::ErrorUnboundThis(_) => "'this' is not bound", Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands", Self::ErrorCharMismatch(_) => "Character expected", Self::ErrorNumericIndexExpr(_) => { @@ -136,7 +138,8 @@ impl EvalAltResult { Self::ErrorLogicGuard(_) => "Boolean value expected", Self::ErrorFor(_) => "For loop expects an array, object map, or range", Self::ErrorVariableNotFound(_, _) => "Variable not found", - Self::ErrorModuleNotFound(_, _) => "module not found", + Self::ErrorModuleNotFound(_, _) => "Module not found", + Self::ErrorDataRace(_, _) => "Data race detected when accessing variable", Self::ErrorAssignmentToUnknownLHS(_) => { "Assignment to an unsupported left-hand side expression" } @@ -180,14 +183,15 @@ impl fmt::Display for EvalAltResult { Self::ErrorFunctionNotFound(s, _) | Self::ErrorVariableNotFound(s, _) + | Self::ErrorDataRace(s, _) | Self::ErrorModuleNotFound(s, _) => write!(f, "{}: '{}'", desc, s)?, - Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{} {}", desc, s)?, + Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{}", s)?, Self::ErrorIndexingType(_, _) | Self::ErrorNumericIndexExpr(_) | Self::ErrorStringIndexExpr(_) - | Self::ErrorUnboundedThis(_) + | Self::ErrorUnboundThis(_) | Self::ErrorImportExpr(_) | Self::ErrorLogicGuard(_) | Self::ErrorFor(_) @@ -276,7 +280,7 @@ impl EvalAltResult { Self::ErrorParsing(_, pos) | Self::ErrorFunctionNotFound(_, pos) | Self::ErrorInFunctionCall(_, _, pos) - | Self::ErrorUnboundedThis(pos) + | Self::ErrorUnboundThis(pos) | Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorCharMismatch(pos) | Self::ErrorArrayBounds(_, _, pos) @@ -289,6 +293,7 @@ impl EvalAltResult { | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) | Self::ErrorModuleNotFound(_, pos) + | Self::ErrorDataRace(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, _, pos) @@ -316,7 +321,7 @@ impl EvalAltResult { Self::ErrorParsing(_, pos) | Self::ErrorFunctionNotFound(_, pos) | Self::ErrorInFunctionCall(_, _, pos) - | Self::ErrorUnboundedThis(pos) + | Self::ErrorUnboundThis(pos) | Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorCharMismatch(pos) | Self::ErrorArrayBounds(_, _, pos) @@ -329,6 +334,7 @@ impl EvalAltResult { | Self::ErrorFor(pos) | Self::ErrorVariableNotFound(_, pos) | Self::ErrorModuleNotFound(_, pos) + | Self::ErrorDataRace(_, pos) | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, _, pos) diff --git a/src/scope.rs b/src/scope.rs index 5693554c..61f6fa17 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -316,6 +316,14 @@ impl<'a> Scope<'a> { }) } + /// Get an entry in the Scope, starting from the last. + pub(crate) fn get_entry(&self, name: &str) -> Option<&Entry> { + self.0 + .iter() + .rev() + .find(|Entry { name: key, .. }| name == key) + } + /// Get the value of an entry in the Scope, starting from the last. /// /// # Examples @@ -329,11 +337,8 @@ impl<'a> Scope<'a> { /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` pub fn get_value(&self, name: &str) -> Option { - self.0 - .iter() - .rev() - .find(|Entry { name: key, .. }| name == key) - .and_then(|Entry { value, .. }| value.downcast_ref::().cloned()) + self.get_entry(name) + .and_then(|Entry { value, .. }| value.clone().clone_inner_data::()) } /// Update the value of the named entry. @@ -384,13 +389,30 @@ impl<'a> Scope<'a> { self } + /// Clone the Scope, keeping only the last instances of each variable name. + /// Shadowed variables are omitted in the copy. + pub(crate) fn flatten_clone(&self) -> Self { + let mut entries: Vec = Default::default(); + + self.0.iter().rev().for_each(|entry| { + if entries + .iter() + .find(|Entry { name, .. }| &entry.name == name) + .is_none() + { + entries.push(entry.clone()); + } + }); + + Self(entries) + } + /// Get an iterator to entries in the Scope. - #[cfg(not(feature = "no_module"))] pub(crate) fn into_iter(self) -> impl Iterator> { 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 { self.0.iter().rev() // Always search a Scope in reverse order } @@ -411,13 +433,20 @@ impl<'a> Scope<'a> { /// /// let (name, value) = iter.next().unwrap(); /// assert_eq!(name, "x"); - /// assert_eq!(value.clone().cast::(), 42); + /// assert_eq!(value.cast::(), 42); /// /// let (name, value) = iter.next().unwrap(); /// assert_eq!(name, "foo"); - /// assert_eq!(value.clone().cast::(), "hello"); + /// assert_eq!(value.cast::(), "hello"); /// ``` - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { + 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 { self.0 .iter() .map(|Entry { name, value, .. }| (name.as_ref(), value)) diff --git a/src/serde/de.rs b/src/serde/de.rs index 8a60604c..f6d328db 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -177,6 +177,9 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> { Union::Variant(value) if value.is::() => self.deserialize_u128(visitor), Union::Variant(_) => self.type_error(), + + #[cfg(not(feature = "no_closure"))] + Union::Shared(_) => self.type_error(), } } diff --git a/src/settings.rs b/src/settings.rs index d638499c..d4949a41 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -18,9 +18,9 @@ impl Engine { /// /// When searching for functions, packages loaded later are preferred. /// In other words, loaded packages are searched in reverse order. - pub fn load_package(&mut self, package: PackageLibrary) -> &mut Self { + pub fn load_package(&mut self, package: impl Into) -> &mut Self { // Push the package to the top - packages are searched in reverse order - self.packages.push(package); + self.packages.push(package.into()); self } diff --git a/src/token.rs b/src/token.rs index d2f016cb..4a4d5fc1 100644 --- a/src/token.rs +++ b/src/token.rs @@ -2,7 +2,7 @@ use crate::engine::{ Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, - KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, + KEYWORD_IS_SHARED, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::error::LexError; @@ -21,7 +21,6 @@ use crate::stdlib::{ iter::Peekable, str::{Chars, FromStr}, string::{String, ToString}, - vec::Vec, }; type LERR = LexError; @@ -30,8 +29,11 @@ pub type TokenStream<'a, 't> = Peekable>; /// A location (line number + character position) in the input script. /// -/// In order to keep footprint small, both line number and character position have 16-bit unsigned resolution, -/// meaning they go up to a maximum of 65,535 lines and characters per line. +/// # Limitations +/// +/// In order to keep footprint small, both line number and character position have 16-bit resolution, +/// meaning they go up to a maximum of 65,535 lines and 65,535 characters per line. +/// /// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect. #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { @@ -43,6 +45,13 @@ pub struct Position { impl Position { /// Create a new `Position`. + /// + /// `line` must not be zero. + /// If `position` is zero, then it is at the beginning of a line. + /// + /// # Panics + /// + /// Panics if `line` is zero. pub fn new(line: u16, position: u16) -> Self { assert!(line != 0, "line cannot be zero"); @@ -52,7 +61,7 @@ impl Position { } } - /// Get the line number (1-based), or `None` if no position. + /// Get the line number (1-based), or `None` if there is no position. pub fn line(&self) -> Option { if self.is_none() { None @@ -85,7 +94,6 @@ impl Position { /// # Panics /// /// Panics if already at beginning of a line - cannot rewind to a previous line. - /// pub(crate) fn rewind(&mut self) { assert!(!self.is_none(), "cannot rewind Position::none"); assert!(self.pos > 0, "cannot rewind at position 0"); @@ -104,7 +112,7 @@ impl Position { } /// Create a `Position` representing no position. - pub(crate) fn none() -> Self { + pub fn none() -> Self { Self { line: 0, pos: 0 } } @@ -146,9 +154,9 @@ impl fmt::Debug for Position { pub enum Token { /// An `INT` constant. IntegerConstant(INT), - /// A `FLOAT` constaint. + /// A `FLOAT` constant. /// - /// Never appears under the `no_float` feature. + /// Reserved under the `no_float` feature. #[cfg(not(feature = "no_float"))] FloatConstant(FLOAT), /// An identifier. @@ -249,7 +257,7 @@ pub enum Token { And, /// `fn` /// - /// Never appears under the `no_function` feature. + /// Reserved under the `no_function` feature. #[cfg(not(feature = "no_function"))] Fn, /// `continue` @@ -284,22 +292,22 @@ pub enum Token { PowerOfAssign, /// `private` /// - /// Never appears under the `no_function` feature. + /// Reserved under the `no_function` feature. #[cfg(not(feature = "no_function"))] Private, /// `import` /// - /// Never appears under the `no_module` feature. + /// Reserved under the `no_module` feature. #[cfg(not(feature = "no_module"))] Import, /// `export` /// - /// Never appears under the `no_module` feature. + /// Reserved under the `no_module` feature. #[cfg(not(feature = "no_module"))] Export, /// `as` /// - /// Never appears under the `no_module` feature. + /// Reserved under the `no_module` feature. #[cfg(not(feature = "no_module"))] As, /// A lexer error. @@ -492,11 +500,16 @@ impl Token { #[cfg(feature = "no_module")] "import" | "export" | "as" => Reserved(syntax.into()), - "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" => { + "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public" + | "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with" + | "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" + | "catch" | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async" + | "await" | "yield" => Reserved(syntax.into()), + + KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_SHARED | KEYWORD_THIS => { Reserved(syntax.into()) } - KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR - | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()), _ => return None, }) @@ -640,7 +653,7 @@ impl Token { } } - /// Is this token a standard keyword? + /// Is this token an active standard keyword? pub fn is_keyword(&self) -> bool { use Token::*; @@ -666,6 +679,15 @@ impl Token { } } + /// Convert a token into a function name, if possible. + pub(crate) fn into_function_name_for_override(self) -> Result { + match self { + Self::Reserved(s) if can_override_keyword(&s) => Ok(s), + Self::Custom(s) | Self::Identifier(s) if is_valid_identifier(s.chars()) => Ok(s), + _ => Err(self), + } + } + /// Is this token a custom keyword? pub fn is_custom(&self) -> bool { match self { @@ -726,8 +748,8 @@ pub fn parse_string_literal( pos: &mut Position, enclosing_char: char, ) -> Result { - let mut result = Vec::new(); - let mut escape = String::with_capacity(12); + let mut result: StaticVec = Default::default(); + let mut escape: StaticVec = Default::default(); loop { let next_char = stream.get_next().ok_or((LERR::UnterminatedString, *pos))?; @@ -766,8 +788,8 @@ pub fn parse_string_literal( // \x??, \u????, \U???????? ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => { let mut seq = escape.clone(); - seq.push(ch); escape.clear(); + seq.push(ch); let mut out_val: u32 = 0; let len = match ch { @@ -778,23 +800,31 @@ pub fn parse_string_literal( }; for _ in 0..len { - let c = stream - .get_next() - .ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?; + let c = stream.get_next().ok_or_else(|| { + ( + LERR::MalformedEscapeSequence(seq.iter().cloned().collect()), + *pos, + ) + })?; seq.push(c); pos.advance(); out_val *= 16; - out_val += c - .to_digit(16) - .ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?; + out_val += c.to_digit(16).ok_or_else(|| { + ( + LERR::MalformedEscapeSequence(seq.iter().cloned().collect()), + *pos, + ) + })?; } - result.push( - char::from_u32(out_val) - .ok_or_else(|| (LERR::MalformedEscapeSequence(seq), *pos))?, - ); + result.push(char::from_u32(out_val).ok_or_else(|| { + ( + LERR::MalformedEscapeSequence(seq.into_iter().collect()), + *pos, + ) + })?); } // \{enclosing_char} - escaped @@ -807,7 +837,12 @@ pub fn parse_string_literal( ch if enclosing_char == ch && escape.is_empty() => break, // Unknown escape sequence - _ if !escape.is_empty() => return Err((LERR::MalformedEscapeSequence(escape), *pos)), + _ if !escape.is_empty() => { + return Err(( + LERR::MalformedEscapeSequence(escape.into_iter().collect()), + *pos, + )) + } // Cannot have new-lines inside string literals '\n' => { @@ -962,7 +997,7 @@ fn get_next_token_inner( // digit ... ('0'..='9', _) => { - let mut result = Vec::new(); + let mut result: StaticVec = Default::default(); let mut radix_base: Option = None; result.push(c); @@ -1083,7 +1118,7 @@ fn get_next_token_inner( |err| (Token::LexError(Box::new(err.0)), err.1), |result| { let mut chars = result.chars(); - let first = chars.next(); + let first = chars.next().unwrap(); if chars.next().is_some() { ( @@ -1091,10 +1126,7 @@ fn get_next_token_inner( start_pos, ) } else { - ( - Token::CharConstant(first.expect("should be Some")), - start_pos, - ) + (Token::CharConstant(first), start_pos) } }, )) @@ -1367,7 +1399,7 @@ fn get_identifier( start_pos: Position, first_char: char, ) -> Option<(Token, Position)> { - let mut result = Vec::new(); + let mut result: StaticVec<_> = Default::default(); result.push(first_char); while let Some(next_char) = stream.peek_next() { @@ -1382,7 +1414,7 @@ fn get_identifier( let is_valid_identifier = is_valid_identifier(result.iter().cloned()); - let identifier: String = result.into_iter().collect(); + let identifier = result.into_iter().collect(); if !is_valid_identifier { return Some(( @@ -1397,6 +1429,27 @@ fn get_identifier( )); } +/// Is this keyword allowed as a function? +#[inline(always)] +pub fn is_keyword_function(name: &str) -> bool { + match name { + #[cfg(not(feature = "no_closure"))] + KEYWORD_IS_SHARED => true, + KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => true, + _ => false, + } +} + +/// Can this keyword be overridden as a function? +#[inline(always)] +pub fn can_override_keyword(name: &str) -> bool { + match name { + KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR => true, + _ => false, + } +} + pub fn is_valid_identifier(name: impl Iterator) -> bool { let mut first_alphabetic = false; @@ -1414,22 +1467,25 @@ pub fn is_valid_identifier(name: impl Iterator) -> bool { } #[cfg(feature = "unicode-xid-ident")] +#[inline(always)] fn is_id_first_alphabetic(x: char) -> bool { unicode_xid::UnicodeXID::is_xid_start(x) } #[cfg(feature = "unicode-xid-ident")] +#[inline(always)] fn is_id_continue(x: char) -> bool { unicode_xid::UnicodeXID::is_xid_continue(x) } #[cfg(not(feature = "unicode-xid-ident"))] - +#[inline(always)] fn is_id_first_alphabetic(x: char) -> bool { x.is_ascii_alphabetic() } #[cfg(not(feature = "unicode-xid-ident"))] +#[inline(always)] fn is_id_continue(x: char) -> bool { x.is_ascii_alphanumeric() || x == '_' } @@ -1486,13 +1542,15 @@ pub struct TokenIterator<'a, 'e> { pos: Position, /// Input character stream. stream: MultiInputsStream<'a>, + /// A processor function (if any) that maps a token to another. + map: Option Token>>, } impl<'a> Iterator for TokenIterator<'a, '_> { type Item = (Token, Position); fn next(&mut self) -> Option { - match ( + let token = match ( get_next_token(&mut self.stream, &mut self.state, &mut self.pos), self.engine.disabled_symbols.as_ref(), self.engine.custom_keywords.as_ref(), @@ -1574,12 +1632,27 @@ impl<'a> Iterator for TokenIterator<'a, '_> { Some((Token::Reserved(token.syntax().into()), pos)) } (r, _, _) => r, + }; + + match token { + None => None, + Some((token, pos)) => { + if let Some(ref map) = self.map { + Some((map(token), pos)) + } else { + Some((token, pos)) + } + } } } } /// Tokenize an input text stream. -pub fn lex<'a, 'e>(input: &'a [&'a str], engine: &'e Engine) -> TokenIterator<'a, 'e> { +pub fn lex<'a, 'e>( + input: &'a [&'a str], + map: Option Token>>, + engine: &'e Engine, +) -> TokenIterator<'a, 'e> { TokenIterator { engine, state: TokenizeState { @@ -1597,5 +1670,6 @@ pub fn lex<'a, 'e>(input: &'a [&'a str], engine: &'e Engine) -> TokenIterator<'a streams: input.iter().map(|s| s.chars().peekable()).collect(), index: 0, }, + map, } } diff --git a/src/utils.rs b/src/utils.rs index 760c809e..ee448260 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,8 +1,4 @@ //! Module containing various utility types and functions. -//! -//! # Safety -//! -//! The `StaticVec` type has some `unsafe` blocks to handle conversions between `MaybeUninit` and regular types. use crate::fn_native::{shared_make_mut, shared_take, Shared}; @@ -10,15 +6,13 @@ use crate::stdlib::{ any::TypeId, borrow::Borrow, boxed::Box, + cmp::Ordering, fmt, hash::{BuildHasher, Hash, Hasher}, iter::FromIterator, - mem, - mem::MaybeUninit, - ops::{Add, AddAssign, Deref, DerefMut, Drop, Index, IndexMut}, + ops::{Add, AddAssign, Deref}, str::FromStr, string::{String, ToString}, - vec::Vec, }; #[cfg(not(feature = "no_std"))] @@ -27,6 +21,8 @@ use crate::stdlib::collections::hash_map::DefaultHasher; #[cfg(feature = "no_std")] use ahash::AHasher; +use smallvec::SmallVec; + /// A hasher that only takes one single `u64` and returns it as a hash key. /// /// # Panics @@ -92,549 +88,10 @@ pub fn calc_fn_spec<'a>( s.finish() } -/// [INTERNALS] An array-like type that holds a number of values in static storage for no-allocation, quick access. +/// [INTERNALS] Alias to [`smallvec::SmallVec<[T; 4]>`](https://crates.io/crates/smallvec), +/// which is a specialized `Vec` backed by a small, fixed-size array when there are <= 4 items stored. /// Exported under the `internals` feature only. -/// -/// If too many items are stored, it converts into using a `Vec`. -/// -/// -/// This is essentially a knock-off of the [`staticvec`](https://crates.io/crates/staticvec) crate. -/// This simplified implementation here is to avoid pulling in another crate. -/// -/// # Implementation -/// -/// A `StaticVec` holds data in _either one_ of two storages: 1) a fixed-size array of `MAX_STATIC_VEC` -/// items, and 2) a dynamic `Vec`. At any time, either one of them (or both) must be empty, depending on the -/// total number of items. -/// -/// There is a `len` field containing the total number of items held by the `StaticVec`. -/// -/// The fixed-size array (`list`) is not initialized (i.e. initialized with `MaybeUninit::uninit()`). -/// -/// When `len <= MAX_STATIC_VEC`, all elements are stored in the fixed-size array. -/// Array slots `>= len` are `MaybeUninit::uninit()` while slots `< len` are considered actual data. -/// In this scenario, the `Vec` (`more`) is empty. -/// -/// As soon as we try to push a new item into the `StaticVec` that makes the total number exceed -/// `MAX_STATIC_VEC`, all the items in the fixed-sized array are taken out, replaced with -/// `MaybeUninit::uninit()` (via `mem::replace`) and pushed into the `Vec`. -/// Then the new item is added to the `Vec`. -/// -/// Therefore, if `len > MAX_STATIC_VEC`, then the fixed-size array (`list`) is considered -/// empty and uninitialized while all data resides in the `Vec` (`more`). -/// -/// When popping an item off of the `StaticVec`, the reverse is true. When `len = MAX_STATIC_VEC + 1`, -/// after popping the item, all the items residing in the `Vec` are moved back to the fixed-size array (`list`). -/// The `Vec` will then be empty. -/// -/// Therefore, if `len <= MAX_STATIC_VEC`, data is in the fixed-size array (`list`). -/// Otherwise, data is in the `Vec` (`more`). -/// -/// # Safety -/// -/// This type uses some unsafe code (mainly for uninitialized/unused array slots) for efficiency. -/// -/// ## WARNING -/// -/// This type is volatile and may change. -// -// TODO - remove unsafe code -pub struct StaticVec { - /// Total number of values held. - len: usize, - /// Fixed-size storage for fast, no-allocation access. - list: [MaybeUninit; MAX_STATIC_VEC], - /// Dynamic storage. For spill-overs. - more: Vec, -} - -/// Maximum slots of fixed-size storage for a `StaticVec`. -/// 4 slots should be enough for most cases. -const MAX_STATIC_VEC: usize = 4; - -impl Drop for StaticVec { - fn drop(&mut self) { - self.clear(); - } -} - -impl Hash for StaticVec { - fn hash(&self, state: &mut H) { - self.iter().for_each(|x| x.hash(state)); - } -} - -impl Default for StaticVec { - fn default() -> Self { - Self { - len: 0, - list: unsafe { mem::MaybeUninit::uninit().assume_init() }, - more: Vec::new(), - } - } -} - -impl PartialEq for StaticVec { - 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 Clone for StaticVec { - 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 Eq for StaticVec {} - -impl FromIterator for StaticVec { - fn from_iter>(iter: X) -> Self { - let mut vec = StaticVec::new(); - - for x in iter { - vec.push(x); - } - - vec - } -} - -impl IntoIterator for StaticVec { - type Item = T; - type IntoIter = Box>; - - fn into_iter(self) -> Self::IntoIter { - self.into_iter() - } -} - -impl StaticVec { - /// 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 { - 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>(&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>(&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 { - 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 { - 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 StaticVec { - /// Get a mutable iterator to entries in the `StaticVec`. - pub fn into_iter(mut self) -> Box> { - 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 { - data: [MaybeUninit; MAX_STATIC_VEC], - index: usize, - limit: usize, -} - -impl Iterator for FixedStorageIterator { - type Item = T; - - fn next(&mut self) -> Option { - 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 StaticVec { - /// 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 fmt::Debug for StaticVec { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.iter().collect::>(), f) - } -} - -impl AsRef<[T]> for StaticVec { - 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 AsMut<[T]> for StaticVec { - 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 Deref for StaticVec { - type Target = [T]; - fn deref(&self) -> &Self::Target { - self.as_ref() - } -} - -impl DerefMut for StaticVec { - fn deref_mut(&mut self) -> &mut Self::Target { - self.as_mut() - } -} - -impl Index for StaticVec { - type Output = T; - - fn index(&self, index: usize) -> &Self::Output { - self.get(index) - } -} - -impl IndexMut for StaticVec { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - self.get_mut(index) - } -} - -impl From> for Vec { - fn from(mut value: StaticVec) -> 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 From> for StaticVec { - fn from(mut value: Vec) -> Self { - let mut arr: Self = Default::default(); - arr.len = value.len(); - - if arr.len <= MAX_STATIC_VEC { - for x in (0..arr.len).rev() { - arr.set_into_list(x, value.pop().unwrap(), false); - } - } else { - arr.more = value; - } - - arr - } -} +pub type StaticVec = SmallVec<[T; 4]>; /// The system immutable string type. /// @@ -685,6 +142,12 @@ impl AsRef for ImmutableString { } } +impl Borrow for ImmutableString { + fn borrow(&self) -> &String { + &self.0 + } +} + impl Borrow for ImmutableString { fn borrow(&self) -> &str { self.0.as_str() @@ -801,6 +264,18 @@ impl AddAssign<&ImmutableString> for ImmutableString { } } +impl AddAssign for ImmutableString { + fn add_assign(&mut self, rhs: ImmutableString) { + if !rhs.is_empty() { + if self.is_empty() { + self.0 = rhs.0; + } else { + self.make_mut().push_str(rhs.0.as_str()); + } + } + } +} + impl Add<&str> for ImmutableString { type Output = Self; @@ -892,6 +367,42 @@ impl AddAssign for ImmutableString { } } +impl> PartialEq for ImmutableString { + fn eq(&self, other: &S) -> bool { + self.as_str().eq(other.as_ref()) + } +} + +impl PartialEq for str { + fn eq(&self, other: &ImmutableString) -> bool { + self.eq(other.as_str()) + } +} + +impl PartialEq for String { + fn eq(&self, other: &ImmutableString) -> bool { + self.eq(other.as_str()) + } +} + +impl> PartialOrd for ImmutableString { + fn partial_cmp(&self, other: &S) -> Option { + self.as_str().partial_cmp(other.as_ref()) + } +} + +impl PartialOrd for str { + fn partial_cmp(&self, other: &ImmutableString) -> Option { + self.partial_cmp(other.as_str()) + } +} + +impl PartialOrd for String { + fn partial_cmp(&self, other: &ImmutableString) -> Option { + self.as_str().partial_cmp(other.as_str()) + } +} + impl ImmutableString { /// Consume the `ImmutableString` and convert it into a `String`. /// If there are other references to the same string, a cloned copy is returned. diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 9808ff70..a22f7b6d 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,6 +1,7 @@ #![cfg(not(feature = "no_function"))] use rhai::{ - Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT, + Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, RegisterFn, + Scope, INT, }; use std::any::TypeId; @@ -82,6 +83,70 @@ fn test_call_fn_private() -> Result<(), Box> { Ok(()) } +#[test] +#[cfg(not(feature = "no_object"))] +fn test_fn_ptr_raw() -> Result<(), Box> { + let mut engine = Engine::new(); + + #[allow(deprecated)] + engine + .register_fn("mul", |x: &mut INT, y: INT| *x *= y) + .register_raw_fn( + "bar", + &[ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ], + move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + let fp = std::mem::take(args[1]).cast::(); + 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::( + r#" + fn foo(x) { this += x; } + + let x = 41; + x.bar(Fn("foo"), 1); + x + "# + )?, + 42 + ); + + assert!(matches!( + *engine.eval::( + 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::( + r#" + let x = 21; + x.bar(Fn("mul"), 2); + x + "# + )?, + 42 + ); + + Ok(()) +} + #[test] fn test_anonymous_fn() -> Result<(), Box> { let calc_func = Func::<(INT, INT, INT), INT>::create_from_script( @@ -113,80 +178,3 @@ fn test_anonymous_fn() -> Result<(), Box> { Ok(()) } - -#[test] -#[cfg(not(feature = "no_object"))] -fn test_fn_ptr_raw() -> Result<(), Box> { - let mut engine = Engine::new(); - - engine.register_raw_fn( - "bar", - &[ - TypeId::of::(), - TypeId::of::(), - TypeId::of::(), - ], - move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { - let fp = std::mem::take(args[1]).cast::(); - 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::( - 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> { - let mut module = Module::new(); - - module.set_raw_fn( - "call_with_arg", - &[TypeId::of::(), TypeId::of::()], - |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { - let fn_ptr = std::mem::take(args[0]).cast::(); - 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::( - r#" - let addition = |x, y| { x + y }; - let curried = addition.curry(2); - - call_with_arg(curried, 40) - "# - )?, - 42 - ); - - Ok(()) -} diff --git a/tests/closures.rs b/tests/closures.rs new file mode 100644 index 00000000..0736654b --- /dev/null +++ b/tests/closures.rs @@ -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> { + let mut module = Module::new(); + + module.set_raw_fn( + "call_with_arg", + &[TypeId::of::(), TypeId::of::()], + |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + let fn_ptr = std::mem::take(args[0]).cast::(); + 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::( + 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> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::( + 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::( + r#" + let a = 41; + let foo = |x| { a += x }; + foo.call(1); + a + "# + )?, + 42 + ); + + assert!(engine.eval::( + 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> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::( + r#" + let a = 1; + let b = 40; + let foo = |x| { this += a + x }; + b.call(foo, 1); + b + "# + )?, + 42 + ); + + assert!(matches!( + *engine + .eval::( + r#" + let a = 20; + let foo = |x| { this += a + x }; + a.call(foo, 1); + a + "# + ) + .expect_err("should error"), + EvalAltResult::ErrorDataRace(_, _) + )); + + Ok(()) +} diff --git a/tests/fn_ptr.rs b/tests/fn_ptr.rs index 833bb20c..c6658f17 100644 --- a/tests/fn_ptr.rs +++ b/tests/fn_ptr.rs @@ -73,7 +73,7 @@ fn test_fn_ptr() -> Result<(), Box> { "# ) .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(()) diff --git a/tests/functions.rs b/tests/functions.rs index 78d20fa3..58ef8d99 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, INT}; #[test] fn test_functions() -> Result<(), Box> { @@ -120,3 +120,53 @@ fn test_function_pointers() -> Result<(), Box> { Ok(()) } + +#[test] +#[cfg(not(feature = "no_closure"))] +fn test_function_captures() -> Result<(), Box> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::( + r#" + fn foo(y) { x += y; x } + + let x = 41; + let y = 999; + + foo!(1) + x + "# + )?, + 83 + ); + + assert!(engine + .eval::( + 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(()) +} diff --git a/tests/modules.rs b/tests/modules.rs index fd049815..3b00ab17 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -35,7 +35,7 @@ fn test_module_sub_module() -> Result<(), Box> { let m2 = m.get_sub_module("universe").unwrap(); assert!(m2.contains_var("answer")); - assert!(m2.contains_fn(hash_inc)); + assert!(m2.contains_fn(hash_inc, false)); assert_eq!(m2.get_var_value::("answer").unwrap(), 41); diff --git a/tests/time.rs b/tests/time.rs index 9873decf..b3f6668f 100644 --- a/tests/time.rs +++ b/tests/time.rs @@ -1,11 +1,14 @@ #![cfg(not(feature = "no_std"))] #![cfg(not(target_arch = "wasm32"))] -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult}; #[cfg(not(feature = "no_float"))] use rhai::FLOAT; +#[cfg(feature = "no_float")] +use rhai::INT; + #[test] fn test_timestamp() -> Result<(), Box> { let engine = Engine::new(); diff --git a/tests/tokens.rs b/tests/tokens.rs index 843fc719..e393663d 100644 --- a/tests/tokens.rs +++ b/tests/tokens.rs @@ -57,9 +57,9 @@ fn test_tokens_unicode_xid_ident() -> Result<(), Box> { let engine = Engine::new(); let result = engine.eval::( r" - fn すべての答え() { 42 } - すべての答え() - ", + fn すべての答え() { 42 } + すべての答え() + ", ); #[cfg(feature = "unicode-xid-ident")] assert_eq!(result?, 42); @@ -69,9 +69,9 @@ fn test_tokens_unicode_xid_ident() -> Result<(), Box> { let result = engine.eval::( r" - fn _1() { 1 } - _1() - ", + fn _1() { 1 } + _1() + ", ); assert!(result.is_err());