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).
* Freely pass Rust variables/constants into a script via an external [`Scope`].
* Easily [call a script-defined function](#calling-rhai-functions-from-rust) from Rust.
* Low compile-time overhead (~0.6 sec debug/~3 sec release for `rhai_runner` sample app).
* Fairly efficient evaluation (1 million iterations in 0.75 sec on my 5 year old laptop).
* Fairly low compile-time overhead.
* Fairly efficient evaluation (1 million iterations in 0.25 sec on a single core, 2.3 GHz Linux VM).
* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to
one single source file, all with names starting with `"unsafe_"`).
* Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature).
@ -37,7 +37,7 @@ Features
to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are
pulled in to provide for functionalities that used to be in `std`.
**Note:** Currently, the version is 0.15.0, so the language and API's may change before they stabilize.
**Note:** Currently, the version is `0.15.0`, so the language and API's may change before they stabilize.
What Rhai doesn't do
--------------------
@ -47,15 +47,27 @@ It doesn't attempt to be a new language. For example:
* No classes. Well, Rust doesn't either. On the other hand...
* No traits... so it is also not Rust. Do your Rusty stuff in Rust.
* No structures - define your types in Rust instead; Rhai can seamlessly work with _any Rust type_.
* No structures/records - define your types in Rust instead; Rhai can seamlessly work with _any Rust type_.
There is, however, a built-in [object map] type which is adequate for most uses.
* No first-class functions - Code your functions in Rust instead, and register them with Rhai.
* No garbage collection - this should be expected, so...
* No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure](#calling-rhai-functions-from-rust).
* It is best to expose an API in Rhai for scripts to call. All your core functionalities should be in Rust.
* No byte-codes/JIT - Rhai has an AST-walking interpreter which will not win any speed races. The purpose of Rhai is not
to be extremely _fast_, but to make it as easy as possible to integrate with native Rust programs.
Due to this intended usage, Rhai deliberately keeps the language simple and small by omitting advanced language features
such as classes, inheritance, first-class functions, closures, concurrency, byte-codes, JIT etc.
Avoid the temptation to write full-fledge program logic entirely in Rhai - that use case is best fulfilled by
more complete languages such as JS or Lua.
Therefore, in actual practice, it is usually best to expose a Rust API into Rhai for scripts to call.
All your core functionalities should be in Rust.
This is similar to some dynamic languages where most of the core functionalities reside in a C/C++ standard library.
Installation
------------
Install the Rhai crate by adding this line to `dependencies`:
Install the Rhai crate on [`crates.io`](https::/crates.io/crates/rhai/) by adding this line to `dependencies`:
```toml
[dependencies]
@ -93,7 +105,7 @@ Optional features
| `no_index` | Disable [arrays] and indexing features. |
| `no_object` | Disable support for custom types and [object maps]. |
| `no_function` | Disable script-defined functions. |
| `no_module` | Disable loading modules. |
| `no_module` | Disable loading external modules. |
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
By default, Rhai includes all the standard functionalities in a small, tight package.
@ -179,33 +191,35 @@ A number of examples can be found in the `examples` folder:
Examples can be run with the following command:
```bash
cargo run --example name
cargo run --example {example_name}
```
The `repl` example is a particularly good one as it allows one to interactively try out Rhai's
language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop).
Example Scripts
Example scripts
---------------
There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` folder:
| Language feature scripts | Description |
| ---------------------------------------------------- | ------------------------------------------------------------- |
| ---------------------------------------------------- | ----------------------------------------------------------------------------- |
| [`array.rhai`](scripts/array.rhai) | [arrays] in Rhai |
| [`assignment.rhai`](scripts/assignment.rhai) | variable declarations |
| [`comments.rhai`](scripts/comments.rhai) | just comments |
| [`for1.rhai`](scripts/for1.rhai) | for loops |
| [`function_decl1.rhai`](scripts/function_decl1.rhai) | a function without parameters |
| [`function_decl2.rhai`](scripts/function_decl2.rhai) | a function with two parameters |
| [`function_decl3.rhai`](scripts/function_decl3.rhai) | a function with many parameters |
| [`if1.rhai`](scripts/if1.rhai) | if example |
| [`loop.rhai`](scripts/loop.rhai) | endless loop in Rhai, this example emulates a do..while cycle |
| [`op1.rhai`](scripts/op1.rhai) | just a simple addition |
| [`for1.rhai`](scripts/for1.rhai) | [`for`](#for-loop) loops |
| [`for2.rhai`](scripts/for2.rhai) | [`for`](#for-loop) loops on [arrays] |
| [`function_decl1.rhai`](scripts/function_decl1.rhai) | a [function] without parameters |
| [`function_decl2.rhai`](scripts/function_decl2.rhai) | a [function] with two parameters |
| [`function_decl3.rhai`](scripts/function_decl3.rhai) | a [function] with many parameters |
| [`if1.rhai`](scripts/if1.rhai) | [`if`](#if-statement) example |
| [`loop.rhai`](scripts/loop.rhai) | count-down [`loop`](#infinite-loop) in Rhai, emulating a `do` .. `while` loop |
| [`op1.rhai`](scripts/op1.rhai) | just simple addition |
| [`op2.rhai`](scripts/op2.rhai) | simple addition and multiplication |
| [`op3.rhai`](scripts/op3.rhai) | change evaluation order with parenthesis |
| [`string.rhai`](scripts/string.rhai) | [string] operations |
| [`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 |
| -------------------------------------------- | ---------------------------------------------------------------------------------- |
@ -235,6 +249,7 @@ fn main() -> Result<(), Box<EvalAltResult>>
let engine = Engine::new();
let result = engine.eval::<i64>("40 + 2")?;
// ^^^^^^^ cast the result to an 'i64', this is required
println!("Answer: {}", result); // prints 42
@ -291,6 +306,8 @@ let ast = engine.compile_file("hello_world.rhai".into())?;
### Calling Rhai functions from Rust
[`private`]: #calling-rhai-functions-from-rust
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust via `Engine::call_fn`.
Functions declared with `private` are hidden and cannot be called from Rust (see also [modules]).
@ -341,6 +358,16 @@ let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?;
let result: () = engine.call_fn(&mut scope, &ast, "hidden", ())?;
```
For more control, construct all arguments as `Dynamic` values and use `Engine::call_fn_dynamic`:
```rust
let result: Dynamic = engine.call_fn_dynamic(&mut scope, &ast, "hello",
&mut [ String::from("abc").into(), 123_i64.into() ])?;
```
However, beware that `Engine::call_fn_dynamic` _consumes_ its arguments, meaning that all arguments passed to it
will be replaced by `()` afterwards. To re-use the arguments, clone them beforehand and pass in the clone.
### Creating Rust anonymous functions from Rhai script
[`Func`]: #creating-rust-anonymous-functions-from-rhai-script
@ -704,11 +731,16 @@ let x = (42_i64).into(); // 'into()' works for standard t
let y = Dynamic::from(String::from("hello!")); // remember &str is not supported by Rhai
```
Functions registered with the [`Engine`] can be _overloaded_ as long as the _signature_ is unique,
i.e. different functions can have the same name as long as their parameters are of different types
and/or different number.
New definitions _overwrite_ previous definitions of the same name and same number/types of parameters.
Generic functions
-----------------
Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately.
This is essentially function overloading (Rhai does not natively support generics).
This essentially overloads the function with different parameter types (Rhai does not natively support generics).
```rust
use std::fmt::Display;
@ -729,8 +761,8 @@ fn main()
}
```
This example shows how to register multiple functions (or, in this case, multiple overloaded versions of the same function)
under the same name. This enables function overloading based on the number and types of parameters.
The above example shows how to register multiple functions (or, in this case, multiple overloaded versions of the same function)
under the same name.
Fallible functions
------------------
@ -773,7 +805,8 @@ fn main()
Overriding built-in functions
----------------------------
Any similarly-named function defined in a script overrides any built-in function.
Any similarly-named function defined in a script overrides any built-in function and any registered
native Rust function of the same name and number of parameters.
```rust
// Override the built-in function 'to_int'
@ -784,11 +817,13 @@ fn to_int(num) {
print(to_int(123)); // what happens?
```
A registered function, in turn, overrides any built-in function of the same name and number/types of parameters.
Operator overloading
--------------------
In Rhai, a lot of functionalities are actually implemented as functions, including basic operations such as arithmetic calculations.
For example, in the expression "`a + b`", the `+` operator is _not_ built-in, but calls a function named "`+`" instead!
For example, in the expression "`a + b`", the `+` operator is _not_ built in, but calls a function named "`+`" instead!
```rust
let x = a + b;
@ -801,7 +836,7 @@ overriding them has no effect at all.
Operator functions cannot be defined as a script function (because operators syntax are not valid function names).
However, operator functions _can_ be registered to the [`Engine`] via the methods `Engine::register_fn`, `Engine::register_result_fn` etc.
When a custom operator function is registered with the same name as an operator, it _overloads_ (or overrides) the built-in version.
When a custom operator function is registered with the same name as an operator, it overrides the built-in version.
```rust
use rhai::{Engine, EvalAltResult, RegisterFn};
@ -828,7 +863,7 @@ let result: i64 = engine.eval("1 + 1.0"); // prints 2.0 (normally an e
```
Use operator overloading for custom types (described below) only.
Be very careful when overloading built-in operators because script authors expect standard operators to behave in a
Be very careful when overriding built-in operators because script authors expect standard operators to behave in a
consistent and predictable manner, and will be annoyed if a calculation for '`+`' turns into a subtraction, for example.
Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`].
@ -915,7 +950,7 @@ engine.register_fn("update", TestStruct::update); // registers 'update(&mut Te
engine.register_fn("new_ts", TestStruct::new); // registers 'new()'
```
The custom type is then ready for us in scripts. Scripts can see the functions and methods registered earlier.
The custom type is then ready for use in scripts. Scripts can see the functions and methods registered earlier.
Get the evaluation result back out from script-land just as before, this time casting to the custom type:
```rust
@ -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:
| 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 |
### Examples
@ -1777,15 +1812,19 @@ set of types (see [built-in operators](#built-in-operators)).
"42" == 42; // false
```
Comparing two values of _different_ data types, or of unknown data types, always results in `false`.
Comparing two values of _different_ data types, or of unknown data types, always results in `false`,
except for '`!=`' (not equals) which results in `true`. This is in line with intuition.
```rust
42 == 42.0; // false - i64 is different from f64
42 > "42"; // false - i64 is different from string
42 <= "42"; // false again
42 == 42.0; // false - i64 cannot be compared with f64
42 != 42.0; // true - i64 cannot be compared with f64
42 > "42"; // false - i64 cannot be compared with string
42 <= "42"; // false - i64 cannot be compared with string
let ts = new_ts(); // custom type
ts == 42; // false - types are not the same
ts == 42; // false - types cannot be compared
ts != 42; // true - types cannot be compared
```
Boolean operators
@ -1836,8 +1875,8 @@ my_str += 12345;
my_str == "abcABC12345"
```
`if` statements
---------------
`if` statement
--------------
```rust
if foo(x) {
@ -1872,8 +1911,8 @@ let x = if decision { 42 }; // no else branch defaults to '()'
x == ();
```
`while` loops
-------------
`while` loop
------------
```rust
let x = 10;
@ -1900,8 +1939,8 @@ loop {
}
```
`for` loops
-----------
`for` loop
----------
Iterating through a range or an [array] is provided by the `for` ... `in` loop.
@ -2073,8 +2112,8 @@ This is similar to Rust and many other modern languages.
### Function overloading
Functions can be _overloaded_ and are resolved purely upon the function's _name_ and the _number_ of parameters
(but not parameter _types_, since all parameters are the same type - [`Dynamic`]).
Functions defined in script can be _overloaded_ by _arity_ (i.e. they are resolved purely upon the function's _name_
and _number_ of parameters, but not parameter _types_ since all parameters are the same type - [`Dynamic`]).
New definitions _overwrite_ previous definitions of the same name and number of parameters.
```rust
@ -2172,8 +2211,8 @@ Modules can be disabled via the [`no_module`] feature.
A _module_ is a single script (or pre-compiled `AST`) containing global variables and functions.
The `export` statement, which can only be at global level, exposes selected variables as members of a module.
Variables not exported are _private_ and invisible to the outside.
On the other hand, all functions are automatically exported, _unless_ it is explicitly opt-out with the `private` prefix.
Functions declared `private` are invisible to the outside.
On the other hand, all functions are automatically exported, _unless_ it is explicitly opt-out with the [`private`] prefix.
Functions declared [`private`] are invisible to the outside.
Everything exported from a module is **constant** (**read-only**).
@ -2267,7 +2306,7 @@ engine.eval_expression_with_scope::<i64>(&scope, "question::inc(question::answer
It is easy to convert a pre-compiled `AST` into a module: just use `Module::eval_ast_as_new`.
Don't forget the `export` statement, otherwise there will be no variables exposed by the module
other than non-`private` functions (unless that's intentional).
other than non-[`private`] functions (unless that's intentional).
```rust
use rhai::{Engine, Module};
@ -2346,19 +2385,20 @@ so that it does not consume more resources that it is allowed to.
The most important resources to watch out for are:
* **Memory**: A malignant script may continuously grow an [array] or [object map] until all memory is consumed.
* **Memory**: A malicous script may continuously grow an [array] or [object map] until all memory is consumed.
It may also create a large [array] or [object map] literal that exhausts all memory during parsing.
* **CPU**: A malignant script may run an infinite tight loop that consumes all CPU cycles.
* **Time**: A malignant script may run indefinitely, thereby blocking the calling system which is waiting for a result.
* **Stack**: A malignant script may attempt an infinite recursive call that exhausts the call stack.
* **CPU**: A malicous script may run an infinite tight loop that consumes all CPU cycles.
* **Time**: A malicous script may run indefinitely, thereby blocking the calling system which is waiting for a result.
* **Stack**: A malicous script may attempt an infinite recursive call that exhausts the call stack.
Alternatively, it may create a degenerated deep expression with so many levels that the parser exhausts the call stack
when parsing the expression; or even deeply-nested statement blocks, if nested deep enough.
* **Overflows**: A malignant script may deliberately cause numeric over-flows and/or under-flows, divide by zero, and/or
* **Overflows**: A malicous script may deliberately cause numeric over-flows and/or under-flows, divide by zero, and/or
create bad floating-point representations, in order to crash the system.
* **Files**: A malignant script may continuously [`import`] an external module within an infinite loop,
* **Files**: A malicous script may continuously [`import`] an external module within an infinite loop,
thereby putting heavy load on the file-system (or even the network if the file is not local).
Furthermore, the module script may simply [`import`] itself in an infinite recursion.
Even when modules are not created from files, they still typically consume a lot of resources to load.
* **Data**: A malignant script may attempt to read from and/or write to data that it does not own. If this happens,
* **Data**: A malicous script may attempt to read from and/or write to data that it does not own. If this happens,
it is a severe security breach and may put the entire system at risk.
### Maximum number of operations
@ -2434,7 +2474,7 @@ Rhai by default limits function calls to a maximum depth of 128 levels (16 level
This limit may be changed via the `Engine::set_max_call_levels` method.
When setting this limit, care must be also taken to the evaluation depth of each _statement_
within the function. It is entirely possible for a malignant script to embed an recursive call deep
within the function. It is entirely possible for a malicous script to embed an recursive call deep
inside a nested expression or statement block (see [maximum statement depth](#maximum-statement-depth)).
The limit can be disabled via the [`unchecked`] feature for higher performance
@ -2479,7 +2519,7 @@ engine.set_max_expr_depths(50, 5); // allow nesting up to 50 layers of
Beware that there may be multiple layers for a simple language construct, even though it may correspond
to only one AST node. That is because the Rhai _parser_ internally runs a recursive chain of function calls
and it is important that a malignant script does not panic the parser in the first place.
and it is important that a malicous script does not panic the parser in the first place.
Functions are placed under stricter limits because of the multiplicative effect of recursion.
A script can effectively call itself while deep inside an expression chain within the function body,
@ -2492,7 +2532,7 @@ Make sure that `C x ( 5 + F ) + S` layered calls do not cause a stack overflow,
* `S` = maximum statement depth at global level.
A script exceeding the maximum nesting depths will terminate with a parsing error.
The malignant `AST` will not be able to get past parsing in the first place.
The malicous `AST` will not be able to get past parsing in the first place.
This check can be disabled via the [`unchecked`] feature for higher performance
(but higher risks as well).

View File

@ -13,13 +13,13 @@ Bug fixes
---------
* Indexing with an index or dot expression now works property (it compiled wrongly before).
For example, `let s = "hello"; s[s.len-1] = 'x';` now works property instead of an error.
For example, `let s = "hello"; s[s.len-1] = 'x';` now works property instead of causing a runtime error.
Breaking changes
----------------
* `Engine::compile_XXX` functions now return `ParseError` instead of `Box<ParseError>`.
* The `RegisterDynamicFn` trait is merged into the `RegisterResutlFn` trait which now always returns
* The `RegisterDynamicFn` trait is merged into the `RegisterResultFn` trait which now always returns
`Result<Dynamic, Box<EvalAltResult>>`.
* Default maximum limit on levels of nested function calls is fine-tuned and set to a different value.
* Some operator functions are now built in (see _Speed enhancements_ below), so they are available even
@ -41,6 +41,7 @@ New features
* Set limit on maximum level of nesting expressions and statements to avoid panics during parsing.
* New `EvalPackage` to disable `eval`.
* `Module::set_getter_fn`, `Module::set_setter_fn` and `Module:set_indexer_fn` to register getter/setter/indexer functions.
* `Engine::call_fn_dynamic` for more control in calling script functions.
Speed enhancements
------------------
@ -60,6 +61,8 @@ Speed enhancements
excessive cloning. For example, if `a` is a large array, getting its length in this manner: `len(a)` used to result
in a full clone of `a` before taking the length and throwing the copy away. Now, `a` is simply passed by reference,
avoiding the cloning altogether.
* A custom hasher simply passes through `u64` keys without hashing to avoid function call hash keys
(which are by themselves `u64`) being hashed twice.
Version 0.14.1

View File

@ -61,3 +61,27 @@ fn bench_eval_array_large_set(bench: &mut Bencher) {
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_array_loop(bench: &mut Bencher) {
let script = r#"
let list = [];
for i in range(0, 10_000) {
list.push(i);
}
let sum = 0;
for i in list {
sum += i;
}
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}

22
scripts/for2.rhai Normal file
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 c = mat_mul(a, b);
/*
for i in range(0, SIZE) {
print(c[i]);
}
*/
print("Finished. Run time = " + now.elapsed + " seconds.");

View File

@ -2,7 +2,7 @@
let now = timestamp();
const MAX_NUMBER_TO_CHECK = 100_000; // 9592 primes <= 100000
const MAX_NUMBER_TO_CHECK = 1_000_000; // 9592 primes <= 100000
let prime_mask = [];
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
@ -15,7 +15,7 @@ let total_primes_found = 0;
for p in range(2, MAX_NUMBER_TO_CHECK) {
if !prime_mask[p] { continue; }
print(p);
//print(p);
total_primes_found += 1;
@ -28,6 +28,6 @@ for p in range(2, MAX_NUMBER_TO_CHECK) {
print("Total " + total_primes_found + " primes <= " + MAX_NUMBER_TO_CHECK);
print("Run time = " + now.elapsed + " seconds.");
if total_primes_found != 9_592 {
print("The answer is WRONG! Should be 9,592!");
if total_primes_found != 78_498 {
print("The answer is WRONG! Should be 78,498!");
}

103
scripts/strings_map.rhai Normal file
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.
use crate::module::Module;
use crate::parser::{ImmutableString, INT};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
#[cfg(not(feature = "no_module"))]
use crate::module::Module;
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
@ -160,7 +158,6 @@ pub enum Union {
Array(Box<Array>),
#[cfg(not(feature = "no_object"))]
Map(Box<Map>),
#[cfg(not(feature = "no_module"))]
Module(Box<Module>),
Variant(Box<Box<dyn Variant>>),
}
@ -198,7 +195,6 @@ impl Dynamic {
Union::Array(_) => TypeId::of::<Array>(),
#[cfg(not(feature = "no_object"))]
Union::Map(_) => TypeId::of::<Map>(),
#[cfg(not(feature = "no_module"))]
Union::Module(_) => TypeId::of::<Module>(),
Union::Variant(value) => (***value).type_id(),
}
@ -218,7 +214,6 @@ impl Dynamic {
Union::Array(_) => "array",
#[cfg(not(feature = "no_object"))]
Union::Map(_) => "map",
#[cfg(not(feature = "no_module"))]
Union::Module(_) => "sub-scope",
#[cfg(not(feature = "no_std"))]
@ -242,7 +237,6 @@ impl fmt::Display for Dynamic {
Union::Array(value) => write!(f, "{:?}", value),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => write!(f, "#{:?}", value),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => write!(f, "{:?}", value),
#[cfg(not(feature = "no_std"))]
@ -266,7 +260,6 @@ impl fmt::Debug for Dynamic {
Union::Array(value) => write!(f, "{:?}", value),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => write!(f, "#{:?}", value),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => write!(f, "{:?}", value),
#[cfg(not(feature = "no_std"))]
@ -290,7 +283,6 @@ impl Clone for Dynamic {
Union::Array(ref value) => Self(Union::Array(value.clone())),
#[cfg(not(feature = "no_object"))]
Union::Map(ref value) => Self(Union::Map(value.clone())),
#[cfg(not(feature = "no_module"))]
Union::Module(ref value) => Self(Union::Module(value.clone())),
Union::Variant(ref value) => (***value).clone_into_dynamic(),
}
@ -426,7 +418,6 @@ impl Dynamic {
Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
}
@ -470,7 +461,6 @@ impl Dynamic {
Union::Array(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).unwrap(),
}
@ -498,7 +488,6 @@ impl Dynamic {
Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(),
}
@ -524,7 +513,6 @@ impl Dynamic {
Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(),
}

View File

@ -997,6 +997,7 @@ impl Engine {
}
/// Call a script function defined in an `AST` with multiple arguments.
/// Arguments are passed as a tuple.
///
/// # Example
///
@ -1040,6 +1041,67 @@ impl Engine {
args: A,
) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = args.into_vec();
let result = self.call_fn_dynamic(scope, ast, name, arg_values.as_mut())?;
let return_type = self.map_type_name(result.type_name());
return result.try_cast().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
return_type.into(),
Position::none(),
))
});
}
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments.
///
/// ## WARNING
///
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
/// This is to avoid unnecessarily cloning the arguments.
/// Do you use the arguments after this call. If you need them afterwards,
/// clone them _before_ calling this function.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_function"))]
/// # {
/// use rhai::{Engine, Scope};
///
/// let engine = Engine::new();
///
/// let ast = engine.compile(r"
/// fn add(x, y) { len(x) + y + foo }
/// fn add1(x) { len(x) + 1 + foo }
/// fn bar() { foo/2 }
/// ")?;
///
/// let mut scope = Scope::new();
/// scope.push("foo", 42_i64);
///
/// // Call the script-defined function
/// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", &mut [ String::from("abc").into(), 123_i64.into() ])?;
/// assert_eq!(result.cast::<i64>(), 168);
///
/// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", &mut [ String::from("abc").into() ])?;
/// assert_eq!(result.cast::<i64>(), 46);
///
/// let result= engine.call_fn_dynamic(&mut scope, &ast, "bar", &mut [])?;
/// assert_eq!(result.cast::<i64>(), 21);
/// # }
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_function"))]
pub fn call_fn_dynamic(
&self,
scope: &mut Scope,
ast: &AST,
name: &str,
arg_values: &mut [Dynamic],
) -> Result<Dynamic, Box<EvalAltResult>> {
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let lib = ast.lib();
let pos = Position::none();
@ -1051,16 +1113,7 @@ impl Engine {
let mut state = State::new();
let args = args.as_mut();
let result = self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, pos, 0)?;
let return_type = self.map_type_name(result.type_name());
return result.try_cast().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
return_type.into(),
pos,
))
});
self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, pos, 0)
}
/// Optimize the `AST` with constants defined in an external Scope.

View File

@ -4,7 +4,7 @@ use crate::any::{Dynamic, Union};
use crate::calc_fn_hash;
use crate::error::ParseErrorType;
use crate::fn_native::{CallableFunction, FnCallArgs, Shared};
use crate::module::Module;
use crate::module::{resolvers, Module, ModuleResolver};
use crate::optimize::OptimizationLevel;
use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage};
use crate::parser::{Expr, FnAccess, FnDef, ImmutableString, ReturnType, Stmt, AST, INT};
@ -12,14 +12,11 @@ use crate::r#unsafe::{unsafe_cast_var_name_to_lifetime, unsafe_mut_cast_to_lifet
use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::token::Position;
use crate::utils::StaticVec;
use crate::utils::{StaticVec, StraightHasherBuilder};
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
#[cfg(not(feature = "no_module"))]
use crate::module::{resolvers, ModuleResolver};
use crate::stdlib::{
any::TypeId,
boxed::Box,
@ -196,7 +193,7 @@ impl State {
///
/// The key of the `HashMap` is a `u64` hash calculated by the function `calc_fn_hash`.
#[derive(Debug, Clone, Default)]
pub struct FunctionsLib(HashMap<u64, Shared<FnDef>>);
pub struct FunctionsLib(HashMap<u64, Shared<FnDef>, StraightHasherBuilder>);
impl FunctionsLib {
/// Create a new `FunctionsLib` from a collection of `FnDef`.
@ -261,7 +258,7 @@ impl From<Vec<(u64, Shared<FnDef>)>> for FunctionsLib {
}
impl Deref for FunctionsLib {
type Target = HashMap<u64, Shared<FnDef>>;
type Target = HashMap<u64, Shared<FnDef>, StraightHasherBuilder>;
fn deref(&self) -> &Self::Target {
&self.0
@ -269,7 +266,7 @@ impl Deref for FunctionsLib {
}
impl DerefMut for FunctionsLib {
fn deref_mut(&mut self) -> &mut HashMap<u64, Shared<FnDef>> {
fn deref_mut(&mut self) -> &mut HashMap<u64, Shared<FnDef>, StraightHasherBuilder> {
&mut self.0
}
}
@ -297,7 +294,6 @@ pub struct Engine {
pub(crate) packages: PackagesCollection,
/// A module resolution service.
#[cfg(not(feature = "no_module"))]
pub(crate) module_resolver: Option<Box<dyn ModuleResolver>>,
/// A hashmap mapping type names to pretty-print names.
@ -350,8 +346,7 @@ impl Default for Engine {
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))]
module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())),
#[cfg(not(feature = "no_module"))]
#[cfg(feature = "no_std")]
#[cfg(any(feature = "no_module", feature = "no_std"))]
module_resolver: None,
type_names: HashMap::new(),
@ -441,10 +436,7 @@ fn search_scope<'s, 'a>(
Expr::Variable(x) => x.as_ref(),
_ => unreachable!(),
};
let index = if state.always_search { None } else { *index };
#[cfg(not(feature = "no_module"))]
{
if let Some(modules) = modules.as_ref() {
let module = if let Some(index) = modules.index() {
scope
@ -455,9 +447,9 @@ fn search_scope<'s, 'a>(
} else {
let (id, root_pos) = modules.get(0);
scope.find_module(id).ok_or_else(|| {
Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos))
})?
scope
.find_module_internal(id)
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))?
};
return Ok((
@ -468,7 +460,8 @@ fn search_scope<'s, 'a>(
*pos,
));
}
}
let index = if state.always_search { None } else { *index };
let index = if let Some(index) = index {
scope.len() - index.get()
@ -495,8 +488,6 @@ impl Engine {
Self {
packages: Default::default(),
global_module: Default::default(),
#[cfg(not(feature = "no_module"))]
module_resolver: None,
type_names: HashMap::new(),
@ -952,21 +943,24 @@ impl Engine {
let mut idx_val = idx_values.pop();
if is_index {
let pos = rhs.position();
match rhs {
// xxx[idx].dot_rhs... | xxx[idx][dot_rhs]...
// xxx[idx].expr... | xxx[idx][expr]...
Expr::Dot(x) | Expr::Index(x) => {
let (idx, expr, pos) = x.as_ref();
let is_idx = matches!(rhs, Expr::Index(_));
let pos = x.0.position();
let this_ptr = &mut self
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, false)?;
let idx_pos = idx.position();
let this_ptr = &mut self.get_indexed_mut(
state, lib, obj, is_ref, idx_val, idx_pos, op_pos, false,
)?;
self.eval_dot_index_chain_helper(
state, lib, this_ptr, &x.1, idx_values, is_idx, x.2, level, new_val,
state, lib, this_ptr, expr, idx_values, is_idx, *pos, level, new_val,
)
}
// xxx[rhs] = new_val
_ if new_val.is_some() => {
let pos = rhs.position();
let this_ptr = &mut self
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, true)?;
@ -975,16 +969,7 @@ impl Engine {
}
// xxx[rhs]
_ => self
.get_indexed_mut(
state,
lib,
obj,
is_ref,
idx_val,
rhs.position(),
op_pos,
false,
)
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, false)
.map(|v| (v.clone_into_dynamic(), false)),
}
} else {
@ -1050,57 +1035,51 @@ impl Engine {
.map(|(v, _)| (v, false))
}
#[cfg(not(feature = "no_object"))]
// {xxx:map}.idx_lhs[idx_expr] | {xxx:map}.dot_lhs.rhs
// {xxx:map}.prop[expr] | {xxx:map}.prop.expr
Expr::Index(x) | Expr::Dot(x) if obj.is::<Map>() => {
let (prop, expr, pos) = x.as_ref();
let is_idx = matches!(rhs, Expr::Index(_));
let mut val = if let Expr::Property(p) = &x.0 {
let mut val = if let Expr::Property(p) = prop {
let ((prop, _, _), _) = p.as_ref();
let index = prop.clone().into();
self.get_indexed_mut(state, lib, obj, is_ref, index, x.2, op_pos, false)?
self.get_indexed_mut(state, lib, obj, is_ref, index, *pos, op_pos, false)?
} else {
// Syntax error
return Err(Box::new(EvalAltResult::ErrorDotExpr(
"".into(),
rhs.position(),
)));
unreachable!();
};
self.eval_dot_index_chain_helper(
state, lib, &mut val, &x.1, idx_values, is_idx, x.2, level, new_val,
state, lib, &mut val, expr, idx_values, is_idx, *pos, level, new_val,
)
}
// xxx.idx_lhs[idx_expr] | xxx.dot_lhs.rhs
// xxx.prop[expr] | xxx.prop.expr
Expr::Index(x) | Expr::Dot(x) => {
let (prop, expr, pos) = x.as_ref();
let is_idx = matches!(rhs, Expr::Index(_));
let args = &mut [obj, &mut Default::default()];
let (mut val, updated) = if let Expr::Property(p) = &x.0 {
let (mut val, updated) = if let Expr::Property(p) = prop {
let ((_, getter, _), _) = p.as_ref();
let args = &mut args[..1];
self.exec_fn_call(state, lib, getter, true, 0, args, is_ref, None, x.2, 0)?
self.exec_fn_call(state, lib, getter, true, 0, args, is_ref, None, *pos, 0)?
} else {
// Syntax error
return Err(Box::new(EvalAltResult::ErrorDotExpr(
"".into(),
rhs.position(),
)));
unreachable!();
};
let val = &mut val;
let target = &mut val.into();
let (result, may_be_changed) = self.eval_dot_index_chain_helper(
state, lib, target, &x.1, idx_values, is_idx, x.2, level, new_val,
state, lib, target, expr, idx_values, is_idx, *pos, level, new_val,
)?;
// Feed the value back via a setter just in case it has been updated
if updated || may_be_changed {
if let Expr::Property(p) = &x.0 {
if let Expr::Property(p) = prop {
let ((_, _, setter), _) = p.as_ref();
// Re-use args because the first &mut parameter will not be consumed
args[1] = val;
self.exec_fn_call(
state, lib, setter, true, 0, args, is_ref, None, x.2, 0,
state, lib, setter, true, 0, args, is_ref, None, *pos, 0,
)
.or_else(|err| match *err {
// If there is no setter, no need to feed it back because the property is read-only
@ -1127,13 +1106,16 @@ impl Engine {
scope: &mut Scope,
state: &mut State,
lib: &FunctionsLib,
dot_lhs: &Expr,
dot_rhs: &Expr,
is_index: bool,
op_pos: Position,
expr: &Expr,
level: usize,
new_val: Option<Dynamic>,
) -> Result<Dynamic, Box<EvalAltResult>> {
let ((dot_lhs, dot_rhs, op_pos), is_index) = match expr {
Expr::Index(x) => (x.as_ref(), true),
Expr::Dot(x) => (x.as_ref(), false),
_ => unreachable!(),
};
let idx_values = &mut StaticVec::new();
self.eval_indexed_chain(scope, state, lib, dot_rhs, idx_values, 0, level)?;
@ -1158,7 +1140,7 @@ impl Engine {
let this_ptr = &mut target.into();
self.eval_dot_index_chain_helper(
state, lib, this_ptr, dot_rhs, idx_values, is_index, op_pos, level, new_val,
state, lib, this_ptr, dot_rhs, idx_values, is_index, *op_pos, level, new_val,
)
.map(|(v, _)| v)
}
@ -1173,7 +1155,7 @@ impl Engine {
let val = self.eval_expr(scope, state, lib, expr, level)?;
let this_ptr = &mut val.into();
self.eval_dot_index_chain_helper(
state, lib, this_ptr, dot_rhs, idx_values, is_index, op_pos, level, new_val,
state, lib, this_ptr, dot_rhs, idx_values, is_index, *op_pos, level, new_val,
)
.map(|(v, _)| v)
}
@ -1489,14 +1471,14 @@ impl Engine {
Expr::Variable(_) => unreachable!(),
// idx_lhs[idx_expr] op= rhs
#[cfg(not(feature = "no_index"))]
Expr::Index(x) => self.eval_dot_index_chain(
scope, state, lib, &x.0, &x.1, true, x.2, level, new_val,
),
Expr::Index(_) => {
self.eval_dot_index_chain(scope, state, lib, lhs_expr, level, new_val)
}
// dot_lhs.dot_rhs op= rhs
#[cfg(not(feature = "no_object"))]
Expr::Dot(x) => self.eval_dot_index_chain(
scope, state, lib, &x.0, &x.1, false, *op_pos, level, new_val,
),
Expr::Dot(_) => {
self.eval_dot_index_chain(scope, state, lib, lhs_expr, level, new_val)
}
// Error assignment to constant
expr if expr.is_constant() => {
Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
@ -1513,15 +1495,11 @@ impl Engine {
// lhs[idx_expr]
#[cfg(not(feature = "no_index"))]
Expr::Index(x) => {
self.eval_dot_index_chain(scope, state, lib, &x.0, &x.1, true, x.2, level, None)
}
Expr::Index(_) => self.eval_dot_index_chain(scope, state, lib, expr, level, None),
// lhs.dot_rhs
#[cfg(not(feature = "no_object"))]
Expr::Dot(x) => {
self.eval_dot_index_chain(scope, state, lib, &x.0, &x.1, false, x.2, level, None)
}
Expr::Dot(_) => self.eval_dot_index_chain(scope, state, lib, expr, level, None),
#[cfg(not(feature = "no_index"))]
Expr::Array(x) => Ok(Dynamic(Union::Array(Box::new(
@ -1621,7 +1599,6 @@ impl Engine {
}
// Module-qualified function call
#[cfg(not(feature = "no_module"))]
Expr::FnCall(x) if x.1.is_some() => {
let ((name, _, pos), modules, hash_fn_def, args_expr, def_val) = x.as_ref();
let modules = modules.as_ref().unwrap();
@ -1642,7 +1619,7 @@ impl Engine {
.downcast_mut::<Module>()
.unwrap()
} else {
scope.find_module(id).ok_or_else(|| {
scope.find_module_internal(id).ok_or_else(|| {
Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos))
})?
};
@ -1931,11 +1908,6 @@ impl Engine {
// Import statement
Stmt::Import(x) => {
#[cfg(feature = "no_module")]
unreachable!();
#[cfg(not(feature = "no_module"))]
{
let (expr, (name, pos)) = x.as_ref();
// Guard against too many modules
@ -1946,6 +1918,8 @@ impl Engine {
if let Some(path) = self
.eval_expr(scope, state, lib, &expr, level)?
.try_cast::<ImmutableString>()
{
#[cfg(not(feature = "no_module"))]
{
if let Some(resolver) = &self.module_resolver {
// Use an empty scope to create a module
@ -1953,7 +1927,7 @@ impl Engine {
resolver.resolve(self, Scope::new(), &path, expr.position())?;
let mod_name = unsafe_cast_var_name_to_lifetime(name, &state);
scope.push_module(mod_name, module);
scope.push_module_internal(mod_name, module);
state.modules += 1;
@ -1964,11 +1938,14 @@ impl Engine {
expr.position(),
)))
}
}
#[cfg(feature = "no_module")]
Ok(Default::default())
} else {
Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position())))
}
}
}
// Export statement
Stmt::Export(list) => {

View File

@ -3,7 +3,7 @@
use crate::any::{Dynamic, Variant};
use crate::calc_fn_hash;
use crate::engine::{make_getter, make_setter, Engine, FunctionsLib, FUNC_INDEXER};
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn};
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync};
use crate::parser::{
FnAccess,
FnAccess::{Private, Public},
@ -12,7 +12,7 @@ use crate::parser::{
use crate::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::token::{Position, Token};
use crate::utils::StaticVec;
use crate::utils::{StaticVec, StraightHasherBuilder};
use crate::stdlib::{
any::TypeId,
@ -44,10 +44,14 @@ pub struct Module {
variables: HashMap<String, Dynamic>,
/// Flattened collection of all module variables, including those in sub-modules.
all_variables: HashMap<u64, Dynamic>,
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
/// External Rust functions.
functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, CallableFunction)>,
functions: HashMap<
u64,
(String, FnAccess, StaticVec<TypeId>, CallableFunction),
StraightHasherBuilder,
>,
/// Script-defined functions.
lib: FunctionsLib,
@ -57,7 +61,7 @@ pub struct Module {
/// Flattened collection of all external Rust functions, native or scripted,
/// including those in sub-modules.
all_functions: HashMap<u64, CallableFunction>,
all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>,
}
impl fmt::Debug for Module {
@ -101,7 +105,7 @@ impl Module {
/// ```
pub fn new_with_capacity(capacity: usize) -> Self {
Self {
functions: HashMap::with_capacity(capacity),
functions: HashMap::with_capacity_and_hasher(capacity, StraightHasherBuilder),
..Default::default()
}
}
@ -941,23 +945,7 @@ impl ModuleRef {
}
/// Trait that encapsulates a module resolution service.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "sync"))]
pub trait ModuleResolver {
/// Resolve a module based on a path string.
fn resolve(
&self,
engine: &Engine,
scope: Scope,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>>;
}
/// Trait that encapsulates a module resolution service.
#[cfg(not(feature = "no_module"))]
#[cfg(feature = "sync")]
pub trait ModuleResolver: Send + Sync {
pub trait ModuleResolver: SendSync {
/// Resolve a module based on a path string.
fn resolve(
&self,
@ -975,6 +963,8 @@ pub mod resolvers {
pub use super::file::FileModuleResolver;
pub use super::stat::StaticModuleResolver;
}
#[cfg(feature = "no_module")]
pub mod resolvers {}
/// Script file-based module resolver.
#[cfg(not(feature = "no_module"))]

View File

@ -37,6 +37,10 @@ impl OptimizationLevel {
pub fn is_none(self) -> bool {
self == Self::None
}
/// Is the `OptimizationLevel` Simple.
pub fn is_simple(self) -> bool {
self == Self::Simple
}
/// Is the `OptimizationLevel` Full.
pub fn is_full(self) -> bool {
self == Self::Full
@ -381,10 +385,10 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// ( stmt )
stmt => Expr::Stmt(Box::new((stmt, x.1))),
},
// id = expr
// id op= expr
Expr::Assignment(x) => match x.2 {
//id = id2 op= expr2
Expr::Assignment(x2) if x.1 == "=" => match (x.0, x2.0) {
//id = id2 op= rhs
Expr::Assignment(x2) if x.1.is_empty() => match (x.0, &x2.0) {
// var = var op= expr2 -> var op= expr2
(Expr::Variable(a), Expr::Variable(b))
if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 =>
@ -393,14 +397,10 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
state.set_dirty();
Expr::Assignment(Box::new((Expr::Variable(a), x2.1, optimize_expr(x2.2, state), x.3)))
}
// id1 = id2 op= expr2
(id1, id2) => {
Expr::Assignment(Box::new((
id1, x.1, Expr::Assignment(Box::new((id2, x2.1, optimize_expr(x2.2, state), x2.3))), x.3,
)))
}
// expr1 = expr2 op= rhs
(expr1, _) => Expr::Assignment(Box::new((expr1, x.1, optimize_expr(Expr::Assignment(x2), state), x.3))),
},
// id op= expr
// expr = rhs
expr => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(expr, state), x.3))),
},

View File

@ -4,17 +4,11 @@ use crate::any::{Dynamic, Union};
use crate::calc_fn_hash;
use crate::engine::{make_getter, make_setter, Engine, FunctionsLib};
use crate::error::{LexError, ParseError, ParseErrorType};
use crate::module::ModuleRef;
use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::token::{Position, Token, TokenIterator};
use crate::utils::StaticVec;
#[cfg(not(feature = "no_module"))]
use crate::module::ModuleRef;
#[cfg(feature = "no_module")]
#[derive(Debug, Eq, PartialEq, Clone, Hash, Copy, Default)]
pub struct ModuleRef;
use crate::utils::{StaticVec, StraightHasherBuilder};
use crate::stdlib::{
borrow::Cow,
@ -644,7 +638,6 @@ impl Expr {
Self::Variable(_) => match token {
Token::LeftBracket | Token::LeftParen => true,
#[cfg(not(feature = "no_module"))]
Token::DoubleColon => true,
_ => false,
},
@ -761,9 +754,7 @@ fn parse_call_expr<'a>(
Token::RightParen => {
eat_token(input, Token::RightParen);
#[cfg(not(feature = "no_module"))]
let hash_fn_def = {
if let Some(modules) = modules.as_mut() {
let hash_fn_def = if let Some(modules) = modules.as_mut() {
modules.set_index(state.find_module(&modules.get(0).0));
// Rust functions are indexed in two steps:
@ -777,11 +768,7 @@ fn parse_call_expr<'a>(
} else {
// Qualifiers (none) + function name + no parameters.
calc_fn_hash(empty(), &id, 0, empty())
}
};
// Qualifiers (none) + function name + no parameters.
#[cfg(feature = "no_module")]
let hash_fn_def = calc_fn_hash(empty(), &id, 0, empty());
return Ok(Expr::FnCall(Box::new((
(id.into(), false, begin),
@ -803,9 +790,7 @@ fn parse_call_expr<'a>(
(Token::RightParen, _) => {
eat_token(input, Token::RightParen);
#[cfg(not(feature = "no_module"))]
let hash_fn_def = {
if let Some(modules) = modules.as_mut() {
let hash_fn_def = if let Some(modules) = modules.as_mut() {
modules.set_index(state.find_module(&modules.get(0).0));
// Rust functions are indexed in two steps:
@ -819,11 +804,7 @@ fn parse_call_expr<'a>(
} else {
// Qualifiers (none) + function name + number of arguments.
calc_fn_hash(empty(), &id, args.len(), empty())
}
};
// Qualifiers (none) + function name + number of arguments.
#[cfg(feature = "no_module")]
let hash_fn_def = calc_fn_hash(empty(), &id, args.len(), empty());
return Ok(Expr::FnCall(Box::new((
(id.into(), false, begin),
@ -1265,7 +1246,6 @@ fn parse_primary<'a>(
}
(Expr::Property(_), _) => unreachable!(),
// module access
#[cfg(not(feature = "no_module"))]
(Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() {
(Token::Identifier(id2), pos2) => {
let ((name, pos), mut modules, _, index) = *x;
@ -1293,7 +1273,6 @@ fn parse_primary<'a>(
match &mut root_expr {
// Cache the hash key for module-qualified variables
#[cfg(not(feature = "no_module"))]
Expr::Variable(x) if x.1.is_some() => {
let ((name, _), modules, hash, _) = x.as_mut();
let modules = modules.as_mut().unwrap();
@ -1496,22 +1475,21 @@ fn parse_op_assignment_stmt<'a>(
}
/// Make a dot expression.
fn make_dot_expr(
lhs: Expr,
rhs: Expr,
op_pos: Position,
is_index: bool,
) -> Result<Expr, ParseError> {
fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseError> {
Ok(match (lhs, rhs) {
// idx_lhs[idx_rhs].rhs
// idx_lhs[idx_expr].rhs
// Attach dot chain to the bottom level of indexing chain
(Expr::Index(x), rhs) => {
Expr::Index(Box::new((x.0, make_dot_expr(x.1, rhs, op_pos, true)?, x.2)))
let (idx_lhs, idx_expr, pos) = *x;
Expr::Index(Box::new((
idx_lhs,
make_dot_expr(idx_expr, rhs, op_pos)?,
pos,
)))
}
// lhs.id
(lhs, Expr::Variable(x)) if x.1.is_none() => {
let (name, pos) = x.0;
let lhs = if is_index { lhs.into_property() } else { lhs };
let getter = make_getter(&name);
let setter = make_setter(&name);
@ -1519,46 +1497,34 @@ fn make_dot_expr(
Expr::Dot(Box::new((lhs, rhs, op_pos)))
}
(lhs, Expr::Property(x)) => {
let lhs = if is_index { lhs.into_property() } else { lhs };
let rhs = Expr::Property(x);
Expr::Dot(Box::new((lhs, rhs, op_pos)))
}
// lhs.module::id - syntax error
(_, Expr::Variable(x)) if x.1.is_some() => {
#[cfg(feature = "no_module")]
unreachable!();
#[cfg(not(feature = "no_module"))]
return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get(0).1));
}
// lhs.prop
(lhs, prop @ Expr::Property(_)) => Expr::Dot(Box::new((lhs, prop, op_pos))),
// lhs.dot_lhs.dot_rhs
(lhs, Expr::Dot(x)) => {
let (dot_lhs, dot_rhs, pos) = *x;
Expr::Dot(Box::new((
lhs,
Expr::Dot(Box::new((
dot_lhs.into_property(),
dot_rhs.into_property(),
pos,
))),
Expr::Dot(Box::new((dot_lhs.into_property(), dot_rhs, pos))),
op_pos,
)))
}
// lhs.idx_lhs[idx_rhs]
(lhs, Expr::Index(x)) => {
let (idx_lhs, idx_rhs, pos) = *x;
let (dot_lhs, dot_rhs, pos) = *x;
Expr::Dot(Box::new((
lhs,
Expr::Index(Box::new((
idx_lhs.into_property(),
idx_rhs.into_property(),
pos,
))),
Expr::Index(Box::new((dot_lhs.into_property(), dot_rhs, pos))),
op_pos,
)))
}
// lhs.func()
(lhs, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))),
// lhs.rhs
(lhs, rhs) => Expr::Dot(Box::new((lhs, rhs.into_property(), op_pos))),
_ => unreachable!(),
})
}
@ -1822,7 +1788,7 @@ fn parse_binary_op<'a>(
_ => (),
}
make_dot_expr(current_lhs, rhs, pos, false)?
make_dot_expr(current_lhs, rhs, pos)?
}
token => return Err(PERR::UnknownOperator(token.into()).into_err(pos)),
@ -2123,6 +2089,7 @@ fn parse_import<'a>(
}
/// Parse an export statement.
#[cfg(not(feature = "no_module"))]
fn parse_export<'a>(
input: &mut Peekable<TokenIterator<'a>>,
state: &mut ParseState,
@ -2304,7 +2271,9 @@ fn parse_stmt<'a>(
Token::LeftBrace => parse_block(input, state, breakable, level + 1, allow_stmt_expr),
// fn ...
#[cfg(not(feature = "no_function"))]
Token::Fn if !is_global => Err(PERR::WrongFnDefinition.into_err(*pos)),
#[cfg(not(feature = "no_function"))]
Token::Fn => unreachable!(),
Token::If => parse_if(input, state, breakable, level + 1, allow_stmt_expr),
@ -2353,8 +2322,6 @@ fn parse_stmt<'a>(
Token::Let => parse_let(input, state, Normal, level + 1, allow_stmt_expr),
Token::Const => parse_let(input, state, Constant, level + 1, allow_stmt_expr),
#[cfg(not(feature = "no_module"))]
Token::Import => parse_import(input, state, level + 1, allow_stmt_expr),
#[cfg(not(feature = "no_module"))]
@ -2368,6 +2335,7 @@ fn parse_stmt<'a>(
}
/// Parse a function definition.
#[cfg(not(feature = "no_function"))]
fn parse_fn<'a>(
input: &mut Peekable<TokenIterator<'a>>,
state: &mut ParseState,
@ -2493,24 +2461,23 @@ pub fn parse_global_expr<'a>(
fn parse_global_level<'a>(
input: &mut Peekable<TokenIterator<'a>>,
max_expr_depth: (usize, usize),
) -> Result<(Vec<Stmt>, HashMap<u64, FnDef>), ParseError> {
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
let mut statements = Vec::<Stmt>::new();
let mut functions = HashMap::<u64, FnDef>::new();
let mut functions = HashMap::<u64, FnDef, _>::with_hasher(StraightHasherBuilder);
let mut state = ParseState::new(max_expr_depth.0);
while !input.peek().unwrap().0.is_eof() {
// Collect all the function definitions
#[cfg(not(feature = "no_function"))]
{
let mut access = FnAccess::Public;
let mut must_be_fn = false;
if match_token(input, Token::Private)? {
access = FnAccess::Private;
must_be_fn = true;
}
let (access, must_be_fn) = if match_token(input, Token::Private)? {
(FnAccess::Private, true)
} else {
(FnAccess::Public, false)
};
match input.peek().unwrap() {
#[cfg(not(feature = "no_function"))]
(Token::Fn, _) => {
let mut state = ParseState::new(max_expr_depth.1);
let func = parse_fn(input, &mut state, access, 0, true)?;
@ -2565,7 +2532,7 @@ fn parse_global_level<'a>(
}
}
Ok((statements, functions))
Ok((statements, functions.into_iter().map(|(_, v)| v).collect()))
}
/// Run the parser on an input stream, returning an AST.
@ -2576,9 +2543,8 @@ pub fn parse<'a>(
optimization_level: OptimizationLevel,
max_expr_depth: (usize, usize),
) -> Result<AST, ParseError> {
let (statements, functions) = parse_global_level(input, max_expr_depth)?;
let (statements, lib) = parse_global_level(input, max_expr_depth)?;
let lib = functions.into_iter().map(|(_, v)| v).collect();
Ok(
// Optimize AST
optimize_into_ast(engine, scope, statements, lib, optimization_level),

View File

@ -1,12 +1,10 @@
//! Module that defines the `Scope` type representing a function call-stack scope.
use crate::any::{Dynamic, Union, Variant};
use crate::module::Module;
use crate::parser::{map_dynamic_to_expr, Expr};
use crate::token::Position;
#[cfg(not(feature = "no_module"))]
use crate::module::Module;
use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec};
/// Type of an entry in the Scope.
@ -178,6 +176,17 @@ impl<'a> Scope<'a> {
/// Modules are used for accessing member variables, functions and plugins under a namespace.
#[cfg(not(feature = "no_module"))]
pub fn push_module<K: Into<Cow<'a, str>>>(&mut self, name: K, mut value: Module) {
self.push_module_internal(name, value);
}
/// Add (push) a new module to the Scope.
///
/// Modules are used for accessing member variables, functions and plugins under a namespace.
pub(crate) fn push_module_internal<K: Into<Cow<'a, str>>>(
&mut self,
name: K,
mut value: Module,
) {
value.index_all_sub_modules();
self.push_dynamic_value(
@ -350,6 +359,11 @@ impl<'a> Scope<'a> {
/// Find a module in the Scope, starting from the last entry.
#[cfg(not(feature = "no_module"))]
pub fn find_module(&mut self, name: &str) -> Option<&mut Module> {
self.find_module_internal(name)
}
/// Find a module in the Scope, starting from the last entry.
pub(crate) fn find_module_internal(&mut self, name: &str) -> Option<&mut Module> {
let index = self.get_module_index(name)?;
self.get_mut(index).0.downcast_mut::<Module>()
}

View File

@ -181,6 +181,7 @@ pub enum Token {
XOr,
Ampersand,
And,
#[cfg(not(feature = "no_function"))]
Fn,
Continue,
Break,
@ -199,6 +200,7 @@ pub enum Token {
PowerOfAssign,
Private,
Import,
#[cfg(not(feature = "no_module"))]
Export,
As,
LexError(Box<LexError>),
@ -260,6 +262,7 @@ impl Token {
Or => "||",
Ampersand => "&",
And => "&&",
#[cfg(not(feature = "no_function"))]
Fn => "fn",
Continue => "continue",
Break => "break",
@ -283,6 +286,7 @@ impl Token {
PowerOfAssign => "~=",
Private => "private",
Import => "import",
#[cfg(not(feature = "no_module"))]
Export => "export",
As => "as",
EOF => "{EOF}",
@ -754,12 +758,9 @@ impl<'a> TokenIterator<'a> {
"for" => Token::For,
"in" => Token::In,
"private" => Token::Private,
#[cfg(not(feature = "no_module"))]
"import" => Token::Import,
#[cfg(not(feature = "no_module"))]
"export" => Token::Export,
#[cfg(not(feature = "no_module"))]
"as" => Token::As,
#[cfg(not(feature = "no_function"))]
@ -916,7 +917,6 @@ impl<'a> TokenIterator<'a> {
}
('=', _) => return Some((Token::Equals, pos)),
#[cfg(not(feature = "no_module"))]
(':', ':') => {
self.eat_next();
return Some((Token::DoubleColon, pos));

View File

@ -11,7 +11,7 @@ use crate::stdlib::{
borrow::Borrow,
boxed::Box,
fmt,
hash::{Hash, Hasher},
hash::{BuildHasher, Hash, Hasher},
iter::FromIterator,
mem,
mem::MaybeUninit,
@ -27,6 +27,48 @@ use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(feature = "no_std")]
use ahash::AHasher;
/// A hasher that only takes one single `u64` and returns it as a hash key.
///
/// # Panics
///
/// Panics when hashing any data type other than a `u64`.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct StraightHasher(u64);
impl Hasher for StraightHasher {
#[inline(always)]
fn finish(&self) -> u64 {
self.0
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
let mut key = [0_u8; 8];
key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes
self.0 = u64::from_le_bytes(key);
}
}
impl StraightHasher {
/// Create a `StraightHasher`.
#[inline(always)]
pub fn new() -> Self {
Self(0)
}
}
/// A hash builder for `StraightHasher`.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct StraightHasherBuilder;
impl BuildHasher for StraightHasherBuilder {
type Hasher = StraightHasher;
#[inline(always)]
fn build_hasher(&self) -> Self::Hasher {
StraightHasher::new()
}
}
/// Calculate a `u64` hash key from a module-qualified function name and parameter types.
///
/// Module names are passed in via `&str` references from an iterator.
@ -108,6 +150,7 @@ pub struct StaticVec<T> {
const MAX_STATIC_VEC: usize = 4;
impl<T> Drop for StaticVec<T> {
#[inline(always)]
fn drop(&mut self) {
self.clear();
}
@ -174,6 +217,7 @@ impl<T> FromIterator<T> for StaticVec<T> {
impl<T> StaticVec<T> {
/// Create a new `StaticVec`.
#[inline(always)]
pub fn new() -> Self {
Default::default()
}
@ -189,6 +233,7 @@ impl<T> StaticVec<T> {
self.len = 0;
}
/// Extract a `MaybeUninit` into a concrete initialized type.
#[inline(always)]
fn extract(value: MaybeUninit<T>) -> T {
unsafe { value.assume_init() }
}
@ -250,6 +295,7 @@ impl<T> StaticVec<T> {
);
}
/// Is data stored in fixed-size storage?
#[inline(always)]
fn is_fixed_storage(&self) -> bool {
self.len <= MAX_STATIC_VEC
}
@ -359,10 +405,12 @@ impl<T> StaticVec<T> {
result
}
/// Get the number of items in this `StaticVec`.
#[inline(always)]
pub fn len(&self) -> usize {
self.len
}
/// Is this `StaticVec` empty?
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.len == 0
}
@ -605,41 +653,48 @@ pub struct ImmutableString(Shared<String>);
impl Deref for ImmutableString {
type Target = String;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<String> for ImmutableString {
#[inline(always)]
fn as_ref(&self) -> &String {
&self.0
}
}
impl Borrow<str> for ImmutableString {
#[inline(always)]
fn borrow(&self) -> &str {
self.0.as_str()
}
}
impl From<&str> for ImmutableString {
#[inline(always)]
fn from(value: &str) -> Self {
Self(value.to_string().into())
}
}
impl From<String> for ImmutableString {
#[inline(always)]
fn from(value: String) -> Self {
Self(value.into())
}
}
impl From<Box<String>> for ImmutableString {
#[inline(always)]
fn from(value: Box<String>) -> Self {
Self(value.into())
}
}
impl From<ImmutableString> for String {
#[inline(always)]
fn from(value: ImmutableString) -> Self {
value.into_owned()
}
@ -648,42 +703,49 @@ impl From<ImmutableString> for String {
impl FromStr for ImmutableString {
type Err = ();
#[inline(always)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_string().into()))
}
}
impl FromIterator<char> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl<'a> FromIterator<&'a char> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self {
Self(iter.into_iter().cloned().collect::<String>().into())
}
}
impl<'a> FromIterator<&'a str> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl<'a> FromIterator<String> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl fmt::Display for ImmutableString {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.0.as_str(), f)
}
}
impl fmt::Debug for ImmutableString {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.0.as_str(), f)
}
@ -818,6 +880,7 @@ impl Add<char> for &ImmutableString {
}
impl AddAssign<char> for ImmutableString {
#[inline(always)]
fn add_assign(&mut self, rhs: char) {
self.make_mut().push(rhs);
}
@ -832,6 +895,7 @@ impl ImmutableString {
}
/// Make sure that the `ImmutableString` is unique (i.e. no other outstanding references).
/// Then return a mutable reference to the `String`.
#[inline(always)]
pub fn make_mut(&mut self) -> &mut String {
shared_make_mut(&mut self.0)
}

View File

@ -21,6 +21,10 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
)?,
'o'
);
assert_eq!(
engine.eval::<String>(r#"let a = [#{s:"hello"}]; a[0].s[2] = 'X'; a[0].s"#)?,
"heXlo"
);
}
assert_eq!(

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 mut scope = Scope::new();
scope.push_module("testing", module);
assert_eq!(