Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-06-01 10:58:26 +08:00
commit 8e876b0b86
18 changed files with 586 additions and 335 deletions

148
README.md
View File

@ -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).

View File

@ -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

View File

@ -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
View 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.");

View File

@ -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.");

View File

@ -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
View 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.");

View File

@ -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>(),
} }

View File

@ -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.

View File

@ -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) => {

View File

@ -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"))]

View File

@ -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))),
}, },

View File

@ -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),

View File

@ -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>()
} }

View File

@ -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));

View File

@ -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)
} }

View File

@ -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!(

View File

@ -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!(