Merge branch 'master' into plugins
This commit is contained in:
commit
8e876b0b86
148
README.md
148
README.md
@ -19,8 +19,8 @@ Features
|
|||||||
including [getters/setters](#getters-and-setters), [methods](#members-and-methods) and [indexers](#indexers).
|
including [getters/setters](#getters-and-setters), [methods](#members-and-methods) and [indexers](#indexers).
|
||||||
* Freely pass Rust variables/constants into a script via an external [`Scope`].
|
* Freely pass Rust variables/constants into a script via an external [`Scope`].
|
||||||
* Easily [call a script-defined function](#calling-rhai-functions-from-rust) from Rust.
|
* Easily [call a script-defined function](#calling-rhai-functions-from-rust) from Rust.
|
||||||
* Low compile-time overhead (~0.6 sec debug/~3 sec release for `rhai_runner` sample app).
|
* Fairly low compile-time overhead.
|
||||||
* Fairly efficient evaluation (1 million iterations in 0.75 sec on my 5 year old laptop).
|
* Fairly efficient evaluation (1 million iterations in 0.25 sec on a single core, 2.3 GHz Linux VM).
|
||||||
* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to
|
* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to
|
||||||
one single source file, all with names starting with `"unsafe_"`).
|
one single source file, all with names starting with `"unsafe_"`).
|
||||||
* Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature).
|
* Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature).
|
||||||
@ -37,7 +37,7 @@ Features
|
|||||||
to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are
|
to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are
|
||||||
pulled in to provide for functionalities that used to be in `std`.
|
pulled in to provide for functionalities that used to be in `std`.
|
||||||
|
|
||||||
**Note:** Currently, the version is 0.15.0, so the language and API's may change before they stabilize.
|
**Note:** Currently, the version is `0.15.0`, so the language and API's may change before they stabilize.
|
||||||
|
|
||||||
What Rhai doesn't do
|
What Rhai doesn't do
|
||||||
--------------------
|
--------------------
|
||||||
@ -47,15 +47,27 @@ It doesn't attempt to be a new language. For example:
|
|||||||
|
|
||||||
* No classes. Well, Rust doesn't either. On the other hand...
|
* No classes. Well, Rust doesn't either. On the other hand...
|
||||||
* No traits... so it is also not Rust. Do your Rusty stuff in Rust.
|
* No traits... so it is also not Rust. Do your Rusty stuff in Rust.
|
||||||
* No structures - define your types in Rust instead; Rhai can seamlessly work with _any Rust type_.
|
* No structures/records - define your types in Rust instead; Rhai can seamlessly work with _any Rust type_.
|
||||||
|
There is, however, a built-in [object map] type which is adequate for most uses.
|
||||||
* No first-class functions - Code your functions in Rust instead, and register them with Rhai.
|
* No first-class functions - Code your functions in Rust instead, and register them with Rhai.
|
||||||
|
* No garbage collection - this should be expected, so...
|
||||||
* No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure](#calling-rhai-functions-from-rust).
|
* No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure](#calling-rhai-functions-from-rust).
|
||||||
* It is best to expose an API in Rhai for scripts to call. All your core functionalities should be in Rust.
|
* No byte-codes/JIT - Rhai has an AST-walking interpreter which will not win any speed races. The purpose of Rhai is not
|
||||||
|
to be extremely _fast_, but to make it as easy as possible to integrate with native Rust programs.
|
||||||
|
|
||||||
|
Due to this intended usage, Rhai deliberately keeps the language simple and small by omitting advanced language features
|
||||||
|
such as classes, inheritance, first-class functions, closures, concurrency, byte-codes, JIT etc.
|
||||||
|
Avoid the temptation to write full-fledge program logic entirely in Rhai - that use case is best fulfilled by
|
||||||
|
more complete languages such as JS or Lua.
|
||||||
|
|
||||||
|
Therefore, in actual practice, it is usually best to expose a Rust API into Rhai for scripts to call.
|
||||||
|
All your core functionalities should be in Rust.
|
||||||
|
This is similar to some dynamic languages where most of the core functionalities reside in a C/C++ standard library.
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Install the Rhai crate by adding this line to `dependencies`:
|
Install the Rhai crate on [`crates.io`](https::/crates.io/crates/rhai/) by adding this line to `dependencies`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@ -93,7 +105,7 @@ Optional features
|
|||||||
| `no_index` | Disable [arrays] and indexing features. |
|
| `no_index` | Disable [arrays] and indexing features. |
|
||||||
| `no_object` | Disable support for custom types and [object maps]. |
|
| `no_object` | Disable support for custom types and [object maps]. |
|
||||||
| `no_function` | Disable script-defined functions. |
|
| `no_function` | Disable script-defined functions. |
|
||||||
| `no_module` | Disable loading modules. |
|
| `no_module` | Disable loading external modules. |
|
||||||
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||||
|
|
||||||
By default, Rhai includes all the standard functionalities in a small, tight package.
|
By default, Rhai includes all the standard functionalities in a small, tight package.
|
||||||
@ -179,33 +191,35 @@ A number of examples can be found in the `examples` folder:
|
|||||||
Examples can be run with the following command:
|
Examples can be run with the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --example name
|
cargo run --example {example_name}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `repl` example is a particularly good one as it allows one to interactively try out Rhai's
|
The `repl` example is a particularly good one as it allows one to interactively try out Rhai's
|
||||||
language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop).
|
language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop).
|
||||||
|
|
||||||
Example Scripts
|
Example scripts
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` folder:
|
There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` folder:
|
||||||
|
|
||||||
| Language feature scripts | Description |
|
| Language feature scripts | Description |
|
||||||
| ---------------------------------------------------- | ------------------------------------------------------------- |
|
| ---------------------------------------------------- | ----------------------------------------------------------------------------- |
|
||||||
| [`array.rhai`](scripts/array.rhai) | [arrays] in Rhai |
|
| [`array.rhai`](scripts/array.rhai) | [arrays] in Rhai |
|
||||||
| [`assignment.rhai`](scripts/assignment.rhai) | variable declarations |
|
| [`assignment.rhai`](scripts/assignment.rhai) | variable declarations |
|
||||||
| [`comments.rhai`](scripts/comments.rhai) | just comments |
|
| [`comments.rhai`](scripts/comments.rhai) | just comments |
|
||||||
| [`for1.rhai`](scripts/for1.rhai) | for loops |
|
| [`for1.rhai`](scripts/for1.rhai) | [`for`](#for-loop) loops |
|
||||||
| [`function_decl1.rhai`](scripts/function_decl1.rhai) | a function without parameters |
|
| [`for2.rhai`](scripts/for2.rhai) | [`for`](#for-loop) loops on [arrays] |
|
||||||
| [`function_decl2.rhai`](scripts/function_decl2.rhai) | a function with two parameters |
|
| [`function_decl1.rhai`](scripts/function_decl1.rhai) | a [function] without parameters |
|
||||||
| [`function_decl3.rhai`](scripts/function_decl3.rhai) | a function with many parameters |
|
| [`function_decl2.rhai`](scripts/function_decl2.rhai) | a [function] with two parameters |
|
||||||
| [`if1.rhai`](scripts/if1.rhai) | if example |
|
| [`function_decl3.rhai`](scripts/function_decl3.rhai) | a [function] with many parameters |
|
||||||
| [`loop.rhai`](scripts/loop.rhai) | endless loop in Rhai, this example emulates a do..while cycle |
|
| [`if1.rhai`](scripts/if1.rhai) | [`if`](#if-statement) example |
|
||||||
| [`op1.rhai`](scripts/op1.rhai) | just a simple addition |
|
| [`loop.rhai`](scripts/loop.rhai) | count-down [`loop`](#infinite-loop) in Rhai, emulating a `do` .. `while` loop |
|
||||||
|
| [`op1.rhai`](scripts/op1.rhai) | just simple addition |
|
||||||
| [`op2.rhai`](scripts/op2.rhai) | simple addition and multiplication |
|
| [`op2.rhai`](scripts/op2.rhai) | simple addition and multiplication |
|
||||||
| [`op3.rhai`](scripts/op3.rhai) | change evaluation order with parenthesis |
|
| [`op3.rhai`](scripts/op3.rhai) | change evaluation order with parenthesis |
|
||||||
| [`string.rhai`](scripts/string.rhai) | [string] operations |
|
| [`string.rhai`](scripts/string.rhai) | [string] operations |
|
||||||
| [`while.rhai`](scripts/while.rhai) | while loop |
|
| [`strings_map.rhai`](scripts/strings_map.rhai) | [string] and [object map] operations |
|
||||||
|
| [`while.rhai`](scripts/while.rhai) | [`while`](#while-loop) loop |
|
||||||
|
|
||||||
| Example scripts | Description |
|
| Example scripts | Description |
|
||||||
| -------------------------------------------- | ---------------------------------------------------------------------------------- |
|
| -------------------------------------------- | ---------------------------------------------------------------------------------- |
|
||||||
@ -235,6 +249,7 @@ fn main() -> Result<(), Box<EvalAltResult>>
|
|||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
let result = engine.eval::<i64>("40 + 2")?;
|
let result = engine.eval::<i64>("40 + 2")?;
|
||||||
|
// ^^^^^^^ cast the result to an 'i64', this is required
|
||||||
|
|
||||||
println!("Answer: {}", result); // prints 42
|
println!("Answer: {}", result); // prints 42
|
||||||
|
|
||||||
@ -291,6 +306,8 @@ let ast = engine.compile_file("hello_world.rhai".into())?;
|
|||||||
|
|
||||||
### Calling Rhai functions from Rust
|
### Calling Rhai functions from Rust
|
||||||
|
|
||||||
|
[`private`]: #calling-rhai-functions-from-rust
|
||||||
|
|
||||||
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust via `Engine::call_fn`.
|
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust via `Engine::call_fn`.
|
||||||
Functions declared with `private` are hidden and cannot be called from Rust (see also [modules]).
|
Functions declared with `private` are hidden and cannot be called from Rust (see also [modules]).
|
||||||
|
|
||||||
@ -341,6 +358,16 @@ let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?;
|
|||||||
let result: () = engine.call_fn(&mut scope, &ast, "hidden", ())?;
|
let result: () = engine.call_fn(&mut scope, &ast, "hidden", ())?;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For more control, construct all arguments as `Dynamic` values and use `Engine::call_fn_dynamic`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let result: Dynamic = engine.call_fn_dynamic(&mut scope, &ast, "hello",
|
||||||
|
&mut [ String::from("abc").into(), 123_i64.into() ])?;
|
||||||
|
```
|
||||||
|
|
||||||
|
However, beware that `Engine::call_fn_dynamic` _consumes_ its arguments, meaning that all arguments passed to it
|
||||||
|
will be replaced by `()` afterwards. To re-use the arguments, clone them beforehand and pass in the clone.
|
||||||
|
|
||||||
### Creating Rust anonymous functions from Rhai script
|
### Creating Rust anonymous functions from Rhai script
|
||||||
|
|
||||||
[`Func`]: #creating-rust-anonymous-functions-from-rhai-script
|
[`Func`]: #creating-rust-anonymous-functions-from-rhai-script
|
||||||
@ -704,11 +731,16 @@ let x = (42_i64).into(); // 'into()' works for standard t
|
|||||||
let y = Dynamic::from(String::from("hello!")); // remember &str is not supported by Rhai
|
let y = Dynamic::from(String::from("hello!")); // remember &str is not supported by Rhai
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Functions registered with the [`Engine`] can be _overloaded_ as long as the _signature_ is unique,
|
||||||
|
i.e. different functions can have the same name as long as their parameters are of different types
|
||||||
|
and/or different number.
|
||||||
|
New definitions _overwrite_ previous definitions of the same name and same number/types of parameters.
|
||||||
|
|
||||||
Generic functions
|
Generic functions
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately.
|
Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately.
|
||||||
This is essentially function overloading (Rhai does not natively support generics).
|
This essentially overloads the function with different parameter types (Rhai does not natively support generics).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
@ -729,8 +761,8 @@ fn main()
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This example shows how to register multiple functions (or, in this case, multiple overloaded versions of the same function)
|
The above example shows how to register multiple functions (or, in this case, multiple overloaded versions of the same function)
|
||||||
under the same name. This enables function overloading based on the number and types of parameters.
|
under the same name.
|
||||||
|
|
||||||
Fallible functions
|
Fallible functions
|
||||||
------------------
|
------------------
|
||||||
@ -773,7 +805,8 @@ fn main()
|
|||||||
Overriding built-in functions
|
Overriding built-in functions
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
Any similarly-named function defined in a script overrides any built-in function.
|
Any similarly-named function defined in a script overrides any built-in function and any registered
|
||||||
|
native Rust function of the same name and number of parameters.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Override the built-in function 'to_int'
|
// Override the built-in function 'to_int'
|
||||||
@ -784,11 +817,13 @@ fn to_int(num) {
|
|||||||
print(to_int(123)); // what happens?
|
print(to_int(123)); // what happens?
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A registered function, in turn, overrides any built-in function of the same name and number/types of parameters.
|
||||||
|
|
||||||
Operator overloading
|
Operator overloading
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
In Rhai, a lot of functionalities are actually implemented as functions, including basic operations such as arithmetic calculations.
|
In Rhai, a lot of functionalities are actually implemented as functions, including basic operations such as arithmetic calculations.
|
||||||
For example, in the expression "`a + b`", the `+` operator is _not_ built-in, but calls a function named "`+`" instead!
|
For example, in the expression "`a + b`", the `+` operator is _not_ built in, but calls a function named "`+`" instead!
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let x = a + b;
|
let x = a + b;
|
||||||
@ -801,7 +836,7 @@ overriding them has no effect at all.
|
|||||||
|
|
||||||
Operator functions cannot be defined as a script function (because operators syntax are not valid function names).
|
Operator functions cannot be defined as a script function (because operators syntax are not valid function names).
|
||||||
However, operator functions _can_ be registered to the [`Engine`] via the methods `Engine::register_fn`, `Engine::register_result_fn` etc.
|
However, operator functions _can_ be registered to the [`Engine`] via the methods `Engine::register_fn`, `Engine::register_result_fn` etc.
|
||||||
When a custom operator function is registered with the same name as an operator, it _overloads_ (or overrides) the built-in version.
|
When a custom operator function is registered with the same name as an operator, it overrides the built-in version.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, EvalAltResult, RegisterFn};
|
use rhai::{Engine, EvalAltResult, RegisterFn};
|
||||||
@ -828,7 +863,7 @@ let result: i64 = engine.eval("1 + 1.0"); // prints 2.0 (normally an e
|
|||||||
```
|
```
|
||||||
|
|
||||||
Use operator overloading for custom types (described below) only.
|
Use operator overloading for custom types (described below) only.
|
||||||
Be very careful when overloading built-in operators because script authors expect standard operators to behave in a
|
Be very careful when overriding built-in operators because script authors expect standard operators to behave in a
|
||||||
consistent and predictable manner, and will be annoyed if a calculation for '`+`' turns into a subtraction, for example.
|
consistent and predictable manner, and will be annoyed if a calculation for '`+`' turns into a subtraction, for example.
|
||||||
|
|
||||||
Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`].
|
Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`].
|
||||||
@ -915,7 +950,7 @@ engine.register_fn("update", TestStruct::update); // registers 'update(&mut Te
|
|||||||
engine.register_fn("new_ts", TestStruct::new); // registers 'new()'
|
engine.register_fn("new_ts", TestStruct::new); // registers 'new()'
|
||||||
```
|
```
|
||||||
|
|
||||||
The custom type is then ready for us in scripts. Scripts can see the functions and methods registered earlier.
|
The custom type is then ready for use in scripts. Scripts can see the functions and methods registered earlier.
|
||||||
Get the evaluation result back out from script-land just as before, this time casting to the custom type:
|
Get the evaluation result back out from script-land just as before, this time casting to the custom type:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -1746,8 +1781,8 @@ The Rust type of a timestamp is `std::time::Instant`. [`type_of()`] a timestamp
|
|||||||
The following methods (defined in the [`BasicTimePackage`](#packages) but excluded if using a [raw `Engine`]) operate on timestamps:
|
The following methods (defined in the [`BasicTimePackage`](#packages) but excluded if using a [raw `Engine`]) operate on timestamps:
|
||||||
|
|
||||||
| Function | Parameter(s) | Description |
|
| Function | Parameter(s) | Description |
|
||||||
| ------------------ | ---------------------------------- | -------------------------------------------------------- |
|
| ----------------------------- | ---------------------------------- | -------------------------------------------------------- |
|
||||||
| `elapsed` property | _none_ | returns the number of seconds since the timestamp |
|
| `elapsed` method and property | _none_ | returns the number of seconds since the timestamp |
|
||||||
| `-` operator | later timestamp, earlier timestamp | returns the number of seconds between the two timestamps |
|
| `-` operator | later timestamp, earlier timestamp | returns the number of seconds between the two timestamps |
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
@ -1777,15 +1812,19 @@ set of types (see [built-in operators](#built-in-operators)).
|
|||||||
"42" == 42; // false
|
"42" == 42; // false
|
||||||
```
|
```
|
||||||
|
|
||||||
Comparing two values of _different_ data types, or of unknown data types, always results in `false`.
|
Comparing two values of _different_ data types, or of unknown data types, always results in `false`,
|
||||||
|
except for '`!=`' (not equals) which results in `true`. This is in line with intuition.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
42 == 42.0; // false - i64 is different from f64
|
42 == 42.0; // false - i64 cannot be compared with f64
|
||||||
42 > "42"; // false - i64 is different from string
|
42 != 42.0; // true - i64 cannot be compared with f64
|
||||||
42 <= "42"; // false again
|
|
||||||
|
42 > "42"; // false - i64 cannot be compared with string
|
||||||
|
42 <= "42"; // false - i64 cannot be compared with string
|
||||||
|
|
||||||
let ts = new_ts(); // custom type
|
let ts = new_ts(); // custom type
|
||||||
ts == 42; // false - types are not the same
|
ts == 42; // false - types cannot be compared
|
||||||
|
ts != 42; // true - types cannot be compared
|
||||||
```
|
```
|
||||||
|
|
||||||
Boolean operators
|
Boolean operators
|
||||||
@ -1836,8 +1875,8 @@ my_str += 12345;
|
|||||||
my_str == "abcABC12345"
|
my_str == "abcABC12345"
|
||||||
```
|
```
|
||||||
|
|
||||||
`if` statements
|
`if` statement
|
||||||
---------------
|
--------------
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
if foo(x) {
|
if foo(x) {
|
||||||
@ -1872,8 +1911,8 @@ let x = if decision { 42 }; // no else branch defaults to '()'
|
|||||||
x == ();
|
x == ();
|
||||||
```
|
```
|
||||||
|
|
||||||
`while` loops
|
`while` loop
|
||||||
-------------
|
------------
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let x = 10;
|
let x = 10;
|
||||||
@ -1900,8 +1939,8 @@ loop {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`for` loops
|
`for` loop
|
||||||
-----------
|
----------
|
||||||
|
|
||||||
Iterating through a range or an [array] is provided by the `for` ... `in` loop.
|
Iterating through a range or an [array] is provided by the `for` ... `in` loop.
|
||||||
|
|
||||||
@ -2073,8 +2112,8 @@ This is similar to Rust and many other modern languages.
|
|||||||
|
|
||||||
### Function overloading
|
### Function overloading
|
||||||
|
|
||||||
Functions can be _overloaded_ and are resolved purely upon the function's _name_ and the _number_ of parameters
|
Functions defined in script can be _overloaded_ by _arity_ (i.e. they are resolved purely upon the function's _name_
|
||||||
(but not parameter _types_, since all parameters are the same type - [`Dynamic`]).
|
and _number_ of parameters, but not parameter _types_ since all parameters are the same type - [`Dynamic`]).
|
||||||
New definitions _overwrite_ previous definitions of the same name and number of parameters.
|
New definitions _overwrite_ previous definitions of the same name and number of parameters.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -2172,8 +2211,8 @@ Modules can be disabled via the [`no_module`] feature.
|
|||||||
A _module_ is a single script (or pre-compiled `AST`) containing global variables and functions.
|
A _module_ is a single script (or pre-compiled `AST`) containing global variables and functions.
|
||||||
The `export` statement, which can only be at global level, exposes selected variables as members of a module.
|
The `export` statement, which can only be at global level, exposes selected variables as members of a module.
|
||||||
Variables not exported are _private_ and invisible to the outside.
|
Variables not exported are _private_ and invisible to the outside.
|
||||||
On the other hand, all functions are automatically exported, _unless_ it is explicitly opt-out with the `private` prefix.
|
On the other hand, all functions are automatically exported, _unless_ it is explicitly opt-out with the [`private`] prefix.
|
||||||
Functions declared `private` are invisible to the outside.
|
Functions declared [`private`] are invisible to the outside.
|
||||||
|
|
||||||
Everything exported from a module is **constant** (**read-only**).
|
Everything exported from a module is **constant** (**read-only**).
|
||||||
|
|
||||||
@ -2267,7 +2306,7 @@ engine.eval_expression_with_scope::<i64>(&scope, "question::inc(question::answer
|
|||||||
|
|
||||||
It is easy to convert a pre-compiled `AST` into a module: just use `Module::eval_ast_as_new`.
|
It is easy to convert a pre-compiled `AST` into a module: just use `Module::eval_ast_as_new`.
|
||||||
Don't forget the `export` statement, otherwise there will be no variables exposed by the module
|
Don't forget the `export` statement, otherwise there will be no variables exposed by the module
|
||||||
other than non-`private` functions (unless that's intentional).
|
other than non-[`private`] functions (unless that's intentional).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, Module};
|
use rhai::{Engine, Module};
|
||||||
@ -2346,19 +2385,20 @@ so that it does not consume more resources that it is allowed to.
|
|||||||
|
|
||||||
The most important resources to watch out for are:
|
The most important resources to watch out for are:
|
||||||
|
|
||||||
* **Memory**: A malignant script may continuously grow an [array] or [object map] until all memory is consumed.
|
* **Memory**: A malicous script may continuously grow an [array] or [object map] until all memory is consumed.
|
||||||
It may also create a large [array] or [object map] literal that exhausts all memory during parsing.
|
It may also create a large [array] or [object map] literal that exhausts all memory during parsing.
|
||||||
* **CPU**: A malignant script may run an infinite tight loop that consumes all CPU cycles.
|
* **CPU**: A malicous script may run an infinite tight loop that consumes all CPU cycles.
|
||||||
* **Time**: A malignant script may run indefinitely, thereby blocking the calling system which is waiting for a result.
|
* **Time**: A malicous script may run indefinitely, thereby blocking the calling system which is waiting for a result.
|
||||||
* **Stack**: A malignant script may attempt an infinite recursive call that exhausts the call stack.
|
* **Stack**: A malicous script may attempt an infinite recursive call that exhausts the call stack.
|
||||||
Alternatively, it may create a degenerated deep expression with so many levels that the parser exhausts the call stack
|
Alternatively, it may create a degenerated deep expression with so many levels that the parser exhausts the call stack
|
||||||
when parsing the expression; or even deeply-nested statement blocks, if nested deep enough.
|
when parsing the expression; or even deeply-nested statement blocks, if nested deep enough.
|
||||||
* **Overflows**: A malignant script may deliberately cause numeric over-flows and/or under-flows, divide by zero, and/or
|
* **Overflows**: A malicous script may deliberately cause numeric over-flows and/or under-flows, divide by zero, and/or
|
||||||
create bad floating-point representations, in order to crash the system.
|
create bad floating-point representations, in order to crash the system.
|
||||||
* **Files**: A malignant script may continuously [`import`] an external module within an infinite loop,
|
* **Files**: A malicous script may continuously [`import`] an external module within an infinite loop,
|
||||||
thereby putting heavy load on the file-system (or even the network if the file is not local).
|
thereby putting heavy load on the file-system (or even the network if the file is not local).
|
||||||
|
Furthermore, the module script may simply [`import`] itself in an infinite recursion.
|
||||||
Even when modules are not created from files, they still typically consume a lot of resources to load.
|
Even when modules are not created from files, they still typically consume a lot of resources to load.
|
||||||
* **Data**: A malignant script may attempt to read from and/or write to data that it does not own. If this happens,
|
* **Data**: A malicous script may attempt to read from and/or write to data that it does not own. If this happens,
|
||||||
it is a severe security breach and may put the entire system at risk.
|
it is a severe security breach and may put the entire system at risk.
|
||||||
|
|
||||||
### Maximum number of operations
|
### Maximum number of operations
|
||||||
@ -2434,7 +2474,7 @@ Rhai by default limits function calls to a maximum depth of 128 levels (16 level
|
|||||||
This limit may be changed via the `Engine::set_max_call_levels` method.
|
This limit may be changed via the `Engine::set_max_call_levels` method.
|
||||||
|
|
||||||
When setting this limit, care must be also taken to the evaluation depth of each _statement_
|
When setting this limit, care must be also taken to the evaluation depth of each _statement_
|
||||||
within the function. It is entirely possible for a malignant script to embed an recursive call deep
|
within the function. It is entirely possible for a malicous script to embed an recursive call deep
|
||||||
inside a nested expression or statement block (see [maximum statement depth](#maximum-statement-depth)).
|
inside a nested expression or statement block (see [maximum statement depth](#maximum-statement-depth)).
|
||||||
|
|
||||||
The limit can be disabled via the [`unchecked`] feature for higher performance
|
The limit can be disabled via the [`unchecked`] feature for higher performance
|
||||||
@ -2479,7 +2519,7 @@ engine.set_max_expr_depths(50, 5); // allow nesting up to 50 layers of
|
|||||||
|
|
||||||
Beware that there may be multiple layers for a simple language construct, even though it may correspond
|
Beware that there may be multiple layers for a simple language construct, even though it may correspond
|
||||||
to only one AST node. That is because the Rhai _parser_ internally runs a recursive chain of function calls
|
to only one AST node. That is because the Rhai _parser_ internally runs a recursive chain of function calls
|
||||||
and it is important that a malignant script does not panic the parser in the first place.
|
and it is important that a malicous script does not panic the parser in the first place.
|
||||||
|
|
||||||
Functions are placed under stricter limits because of the multiplicative effect of recursion.
|
Functions are placed under stricter limits because of the multiplicative effect of recursion.
|
||||||
A script can effectively call itself while deep inside an expression chain within the function body,
|
A script can effectively call itself while deep inside an expression chain within the function body,
|
||||||
@ -2492,7 +2532,7 @@ Make sure that `C x ( 5 + F ) + S` layered calls do not cause a stack overflow,
|
|||||||
* `S` = maximum statement depth at global level.
|
* `S` = maximum statement depth at global level.
|
||||||
|
|
||||||
A script exceeding the maximum nesting depths will terminate with a parsing error.
|
A script exceeding the maximum nesting depths will terminate with a parsing error.
|
||||||
The malignant `AST` will not be able to get past parsing in the first place.
|
The malicous `AST` will not be able to get past parsing in the first place.
|
||||||
|
|
||||||
This check can be disabled via the [`unchecked`] feature for higher performance
|
This check can be disabled via the [`unchecked`] feature for higher performance
|
||||||
(but higher risks as well).
|
(but higher risks as well).
|
||||||
|
@ -13,13 +13,13 @@ Bug fixes
|
|||||||
---------
|
---------
|
||||||
|
|
||||||
* Indexing with an index or dot expression now works property (it compiled wrongly before).
|
* Indexing with an index or dot expression now works property (it compiled wrongly before).
|
||||||
For example, `let s = "hello"; s[s.len-1] = 'x';` now works property instead of an error.
|
For example, `let s = "hello"; s[s.len-1] = 'x';` now works property instead of causing a runtime error.
|
||||||
|
|
||||||
Breaking changes
|
Breaking changes
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
* `Engine::compile_XXX` functions now return `ParseError` instead of `Box<ParseError>`.
|
* `Engine::compile_XXX` functions now return `ParseError` instead of `Box<ParseError>`.
|
||||||
* The `RegisterDynamicFn` trait is merged into the `RegisterResutlFn` trait which now always returns
|
* The `RegisterDynamicFn` trait is merged into the `RegisterResultFn` trait which now always returns
|
||||||
`Result<Dynamic, Box<EvalAltResult>>`.
|
`Result<Dynamic, Box<EvalAltResult>>`.
|
||||||
* Default maximum limit on levels of nested function calls is fine-tuned and set to a different value.
|
* Default maximum limit on levels of nested function calls is fine-tuned and set to a different value.
|
||||||
* Some operator functions are now built in (see _Speed enhancements_ below), so they are available even
|
* Some operator functions are now built in (see _Speed enhancements_ below), so they are available even
|
||||||
@ -41,6 +41,7 @@ New features
|
|||||||
* Set limit on maximum level of nesting expressions and statements to avoid panics during parsing.
|
* Set limit on maximum level of nesting expressions and statements to avoid panics during parsing.
|
||||||
* New `EvalPackage` to disable `eval`.
|
* New `EvalPackage` to disable `eval`.
|
||||||
* `Module::set_getter_fn`, `Module::set_setter_fn` and `Module:set_indexer_fn` to register getter/setter/indexer functions.
|
* `Module::set_getter_fn`, `Module::set_setter_fn` and `Module:set_indexer_fn` to register getter/setter/indexer functions.
|
||||||
|
* `Engine::call_fn_dynamic` for more control in calling script functions.
|
||||||
|
|
||||||
Speed enhancements
|
Speed enhancements
|
||||||
------------------
|
------------------
|
||||||
@ -60,6 +61,8 @@ Speed enhancements
|
|||||||
excessive cloning. For example, if `a` is a large array, getting its length in this manner: `len(a)` used to result
|
excessive cloning. For example, if `a` is a large array, getting its length in this manner: `len(a)` used to result
|
||||||
in a full clone of `a` before taking the length and throwing the copy away. Now, `a` is simply passed by reference,
|
in a full clone of `a` before taking the length and throwing the copy away. Now, `a` is simply passed by reference,
|
||||||
avoiding the cloning altogether.
|
avoiding the cloning altogether.
|
||||||
|
* A custom hasher simply passes through `u64` keys without hashing to avoid function call hash keys
|
||||||
|
(which are by themselves `u64`) being hashed twice.
|
||||||
|
|
||||||
|
|
||||||
Version 0.14.1
|
Version 0.14.1
|
||||||
|
@ -61,3 +61,27 @@ fn bench_eval_array_large_set(bench: &mut Bencher) {
|
|||||||
|
|
||||||
bench.iter(|| engine.consume_ast(&ast).unwrap());
|
bench.iter(|| engine.consume_ast(&ast).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_eval_array_loop(bench: &mut Bencher) {
|
||||||
|
let script = r#"
|
||||||
|
let list = [];
|
||||||
|
|
||||||
|
for i in range(0, 10_000) {
|
||||||
|
list.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sum = 0;
|
||||||
|
|
||||||
|
for i in list {
|
||||||
|
sum += i;
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
engine.set_optimization_level(OptimizationLevel::None);
|
||||||
|
|
||||||
|
let ast = engine.compile(script).unwrap();
|
||||||
|
|
||||||
|
bench.iter(|| engine.consume_ast(&ast).unwrap());
|
||||||
|
}
|
||||||
|
22
scripts/for2.rhai
Normal file
22
scripts/for2.rhai
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
const MAX = 1_000_000;
|
||||||
|
|
||||||
|
print("Iterating an array with " + MAX + " items...");
|
||||||
|
|
||||||
|
print("Ready... Go!");
|
||||||
|
|
||||||
|
let now = timestamp();
|
||||||
|
|
||||||
|
let list = [];
|
||||||
|
|
||||||
|
for i in range(0, MAX) {
|
||||||
|
list.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sum = 0;
|
||||||
|
|
||||||
|
for i in list {
|
||||||
|
sum += i;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Sum = " + sum);
|
||||||
|
print("Finished. Run time = " + now.elapsed + " seconds.");
|
@ -62,8 +62,10 @@ let a = mat_gen(SIZE);
|
|||||||
let b = mat_gen(SIZE);
|
let b = mat_gen(SIZE);
|
||||||
let c = mat_mul(a, b);
|
let c = mat_mul(a, b);
|
||||||
|
|
||||||
|
/*
|
||||||
for i in range(0, SIZE) {
|
for i in range(0, SIZE) {
|
||||||
print(c[i]);
|
print(c[i]);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
print("Finished. Run time = " + now.elapsed + " seconds.");
|
print("Finished. Run time = " + now.elapsed + " seconds.");
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
let now = timestamp();
|
let now = timestamp();
|
||||||
|
|
||||||
const MAX_NUMBER_TO_CHECK = 100_000; // 9592 primes <= 100000
|
const MAX_NUMBER_TO_CHECK = 1_000_000; // 9592 primes <= 100000
|
||||||
|
|
||||||
let prime_mask = [];
|
let prime_mask = [];
|
||||||
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
|
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
|
||||||
@ -15,7 +15,7 @@ let total_primes_found = 0;
|
|||||||
for p in range(2, MAX_NUMBER_TO_CHECK) {
|
for p in range(2, MAX_NUMBER_TO_CHECK) {
|
||||||
if !prime_mask[p] { continue; }
|
if !prime_mask[p] { continue; }
|
||||||
|
|
||||||
print(p);
|
//print(p);
|
||||||
|
|
||||||
total_primes_found += 1;
|
total_primes_found += 1;
|
||||||
|
|
||||||
@ -28,6 +28,6 @@ for p in range(2, MAX_NUMBER_TO_CHECK) {
|
|||||||
print("Total " + total_primes_found + " primes <= " + MAX_NUMBER_TO_CHECK);
|
print("Total " + total_primes_found + " primes <= " + MAX_NUMBER_TO_CHECK);
|
||||||
print("Run time = " + now.elapsed + " seconds.");
|
print("Run time = " + now.elapsed + " seconds.");
|
||||||
|
|
||||||
if total_primes_found != 9_592 {
|
if total_primes_found != 78_498 {
|
||||||
print("The answer is WRONG! Should be 9,592!");
|
print("The answer is WRONG! Should be 78,498!");
|
||||||
}
|
}
|
103
scripts/strings_map.rhai
Normal file
103
scripts/strings_map.rhai
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
print("Ready... Go!");
|
||||||
|
|
||||||
|
let now = timestamp();
|
||||||
|
|
||||||
|
let adverbs = [ "moderately", "really", "slightly", "very" ];
|
||||||
|
|
||||||
|
let adjectives = [
|
||||||
|
"abandoned", "able", "absolute", "academic", "acceptable", "acclaimed",
|
||||||
|
"accomplished", "accurate", "aching", "acidic", "acrobatic", "active",
|
||||||
|
"actual", "adept", "admirable", "admired", "adolescent", "adorable", "adored",
|
||||||
|
"advanced", "adventurous", "affectionate", "afraid", "aged", "aggravating",
|
||||||
|
"aggressive", "agile", "agitated", "agonizing", "agreeable", "ajar",
|
||||||
|
"alarmed", "alarming", "alert", "alienated", "alive", "all", "altruistic",
|
||||||
|
"amazing", "ambitious", "ample", "amused", "amusing", "anchored", "ancient",
|
||||||
|
"angelic", "angry", "anguished", "animated", "annual", "another", "antique",
|
||||||
|
"anxious", "any", "apprehensive", "appropriate", "apt", "arctic", "arid",
|
||||||
|
"aromatic", "artistic", "ashamed", "assured", "astonishing", "athletic",
|
||||||
|
"attached", "attentive", "attractive", "austere", "authentic", "authorized",
|
||||||
|
"automatic", "avaricious", "average", "aware", "awesome", "awful", "awkward",
|
||||||
|
"babyish", "back", "bad", "baggy", "bare", "barren", "basic", "beautiful",
|
||||||
|
"belated", "beloved", "beneficial", "best", "better", "bewitched", "big",
|
||||||
|
"big-hearted", "biodegradable", "bite-sized", "bitter", "black",
|
||||||
|
"black-and-white", "bland", "blank", "blaring", "bleak", "blind", "blissful",
|
||||||
|
"blond", "blue", "blushing", "bogus", "boiling", "bold", "bony", "boring",
|
||||||
|
"bossy", "both", "bouncy", "bountiful", "bowed", "brave", "breakable",
|
||||||
|
"brief", "bright", "brilliant", "brisk", "broken", "bronze", "brown",
|
||||||
|
"bruised", "bubbly", "bulky", "bumpy", "buoyant", "burdensome", "burly",
|
||||||
|
"bustling", "busy", "buttery", "buzzing", "calculating", "calm", "candid",
|
||||||
|
"canine", "capital", "carefree", "careful", "careless", "caring", "cautious",
|
||||||
|
"cavernous", "celebrated", "charming", "cheap", "cheerful", "cheery", "chief",
|
||||||
|
"chilly", "chubby", "circular", "classic", "clean", "clear", "clear-cut",
|
||||||
|
"clever", "close", "closed", "cloudy", "clueless", "clumsy", "cluttered",
|
||||||
|
"coarse", "cold", "colorful", "colorless", "colossal", "comfortable",
|
||||||
|
"common", "compassionate", "competent", "complete", "complex", "complicated",
|
||||||
|
"composed", "concerned", "concrete", "confused", "conscious", "considerate",
|
||||||
|
"constant", "content", "conventional", "cooked", "cool", "cooperative",
|
||||||
|
"coordinated", "corny", "corrupt", "costly", "courageous", "courteous",
|
||||||
|
"crafty"
|
||||||
|
];
|
||||||
|
|
||||||
|
let animals = [
|
||||||
|
"aardvark", "african buffalo", "albatross", "alligator", "alpaca", "ant",
|
||||||
|
"anteater", "antelope", "ape", "armadillo", "baboon", "badger", "barracuda",
|
||||||
|
"bat", "bear", "beaver", "bee", "bison", "black panther", "blue jay", "boar",
|
||||||
|
"butterfly", "camel", "capybara", "carduelis", "caribou", "cassowary", "cat",
|
||||||
|
"caterpillar", "cattle", "chamois", "cheetah", "chicken", "chimpanzee",
|
||||||
|
"chinchilla", "chough", "clam", "cobra", "cockroach", "cod", "cormorant",
|
||||||
|
"coyote", "crab", "crane", "crocodile", "crow", "curlew", "deer", "dinosaur",
|
||||||
|
"dog", "dolphin", "domestic pig", "donkey", "dotterel", "dove", "dragonfly",
|
||||||
|
"duck", "dugong", "dunlin", "eagle", "echidna", "eel", "elephant seal",
|
||||||
|
"elephant", "elk", "emu", "falcon", "ferret", "finch", "fish", "flamingo",
|
||||||
|
"fly", "fox", "frog", "gaur", "gazelle", "gerbil", "giant panda", "giraffe",
|
||||||
|
"gnat", "goat", "goldfish", "goose", "gorilla", "goshawk", "grasshopper",
|
||||||
|
"grouse", "guanaco", "guinea fowl", "guinea pig", "gull", "hamster", "hare",
|
||||||
|
"hawk", "hedgehog", "heron", "herring", "hippopotamus", "hornet", "horse",
|
||||||
|
"human", "hummingbird", "hyena", "ibex", "ibis", "jackal", "jaguar", "jay",
|
||||||
|
"jellyfish", "kangaroo", "kingfisher", "koala", "komodo dragon", "kookabura",
|
||||||
|
"kouprey", "kudu", "lapwing", "lark", "lemur", "leopard", "lion", "llama",
|
||||||
|
"lobster", "locust", "loris", "louse", "lyrebird", "magpie", "mallard",
|
||||||
|
"manatee", "mandrill", "mantis", "marten", "meerkat", "mink", "mole",
|
||||||
|
"mongoose", "monkey", "moose", "mosquito", "mouse", "mule", "narwhal", "newt",
|
||||||
|
"nightingale", "octopus", "okapi", "opossum", "oryx", "ostrich", "otter",
|
||||||
|
"owl", "oyster", "parrot", "partridge", "peafowl", "pelican", "penguin",
|
||||||
|
"pheasant", "pigeon", "pinniped", "polar bear", "pony", "porcupine",
|
||||||
|
"porpoise", "prairie dog", "quail", "quelea", "quetzal", "rabbit", "raccoon",
|
||||||
|
"ram", "rat", "raven", "red deer", "red panda", "reindeer", "rhinoceros",
|
||||||
|
"rook", "salamander", "salmon", "sand dollar", "sandpiper", "sardine",
|
||||||
|
"scorpion", "sea lion", "sea urchin", "seahorse", "shark", "sheep", "shrew",
|
||||||
|
"skunk", "snail", "snake", "sparrow", "spider", "spoonbill", "squid",
|
||||||
|
"wallaby", "wildebeest"
|
||||||
|
];
|
||||||
|
|
||||||
|
let keys = [];
|
||||||
|
|
||||||
|
for animal in animals {
|
||||||
|
for adjective in adjectives {
|
||||||
|
for adverb in adverbs {
|
||||||
|
keys.push(adverb + " " + adjective + " " + animal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let map = #{};
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
for key in keys {
|
||||||
|
map[key] = i;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sum = 0;
|
||||||
|
|
||||||
|
for key in keys {
|
||||||
|
sum += map[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in keys {
|
||||||
|
map.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Sum = " + sum);
|
||||||
|
print("Finished. Run time = " + now.elapsed + " seconds.");
|
14
src/any.rs
14
src/any.rs
@ -1,11 +1,9 @@
|
|||||||
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
|
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
|
||||||
|
|
||||||
|
use crate::module::Module;
|
||||||
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_module"))]
|
|
||||||
use crate::module::Module;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
use crate::parser::FLOAT;
|
use crate::parser::FLOAT;
|
||||||
|
|
||||||
@ -160,7 +158,6 @@ pub enum Union {
|
|||||||
Array(Box<Array>),
|
Array(Box<Array>),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Map(Box<Map>),
|
Map(Box<Map>),
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Module(Box<Module>),
|
Module(Box<Module>),
|
||||||
Variant(Box<Box<dyn Variant>>),
|
Variant(Box<Box<dyn Variant>>),
|
||||||
}
|
}
|
||||||
@ -198,7 +195,6 @@ impl Dynamic {
|
|||||||
Union::Array(_) => TypeId::of::<Array>(),
|
Union::Array(_) => TypeId::of::<Array>(),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(_) => TypeId::of::<Map>(),
|
Union::Map(_) => TypeId::of::<Map>(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Union::Module(_) => TypeId::of::<Module>(),
|
Union::Module(_) => TypeId::of::<Module>(),
|
||||||
Union::Variant(value) => (***value).type_id(),
|
Union::Variant(value) => (***value).type_id(),
|
||||||
}
|
}
|
||||||
@ -218,7 +214,6 @@ impl Dynamic {
|
|||||||
Union::Array(_) => "array",
|
Union::Array(_) => "array",
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(_) => "map",
|
Union::Map(_) => "map",
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Union::Module(_) => "sub-scope",
|
Union::Module(_) => "sub-scope",
|
||||||
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
@ -242,7 +237,6 @@ impl fmt::Display for Dynamic {
|
|||||||
Union::Array(value) => write!(f, "{:?}", value),
|
Union::Array(value) => write!(f, "{:?}", value),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(value) => write!(f, "#{:?}", value),
|
Union::Map(value) => write!(f, "#{:?}", value),
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Union::Module(value) => write!(f, "{:?}", value),
|
Union::Module(value) => write!(f, "{:?}", value),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
@ -266,7 +260,6 @@ impl fmt::Debug for Dynamic {
|
|||||||
Union::Array(value) => write!(f, "{:?}", value),
|
Union::Array(value) => write!(f, "{:?}", value),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(value) => write!(f, "#{:?}", value),
|
Union::Map(value) => write!(f, "#{:?}", value),
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Union::Module(value) => write!(f, "{:?}", value),
|
Union::Module(value) => write!(f, "{:?}", value),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
@ -290,7 +283,6 @@ impl Clone for Dynamic {
|
|||||||
Union::Array(ref value) => Self(Union::Array(value.clone())),
|
Union::Array(ref value) => Self(Union::Array(value.clone())),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(ref value) => Self(Union::Map(value.clone())),
|
Union::Map(ref value) => Self(Union::Map(value.clone())),
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Union::Module(ref value) => Self(Union::Module(value.clone())),
|
Union::Module(ref value) => Self(Union::Module(value.clone())),
|
||||||
Union::Variant(ref value) => (***value).clone_into_dynamic(),
|
Union::Variant(ref value) => (***value).clone_into_dynamic(),
|
||||||
}
|
}
|
||||||
@ -426,7 +418,6 @@ impl Dynamic {
|
|||||||
Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Union::Module(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
Union::Module(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
||||||
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
|
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
|
||||||
}
|
}
|
||||||
@ -470,7 +461,6 @@ impl Dynamic {
|
|||||||
Union::Array(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
|
Union::Array(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
|
Union::Map(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Union::Module(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
|
Union::Module(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
|
||||||
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).unwrap(),
|
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).unwrap(),
|
||||||
}
|
}
|
||||||
@ -498,7 +488,6 @@ impl Dynamic {
|
|||||||
Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
|
Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
|
Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Union::Module(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
|
Union::Module(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
|
||||||
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>(),
|
||||||
}
|
}
|
||||||
@ -524,7 +513,6 @@ impl Dynamic {
|
|||||||
Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
|
Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
|
Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Union::Module(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
|
Union::Module(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
|
||||||
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(),
|
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(),
|
||||||
}
|
}
|
||||||
|
73
src/api.rs
73
src/api.rs
@ -997,6 +997,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Call a script function defined in an `AST` with multiple arguments.
|
/// Call a script function defined in an `AST` with multiple arguments.
|
||||||
|
/// Arguments are passed as a tuple.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -1040,6 +1041,67 @@ impl Engine {
|
|||||||
args: A,
|
args: A,
|
||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
let mut arg_values = args.into_vec();
|
let mut arg_values = args.into_vec();
|
||||||
|
let result = self.call_fn_dynamic(scope, ast, name, arg_values.as_mut())?;
|
||||||
|
|
||||||
|
let return_type = self.map_type_name(result.type_name());
|
||||||
|
|
||||||
|
return result.try_cast().ok_or_else(|| {
|
||||||
|
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||||
|
return_type.into(),
|
||||||
|
Position::none(),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments.
|
||||||
|
///
|
||||||
|
/// ## WARNING
|
||||||
|
///
|
||||||
|
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
|
||||||
|
/// This is to avoid unnecessarily cloning the arguments.
|
||||||
|
/// Do you use the arguments after this call. If you need them afterwards,
|
||||||
|
/// clone them _before_ calling this function.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||||
|
/// # #[cfg(not(feature = "no_function"))]
|
||||||
|
/// # {
|
||||||
|
/// use rhai::{Engine, Scope};
|
||||||
|
///
|
||||||
|
/// let engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// let ast = engine.compile(r"
|
||||||
|
/// fn add(x, y) { len(x) + y + foo }
|
||||||
|
/// fn add1(x) { len(x) + 1 + foo }
|
||||||
|
/// fn bar() { foo/2 }
|
||||||
|
/// ")?;
|
||||||
|
///
|
||||||
|
/// let mut scope = Scope::new();
|
||||||
|
/// scope.push("foo", 42_i64);
|
||||||
|
///
|
||||||
|
/// // Call the script-defined function
|
||||||
|
/// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", &mut [ String::from("abc").into(), 123_i64.into() ])?;
|
||||||
|
/// assert_eq!(result.cast::<i64>(), 168);
|
||||||
|
///
|
||||||
|
/// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", &mut [ String::from("abc").into() ])?;
|
||||||
|
/// assert_eq!(result.cast::<i64>(), 46);
|
||||||
|
///
|
||||||
|
/// let result= engine.call_fn_dynamic(&mut scope, &ast, "bar", &mut [])?;
|
||||||
|
/// assert_eq!(result.cast::<i64>(), 21);
|
||||||
|
/// # }
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
pub fn call_fn_dynamic(
|
||||||
|
&self,
|
||||||
|
scope: &mut Scope,
|
||||||
|
ast: &AST,
|
||||||
|
name: &str,
|
||||||
|
arg_values: &mut [Dynamic],
|
||||||
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
|
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
|
||||||
let lib = ast.lib();
|
let lib = ast.lib();
|
||||||
let pos = Position::none();
|
let pos = Position::none();
|
||||||
@ -1051,16 +1113,7 @@ impl Engine {
|
|||||||
let mut state = State::new();
|
let mut state = State::new();
|
||||||
let args = args.as_mut();
|
let args = args.as_mut();
|
||||||
|
|
||||||
let result = self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, pos, 0)?;
|
self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, pos, 0)
|
||||||
|
|
||||||
let return_type = self.map_type_name(result.type_name());
|
|
||||||
|
|
||||||
return result.try_cast().ok_or_else(|| {
|
|
||||||
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
|
||||||
return_type.into(),
|
|
||||||
pos,
|
|
||||||
))
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize the `AST` with constants defined in an external Scope.
|
/// Optimize the `AST` with constants defined in an external Scope.
|
||||||
|
143
src/engine.rs
143
src/engine.rs
@ -4,7 +4,7 @@ use crate::any::{Dynamic, Union};
|
|||||||
use crate::calc_fn_hash;
|
use crate::calc_fn_hash;
|
||||||
use crate::error::ParseErrorType;
|
use crate::error::ParseErrorType;
|
||||||
use crate::fn_native::{CallableFunction, FnCallArgs, Shared};
|
use crate::fn_native::{CallableFunction, FnCallArgs, Shared};
|
||||||
use crate::module::Module;
|
use crate::module::{resolvers, Module, ModuleResolver};
|
||||||
use crate::optimize::OptimizationLevel;
|
use crate::optimize::OptimizationLevel;
|
||||||
use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage};
|
use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage};
|
||||||
use crate::parser::{Expr, FnAccess, FnDef, ImmutableString, ReturnType, Stmt, AST, INT};
|
use crate::parser::{Expr, FnAccess, FnDef, ImmutableString, ReturnType, Stmt, AST, INT};
|
||||||
@ -12,14 +12,11 @@ use crate::r#unsafe::{unsafe_cast_var_name_to_lifetime, unsafe_mut_cast_to_lifet
|
|||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||||
use crate::token::Position;
|
use crate::token::Position;
|
||||||
use crate::utils::StaticVec;
|
use crate::utils::{StaticVec, StraightHasherBuilder};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
use crate::parser::FLOAT;
|
use crate::parser::FLOAT;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
use crate::module::{resolvers, ModuleResolver};
|
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
@ -196,7 +193,7 @@ impl State {
|
|||||||
///
|
///
|
||||||
/// The key of the `HashMap` is a `u64` hash calculated by the function `calc_fn_hash`.
|
/// The key of the `HashMap` is a `u64` hash calculated by the function `calc_fn_hash`.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct FunctionsLib(HashMap<u64, Shared<FnDef>>);
|
pub struct FunctionsLib(HashMap<u64, Shared<FnDef>, StraightHasherBuilder>);
|
||||||
|
|
||||||
impl FunctionsLib {
|
impl FunctionsLib {
|
||||||
/// Create a new `FunctionsLib` from a collection of `FnDef`.
|
/// Create a new `FunctionsLib` from a collection of `FnDef`.
|
||||||
@ -261,7 +258,7 @@ impl From<Vec<(u64, Shared<FnDef>)>> for FunctionsLib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for FunctionsLib {
|
impl Deref for FunctionsLib {
|
||||||
type Target = HashMap<u64, Shared<FnDef>>;
|
type Target = HashMap<u64, Shared<FnDef>, StraightHasherBuilder>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
@ -269,7 +266,7 @@ impl Deref for FunctionsLib {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DerefMut for FunctionsLib {
|
impl DerefMut for FunctionsLib {
|
||||||
fn deref_mut(&mut self) -> &mut HashMap<u64, Shared<FnDef>> {
|
fn deref_mut(&mut self) -> &mut HashMap<u64, Shared<FnDef>, StraightHasherBuilder> {
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -297,7 +294,6 @@ pub struct Engine {
|
|||||||
pub(crate) packages: PackagesCollection,
|
pub(crate) packages: PackagesCollection,
|
||||||
|
|
||||||
/// A module resolution service.
|
/// A module resolution service.
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
pub(crate) module_resolver: Option<Box<dyn ModuleResolver>>,
|
pub(crate) module_resolver: Option<Box<dyn ModuleResolver>>,
|
||||||
|
|
||||||
/// A hashmap mapping type names to pretty-print names.
|
/// A hashmap mapping type names to pretty-print names.
|
||||||
@ -350,8 +346,7 @@ impl Default for Engine {
|
|||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())),
|
module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())),
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(any(feature = "no_module", feature = "no_std"))]
|
||||||
#[cfg(feature = "no_std")]
|
|
||||||
module_resolver: None,
|
module_resolver: None,
|
||||||
|
|
||||||
type_names: HashMap::new(),
|
type_names: HashMap::new(),
|
||||||
@ -441,10 +436,7 @@ fn search_scope<'s, 'a>(
|
|||||||
Expr::Variable(x) => x.as_ref(),
|
Expr::Variable(x) => x.as_ref(),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let index = if state.always_search { None } else { *index };
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
{
|
|
||||||
if let Some(modules) = modules.as_ref() {
|
if let Some(modules) = modules.as_ref() {
|
||||||
let module = if let Some(index) = modules.index() {
|
let module = if let Some(index) = modules.index() {
|
||||||
scope
|
scope
|
||||||
@ -455,9 +447,9 @@ fn search_scope<'s, 'a>(
|
|||||||
} else {
|
} else {
|
||||||
let (id, root_pos) = modules.get(0);
|
let (id, root_pos) = modules.get(0);
|
||||||
|
|
||||||
scope.find_module(id).ok_or_else(|| {
|
scope
|
||||||
Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos))
|
.find_module_internal(id)
|
||||||
})?
|
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))?
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok((
|
return Ok((
|
||||||
@ -468,7 +460,8 @@ fn search_scope<'s, 'a>(
|
|||||||
*pos,
|
*pos,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
let index = if state.always_search { None } else { *index };
|
||||||
|
|
||||||
let index = if let Some(index) = index {
|
let index = if let Some(index) = index {
|
||||||
scope.len() - index.get()
|
scope.len() - index.get()
|
||||||
@ -495,8 +488,6 @@ impl Engine {
|
|||||||
Self {
|
Self {
|
||||||
packages: Default::default(),
|
packages: Default::default(),
|
||||||
global_module: Default::default(),
|
global_module: Default::default(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
module_resolver: None,
|
module_resolver: None,
|
||||||
|
|
||||||
type_names: HashMap::new(),
|
type_names: HashMap::new(),
|
||||||
@ -952,21 +943,24 @@ impl Engine {
|
|||||||
let mut idx_val = idx_values.pop();
|
let mut idx_val = idx_values.pop();
|
||||||
|
|
||||||
if is_index {
|
if is_index {
|
||||||
|
let pos = rhs.position();
|
||||||
|
|
||||||
match rhs {
|
match rhs {
|
||||||
// xxx[idx].dot_rhs... | xxx[idx][dot_rhs]...
|
// xxx[idx].expr... | xxx[idx][expr]...
|
||||||
Expr::Dot(x) | Expr::Index(x) => {
|
Expr::Dot(x) | Expr::Index(x) => {
|
||||||
|
let (idx, expr, pos) = x.as_ref();
|
||||||
let is_idx = matches!(rhs, Expr::Index(_));
|
let is_idx = matches!(rhs, Expr::Index(_));
|
||||||
let pos = x.0.position();
|
let idx_pos = idx.position();
|
||||||
let this_ptr = &mut self
|
let this_ptr = &mut self.get_indexed_mut(
|
||||||
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, false)?;
|
state, lib, obj, is_ref, idx_val, idx_pos, op_pos, false,
|
||||||
|
)?;
|
||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, lib, this_ptr, &x.1, idx_values, is_idx, x.2, level, new_val,
|
state, lib, this_ptr, expr, idx_values, is_idx, *pos, level, new_val,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// xxx[rhs] = new_val
|
// xxx[rhs] = new_val
|
||||||
_ if new_val.is_some() => {
|
_ if new_val.is_some() => {
|
||||||
let pos = rhs.position();
|
|
||||||
let this_ptr = &mut self
|
let this_ptr = &mut self
|
||||||
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, true)?;
|
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, true)?;
|
||||||
|
|
||||||
@ -975,16 +969,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
// xxx[rhs]
|
// xxx[rhs]
|
||||||
_ => self
|
_ => self
|
||||||
.get_indexed_mut(
|
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, false)
|
||||||
state,
|
|
||||||
lib,
|
|
||||||
obj,
|
|
||||||
is_ref,
|
|
||||||
idx_val,
|
|
||||||
rhs.position(),
|
|
||||||
op_pos,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.map(|v| (v.clone_into_dynamic(), false)),
|
.map(|v| (v.clone_into_dynamic(), false)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1050,57 +1035,51 @@ impl Engine {
|
|||||||
.map(|(v, _)| (v, false))
|
.map(|(v, _)| (v, false))
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
// {xxx:map}.idx_lhs[idx_expr] | {xxx:map}.dot_lhs.rhs
|
// {xxx:map}.prop[expr] | {xxx:map}.prop.expr
|
||||||
Expr::Index(x) | Expr::Dot(x) if obj.is::<Map>() => {
|
Expr::Index(x) | Expr::Dot(x) if obj.is::<Map>() => {
|
||||||
|
let (prop, expr, pos) = x.as_ref();
|
||||||
let is_idx = matches!(rhs, Expr::Index(_));
|
let is_idx = matches!(rhs, Expr::Index(_));
|
||||||
|
|
||||||
let mut val = if let Expr::Property(p) = &x.0 {
|
let mut val = if let Expr::Property(p) = prop {
|
||||||
let ((prop, _, _), _) = p.as_ref();
|
let ((prop, _, _), _) = p.as_ref();
|
||||||
let index = prop.clone().into();
|
let index = prop.clone().into();
|
||||||
self.get_indexed_mut(state, lib, obj, is_ref, index, x.2, op_pos, false)?
|
self.get_indexed_mut(state, lib, obj, is_ref, index, *pos, op_pos, false)?
|
||||||
} else {
|
} else {
|
||||||
// Syntax error
|
unreachable!();
|
||||||
return Err(Box::new(EvalAltResult::ErrorDotExpr(
|
|
||||||
"".into(),
|
|
||||||
rhs.position(),
|
|
||||||
)));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, lib, &mut val, &x.1, idx_values, is_idx, x.2, level, new_val,
|
state, lib, &mut val, expr, idx_values, is_idx, *pos, level, new_val,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// xxx.idx_lhs[idx_expr] | xxx.dot_lhs.rhs
|
// xxx.prop[expr] | xxx.prop.expr
|
||||||
Expr::Index(x) | Expr::Dot(x) => {
|
Expr::Index(x) | Expr::Dot(x) => {
|
||||||
|
let (prop, expr, pos) = x.as_ref();
|
||||||
let is_idx = matches!(rhs, Expr::Index(_));
|
let is_idx = matches!(rhs, Expr::Index(_));
|
||||||
let args = &mut [obj, &mut Default::default()];
|
let args = &mut [obj, &mut Default::default()];
|
||||||
|
|
||||||
let (mut val, updated) = if let Expr::Property(p) = &x.0 {
|
let (mut val, updated) = if let Expr::Property(p) = prop {
|
||||||
let ((_, getter, _), _) = p.as_ref();
|
let ((_, getter, _), _) = p.as_ref();
|
||||||
let args = &mut args[..1];
|
let args = &mut args[..1];
|
||||||
self.exec_fn_call(state, lib, getter, true, 0, args, is_ref, None, x.2, 0)?
|
self.exec_fn_call(state, lib, getter, true, 0, args, is_ref, None, *pos, 0)?
|
||||||
} else {
|
} else {
|
||||||
// Syntax error
|
unreachable!();
|
||||||
return Err(Box::new(EvalAltResult::ErrorDotExpr(
|
|
||||||
"".into(),
|
|
||||||
rhs.position(),
|
|
||||||
)));
|
|
||||||
};
|
};
|
||||||
let val = &mut val;
|
let val = &mut val;
|
||||||
let target = &mut val.into();
|
let target = &mut val.into();
|
||||||
|
|
||||||
let (result, may_be_changed) = self.eval_dot_index_chain_helper(
|
let (result, may_be_changed) = self.eval_dot_index_chain_helper(
|
||||||
state, lib, target, &x.1, idx_values, is_idx, x.2, level, new_val,
|
state, lib, target, expr, idx_values, is_idx, *pos, level, new_val,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Feed the value back via a setter just in case it has been updated
|
// Feed the value back via a setter just in case it has been updated
|
||||||
if updated || may_be_changed {
|
if updated || may_be_changed {
|
||||||
if let Expr::Property(p) = &x.0 {
|
if let Expr::Property(p) = prop {
|
||||||
let ((_, _, setter), _) = p.as_ref();
|
let ((_, _, setter), _) = p.as_ref();
|
||||||
// Re-use args because the first &mut parameter will not be consumed
|
// Re-use args because the first &mut parameter will not be consumed
|
||||||
args[1] = val;
|
args[1] = val;
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
state, lib, setter, true, 0, args, is_ref, None, x.2, 0,
|
state, lib, setter, true, 0, args, is_ref, None, *pos, 0,
|
||||||
)
|
)
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
// If there is no setter, no need to feed it back because the property is read-only
|
// If there is no setter, no need to feed it back because the property is read-only
|
||||||
@ -1127,13 +1106,16 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
lib: &FunctionsLib,
|
lib: &FunctionsLib,
|
||||||
dot_lhs: &Expr,
|
expr: &Expr,
|
||||||
dot_rhs: &Expr,
|
|
||||||
is_index: bool,
|
|
||||||
op_pos: Position,
|
|
||||||
level: usize,
|
level: usize,
|
||||||
new_val: Option<Dynamic>,
|
new_val: Option<Dynamic>,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
|
let ((dot_lhs, dot_rhs, op_pos), is_index) = match expr {
|
||||||
|
Expr::Index(x) => (x.as_ref(), true),
|
||||||
|
Expr::Dot(x) => (x.as_ref(), false),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
let idx_values = &mut StaticVec::new();
|
let idx_values = &mut StaticVec::new();
|
||||||
|
|
||||||
self.eval_indexed_chain(scope, state, lib, dot_rhs, idx_values, 0, level)?;
|
self.eval_indexed_chain(scope, state, lib, dot_rhs, idx_values, 0, level)?;
|
||||||
@ -1158,7 +1140,7 @@ impl Engine {
|
|||||||
|
|
||||||
let this_ptr = &mut target.into();
|
let this_ptr = &mut target.into();
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, lib, this_ptr, dot_rhs, idx_values, is_index, op_pos, level, new_val,
|
state, lib, this_ptr, dot_rhs, idx_values, is_index, *op_pos, level, new_val,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
}
|
}
|
||||||
@ -1173,7 +1155,7 @@ impl Engine {
|
|||||||
let val = self.eval_expr(scope, state, lib, expr, level)?;
|
let val = self.eval_expr(scope, state, lib, expr, level)?;
|
||||||
let this_ptr = &mut val.into();
|
let this_ptr = &mut val.into();
|
||||||
self.eval_dot_index_chain_helper(
|
self.eval_dot_index_chain_helper(
|
||||||
state, lib, this_ptr, dot_rhs, idx_values, is_index, op_pos, level, new_val,
|
state, lib, this_ptr, dot_rhs, idx_values, is_index, *op_pos, level, new_val,
|
||||||
)
|
)
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
}
|
}
|
||||||
@ -1489,14 +1471,14 @@ impl Engine {
|
|||||||
Expr::Variable(_) => unreachable!(),
|
Expr::Variable(_) => unreachable!(),
|
||||||
// idx_lhs[idx_expr] op= rhs
|
// idx_lhs[idx_expr] op= rhs
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(x) => self.eval_dot_index_chain(
|
Expr::Index(_) => {
|
||||||
scope, state, lib, &x.0, &x.1, true, x.2, level, new_val,
|
self.eval_dot_index_chain(scope, state, lib, lhs_expr, level, new_val)
|
||||||
),
|
}
|
||||||
// dot_lhs.dot_rhs op= rhs
|
// dot_lhs.dot_rhs op= rhs
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(x) => self.eval_dot_index_chain(
|
Expr::Dot(_) => {
|
||||||
scope, state, lib, &x.0, &x.1, false, *op_pos, level, new_val,
|
self.eval_dot_index_chain(scope, state, lib, lhs_expr, level, new_val)
|
||||||
),
|
}
|
||||||
// Error assignment to constant
|
// Error assignment to constant
|
||||||
expr if expr.is_constant() => {
|
expr if expr.is_constant() => {
|
||||||
Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
|
Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
|
||||||
@ -1513,15 +1495,11 @@ impl Engine {
|
|||||||
|
|
||||||
// lhs[idx_expr]
|
// lhs[idx_expr]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(x) => {
|
Expr::Index(_) => self.eval_dot_index_chain(scope, state, lib, expr, level, None),
|
||||||
self.eval_dot_index_chain(scope, state, lib, &x.0, &x.1, true, x.2, level, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
// lhs.dot_rhs
|
// lhs.dot_rhs
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(x) => {
|
Expr::Dot(_) => self.eval_dot_index_chain(scope, state, lib, expr, level, None),
|
||||||
self.eval_dot_index_chain(scope, state, lib, &x.0, &x.1, false, x.2, level, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(x) => Ok(Dynamic(Union::Array(Box::new(
|
Expr::Array(x) => Ok(Dynamic(Union::Array(Box::new(
|
||||||
@ -1621,7 +1599,6 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Module-qualified function call
|
// Module-qualified function call
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Expr::FnCall(x) if x.1.is_some() => {
|
Expr::FnCall(x) if x.1.is_some() => {
|
||||||
let ((name, _, pos), modules, hash_fn_def, args_expr, def_val) = x.as_ref();
|
let ((name, _, pos), modules, hash_fn_def, args_expr, def_val) = x.as_ref();
|
||||||
let modules = modules.as_ref().unwrap();
|
let modules = modules.as_ref().unwrap();
|
||||||
@ -1642,7 +1619,7 @@ impl Engine {
|
|||||||
.downcast_mut::<Module>()
|
.downcast_mut::<Module>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
} else {
|
} else {
|
||||||
scope.find_module(id).ok_or_else(|| {
|
scope.find_module_internal(id).ok_or_else(|| {
|
||||||
Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos))
|
Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos))
|
||||||
})?
|
})?
|
||||||
};
|
};
|
||||||
@ -1931,11 +1908,6 @@ impl Engine {
|
|||||||
|
|
||||||
// Import statement
|
// Import statement
|
||||||
Stmt::Import(x) => {
|
Stmt::Import(x) => {
|
||||||
#[cfg(feature = "no_module")]
|
|
||||||
unreachable!();
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
{
|
|
||||||
let (expr, (name, pos)) = x.as_ref();
|
let (expr, (name, pos)) = x.as_ref();
|
||||||
|
|
||||||
// Guard against too many modules
|
// Guard against too many modules
|
||||||
@ -1946,6 +1918,8 @@ impl Engine {
|
|||||||
if let Some(path) = self
|
if let Some(path) = self
|
||||||
.eval_expr(scope, state, lib, &expr, level)?
|
.eval_expr(scope, state, lib, &expr, level)?
|
||||||
.try_cast::<ImmutableString>()
|
.try_cast::<ImmutableString>()
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
{
|
{
|
||||||
if let Some(resolver) = &self.module_resolver {
|
if let Some(resolver) = &self.module_resolver {
|
||||||
// Use an empty scope to create a module
|
// Use an empty scope to create a module
|
||||||
@ -1953,7 +1927,7 @@ impl Engine {
|
|||||||
resolver.resolve(self, Scope::new(), &path, expr.position())?;
|
resolver.resolve(self, Scope::new(), &path, expr.position())?;
|
||||||
|
|
||||||
let mod_name = unsafe_cast_var_name_to_lifetime(name, &state);
|
let mod_name = unsafe_cast_var_name_to_lifetime(name, &state);
|
||||||
scope.push_module(mod_name, module);
|
scope.push_module_internal(mod_name, module);
|
||||||
|
|
||||||
state.modules += 1;
|
state.modules += 1;
|
||||||
|
|
||||||
@ -1964,11 +1938,14 @@ impl Engine {
|
|||||||
expr.position(),
|
expr.position(),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "no_module")]
|
||||||
|
Ok(Default::default())
|
||||||
} else {
|
} else {
|
||||||
Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position())))
|
Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Export statement
|
// Export statement
|
||||||
Stmt::Export(list) => {
|
Stmt::Export(list) => {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use crate::any::{Dynamic, Variant};
|
use crate::any::{Dynamic, Variant};
|
||||||
use crate::calc_fn_hash;
|
use crate::calc_fn_hash;
|
||||||
use crate::engine::{make_getter, make_setter, Engine, FunctionsLib, FUNC_INDEXER};
|
use crate::engine::{make_getter, make_setter, Engine, FunctionsLib, FUNC_INDEXER};
|
||||||
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn};
|
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync};
|
||||||
use crate::parser::{
|
use crate::parser::{
|
||||||
FnAccess,
|
FnAccess,
|
||||||
FnAccess::{Private, Public},
|
FnAccess::{Private, Public},
|
||||||
@ -12,7 +12,7 @@ use crate::parser::{
|
|||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
||||||
use crate::token::{Position, Token};
|
use crate::token::{Position, Token};
|
||||||
use crate::utils::StaticVec;
|
use crate::utils::{StaticVec, StraightHasherBuilder};
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
@ -44,10 +44,14 @@ pub struct Module {
|
|||||||
variables: HashMap<String, Dynamic>,
|
variables: HashMap<String, Dynamic>,
|
||||||
|
|
||||||
/// Flattened collection of all module variables, including those in sub-modules.
|
/// Flattened collection of all module variables, including those in sub-modules.
|
||||||
all_variables: HashMap<u64, Dynamic>,
|
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
|
||||||
|
|
||||||
/// External Rust functions.
|
/// External Rust functions.
|
||||||
functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, CallableFunction)>,
|
functions: HashMap<
|
||||||
|
u64,
|
||||||
|
(String, FnAccess, StaticVec<TypeId>, CallableFunction),
|
||||||
|
StraightHasherBuilder,
|
||||||
|
>,
|
||||||
|
|
||||||
/// Script-defined functions.
|
/// Script-defined functions.
|
||||||
lib: FunctionsLib,
|
lib: FunctionsLib,
|
||||||
@ -57,7 +61,7 @@ pub struct Module {
|
|||||||
|
|
||||||
/// Flattened collection of all external Rust functions, native or scripted,
|
/// Flattened collection of all external Rust functions, native or scripted,
|
||||||
/// including those in sub-modules.
|
/// including those in sub-modules.
|
||||||
all_functions: HashMap<u64, CallableFunction>,
|
all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Module {
|
impl fmt::Debug for Module {
|
||||||
@ -101,7 +105,7 @@ impl Module {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn new_with_capacity(capacity: usize) -> Self {
|
pub fn new_with_capacity(capacity: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
functions: HashMap::with_capacity(capacity),
|
functions: HashMap::with_capacity_and_hasher(capacity, StraightHasherBuilder),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -941,23 +945,7 @@ impl ModuleRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Trait that encapsulates a module resolution service.
|
/// Trait that encapsulates a module resolution service.
|
||||||
#[cfg(not(feature = "no_module"))]
|
pub trait ModuleResolver: SendSync {
|
||||||
#[cfg(not(feature = "sync"))]
|
|
||||||
pub trait ModuleResolver {
|
|
||||||
/// Resolve a module based on a path string.
|
|
||||||
fn resolve(
|
|
||||||
&self,
|
|
||||||
engine: &Engine,
|
|
||||||
scope: Scope,
|
|
||||||
path: &str,
|
|
||||||
pos: Position,
|
|
||||||
) -> Result<Module, Box<EvalAltResult>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait that encapsulates a module resolution service.
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
#[cfg(feature = "sync")]
|
|
||||||
pub trait ModuleResolver: Send + Sync {
|
|
||||||
/// Resolve a module based on a path string.
|
/// Resolve a module based on a path string.
|
||||||
fn resolve(
|
fn resolve(
|
||||||
&self,
|
&self,
|
||||||
@ -975,6 +963,8 @@ pub mod resolvers {
|
|||||||
pub use super::file::FileModuleResolver;
|
pub use super::file::FileModuleResolver;
|
||||||
pub use super::stat::StaticModuleResolver;
|
pub use super::stat::StaticModuleResolver;
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "no_module")]
|
||||||
|
pub mod resolvers {}
|
||||||
|
|
||||||
/// Script file-based module resolver.
|
/// Script file-based module resolver.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
@ -37,6 +37,10 @@ impl OptimizationLevel {
|
|||||||
pub fn is_none(self) -> bool {
|
pub fn is_none(self) -> bool {
|
||||||
self == Self::None
|
self == Self::None
|
||||||
}
|
}
|
||||||
|
/// Is the `OptimizationLevel` Simple.
|
||||||
|
pub fn is_simple(self) -> bool {
|
||||||
|
self == Self::Simple
|
||||||
|
}
|
||||||
/// Is the `OptimizationLevel` Full.
|
/// Is the `OptimizationLevel` Full.
|
||||||
pub fn is_full(self) -> bool {
|
pub fn is_full(self) -> bool {
|
||||||
self == Self::Full
|
self == Self::Full
|
||||||
@ -381,10 +385,10 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
// ( stmt )
|
// ( stmt )
|
||||||
stmt => Expr::Stmt(Box::new((stmt, x.1))),
|
stmt => Expr::Stmt(Box::new((stmt, x.1))),
|
||||||
},
|
},
|
||||||
// id = expr
|
// id op= expr
|
||||||
Expr::Assignment(x) => match x.2 {
|
Expr::Assignment(x) => match x.2 {
|
||||||
//id = id2 op= expr2
|
//id = id2 op= rhs
|
||||||
Expr::Assignment(x2) if x.1 == "=" => match (x.0, x2.0) {
|
Expr::Assignment(x2) if x.1.is_empty() => match (x.0, &x2.0) {
|
||||||
// var = var op= expr2 -> var op= expr2
|
// var = var op= expr2 -> var op= expr2
|
||||||
(Expr::Variable(a), Expr::Variable(b))
|
(Expr::Variable(a), Expr::Variable(b))
|
||||||
if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 =>
|
if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 =>
|
||||||
@ -393,14 +397,10 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
|||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
Expr::Assignment(Box::new((Expr::Variable(a), x2.1, optimize_expr(x2.2, state), x.3)))
|
Expr::Assignment(Box::new((Expr::Variable(a), x2.1, optimize_expr(x2.2, state), x.3)))
|
||||||
}
|
}
|
||||||
// id1 = id2 op= expr2
|
// expr1 = expr2 op= rhs
|
||||||
(id1, id2) => {
|
(expr1, _) => Expr::Assignment(Box::new((expr1, x.1, optimize_expr(Expr::Assignment(x2), state), x.3))),
|
||||||
Expr::Assignment(Box::new((
|
|
||||||
id1, x.1, Expr::Assignment(Box::new((id2, x2.1, optimize_expr(x2.2, state), x2.3))), x.3,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
// id op= expr
|
// expr = rhs
|
||||||
expr => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(expr, state), x.3))),
|
expr => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(expr, state), x.3))),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
104
src/parser.rs
104
src/parser.rs
@ -4,17 +4,11 @@ use crate::any::{Dynamic, Union};
|
|||||||
use crate::calc_fn_hash;
|
use crate::calc_fn_hash;
|
||||||
use crate::engine::{make_getter, make_setter, Engine, FunctionsLib};
|
use crate::engine::{make_getter, make_setter, Engine, FunctionsLib};
|
||||||
use crate::error::{LexError, ParseError, ParseErrorType};
|
use crate::error::{LexError, ParseError, ParseErrorType};
|
||||||
|
use crate::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};
|
||||||
use crate::token::{Position, Token, TokenIterator};
|
use crate::token::{Position, Token, TokenIterator};
|
||||||
use crate::utils::StaticVec;
|
use crate::utils::{StaticVec, StraightHasherBuilder};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
use crate::module::ModuleRef;
|
|
||||||
|
|
||||||
#[cfg(feature = "no_module")]
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Hash, Copy, Default)]
|
|
||||||
pub struct ModuleRef;
|
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
@ -644,7 +638,6 @@ impl Expr {
|
|||||||
|
|
||||||
Self::Variable(_) => match token {
|
Self::Variable(_) => match token {
|
||||||
Token::LeftBracket | Token::LeftParen => true,
|
Token::LeftBracket | Token::LeftParen => true,
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Token::DoubleColon => true,
|
Token::DoubleColon => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
@ -761,9 +754,7 @@ fn parse_call_expr<'a>(
|
|||||||
Token::RightParen => {
|
Token::RightParen => {
|
||||||
eat_token(input, Token::RightParen);
|
eat_token(input, Token::RightParen);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
let hash_fn_def = if let Some(modules) = modules.as_mut() {
|
||||||
let hash_fn_def = {
|
|
||||||
if let Some(modules) = modules.as_mut() {
|
|
||||||
modules.set_index(state.find_module(&modules.get(0).0));
|
modules.set_index(state.find_module(&modules.get(0).0));
|
||||||
|
|
||||||
// Rust functions are indexed in two steps:
|
// Rust functions are indexed in two steps:
|
||||||
@ -777,11 +768,7 @@ fn parse_call_expr<'a>(
|
|||||||
} else {
|
} else {
|
||||||
// Qualifiers (none) + function name + no parameters.
|
// Qualifiers (none) + function name + no parameters.
|
||||||
calc_fn_hash(empty(), &id, 0, empty())
|
calc_fn_hash(empty(), &id, 0, empty())
|
||||||
}
|
|
||||||
};
|
};
|
||||||
// Qualifiers (none) + function name + no parameters.
|
|
||||||
#[cfg(feature = "no_module")]
|
|
||||||
let hash_fn_def = calc_fn_hash(empty(), &id, 0, empty());
|
|
||||||
|
|
||||||
return Ok(Expr::FnCall(Box::new((
|
return Ok(Expr::FnCall(Box::new((
|
||||||
(id.into(), false, begin),
|
(id.into(), false, begin),
|
||||||
@ -803,9 +790,7 @@ fn parse_call_expr<'a>(
|
|||||||
(Token::RightParen, _) => {
|
(Token::RightParen, _) => {
|
||||||
eat_token(input, Token::RightParen);
|
eat_token(input, Token::RightParen);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
let hash_fn_def = if let Some(modules) = modules.as_mut() {
|
||||||
let hash_fn_def = {
|
|
||||||
if let Some(modules) = modules.as_mut() {
|
|
||||||
modules.set_index(state.find_module(&modules.get(0).0));
|
modules.set_index(state.find_module(&modules.get(0).0));
|
||||||
|
|
||||||
// Rust functions are indexed in two steps:
|
// Rust functions are indexed in two steps:
|
||||||
@ -819,11 +804,7 @@ fn parse_call_expr<'a>(
|
|||||||
} else {
|
} else {
|
||||||
// Qualifiers (none) + function name + number of arguments.
|
// Qualifiers (none) + function name + number of arguments.
|
||||||
calc_fn_hash(empty(), &id, args.len(), empty())
|
calc_fn_hash(empty(), &id, args.len(), empty())
|
||||||
}
|
|
||||||
};
|
};
|
||||||
// Qualifiers (none) + function name + number of arguments.
|
|
||||||
#[cfg(feature = "no_module")]
|
|
||||||
let hash_fn_def = calc_fn_hash(empty(), &id, args.len(), empty());
|
|
||||||
|
|
||||||
return Ok(Expr::FnCall(Box::new((
|
return Ok(Expr::FnCall(Box::new((
|
||||||
(id.into(), false, begin),
|
(id.into(), false, begin),
|
||||||
@ -1265,7 +1246,6 @@ fn parse_primary<'a>(
|
|||||||
}
|
}
|
||||||
(Expr::Property(_), _) => unreachable!(),
|
(Expr::Property(_), _) => unreachable!(),
|
||||||
// module access
|
// module access
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
(Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() {
|
(Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() {
|
||||||
(Token::Identifier(id2), pos2) => {
|
(Token::Identifier(id2), pos2) => {
|
||||||
let ((name, pos), mut modules, _, index) = *x;
|
let ((name, pos), mut modules, _, index) = *x;
|
||||||
@ -1293,7 +1273,6 @@ fn parse_primary<'a>(
|
|||||||
|
|
||||||
match &mut root_expr {
|
match &mut root_expr {
|
||||||
// Cache the hash key for module-qualified variables
|
// Cache the hash key for module-qualified variables
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Expr::Variable(x) if x.1.is_some() => {
|
Expr::Variable(x) if x.1.is_some() => {
|
||||||
let ((name, _), modules, hash, _) = x.as_mut();
|
let ((name, _), modules, hash, _) = x.as_mut();
|
||||||
let modules = modules.as_mut().unwrap();
|
let modules = modules.as_mut().unwrap();
|
||||||
@ -1496,22 +1475,21 @@ fn parse_op_assignment_stmt<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Make a dot expression.
|
/// Make a dot expression.
|
||||||
fn make_dot_expr(
|
fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseError> {
|
||||||
lhs: Expr,
|
|
||||||
rhs: Expr,
|
|
||||||
op_pos: Position,
|
|
||||||
is_index: bool,
|
|
||||||
) -> Result<Expr, ParseError> {
|
|
||||||
Ok(match (lhs, rhs) {
|
Ok(match (lhs, rhs) {
|
||||||
// idx_lhs[idx_rhs].rhs
|
// idx_lhs[idx_expr].rhs
|
||||||
// Attach dot chain to the bottom level of indexing chain
|
// Attach dot chain to the bottom level of indexing chain
|
||||||
(Expr::Index(x), rhs) => {
|
(Expr::Index(x), rhs) => {
|
||||||
Expr::Index(Box::new((x.0, make_dot_expr(x.1, rhs, op_pos, true)?, x.2)))
|
let (idx_lhs, idx_expr, pos) = *x;
|
||||||
|
Expr::Index(Box::new((
|
||||||
|
idx_lhs,
|
||||||
|
make_dot_expr(idx_expr, rhs, op_pos)?,
|
||||||
|
pos,
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
// lhs.id
|
// lhs.id
|
||||||
(lhs, Expr::Variable(x)) if x.1.is_none() => {
|
(lhs, Expr::Variable(x)) if x.1.is_none() => {
|
||||||
let (name, pos) = x.0;
|
let (name, pos) = x.0;
|
||||||
let lhs = if is_index { lhs.into_property() } else { lhs };
|
|
||||||
|
|
||||||
let getter = make_getter(&name);
|
let getter = make_getter(&name);
|
||||||
let setter = make_setter(&name);
|
let setter = make_setter(&name);
|
||||||
@ -1519,46 +1497,34 @@ fn make_dot_expr(
|
|||||||
|
|
||||||
Expr::Dot(Box::new((lhs, rhs, op_pos)))
|
Expr::Dot(Box::new((lhs, rhs, op_pos)))
|
||||||
}
|
}
|
||||||
(lhs, Expr::Property(x)) => {
|
|
||||||
let lhs = if is_index { lhs.into_property() } else { lhs };
|
|
||||||
let rhs = Expr::Property(x);
|
|
||||||
Expr::Dot(Box::new((lhs, rhs, op_pos)))
|
|
||||||
}
|
|
||||||
// lhs.module::id - syntax error
|
// lhs.module::id - syntax error
|
||||||
(_, Expr::Variable(x)) if x.1.is_some() => {
|
(_, Expr::Variable(x)) if x.1.is_some() => {
|
||||||
#[cfg(feature = "no_module")]
|
|
||||||
unreachable!();
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get(0).1));
|
return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get(0).1));
|
||||||
}
|
}
|
||||||
|
// lhs.prop
|
||||||
|
(lhs, prop @ Expr::Property(_)) => Expr::Dot(Box::new((lhs, prop, op_pos))),
|
||||||
// lhs.dot_lhs.dot_rhs
|
// lhs.dot_lhs.dot_rhs
|
||||||
(lhs, Expr::Dot(x)) => {
|
(lhs, Expr::Dot(x)) => {
|
||||||
let (dot_lhs, dot_rhs, pos) = *x;
|
let (dot_lhs, dot_rhs, pos) = *x;
|
||||||
Expr::Dot(Box::new((
|
Expr::Dot(Box::new((
|
||||||
lhs,
|
lhs,
|
||||||
Expr::Dot(Box::new((
|
Expr::Dot(Box::new((dot_lhs.into_property(), dot_rhs, pos))),
|
||||||
dot_lhs.into_property(),
|
|
||||||
dot_rhs.into_property(),
|
|
||||||
pos,
|
|
||||||
))),
|
|
||||||
op_pos,
|
op_pos,
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
// lhs.idx_lhs[idx_rhs]
|
// lhs.idx_lhs[idx_rhs]
|
||||||
(lhs, Expr::Index(x)) => {
|
(lhs, Expr::Index(x)) => {
|
||||||
let (idx_lhs, idx_rhs, pos) = *x;
|
let (dot_lhs, dot_rhs, pos) = *x;
|
||||||
Expr::Dot(Box::new((
|
Expr::Dot(Box::new((
|
||||||
lhs,
|
lhs,
|
||||||
Expr::Index(Box::new((
|
Expr::Index(Box::new((dot_lhs.into_property(), dot_rhs, pos))),
|
||||||
idx_lhs.into_property(),
|
|
||||||
idx_rhs.into_property(),
|
|
||||||
pos,
|
|
||||||
))),
|
|
||||||
op_pos,
|
op_pos,
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
// lhs.func()
|
||||||
|
(lhs, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))),
|
||||||
// lhs.rhs
|
// lhs.rhs
|
||||||
(lhs, rhs) => Expr::Dot(Box::new((lhs, rhs.into_property(), op_pos))),
|
_ => unreachable!(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1822,7 +1788,7 @@ fn parse_binary_op<'a>(
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
make_dot_expr(current_lhs, rhs, pos, false)?
|
make_dot_expr(current_lhs, rhs, pos)?
|
||||||
}
|
}
|
||||||
|
|
||||||
token => return Err(PERR::UnknownOperator(token.into()).into_err(pos)),
|
token => return Err(PERR::UnknownOperator(token.into()).into_err(pos)),
|
||||||
@ -2123,6 +2089,7 @@ fn parse_import<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an export statement.
|
/// Parse an export statement.
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
fn parse_export<'a>(
|
fn parse_export<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
state: &mut ParseState,
|
state: &mut ParseState,
|
||||||
@ -2304,7 +2271,9 @@ fn parse_stmt<'a>(
|
|||||||
Token::LeftBrace => parse_block(input, state, breakable, level + 1, allow_stmt_expr),
|
Token::LeftBrace => parse_block(input, state, breakable, level + 1, allow_stmt_expr),
|
||||||
|
|
||||||
// fn ...
|
// fn ...
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
Token::Fn if !is_global => Err(PERR::WrongFnDefinition.into_err(*pos)),
|
Token::Fn if !is_global => Err(PERR::WrongFnDefinition.into_err(*pos)),
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
Token::Fn => unreachable!(),
|
Token::Fn => unreachable!(),
|
||||||
|
|
||||||
Token::If => parse_if(input, state, breakable, level + 1, allow_stmt_expr),
|
Token::If => parse_if(input, state, breakable, level + 1, allow_stmt_expr),
|
||||||
@ -2353,8 +2322,6 @@ fn parse_stmt<'a>(
|
|||||||
|
|
||||||
Token::Let => parse_let(input, state, Normal, level + 1, allow_stmt_expr),
|
Token::Let => parse_let(input, state, Normal, level + 1, allow_stmt_expr),
|
||||||
Token::Const => parse_let(input, state, Constant, level + 1, allow_stmt_expr),
|
Token::Const => parse_let(input, state, Constant, level + 1, allow_stmt_expr),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
Token::Import => parse_import(input, state, level + 1, allow_stmt_expr),
|
Token::Import => parse_import(input, state, level + 1, allow_stmt_expr),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -2368,6 +2335,7 @@ fn parse_stmt<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a function definition.
|
/// Parse a function definition.
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
fn parse_fn<'a>(
|
fn parse_fn<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
state: &mut ParseState,
|
state: &mut ParseState,
|
||||||
@ -2493,24 +2461,23 @@ pub fn parse_global_expr<'a>(
|
|||||||
fn parse_global_level<'a>(
|
fn parse_global_level<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
max_expr_depth: (usize, usize),
|
max_expr_depth: (usize, usize),
|
||||||
) -> Result<(Vec<Stmt>, HashMap<u64, FnDef>), ParseError> {
|
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
|
||||||
let mut statements = Vec::<Stmt>::new();
|
let mut statements = Vec::<Stmt>::new();
|
||||||
let mut functions = HashMap::<u64, FnDef>::new();
|
let mut functions = HashMap::<u64, FnDef, _>::with_hasher(StraightHasherBuilder);
|
||||||
let mut state = ParseState::new(max_expr_depth.0);
|
let mut state = ParseState::new(max_expr_depth.0);
|
||||||
|
|
||||||
while !input.peek().unwrap().0.is_eof() {
|
while !input.peek().unwrap().0.is_eof() {
|
||||||
// Collect all the function definitions
|
// Collect all the function definitions
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
{
|
{
|
||||||
let mut access = FnAccess::Public;
|
let (access, must_be_fn) = if match_token(input, Token::Private)? {
|
||||||
let mut must_be_fn = false;
|
(FnAccess::Private, true)
|
||||||
|
} else {
|
||||||
if match_token(input, Token::Private)? {
|
(FnAccess::Public, false)
|
||||||
access = FnAccess::Private;
|
};
|
||||||
must_be_fn = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
match input.peek().unwrap() {
|
match input.peek().unwrap() {
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
(Token::Fn, _) => {
|
(Token::Fn, _) => {
|
||||||
let mut state = ParseState::new(max_expr_depth.1);
|
let mut state = ParseState::new(max_expr_depth.1);
|
||||||
let func = parse_fn(input, &mut state, access, 0, true)?;
|
let func = parse_fn(input, &mut state, access, 0, true)?;
|
||||||
@ -2565,7 +2532,7 @@ fn parse_global_level<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((statements, functions))
|
Ok((statements, functions.into_iter().map(|(_, v)| v).collect()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the parser on an input stream, returning an AST.
|
/// Run the parser on an input stream, returning an AST.
|
||||||
@ -2576,9 +2543,8 @@ pub fn parse<'a>(
|
|||||||
optimization_level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
max_expr_depth: (usize, usize),
|
max_expr_depth: (usize, usize),
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
let (statements, functions) = parse_global_level(input, max_expr_depth)?;
|
let (statements, lib) = parse_global_level(input, max_expr_depth)?;
|
||||||
|
|
||||||
let lib = functions.into_iter().map(|(_, v)| v).collect();
|
|
||||||
Ok(
|
Ok(
|
||||||
// Optimize AST
|
// Optimize AST
|
||||||
optimize_into_ast(engine, scope, statements, lib, optimization_level),
|
optimize_into_ast(engine, scope, statements, lib, optimization_level),
|
||||||
|
20
src/scope.rs
20
src/scope.rs
@ -1,12 +1,10 @@
|
|||||||
//! Module that defines the `Scope` type representing a function call-stack scope.
|
//! Module that defines the `Scope` type representing a function call-stack scope.
|
||||||
|
|
||||||
use crate::any::{Dynamic, Union, Variant};
|
use crate::any::{Dynamic, Union, Variant};
|
||||||
|
use crate::module::Module;
|
||||||
use crate::parser::{map_dynamic_to_expr, Expr};
|
use crate::parser::{map_dynamic_to_expr, Expr};
|
||||||
use crate::token::Position;
|
use crate::token::Position;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
use crate::module::Module;
|
|
||||||
|
|
||||||
use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec};
|
use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec};
|
||||||
|
|
||||||
/// Type of an entry in the Scope.
|
/// Type of an entry in the Scope.
|
||||||
@ -178,6 +176,17 @@ impl<'a> Scope<'a> {
|
|||||||
/// Modules are used for accessing member variables, functions and plugins under a namespace.
|
/// Modules are used for accessing member variables, functions and plugins under a namespace.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
pub fn push_module<K: Into<Cow<'a, str>>>(&mut self, name: K, mut value: Module) {
|
pub fn push_module<K: Into<Cow<'a, str>>>(&mut self, name: K, mut value: Module) {
|
||||||
|
self.push_module_internal(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add (push) a new module to the Scope.
|
||||||
|
///
|
||||||
|
/// Modules are used for accessing member variables, functions and plugins under a namespace.
|
||||||
|
pub(crate) fn push_module_internal<K: Into<Cow<'a, str>>>(
|
||||||
|
&mut self,
|
||||||
|
name: K,
|
||||||
|
mut value: Module,
|
||||||
|
) {
|
||||||
value.index_all_sub_modules();
|
value.index_all_sub_modules();
|
||||||
|
|
||||||
self.push_dynamic_value(
|
self.push_dynamic_value(
|
||||||
@ -350,6 +359,11 @@ impl<'a> Scope<'a> {
|
|||||||
/// Find a module in the Scope, starting from the last entry.
|
/// Find a module in the Scope, starting from the last entry.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
pub fn find_module(&mut self, name: &str) -> Option<&mut Module> {
|
pub fn find_module(&mut self, name: &str) -> Option<&mut Module> {
|
||||||
|
self.find_module_internal(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find a module in the Scope, starting from the last entry.
|
||||||
|
pub(crate) fn find_module_internal(&mut self, name: &str) -> Option<&mut Module> {
|
||||||
let index = self.get_module_index(name)?;
|
let index = self.get_module_index(name)?;
|
||||||
self.get_mut(index).0.downcast_mut::<Module>()
|
self.get_mut(index).0.downcast_mut::<Module>()
|
||||||
}
|
}
|
||||||
|
@ -181,6 +181,7 @@ pub enum Token {
|
|||||||
XOr,
|
XOr,
|
||||||
Ampersand,
|
Ampersand,
|
||||||
And,
|
And,
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
Fn,
|
Fn,
|
||||||
Continue,
|
Continue,
|
||||||
Break,
|
Break,
|
||||||
@ -199,6 +200,7 @@ pub enum Token {
|
|||||||
PowerOfAssign,
|
PowerOfAssign,
|
||||||
Private,
|
Private,
|
||||||
Import,
|
Import,
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
Export,
|
Export,
|
||||||
As,
|
As,
|
||||||
LexError(Box<LexError>),
|
LexError(Box<LexError>),
|
||||||
@ -260,6 +262,7 @@ impl Token {
|
|||||||
Or => "||",
|
Or => "||",
|
||||||
Ampersand => "&",
|
Ampersand => "&",
|
||||||
And => "&&",
|
And => "&&",
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
Fn => "fn",
|
Fn => "fn",
|
||||||
Continue => "continue",
|
Continue => "continue",
|
||||||
Break => "break",
|
Break => "break",
|
||||||
@ -283,6 +286,7 @@ impl Token {
|
|||||||
PowerOfAssign => "~=",
|
PowerOfAssign => "~=",
|
||||||
Private => "private",
|
Private => "private",
|
||||||
Import => "import",
|
Import => "import",
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
Export => "export",
|
Export => "export",
|
||||||
As => "as",
|
As => "as",
|
||||||
EOF => "{EOF}",
|
EOF => "{EOF}",
|
||||||
@ -754,12 +758,9 @@ impl<'a> TokenIterator<'a> {
|
|||||||
"for" => Token::For,
|
"for" => Token::For,
|
||||||
"in" => Token::In,
|
"in" => Token::In,
|
||||||
"private" => Token::Private,
|
"private" => Token::Private,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
"import" => Token::Import,
|
"import" => Token::Import,
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
"export" => Token::Export,
|
"export" => Token::Export,
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
"as" => Token::As,
|
"as" => Token::As,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -916,7 +917,6 @@ impl<'a> TokenIterator<'a> {
|
|||||||
}
|
}
|
||||||
('=', _) => return Some((Token::Equals, pos)),
|
('=', _) => return Some((Token::Equals, pos)),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
|
||||||
(':', ':') => {
|
(':', ':') => {
|
||||||
self.eat_next();
|
self.eat_next();
|
||||||
return Some((Token::DoubleColon, pos));
|
return Some((Token::DoubleColon, pos));
|
||||||
|
66
src/utils.rs
66
src/utils.rs
@ -11,7 +11,7 @@ use crate::stdlib::{
|
|||||||
borrow::Borrow,
|
borrow::Borrow,
|
||||||
boxed::Box,
|
boxed::Box,
|
||||||
fmt,
|
fmt,
|
||||||
hash::{Hash, Hasher},
|
hash::{BuildHasher, Hash, Hasher},
|
||||||
iter::FromIterator,
|
iter::FromIterator,
|
||||||
mem,
|
mem,
|
||||||
mem::MaybeUninit,
|
mem::MaybeUninit,
|
||||||
@ -27,6 +27,48 @@ use crate::stdlib::collections::hash_map::DefaultHasher;
|
|||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use ahash::AHasher;
|
use ahash::AHasher;
|
||||||
|
|
||||||
|
/// A hasher that only takes one single `u64` and returns it as a hash key.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics when hashing any data type other than a `u64`.
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
|
||||||
|
pub struct StraightHasher(u64);
|
||||||
|
|
||||||
|
impl Hasher for StraightHasher {
|
||||||
|
#[inline(always)]
|
||||||
|
fn finish(&self) -> u64 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn write(&mut self, bytes: &[u8]) {
|
||||||
|
let mut key = [0_u8; 8];
|
||||||
|
key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes
|
||||||
|
self.0 = u64::from_le_bytes(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StraightHasher {
|
||||||
|
/// Create a `StraightHasher`.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A hash builder for `StraightHasher`.
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
|
||||||
|
pub struct StraightHasherBuilder;
|
||||||
|
|
||||||
|
impl BuildHasher for StraightHasherBuilder {
|
||||||
|
type Hasher = StraightHasher;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn build_hasher(&self) -> Self::Hasher {
|
||||||
|
StraightHasher::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Calculate a `u64` hash key from a module-qualified function name and parameter types.
|
/// Calculate a `u64` hash key from a module-qualified function name and parameter types.
|
||||||
///
|
///
|
||||||
/// Module names are passed in via `&str` references from an iterator.
|
/// Module names are passed in via `&str` references from an iterator.
|
||||||
@ -108,6 +150,7 @@ pub struct StaticVec<T> {
|
|||||||
const MAX_STATIC_VEC: usize = 4;
|
const MAX_STATIC_VEC: usize = 4;
|
||||||
|
|
||||||
impl<T> Drop for StaticVec<T> {
|
impl<T> Drop for StaticVec<T> {
|
||||||
|
#[inline(always)]
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.clear();
|
self.clear();
|
||||||
}
|
}
|
||||||
@ -174,6 +217,7 @@ impl<T> FromIterator<T> for StaticVec<T> {
|
|||||||
|
|
||||||
impl<T> StaticVec<T> {
|
impl<T> StaticVec<T> {
|
||||||
/// Create a new `StaticVec`.
|
/// Create a new `StaticVec`.
|
||||||
|
#[inline(always)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
@ -189,6 +233,7 @@ impl<T> StaticVec<T> {
|
|||||||
self.len = 0;
|
self.len = 0;
|
||||||
}
|
}
|
||||||
/// Extract a `MaybeUninit` into a concrete initialized type.
|
/// Extract a `MaybeUninit` into a concrete initialized type.
|
||||||
|
#[inline(always)]
|
||||||
fn extract(value: MaybeUninit<T>) -> T {
|
fn extract(value: MaybeUninit<T>) -> T {
|
||||||
unsafe { value.assume_init() }
|
unsafe { value.assume_init() }
|
||||||
}
|
}
|
||||||
@ -250,6 +295,7 @@ impl<T> StaticVec<T> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
/// Is data stored in fixed-size storage?
|
/// Is data stored in fixed-size storage?
|
||||||
|
#[inline(always)]
|
||||||
fn is_fixed_storage(&self) -> bool {
|
fn is_fixed_storage(&self) -> bool {
|
||||||
self.len <= MAX_STATIC_VEC
|
self.len <= MAX_STATIC_VEC
|
||||||
}
|
}
|
||||||
@ -359,10 +405,12 @@ impl<T> StaticVec<T> {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
/// Get the number of items in this `StaticVec`.
|
/// Get the number of items in this `StaticVec`.
|
||||||
|
#[inline(always)]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.len
|
self.len
|
||||||
}
|
}
|
||||||
/// Is this `StaticVec` empty?
|
/// Is this `StaticVec` empty?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.len == 0
|
self.len == 0
|
||||||
}
|
}
|
||||||
@ -605,41 +653,48 @@ pub struct ImmutableString(Shared<String>);
|
|||||||
impl Deref for ImmutableString {
|
impl Deref for ImmutableString {
|
||||||
type Target = String;
|
type Target = String;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<String> for ImmutableString {
|
impl AsRef<String> for ImmutableString {
|
||||||
|
#[inline(always)]
|
||||||
fn as_ref(&self) -> &String {
|
fn as_ref(&self) -> &String {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Borrow<str> for ImmutableString {
|
impl Borrow<str> for ImmutableString {
|
||||||
|
#[inline(always)]
|
||||||
fn borrow(&self) -> &str {
|
fn borrow(&self) -> &str {
|
||||||
self.0.as_str()
|
self.0.as_str()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for ImmutableString {
|
impl From<&str> for ImmutableString {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: &str) -> Self {
|
fn from(value: &str) -> Self {
|
||||||
Self(value.to_string().into())
|
Self(value.to_string().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<String> for ImmutableString {
|
impl From<String> for ImmutableString {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: String) -> Self {
|
fn from(value: String) -> Self {
|
||||||
Self(value.into())
|
Self(value.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Box<String>> for ImmutableString {
|
impl From<Box<String>> for ImmutableString {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: Box<String>) -> Self {
|
fn from(value: Box<String>) -> Self {
|
||||||
Self(value.into())
|
Self(value.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ImmutableString> for String {
|
impl From<ImmutableString> for String {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: ImmutableString) -> Self {
|
fn from(value: ImmutableString) -> Self {
|
||||||
value.into_owned()
|
value.into_owned()
|
||||||
}
|
}
|
||||||
@ -648,42 +703,49 @@ impl From<ImmutableString> for String {
|
|||||||
impl FromStr for ImmutableString {
|
impl FromStr for ImmutableString {
|
||||||
type Err = ();
|
type Err = ();
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
Ok(Self(s.to_string().into()))
|
Ok(Self(s.to_string().into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromIterator<char> for ImmutableString {
|
impl FromIterator<char> for ImmutableString {
|
||||||
|
#[inline(always)]
|
||||||
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
|
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
|
||||||
Self(iter.into_iter().collect::<String>().into())
|
Self(iter.into_iter().collect::<String>().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FromIterator<&'a char> for ImmutableString {
|
impl<'a> FromIterator<&'a char> for ImmutableString {
|
||||||
|
#[inline(always)]
|
||||||
fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self {
|
fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self {
|
||||||
Self(iter.into_iter().cloned().collect::<String>().into())
|
Self(iter.into_iter().cloned().collect::<String>().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FromIterator<&'a str> for ImmutableString {
|
impl<'a> FromIterator<&'a str> for ImmutableString {
|
||||||
|
#[inline(always)]
|
||||||
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
|
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
|
||||||
Self(iter.into_iter().collect::<String>().into())
|
Self(iter.into_iter().collect::<String>().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FromIterator<String> for ImmutableString {
|
impl<'a> FromIterator<String> for ImmutableString {
|
||||||
|
#[inline(always)]
|
||||||
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
|
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
|
||||||
Self(iter.into_iter().collect::<String>().into())
|
Self(iter.into_iter().collect::<String>().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ImmutableString {
|
impl fmt::Display for ImmutableString {
|
||||||
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fmt::Display::fmt(self.0.as_str(), f)
|
fmt::Display::fmt(self.0.as_str(), f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for ImmutableString {
|
impl fmt::Debug for ImmutableString {
|
||||||
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fmt::Debug::fmt(self.0.as_str(), f)
|
fmt::Debug::fmt(self.0.as_str(), f)
|
||||||
}
|
}
|
||||||
@ -818,6 +880,7 @@ impl Add<char> for &ImmutableString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AddAssign<char> for ImmutableString {
|
impl AddAssign<char> for ImmutableString {
|
||||||
|
#[inline(always)]
|
||||||
fn add_assign(&mut self, rhs: char) {
|
fn add_assign(&mut self, rhs: char) {
|
||||||
self.make_mut().push(rhs);
|
self.make_mut().push(rhs);
|
||||||
}
|
}
|
||||||
@ -832,6 +895,7 @@ impl ImmutableString {
|
|||||||
}
|
}
|
||||||
/// Make sure that the `ImmutableString` is unique (i.e. no other outstanding references).
|
/// Make sure that the `ImmutableString` is unique (i.e. no other outstanding references).
|
||||||
/// Then return a mutable reference to the `String`.
|
/// Then return a mutable reference to the `String`.
|
||||||
|
#[inline(always)]
|
||||||
pub fn make_mut(&mut self) -> &mut String {
|
pub fn make_mut(&mut self) -> &mut String {
|
||||||
shared_make_mut(&mut self.0)
|
shared_make_mut(&mut self.0)
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,10 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
|
|||||||
)?,
|
)?,
|
||||||
'o'
|
'o'
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<String>(r#"let a = [#{s:"hello"}]; a[0].s[2] = 'X'; a[0].s"#)?,
|
||||||
|
"heXlo"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -195,6 +195,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
||||||
|
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
scope.push_module("testing", module);
|
scope.push_module("testing", module);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
Loading…
Reference in New Issue
Block a user