diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 259d8c51..d3ff29e9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - closures pull_request: {} jobs: @@ -28,6 +29,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 297a5a88..5f26563e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,13 +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_capture = [] # no automatic read/write binding of anonymous function's local variables to it's external context +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 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/RELEASES.md b/RELEASES.md index 7f59cddf..1d2a842b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,6 +9,8 @@ This version adds: * 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 ------------ @@ -19,8 +21,11 @@ New features * 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 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 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 ---------------- @@ -29,6 +34,8 @@ Breaking changes * 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 ------------ diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 78ea9882..3b23b321 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -79,7 +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. [Capturing External Variables](language/fn-closure.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) @@ -100,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 245668b2..6c2e3965 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -37,6 +37,8 @@ Dynamic * 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]. Safe 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 5877f1a0..d896739a 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -3,35 +3,36 @@ Keywords List {{#include ../links.md}} -| Keyword | Description | Inactive 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 @@ -41,6 +42,7 @@ Reserved Keywords | --------- | --------------------- | | `var` | Variable declaration | | `static` | Variable declaration | +| `shared` | Share value | | `do` | Looping | | `each` | Looping | | `then` | Control flow | @@ -59,7 +61,6 @@ Reserved Keywords | `package` | Package | | `spawn` | Threading | | `go` | Threading | -| `shared` | Threading | | `await` | Async | | `async` | Async | | `sync` | Async | diff --git a/doc/src/language/fn-anon.md b/doc/src/language/fn-anon.md index a4cb0fee..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 = #{ @@ -50,12 +50,11 @@ 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. -They do, however, _capture_ variable _values_ from their execution environment, unless the [`no_capture`] -feature is turned on. This is accomplished via [automatic currying][capture]. +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 index d8749601..2f6ce2c5 100644 --- a/doc/src/language/fn-closure.md +++ b/doc/src/language/fn-closure.md @@ -1,8 +1,10 @@ -Capture External Variables via Automatic Currying -================================================ +Simulating Closures +=================== -Poor Man's 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. @@ -13,13 +15,23 @@ 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 values captured are the values of those variables at the time of the [anonymous function]'s creation. + +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. -New Parameters For Captured Variables ------------------------------------- +Actual Implementation +--------------------- -In actual implementation, this de-sugars to: +The actual implementation de-sugars to: 1. Keeping track of what variables are accessed inside the anonymous function, @@ -27,28 +39,148 @@ In actual implementation, this de-sugars to: 3. The variable is added to the parameters list of the anonymous function, at the front. -4. The current value of the variable is then [curried][currying] into the [function pointer] itself, essentially carrying that value and inserting it into future calls of the function. +4. The variable is then converted into a **reference-counted shared value**. -Automatic currying can be turned off via the [`no_capture`] feature. + 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 = 40; +let x = 1; // a normal variable -let f = |y| x + y; // current value of variable 'x' is auto-curried - // the value 40 is curried into 'f' +let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f' -x = 1; // 'x' can be changed but the curried value is not +x.is_shared() == true; // 'x' is now a shared value! -f.call(2) == 42; // the value of 'x' is still 40 +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 -let f = Fn("anon$1001").curry(x); // current value of 'x' is curried +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 71c8933a..c223d8cd 100644 --- a/doc/src/language/fn-curry.md +++ b/doc/src/language/fn-curry.md @@ -33,7 +33,7 @@ curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)' Automatic Currying ------------------ -[Anonymous functions] defined via a closure syntax _capture_ external variables that are not shadowed inside -the function's scope. +[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/functions.md b/doc/src/language/functions.md index d8f67502..3bd15cd0 100644 --- a/doc/src/language/functions.md +++ b/doc/src/language/functions.md @@ -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 509823db..4ddaae8f 100644 --- a/doc/src/language/keywords.md +++ b/doc/src/language/keywords.md @@ -5,21 +5,21 @@ Keywords The following are reserved keywords in Rhai: -| Active keywords | Reserved keywords | Usage | Inactive under feature | -| ------------------------------------------------- | ---------------------------------------------------------- | --------------------- | :--------------------: | -| `true`, `false` | | Boolean constants | | -| `let`, `const` | `var`, `static` | Variable declarations | | -| `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`, `shared`, `sync`, `async`, `await`, `yield` | Threading/async | | -| `type_of`, `print`, `debug`, `eval` | | Special functions | | -| | `default`, `void`, `null`, `nil` | Special values | | +| 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 become the name of a [function] or [variable], even when they are disabled. - diff --git a/doc/src/language/oop.md b/doc/src/language/oop.md index 09bc7b55..017c4932 100644 --- a/doc/src/language/oop.md +++ b/doc/src/language/oop.md @@ -30,26 +30,33 @@ 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_capture`] feature. +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/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/links.md b/doc/src/links.md index 19fdf0e9..11b16796 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -9,7 +9,7 @@ [`no_object`]: {{rootUrl}}/start/features.md [`no_function`]: {{rootUrl}}/start/features.md [`no_module`]: {{rootUrl}}/start/features.md -[`no_capture`]: {{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 @@ -79,8 +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-closure.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 cf997286..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 @@ -84,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. @@ -156,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 7a06618a..3201aa07 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -23,8 +23,8 @@ more control over what a script can (or cannot) do. | `no_object` | Disable support for [custom types] and [object maps]. | | `no_function` | Disable script-defined [functions]. | | `no_module` | Disable loading external [modules]. | -| `no_capture` | Disable capturing external variables in [anonymous functions]. | -| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | +| `no_closure` | Disable [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | +| `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. | diff --git a/examples/repl.rs b/examples/repl.rs index b3877dd0..ee73c193 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -114,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 e310787a..1fb72d47 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, } } @@ -693,6 +1053,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()), } } @@ -703,6 +1065,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()), } } @@ -712,6 +1076,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()), } } @@ -721,12 +1087,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), @@ -748,6 +1118,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()), } } @@ -813,7 +1204,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 42ff7037..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}; @@ -1282,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, ) @@ -1305,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 7806382f..65228a8f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -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 { @@ -392,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 { @@ -504,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, @@ -556,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))); } } @@ -574,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)) } @@ -604,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 { @@ -664,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, @@ -675,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, false, - 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, false, - 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)), } } @@ -732,7 +800,7 @@ impl Engine { match rhs { // xxx.fn_name(arg_expr_list) Expr::FnCall(x) if x.1.is_none() => { - let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + 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, @@ -745,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()))?; @@ -756,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)) } @@ -766,7 +835,7 @@ 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, false, None, + state, lib, setter, 0, &mut args, is_ref, true, false, None, None, level, ) .map(|(v, _)| (v, true)) @@ -777,7 +846,7 @@ 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, false, None, + state, lib, getter, 0, &mut args, is_ref, true, false, None, None, level, ) .map(|(v, _)| (v, false)) @@ -791,11 +860,13 @@ impl Engine { Expr::Property(p) => { 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 ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + 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, @@ -828,18 +899,24 @@ impl Engine { let args = &mut arg_values[..1]; let (mut val, updated) = self .exec_fn_call( - state, lib, getter, true, 0, args, is_ref, true, false, + 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))?; @@ -848,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, - false, None, level, + state, lib, setter, 0, arg_values, is_ref, true, false, + None, None, level, ) .or_else( |err| match *err { @@ -866,7 +943,7 @@ 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 ((name, native, pos), _, hash, _, def_val) = x.as_ref(); + 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, @@ -1058,6 +1135,7 @@ impl Engine { mut _idx: Dynamic, idx_pos: Position, _create: bool, + _indexers: bool, _level: usize, ) -> Result, Box> { self.inc_operations(state)?; @@ -1102,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(())) }) @@ -1134,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, false, 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 { @@ -1149,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(), @@ -1179,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, 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()); } } @@ -1208,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 { @@ -1251,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(_) => { @@ -1280,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: @@ -1298,23 +1375,36 @@ impl Engine { .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, 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()) } @@ -1333,13 +1423,12 @@ 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, false, None, level, + state, lib, op, 0, args, false, false, false, None, None, level, ) .map(|(v, _)| v) .map_err(|err| err.new_position(*op_pos))? @@ -1409,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, - false, 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)) } @@ -1612,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()))?; @@ -1675,7 +1772,10 @@ impl Engine { Stmt::Let(x) if x.1.is_some() => { 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)?; + 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()) @@ -1691,7 +1791,10 @@ 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 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()) @@ -1751,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. @@ -1773,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 fc92e5ea..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,6 +170,7 @@ 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", @@ -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 f4c9cb83..d2311c7a 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,94 +194,33 @@ 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, pub_only) //.or_else(|| lib.get_fn(hash_fn, pub_only)) - } else { - None - } - //.or_else(|| self.global_module.get_fn(hash_script, pub_only)) - .or_else(|| self.global_module.get_fn(hash_fn, pub_only)) - //.or_else(|| self.packages.get_fn(hash_script, pub_only)) - .or_else(|| self.packages.get_fn(hash_fn, pub_only)); + 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 = if func.is_plugin_fn() { @@ -211,7 +230,9 @@ impl Engine { }; // 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 { @@ -343,6 +364,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; @@ -405,7 +437,7 @@ impl Engine { || 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 @@ -418,24 +450,28 @@ 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, pub_only) => + 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(), @@ -445,7 +481,7 @@ impl Engine { // Fn KEYWORD_FN_PTR - if args.len() == 1 && !self.has_override(lib, hashes.0, hashes.1, pub_only) => + 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(), @@ -455,7 +491,7 @@ 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, pub_only) => + 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(), @@ -463,15 +499,57 @@ impl Engine { ))) } - // 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, - pub_only, 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, + ), } } @@ -483,9 +561,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(), @@ -526,7 +616,7 @@ impl Engine { state: &mut State, lib: &Module, name: &str, - hash: u64, + hash_script: u64, target: &mut Target, idx_val: Dynamic, def_val: Option, @@ -544,12 +634,16 @@ impl Engine { 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() @@ -559,7 +653,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, pub_only, 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 @@ -568,7 +662,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()) @@ -578,11 +676,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, pub_only, 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(), @@ -596,16 +694,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; @@ -615,12 +719,16 @@ 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, pub_only, def_val, level, + state, lib, _fn_name, _hash, args, is_ref, true, pub_only, None, def_val, level, ) }?; @@ -645,16 +753,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, pub_only) { + 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)?; @@ -669,7 +778,8 @@ impl Engine { )) }) .and_then(|s| FnPtr::try_from(s)) - .map(Into::::into); + .map(Into::::into) + .map_err(|err| err.new_position(expr.position())); } } @@ -701,27 +811,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, 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) - .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 @@ -732,7 +827,7 @@ impl Engine { if name == KEYWORD_FN_PTR_CALL && args_expr.len() >= 1 - && !self.has_override(lib, 0, hash, pub_only) + && !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)?; @@ -746,7 +841,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(), @@ -756,20 +851,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) @@ -781,12 +904,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(..., ...) _ => { @@ -800,9 +925,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, pub_only, def_val, level, + state, lib, name, hash, args, is_ref, false, pub_only, capture, def_val, level, ) .map(|(v, _)| v) } @@ -821,10 +948,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<_>; @@ -890,12 +1017,22 @@ impl Engine { #[cfg(not(feature = "no_function"))] 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) } Some(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut(), Position::none()), Some(f) => f.get_native_fn()(self, lib, args.as_mut()), @@ -938,30 +1075,30 @@ pub fn run_builtin_binary_op( 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 { @@ -989,8 +1126,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())), @@ -1063,33 +1200,33 @@ pub fn run_builtin_op_assignment( } if args_type == TypeId::of::() { - let x = x.downcast_mut::().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 { @@ -1099,8 +1236,8 @@ pub fn run_builtin_op_assignment( _ => (), } } else if args_type == TypeId::of::() { - let x = x.downcast_mut::().unwrap(); let y = y.clone().cast::(); + let mut x = x.write_lock::().unwrap(); match op { "&=" => return Ok(Some(*x = *x && y)), @@ -1108,8 +1245,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)), @@ -1119,8 +1256,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.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 116c62ab..7d1fbe96 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -23,23 +23,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 { @@ -131,13 +151,13 @@ impl FnPtr { &mut Default::default(), lib.as_ref(), fn_name, - false, hash_script, args.as_mut(), has_this, has_this, true, None, + None, 0, ) .map(|(v, _)| v) @@ -297,6 +317,16 @@ impl CallableFunction { Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Script(_) => 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 { diff --git a/src/fn_register.rs b/src/fn_register.rs index 55fb2218..21009ee0 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; @@ -16,7 +16,7 @@ use crate::stdlib::{ any::TypeId, boxed::Box, mem, - string::{String, ToString}, + string::String, }; /// A trait to register custom plugins with the `Engine`. @@ -184,11 +184,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. @@ -201,7 +201,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. @@ -217,24 +217,23 @@ impl RegisterPlugin for Engine { /// 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) @@ -274,12 +273,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, @@ -289,7 +289,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 } @@ -303,7 +303,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 } @@ -312,11 +312,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/module.rs b/src/module.rs index 8eacde7b..fa23abd6 100644 --- a/src/module.rs +++ b/src/module.rs @@ -442,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; /// @@ -534,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))) @@ -615,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))) @@ -739,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))) @@ -773,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( @@ -896,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::(), diff --git a/src/optimize.rs b/src/optimize.rs index b46b7488..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,22 +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. @@ -418,7 +415,7 @@ 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()) + m.0.into_iter().find(|((name, _), _)| name == prop) .map(|(_, mut expr)| { expr.set_position(pos); expr }) .unwrap_or_else(|| Expr::Unit(pos)) } @@ -495,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) @@ -553,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!() } } @@ -570,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); @@ -746,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() { @@ -765,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() @@ -810,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 fd95e675..d8e256cc 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -2,38 +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, }; -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] -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), @@ -42,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), @@ -52,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), @@ -62,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, { @@ -83,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), @@ -93,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() { @@ -109,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>, @@ -147,24 +127,17 @@ where } } // Bit operators -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] fn binary_and(x: T, y: T) -> FuncReturn<::Output> { Ok(x & y) } -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] fn binary_or(x: T, y: T) -> FuncReturn<::Output> { Ok(x | y) } -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] 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( @@ -181,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( @@ -199,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), @@ -219,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( @@ -286,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)) } @@ -317,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); @@ -332,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); @@ -345,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); @@ -357,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); @@ -372,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); @@ -387,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); @@ -405,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); @@ -421,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); } @@ -441,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/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 6ddc2b0c..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; @@ -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"))] @@ -243,7 +241,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str if len > 0 { let ch = mem::take(args[2]).cast::(); - let s = args[0].downcast_mut::().unwrap(); + 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 3068bad0..272215ac 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,9 +2,11 @@ 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}; @@ -15,9 +17,6 @@ use crate::utils::{StaticVec, StraightHasherBuilder}; #[cfg(not(feature = "no_function"))] use crate::engine::FN_ANONYMOUS; -#[cfg(not(feature = "no_capture"))] -use crate::engine::KEYWORD_FN_PTR_CURRY; - #[cfg(not(feature = "no_object"))] use crate::engine::{make_getter, make_setter}; @@ -40,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; @@ -355,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, @@ -363,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. @@ -409,8 +414,13 @@ struct ParseState<'e> { /// 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_capture"))] + #[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. @@ -434,8 +444,10 @@ impl<'e> ParseState<'e> { max_expr_depth, #[cfg(not(feature = "unchecked"))] max_function_expr_depth, - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] externals: Default::default(), + #[cfg(not(feature = "no_closure"))] + allow_capture: true, stack: Default::default(), modules: Default::default(), } @@ -448,7 +460,7 @@ impl<'e> ParseState<'e> { /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. /// Return `None` when the variable name is not found in the `stack`. - fn access_var(&mut self, name: &str, pos: Position) -> Option { + fn access_var(&mut self, name: &str, _pos: Position) -> Option { let index = self .stack .iter() @@ -457,9 +469,13 @@ impl<'e> ParseState<'e> { .find(|(_, (n, _))| *n == name) .and_then(|(i, _)| NonZeroUsize::new(i + 1)); - #[cfg(not(feature = "no_capture"))] - if index.is_none() && !self.externals.contains_key(name) { - self.externals.insert(name.to_string(), pos); + #[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 @@ -563,6 +579,9 @@ pub enum Stmt { Position, )>, ), + /// Convert a variable to shared. + #[cfg(not(feature = "no_closure"))] + Share(Box<(String, Position)>), } impl Default for Stmt { @@ -590,6 +609,9 @@ impl Stmt { Stmt::Import(x) => x.2, #[cfg(not(feature = "no_module"))] Stmt::Export(x) => x.1, + + #[cfg(not(feature = "no_closure"))] + Stmt::Share(x) => x.1, } } @@ -613,6 +635,9 @@ impl Stmt { 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 @@ -639,6 +664,9 @@ impl Stmt { #[cfg(not(feature = "no_module"))] Stmt::Import(_) | Stmt::Export(_) => false, + + #[cfg(not(feature = "no_closure"))] + Stmt::Share(_) => false, } } @@ -662,6 +690,9 @@ impl Stmt { Stmt::Import(_) => false, #[cfg(not(feature = "no_module"))] Stmt::Export(_) => false, + + #[cfg(not(feature = "no_closure"))] + Stmt::Share(_) => false, } } } @@ -745,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, @@ -804,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(), @@ -868,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, @@ -900,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, @@ -1006,6 +1041,7 @@ impl Expr { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, Token::LeftParen => true, + Token::Bang => true, Token::DoubleColon => true, _ => false, }, @@ -1098,6 +1134,7 @@ fn parse_fn_call( state: &mut ParseState, lib: &mut FunctionsLib, id: String, + capture: bool, mut modules: Option>, settings: ParseSettings, ) -> Result { @@ -1140,7 +1177,7 @@ fn parse_fn_call( }; return Ok(Expr::FnCall(Box::new(( - (id.into(), false, settings.pos), + (id.into(), false, capture, settings.pos), modules, hash_script, args, @@ -1182,7 +1219,7 @@ fn parse_fn_call( }; return Ok(Expr::FnCall(Box::new(( - (id.into(), false, settings.pos), + (id.into(), false, capture, settings.pos), modules, hash_script, args, @@ -1591,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"))] @@ -1602,7 +1641,7 @@ fn parse_primary( Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) } // Function call is allowed to have reserved keyword - Token::Reserved(s) if input.peek().unwrap().0 == Token::LeftParen => { + 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 { @@ -1610,7 +1649,7 @@ fn parse_primary( } } // 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)) @@ -1650,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_fn_call(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 @@ -1764,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, @@ -1789,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, @@ -1820,7 +1874,7 @@ fn parse_unary( let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; - #[cfg(not(feature = "no_capture"))] + #[cfg(not(feature = "no_closure"))] new_state.externals.iter().for_each(|(closure, pos)| { state.access_var(closure, *pos); }); @@ -1984,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())), @@ -2171,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); @@ -2193,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); @@ -2254,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))) } @@ -2848,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, @@ -2867,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()); @@ -2972,10 +3043,12 @@ fn parse_fn( let (token, pos) = input.next().unwrap(); - let name = token.into_function_name().map_err(|t| match t { - Token::Reserved(s) => PERR::Reserved(s).into_err(pos), - _ => PERR::FnMissingName.into_err(pos), - })?; + 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), @@ -3039,55 +3112,83 @@ 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 -#[cfg(not(feature = "no_capture"))] fn make_curry_from_externals( fn_expr: Expr, - state: &mut ParseState, - settings: &ParseSettings, + externals: StaticVec<(String, Position)>, + pos: Position, ) -> Expr { - if state.externals.is_empty() { + if externals.is_empty() { return fn_expr; } + let num_externals = externals.len(); let mut args: StaticVec<_> = Default::default(); - state.externals.iter().for_each(|(var_name, pos)| { + #[cfg(not(feature = "no_closure"))] + externals.iter().for_each(|(var_name, pos)| { args.push(Expr::Variable(Box::new(( - (var_name.clone(), *pos), + (var_name.into(), *pos), None, 0, None, )))); }); - let hash = calc_fn_hash( - empty(), - KEYWORD_FN_PTR_CURRY, - state.externals.len(), - empty(), - ); + #[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, settings.pos), + (KEYWORD_FN_PTR_CURRY.into(), false, false, pos), None, hash, args, None, ))); - Expr::Dot(Box::new((fn_expr, fn_call, settings.pos))) + 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. @@ -3157,17 +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)))?; - #[cfg(feature = "no_capture")] - let params: StaticVec<_> = params.into_iter().map(|(v, _)| v).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() + }; - // Add parameters that are auto-curried - #[cfg(not(feature = "no_capture"))] - let params: StaticVec<_> = state - .externals - .keys() - .cloned() - .chain(params.into_iter().map(|(v, _)| v)) - .collect(); + 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")] @@ -3181,20 +3296,26 @@ 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))); - #[cfg(not(feature = "no_capture"))] - let expr = make_curry_from_externals(expr, state, &settings); + let expr = if cfg!(not(feature = "no_closure")) { + make_curry_from_externals(expr, externals, settings.pos) + } else { + expr + }; Ok((expr, script)) } diff --git a/src/result.rs b/src/result.rs index 550dcc37..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,6 +183,7 @@ 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, "{}", s)?, @@ -187,7 +191,7 @@ impl fmt::Display for EvalAltResult { 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/token.rs b/src/token.rs index 7d1a07ba..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; @@ -501,13 +501,15 @@ impl Token { "import" | "export" | "as" => Reserved(syntax.into()), "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public" - | "new" | "use" | "module" | "package" | "var" | "static" | "with" | "do" | "each" - | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" | "catch" - | "default" | "void" | "null" | "nil" | "spawn" | "go" | "shared" | "sync" - | "async" | "await" | "yield" => Reserved(syntax.into()), + | "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_THIS => Reserved(syntax.into()), + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_SHARED | KEYWORD_THIS => { + Reserved(syntax.into()) + } _ => return None, }) @@ -678,9 +680,9 @@ impl Token { } /// Convert a token into a function name, if possible. - pub(crate) fn into_function_name(self) -> Result { + pub(crate) fn into_function_name_for_override(self) -> Result { match self { - Self::Reserved(s) if is_keyword_function(&s) => Ok(s), + 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), } @@ -1430,13 +1432,22 @@ fn get_identifier( /// Is this keyword allowed as a function? #[inline(always)] pub fn is_keyword_function(name: &str) -> bool { - name == KEYWORD_PRINT - || name == KEYWORD_DEBUG - || name == KEYWORD_TYPE_OF - || name == KEYWORD_EVAL - || name == KEYWORD_FN_PTR - || name == KEYWORD_FN_PTR_CALL - || name == KEYWORD_FN_PTR_CURRY + 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 { diff --git a/src/utils.rs b/src/utils.rs index 13e11704..ee448260 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,6 +6,7 @@ use crate::stdlib::{ any::TypeId, borrow::Borrow, boxed::Box, + cmp::Ordering, fmt, hash::{BuildHasher, Hash, Hasher}, iter::FromIterator, @@ -141,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() @@ -257,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; @@ -348,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 0f9087d2..a22f7b6d 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -88,6 +88,7 @@ fn test_call_fn_private() -> Result<(), Box> { 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( diff --git a/tests/closures.rs b/tests/closures.rs index 27ecc5e6..0736654b 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -35,7 +35,8 @@ fn test_fn_ptr_curry_call() -> Result<(), Box> { } #[test] -#[cfg(not(feature = "no_capture"))] +#[cfg(not(feature = "no_closure"))] +#[cfg(not(feature = "no_object"))] fn test_closures() -> Result<(), Box> { let engine = Engine::new(); @@ -56,5 +57,61 @@ fn test_closures() -> Result<(), Box> { 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(()) +}