Merge branch 'master' into plugins
This commit is contained in:
commit
8e876b0b86
166
README.md
166
README.md
@ -19,8 +19,8 @@ Features
|
||||
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`].
|
||||
* 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 efficient evaluation (1 million iterations in 0.75 sec on my 5 year old laptop).
|
||||
* Fairly low compile-time overhead.
|
||||
* 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
|
||||
one single source file, all with names starting with `"unsafe_"`).
|
||||
* 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
|
||||
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
|
||||
--------------------
|
||||
@ -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 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 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).
|
||||
* 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
|
||||
------------
|
||||
|
||||
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
|
||||
[dependencies]
|
||||
@ -93,7 +105,7 @@ Optional features
|
||||
| `no_index` | Disable [arrays] and indexing features. |
|
||||
| `no_object` | Disable support for custom types and [object maps]. |
|
||||
| `no_function` | Disable script-defined functions. |
|
||||
| `no_module` | Disable loading 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. |
|
||||
|
||||
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:
|
||||
|
||||
```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
|
||||
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:
|
||||
|
||||
| Language feature scripts | Description |
|
||||
| ---------------------------------------------------- | ------------------------------------------------------------- |
|
||||
| [`array.rhai`](scripts/array.rhai) | [arrays] in Rhai |
|
||||
| [`assignment.rhai`](scripts/assignment.rhai) | variable declarations |
|
||||
| [`comments.rhai`](scripts/comments.rhai) | just comments |
|
||||
| [`for1.rhai`](scripts/for1.rhai) | for loops |
|
||||
| [`function_decl1.rhai`](scripts/function_decl1.rhai) | a function without parameters |
|
||||
| [`function_decl2.rhai`](scripts/function_decl2.rhai) | a function with two parameters |
|
||||
| [`function_decl3.rhai`](scripts/function_decl3.rhai) | a function with many parameters |
|
||||
| [`if1.rhai`](scripts/if1.rhai) | if example |
|
||||
| [`loop.rhai`](scripts/loop.rhai) | endless loop in Rhai, this example emulates a do..while cycle |
|
||||
| [`op1.rhai`](scripts/op1.rhai) | just a simple addition |
|
||||
| [`op2.rhai`](scripts/op2.rhai) | simple addition and multiplication |
|
||||
| [`op3.rhai`](scripts/op3.rhai) | change evaluation order with parenthesis |
|
||||
| [`string.rhai`](scripts/string.rhai) | [string] operations |
|
||||
| [`while.rhai`](scripts/while.rhai) | while loop |
|
||||
| Language feature scripts | Description |
|
||||
| ---------------------------------------------------- | ----------------------------------------------------------------------------- |
|
||||
| [`array.rhai`](scripts/array.rhai) | [arrays] in Rhai |
|
||||
| [`assignment.rhai`](scripts/assignment.rhai) | variable declarations |
|
||||
| [`comments.rhai`](scripts/comments.rhai) | just comments |
|
||||
| [`for1.rhai`](scripts/for1.rhai) | [`for`](#for-loop) loops |
|
||||
| [`for2.rhai`](scripts/for2.rhai) | [`for`](#for-loop) loops on [arrays] |
|
||||
| [`function_decl1.rhai`](scripts/function_decl1.rhai) | a [function] without parameters |
|
||||
| [`function_decl2.rhai`](scripts/function_decl2.rhai) | a [function] with two parameters |
|
||||
| [`function_decl3.rhai`](scripts/function_decl3.rhai) | a [function] with many parameters |
|
||||
| [`if1.rhai`](scripts/if1.rhai) | [`if`](#if-statement) example |
|
||||
| [`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 |
|
||||
| [`op3.rhai`](scripts/op3.rhai) | change evaluation order with parenthesis |
|
||||
| [`string.rhai`](scripts/string.rhai) | [string] operations |
|
||||
| [`strings_map.rhai`](scripts/strings_map.rhai) | [string] and [object map] operations |
|
||||
| [`while.rhai`](scripts/while.rhai) | [`while`](#while-loop) loop |
|
||||
|
||||
| Example scripts | Description |
|
||||
| -------------------------------------------- | ---------------------------------------------------------------------------------- |
|
||||
@ -235,6 +249,7 @@ fn main() -> Result<(), Box<EvalAltResult>>
|
||||
let engine = Engine::new();
|
||||
|
||||
let result = engine.eval::<i64>("40 + 2")?;
|
||||
// ^^^^^^^ cast the result to an 'i64', this is required
|
||||
|
||||
println!("Answer: {}", result); // prints 42
|
||||
|
||||
@ -291,6 +306,8 @@ let ast = engine.compile_file("hello_world.rhai".into())?;
|
||||
|
||||
### 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`.
|
||||
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", ())?;
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
[`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
|
||||
```
|
||||
|
||||
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
|
||||
-----------------
|
||||
|
||||
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
|
||||
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)
|
||||
under the same name. This enables function overloading based on the number and types of parameters.
|
||||
The above example shows how to register multiple functions (or, in this case, multiple overloaded versions of the same function)
|
||||
under the same name.
|
||||
|
||||
Fallible functions
|
||||
------------------
|
||||
@ -773,7 +805,8 @@ fn main()
|
||||
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
|
||||
// Override the built-in function 'to_int'
|
||||
@ -784,11 +817,13 @@ fn to_int(num) {
|
||||
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
|
||||
--------------------
|
||||
|
||||
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
|
||||
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).
|
||||
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
|
||||
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.
|
||||
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.
|
||||
|
||||
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()'
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```rust
|
||||
@ -1745,10 +1780,10 @@ 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:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------------ | ---------------------------------- | -------------------------------------------------------- |
|
||||
| `elapsed` property | _none_ | returns the number of seconds since the timestamp |
|
||||
| `-` operator | later timestamp, earlier timestamp | returns the number of seconds between the two timestamps |
|
||||
| Function | Parameter(s) | Description |
|
||||
| ----------------------------- | ---------------------------------- | -------------------------------------------------------- |
|
||||
| `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 |
|
||||
|
||||
### Examples
|
||||
|
||||
@ -1777,15 +1812,19 @@ set of types (see [built-in operators](#built-in-operators)).
|
||||
"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
|
||||
42 == 42.0; // false - i64 is different from f64
|
||||
42 > "42"; // false - i64 is different from string
|
||||
42 <= "42"; // false again
|
||||
42 == 42.0; // false - i64 cannot be compared with f64
|
||||
42 != 42.0; // true - i64 cannot be compared with f64
|
||||
|
||||
42 > "42"; // false - i64 cannot be compared with string
|
||||
42 <= "42"; // false - i64 cannot be compared with string
|
||||
|
||||
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
|
||||
@ -1836,8 +1875,8 @@ my_str += 12345;
|
||||
my_str == "abcABC12345"
|
||||
```
|
||||
|
||||
`if` statements
|
||||
---------------
|
||||
`if` statement
|
||||
--------------
|
||||
|
||||
```rust
|
||||
if foo(x) {
|
||||
@ -1872,8 +1911,8 @@ let x = if decision { 42 }; // no else branch defaults to '()'
|
||||
x == ();
|
||||
```
|
||||
|
||||
`while` loops
|
||||
-------------
|
||||
`while` loop
|
||||
------------
|
||||
|
||||
```rust
|
||||
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.
|
||||
|
||||
@ -2073,8 +2112,8 @@ This is similar to Rust and many other modern languages.
|
||||
|
||||
### Function overloading
|
||||
|
||||
Functions can be _overloaded_ and are resolved purely upon the function's _name_ and the _number_ of parameters
|
||||
(but not parameter _types_, since all parameters are the same type - [`Dynamic`]).
|
||||
Functions defined in script can be _overloaded_ by _arity_ (i.e. they are resolved purely upon the function's _name_
|
||||
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.
|
||||
|
||||
```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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
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`.
|
||||
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
|
||||
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:
|
||||
|
||||
* **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.
|
||||
* **CPU**: A malignant 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.
|
||||
* **Stack**: A malignant script may attempt an infinite recursive call that exhausts the call stack.
|
||||
* **CPU**: A malicous script may run an infinite tight loop that consumes all CPU cycles.
|
||||
* **Time**: A malicous script may run indefinitely, thereby blocking the calling system which is waiting for a result.
|
||||
* **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
|
||||
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.
|
||||
* **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).
|
||||
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.
|
||||
* **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.
|
||||
|
||||
### 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.
|
||||
|
||||
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)).
|
||||
|
||||
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
|
||||
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.
|
||||
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.
|
||||
|
||||
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
|
||||
(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).
|
||||
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
|
||||
----------------
|
||||
|
||||
* `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>>`.
|
||||
* 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
|
||||
@ -41,6 +41,7 @@ New features
|
||||
* Set limit on maximum level of nesting expressions and statements to avoid panics during parsing.
|
||||
* New `EvalPackage` to disable `eval`.
|
||||
* `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
|
||||
------------------
|
||||
@ -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
|
||||
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.
|
||||
* 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
|
||||
|
@ -61,3 +61,27 @@ fn bench_eval_array_large_set(bench: &mut Bencher) {
|
||||
|
||||
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 c = mat_mul(a, b);
|
||||
|
||||
/*
|
||||
for i in range(0, SIZE) {
|
||||
print(c[i]);
|
||||
}
|
||||
*/
|
||||
|
||||
print("Finished. Run time = " + now.elapsed + " seconds.");
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
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 = [];
|
||||
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) {
|
||||
if !prime_mask[p] { continue; }
|
||||
|
||||
print(p);
|
||||
//print(p);
|
||||
|
||||
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("Run time = " + now.elapsed + " seconds.");
|
||||
|
||||
if total_primes_found != 9_592 {
|
||||
print("The answer is WRONG! Should be 9,592!");
|
||||
if total_primes_found != 78_498 {
|
||||
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.
|
||||
|
||||
use crate::module::Module;
|
||||
use crate::parser::{ImmutableString, INT};
|
||||
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
use crate::module::Module;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::parser::FLOAT;
|
||||
|
||||
@ -160,7 +158,6 @@ pub enum Union {
|
||||
Array(Box<Array>),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Map(Box<Map>),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Module(Box<Module>),
|
||||
Variant(Box<Box<dyn Variant>>),
|
||||
}
|
||||
@ -198,7 +195,6 @@ impl Dynamic {
|
||||
Union::Array(_) => TypeId::of::<Array>(),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_) => TypeId::of::<Map>(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(_) => TypeId::of::<Module>(),
|
||||
Union::Variant(value) => (***value).type_id(),
|
||||
}
|
||||
@ -218,7 +214,6 @@ impl Dynamic {
|
||||
Union::Array(_) => "array",
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_) => "map",
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(_) => "sub-scope",
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
@ -242,7 +237,6 @@ impl fmt::Display for Dynamic {
|
||||
Union::Array(value) => write!(f, "{:?}", value),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => write!(f, "#{:?}", value),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(value) => write!(f, "{:?}", value),
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
@ -266,7 +260,6 @@ impl fmt::Debug for Dynamic {
|
||||
Union::Array(value) => write!(f, "{:?}", value),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => write!(f, "#{:?}", value),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(value) => write!(f, "{:?}", value),
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
@ -290,7 +283,6 @@ impl Clone for Dynamic {
|
||||
Union::Array(ref value) => Self(Union::Array(value.clone())),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref value) => Self(Union::Map(value.clone())),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(ref value) => Self(Union::Module(value.clone())),
|
||||
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),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
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::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(),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Union::Module(value) => *unsafe_cast_box::<_, T>(value).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>(),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
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::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>(),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
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::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.
|
||||
/// Arguments are passed as a tuple.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -1040,6 +1041,67 @@ impl Engine {
|
||||
args: A,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
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 lib = ast.lib();
|
||||
let pos = Position::none();
|
||||
@ -1051,16 +1113,7 @@ impl Engine {
|
||||
let mut state = State::new();
|
||||
let args = args.as_mut();
|
||||
|
||||
let result = 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,
|
||||
))
|
||||
});
|
||||
self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, pos, 0)
|
||||
}
|
||||
|
||||
/// Optimize the `AST` with constants defined in an external Scope.
|
||||
|
195
src/engine.rs
195
src/engine.rs
@ -4,7 +4,7 @@ use crate::any::{Dynamic, Union};
|
||||
use crate::calc_fn_hash;
|
||||
use crate::error::ParseErrorType;
|
||||
use crate::fn_native::{CallableFunction, FnCallArgs, Shared};
|
||||
use crate::module::Module;
|
||||
use crate::module::{resolvers, Module, ModuleResolver};
|
||||
use crate::optimize::OptimizationLevel;
|
||||
use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage};
|
||||
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::scope::{EntryType as ScopeEntryType, Scope};
|
||||
use crate::token::Position;
|
||||
use crate::utils::StaticVec;
|
||||
use crate::utils::{StaticVec, StraightHasherBuilder};
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::parser::FLOAT;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
use crate::module::{resolvers, ModuleResolver};
|
||||
|
||||
use crate::stdlib::{
|
||||
any::TypeId,
|
||||
boxed::Box,
|
||||
@ -196,7 +193,7 @@ impl State {
|
||||
///
|
||||
/// The key of the `HashMap` is a `u64` hash calculated by the function `calc_fn_hash`.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FunctionsLib(HashMap<u64, Shared<FnDef>>);
|
||||
pub struct FunctionsLib(HashMap<u64, Shared<FnDef>, StraightHasherBuilder>);
|
||||
|
||||
impl FunctionsLib {
|
||||
/// 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 {
|
||||
type Target = HashMap<u64, Shared<FnDef>>;
|
||||
type Target = HashMap<u64, Shared<FnDef>, StraightHasherBuilder>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
@ -269,7 +266,7 @@ impl Deref 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
|
||||
}
|
||||
}
|
||||
@ -297,7 +294,6 @@ pub struct Engine {
|
||||
pub(crate) packages: PackagesCollection,
|
||||
|
||||
/// A module resolution service.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub(crate) module_resolver: Option<Box<dyn ModuleResolver>>,
|
||||
|
||||
/// 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_std"))]
|
||||
module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(feature = "no_std")]
|
||||
#[cfg(any(feature = "no_module", feature = "no_std"))]
|
||||
module_resolver: None,
|
||||
|
||||
type_names: HashMap::new(),
|
||||
@ -441,35 +436,33 @@ fn search_scope<'s, 'a>(
|
||||
Expr::Variable(x) => x.as_ref(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let index = if state.always_search { None } else { *index };
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
if let Some(modules) = modules.as_ref() {
|
||||
let module = if let Some(index) = modules.index() {
|
||||
scope
|
||||
.get_mut(scope.len() - index.get())
|
||||
.0
|
||||
.downcast_mut::<Module>()
|
||||
.unwrap()
|
||||
} else {
|
||||
let (id, root_pos) = modules.get(0);
|
||||
if let Some(modules) = modules.as_ref() {
|
||||
let module = if let Some(index) = modules.index() {
|
||||
scope
|
||||
.get_mut(scope.len() - index.get())
|
||||
.0
|
||||
.downcast_mut::<Module>()
|
||||
.unwrap()
|
||||
} else {
|
||||
let (id, root_pos) = modules.get(0);
|
||||
|
||||
scope.find_module(id).ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos))
|
||||
})?
|
||||
};
|
||||
scope
|
||||
.find_module_internal(id)
|
||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))?
|
||||
};
|
||||
|
||||
return Ok((
|
||||
module.get_qualified_var_mut(name, *hash_var, *pos)?,
|
||||
name,
|
||||
// Module variables are constant
|
||||
ScopeEntryType::Constant,
|
||||
*pos,
|
||||
));
|
||||
}
|
||||
return Ok((
|
||||
module.get_qualified_var_mut(name, *hash_var, *pos)?,
|
||||
name,
|
||||
// Module variables are constant
|
||||
ScopeEntryType::Constant,
|
||||
*pos,
|
||||
));
|
||||
}
|
||||
|
||||
let index = if state.always_search { None } else { *index };
|
||||
|
||||
let index = if let Some(index) = index {
|
||||
scope.len() - index.get()
|
||||
} else {
|
||||
@ -495,8 +488,6 @@ impl Engine {
|
||||
Self {
|
||||
packages: Default::default(),
|
||||
global_module: Default::default(),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
module_resolver: None,
|
||||
|
||||
type_names: HashMap::new(),
|
||||
@ -952,21 +943,24 @@ impl Engine {
|
||||
let mut idx_val = idx_values.pop();
|
||||
|
||||
if is_index {
|
||||
let pos = rhs.position();
|
||||
|
||||
match rhs {
|
||||
// xxx[idx].dot_rhs... | xxx[idx][dot_rhs]...
|
||||
// xxx[idx].expr... | xxx[idx][expr]...
|
||||
Expr::Dot(x) | Expr::Index(x) => {
|
||||
let (idx, expr, pos) = x.as_ref();
|
||||
let is_idx = matches!(rhs, Expr::Index(_));
|
||||
let pos = x.0.position();
|
||||
let this_ptr = &mut self
|
||||
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, false)?;
|
||||
let idx_pos = idx.position();
|
||||
let this_ptr = &mut self.get_indexed_mut(
|
||||
state, lib, obj, is_ref, idx_val, idx_pos, op_pos, false,
|
||||
)?;
|
||||
|
||||
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
|
||||
_ if new_val.is_some() => {
|
||||
let pos = rhs.position();
|
||||
let this_ptr = &mut self
|
||||
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, true)?;
|
||||
|
||||
@ -975,16 +969,7 @@ impl Engine {
|
||||
}
|
||||
// xxx[rhs]
|
||||
_ => self
|
||||
.get_indexed_mut(
|
||||
state,
|
||||
lib,
|
||||
obj,
|
||||
is_ref,
|
||||
idx_val,
|
||||
rhs.position(),
|
||||
op_pos,
|
||||
false,
|
||||
)
|
||||
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, false)
|
||||
.map(|v| (v.clone_into_dynamic(), false)),
|
||||
}
|
||||
} else {
|
||||
@ -1050,57 +1035,51 @@ impl Engine {
|
||||
.map(|(v, _)| (v, false))
|
||||
}
|
||||
#[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>() => {
|
||||
let (prop, expr, pos) = x.as_ref();
|
||||
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 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 {
|
||||
// Syntax error
|
||||
return Err(Box::new(EvalAltResult::ErrorDotExpr(
|
||||
"".into(),
|
||||
rhs.position(),
|
||||
)));
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
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) => {
|
||||
let (prop, expr, pos) = x.as_ref();
|
||||
let is_idx = matches!(rhs, Expr::Index(_));
|
||||
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 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 {
|
||||
// Syntax error
|
||||
return Err(Box::new(EvalAltResult::ErrorDotExpr(
|
||||
"".into(),
|
||||
rhs.position(),
|
||||
)));
|
||||
unreachable!();
|
||||
};
|
||||
let val = &mut val;
|
||||
let target = &mut val.into();
|
||||
|
||||
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
|
||||
if updated || may_be_changed {
|
||||
if let Expr::Property(p) = &x.0 {
|
||||
if let Expr::Property(p) = prop {
|
||||
let ((_, _, setter), _) = p.as_ref();
|
||||
// Re-use args because the first &mut parameter will not be consumed
|
||||
args[1] = val;
|
||||
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 {
|
||||
// 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,
|
||||
state: &mut State,
|
||||
lib: &FunctionsLib,
|
||||
dot_lhs: &Expr,
|
||||
dot_rhs: &Expr,
|
||||
is_index: bool,
|
||||
op_pos: Position,
|
||||
expr: &Expr,
|
||||
level: usize,
|
||||
new_val: Option<Dynamic>,
|
||||
) -> 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();
|
||||
|
||||
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();
|
||||
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)
|
||||
}
|
||||
@ -1173,7 +1155,7 @@ impl Engine {
|
||||
let val = self.eval_expr(scope, state, lib, expr, level)?;
|
||||
let this_ptr = &mut val.into();
|
||||
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)
|
||||
}
|
||||
@ -1489,14 +1471,14 @@ impl Engine {
|
||||
Expr::Variable(_) => unreachable!(),
|
||||
// idx_lhs[idx_expr] op= rhs
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(x) => self.eval_dot_index_chain(
|
||||
scope, state, lib, &x.0, &x.1, true, x.2, level, new_val,
|
||||
),
|
||||
Expr::Index(_) => {
|
||||
self.eval_dot_index_chain(scope, state, lib, lhs_expr, level, new_val)
|
||||
}
|
||||
// dot_lhs.dot_rhs op= rhs
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Dot(x) => self.eval_dot_index_chain(
|
||||
scope, state, lib, &x.0, &x.1, false, *op_pos, level, new_val,
|
||||
),
|
||||
Expr::Dot(_) => {
|
||||
self.eval_dot_index_chain(scope, state, lib, lhs_expr, level, new_val)
|
||||
}
|
||||
// Error assignment to constant
|
||||
expr if expr.is_constant() => {
|
||||
Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
|
||||
@ -1513,15 +1495,11 @@ impl Engine {
|
||||
|
||||
// lhs[idx_expr]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(x) => {
|
||||
self.eval_dot_index_chain(scope, state, lib, &x.0, &x.1, true, x.2, level, None)
|
||||
}
|
||||
Expr::Index(_) => self.eval_dot_index_chain(scope, state, lib, expr, level, None),
|
||||
|
||||
// lhs.dot_rhs
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Dot(x) => {
|
||||
self.eval_dot_index_chain(scope, state, lib, &x.0, &x.1, false, x.2, level, None)
|
||||
}
|
||||
Expr::Dot(_) => self.eval_dot_index_chain(scope, state, lib, expr, level, None),
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(x) => Ok(Dynamic(Union::Array(Box::new(
|
||||
@ -1621,7 +1599,6 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Module-qualified function call
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Expr::FnCall(x) if x.1.is_some() => {
|
||||
let ((name, _, pos), modules, hash_fn_def, args_expr, def_val) = x.as_ref();
|
||||
let modules = modules.as_ref().unwrap();
|
||||
@ -1642,7 +1619,7 @@ impl Engine {
|
||||
.downcast_mut::<Module>()
|
||||
.unwrap()
|
||||
} else {
|
||||
scope.find_module(id).ok_or_else(|| {
|
||||
scope.find_module_internal(id).ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos))
|
||||
})?
|
||||
};
|
||||
@ -1931,21 +1908,18 @@ impl Engine {
|
||||
|
||||
// Import statement
|
||||
Stmt::Import(x) => {
|
||||
#[cfg(feature = "no_module")]
|
||||
unreachable!();
|
||||
let (expr, (name, pos)) = x.as_ref();
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
// Guard against too many modules
|
||||
if state.modules >= self.max_modules {
|
||||
return Err(Box::new(EvalAltResult::ErrorTooManyModules(*pos)));
|
||||
}
|
||||
|
||||
if let Some(path) = self
|
||||
.eval_expr(scope, state, lib, &expr, level)?
|
||||
.try_cast::<ImmutableString>()
|
||||
{
|
||||
let (expr, (name, pos)) = x.as_ref();
|
||||
|
||||
// Guard against too many modules
|
||||
if state.modules >= self.max_modules {
|
||||
return Err(Box::new(EvalAltResult::ErrorTooManyModules(*pos)));
|
||||
}
|
||||
|
||||
if let Some(path) = self
|
||||
.eval_expr(scope, state, lib, &expr, level)?
|
||||
.try_cast::<ImmutableString>()
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
{
|
||||
if let Some(resolver) = &self.module_resolver {
|
||||
// Use an empty scope to create a module
|
||||
@ -1953,7 +1927,7 @@ impl Engine {
|
||||
resolver.resolve(self, Scope::new(), &path, expr.position())?;
|
||||
|
||||
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;
|
||||
|
||||
@ -1964,9 +1938,12 @@ impl Engine {
|
||||
expr.position(),
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position())))
|
||||
}
|
||||
|
||||
#[cfg(feature = "no_module")]
|
||||
Ok(Default::default())
|
||||
} else {
|
||||
Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position())))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::calc_fn_hash;
|
||||
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::{
|
||||
FnAccess,
|
||||
FnAccess::{Private, Public},
|
||||
@ -12,7 +12,7 @@ use crate::parser::{
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
||||
use crate::token::{Position, Token};
|
||||
use crate::utils::StaticVec;
|
||||
use crate::utils::{StaticVec, StraightHasherBuilder};
|
||||
|
||||
use crate::stdlib::{
|
||||
any::TypeId,
|
||||
@ -44,10 +44,14 @@ pub struct Module {
|
||||
variables: HashMap<String, Dynamic>,
|
||||
|
||||
/// 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.
|
||||
functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, CallableFunction)>,
|
||||
functions: HashMap<
|
||||
u64,
|
||||
(String, FnAccess, StaticVec<TypeId>, CallableFunction),
|
||||
StraightHasherBuilder,
|
||||
>,
|
||||
|
||||
/// Script-defined functions.
|
||||
lib: FunctionsLib,
|
||||
@ -57,7 +61,7 @@ pub struct Module {
|
||||
|
||||
/// Flattened collection of all external Rust functions, native or scripted,
|
||||
/// including those in sub-modules.
|
||||
all_functions: HashMap<u64, CallableFunction>,
|
||||
all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Module {
|
||||
@ -101,7 +105,7 @@ impl Module {
|
||||
/// ```
|
||||
pub fn new_with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
functions: HashMap::with_capacity(capacity),
|
||||
functions: HashMap::with_capacity_and_hasher(capacity, StraightHasherBuilder),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@ -941,23 +945,7 @@ impl ModuleRef {
|
||||
}
|
||||
|
||||
/// Trait that encapsulates a module resolution service.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[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 {
|
||||
pub trait ModuleResolver: SendSync {
|
||||
/// Resolve a module based on a path string.
|
||||
fn resolve(
|
||||
&self,
|
||||
@ -975,6 +963,8 @@ pub mod resolvers {
|
||||
pub use super::file::FileModuleResolver;
|
||||
pub use super::stat::StaticModuleResolver;
|
||||
}
|
||||
#[cfg(feature = "no_module")]
|
||||
pub mod resolvers {}
|
||||
|
||||
/// Script file-based module resolver.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
|
@ -37,6 +37,10 @@ impl OptimizationLevel {
|
||||
pub fn is_none(self) -> bool {
|
||||
self == Self::None
|
||||
}
|
||||
/// Is the `OptimizationLevel` Simple.
|
||||
pub fn is_simple(self) -> bool {
|
||||
self == Self::Simple
|
||||
}
|
||||
/// Is the `OptimizationLevel` Full.
|
||||
pub fn is_full(self) -> bool {
|
||||
self == Self::Full
|
||||
@ -381,10 +385,10 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||
// ( stmt )
|
||||
stmt => Expr::Stmt(Box::new((stmt, x.1))),
|
||||
},
|
||||
// id = expr
|
||||
// id op= expr
|
||||
Expr::Assignment(x) => match x.2 {
|
||||
//id = id2 op= expr2
|
||||
Expr::Assignment(x2) if x.1 == "=" => match (x.0, x2.0) {
|
||||
//id = id2 op= rhs
|
||||
Expr::Assignment(x2) if x.1.is_empty() => match (x.0, &x2.0) {
|
||||
// var = var op= expr2 -> var op= expr2
|
||||
(Expr::Variable(a), Expr::Variable(b))
|
||||
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();
|
||||
Expr::Assignment(Box::new((Expr::Variable(a), x2.1, optimize_expr(x2.2, state), x.3)))
|
||||
}
|
||||
// id1 = id2 op= expr2
|
||||
(id1, id2) => {
|
||||
Expr::Assignment(Box::new((
|
||||
id1, x.1, Expr::Assignment(Box::new((id2, x2.1, optimize_expr(x2.2, state), x2.3))), x.3,
|
||||
)))
|
||||
}
|
||||
// expr1 = expr2 op= rhs
|
||||
(expr1, _) => Expr::Assignment(Box::new((expr1, x.1, optimize_expr(Expr::Assignment(x2), state), x.3))),
|
||||
},
|
||||
// id op= expr
|
||||
// expr = rhs
|
||||
expr => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(expr, state), x.3))),
|
||||
},
|
||||
|
||||
|
152
src/parser.rs
152
src/parser.rs
@ -4,17 +4,11 @@ use crate::any::{Dynamic, Union};
|
||||
use crate::calc_fn_hash;
|
||||
use crate::engine::{make_getter, make_setter, Engine, FunctionsLib};
|
||||
use crate::error::{LexError, ParseError, ParseErrorType};
|
||||
use crate::module::ModuleRef;
|
||||
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||
use crate::token::{Position, Token, TokenIterator};
|
||||
use crate::utils::StaticVec;
|
||||
|
||||
#[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::utils::{StaticVec, StraightHasherBuilder};
|
||||
|
||||
use crate::stdlib::{
|
||||
borrow::Cow,
|
||||
@ -644,7 +638,6 @@ impl Expr {
|
||||
|
||||
Self::Variable(_) => match token {
|
||||
Token::LeftBracket | Token::LeftParen => true,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Token::DoubleColon => true,
|
||||
_ => false,
|
||||
},
|
||||
@ -761,27 +754,21 @@ fn parse_call_expr<'a>(
|
||||
Token::RightParen => {
|
||||
eat_token(input, Token::RightParen);
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let hash_fn_def = {
|
||||
if let Some(modules) = modules.as_mut() {
|
||||
modules.set_index(state.find_module(&modules.get(0).0));
|
||||
let hash_fn_def = if let Some(modules) = modules.as_mut() {
|
||||
modules.set_index(state.find_module(&modules.get(0).0));
|
||||
|
||||
// Rust functions are indexed in two steps:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
// i.e. qualifiers + function name + number of arguments.
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name,
|
||||
// zero number of arguments, and the actual list of argument `TypeId`'s.
|
||||
// 3) The final hash is the XOR of the two hashes.
|
||||
let qualifiers = modules.iter().map(|(m, _)| m.as_str());
|
||||
calc_fn_hash(qualifiers, &id, 0, empty())
|
||||
} else {
|
||||
// Qualifiers (none) + function name + no parameters.
|
||||
calc_fn_hash(empty(), &id, 0, empty())
|
||||
}
|
||||
// Rust functions are indexed in two steps:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
// i.e. qualifiers + function name + number of arguments.
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name,
|
||||
// zero number of arguments, and the actual list of argument `TypeId`'s.
|
||||
// 3) The final hash is the XOR of the two hashes.
|
||||
let qualifiers = modules.iter().map(|(m, _)| m.as_str());
|
||||
calc_fn_hash(qualifiers, &id, 0, empty())
|
||||
} else {
|
||||
// Qualifiers (none) + function name + no parameters.
|
||||
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((
|
||||
(id.into(), false, begin),
|
||||
@ -803,27 +790,21 @@ fn parse_call_expr<'a>(
|
||||
(Token::RightParen, _) => {
|
||||
eat_token(input, Token::RightParen);
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let hash_fn_def = {
|
||||
if let Some(modules) = modules.as_mut() {
|
||||
modules.set_index(state.find_module(&modules.get(0).0));
|
||||
let hash_fn_def = if let Some(modules) = modules.as_mut() {
|
||||
modules.set_index(state.find_module(&modules.get(0).0));
|
||||
|
||||
// Rust functions are indexed in two steps:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
// i.e. qualifiers + function name + number of arguments.
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name,
|
||||
// zero number of arguments, and the actual list of argument `TypeId`'s.
|
||||
// 3) The final hash is the XOR of the two hashes.
|
||||
let qualifiers = modules.iter().map(|(m, _)| m.as_str());
|
||||
calc_fn_hash(qualifiers, &id, args.len(), empty())
|
||||
} else {
|
||||
// Qualifiers (none) + function name + number of arguments.
|
||||
calc_fn_hash(empty(), &id, args.len(), empty())
|
||||
}
|
||||
// Rust functions are indexed in two steps:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
// i.e. qualifiers + function name + number of arguments.
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name,
|
||||
// zero number of arguments, and the actual list of argument `TypeId`'s.
|
||||
// 3) The final hash is the XOR of the two hashes.
|
||||
let qualifiers = modules.iter().map(|(m, _)| m.as_str());
|
||||
calc_fn_hash(qualifiers, &id, args.len(), empty())
|
||||
} else {
|
||||
// Qualifiers (none) + function name + number of arguments.
|
||||
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((
|
||||
(id.into(), false, begin),
|
||||
@ -1265,7 +1246,6 @@ fn parse_primary<'a>(
|
||||
}
|
||||
(Expr::Property(_), _) => unreachable!(),
|
||||
// module access
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
(Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() {
|
||||
(Token::Identifier(id2), pos2) => {
|
||||
let ((name, pos), mut modules, _, index) = *x;
|
||||
@ -1293,7 +1273,6 @@ fn parse_primary<'a>(
|
||||
|
||||
match &mut root_expr {
|
||||
// Cache the hash key for module-qualified variables
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Expr::Variable(x) if x.1.is_some() => {
|
||||
let ((name, _), modules, hash, _) = x.as_mut();
|
||||
let modules = modules.as_mut().unwrap();
|
||||
@ -1496,22 +1475,21 @@ fn parse_op_assignment_stmt<'a>(
|
||||
}
|
||||
|
||||
/// Make a dot expression.
|
||||
fn make_dot_expr(
|
||||
lhs: Expr,
|
||||
rhs: Expr,
|
||||
op_pos: Position,
|
||||
is_index: bool,
|
||||
) -> Result<Expr, ParseError> {
|
||||
fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseError> {
|
||||
Ok(match (lhs, rhs) {
|
||||
// idx_lhs[idx_rhs].rhs
|
||||
// idx_lhs[idx_expr].rhs
|
||||
// Attach dot chain to the bottom level of indexing chain
|
||||
(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, Expr::Variable(x)) if x.1.is_none() => {
|
||||
let (name, pos) = x.0;
|
||||
let lhs = if is_index { lhs.into_property() } else { lhs };
|
||||
|
||||
let getter = make_getter(&name);
|
||||
let setter = make_setter(&name);
|
||||
@ -1519,46 +1497,34 @@ fn make_dot_expr(
|
||||
|
||||
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
|
||||
(_, 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));
|
||||
}
|
||||
// lhs.prop
|
||||
(lhs, prop @ Expr::Property(_)) => Expr::Dot(Box::new((lhs, prop, op_pos))),
|
||||
// lhs.dot_lhs.dot_rhs
|
||||
(lhs, Expr::Dot(x)) => {
|
||||
let (dot_lhs, dot_rhs, pos) = *x;
|
||||
Expr::Dot(Box::new((
|
||||
lhs,
|
||||
Expr::Dot(Box::new((
|
||||
dot_lhs.into_property(),
|
||||
dot_rhs.into_property(),
|
||||
pos,
|
||||
))),
|
||||
Expr::Dot(Box::new((dot_lhs.into_property(), dot_rhs, pos))),
|
||||
op_pos,
|
||||
)))
|
||||
}
|
||||
// lhs.idx_lhs[idx_rhs]
|
||||
(lhs, Expr::Index(x)) => {
|
||||
let (idx_lhs, idx_rhs, pos) = *x;
|
||||
let (dot_lhs, dot_rhs, pos) = *x;
|
||||
Expr::Dot(Box::new((
|
||||
lhs,
|
||||
Expr::Index(Box::new((
|
||||
idx_lhs.into_property(),
|
||||
idx_rhs.into_property(),
|
||||
pos,
|
||||
))),
|
||||
Expr::Index(Box::new((dot_lhs.into_property(), dot_rhs, pos))),
|
||||
op_pos,
|
||||
)))
|
||||
}
|
||||
// lhs.func()
|
||||
(lhs, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))),
|
||||
// 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)),
|
||||
@ -2123,6 +2089,7 @@ fn parse_import<'a>(
|
||||
}
|
||||
|
||||
/// Parse an export statement.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
fn parse_export<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
state: &mut ParseState,
|
||||
@ -2304,7 +2271,9 @@ fn parse_stmt<'a>(
|
||||
Token::LeftBrace => parse_block(input, state, breakable, level + 1, allow_stmt_expr),
|
||||
|
||||
// fn ...
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Token::Fn if !is_global => Err(PERR::WrongFnDefinition.into_err(*pos)),
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Token::Fn => unreachable!(),
|
||||
|
||||
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::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),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -2368,6 +2335,7 @@ fn parse_stmt<'a>(
|
||||
}
|
||||
|
||||
/// Parse a function definition.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
fn parse_fn<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
state: &mut ParseState,
|
||||
@ -2493,24 +2461,23 @@ pub fn parse_global_expr<'a>(
|
||||
fn parse_global_level<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
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 functions = HashMap::<u64, FnDef>::new();
|
||||
let mut functions = HashMap::<u64, FnDef, _>::with_hasher(StraightHasherBuilder);
|
||||
let mut state = ParseState::new(max_expr_depth.0);
|
||||
|
||||
while !input.peek().unwrap().0.is_eof() {
|
||||
// Collect all the function definitions
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
{
|
||||
let mut access = FnAccess::Public;
|
||||
let mut must_be_fn = false;
|
||||
|
||||
if match_token(input, Token::Private)? {
|
||||
access = FnAccess::Private;
|
||||
must_be_fn = true;
|
||||
}
|
||||
let (access, must_be_fn) = if match_token(input, Token::Private)? {
|
||||
(FnAccess::Private, true)
|
||||
} else {
|
||||
(FnAccess::Public, false)
|
||||
};
|
||||
|
||||
match input.peek().unwrap() {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
(Token::Fn, _) => {
|
||||
let mut state = ParseState::new(max_expr_depth.1);
|
||||
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.
|
||||
@ -2576,9 +2543,8 @@ pub fn parse<'a>(
|
||||
optimization_level: OptimizationLevel,
|
||||
max_expr_depth: (usize, usize),
|
||||
) -> 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(
|
||||
// Optimize AST
|
||||
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.
|
||||
|
||||
use crate::any::{Dynamic, Union, Variant};
|
||||
use crate::module::Module;
|
||||
use crate::parser::{map_dynamic_to_expr, Expr};
|
||||
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};
|
||||
|
||||
/// 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.
|
||||
#[cfg(not(feature = "no_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();
|
||||
|
||||
self.push_dynamic_value(
|
||||
@ -350,6 +359,11 @@ impl<'a> Scope<'a> {
|
||||
/// Find a module in the Scope, starting from the last entry.
|
||||
#[cfg(not(feature = "no_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)?;
|
||||
self.get_mut(index).0.downcast_mut::<Module>()
|
||||
}
|
||||
|
@ -181,6 +181,7 @@ pub enum Token {
|
||||
XOr,
|
||||
Ampersand,
|
||||
And,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Fn,
|
||||
Continue,
|
||||
Break,
|
||||
@ -199,6 +200,7 @@ pub enum Token {
|
||||
PowerOfAssign,
|
||||
Private,
|
||||
Import,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Export,
|
||||
As,
|
||||
LexError(Box<LexError>),
|
||||
@ -260,6 +262,7 @@ impl Token {
|
||||
Or => "||",
|
||||
Ampersand => "&",
|
||||
And => "&&",
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Fn => "fn",
|
||||
Continue => "continue",
|
||||
Break => "break",
|
||||
@ -283,6 +286,7 @@ impl Token {
|
||||
PowerOfAssign => "~=",
|
||||
Private => "private",
|
||||
Import => "import",
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Export => "export",
|
||||
As => "as",
|
||||
EOF => "{EOF}",
|
||||
@ -754,12 +758,9 @@ impl<'a> TokenIterator<'a> {
|
||||
"for" => Token::For,
|
||||
"in" => Token::In,
|
||||
"private" => Token::Private,
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"import" => Token::Import,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"export" => Token::Export,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
"as" => Token::As,
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
@ -916,7 +917,6 @@ impl<'a> TokenIterator<'a> {
|
||||
}
|
||||
('=', _) => return Some((Token::Equals, pos)),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
(':', ':') => {
|
||||
self.eat_next();
|
||||
return Some((Token::DoubleColon, pos));
|
||||
|
66
src/utils.rs
66
src/utils.rs
@ -11,7 +11,7 @@ use crate::stdlib::{
|
||||
borrow::Borrow,
|
||||
boxed::Box,
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
hash::{BuildHasher, Hash, Hasher},
|
||||
iter::FromIterator,
|
||||
mem,
|
||||
mem::MaybeUninit,
|
||||
@ -27,6 +27,48 @@ use crate::stdlib::collections::hash_map::DefaultHasher;
|
||||
#[cfg(feature = "no_std")]
|
||||
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.
|
||||
///
|
||||
/// 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;
|
||||
|
||||
impl<T> Drop for StaticVec<T> {
|
||||
#[inline(always)]
|
||||
fn drop(&mut self) {
|
||||
self.clear();
|
||||
}
|
||||
@ -174,6 +217,7 @@ impl<T> FromIterator<T> for StaticVec<T> {
|
||||
|
||||
impl<T> StaticVec<T> {
|
||||
/// Create a new `StaticVec`.
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
@ -189,6 +233,7 @@ impl<T> StaticVec<T> {
|
||||
self.len = 0;
|
||||
}
|
||||
/// Extract a `MaybeUninit` into a concrete initialized type.
|
||||
#[inline(always)]
|
||||
fn extract(value: MaybeUninit<T>) -> T {
|
||||
unsafe { value.assume_init() }
|
||||
}
|
||||
@ -250,6 +295,7 @@ impl<T> StaticVec<T> {
|
||||
);
|
||||
}
|
||||
/// Is data stored in fixed-size storage?
|
||||
#[inline(always)]
|
||||
fn is_fixed_storage(&self) -> bool {
|
||||
self.len <= MAX_STATIC_VEC
|
||||
}
|
||||
@ -359,10 +405,12 @@ impl<T> StaticVec<T> {
|
||||
result
|
||||
}
|
||||
/// Get the number of items in this `StaticVec`.
|
||||
#[inline(always)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
/// Is this `StaticVec` empty?
|
||||
#[inline(always)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
@ -605,41 +653,48 @@ pub struct ImmutableString(Shared<String>);
|
||||
impl Deref for ImmutableString {
|
||||
type Target = String;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<String> for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &String {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn borrow(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn from(value: &str) -> Self {
|
||||
Self(value.to_string().into())
|
||||
}
|
||||
}
|
||||
impl From<String> for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn from(value: String) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<String>> for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn from(value: Box<String>) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ImmutableString> for String {
|
||||
#[inline(always)]
|
||||
fn from(value: ImmutableString) -> Self {
|
||||
value.into_owned()
|
||||
}
|
||||
@ -648,42 +703,49 @@ impl From<ImmutableString> for String {
|
||||
impl FromStr for ImmutableString {
|
||||
type Err = ();
|
||||
|
||||
#[inline(always)]
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(s.to_string().into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<char> for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
|
||||
Self(iter.into_iter().collect::<String>().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<&'a char> for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self {
|
||||
Self(iter.into_iter().cloned().collect::<String>().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<&'a str> for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
|
||||
Self(iter.into_iter().collect::<String>().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<String> for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
|
||||
Self(iter.into_iter().collect::<String>().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(self.0.as_str(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(self.0.as_str(), f)
|
||||
}
|
||||
@ -818,6 +880,7 @@ impl Add<char> for &ImmutableString {
|
||||
}
|
||||
|
||||
impl AddAssign<char> for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn add_assign(&mut self, rhs: char) {
|
||||
self.make_mut().push(rhs);
|
||||
}
|
||||
@ -832,6 +895,7 @@ impl ImmutableString {
|
||||
}
|
||||
/// Make sure that the `ImmutableString` is unique (i.e. no other outstanding references).
|
||||
/// Then return a mutable reference to the `String`.
|
||||
#[inline(always)]
|
||||
pub fn make_mut(&mut self) -> &mut String {
|
||||
shared_make_mut(&mut self.0)
|
||||
}
|
||||
|
@ -21,6 +21,10 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
|
||||
)?,
|
||||
'o'
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<String>(r#"let a = [#{s:"hello"}]; a[0].s[2] = 'X'; a[0].s"#)?,
|
||||
"heXlo"
|
||||
);
|
||||
}
|
||||
|
||||
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 mut scope = Scope::new();
|
||||
|
||||
scope.push_module("testing", module);
|
||||
|
||||
assert_eq!(
|
||||
|
Loading…
Reference in New Issue
Block a user