Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-08-04 16:51:22 +08:00
commit 24346b9728
46 changed files with 2041 additions and 869 deletions

View File

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

View File

@ -33,13 +33,13 @@ only_i64 = [] # set INT=i64 (default) and disable support for all other in
no_index = [] # no arrays and indexing no_index = [] # no arrays and indexing
no_object = [] # no custom objects no_object = [] # no custom objects
no_function = [] # no script-defined functions no_function = [] # no script-defined functions
no_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 no_module = [] # no modules
internals = [] # expose internal data structures internals = [] # expose internal data structures
unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers.
# compiling for no-std # compiling for no-std
no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] no_std = [ "no_closure", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]
[profile.release] [profile.release]
lto = "fat" lto = "fat"

View File

@ -9,6 +9,8 @@ This version adds:
* Binding the `this` pointer in a function pointer `call`. * Binding the `this` pointer in a function pointer `call`.
* Anonymous functions (in Rust closure syntax). Simplifies creation of single-use ad-hoc functions. * Anonymous functions (in Rust closure syntax). Simplifies creation of single-use ad-hoc functions.
* Currying of function pointers. * Currying of function pointers.
* Closures - auto-currying of anonymous functions to capture shared variables from the external scope.
* Capturing call scope via `func!(...)` syntax.
New features New features
------------ ------------
@ -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`. * Anonymous functions are supported in the syntax of a Rust closure, e.g. `|x, y, z| x + y - z`.
* Custom syntax now works even without the `internals` feature. * Custom syntax now works even without the `internals` feature.
* Currying of function pointers is supported via the new `curry` keyword. * Currying of function pointers is supported via the new `curry` keyword.
* Automatic currying of anonymous functions to capture shared variables from the external scope.
* Capturing of the calling scope for function call via the `func!(...)` syntax.
* `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`. * `Module::set_indexer_get_set_fn` is added as a shorthand of both `Module::set_indexer_get_fn` and `Module::set_indexer_set_fn`.
* New `unicode-xid-ident` feature to allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) for identifiers. * New `unicode-xid-ident` feature to allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) for identifiers.
* `Scope::iter_raw` returns an iterator with a reference to the underlying `Dynamic` value (which may be shared).
Breaking changes Breaking changes
---------------- ----------------
@ -29,6 +34,8 @@ Breaking changes
* Function signature for defining custom syntax is simplified. * Function signature for defining custom syntax is simplified.
* `Engine::register_raw_fn_XXX` API shortcuts are removed. * `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. * `PackagesCollection::get_fn`, `PackagesCollection::contains_fn`, `Module::get_fn` and `Module::contains_fn` now take an additional `public_only` parameter indicating whether only public functions are accepted.
* The iterator returned by `Scope::iter` now contains a clone of the `Dynamic` value (unshared).
* `Engine::load_package` takes any type that is `Into<PackageLibrary>`.
Housekeeping Housekeeping
------------ ------------

View File

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

View File

@ -37,6 +37,8 @@ Dynamic
* Dynamic dispatch via [function pointers] with additional support for [currying]. * Dynamic dispatch via [function pointers] with additional support for [currying].
* Closures via [automatic currying] with capturing shared variables from the external scope.
* Some support for [object-oriented programming (OOP)][OOP]. * Some support for [object-oriented programming (OOP)][OOP].
Safe Safe

View File

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

View File

@ -3,35 +3,36 @@ Keywords List
{{#include ../links.md}} {{#include ../links.md}}
| Keyword | Description | Inactive under | | Keyword | Description | Inactive under | Overloadable |
| :-------------------: | ---------------------------------------- | :-------------: | | :-------------------: | ---------------------------------------- | :-------------: | :----------: |
| `true` | Boolean true literal | | | `true` | Boolean true literal | | No |
| `false` | Boolean false literal | | | `false` | Boolean false literal | | No |
| `let` | Variable declaration | | | `let` | Variable declaration | | No |
| `const` | Constant declaration | | | `const` | Constant declaration | | No |
| `if` | If statement | | | `is_shared` | Is a value shared? | | No |
| `else` | else block of if statement | | | `if` | If statement | | No |
| `while` | While loop | | | `else` | else block of if statement | | No |
| `loop` | Infinite loop | | | `while` | While loop | | No |
| `for` | For loop | | | `loop` | Infinite loop | | No |
| `in` | Containment test, part of for loop | | | `for` | For loop | | No |
| `continue` | Continue a loop at the next iteration | | | `in` | Containment test, part of for loop | | No |
| `break` | Loop breaking | | | `continue` | Continue a loop at the next iteration | | No |
| `return` | Return value | | | `break` | Loop breaking | | No |
| `throw` | Throw exception | | | `return` | Return value | | No |
| `import` | Import module | [`no_module`] | | `throw` | Throw exception | | No |
| `export` | Export variable | [`no_module`] | | `import` | Import module | [`no_module`] | No |
| `as` | Alias for variable export | [`no_module`] | | `export` | Export variable | [`no_module`] | No |
| `private` | Mark function private | [`no_function`] | | `as` | Alias for variable export | [`no_module`] | No |
| `fn` (lower-case `f`) | Function definition | [`no_function`] | | `private` | Mark function private | [`no_function`] | No |
| `Fn` (capital `F`) | Function to create a [function pointer] | | | `fn` (lower-case `f`) | Function definition | [`no_function`] | No |
| `call` | Call a [function pointer] | | | `Fn` (capital `F`) | Function to create a [function pointer] | | Yes |
| `curry` | Curry a [function pointer] | | | `call` | Call a [function pointer] | | No |
| `this` | Reference to base object for method call | [`no_function`] | | `curry` | Curry a [function pointer] | | No |
| `type_of` | Get type name of value | | | `this` | Reference to base object for method call | [`no_function`] | No |
| `print` | Print value | | | `type_of` | Get type name of value | | Yes |
| `debug` | Print value in debug format | | | `print` | Print value | | Yes |
| `eval` | Evaluate script | | | `debug` | Print value in debug format | | Yes |
| `eval` | Evaluate script | | Yes |
Reserved Keywords Reserved Keywords
@ -41,6 +42,7 @@ Reserved Keywords
| --------- | --------------------- | | --------- | --------------------- |
| `var` | Variable declaration | | `var` | Variable declaration |
| `static` | Variable declaration | | `static` | Variable declaration |
| `shared` | Share value |
| `do` | Looping | | `do` | Looping |
| `each` | Looping | | `each` | Looping |
| `then` | Control flow | | `then` | Control flow |
@ -59,7 +61,6 @@ Reserved Keywords
| `package` | Package | | `package` | Package |
| `spawn` | Threading | | `spawn` | Threading |
| `go` | Threading | | `go` | Threading |
| `shared` | Threading |
| `await` | Async | | `await` | Async |
| `async` | Async | | `async` | Async |
| `sync` | Async | | `sync` | Async |

View File

@ -22,7 +22,7 @@ fn print_obj() { print(this.data); }
``` ```
The above can be replaced by using _anonymous functions_ which have the same syntax as Rust's closures The above can be replaced by using _anonymous functions_ which have the same syntax as Rust's closures
(but they are **NOT** closures, merely syntactic sugar): (but they are **NOT** real closures, merely syntactic sugar):
```rust ```rust
let obj = #{ let obj = #{
@ -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 Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves
**not** closures. In particular, they do not capture their running environment. They are more like **not** real closures.
Rust's function pointers.
They do, however, _capture_ variable _values_ from their execution environment, unless the [`no_capture`] In particular, they capture their execution environment via [automatic currying]
feature is turned on. This is accomplished via [automatic currying][capture]. (disabled via [`no_closure`]).

View File

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

View File

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

View File

@ -33,7 +33,7 @@ curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)'
Automatic Currying Automatic Currying
------------------ ------------------
[Anonymous functions] defined via a closure syntax _capture_ external variables that are not shadowed inside [Anonymous functions] defined via a closure syntax _capture_ external variables
the function's scope. that are not shadowed inside the function's scope.
This is accomplished via [automatic currying]. This is accomplished via [automatic currying].

View File

@ -127,5 +127,5 @@ x.change(); // call 'change' in method-call style, 'this' binds to 'x'
x == 42; // 'x' is changed! x == 42; // 'x' is changed!
change(); // <- error: `this` is unbounded change(); // <- error: `this` is unbound
``` ```

View File

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

View File

@ -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 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 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 Examples
-------- --------
```rust ```rust
let factor = 1;
// Define the object // Define the object
let obj = let obj =
#{ #{
data: 0, data: 0,
increment: |x| this.data += x, // when called, 'this' binds to 'obj' increment: |x| this.data += x, // 'this' binds to 'obj'
update: |x| this.data = x, // when called, 'this' binds to 'obj' update: |x| this.data = x * factor, // 'this' binds to 'obj', 'factor' is captured
action: || print(this.data) // when called, 'this' binds to 'obj' action: || print(this.data) // 'this' binds to 'obj'
}; };
// Use the object // Use the object
obj.increment(1); obj.increment(1);
obj.action(); // prints 1 obj.action(); // prints 1
obj.update(42); obj.update(42);
obj.action(); // prints 42 obj.action(); // prints 42
factor = 2;
obj.update(42);
obj.action(); // prints 84
``` ```

View File

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

View File

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

View File

@ -35,12 +35,12 @@ engine.register_raw_fn(
// Therefore, get a '&mut' reference to the first argument _last_. // Therefore, get a '&mut' reference to the first argument _last_.
// Alternatively, use `args.split_at_mut(1)` etc. to split the slice first. // Alternatively, use `args.split_at_mut(1)` etc. to split the slice first.
let y: i64 = *args[1].downcast_ref::<i64>() // get a reference to the second argument let y: i64 = *args[1].read_lock::<i64>() // get a reference to the second argument
.unwrap(); // then copying it because it is a primary type .unwrap(); // then copying it because it is a primary type
let y: i64 = std::mem::take(args[1]).cast::<i64>(); // alternatively, directly 'consume' it let y: i64 = std::mem::take(args[1]).cast::<i64>(); // alternatively, directly 'consume' it
let x: &mut i64 = args[0].downcast_mut::<i64>() // get a '&mut' reference to the let x: &mut i64 = args[0].write_lock::<i64>() // get a '&mut' reference to the
.unwrap(); // first argument .unwrap(); // first argument
*x += y; // perform the action *x += y; // perform the action
@ -84,12 +84,12 @@ Extract Arguments
To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following: To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following:
| Argument type | Access (`n` = argument position) | Result | | Argument type | Access (`n` = argument position) | Result |
| ------------------------------ | -------------------------------------- | ---------------------------------------------------------- | | ------------------------------ | ------------------------------------- | ---------------------------------------------------------- |
| [Primary type][standard types] | `args[n].clone().cast::<T>()` | Copy of value. | | [Primary type][standard types] | `args[n].clone().cast::<T>()` | Copy of value. |
| Custom type | `args[n].downcast_ref::<T>().unwrap()` | Immutable reference to value. | | Custom type | `args[n].read_lock::<T>().unwrap()` | Immutable reference to value. |
| Custom type (consumed) | `std::mem::take(args[n]).cast::<T>()` | The _consumed_ value.<br/>The original value becomes `()`. | | Custom type (consumed) | `std::mem::take(args[n]).cast::<T>()` | The _consumed_ value.<br/>The original value becomes `()`. |
| `this` object | `args[0].downcast_mut::<T>().unwrap()` | Mutable reference to value. | | `this` object | `args[0].write_lock::<T>().unwrap()` | Mutable reference to value. |
When there is a mutable reference to the `this` object (i.e. the first argument), When there is a mutable reference to the `this` object (i.e. the first argument),
there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain. there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain.
@ -156,5 +156,5 @@ let this_ptr = first[0].downcast_mut::<A>().unwrap();
// Immutable reference to the second value parameter // Immutable reference to the second value parameter
// This can be mutable but there is no point because the parameter is passed by value // This can be mutable but there is no point because the parameter is passed by value
let value = rest[0].downcast_ref::<B>().unwrap(); let value_ref = rest[0].read_lock::<B>().unwrap();
``` ```

View File

@ -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_object` | Disable support for [custom types] and [object maps]. |
| `no_function` | Disable script-defined [functions]. | | `no_function` | Disable script-defined [functions]. |
| `no_module` | Disable loading external [modules]. | | `no_module` | Disable loading external [modules]. |
| `no_capture` | Disable capturing external variables in [anonymous functions]. | | `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`. Notice that additional dependencies will be pulled in to replace `std` features. | | `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. | | `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. | | `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. | | `unicode-xid-ident` | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. |

View File

@ -114,10 +114,15 @@ fn main() {
} }
"exit" | "quit" => break, // quit "exit" | "quit" => break, // quit
"scope" => { "scope" => {
scope scope.iter_raw().enumerate().for_each(|(i, (name, value))| {
.iter() println!(
.enumerate() "[{}] {}{} = {:?}",
.for_each(|(i, (name, value))| println!("[{}] {} = {:?}", i + 1, name, value)); i + 1,
name,
if value.is_shared() { " (shared)" } else { "" },
*value.read_lock::<Dynamic>().unwrap(),
)
});
continue; continue;
} }
"astu" => { "astu" => {

View File

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

View File

@ -3,6 +3,7 @@
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::engine::{Engine, Imports, State}; use crate::engine::{Engine, Imports, State};
use crate::error::ParseError; use crate::error::ParseError;
use crate::fn_call::ensure_no_data_race;
use crate::fn_native::{IteratorFn, SendSync}; use crate::fn_native::{IteratorFn, SendSync};
use crate::module::{FuncReturn, Module}; use crate::module::{FuncReturn, Module};
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::{optimize_into_ast, OptimizationLevel};
@ -1282,6 +1283,11 @@ impl Engine {
let mut mods = Imports::new(); let mut mods = Imports::new();
let args = args.as_mut(); let args = args.as_mut();
// Check for data race.
if cfg!(not(feature = "no_closure")) {
ensure_no_data_race(name, args, false)?;
}
self.call_script_fn( self.call_script_fn(
scope, &mut mods, &mut state, lib, this_ptr, name, fn_def, args, 0, scope, &mut mods, &mut state, lib, this_ptr, name, fn_def, args, 0,
) )
@ -1305,16 +1311,15 @@ impl Engine {
mut ast: AST, mut ast: AST,
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> AST { ) -> AST {
#[cfg(not(feature = "no_function"))] let lib = if cfg!(not(feature = "no_function")) {
let lib = ast ast.lib()
.lib() .iter_fn()
.iter_fn() .filter(|(_, _, _, f)| f.is_script())
.filter(|(_, _, _, f)| f.is_script()) .map(|(_, _, _, f)| f.get_fn_def().clone())
.map(|(_, _, _, f)| f.get_fn_def().clone()) .collect()
.collect(); } else {
Default::default()
#[cfg(feature = "no_function")] };
let lib = Default::default();
let stmt = mem::take(ast.statements_mut()); let stmt = mem::take(ast.statements_mut());
optimize_into_ast(self, scope, stmt, lib, optimization_level) optimize_into_ast(self, scope, stmt, lib, optimization_level)

View File

@ -31,12 +31,17 @@ use crate::module::resolvers;
#[cfg(any(not(feature = "no_object"), not(feature = "no_module")))] #[cfg(any(not(feature = "no_object"), not(feature = "no_module")))]
use crate::utils::ImmutableString; use crate::utils::ImmutableString;
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
use crate::any::DynamicWriteLock;
use crate::stdlib::{ use crate::stdlib::{
borrow::Cow, borrow::Cow,
boxed::Box, boxed::Box,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
fmt, format, fmt, format,
iter::{empty, once}, iter::{empty, once},
ops::DerefMut,
string::{String, ToString}, string::{String, ToString},
vec::Vec, vec::Vec,
}; };
@ -44,6 +49,9 @@ use crate::stdlib::{
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::stdlib::any::TypeId; use crate::stdlib::any::TypeId;
#[cfg(not(feature = "no_closure"))]
use crate::stdlib::mem;
/// Variable-sized array of `Dynamic` values. /// Variable-sized array of `Dynamic` values.
/// ///
/// Not available under the `no_index` feature. /// Not available under the `no_index` feature.
@ -91,6 +99,7 @@ pub const KEYWORD_EVAL: &str = "eval";
pub const KEYWORD_FN_PTR: &str = "Fn"; pub const KEYWORD_FN_PTR: &str = "Fn";
pub const KEYWORD_FN_PTR_CALL: &str = "call"; pub const KEYWORD_FN_PTR_CALL: &str = "call";
pub const KEYWORD_FN_PTR_CURRY: &str = "curry"; pub const KEYWORD_FN_PTR_CURRY: &str = "curry";
pub const KEYWORD_IS_SHARED: &str = "is_shared";
pub const KEYWORD_THIS: &str = "this"; pub const KEYWORD_THIS: &str = "this";
pub const FN_TO_STRING: &str = "to_string"; pub const FN_TO_STRING: &str = "to_string";
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -122,6 +131,11 @@ pub enum ChainType {
pub enum Target<'a> { pub enum Target<'a> {
/// The target is a mutable reference to a `Dynamic` value somewhere. /// The target is a mutable reference to a `Dynamic` value somewhere.
Ref(&'a mut Dynamic), Ref(&'a mut Dynamic),
/// The target is a mutable reference to a Shared `Dynamic` value.
/// It holds both the access guard and the original shared value.
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
LockGuard((DynamicWriteLock<'a, Dynamic>, Dynamic)),
/// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects). /// The target is a temporary `Dynamic` value (i.e. the mutation can cause no side effects).
Value(Dynamic), Value(Dynamic),
/// The target is a character inside a String. /// The target is a character inside a String.
@ -136,6 +150,9 @@ impl Target<'_> {
pub fn is_ref(&self) -> bool { pub fn is_ref(&self) -> bool {
match self { match self {
Self::Ref(_) => true, Self::Ref(_) => true,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard(_) => true,
Self::Value(_) => false, Self::Value(_) => false,
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, _) => false, Self::StringChar(_, _, _) => false,
@ -145,16 +162,34 @@ impl Target<'_> {
pub fn is_value(&self) -> bool { pub fn is_value(&self) -> bool {
match self { match self {
Self::Ref(_) => false, Self::Ref(_) => false,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard(_) => false,
Self::Value(_) => true, Self::Value(_) => true,
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, _) => false, Self::StringChar(_, _, _) => false,
} }
} }
/// Is the `Target` a shared value?
pub fn is_shared(&self) -> bool {
match self {
Self::Ref(r) => r.is_shared(),
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard(_) => true,
Self::Value(r) => r.is_shared(),
#[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, _) => false,
}
}
/// Is the `Target` a specific type? /// Is the `Target` a specific type?
#[allow(dead_code)] #[allow(dead_code)]
pub fn is<T: Variant + Clone>(&self) -> bool { pub fn is<T: Variant + Clone>(&self) -> bool {
match self { match self {
Target::Ref(r) => r.is::<T>(), Target::Ref(r) => r.is::<T>(),
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Target::LockGuard((r, _)) => r.is::<T>(),
Target::Value(r) => r.is::<T>(), Target::Value(r) => r.is::<T>(),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Target::StringChar(_, _, _) => TypeId::of::<T>() == TypeId::of::<char>(), Target::StringChar(_, _, _) => TypeId::of::<T>() == TypeId::of::<char>(),
@ -164,6 +199,9 @@ impl Target<'_> {
pub fn clone_into_dynamic(self) -> Dynamic { pub fn clone_into_dynamic(self) -> Dynamic {
match self { match self {
Self::Ref(r) => r.clone(), // Referenced value is cloned Self::Ref(r) => r.clone(), // Referenced value is cloned
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard((_, orig)) => orig, // Original value is simply taken
Self::Value(v) => v, // Owned value is simply taken Self::Value(v) => v, // Owned value is simply taken
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, ch) => ch, // Character is taken Self::StringChar(_, _, ch) => ch, // Character is taken
@ -173,6 +211,9 @@ impl Target<'_> {
pub fn as_mut(&mut self) -> &mut Dynamic { pub fn as_mut(&mut self) -> &mut Dynamic {
match self { match self {
Self::Ref(r) => *r, Self::Ref(r) => *r,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard((r, _)) => r.deref_mut(),
Self::Value(ref mut r) => r, Self::Value(ref mut r) => r,
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::StringChar(_, _, ref mut r) => r, Self::StringChar(_, _, ref mut r) => r,
@ -183,13 +224,18 @@ impl Target<'_> {
pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> { pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> {
match self { match self {
Self::Ref(r) => **r = new_val, Self::Ref(r) => **r = new_val,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
Self::LockGuard((r, _)) => **r = new_val,
Self::Value(_) => { Self::Value(_) => {
return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(
Position::none(), Position::none(),
))) )))
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::StringChar(Dynamic(Union::Str(ref mut s)), index, _) => { Self::StringChar(string, index, _) if string.is::<ImmutableString>() => {
let mut s = string.write_lock::<ImmutableString>().unwrap();
// Replace the character at the specified index position // Replace the character at the specified index position
let new_ch = new_val let new_ch = new_val
.as_char() .as_char()
@ -215,9 +261,18 @@ impl Target<'_> {
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl<'a> From<&'a mut Dynamic> for Target<'a> { impl<'a> From<&'a mut Dynamic> for Target<'a> {
fn from(value: &'a mut Dynamic) -> Self { fn from(value: &'a mut Dynamic) -> Self {
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
if value.is_shared() {
// Cloning is cheap for a shared value
let container = value.clone();
return Self::LockGuard((value.write_lock::<Dynamic>().unwrap(), container));
}
Self::Ref(value) Self::Ref(value)
} }
} }
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl<T: Into<Dynamic>> From<T> for Target<'_> { impl<T: Into<Dynamic>> From<T> for Target<'_> {
fn from(value: T) -> Self { fn from(value: T) -> Self {
@ -392,11 +447,11 @@ impl Default for Engine {
progress: None, progress: None,
// optimization level // optimization level
#[cfg(feature = "no_optimize")] optimization_level: if cfg!(feature = "no_optimize") {
optimization_level: OptimizationLevel::None, OptimizationLevel::None
} else {
#[cfg(not(feature = "no_optimize"))] OptimizationLevel::Simple
optimization_level: OptimizationLevel::Simple, },
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
limits: Limits { limits: Limits {
@ -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>( pub fn search_namespace<'s, 'a>(
scope: &'s mut Scope, scope: &'s mut Scope,
mods: &'s mut Imports, mods: &'s mut Imports,
@ -556,7 +612,7 @@ pub fn search_scope_only<'s, 'a>(
if let Some(val) = this_ptr { if let Some(val) = this_ptr {
return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos)); return Ok(((*val).into(), KEYWORD_THIS, ScopeEntryType::Normal, *pos));
} else { } else {
return Err(Box::new(EvalAltResult::ErrorUnboundedThis(*pos))); return Err(Box::new(EvalAltResult::ErrorUnboundThis(*pos)));
} }
} }
@ -574,6 +630,13 @@ pub fn search_scope_only<'s, 'a>(
}; };
let (val, typ) = scope.get_mut(index); let (val, typ) = scope.get_mut(index);
// Check for data race - probably not necessary because the only place it should conflict is in a method call
// when the object variable is also used as a parameter.
// if cfg!(not(feature = "no_closure")) && val.is_locked() {
// return Err(Box::new(EvalAltResult::ErrorDataRace(name.into(), *pos)));
// }
Ok((val, name, typ, *pos)) Ok((val, name, typ, *pos))
} }
@ -604,11 +667,11 @@ impl Engine {
debug: Box::new(|_| {}), debug: Box::new(|_| {}),
progress: None, progress: None,
#[cfg(feature = "no_optimize")] optimization_level: if cfg!(feature = "no_optimize") {
optimization_level: OptimizationLevel::None, OptimizationLevel::None
} else {
#[cfg(not(feature = "no_optimize"))] OptimizationLevel::Simple
optimization_level: OptimizationLevel::Simple, },
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
limits: Limits { limits: Limits {
@ -664,8 +727,9 @@ impl Engine {
Expr::Dot(x) | Expr::Index(x) => { Expr::Dot(x) | Expr::Index(x) => {
let (idx, expr, pos) = x.as_ref(); let (idx, expr, pos) = x.as_ref();
let idx_pos = idx.position(); let idx_pos = idx.position();
let obj_ptr = &mut self let obj_ptr = &mut self.get_indexed_mut(
.get_indexed_mut(state, lib, target, idx_val, idx_pos, false, level)?; state, lib, target, idx_val, idx_pos, false, true, level,
)?;
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level, state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level,
@ -675,54 +739,58 @@ impl Engine {
} }
// xxx[rhs] = new_val // xxx[rhs] = new_val
_ if _new_val.is_some() => { _ if _new_val.is_some() => {
let mut new_val = _new_val.unwrap();
let mut idx_val2 = idx_val.clone(); let mut idx_val2 = idx_val.clone();
match self.get_indexed_mut(state, lib, target, idx_val, pos, true, level) { // `call_setter` is introduced to bypass double mutable borrowing of target
// Indexed value is an owned value - the only possibility is an indexer let _call_setter = match self
// Try to call an index setter .get_indexed_mut(state, lib, target, idx_val, pos, true, false, level)
#[cfg(not(feature = "no_index"))] {
Ok(obj_ptr) if obj_ptr.is_value() => {
let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val];
self.exec_fn_call(
state, lib, FN_IDX_SET, true, 0, args, is_ref, true, 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),
})?;
}
// Indexed value is a reference - update directly // Indexed value is a reference - update directly
Ok(ref mut obj_ptr) => { Ok(ref mut obj_ptr) => {
obj_ptr obj_ptr
.set_value(new_val) .set_value(_new_val.unwrap())
.map_err(|err| err.new_position(rhs.position()))?; .map_err(|err| err.new_position(rhs.position()))?;
None
} }
Err(err) => match *err { Err(err) => match *err {
// No index getter - try to call an index setter // No index getter - try to call an index setter
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
EvalAltResult::ErrorIndexingType(_, _) => { EvalAltResult::ErrorIndexingType(_, _) => {
let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val]; // Raise error if there is no index getter nor setter
Some(_new_val.unwrap())
self.exec_fn_call(
state, lib, FN_IDX_SET, true, 0, args, is_ref, true, false,
None, level,
)?;
} }
// Error // Any other error - return
err => return Err(Box::new(err)), err => return Err(Box::new(err)),
}, },
};
#[cfg(not(feature = "no_index"))]
if let Some(mut new_val) = _call_setter {
let val = target.as_mut();
let val_type_name = val.type_name();
let args = &mut [val, &mut idx_val2, &mut new_val];
self.exec_fn_call(
state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, None,
level,
)
.map_err(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(_, _) => {
EvalAltResult::ErrorIndexingType(
self.map_type_name(val_type_name).into(),
Position::none(),
)
}
err => err,
})?;
} }
Ok(Default::default()) Ok(Default::default())
} }
// xxx[rhs] // xxx[rhs]
_ => self _ => self
.get_indexed_mut(state, lib, target, idx_val, pos, false, level) .get_indexed_mut(state, lib, target, idx_val, pos, false, true, level)
.map(|v| (v.clone_into_dynamic(), false)), .map(|v| (v.clone_into_dynamic(), false)),
} }
} }
@ -732,7 +800,7 @@ impl Engine {
match rhs { match rhs {
// xxx.fn_name(arg_expr_list) // xxx.fn_name(arg_expr_list)
Expr::FnCall(x) if x.1.is_none() => { Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
self.make_method_call( self.make_method_call(
state, lib, name, *hash, target, idx_val, *def_val, *native, false, state, lib, name, *hash, target, idx_val, *def_val, *native, false,
level, level,
@ -745,8 +813,8 @@ impl Engine {
Expr::Property(x) if target.is::<Map>() && _new_val.is_some() => { Expr::Property(x) if target.is::<Map>() && _new_val.is_some() => {
let ((prop, _, _), pos) = x.as_ref(); let ((prop, _, _), pos) = x.as_ref();
let index = prop.clone().into(); let index = prop.clone().into();
let mut val = let mut val = self
self.get_indexed_mut(state, lib, target, index, *pos, true, level)?; .get_indexed_mut(state, lib, target, index, *pos, true, false, level)?;
val.set_value(_new_val.unwrap()) val.set_value(_new_val.unwrap())
.map_err(|err| err.new_position(rhs.position()))?; .map_err(|err| err.new_position(rhs.position()))?;
@ -756,8 +824,9 @@ impl Engine {
Expr::Property(x) if target.is::<Map>() => { Expr::Property(x) if target.is::<Map>() => {
let ((prop, _, _), pos) = x.as_ref(); let ((prop, _, _), pos) = x.as_ref();
let index = prop.clone().into(); let index = prop.clone().into();
let val = let val = self.get_indexed_mut(
self.get_indexed_mut(state, lib, target, index, *pos, false, level)?; state, lib, target, index, *pos, false, false, level,
)?;
Ok((val.clone_into_dynamic(), false)) Ok((val.clone_into_dynamic(), false))
} }
@ -766,7 +835,7 @@ impl Engine {
let ((_, _, setter), pos) = x.as_ref(); let ((_, _, setter), pos) = x.as_ref();
let mut args = [target.as_mut(), _new_val.as_mut().unwrap()]; let mut args = [target.as_mut(), _new_val.as_mut().unwrap()];
self.exec_fn_call( self.exec_fn_call(
state, lib, setter, true, 0, &mut args, is_ref, true, false, None, state, lib, setter, 0, &mut args, is_ref, true, false, None, None,
level, level,
) )
.map(|(v, _)| (v, true)) .map(|(v, _)| (v, true))
@ -777,7 +846,7 @@ impl Engine {
let ((_, getter, _), pos) = x.as_ref(); let ((_, getter, _), pos) = x.as_ref();
let mut args = [target.as_mut()]; let mut args = [target.as_mut()];
self.exec_fn_call( self.exec_fn_call(
state, lib, getter, true, 0, &mut args, is_ref, true, false, None, state, lib, getter, 0, &mut args, is_ref, true, false, None, None,
level, level,
) )
.map(|(v, _)| (v, false)) .map(|(v, _)| (v, false))
@ -791,11 +860,13 @@ impl Engine {
Expr::Property(p) => { Expr::Property(p) => {
let ((prop, _, _), pos) = p.as_ref(); let ((prop, _, _), pos) = p.as_ref();
let index = prop.clone().into(); let index = prop.clone().into();
self.get_indexed_mut(state, lib, target, index, *pos, false, level)? self.get_indexed_mut(
state, lib, target, index, *pos, false, true, level,
)?
} }
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
Expr::FnCall(x) if x.1.is_none() => { Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
let (val, _) = self let (val, _) = self
.make_method_call( .make_method_call(
state, lib, name, *hash, target, idx_val, *def_val, state, lib, name, *hash, target, idx_val, *def_val,
@ -828,18 +899,24 @@ impl Engine {
let args = &mut arg_values[..1]; let args = &mut arg_values[..1];
let (mut val, updated) = self let (mut val, updated) = self
.exec_fn_call( .exec_fn_call(
state, lib, getter, true, 0, args, is_ref, true, false, state, lib, getter, 0, args, is_ref, true, false, None,
None, level, None, level,
) )
.map_err(|err| err.new_position(*pos))?; .map_err(|err| err.new_position(*pos))?;
let val = &mut val; let val = &mut val;
let target = &mut val.into();
let (result, may_be_changed) = self let (result, may_be_changed) = self
.eval_dot_index_chain_helper( .eval_dot_index_chain_helper(
state, lib, this_ptr, target, expr, idx_values, next_chain, state,
level, _new_val, lib,
this_ptr,
&mut val.into(),
expr,
idx_values,
next_chain,
level,
_new_val,
) )
.map_err(|err| err.new_position(*pos))?; .map_err(|err| err.new_position(*pos))?;
@ -848,8 +925,8 @@ impl Engine {
// Re-use args because the first &mut parameter will not be consumed // Re-use args because the first &mut parameter will not be consumed
arg_values[1] = val; arg_values[1] = val;
self.exec_fn_call( self.exec_fn_call(
state, lib, setter, true, 0, arg_values, is_ref, true, state, lib, setter, 0, arg_values, is_ref, true, false,
false, None, level, None, None, level,
) )
.or_else( .or_else(
|err| match *err { |err| match *err {
@ -866,7 +943,7 @@ impl Engine {
} }
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
Expr::FnCall(x) if x.1.is_none() => { Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, pos), _, hash, _, def_val) = x.as_ref(); let ((name, native, _, pos), _, hash, _, def_val) = x.as_ref();
let (mut val, _) = self let (mut val, _) = self
.make_method_call( .make_method_call(
state, lib, name, *hash, target, idx_val, *def_val, state, lib, name, *hash, target, idx_val, *def_val,
@ -1058,6 +1135,7 @@ impl Engine {
mut _idx: Dynamic, mut _idx: Dynamic,
idx_pos: Position, idx_pos: Position,
_create: bool, _create: bool,
_indexers: bool,
_level: usize, _level: usize,
) -> Result<Target<'a>, Box<EvalAltResult>> { ) -> Result<Target<'a>, Box<EvalAltResult>> {
self.inc_operations(state)?; self.inc_operations(state)?;
@ -1102,10 +1180,10 @@ impl Engine {
map.entry(index).or_insert(Default::default()).into() map.entry(index).or_insert(Default::default()).into()
} else { } else {
let index = _idx let index = _idx
.downcast_ref::<ImmutableString>() .read_lock::<ImmutableString>()
.ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; .ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?;
map.get_mut(index.as_str()) map.get_mut(&*index)
.map(Target::from) .map(Target::from)
.unwrap_or_else(|| Target::from(())) .unwrap_or_else(|| Target::from(()))
}) })
@ -1134,11 +1212,11 @@ impl Engine {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
_ => { _ if _indexers => {
let type_name = val.type_name(); let type_name = val.type_name();
let args = &mut [val, &mut _idx]; let args = &mut [val, &mut _idx];
self.exec_fn_call( self.exec_fn_call(
state, _lib, FN_IDX_GET, true, 0, args, is_ref, true, false, None, _level, state, _lib, FN_IDX_GET, 0, args, is_ref, true, false, None, None, _level,
) )
.map(|(v, _)| v.into()) .map(|(v, _)| v.into())
.map_err(|err| match *err { .map_err(|err| match *err {
@ -1149,7 +1227,6 @@ impl Engine {
}) })
} }
#[cfg(any(feature = "no_index", feature = "no_object"))]
_ => Err(Box::new(EvalAltResult::ErrorIndexingType( _ => Err(Box::new(EvalAltResult::ErrorIndexingType(
self.map_type_name(val.type_name()).into(), self.map_type_name(val.type_name()).into(),
Position::none(), Position::none(),
@ -1179,26 +1256,23 @@ impl Engine {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(mut rhs_value)) => { Dynamic(Union::Array(mut rhs_value)) => {
let op = "=="; let op = "==";
let mut scope = Scope::new();
// Call the `==` operator to compare each value // Call the `==` operator to compare each value
for value in rhs_value.iter_mut() { for value in rhs_value.iter_mut() {
let def_value = Some(false); let def_value = Some(false);
let args = &mut [&mut lhs_value.clone(), value]; let args = &mut [&mut lhs_value.clone(), value];
let hashes = ( // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. let hash =
calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())), calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id()));
0,
);
let (r, _) = self if self
.call_fn_raw( .call_native_fn(state, lib, op, hash, args, false, false, def_value)
&mut scope, mods, state, lib, op, hashes, args, false, false, false, .map_err(|err| err.new_position(rhs.position()))?
def_value, level, .0
) .as_bool()
.map_err(|err| err.new_position(rhs.position()))?; .unwrap_or(false)
if r.as_bool().unwrap_or(false) { {
return Ok(true.into()); return Ok(true.into());
} }
} }
@ -1208,10 +1282,8 @@ impl Engine {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(rhs_value)) => match lhs_value { Dynamic(Union::Map(rhs_value)) => match lhs_value {
// Only allows String or char // Only allows String or char
Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(s.as_str()).into()), Dynamic(Union::Str(s)) => Ok(rhs_value.contains_key(&s).into()),
Dynamic(Union::Char(c)) => { Dynamic(Union::Char(c)) => Ok(rhs_value.contains_key(&c.to_string()).into()),
Ok(rhs_value.contains_key(c.to_string().as_str()).into())
}
_ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))), _ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))),
}, },
Dynamic(Union::Str(rhs_value)) => match lhs_value { Dynamic(Union::Str(rhs_value)) => match lhs_value {
@ -1251,7 +1323,7 @@ impl Engine {
if let Some(val) = this_ptr { if let Some(val) = this_ptr {
Ok(val.clone()) Ok(val.clone())
} else { } else {
Err(Box::new(EvalAltResult::ErrorUnboundedThis((x.0).1))) Err(Box::new(EvalAltResult::ErrorUnboundThis((x.0).1)))
} }
} }
Expr::Variable(_) => { Expr::Variable(_) => {
@ -1280,7 +1352,12 @@ impl Engine {
)), )),
// Normal assignment // Normal assignment
ScopeEntryType::Normal if op.is_empty() => { ScopeEntryType::Normal if op.is_empty() => {
*lhs_ptr = rhs_val; let rhs_val = rhs_val.clone_inner_data().unwrap();
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
*lhs_ptr.write_lock::<Dynamic>().unwrap() = rhs_val;
} else {
*lhs_ptr = rhs_val;
}
Ok(Default::default()) Ok(Default::default())
} }
// Op-assignment - in order of precedence: // Op-assignment - in order of precedence:
@ -1298,23 +1375,36 @@ impl Engine {
.get_fn(hash_fn, false) .get_fn(hash_fn, false)
.or_else(|| self.packages.get_fn(hash_fn, false)) .or_else(|| self.packages.get_fn(hash_fn, false))
{ {
// Overriding exact implementation if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
func(self, lib, &mut [lhs_ptr, &mut rhs_val])?; let mut lock_guard = lhs_ptr.write_lock::<Dynamic>().unwrap();
let lhs_ptr_inner = lock_guard.deref_mut();
// Overriding exact implementation
func(self, lib, &mut [lhs_ptr_inner, &mut rhs_val])?;
} else {
// Overriding exact implementation
func(self, lib, &mut [lhs_ptr, &mut rhs_val])?;
}
} else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() {
// Not built in, map to `var = var op rhs` // Not built in, map to `var = var op rhs`
let op = &op[..op.len() - 1]; // extract operator without = let op = &op[..op.len() - 1]; // extract operator without =
let hash = calc_fn_hash(empty(), op, 2, empty());
// Clone the LHS value // Clone the LHS value
let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val]; let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val];
// Run function // Run function
let (value, _) = self let (value, _) = self
.exec_fn_call( .exec_fn_call(
state, lib, op, true, hash, args, false, false, false, None, state, lib, op, 0, args, false, false, false, None, None, level,
level,
) )
.map_err(|err| err.new_position(*op_pos))?; .map_err(|err| err.new_position(*op_pos))?;
// Set value to LHS
*lhs_ptr = value; let value = value.clone_inner_data().unwrap();
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
*lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
} else {
*lhs_ptr = value;
}
} }
Ok(Default::default()) Ok(Default::default())
} }
@ -1333,13 +1423,12 @@ impl Engine {
} else { } else {
// Op-assignment - always map to `lhs = lhs op rhs` // Op-assignment - always map to `lhs = lhs op rhs`
let op = &op[..op.len() - 1]; // extract operator without = let op = &op[..op.len() - 1]; // extract operator without =
let hash = calc_fn_hash(empty(), op, 2, empty());
let args = &mut [ let args = &mut [
&mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?, &mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?,
&mut rhs_val, &mut rhs_val,
]; ];
self.exec_fn_call( 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(|(v, _)| v)
.map_err(|err| err.new_position(*op_pos))? .map_err(|err| err.new_position(*op_pos))?
@ -1409,20 +1498,20 @@ impl Engine {
// Normal function call // Normal function call
Expr::FnCall(x) if x.1.is_none() => { Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref(); let ((name, native, capture, pos), _, hash, args_expr, def_val) = x.as_ref();
self.make_function_call( self.make_function_call(
scope, mods, state, lib, this_ptr, name, args_expr, *def_val, *hash, *native, scope, mods, state, lib, this_ptr, name, args_expr, *def_val, *hash, *native,
false, level, false, *capture, level,
) )
.map_err(|err| err.new_position(*pos)) .map_err(|err| err.new_position(*pos))
} }
// Module-qualified function call // Module-qualified function call
Expr::FnCall(x) if x.1.is_some() => { Expr::FnCall(x) if x.1.is_some() => {
let ((name, _, pos), modules, hash, args_expr, def_val) = x.as_ref(); let ((name, _, capture, pos), modules, hash, args_expr, def_val) = x.as_ref();
self.make_qualified_function_call( self.make_qualified_function_call(
scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash, scope, mods, state, lib, this_ptr, modules, name, args_expr, *def_val, *hash,
level, *capture, level,
) )
.map_err(|err| err.new_position(*pos)) .map_err(|err| err.new_position(*pos))
} }
@ -1612,7 +1701,15 @@ impl Engine {
state.scope_level += 1; state.scope_level += 1;
for loop_var in func(iter_type) { for loop_var in func(iter_type) {
*scope.get_mut(index).0 = loop_var; let for_var = scope.get_mut(index).0;
let value = loop_var.clone_inner_data().unwrap();
if cfg!(not(feature = "no_closure")) && for_var.is_shared() {
*for_var.write_lock().unwrap() = value;
} else {
*for_var = value;
}
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| err.new_position(stmt.position()))?; .map_err(|err| err.new_position(stmt.position()))?;
@ -1675,7 +1772,10 @@ impl Engine {
Stmt::Let(x) if x.1.is_some() => { Stmt::Let(x) if x.1.is_some() => {
let ((var_name, _), expr, _) = x.as_ref(); let ((var_name, _), expr, _) = x.as_ref();
let expr = expr.as_ref().unwrap(); 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); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false); scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false);
Ok(Default::default()) Ok(Default::default())
@ -1691,7 +1791,10 @@ impl Engine {
// Const statement // Const statement
Stmt::Const(x) if x.1.is_constant() => { Stmt::Const(x) if x.1.is_constant() => {
let ((var_name, _), expr, _) = x.as_ref(); let ((var_name, _), expr, _) = x.as_ref();
let val = self.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?; let val = self
.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?
.clone_inner_data()
.unwrap();
let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state);
scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true); scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true);
Ok(Default::default()) Ok(Default::default())
@ -1751,19 +1854,40 @@ impl Engine {
} }
Ok(Default::default()) Ok(Default::default())
} }
// Share statement
#[cfg(not(feature = "no_closure"))]
Stmt::Share(x) => {
let (var_name, _) = x.as_ref();
match scope.get_index(var_name) {
Some((index, ScopeEntryType::Normal)) => {
let (val, _) = scope.get_mut(index);
if !val.is_shared() {
// Replace the variable with a shared value.
*val = mem::take(val).into_shared();
}
}
_ => (),
}
Ok(Default::default())
}
}; };
self.check_data_size(result) self.check_data_size(result)
.map_err(|err| err.new_position(stmt.position())) .map_err(|err| err.new_position(stmt.position()))
} }
/// Check a result to ensure that the data size is within allowable limit.
/// Position in `EvalAltResult` may be None and should be set afterwards.
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
#[inline(always)] #[inline(always)]
fn check_data_size( fn check_data_size(
&self, &self,
result: Result<Dynamic, Box<EvalAltResult>>, result: Result<Dynamic, Box<EvalAltResult>>,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
return result; result
} }
/// Check a result to ensure that the data size is within allowable limit. /// Check a result to ensure that the data size is within allowable limit.
@ -1773,9 +1897,6 @@ impl Engine {
&self, &self,
result: Result<Dynamic, Box<EvalAltResult>>, result: Result<Dynamic, Box<EvalAltResult>>,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
#[cfg(feature = "unchecked")]
return result;
// If no data size limits, just return // If no data size limits, just return
if self.limits.max_string_size + self.limits.max_array_size + self.limits.max_map_size == 0 if self.limits.max_string_size + self.limits.max_array_size + self.limits.max_map_size == 0
{ {

View File

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

View File

@ -4,8 +4,8 @@ use crate::any::Dynamic;
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{ use crate::engine::{
search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG, 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_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_SHARED,
KEYWORD_TYPE_OF, KEYWORD_PRINT, KEYWORD_TYPE_OF,
}; };
use crate::error::ParseErrorType; use crate::error::ParseErrorType;
use crate::fn_native::{FnCallArgs, FnPtr}; use crate::fn_native::{FnCallArgs, FnPtr};
@ -14,6 +14,7 @@ use crate::optimize::OptimizationLevel;
use crate::parser::{Expr, ImmutableString, AST, INT}; use crate::parser::{Expr, ImmutableString, AST, INT};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::Scope;
use crate::stdlib::ops::Deref;
use crate::token::Position; use crate::token::Position;
use crate::utils::StaticVec; use crate::utils::StaticVec;
@ -32,6 +33,9 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET};
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::{Map, Target, FN_GET, FN_SET}; use crate::engine::{Map, Target, FN_GET, FN_SET};
#[cfg(not(feature = "no_closure"))]
use crate::scope::Entry as ScopeEntry;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
boxed::Box, boxed::Box,
@ -43,6 +47,9 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
#[cfg(not(feature = "no_closure"))]
use crate::stdlib::{collections::HashSet, string::String};
/// Extract the property name from a getter function name. /// Extract the property name from a getter function name.
#[inline(always)] #[inline(always)]
fn extract_prop_from_getter(_fn_name: &str) -> Option<&str> { 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 None
} }
/// This function replaces the first argument of a method call with a clone copy. /// A type that temporarily stores a mutable reference to a `Dynamic`,
/// This is to prevent a pure function unintentionally consuming the first argument. /// replacing it with a cloned copy.
fn normalize_first_arg<'a>( #[derive(Debug, Default)]
normalize: bool, struct ArgBackup<'a> {
this_copy: &mut Dynamic, orig_mut: Option<&'a mut Dynamic>,
old_this_ptr: &mut Option<&'a mut Dynamic>, value_copy: 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);
} }
/// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`. impl<'a> ArgBackup<'a> {
fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) { /// This function replaces the first argument of a method call with a clone copy.
if let Some(this_pointer) = old_this_ptr { /// This is to prevent a pure function unintentionally consuming the first argument.
args[0] = this_pointer; ///
/// `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<String>,
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<EvalAltResult>> {
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 { 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. /// Position in `EvalAltResult` is `None` and must be set afterwards.
/// ///
/// ## WARNING /// ## WARNING
@ -114,94 +194,33 @@ impl Engine {
/// Function call arguments be _consumed_ when the function requires them to be passed by value. /// 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. /// 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 `()`! /// **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, &self,
_scope: &mut Scope,
_mods: &mut Imports,
state: &mut State, state: &mut State,
lib: &Module, lib: &Module,
fn_name: &str, fn_name: &str,
(hash_fn, hash_script): (u64, u64), hash_fn: u64,
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref: bool, is_ref: bool,
_is_method: bool,
pub_only: bool, pub_only: bool,
def_val: Option<bool>, def_val: Option<bool>,
_level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
self.inc_operations(state)?; self.inc_operations(state)?;
let native_only = hash_script == 0; // Search for the native function
// First search registered functions (can override packages)
// 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)
// Then search packages // Then search packages
// NOTE: We skip script functions for global_module and packages, and native functions for lib let func = self
let func = if !native_only { .global_module
lib.get_fn(hash_script, pub_only) //.or_else(|| lib.get_fn(hash_fn, pub_only)) .get_fn(hash_fn, pub_only)
} else { .or_else(|| self.packages.get_fn(hash_fn, pub_only));
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));
if let Some(func) = func { if let Some(func) = func {
#[cfg(not(feature = "no_function"))] assert!(func.is_native());
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();
// Calling pure function but the first argument is a reference? // Calling pure function but the first argument is a reference?
normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args); let mut backup: ArgBackup = Default::default();
backup.change_first_arg_to_copy(is_ref && func.is_pure(), 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))
};
}
// Run external function // Run external function
let result = if func.is_plugin_fn() { let result = if func.is_plugin_fn() {
@ -211,7 +230,9 @@ impl Engine {
}; };
// Restore the original reference // 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) // See if the function match print/debug (which requires special processing)
return Ok(match fn_name { return Ok(match fn_name {
@ -343,6 +364,17 @@ impl Engine {
args: &mut FnCallArgs, args: &mut FnCallArgs,
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
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; let orig_scope_level = state.scope_level;
state.scope_level += 1; state.scope_level += 1;
@ -405,7 +437,7 @@ impl Engine {
|| self.packages.contains_fn(hash_fn, pub_only) || self.packages.contains_fn(hash_fn, pub_only)
} }
/// Perform an actual function call, taking care of special functions /// Perform an actual function call, native Rust or scripted, taking care of special functions.
/// Position in `EvalAltResult` is `None` and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
/// ///
/// ## WARNING /// ## WARNING
@ -418,24 +450,28 @@ impl Engine {
state: &mut State, state: &mut State,
lib: &Module, lib: &Module,
fn_name: &str, fn_name: &str,
native_only: bool,
hash_script: u64, hash_script: u64,
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref: bool, is_ref: bool,
is_method: bool, is_method: bool,
pub_only: bool, pub_only: bool,
_capture: Option<Scope>,
def_val: Option<bool>, def_val: Option<bool>,
level: usize, level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
// 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. // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
let arg_types = args.iter().map(|a| a.type_id()); let arg_types = args.iter().map(|a| a.type_id());
let hash_fn = calc_fn_hash(empty(), fn_name, args.len(), arg_types); 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 { match fn_name {
// type_of // type_of
KEYWORD_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(( Ok((
self.map_type_name(args[0].type_name()).to_string().into(), self.map_type_name(args[0].type_name()).to_string().into(),
@ -445,7 +481,7 @@ impl Engine {
// Fn // Fn
KEYWORD_FN_PTR 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( Err(Box::new(EvalAltResult::ErrorRuntime(
"'Fn' should not be called in method style. Try Fn(...);".into(), "'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 // eval - reaching this point it must be a method-style call
KEYWORD_EVAL 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( Err(Box::new(EvalAltResult::ErrorRuntime(
"'eval' should not be called in method style. Try eval(...);".into(), "'eval' should not be called in method style. Try eval(...);".into(),
@ -463,15 +499,57 @@ impl Engine {
))) )))
} }
// Normal function call // Normal script function call
_ => { #[cfg(not(feature = "no_function"))]
let mut scope = Scope::new(); _ if hash_script > 0 && lib.contains_fn(hash_script, pub_only) => {
let mut mods = Imports::new(); // Get scripted function
self.call_fn_raw( let func = lib.get_fn(hash_script, pub_only).unwrap().get_fn_def();
&mut scope, &mut mods, state, lib, fn_name, hashes, args, is_ref, is_method,
pub_only, def_val, level, 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, mods: &mut Imports,
state: &mut State, state: &mut State,
lib: &Module, lib: &Module,
script: &Dynamic, script_expr: &Dynamic,
_level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
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( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(), self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(), typ.into(),
@ -526,7 +616,7 @@ impl Engine {
state: &mut State, state: &mut State,
lib: &Module, lib: &Module,
name: &str, name: &str,
hash: u64, hash_script: u64,
target: &mut Target, target: &mut Target,
idx_val: Dynamic, idx_val: Dynamic,
def_val: Option<bool>, def_val: Option<bool>,
@ -544,12 +634,16 @@ impl Engine {
let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() { let (result, updated) = if _fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
// FnPtr call // FnPtr call
let fn_ptr = obj.downcast_ref::<FnPtr>().unwrap(); let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>(); let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
// Redirect function name // Redirect function name
let fn_name = fn_ptr.fn_name(); let fn_name = fn_ptr.fn_name();
// Recalculate hash // 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 // Arguments are passed as-is, adding the curried arguments
let mut arg_values = curry let mut arg_values = curry
.iter_mut() .iter_mut()
@ -559,7 +653,7 @@ impl Engine {
// Map it to name(args) in function-call style // Map it to name(args) in function-call style
self.exec_fn_call( 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>() { } else if _fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::<FnPtr>() {
// FnPtr call on object // FnPtr call on object
@ -568,7 +662,11 @@ impl Engine {
// Redirect function name // Redirect function name
let fn_name = fn_ptr.get_fn_name().clone(); let fn_name = fn_ptr.get_fn_name().clone();
// Recalculate hash // 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 // Replace the first argument with the object pointer, adding the curried arguments
let mut arg_values = once(obj) let mut arg_values = once(obj)
.chain(curry.iter_mut()) .chain(curry.iter_mut())
@ -578,11 +676,11 @@ impl Engine {
// Map it to name(args) in function-call style // Map it to name(args) in function-call style
self.exec_fn_call( 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::<FnPtr>() { } else if _fn_name == KEYWORD_FN_PTR_CURRY && obj.is::<FnPtr>() {
// Curry call // Curry call
let fn_ptr = obj.downcast_ref::<FnPtr>().unwrap(); let fn_ptr = obj.read_lock::<FnPtr>().unwrap();
Ok(( Ok((
FnPtr::new_unchecked( FnPtr::new_unchecked(
fn_ptr.get_fn_name().clone(), fn_ptr.get_fn_name().clone(),
@ -596,16 +694,22 @@ impl Engine {
.into(), .into(),
false, 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 { } else {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
let redirected; let redirected;
let mut _hash = hash; let mut _hash = hash_script;
// Check if it is a map method call in OOP style // Check if it is a map method call in OOP style
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if let Some(map) = obj.downcast_ref::<Map>() { if let Some(map) = obj.read_lock::<Map>() {
if let Some(val) = map.get(_fn_name) { if let Some(val) = map.get(_fn_name) {
if let Some(f) = val.downcast_ref::<FnPtr>() { if let Some(f) = val.read_lock::<FnPtr>() {
// Remap the function name // Remap the function name
redirected = f.get_fn_name().clone(); redirected = f.get_fn_name().clone();
_fn_name = &redirected; _fn_name = &redirected;
@ -615,12 +719,16 @@ impl Engine {
} }
}; };
if native {
_hash = 0;
}
// Attached object pointer in front of the arguments // Attached object pointer in front of the arguments
let mut arg_values = once(obj).chain(idx.iter_mut()).collect::<StaticVec<_>>(); let mut arg_values = once(obj).chain(idx.iter_mut()).collect::<StaticVec<_>>();
let args = arg_values.as_mut(); let args = arg_values.as_mut();
self.exec_fn_call( 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, name: &str,
args_expr: &[Expr], args_expr: &[Expr],
def_val: Option<bool>, def_val: Option<bool>,
mut hash: u64, mut hash_script: u64,
native: bool, native: bool,
pub_only: bool, pub_only: bool,
capture: bool,
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
// Handle Fn() // Handle Fn()
if name == KEYWORD_FN_PTR && args_expr.len() == 1 { if name == KEYWORD_FN_PTR && args_expr.len() == 1 {
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>())); let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
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 // Fn - only in function call style
let expr = args_expr.get(0).unwrap(); let expr = args_expr.get(0).unwrap();
let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; 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)) .and_then(|s| FnPtr::try_from(s))
.map(Into::<Dynamic>::into); .map(Into::<Dynamic>::into)
.map_err(|err| err.new_position(expr.position()));
} }
} }
@ -701,27 +811,12 @@ impl Engine {
.into()); .into());
} }
// Handle eval() // Handle is_shared()
if name == KEYWORD_EVAL && args_expr.len() == 1 { if cfg!(not(feature = "no_closure")) && name == KEYWORD_IS_SHARED && args_expr.len() == 1 {
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>())); 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) { return Ok(value.is_shared().into());
// 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;
}
} }
// Handle call() - Redirect function call // Handle call() - Redirect function call
@ -732,7 +827,7 @@ impl Engine {
if name == KEYWORD_FN_PTR_CALL if name == KEYWORD_FN_PTR_CALL
&& args_expr.len() >= 1 && 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 expr = args_expr.get(0).unwrap();
let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
@ -746,7 +841,7 @@ impl Engine {
// Skip the first argument // Skip the first argument
args_expr = &args_expr.as_ref()[1..]; args_expr = &args_expr.as_ref()[1..];
// Recalculate hash // 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 { } else {
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType( return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<FnPtr>()).into(), self.map_type_name(type_name::<FnPtr>()).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::<ImmutableString>()));
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 arg_values: StaticVec<_>;
let mut args: StaticVec<_>; let mut args: StaticVec<_>;
let mut is_ref = false; 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() { if args_expr.is_empty() && curry.is_empty() {
// No arguments // No arguments
args = Default::default(); args = Default::default();
} else { } 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 // in order to leverage potential &mut first argument and avoid cloning the value
match args_expr.get(0).unwrap() { match args_expr.get(0).unwrap() {
// func(x, ...) -> x.func(...) // func(x, ...) -> x.func(...)
lhs @ Expr::Variable(_) => { lhs @ Expr::Variable(_) if curry.is_empty() => {
arg_values = args_expr arg_values = args_expr
.iter() .iter()
.skip(1) .skip(1)
@ -781,12 +904,14 @@ impl Engine {
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| err.new_position(pos))?; .map_err(|err| err.new_position(pos))?;
args = once(target) // Turn it into a method call only if the object is not shared
.chain(curry.iter_mut()) args = if target.is_shared() {
.chain(arg_values.iter_mut()) arg_values.insert(0, target.clone().clone_inner_data().unwrap());
.collect(); arg_values.iter_mut().collect()
} else {
is_ref = true; is_ref = true;
once(target).chain(arg_values.iter_mut()).collect()
};
} }
// func(..., ...) // func(..., ...)
_ => { _ => {
@ -800,9 +925,11 @@ impl Engine {
} }
} }
let hash = if native { 0 } else { hash_script };
let args = args.as_mut(); let args = args.as_mut();
self.exec_fn_call( 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) .map(|(v, _)| v)
} }
@ -821,10 +948,10 @@ impl Engine {
args_expr: &[Expr], args_expr: &[Expr],
def_val: Option<bool>, def_val: Option<bool>,
hash_script: u64, hash_script: u64,
_capture: bool,
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let modules = modules.as_ref().unwrap(); let modules = modules.as_ref().unwrap();
let mut arg_values: StaticVec<_>; let mut arg_values: StaticVec<_>;
let mut args: StaticVec<_>; let mut args: StaticVec<_>;
@ -890,12 +1017,22 @@ impl Engine {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Some(f) if f.is_script() => { Some(f) if f.is_script() => {
let args = args.as_mut(); let args = args.as_mut();
let fn_def = f.get_fn_def(); let func = f.get_fn_def();
let mut scope = Scope::new();
let mut mods = Imports::new(); let scope = &mut Scope::new();
self.call_script_fn( let mods = &mut Imports::new();
&mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level,
) // 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) 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()), 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::<INT>(); let x = x.clone().cast::<INT>();
let y = y.clone().cast::<INT>(); let y = y.clone().cast::<INT>();
#[cfg(not(feature = "unchecked"))] if cfg!(not(feature = "unchecked")) {
match op { match op {
"+" => return add(x, y).map(Into::into).map(Some), "+" => return add(x, y).map(Into::into).map(Some),
"-" => return sub(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 mul(x, y).map(Into::into).map(Some),
"/" => return div(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 modulo(x, y).map(Into::into).map(Some),
"~" => return pow_i_i(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 shr(x, y).map(Into::into).map(Some),
"<<" => return shl(x, y).map(Into::into).map(Some), "<<" => return shl(x, y).map(Into::into).map(Some),
_ => (), _ => (),
} }
} else {
#[cfg(feature = "unchecked")] match op {
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 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 pow_i_i_u(x, y).map(Into::into).map(Some), ">>" => return shr_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),
"<<" => return shl_u(x, y).map(Into::into).map(Some), _ => (),
_ => (), }
} }
match op { match op {
@ -989,8 +1126,8 @@ pub fn run_builtin_binary_op(
_ => (), _ => (),
} }
} else if args_type == TypeId::of::<ImmutableString>() { } else if args_type == TypeId::of::<ImmutableString>() {
let x = x.downcast_ref::<ImmutableString>().unwrap(); let x = &*x.read_lock::<ImmutableString>().unwrap();
let y = y.downcast_ref::<ImmutableString>().unwrap(); let y = &*y.read_lock::<ImmutableString>().unwrap();
match op { match op {
"+" => return Ok(Some((x + y).into())), "+" => return Ok(Some((x + y).into())),
@ -1063,33 +1200,33 @@ pub fn run_builtin_op_assignment(
} }
if args_type == TypeId::of::<INT>() { if args_type == TypeId::of::<INT>() {
let x = x.downcast_mut::<INT>().unwrap();
let y = y.clone().cast::<INT>(); let y = y.clone().cast::<INT>();
let mut x = x.write_lock::<INT>().unwrap();
#[cfg(not(feature = "unchecked"))] if cfg!(not(feature = "unchecked")) {
match op { match op {
"+=" => return Ok(Some(*x = add(*x, y)?)), "+=" => return Ok(Some(*x = add(*x, y)?)),
"-=" => return Ok(Some(*x = sub(*x, y)?)), "-=" => return Ok(Some(*x = sub(*x, y)?)),
"*=" => return Ok(Some(*x = mul(*x, y)?)), "*=" => return Ok(Some(*x = mul(*x, y)?)),
"/=" => return Ok(Some(*x = div(*x, y)?)), "/=" => return Ok(Some(*x = div(*x, y)?)),
"%=" => return Ok(Some(*x = modulo(*x, y)?)), "%=" => return Ok(Some(*x = modulo(*x, y)?)),
"~=" => return Ok(Some(*x = pow_i_i(*x, y)?)), "~=" => return Ok(Some(*x = pow_i_i(*x, y)?)),
">>=" => return Ok(Some(*x = shr(*x, y)?)), ">>=" => return Ok(Some(*x = shr(*x, y)?)),
"<<=" => return Ok(Some(*x = shl(*x, y)?)), "<<=" => return Ok(Some(*x = shl(*x, y)?)),
_ => (), _ => (),
} }
} else {
#[cfg(feature = "unchecked")] match op {
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 *= 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 = pow_i_i_u(*x, y)?)), ">>=" => return Ok(Some(*x = shr_u(*x, y)?)),
">>=" => return Ok(Some(*x = shr_u(*x, y)?)), "<<=" => return Ok(Some(*x = shl_u(*x, y)?)),
"<<=" => return Ok(Some(*x = shl_u(*x, y)?)), _ => (),
_ => (), }
} }
match op { match op {
@ -1099,8 +1236,8 @@ pub fn run_builtin_op_assignment(
_ => (), _ => (),
} }
} else if args_type == TypeId::of::<bool>() { } else if args_type == TypeId::of::<bool>() {
let x = x.downcast_mut::<bool>().unwrap();
let y = y.clone().cast::<bool>(); let y = y.clone().cast::<bool>();
let mut x = x.write_lock::<bool>().unwrap();
match op { match op {
"&=" => return Ok(Some(*x = *x && y)), "&=" => return Ok(Some(*x = *x && y)),
@ -1108,8 +1245,8 @@ pub fn run_builtin_op_assignment(
_ => (), _ => (),
} }
} else if args_type == TypeId::of::<ImmutableString>() { } else if args_type == TypeId::of::<ImmutableString>() {
let x = x.downcast_mut::<ImmutableString>().unwrap(); let y = y.read_lock::<ImmutableString>().unwrap().deref().clone();
let y = y.downcast_ref::<ImmutableString>().unwrap(); let mut x = x.write_lock::<ImmutableString>().unwrap();
match op { match op {
"+=" => return Ok(Some(*x += y)), "+=" => return Ok(Some(*x += y)),
@ -1119,8 +1256,8 @@ pub fn run_builtin_op_assignment(
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
if args_type == TypeId::of::<FLOAT>() { if args_type == TypeId::of::<FLOAT>() {
let x = x.downcast_mut::<FLOAT>().unwrap();
let y = y.clone().cast::<FLOAT>(); let y = y.clone().cast::<FLOAT>();
let mut x = x.write_lock::<FLOAT>().unwrap();
match op { match op {
"+=" => return Ok(Some(*x += y)), "+=" => return Ok(Some(*x += y)),

View File

@ -23,23 +23,43 @@ use crate::stdlib::rc::Rc;
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
use crate::stdlib::sync::Arc; use crate::stdlib::sync::Arc;
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
use crate::stdlib::cell::RefCell;
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
use crate::stdlib::sync::RwLock;
/// Trait that maps to `Send + Sync` only under the `sync` feature. /// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub trait SendSync: Send + Sync {} pub trait SendSync: Send + Sync {}
/// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
impl<T: Send + Sync> SendSync for T {} impl<T: Send + Sync> SendSync for T {}
/// Trait that maps to `Send + Sync` only under the `sync` feature. /// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub trait SendSync {} pub trait SendSync {}
/// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
impl<T> SendSync for T {} impl<T> SendSync for T {}
/// Immutable reference-counted container
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type Shared<T> = Rc<T>; pub type Shared<T> = Rc<T>;
/// Immutable reference-counted container
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type Shared<T> = Arc<T>; pub type Shared<T> = Arc<T>;
/// Mutable reference-counted container (read-write lock)
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
pub type SharedMut<T> = Shared<RefCell<T>>;
/// Mutable reference-counted container (read-write lock)
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
pub type SharedMut<T> = Shared<RwLock<T>>;
/// Consume a `Shared` resource and return a mutable reference to the wrapped value. /// Consume a `Shared` resource and return a mutable reference to the wrapped value.
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used. /// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T { pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
@ -131,13 +151,13 @@ impl FnPtr {
&mut Default::default(), &mut Default::default(),
lib.as_ref(), lib.as_ref(),
fn_name, fn_name,
false,
hash_script, hash_script,
args.as_mut(), args.as_mut(),
has_this, has_this,
has_this, has_this,
true, true,
None, None,
None,
0, 0,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
@ -297,6 +317,16 @@ impl CallableFunction {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false, 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. /// Get the access mode.
pub fn access(&self) -> FnAccess { pub fn access(&self) -> FnAccess {
match self { match self {

View File

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

View File

@ -442,7 +442,7 @@ impl Module {
/// // Since it is a primary type, it can also be cheaply copied /// // Since it is a primary type, it can also be cheaply copied
/// let double = args[1].clone().cast::<bool>(); /// let double = args[1].clone().cast::<bool>();
/// // Get a mutable reference to the first argument. /// // Get a mutable reference to the first argument.
/// let x = args[0].downcast_mut::<i64>().unwrap(); /// let mut x = args[0].write_lock::<i64>().unwrap();
/// ///
/// let orig = *x; /// let orig = *x;
/// ///
@ -534,7 +534,7 @@ impl Module {
func: impl Fn(&mut A) -> FuncReturn<T> + SendSync + 'static, func: impl Fn(&mut A) -> FuncReturn<T> + SendSync + 'static,
) -> u64 { ) -> u64 {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from) func(&mut args[0].write_lock::<A>().unwrap()).map(Dynamic::from)
}; };
let arg_types = [TypeId::of::<A>()]; let arg_types = [TypeId::of::<A>()];
self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f)))
@ -615,9 +615,9 @@ impl Module {
) -> u64 { ) -> u64 {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let a = args[0].downcast_mut::<A>().unwrap(); let mut a = args[0].write_lock::<A>().unwrap();
func(a, b).map(Dynamic::from) func(&mut a, b).map(Dynamic::from)
}; };
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f)))
@ -739,9 +739,9 @@ impl Module {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>(); let c = mem::take(args[2]).cast::<C>();
let a = args[0].downcast_mut::<A>().unwrap(); let mut a = args[0].write_lock::<A>().unwrap();
func(a, b, c).map(Dynamic::from) func(&mut a, b, c).map(Dynamic::from)
}; };
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f))) self.set_fn(name, Public, &arg_types, Func::from_method(Box::new(f)))
@ -773,9 +773,9 @@ impl Module {
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| { let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>(); let c = mem::take(args[2]).cast::<C>();
let a = args[0].downcast_mut::<A>().unwrap(); let mut a = args[0].write_lock::<A>().unwrap();
func(a, b, c).map(Dynamic::from) func(&mut a, b, c).map(Dynamic::from)
}; };
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn( self.set_fn(
@ -896,9 +896,9 @@ impl Module {
let b = mem::take(args[1]).cast::<B>(); let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>(); let c = mem::take(args[2]).cast::<C>();
let d = mem::take(args[3]).cast::<D>(); let d = mem::take(args[3]).cast::<D>();
let a = args[0].downcast_mut::<A>().unwrap(); let mut a = args[0].write_lock::<A>().unwrap();
func(a, b, c, d).map(Dynamic::from) func(&mut a, b, c, d).map(Dynamic::from)
}; };
let arg_types = [ let arg_types = [
TypeId::of::<A>(), TypeId::of::<A>(),

View File

@ -3,12 +3,12 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{ use crate::engine::{
Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
}; };
use crate::fn_native::FnPtr;
use crate::module::Module; use crate::module::Module;
use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST}; use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST};
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::token::is_valid_identifier;
use crate::utils::StaticVec; use crate::utils::StaticVec;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -19,6 +19,7 @@ use crate::parser::CustomExpr;
use crate::stdlib::{ use crate::stdlib::{
boxed::Box, boxed::Box,
convert::TryFrom,
iter::empty, iter::empty,
string::{String, ToString}, string::{String, ToString},
vec, vec,
@ -131,22 +132,18 @@ fn call_fn_with_constant_arguments(
state state
.engine .engine
.call_fn_raw( .call_native_fn(
&mut Scope::new(),
&mut Imports::new(),
&mut Default::default(), &mut Default::default(),
state.lib, state.lib,
fn_name, fn_name,
(hash_fn, 0), hash_fn,
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(), arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
false, false,
false,
true, true,
None, None,
0,
) )
.map(|(v, _)| Some(v)) .ok()
.unwrap_or_else(|_| None) .map(|(v, _)| v)
} }
/// Optimize a statement. /// Optimize a statement.
@ -418,7 +415,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// All other items can be thrown away. // All other items can be thrown away.
state.set_dirty(); state.set_dirty();
let pos = m.1; let pos = m.1;
m.0.into_iter().find(|((name, _), _)| name.as_str() == prop.as_str()) m.0.into_iter().find(|((name, _), _)| name == prop)
.map(|(_, mut expr)| { expr.set_position(pos); expr }) .map(|(_, mut expr)| { expr.set_position(pos); expr })
.unwrap_or_else(|| Expr::Unit(pos)) .unwrap_or_else(|| Expr::Unit(pos))
} }
@ -495,7 +492,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
state.set_dirty(); state.set_dirty();
let ch = a.0.to_string(); let ch = a.0.to_string();
if b.0.iter().find(|((name, _), _)| name.as_str() == ch.as_str()).is_some() { if b.0.iter().find(|((name, _), _)| name == &ch).is_some() {
Expr::True(a.1) Expr::True(a.1)
} else { } else {
Expr::False(a.1) Expr::False(a.1)
@ -553,14 +550,19 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// Fn("...") // Fn("...")
Expr::FnCall(x) Expr::FnCall(x)
if x.1.is_none() if x.1.is_none()
&& (x.0).0 == KEYWORD_FN_PTR && (x.0).0 == KEYWORD_FN_PTR
&& x.3.len() == 1 && x.3.len() == 1
&& matches!(x.3[0], Expr::StringConstant(_)) && matches!(x.3[0], Expr::StringConstant(_))
=> { => {
match &x.3[0] { if let Expr::StringConstant(s) = &x.3[0] {
Expr::StringConstant(s) if is_valid_identifier(s.0.chars()) => Expr::FnPointer(s.clone()), if let Ok(fn_ptr) = FnPtr::try_from(s.0.as_str()) {
_ => Expr::FnCall(x) Expr::FnPointer(Box::new((fn_ptr.take_data().0, s.1)))
} else {
Expr::FnCall(x)
}
} else {
unreachable!()
} }
} }
@ -570,21 +572,17 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
&& state.optimization_level == OptimizationLevel::Full // full optimizations && state.optimization_level == OptimizationLevel::Full // full optimizations
&& x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants && x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants
=> { => {
let ((name, _, pos), _, _, args, def_value) = x.as_mut(); let ((name, _, _, pos), _, _, args, def_value) = x.as_mut();
// First search in functions lib (can override built-in) // First search in functions lib (can override built-in)
// Cater for both normal function call style and method call style (one additional arguments) // Cater for both normal function call style and method call style (one additional arguments)
#[cfg(not(feature = "no_function"))] let has_script_fn = cfg!(not(feature = "no_function")) && state.lib.iter_fn().find(|(_, _, _, f)| {
let _has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| {
if !f.is_script() { return false; } if !f.is_script() { return false; }
let fn_def = f.get_fn_def(); let fn_def = f.get_fn_def();
fn_def.name.as_str() == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
}).is_some(); }).is_some();
#[cfg(feature = "no_function")] if has_script_fn {
let _has_script_fn: bool = false;
if _has_script_fn {
// A script-defined function overrides the built-in function - do not make the call // A script-defined function overrides the built-in function - do not make the call
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
return Expr::FnCall(x); return Expr::FnCall(x);
@ -746,11 +744,13 @@ pub fn optimize_into_ast(
_functions: Vec<ScriptFnDef>, _functions: Vec<ScriptFnDef>,
level: OptimizationLevel, level: OptimizationLevel,
) -> AST { ) -> AST {
#[cfg(feature = "no_optimize")] let level = if cfg!(feature = "no_optimize") {
const level: OptimizationLevel = OptimizationLevel::None; OptimizationLevel::None
} else {
level
};
#[cfg(not(feature = "no_function"))] let lib = if cfg!(not(feature = "no_function")) {
let lib = {
let mut module = Module::new(); let mut module = Module::new();
if !level.is_none() { if !level.is_none() {
@ -765,6 +765,8 @@ pub fn optimize_into_ast(
access: fn_def.access, access: fn_def.access,
body: Default::default(), body: Default::default(),
params: fn_def.params.clone(), params: fn_def.params.clone(),
#[cfg(not(feature = "no_closure"))]
externals: fn_def.externals.clone(),
pos: fn_def.pos, pos: fn_def.pos,
} }
.into() .into()
@ -810,11 +812,10 @@ pub fn optimize_into_ast(
} }
module module
} else {
Default::default()
}; };
#[cfg(feature = "no_function")]
let lib = Default::default();
AST::new( AST::new(
match level { match level {
OptimizationLevel::None => statements, OptimizationLevel::None => statements,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,9 +2,11 @@
use crate::any::{Dynamic, Union}; use crate::any::{Dynamic, Union};
use crate::calc_fn_hash; 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::error::{LexError, ParseError, ParseErrorType};
use crate::fn_native::Shared; use crate::fn_native::{FnPtr, Shared};
use crate::module::{Module, ModuleRef}; use crate::module::{Module, ModuleRef};
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::scope::{EntryType as ScopeEntryType, Scope};
@ -15,9 +17,6 @@ use crate::utils::{StaticVec, StraightHasherBuilder};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::engine::FN_ANONYMOUS; use crate::engine::FN_ANONYMOUS;
#[cfg(not(feature = "no_capture"))]
use crate::engine::KEYWORD_FN_PTR_CURRY;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::{make_getter, make_setter}; use crate::engine::{make_getter, make_setter};
@ -40,6 +39,9 @@ use crate::stdlib::{
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::stdlib::collections::hash_map::DefaultHasher; use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(not(feature = "no_closure"))]
use crate::stdlib::collections::HashSet;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use ahash::AHasher; use ahash::AHasher;
@ -355,7 +357,7 @@ impl fmt::Display for FnAccess {
/// ## WARNING /// ## WARNING
/// ///
/// This type is volatile and may change. /// This type is volatile and may change.
#[derive(Debug, Clone, Hash)] #[derive(Debug, Clone)]
pub struct ScriptFnDef { pub struct ScriptFnDef {
/// Function name. /// Function name.
pub name: ImmutableString, pub name: ImmutableString,
@ -363,6 +365,9 @@ pub struct ScriptFnDef {
pub access: FnAccess, pub access: FnAccess,
/// Names of function parameters. /// Names of function parameters.
pub params: StaticVec<String>, pub params: StaticVec<String>,
/// Access to external variables.
#[cfg(not(feature = "no_closure"))]
pub externals: HashSet<String>,
/// Function body. /// Function body.
pub body: Stmt, pub body: Stmt,
/// Position of the function definition. /// 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. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
stack: Vec<(String, ScopeEntryType)>, stack: Vec<(String, ScopeEntryType)>,
/// Tracks a list of external variables (variables that are not explicitly declared in the scope). /// 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<String, Position>, externals: HashMap<String, Position>,
/// 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. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
modules: Vec<String>, modules: Vec<String>,
/// Maximum levels of expression nesting. /// Maximum levels of expression nesting.
@ -434,8 +444,10 @@ impl<'e> ParseState<'e> {
max_expr_depth, max_expr_depth,
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
max_function_expr_depth, max_function_expr_depth,
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
externals: Default::default(), externals: Default::default(),
#[cfg(not(feature = "no_closure"))]
allow_capture: true,
stack: Default::default(), stack: Default::default(),
modules: 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`, /// The return value is the offset to be deducted from `Stack::len`,
/// i.e. the top element of the `ParseState` is offset 1. /// i.e. the top element of the `ParseState` is offset 1.
/// Return `None` when the variable name is not found in the `stack`. /// Return `None` when the variable name is not found in the `stack`.
fn access_var(&mut self, name: &str, pos: Position) -> Option<NonZeroUsize> { fn access_var(&mut self, name: &str, _pos: Position) -> Option<NonZeroUsize> {
let index = self let index = self
.stack .stack
.iter() .iter()
@ -457,9 +469,13 @@ impl<'e> ParseState<'e> {
.find(|(_, (n, _))| *n == name) .find(|(_, (n, _))| *n == name)
.and_then(|(i, _)| NonZeroUsize::new(i + 1)); .and_then(|(i, _)| NonZeroUsize::new(i + 1));
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
if index.is_none() && !self.externals.contains_key(name) { if self.allow_capture {
self.externals.insert(name.to_string(), pos); if index.is_none() && !self.externals.contains_key(name) {
self.externals.insert(name.to_string(), _pos);
}
} else {
self.allow_capture = true
} }
index index
@ -563,6 +579,9 @@ pub enum Stmt {
Position, Position,
)>, )>,
), ),
/// Convert a variable to shared.
#[cfg(not(feature = "no_closure"))]
Share(Box<(String, Position)>),
} }
impl Default for Stmt { impl Default for Stmt {
@ -590,6 +609,9 @@ impl Stmt {
Stmt::Import(x) => x.2, Stmt::Import(x) => x.2,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Export(x) => x.1, 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, Stmt::Import(x) => x.2 = new_pos,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Export(x) => x.1 = new_pos, Stmt::Export(x) => x.1 = new_pos,
#[cfg(not(feature = "no_closure"))]
Stmt::Share(x) => x.1 = new_pos,
} }
self self
@ -639,6 +664,9 @@ impl Stmt {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Import(_) | Stmt::Export(_) => false, Stmt::Import(_) | Stmt::Export(_) => false,
#[cfg(not(feature = "no_closure"))]
Stmt::Share(_) => false,
} }
} }
@ -662,6 +690,9 @@ impl Stmt {
Stmt::Import(_) => false, Stmt::Import(_) => false,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Export(_) => false, Stmt::Export(_) => false,
#[cfg(not(feature = "no_closure"))]
Stmt::Share(_) => false,
} }
} }
} }
@ -745,12 +776,12 @@ pub enum Expr {
Stmt(Box<(Stmt, Position)>), Stmt(Box<(Stmt, Position)>),
/// Wrapped expression - should not be optimized away. /// Wrapped expression - should not be optimized away.
Expr(Box<Expr>), Expr(Box<Expr>),
/// 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 /// 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`. /// and the function names are predictable, so no need to allocate a new `String`.
FnCall( FnCall(
Box<( Box<(
(Cow<'static, str>, bool, Position), (Cow<'static, str>, bool, bool, Position),
Option<Box<ModuleRef>>, Option<Box<ModuleRef>>,
u64, u64,
StaticVec<Expr>, StaticVec<Expr>,
@ -804,6 +835,10 @@ impl Expr {
Self::FloatConstant(x) => x.0.into(), Self::FloatConstant(x) => x.0.into(),
Self::CharConstant(x) => x.0.into(), Self::CharConstant(x) => x.0.into(),
Self::StringConstant(x) => x.0.clone().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::True(_) => true.into(),
Self::False(_) => false.into(), Self::False(_) => false.into(),
Self::Unit(_) => ().into(), Self::Unit(_) => ().into(),
@ -868,7 +903,7 @@ impl Expr {
Self::Property(x) => x.1, Self::Property(x) => x.1,
Self::Stmt(x) => x.1, Self::Stmt(x) => x.1,
Self::Variable(x) => (x.0).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::Assignment(x) => x.0.position(),
Self::And(x) | Self::Or(x) | Self::In(x) => x.2, 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::Variable(x) => (x.0).1 = new_pos,
Self::Property(x) => x.1 = new_pos, Self::Property(x) => x.1 = new_pos,
Self::Stmt(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::And(x) => x.2 = new_pos,
Self::Or(x) => x.2 = new_pos, Self::Or(x) => x.2 = new_pos,
Self::In(x) => x.2 = new_pos, Self::In(x) => x.2 = new_pos,
@ -1006,6 +1041,7 @@ impl Expr {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Token::LeftBracket => true, Token::LeftBracket => true,
Token::LeftParen => true, Token::LeftParen => true,
Token::Bang => true,
Token::DoubleColon => true, Token::DoubleColon => true,
_ => false, _ => false,
}, },
@ -1098,6 +1134,7 @@ fn parse_fn_call(
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
id: String, id: String,
capture: bool,
mut modules: Option<Box<ModuleRef>>, mut modules: Option<Box<ModuleRef>>,
settings: ParseSettings, settings: ParseSettings,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
@ -1140,7 +1177,7 @@ fn parse_fn_call(
}; };
return Ok(Expr::FnCall(Box::new(( return Ok(Expr::FnCall(Box::new((
(id.into(), false, settings.pos), (id.into(), false, capture, settings.pos),
modules, modules,
hash_script, hash_script,
args, args,
@ -1182,7 +1219,7 @@ fn parse_fn_call(
}; };
return Ok(Expr::FnCall(Box::new(( return Ok(Expr::FnCall(Box::new((
(id.into(), false, settings.pos), (id.into(), false, capture, settings.pos),
modules, modules,
hash_script, hash_script,
args, args,
@ -1591,6 +1628,8 @@ fn parse_primary(
_ => input.next().unwrap(), _ => input.next().unwrap(),
}; };
let (next_token, _) = input.peek().unwrap();
let mut root_expr = match token { let mut root_expr = match token {
Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))), Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -1602,7 +1641,7 @@ fn parse_primary(
Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) Expr::Variable(Box::new(((s, settings.pos), None, 0, index)))
} }
// Function call is allowed to have reserved keyword // 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) { if is_keyword_function(&s) {
Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) Expr::Variable(Box::new(((s, settings.pos), None, 0, None)))
} else { } else {
@ -1610,7 +1649,7 @@ fn parse_primary(
} }
} }
// Access to `this` as a variable is OK // 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 { if !settings.is_function_scope {
return Err( return Err(
PERR::BadInput(format!("'{}' can only be used in functions", s)) PERR::BadInput(format!("'{}' can only be used in functions", s))
@ -1650,11 +1689,26 @@ fn parse_primary(
settings.pos = token_pos; settings.pos = token_pos;
root_expr = match (root_expr, token) { 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 // Function call
(Expr::Variable(x), Token::LeftParen) => { (Expr::Variable(x), Token::LeftParen) => {
let ((name, pos), modules, _, _) = *x; let ((name, pos), modules, _, _) = *x;
settings.pos = pos; 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!(), (Expr::Property(_), _) => unreachable!(),
// module access // module access
@ -1764,7 +1818,7 @@ fn parse_unary(
args.push(expr); args.push(expr);
Ok(Expr::FnCall(Box::new(( Ok(Expr::FnCall(Box::new((
(op.into(), true, pos), (op.into(), true, false, pos),
None, None,
hash, hash,
args, args,
@ -1789,7 +1843,7 @@ fn parse_unary(
let hash = calc_fn_hash(empty(), op, 2, empty()); let hash = calc_fn_hash(empty(), op, 2, empty());
Ok(Expr::FnCall(Box::new(( Ok(Expr::FnCall(Box::new((
(op.into(), true, pos), (op.into(), true, false, pos),
None, None,
hash, hash,
args, args,
@ -1820,7 +1874,7 @@ fn parse_unary(
let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?; 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)| { new_state.externals.iter().for_each(|(closure, pos)| {
state.access_var(closure, *pos); state.access_var(closure, *pos);
}); });
@ -1984,7 +2038,14 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseEr
op_pos, op_pos,
))) )))
} }
// lhs.func() // lhs.func!(...)
(_, Expr::FnCall(x)) if (x.0).2 => {
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, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))),
// lhs.rhs // lhs.rhs
(_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())), (_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())),
@ -2171,6 +2232,16 @@ fn parse_binary_op(
let (op_token, pos) = input.next().unwrap(); 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.<var>
#[cfg(not(feature = "no_closure"))]
{
state.allow_capture = false;
}
}
}
let rhs = parse_unary(input, state, lib, settings)?; let rhs = parse_unary(input, state, lib, settings)?;
let next_precedence = input.peek().unwrap().0.precedence(custom); let next_precedence = input.peek().unwrap().0.precedence(custom);
@ -2193,7 +2264,7 @@ fn parse_binary_op(
let cmp_def = Some(false); let cmp_def = Some(false);
let op = op_token.syntax(); let op = op_token.syntax();
let hash = calc_fn_hash(empty(), &op, 2, empty()); 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(); let mut args = StaticVec::new();
args.push(root); args.push(root);
@ -2254,7 +2325,7 @@ fn parse_binary_op(
.unwrap_or(false) => .unwrap_or(false) =>
{ {
// Accept non-native functions for custom operators // 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))) Expr::FnCall(Box::new((op, None, hash, args, None)))
} }
@ -2848,7 +2919,7 @@ fn parse_stmt(
match input.next().unwrap() { match input.next().unwrap() {
(Token::Fn, pos) => { (Token::Fn, pos) => {
let mut state = ParseState::new( let mut new_state = ParseState::new(
state.engine, state.engine,
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
state.max_function_expr_depth, state.max_function_expr_depth,
@ -2867,7 +2938,7 @@ fn parse_stmt(
pos: pos, 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. // Qualifiers (none) + function name + number of arguments.
let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); 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 (token, pos) = input.next().unwrap();
let name = token.into_function_name().map_err(|t| match t { let name = token
Token::Reserved(s) => PERR::Reserved(s).into_err(pos), .into_function_name_for_override()
_ => PERR::FnMissingName.into_err(pos), .map_err(|t| match t {
})?; Token::Reserved(s) => PERR::Reserved(s).into_err(pos),
_ => PERR::FnMissingName.into_err(pos),
})?;
match input.peek().unwrap() { match input.peek().unwrap() {
(Token::LeftParen, _) => eat_token(input, Token::LeftParen), (Token::LeftParen, _) => eat_token(input, Token::LeftParen),
@ -3039,55 +3112,83 @@ fn parse_fn(
(_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), (_, 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 { Ok(ScriptFnDef {
name: name.into(), name: name.into(),
access, access,
params, params,
#[cfg(not(feature = "no_closure"))]
externals,
body, body,
pos: settings.pos, pos: settings.pos,
}) })
} }
/// Creates a curried expression from a list of external variables /// Creates a curried expression from a list of external variables
#[cfg(not(feature = "no_capture"))]
fn make_curry_from_externals( fn make_curry_from_externals(
fn_expr: Expr, fn_expr: Expr,
state: &mut ParseState, externals: StaticVec<(String, Position)>,
settings: &ParseSettings, pos: Position,
) -> Expr { ) -> Expr {
if state.externals.is_empty() { if externals.is_empty() {
return fn_expr; return fn_expr;
} }
let num_externals = externals.len();
let mut args: StaticVec<_> = Default::default(); 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(( args.push(Expr::Variable(Box::new((
(var_name.clone(), *pos), (var_name.into(), *pos),
None, None,
0, 0,
None, None,
)))); ))));
}); });
let hash = calc_fn_hash( #[cfg(feature = "no_closure")]
empty(), externals.into_iter().for_each(|(var_name, pos)| {
KEYWORD_FN_PTR_CURRY, args.push(Expr::Variable(Box::new(((var_name, pos), None, 0, None))));
state.externals.len(), });
empty(),
); let hash = calc_fn_hash(empty(), KEYWORD_FN_PTR_CURRY, num_externals, empty());
let fn_call = Expr::FnCall(Box::new(( let fn_call = Expr::FnCall(Box::new((
(KEYWORD_FN_PTR_CURRY.into(), false, settings.pos), (KEYWORD_FN_PTR_CURRY.into(), false, false, pos),
None, None,
hash, hash,
args, args,
None, 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. /// Parse an anonymous function definition.
@ -3157,17 +3258,31 @@ fn parse_anon_fn(
let body = parse_stmt(input, state, lib, settings.level_up()) let body = parse_stmt(input, state, lib, settings.level_up())
.map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?; .map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?;
#[cfg(feature = "no_capture")] // External variables may need to be processed in a consistent order,
let params: StaticVec<_> = params.into_iter().map(|(v, _)| v).collect(); // 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 let params: StaticVec<_> = if cfg!(not(feature = "no_closure")) {
#[cfg(not(feature = "no_capture"))] externals
let params: StaticVec<_> = state .iter()
.externals .map(|(k, _)| k)
.keys() .cloned()
.cloned() .chain(params.into_iter().map(|(v, _)| v))
.chain(params.into_iter().map(|(v, _)| v)) .collect()
.collect(); } else {
params.into_iter().map(|(v, _)| v).collect()
};
// Calculate hash // Calculate hash
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -3181,20 +3296,26 @@ fn parse_anon_fn(
let hash = s.finish(); let hash = s.finish();
// Create unique function name // 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 { let script = ScriptFnDef {
name: fn_name.clone(), name: fn_name.clone(),
access: FnAccess::Public, access: FnAccess::Public,
params, params,
#[cfg(not(feature = "no_closure"))]
externals: Default::default(),
body, body,
pos: settings.pos, pos: settings.pos,
}; };
let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); let expr = Expr::FnPointer(Box::new((fn_name, settings.pos)));
#[cfg(not(feature = "no_capture"))] let expr = if cfg!(not(feature = "no_closure")) {
let expr = make_curry_from_externals(expr, state, &settings); make_curry_from_externals(expr, externals, settings.pos)
} else {
expr
};
Ok((expr, script)) Ok((expr, script))
} }

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
use crate::engine::{ use crate::engine::{
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY,
KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, KEYWORD_IS_SHARED, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
}; };
use crate::error::LexError; use crate::error::LexError;
@ -501,13 +501,15 @@ impl Token {
"import" | "export" | "as" => Reserved(syntax.into()), "import" | "export" | "as" => Reserved(syntax.into()),
"===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public" "===" | "!==" | "->" | "<-" | "=>" | ":=" | "::<" | "(*" | "*)" | "#" | "public"
| "new" | "use" | "module" | "package" | "var" | "static" | "with" | "do" | "each" | "new" | "use" | "module" | "package" | "var" | "static" | "shared" | "with"
| "then" | "goto" | "exit" | "switch" | "match" | "case" | "try" | "catch" | "do" | "each" | "then" | "goto" | "exit" | "switch" | "match" | "case" | "try"
| "default" | "void" | "null" | "nil" | "spawn" | "go" | "shared" | "sync" | "catch" | "default" | "void" | "null" | "nil" | "spawn" | "go" | "sync" | "async"
| "async" | "await" | "yield" => Reserved(syntax.into()), | "await" | "yield" => Reserved(syntax.into()),
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR 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, _ => return None,
}) })
@ -678,9 +680,9 @@ impl Token {
} }
/// Convert a token into a function name, if possible. /// Convert a token into a function name, if possible.
pub(crate) fn into_function_name(self) -> Result<String, Self> { pub(crate) fn into_function_name_for_override(self) -> Result<String, Self> {
match self { 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), Self::Custom(s) | Self::Identifier(s) if is_valid_identifier(s.chars()) => Ok(s),
_ => Err(self), _ => Err(self),
} }
@ -1430,13 +1432,22 @@ fn get_identifier(
/// Is this keyword allowed as a function? /// Is this keyword allowed as a function?
#[inline(always)] #[inline(always)]
pub fn is_keyword_function(name: &str) -> bool { pub fn is_keyword_function(name: &str) -> bool {
name == KEYWORD_PRINT match name {
|| name == KEYWORD_DEBUG #[cfg(not(feature = "no_closure"))]
|| name == KEYWORD_TYPE_OF KEYWORD_IS_SHARED => true,
|| name == KEYWORD_EVAL KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|| name == KEYWORD_FN_PTR | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => true,
|| name == KEYWORD_FN_PTR_CALL _ => false,
|| name == KEYWORD_FN_PTR_CURRY }
}
/// Can this keyword be overridden as a function?
#[inline(always)]
pub fn can_override_keyword(name: &str) -> bool {
match name {
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR => true,
_ => false,
}
} }
pub fn is_valid_identifier(name: impl Iterator<Item = char>) -> bool { pub fn is_valid_identifier(name: impl Iterator<Item = char>) -> bool {

View File

@ -6,6 +6,7 @@ use crate::stdlib::{
any::TypeId, any::TypeId,
borrow::Borrow, borrow::Borrow,
boxed::Box, boxed::Box,
cmp::Ordering,
fmt, fmt,
hash::{BuildHasher, Hash, Hasher}, hash::{BuildHasher, Hash, Hasher},
iter::FromIterator, iter::FromIterator,
@ -141,6 +142,12 @@ impl AsRef<String> for ImmutableString {
} }
} }
impl Borrow<String> for ImmutableString {
fn borrow(&self) -> &String {
&self.0
}
}
impl Borrow<str> for ImmutableString { impl Borrow<str> for ImmutableString {
fn borrow(&self) -> &str { fn borrow(&self) -> &str {
self.0.as_str() self.0.as_str()
@ -257,6 +264,18 @@ impl AddAssign<&ImmutableString> for ImmutableString {
} }
} }
impl AddAssign<ImmutableString> for ImmutableString {
fn add_assign(&mut self, rhs: ImmutableString) {
if !rhs.is_empty() {
if self.is_empty() {
self.0 = rhs.0;
} else {
self.make_mut().push_str(rhs.0.as_str());
}
}
}
}
impl Add<&str> for ImmutableString { impl Add<&str> for ImmutableString {
type Output = Self; type Output = Self;
@ -348,6 +367,42 @@ impl AddAssign<char> for ImmutableString {
} }
} }
impl<S: AsRef<str>> PartialEq<S> for ImmutableString {
fn eq(&self, other: &S) -> bool {
self.as_str().eq(other.as_ref())
}
}
impl PartialEq<ImmutableString> for str {
fn eq(&self, other: &ImmutableString) -> bool {
self.eq(other.as_str())
}
}
impl PartialEq<ImmutableString> for String {
fn eq(&self, other: &ImmutableString) -> bool {
self.eq(other.as_str())
}
}
impl<S: AsRef<str>> PartialOrd<S> for ImmutableString {
fn partial_cmp(&self, other: &S) -> Option<Ordering> {
self.as_str().partial_cmp(other.as_ref())
}
}
impl PartialOrd<ImmutableString> for str {
fn partial_cmp(&self, other: &ImmutableString) -> Option<Ordering> {
self.partial_cmp(other.as_str())
}
}
impl PartialOrd<ImmutableString> for String {
fn partial_cmp(&self, other: &ImmutableString) -> Option<Ordering> {
self.as_str().partial_cmp(other.as_str())
}
}
impl ImmutableString { impl ImmutableString {
/// Consume the `ImmutableString` and convert it into a `String`. /// Consume the `ImmutableString` and convert it into a `String`.
/// If there are other references to the same string, a cloned copy is returned. /// If there are other references to the same string, a cloned copy is returned.

View File

@ -88,6 +88,7 @@ fn test_call_fn_private() -> Result<(), Box<EvalAltResult>> {
fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> { fn test_fn_ptr_raw() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
#[allow(deprecated)]
engine engine
.register_fn("mul", |x: &mut INT, y: INT| *x *= y) .register_fn("mul", |x: &mut INT, y: INT| *x *= y)
.register_raw_fn( .register_raw_fn(

View File

@ -35,7 +35,8 @@ fn test_fn_ptr_curry_call() -> Result<(), Box<EvalAltResult>> {
} }
#[test] #[test]
#[cfg(not(feature = "no_capture"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
fn test_closures() -> Result<(), Box<EvalAltResult>> { fn test_closures() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
@ -56,5 +57,61 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
42 42
); );
assert_eq!(
engine.eval::<INT>(
r#"
let a = 41;
let foo = |x| { a += x };
foo.call(1);
a
"#
)?,
42
);
assert!(engine.eval::<bool>(
r#"
let a = 41;
let foo = |x| { a += x };
a.is_shared()
"#
)?);
Ok(())
}
#[test]
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "no_object"))]
fn test_closures_data_race() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r#"
let a = 1;
let b = 40;
let foo = |x| { this += a + x };
b.call(foo, 1);
b
"#
)?,
42
);
assert!(matches!(
*engine
.eval::<INT>(
r#"
let a = 20;
let foo = |x| { this += a + x };
a.call(foo, 1);
a
"#
)
.expect_err("should error"),
EvalAltResult::ErrorDataRace(_, _)
));
Ok(()) Ok(())
} }

View File

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

View File

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