Merge pull request #188 from schungx/master

Anonymous functions, no-std fix, simplify custom syntax, minor speed improvements.
This commit is contained in:
Stephen Chung 2020-07-22 13:40:33 +08:00 committed by GitHub
commit 9231dbf7b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 796 additions and 370 deletions

View File

@ -46,10 +46,12 @@ codegen-units = 1
[dependencies.libm] [dependencies.libm]
version = "0.2.1" version = "0.2.1"
default_features = false
optional = true optional = true
[dependencies.core-error] [dependencies.core-error]
version = "0.0.0" version = "0.0.0"
default_features = false
features = ["alloc"] features = ["alloc"]
optional = true optional = true
@ -66,9 +68,9 @@ features = ["compile-time-rng"]
optional = true optional = true
[dependencies.serde] [dependencies.serde]
package = "serde"
version = "1.0.111" version = "1.0.111"
features = ["derive"] default_features = false
features = ["derive", "alloc"]
optional = true optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]

View File

@ -4,12 +4,22 @@ Rhai Release Notes
Version 0.18.0 Version 0.18.0
============== ==============
This version adds:
* Anonymous functions (in closure syntax). Simplifies creation of ad hoc functions.
New features New features
------------ ------------
* `call` can now be called function-call style for function pointers - this is to handle builds with `no_object`. * `call` can now be called function-call style for function pointers - this is to handle builds with `no_object`.
* Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc. * Disallow many keywords as variables, such as `print`, `eval`, `call`, `this` etc.
* `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`. * `x.call(f, ...)` allows binding `x` to `this` for the function referenced by the function pointer `f`.
* Anonymous functions in the syntax of a closure, e.g. `|x, y, z| x + y - z`.
Breaking changes
----------------
* Function signature for defining custom syntax is simplified.
Version 0.17.0 Version 0.17.0

View File

@ -75,6 +75,7 @@ The Rhai Scripting Language
2. [Overloading](language/overload.md) 2. [Overloading](language/overload.md)
3. [Namespaces](language/fn-namespaces.md) 3. [Namespaces](language/fn-namespaces.md)
4. [Function Pointers](language/fn-ptr.md) 4. [Function Pointers](language/fn-ptr.md)
5. [Anonymous Functions](language/fn-anon.md)
15. [Print and Debug](language/print-debug.md) 15. [Print and Debug](language/print-debug.md)
16. [Modules](language/modules/index.md) 16. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)

View File

@ -25,12 +25,12 @@ It doesn't attempt to be a new language. For example:
* No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure]({{rootUrl}}/engine/call-fn.md). * No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure]({{rootUrl}}/engine/call-fn.md).
* No byte-codes/JIT - Rhai has an AST-walking interpreter which will not win any speed races. The purpose of Rhai is not * 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. to be extremely _fast_, but to make it as easy as possible to integrate with native Rust applications.
Due to this intended usage, Rhai deliberately keeps the language simple and small by omitting advanced language features 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. 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 Avoid the temptation to write full-fledge application logic entirely in Rhai - that use case is best fulfilled by
more complete languages such as JavaScript or Lua. more complete languages such as JavaScript or Lua.
Therefore, in actual practice, it is usually best to expose a Rust API into Rhai for scripts to call. Therefore, in actual practice, it is usually best to expose a Rust API into Rhai for scripts to call.

View File

@ -126,32 +126,21 @@ Any custom syntax must include an _implementation_ of it.
The function signature of an implementation is: The function signature of an implementation is:
```rust ```rust
Fn( Fn(engine: &Engine, context: &mut EvalContext, scope: &mut Scope, inputs: &[Expression])
engine: &Engine, -> Result<Dynamic, Box<EvalAltResult>>
scope: &mut Scope,
mods: &mut Imports,
state: &mut State,
lib: &Module,
this_ptr: &mut Option<&mut Dynamic>,
inputs: &[Expression],
level: usize
) -> Result<Dynamic, Box<EvalAltResult>>
``` ```
where: where:
* `engine: &Engine` - reference to the current [`Engine`]. * `engine: &Engine` - reference to the current [`Engine`].
* `context: &mut EvalContext` - mutable reference to the current evaluation _context_; **do not touch**.
* `scope: &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to it. * `scope: &mut Scope` - mutable reference to the current [`Scope`]; variables can be added to it.
* `mods : &mut Imports` - mutable reference to the current collection of imported [`Module`]'s; **do not touch**.
* `state : &mut State` - mutable reference to the current evaluation state; **do not touch**.
* `lib : &Module` - reference to the current collection of script-defined functions.
* `this_ptr : &mut Option<&mut Dynamic>` - mutable reference to the current binding of the `this` pointer; **do not touch**.
* `inputs: &[Expression]` - a list of input expression trees. * `inputs: &[Expression]` - a list of input expression trees.
* `level : usize` - the current function call level.
There are a lot of parameters, most of which should not be touched or Bad Things Happen™. #### WARNING - Lark's Vomit
They represent the running _content_ of a script evaluation and should simply be passed
straight-through the the [`Engine`]. The `context` parameter contains the evaluation _context_ and should not be touched or Bad Things Happen™.
It should simply be passed straight-through the the [`Engine`].
### Access Arguments ### Access Arguments
@ -172,7 +161,7 @@ Use the `engine::eval_expression_tree` method to evaluate an expression tree.
```rust ```rust
let expr = inputs.get(0).unwrap(); let expr = inputs.get(0).unwrap();
let result = engine.eval_expression_tree(scope, mods, state, lib, this_ptr, expr, level)?; let result = engine.eval_expression_tree(context, scope, expr)?;
``` ```
As can be seem above, most arguments are simply passed straight-through to `engine::eval_expression_tree`. As can be seem above, most arguments are simply passed straight-through to `engine::eval_expression_tree`.
@ -210,13 +199,9 @@ The syntax is passed simply as a slice of `&str`.
// Custom syntax implementation // Custom syntax implementation
fn implementation_func( fn implementation_func(
engine: &Engine, engine: &Engine,
context: &mut EvalContext,
scope: &mut Scope, scope: &mut Scope,
mods: &mut Imports, inputs: &[Expression]
state: &mut State,
lib: &Module,
this_ptr: &mut Option<&mut Dynamic>,
inputs: &[Expression],
level: usize
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let var_name = inputs[0].get_variable_name().unwrap().to_string(); let var_name = inputs[0].get_variable_name().unwrap().to_string();
let stmt = inputs.get(1).unwrap(); let stmt = inputs.get(1).unwrap();
@ -227,15 +212,12 @@ fn implementation_func(
loop { loop {
// Evaluate the statement block // Evaluate the statement block
engine.eval_expression_tree(scope, mods, state, lib, this_ptr, stmt, level)?; engine.eval_expression_tree(context, scope, stmt)?;
// Evaluate the condition expression // Evaluate the condition expression
let stop = !engine let stop = !engine.eval_expression_tree(context, scope, condition)?
.eval_expression_tree(scope, mods, state, lib, this_ptr, condition, level)? .as_bool().map_err(|_| EvalAltResult::ErrorBooleanArgMismatch(
.as_bool() "do-while".into(), expr.position()))?;
.map_err(|_| EvalAltResult::ErrorBooleanArgMismatch(
"do-while".into(), expr.position()
))?;
if stop { if stop {
break; break;

View File

@ -7,7 +7,7 @@ Raw `Engine`
`Engine::new` creates a scripting [`Engine`] with common functionalities (e.g. printing to the console via `print`). `Engine::new` creates a scripting [`Engine`] with common functionalities (e.g. printing to the console via `print`).
In many controlled embedded environments, however, these may not be needed and unnecessarily occupy In many controlled embedded environments, however, these may not be needed and unnecessarily occupy
program code storage space. application code storage space.
Use `Engine::new_raw` to create a _raw_ `Engine`, in which only a minimal set of Use `Engine::new_raw` to create a _raw_ `Engine`, in which only a minimal set of
basic arithmetic and logical operators are supported. basic arithmetic and logical operators are supported.

View File

@ -0,0 +1,57 @@
Anonymous Functions
===================
{{#include ../links.md}}
Sometimes it gets tedious to define separate functions only to dispatch them via single [function pointers].
This scenario is especially common when simulating object-oriented programming ([OOP]).
```rust
// Define object
let obj = #{
data: 42,
increment: Fn("inc_obj"), // use function pointers to
decrement: Fn("dec_obj"), // refer to method functions
print: Fn("print_obj")
};
// Define method functions one-by-one
fn inc_obj(x) { this.data += x; }
fn dec_obj(x) { this.data -= x; }
fn print_obj() { print(this.data); }
```
The above can be replaced by using _anonymous functions_ which have the same syntax as Rust's closures
(but they are **NOT** closures, merely syntactic sugar):
```rust
let obj = #{
data: 42,
increment: |x| this.data += x, // one-liner
decrement: |x| this.data -= x,
print_obj: || { print(this.data); } // full function body
};
```
The anonymous functions will be hoisted into separate functions in the global namespace.
The above is equivalent to:
```rust
let obj = #{
data: 42,
increment: Fn("anon_fn_1000"),
decrement: Fn("anon_fn_1001"),
print: Fn("anon_fn_1002")
};
fn anon_fn_1000(x) { this.data += x; }
fn anon_fn_1001(x) { this.data -= x; }
fn anon_fn_1002() { print this.data; }
```
WARNING - NOT Closures
----------------------
Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves
**not** closures. In particular, they do not capture their running environment. They are more like
Rust's function pointers.

View File

@ -163,4 +163,4 @@ x == 42;
Beware that this only works for _method-call_ style. Normal function-call style cannot bind Beware that this only works for _method-call_ style. Normal function-call style cannot bind
the `this` pointer (for syntactic reasons). the `this` pointer (for syntactic reasons).
Therefore, obviously, binding the `this` pointer is unsupported under [`no_function`]. Therefore, obviously, binding the `this` pointer is unsupported under [`no_object`].

View File

@ -20,17 +20,21 @@ to the [object map] before the function is called. There is no way to simulate
via a normal function-call syntax because all scripted function arguments are passed by value. via a normal function-call syntax because all scripted function arguments are passed by value.
```rust ```rust
fn do_action(x) { print(this.data + x); } // 'this' binds to the object when called fn do_action(x) { this.data += x; } // 'this' binds to the object when called
let obj = #{ let obj = #{
data: 40, data: 40,
action: Fn("do_action") // 'action' holds a function pointer to 'do_action' action: Fn("do_action") // 'action' holds a function pointer to 'do_action'
}; };
obj.action(2); // Short-hand syntax: prints 42 obj.action(2); // Calls 'do_action' with `this` bound to 'obj'
// To achieve the above with normal function pointer calls: obj.call(obj.action, 2); // The above de-sugars to this
fn do_action(map, x) { print(map.data + x); }
obj.action.call(obj, 2); // this call cannot mutate 'obj' obj.data == 42;
// To achieve the above with normal function pointer call will fail.
fn do_action(map, x) { map.data += x; } // 'map' is a copy
obj.action.call(obj, 2); // 'obj' is passed as a copy by value
``` ```

View File

@ -17,24 +17,23 @@ Rhai's [object maps] has [special support for OOP]({{rootUrl}}/language/object-m
| [Object map] properties holding values | properties | | [Object map] properties holding values | properties |
| [Object map] properties that hold [function pointers] | methods | | [Object map] properties that hold [function pointers] | methods |
When a property of an [object map] is called like a method function, and if it happens to hold
a valid [function pointer] (perhaps defined via an [anonymous function]), then the call will be
dispatched to the actual function with `this` binding to the [object map] itself.
Examples Examples
-------- --------
```rust ```rust
// Define the object // Define the object
let obj = #{ let obj =
#{
data: 0, data: 0,
increment: Fn("add"), // when called, 'this' binds to 'obj' increment: |x| this.data += x, // when called, 'this' binds to 'obj'
update: Fn("update"), // when called, 'this' binds to 'obj' update: |x| this.data = x, // when called, 'this' binds to 'obj'
action: Fn("action") // when called, 'this' binds to 'obj' action: || print(this.data) // when called, 'this' binds to 'obj'
}; };
// Define functions
fn add(x) { this.data += x; } // update using 'this'
fn update(x) { this.data = x; } // update using 'this'
fn action() { print(this.data); } // access properties of 'this'
// Use the object // Use the object
obj.increment(1); obj.increment(1);
obj.action(); // prints 1 obj.action(); // prints 1

View File

@ -78,6 +78,8 @@
[function pointers]: {{rootUrl}}/language/fn-ptr.md [function pointers]: {{rootUrl}}/language/fn-ptr.md
[function namespace]: {{rootUrl}}/language/fn-namespaces.md [function namespace]: {{rootUrl}}/language/fn-namespaces.md
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md [function namespaces]: {{rootUrl}}/language/fn-namespaces.md
[anonymous function]: {{rootUrl}}/language/fn-anon.md
[anonymous functions]: {{rootUrl}}/language/fn-anon.md
[`Module`]: {{rootUrl}}/language/modules/index.md [`Module`]: {{rootUrl}}/language/modules/index.md
[module]: {{rootUrl}}/language/modules/index.md [module]: {{rootUrl}}/language/modules/index.md

View File

@ -13,3 +13,9 @@ Nightly Required
---------------- ----------------
Currently, [`no_std`] requires the nightly compiler due to the crates that it uses. Currently, [`no_std`] requires the nightly compiler due to the crates that it uses.
Samples
-------
Check out the [`no-std` sample applications](../examples/rust.md#no-std-samples) for different operating environments.

View File

@ -3,14 +3,13 @@ Rust Examples
{{#include ../../links.md}} {{#include ../../links.md}}
A number of examples can be found in the `examples` folder: A number of examples can be found in the `examples` directory:
| Example | Description | | Example | Description |
| ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. | | [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. |
| [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. | | [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. |
| [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | Simple example that evaluates an expression and prints the result. | | [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | Simple example that evaluates an expression and prints the result. |
| [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | Example to test out `no-std` builds.</br>The [`no_std`] feature is required to build in `no-std`. |
| [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. | | [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. |
| [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. | | [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. |
| [`serde`](https://github.com/jonathandturner/rhai/tree/master/examples/serde.rs) | Example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).<br/>The [`serde`] feature is required to run. | | [`serde`](https://github.com/jonathandturner/rhai/tree/master/examples/serde.rs) | Example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).<br/>The [`serde`] feature is required to run. |
@ -30,3 +29,22 @@ Examples can be run with the following command:
```bash ```bash
cargo run --example {example_name} cargo run --example {example_name}
``` ```
`no-std` Samples
----------------
To illustrate `no-std` builds, a number of sample applications are available under the `no_std` directory:
| Sample | Description | Optimization | Allocator | Panics |
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | :----------: | :-----------------------------------------------: | :----: |
| [`no_std_test`](https://github.com/jonathandturner/rhai/tree/master/no_std/no_std_test) | Bare-bones test application that evaluates a Rhai expression and sets the result as the return value. | Size | [`wee_alloc`](https://crates.io/crates/wee_alloc) | Abort |
`cargo run` cannot be used to run a `no-std` sample. It must first be built:
```bash
cd no_std/no_std_test
cargo +nightly build --release
./target/release/no_std_test
```

View File

@ -6,27 +6,27 @@ Example Scripts
Language Feature Scripts Language Feature 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` directory:
| Script | Description | | Script | Description |
| -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| [`array.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/array.rhai) | [Arrays] | | [`array.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/array.rhai) | [Arrays] |
| [`assignment.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/assignment.rhai) | Variable declarations | | [`assignment.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/assignment.rhai) | Variable declarations |
| [`comments.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/comments.rhai) | Just comments | | [`comments.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/comments.rhai) | Just comments |
| [`for1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for1.rhai) | [`for`](#for-loop) loops | | [`for1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for1.rhai) | [`for`]({{rootUrl}}/language/for.md) loops |
| [`for2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for2.rhai) | [`for`](#for-loop) loops on [arrays] | | [`for2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for2.rhai) | [`for`]({{rootUrl}}/language/for.md) loops on [arrays] |
| [`function_decl1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl1.rhai) | A [function] without parameters | | [`function_decl1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl1.rhai) | A [function] without parameters |
| [`function_decl2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl2.rhai) | A [function] with two parameters | | [`function_decl2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl2.rhai) | A [function] with two parameters |
| [`function_decl3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl3.rhai) | A [function] with many parameters | | [`function_decl3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl3.rhai) | A [function] with many parameters |
| [`if1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/if1.rhai) | [`if`](#if-statement) example | | [`if1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/if1.rhai) | [`if`]({{rootUrl}}/language/if.md) example |
| [`loop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/loop.rhai) | Count-down [`loop`](#infinite-loop) in Rhai, emulating a `do` .. `while` loop | | [`loop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/loop.rhai) | Count-down [`loop`]({{rootUrl}}/language/loop.md) in Rhai, emulating a `do` .. `while` loop |
| [`oop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/oop.rhai) | Simulate [object-oriented programming (OOP)][OOP] | | [`oop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/oop.rhai) | Simulate [object-oriented programming (OOP)][OOP] |
| [`op1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op1.rhai) | Just simple addition | | [`op1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op1.rhai) | Just simple addition |
| [`op2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op2.rhai) | Simple addition and multiplication | | [`op2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op2.rhai) | Simple addition and multiplication |
| [`op3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op3.rhai) | Change evaluation order with parenthesis | | [`op3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op3.rhai) | Change evaluation order with parenthesis |
| [`string.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/string.rhai) | [String] operations | | [`string.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/string.rhai) | [String] operations |
| [`strings_map.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/strings_map.rhai) | [String] and [object map] operations | | [`strings_map.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/strings_map.rhai) | [String] and [object map] operations |
| [`while.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/while.rhai) | [`while`](#while-loop) loop | | [`while.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/while.rhai) | [`while`]({{rootUrl}}/language/while.md) loop |
Benchmark Scripts Benchmark Scripts
@ -35,8 +35,8 @@ Benchmark Scripts
The following scripts are for benchmarking the speed of Rhai: The following scripts are for benchmarking the speed of Rhai:
| Scripts | Description | | Scripts | Description |
| ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
| [`speed_test.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/speed_test.rhai) | A simple program to measure the speed of Rhai's interpreter (1 million iterations). | | [`speed_test.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/speed_test.rhai) | A simple application to measure the speed of Rhai's interpreter (1 million iterations). |
| [`primes.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/primes.rhai) | Use Sieve of Eratosthenes to find all primes smaller than a limit. | | [`primes.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/primes.rhai) | Use Sieve of Eratosthenes to find all primes smaller than a limit. |
| [`fibonacci.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/fibonacci.rhai) | Calculate the n-th Fibonacci number using a really dumb algorithm. | | [`fibonacci.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/fibonacci.rhai) | Calculate the n-th Fibonacci number using a really dumb algorithm. |
| [`mat_mul.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/mat_mul.rhai) | Matrix multiplication test to measure the speed of multi-dimensional array access. | | [`mat_mul.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/mat_mul.rhai) | Matrix multiplication test to measure the speed of multi-dimensional array access. |
@ -45,7 +45,7 @@ The following scripts are for benchmarking the speed of Rhai:
Running Example Scripts Running Example Scripts
---------------------- ----------------------
To run the scripts, either make a tiny program or use of the `rhai_runner` example: The [`rhai_runner`](../examples/rust.md) example can be used to run the scripts:
```bash ```bash
cargo run --example rhai_runner scripts/any_script.rhai cargo run --example rhai_runner scripts/any_script.rhai

View File

@ -1,23 +0,0 @@
#![cfg_attr(feature = "no_std", no_std)]
use rhai::{Engine, EvalAltResult, INT};
#[cfg(feature = "no_std")]
extern crate alloc;
#[cfg(feature = "no_std")]
use alloc::boxed::Box;
fn main() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let result = engine.eval::<INT>("40 + 2")?;
#[cfg(not(feature = "no_std"))]
println!("Answer: {}", result);
#[cfg(feature = "no_std")]
assert_eq!(result, 42);
Ok(())
}

View File

@ -0,0 +1,24 @@
[package]
name = "no_std_test"
version = "0.1.0"
edition = "2018"
authors = ["Stephen Chung"]
description = "no-std test application"
homepage = "https://github.com/jonathandturner/rhai/tree/master/no_std/no_std_test"
repository = "https://github.com/jonathandturner/rhai"
[dependencies]
rhai = { path = "../../", features = [ "no_std", "unchecked", "only_i32", "no_module" ], default_features = false }
wee_alloc = { version = "0.4.5", default_features = false }
[profile.dev]
panic = "abort"
[profile.release]
opt-level = "z" # optimize for size
debug = false
rpath = false
lto = "fat"
debug-assertions = false
codegen-units = 1
panic = "abort"

View File

@ -0,0 +1,18 @@
`no-std` Test Sample
====================
This sample application is a bare-bones `no-std` build for testing.
[`wee_alloc`](https://crates.io/crates/wee_alloc) is used as the allocator.
To Compile
----------
The nightly compiler is required:
```bash
cargo +nightly build --release
```
The release build is optimized for size. It can be changed to optimize on speed instead.

View File

@ -0,0 +1,43 @@
//! This is a bare-bones `no-std` application that evaluates
//! a simple expression and uses the result as the return value.
#![no_std]
#![feature(alloc_error_handler, start, core_intrinsics, lang_items)]
extern crate alloc;
extern crate wee_alloc;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
use rhai::{Engine, INT};
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
// Notice that this is a _raw_ engine.
// To do anything useful, load a few packages from `rhai::packages`.
let engine = Engine::new_raw();
// Evaluate a simple expression: 40 + 2
engine.eval_expression::<INT>("40 + 2").unwrap() as isize
}
#[alloc_error_handler]
fn foo(_: core::alloc::Layout) -> ! {
core::intrinsics::abort();
}
#[panic_handler]
#[lang = "panic_impl"]
extern "C" fn rust_begin_panic(_: &core::panic::PanicInfo) -> ! {
core::intrinsics::abort();
}
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
#[no_mangle]
extern "C" fn rust_eh_register_frames() {}
#[no_mangle]
extern "C" fn rust_eh_unregister_frames() {}

View File

@ -203,6 +203,7 @@ impl Dynamic {
} }
/// Map the name of a standard type into a friendly form. /// Map the name of a standard type into a friendly form.
#[inline]
pub(crate) fn map_std_type_name(name: &str) -> &str { pub(crate) fn map_std_type_name(name: &str) -> &str {
if name == type_name::<String>() { if name == type_name::<String>() {
"string" "string"
@ -243,12 +244,15 @@ impl fmt::Display for Dynamic {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(value) => fmt::Debug::fmt(value, f), Union::Array(value) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => write!(f, "#{:?}", value), Union::Map(value) => {
f.write_str("#")?;
fmt::Debug::fmt(value, f)
}
Union::FnPtr(value) => fmt::Display::fmt(value, f), Union::FnPtr(value) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"), Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
Union::Variant(_) => write!(f, "?"), Union::Variant(value) => write!(f, "{}", (*value).type_name()),
} }
} }
} }
@ -266,12 +270,15 @@ impl fmt::Debug for Dynamic {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(value) => fmt::Debug::fmt(value, f), Union::Array(value) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(value) => write!(f, "#{:?}", value), Union::Map(value) => {
f.write_str("#")?;
fmt::Debug::fmt(value, f)
}
Union::FnPtr(value) => fmt::Display::fmt(value, f), Union::FnPtr(value) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"), Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
Union::Variant(_) => write!(f, "<dynamic>"), Union::Variant(value) => write!(f, "{}", (*value).type_name()),
} }
} }
} }
@ -334,30 +341,47 @@ impl Dynamic {
/// assert_eq!(new_result.type_name(), "string"); /// assert_eq!(new_result.type_name(), "string");
/// assert_eq!(new_result.to_string(), "hello"); /// assert_eq!(new_result.to_string(), "hello");
/// ``` /// ```
#[inline(always)]
pub fn from<T: Variant + Clone>(value: T) -> Self { pub fn from<T: Variant + Clone>(value: T) -> Self {
if let Some(result) = <dyn Any>::downcast_ref::<()>(&value) { let type_id = TypeId::of::<T>();
return result.clone().into();
} else if let Some(result) = <dyn Any>::downcast_ref::<bool>(&value) {
return result.clone().into();
} else if let Some(result) = <dyn Any>::downcast_ref::<INT>(&value) {
return result.clone().into();
} else if let Some(result) = <dyn Any>::downcast_ref::<char>(&value) {
return result.clone().into();
} else if let Some(result) = <dyn Any>::downcast_ref::<ImmutableString>(&value) {
return result.clone().into();
}
if type_id == TypeId::of::<INT>() {
return <dyn Any>::downcast_ref::<INT>(&value)
.unwrap()
.clone()
.into();
}
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
if let Some(result) = <dyn Any>::downcast_ref::<FLOAT>(&value) { if type_id == TypeId::of::<FLOAT>() {
return result.clone().into(); return <dyn Any>::downcast_ref::<FLOAT>(&value)
.unwrap()
.clone()
.into();
}
if type_id == TypeId::of::<bool>() {
return <dyn Any>::downcast_ref::<bool>(&value)
.unwrap()
.clone()
.into();
}
if type_id == TypeId::of::<char>() {
return <dyn Any>::downcast_ref::<char>(&value)
.unwrap()
.clone()
.into();
}
if type_id == TypeId::of::<ImmutableString>() {
return <dyn Any>::downcast_ref::<ImmutableString>(&value)
.unwrap()
.clone()
.into();
}
if type_id == TypeId::of::<()>() {
return ().into();
} }
let mut boxed = Box::new(value); let mut boxed = Box::new(value);
boxed = match unsafe_cast_box::<_, Dynamic>(boxed) {
Ok(d) => return *d,
Err(val) => val,
};
boxed = match unsafe_cast_box::<_, String>(boxed) { boxed = match unsafe_cast_box::<_, String>(boxed) {
Ok(s) => return (*s).into(), Ok(s) => return (*s).into(),
Err(val) => val, Err(val) => val,
@ -378,6 +402,11 @@ impl Dynamic {
} }
} }
boxed = match unsafe_cast_box::<_, Dynamic>(boxed) {
Ok(d) => return *d,
Err(val) => val,
};
Self(Union::Variant(Box::new(boxed))) Self(Union::Variant(Box::new(boxed)))
} }
@ -395,30 +424,80 @@ impl Dynamic {
/// ///
/// assert_eq!(x.try_cast::<u32>().unwrap(), 42); /// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
/// ``` /// ```
#[inline(always)]
pub fn try_cast<T: Variant>(self) -> Option<T> { pub fn try_cast<T: Variant>(self) -> Option<T> {
let type_id = TypeId::of::<T>(); let type_id = TypeId::of::<T>();
if type_id == TypeId::of::<INT>() {
return match self.0 {
Union::Int(value) => unsafe_try_cast(value),
_ => None,
};
}
#[cfg(not(feature = "no_float"))]
if type_id == TypeId::of::<FLOAT>() {
return match self.0 {
Union::Float(value) => unsafe_try_cast(value),
_ => None,
};
}
if type_id == TypeId::of::<bool>() {
return match self.0 {
Union::Bool(value) => unsafe_try_cast(value),
_ => None,
};
}
if type_id == TypeId::of::<ImmutableString>() {
return match self.0 {
Union::Str(value) => unsafe_try_cast(value),
_ => None,
};
}
if type_id == TypeId::of::<String>() {
return match self.0 {
Union::Str(value) => unsafe_try_cast(value.into_owned()),
_ => None,
};
}
if type_id == TypeId::of::<char>() {
return match self.0 {
Union::Char(value) => unsafe_try_cast(value),
_ => None,
};
}
#[cfg(not(feature = "no_index"))]
if type_id == TypeId::of::<Array>() {
return match self.0 {
Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None,
};
}
#[cfg(not(feature = "no_object"))]
if type_id == TypeId::of::<Map>() {
return match self.0 {
Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None,
};
}
if type_id == TypeId::of::<FnPtr>() {
return match self.0 {
Union::FnPtr(value) => unsafe_try_cast(value),
_ => None,
};
}
if type_id == TypeId::of::<()>() {
return match self.0 {
Union::Unit(value) => unsafe_try_cast(value),
_ => None,
};
}
if type_id == TypeId::of::<Dynamic>() { if type_id == TypeId::of::<Dynamic>() {
return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v);
} }
match self.0 { match self.0 {
Union::Unit(value) => unsafe_try_cast(value),
Union::Bool(value) => unsafe_try_cast(value),
Union::Str(value) if type_id == TypeId::of::<ImmutableString>() => {
unsafe_try_cast(value)
}
Union::Str(value) => unsafe_try_cast(value.into_owned()),
Union::Char(value) => unsafe_try_cast(value),
Union::Int(value) => unsafe_try_cast(value),
#[cfg(not(feature = "no_float"))]
Union::Float(value) => unsafe_try_cast(value),
#[cfg(not(feature = "no_index"))]
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),
Union::FnPtr(value) => unsafe_try_cast(value),
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(), Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
_ => None,
} }
} }
@ -438,81 +517,162 @@ impl Dynamic {
/// ///
/// assert_eq!(x.cast::<u32>(), 42); /// assert_eq!(x.cast::<u32>(), 42);
/// ``` /// ```
#[inline(always)]
pub fn cast<T: Variant + Clone>(self) -> T { pub fn cast<T: Variant + Clone>(self) -> T {
let type_id = TypeId::of::<T>(); self.try_cast::<T>().unwrap()
if type_id == TypeId::of::<Dynamic>() {
return *unsafe_cast_box::<_, T>(Box::new(self)).unwrap();
}
match self.0 {
Union::Unit(value) => unsafe_try_cast(value).unwrap(),
Union::Bool(value) => unsafe_try_cast(value).unwrap(),
Union::Str(value) if type_id == TypeId::of::<ImmutableString>() => {
unsafe_try_cast(value).unwrap()
}
Union::Str(value) => unsafe_try_cast(value.into_owned()).unwrap(),
Union::Char(value) => unsafe_try_cast(value).unwrap(),
Union::Int(value) => unsafe_try_cast(value).unwrap(),
#[cfg(not(feature = "no_float"))]
Union::Float(value) => unsafe_try_cast(value).unwrap(),
#[cfg(not(feature = "no_index"))]
Union::Array(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
Union::FnPtr(value) => unsafe_try_cast(value).unwrap(),
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).unwrap(),
}
} }
/// Get a reference of a specific type to the `Dynamic`. /// Get a reference of a specific type to the `Dynamic`.
/// Casting to `Dynamic` just returns a reference to it. /// Casting to `Dynamic` just returns a reference to it.
/// Returns `None` if the cast fails. /// Returns `None` if the cast fails.
#[inline(always)]
pub fn downcast_ref<T: Variant + Clone>(&self) -> Option<&T> { pub fn downcast_ref<T: Variant + Clone>(&self) -> Option<&T> {
if TypeId::of::<T>() == TypeId::of::<Dynamic>() { let type_id = TypeId::of::<T>();
if type_id == TypeId::of::<INT>() {
return match &self.0 {
Union::Int(value) => <dyn Any>::downcast_ref::<T>(value),
_ => None,
};
}
#[cfg(not(feature = "no_float"))]
if type_id == TypeId::of::<FLOAT>() {
return match &self.0 {
Union::Float(value) => <dyn Any>::downcast_ref::<T>(value),
_ => None,
};
}
if type_id == TypeId::of::<bool>() {
return match &self.0 {
Union::Bool(value) => <dyn Any>::downcast_ref::<T>(value),
_ => None,
};
}
if type_id == TypeId::of::<ImmutableString>() {
return match &self.0 {
Union::Str(value) => <dyn Any>::downcast_ref::<T>(value),
_ => None,
};
}
if type_id == TypeId::of::<String>() {
return match &self.0 {
Union::Str(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None,
};
}
if type_id == TypeId::of::<char>() {
return match &self.0 {
Union::Char(value) => <dyn Any>::downcast_ref::<T>(value),
_ => None,
};
}
#[cfg(not(feature = "no_index"))]
if type_id == TypeId::of::<Array>() {
return match &self.0 {
Union::Array(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None,
};
}
#[cfg(not(feature = "no_object"))]
if type_id == TypeId::of::<Map>() {
return match &self.0 {
Union::Map(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None,
};
}
if type_id == TypeId::of::<FnPtr>() {
return match &self.0 {
Union::FnPtr(value) => <dyn Any>::downcast_ref::<T>(value),
_ => None,
};
}
if type_id == TypeId::of::<()>() {
return match &self.0 {
Union::Unit(value) => <dyn Any>::downcast_ref::<T>(value),
_ => None,
};
}
if type_id == TypeId::of::<Dynamic>() {
return <dyn Any>::downcast_ref::<T>(self); return <dyn Any>::downcast_ref::<T>(self);
} }
match &self.0 { match &self.0 {
Union::Unit(value) => <dyn Any>::downcast_ref::<T>(value),
Union::Bool(value) => <dyn Any>::downcast_ref::<T>(value),
Union::Str(value) => <dyn Any>::downcast_ref::<T>(value)
.or_else(|| <dyn Any>::downcast_ref::<T>(value.as_ref())),
Union::Char(value) => <dyn Any>::downcast_ref::<T>(value),
Union::Int(value) => <dyn Any>::downcast_ref::<T>(value),
#[cfg(not(feature = "no_float"))]
Union::Float(value) => <dyn Any>::downcast_ref::<T>(value),
#[cfg(not(feature = "no_index"))]
Union::Array(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
Union::FnPtr(value) => <dyn Any>::downcast_ref::<T>(value),
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>(),
_ => None,
} }
} }
/// Get a mutable reference of a specific type to the `Dynamic`. /// Get a mutable reference of a specific type to the `Dynamic`.
/// Casting to `Dynamic` just returns a mutable reference to it. /// Casting to `Dynamic` just returns a mutable reference to it.
/// Returns `None` if the cast fails. /// Returns `None` if the cast fails.
#[inline(always)]
pub fn downcast_mut<T: Variant + Clone>(&mut self) -> Option<&mut T> { pub fn downcast_mut<T: Variant + Clone>(&mut self) -> Option<&mut T> {
if TypeId::of::<T>() == TypeId::of::<Dynamic>() { let type_id = TypeId::of::<T>();
if type_id == TypeId::of::<INT>() {
return match &mut self.0 {
Union::Int(value) => <dyn Any>::downcast_mut::<T>(value),
_ => None,
};
}
#[cfg(not(feature = "no_float"))]
if type_id == TypeId::of::<FLOAT>() {
return match &mut self.0 {
Union::Float(value) => <dyn Any>::downcast_mut::<T>(value),
_ => None,
};
}
if type_id == TypeId::of::<bool>() {
return match &mut self.0 {
Union::Bool(value) => <dyn Any>::downcast_mut::<T>(value),
_ => None,
};
}
if type_id == TypeId::of::<ImmutableString>() {
return match &mut self.0 {
Union::Str(value) => <dyn Any>::downcast_mut::<T>(value),
_ => None,
};
}
if type_id == TypeId::of::<char>() {
return match &mut self.0 {
Union::Char(value) => <dyn Any>::downcast_mut::<T>(value),
_ => None,
};
}
#[cfg(not(feature = "no_index"))]
if type_id == TypeId::of::<Array>() {
return match &mut self.0 {
Union::Array(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
_ => None,
};
}
#[cfg(not(feature = "no_object"))]
if type_id == TypeId::of::<Map>() {
return match &mut self.0 {
Union::Map(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
_ => None,
};
}
if type_id == TypeId::of::<FnPtr>() {
return match &mut self.0 {
Union::FnPtr(value) => <dyn Any>::downcast_mut::<T>(value),
_ => None,
};
}
if type_id == TypeId::of::<()>() {
return match &mut self.0 {
Union::Unit(value) => <dyn Any>::downcast_mut::<T>(value),
_ => None,
};
}
if type_id == TypeId::of::<Dynamic>() {
return <dyn Any>::downcast_mut::<T>(self); return <dyn Any>::downcast_mut::<T>(self);
} }
match &mut self.0 { match &mut self.0 {
Union::Unit(value) => <dyn Any>::downcast_mut::<T>(value),
Union::Bool(value) => <dyn Any>::downcast_mut::<T>(value),
Union::Str(value) => <dyn Any>::downcast_mut::<T>(value),
Union::Char(value) => <dyn Any>::downcast_mut::<T>(value),
Union::Int(value) => <dyn Any>::downcast_mut::<T>(value),
#[cfg(not(feature = "no_float"))]
Union::Float(value) => <dyn Any>::downcast_mut::<T>(value),
#[cfg(not(feature = "no_index"))]
Union::Array(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
Union::FnPtr(value) => <dyn Any>::downcast_mut::<T>(value),
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(), Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(),
_ => None,
} }
} }

View File

@ -18,7 +18,7 @@ use crate::utils::StaticVec;
use crate::parser::FLOAT; use crate::parser::FLOAT;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
use crate::syntax::CustomSyntax; use crate::syntax::{CustomSyntax, EvalContext};
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
@ -87,6 +87,7 @@ pub const FN_GET: &str = "get$";
pub const FN_SET: &str = "set$"; pub const FN_SET: &str = "set$";
pub const FN_IDX_GET: &str = "index$get$"; pub const FN_IDX_GET: &str = "index$get$";
pub const FN_IDX_SET: &str = "index$set$"; pub const FN_IDX_SET: &str = "index$set$";
pub const FN_ANONYMOUS: &str = "anon$";
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
pub const MARKER_EXPR: &str = "$expr$"; pub const MARKER_EXPR: &str = "$expr$";
@ -187,7 +188,7 @@ impl Target<'_> {
} }
} }
/// Update the value of the `Target`. /// Update the value of the `Target`.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> { pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> {
match self { match self {
Self::Ref(r) => **r = new_val, Self::Ref(r) => **r = new_val,
@ -418,12 +419,9 @@ pub fn make_getter(id: &str) -> String {
fn extract_prop_from_getter(fn_name: &str) -> Option<&str> { fn extract_prop_from_getter(fn_name: &str) -> Option<&str> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if fn_name.starts_with(FN_GET) { if fn_name.starts_with(FN_GET) {
Some(&fn_name[FN_GET.len()..]) return Some(&fn_name[FN_GET.len()..]);
} else {
None
} }
#[cfg(feature = "no_object")]
None None
} }
@ -436,12 +434,9 @@ pub fn make_setter(id: &str) -> String {
fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { fn extract_prop_from_setter(fn_name: &str) -> Option<&str> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if fn_name.starts_with(FN_SET) { if fn_name.starts_with(FN_SET) {
Some(&fn_name[FN_SET.len()..]) return Some(&fn_name[FN_SET.len()..]);
} else {
None
} }
#[cfg(feature = "no_object")]
None None
} }
@ -453,7 +448,7 @@ fn default_print(s: &str) {
} }
/// Search for a module within an imports stack. /// Search for a module within an imports stack.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
fn search_imports<'s>( fn search_imports<'s>(
mods: &'s Imports, mods: &'s Imports,
state: &mut State, state: &mut State,
@ -486,7 +481,7 @@ fn search_imports<'s>(
} }
/// Search for a module within an imports stack. /// Search for a module within an imports stack.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
fn search_imports_mut<'s>( fn search_imports_mut<'s>(
mods: &'s mut Imports, mods: &'s mut Imports,
state: &mut State, state: &mut State,
@ -636,7 +631,7 @@ impl Engine {
} }
/// Universal method for calling functions either registered with the `Engine` or written in Rhai. /// Universal method for calling functions either registered with the `Engine` or written in Rhai.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
/// ///
/// ## WARNING /// ## WARNING
/// ///
@ -654,7 +649,7 @@ impl Engine {
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref: bool, is_ref: bool,
is_method: bool, is_method: bool,
def_val: Option<&Dynamic>, def_val: Option<bool>,
level: usize, level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
self.inc_operations(state)?; self.inc_operations(state)?;
@ -709,7 +704,7 @@ impl Engine {
/// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`. /// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`.
fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) { fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) {
if let Some(this_pointer) = old_this_ptr { if let Some(this_pointer) = old_this_ptr {
mem::replace(args.get_mut(0).unwrap(), this_pointer); args[0] = this_pointer;
} }
} }
@ -815,7 +810,7 @@ impl Engine {
// Return default value (if any) // Return default value (if any)
if let Some(val) = def_val { if let Some(val) = def_val {
return Ok((val.clone(), false)); return Ok((val.into(), false));
} }
// Getter function not found? // Getter function not found?
@ -877,7 +872,7 @@ impl Engine {
} }
/// Call a script-defined function. /// Call a script-defined function.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
/// ///
/// ## WARNING /// ## WARNING
/// ///
@ -959,7 +954,7 @@ impl Engine {
} }
/// Perform an actual function call, taking care of special functions /// Perform an actual function call, taking care of special functions
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
/// ///
/// ## WARNING /// ## WARNING
/// ///
@ -976,7 +971,7 @@ impl Engine {
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref: bool, is_ref: bool,
is_method: bool, is_method: bool,
def_val: Option<&Dynamic>, def_val: Option<bool>,
level: usize, level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
@ -1022,7 +1017,7 @@ impl Engine {
} }
/// Evaluate a text string as a script - used primarily for 'eval'. /// Evaluate a text string as a script - used primarily for 'eval'.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
fn eval_script_expr( fn eval_script_expr(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -1081,7 +1076,6 @@ impl Engine {
let is_ref = target.is_ref(); let is_ref = target.is_ref();
let is_value = target.is_value(); let is_value = target.is_value();
let def_val = def_val.as_ref();
// Get a reference to the mutation target Dynamic // Get a reference to the mutation target Dynamic
let obj = target.as_mut(); let obj = target.as_mut();
@ -1100,7 +1094,7 @@ impl Engine {
// Map it to name(args) in function-call style // Map it to name(args) in function-call style
self.exec_fn_call( self.exec_fn_call(
state, lib, fn_name, *native, hash, args, false, false, def_val, level, state, lib, fn_name, *native, hash, args, false, false, *def_val, level,
) )
} else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::<FnPtr>() { } else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::<FnPtr>() {
// FnPtr call on object // FnPtr call on object
@ -1120,7 +1114,7 @@ impl Engine {
// Map it to name(args) in function-call style // Map it to name(args) in function-call style
self.exec_fn_call( self.exec_fn_call(
state, lib, &fn_name, *native, hash, args, is_ref, true, def_val, level, state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level,
) )
} else { } else {
let redirected: Option<ImmutableString>; let redirected: Option<ImmutableString>;
@ -1146,7 +1140,7 @@ impl Engine {
let args = arg_values.as_mut(); let args = arg_values.as_mut();
self.exec_fn_call( self.exec_fn_call(
state, lib, fn_name, *native, hash, args, is_ref, true, def_val, level, state, lib, fn_name, *native, hash, args, is_ref, true, *def_val, level,
) )
} }
.map_err(|err| err.new_position(*pos))?; .map_err(|err| err.new_position(*pos))?;
@ -1161,7 +1155,7 @@ impl Engine {
} }
/// Chain-evaluate a dot/index chain. /// Chain-evaluate a dot/index chain.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
fn eval_dot_index_chain_helper( fn eval_dot_index_chain_helper(
&self, &self,
state: &mut State, state: &mut State,
@ -1210,14 +1204,14 @@ impl Engine {
} }
// xxx[rhs] = new_val // xxx[rhs] = new_val
_ if new_val.is_some() => { _ if new_val.is_some() => {
let mut new_val = new_val.unwrap();
let mut idx_val2 = idx_val.clone(); let mut idx_val2 = idx_val.clone();
match self.get_indexed_mut(state, lib, target, idx_val, pos, true, level) { match self.get_indexed_mut(state, lib, target, idx_val, pos, true, level) {
// Indexed value is an owned value - the only possibility is an indexer // Indexed value is an owned value - the only possibility is an indexer
// Try to call an index setter // Try to call an index setter
Ok(obj_ptr) if obj_ptr.is_value() => { Ok(obj_ptr) if obj_ptr.is_value() => {
let args = let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val];
&mut [target.as_mut(), &mut idx_val2, &mut new_val.unwrap()];
self.exec_fn_call( self.exec_fn_call(
state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None, state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None,
@ -1236,17 +1230,13 @@ impl Engine {
// Indexed value is a reference - update directly // Indexed value is a reference - update directly
Ok(ref mut obj_ptr) => { Ok(ref mut obj_ptr) => {
obj_ptr obj_ptr
.set_value(new_val.unwrap()) .set_value(new_val)
.map_err(|err| err.new_position(rhs.position()))?; .map_err(|err| err.new_position(rhs.position()))?;
} }
Err(err) => match *err { Err(err) => match *err {
// No index getter - try to call an index setter // No index getter - try to call an index setter
EvalAltResult::ErrorIndexingType(_, _) => { EvalAltResult::ErrorIndexingType(_, _) => {
let args = &mut [ let args = &mut [target.as_mut(), &mut idx_val2, &mut new_val];
target.as_mut(),
&mut idx_val2,
&mut new_val.unwrap(),
];
self.exec_fn_call( self.exec_fn_call(
state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None, state, lib, FN_IDX_SET, true, 0, args, is_ref, true, None,
@ -1694,13 +1684,12 @@ impl Engine {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(mut rhs_value)) => { Dynamic(Union::Array(mut rhs_value)) => {
let op = "=="; let op = "==";
let def_value = false.into();
let mut scope = Scope::new(); let mut scope = Scope::new();
// Call the `==` operator to compare each value // Call the `==` operator to compare each value
for value in rhs_value.iter_mut() { for value in rhs_value.iter_mut() {
let def_value = Some(false);
let args = &mut [&mut lhs_value.clone(), value]; let args = &mut [&mut lhs_value.clone(), value];
let def_value = Some(&def_value);
let hashes = ( let hashes = (
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
@ -1749,15 +1738,19 @@ impl Engine {
#[deprecated(note = "this method is volatile and may change")] #[deprecated(note = "this method is volatile and may change")]
pub fn eval_expression_tree( pub fn eval_expression_tree(
&self, &self,
context: &mut EvalContext,
scope: &mut Scope, scope: &mut Scope,
mods: &mut Imports,
state: &mut State,
lib: &Module,
this_ptr: &mut Option<&mut Dynamic>,
expr: &Expression, expr: &Expression,
level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
self.eval_expr(scope, mods, state, lib, this_ptr, expr.expr(), level) self.eval_expr(
scope,
context.mods,
context.state,
context.lib,
context.this_ptr,
expr.expr(),
context.level,
)
} }
/// Evaluate an expression /// Evaluate an expression
@ -1782,9 +1775,10 @@ impl Engine {
Expr::FloatConstant(x) => Ok(x.0.into()), Expr::FloatConstant(x) => Ok(x.0.into()),
Expr::StringConstant(x) => Ok(x.0.to_string().into()), Expr::StringConstant(x) => Ok(x.0.to_string().into()),
Expr::CharConstant(x) => Ok(x.0.into()), Expr::CharConstant(x) => Ok(x.0.into()),
Expr::FnPointer(x) => Ok(FnPtr::new_unchecked(x.0.clone()).into()),
Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => { Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => {
if let Some(ref val) = this_ptr { if let Some(val) = this_ptr {
Ok((*val).clone()) Ok(val.clone())
} else { } else {
Err(Box::new(EvalAltResult::ErrorUnboundedThis((x.0).1))) Err(Box::new(EvalAltResult::ErrorUnboundedThis((x.0).1)))
} }
@ -1839,15 +1833,16 @@ impl Engine {
// Not built in, map to `var = var op rhs` // Not built in, map to `var = var op rhs`
let op = &op[..op.len() - 1]; // extract operator without = let op = &op[..op.len() - 1]; // extract operator without =
let hash = calc_fn_hash(empty(), op, 2, empty()); let hash = calc_fn_hash(empty(), op, 2, empty());
// Clone the LHS value
let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val]; let args = &mut [&mut lhs_ptr.clone(), &mut rhs_val];
// Run function
// Set variable value let (value, _) = self
*lhs_ptr = self
.exec_fn_call( .exec_fn_call(
state, lib, op, true, hash, args, false, false, None, level, state, lib, op, true, hash, args, false, false, None, level,
) )
.map(|(v, _)| v)
.map_err(|err| err.new_position(*op_pos))?; .map_err(|err| err.new_position(*op_pos))?;
// Set value to LHS
*lhs_ptr = value;
} }
Ok(Default::default()) Ok(Default::default())
} }
@ -1941,7 +1936,6 @@ impl Engine {
// Normal function call // Normal function call
Expr::FnCall(x) if x.1.is_none() => { Expr::FnCall(x) if x.1.is_none() => {
let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref(); let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref();
let def_val = def_val.as_ref();
// Handle Fn() // Handle Fn()
if name == KEYWORD_FN_PTR && args_expr.len() == 1 { if name == KEYWORD_FN_PTR && args_expr.len() == 1 {
@ -2072,7 +2066,7 @@ impl Engine {
let args = args.as_mut(); let args = args.as_mut();
self.exec_fn_call( self.exec_fn_call(
state, lib, name, *native, hash, args, is_ref, false, def_val, level, state, lib, name, *native, hash, args, is_ref, false, *def_val, level,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
.map_err(|err| err.new_position(*pos)) .map_err(|err| err.new_position(*pos))
@ -2166,7 +2160,7 @@ impl Engine {
.map_err(|err| err.new_position(*pos)), .map_err(|err| err.new_position(*pos)),
Err(err) => match *err { Err(err) => match *err {
EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => { EvalAltResult::ErrorFunctionNotFound(_, _) if def_val.is_some() => {
Ok(def_val.clone().unwrap()) Ok(def_val.unwrap().into())
} }
EvalAltResult::ErrorFunctionNotFound(_, _) => { EvalAltResult::ErrorFunctionNotFound(_, _) => {
Err(Box::new(EvalAltResult::ErrorFunctionNotFound( Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
@ -2225,7 +2219,14 @@ impl Engine {
Expr::Custom(x) => { Expr::Custom(x) => {
let func = (x.0).1.as_ref(); let func = (x.0).1.as_ref();
let ep: StaticVec<_> = (x.0).0.iter().map(|e| e.into()).collect(); let ep: StaticVec<_> = (x.0).0.iter().map(|e| e.into()).collect();
func(self, scope, mods, state, lib, this_ptr, ep.as_ref(), level) let mut context = EvalContext {
mods,
state,
lib,
this_ptr,
level,
};
func(self, &mut context, scope, ep.as_ref())
} }
_ => unreachable!(), _ => unreachable!(),
@ -2502,12 +2503,8 @@ impl Engine {
for ((id, id_pos), rename) in list.iter() { for ((id, id_pos), rename) in list.iter() {
// Mark scope variables as public // Mark scope variables as public
if let Some(index) = scope.get_index(id).map(|(i, _)| i) { if let Some(index) = scope.get_index(id).map(|(i, _)| i) {
let alias = rename let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id);
.as_ref() scope.set_entry_alias(index, alias.clone());
.map(|(n, _)| n.clone())
.unwrap_or_else(|| id.clone());
scope.set_entry_alias(index, alias);
} else { } else {
return Err(Box::new(EvalAltResult::ErrorVariableNotFound( return Err(Box::new(EvalAltResult::ErrorVariableNotFound(
id.into(), id.into(),
@ -2633,7 +2630,7 @@ impl Engine {
} }
/// Check if the number of operations stay within limit. /// Check if the number of operations stay within limit.
/// Position in `EvalAltResult` is None and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
fn inc_operations(&self, state: &mut State) -> Result<(), Box<EvalAltResult>> { fn inc_operations(&self, state: &mut State) -> Result<(), Box<EvalAltResult>> {
state.operations += 1; state.operations += 1;

View File

@ -206,6 +206,9 @@ impl fmt::Display for ParseErrorType {
Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s), Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s),
Self::FnMissingBody(s) if s.is_empty() => {
f.write_str("Expecting body statement block for anonymous function")
}
Self::FnMissingBody(s) => { Self::FnMissingBody(s) => {
write!(f, "Expecting body statement block for function '{}'", s) write!(f, "Expecting body statement block for function '{}'", s)
} }

View File

@ -171,6 +171,10 @@ pub use parser::{CustomExpr, Expr, ReturnType, ScriptFnDef, Stmt};
#[deprecated(note = "this type is volatile and may change")] #[deprecated(note = "this type is volatile and may change")]
pub use engine::{Expression, Imports, State as EvalState}; pub use engine::{Expression, Imports, State as EvalState};
#[cfg(feature = "internals")]
#[deprecated(note = "this type is volatile and may change")]
pub use syntax::EvalContext;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated(note = "this type is volatile and may change")] #[deprecated(note = "this type is volatile and may change")]
pub use module::ModuleRef; pub use module::ModuleRef;

View File

@ -30,6 +30,7 @@ use crate::stdlib::{
}; };
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(feature = "sync")]
use crate::stdlib::sync::RwLock; use crate::stdlib::sync::RwLock;
/// Return type of module-level Rust function. /// Return type of module-level Rust function.
@ -1098,7 +1099,7 @@ impl Module {
/// ///
/// A `StaticVec` is used because most module-level access contains only one level, /// A `StaticVec` is used because most module-level access contains only one level,
/// and it is wasteful to always allocate a `Vec` with one element. /// and it is wasteful to always allocate a `Vec` with one element.
#[derive(Clone, Eq, PartialEq, Default)] #[derive(Clone, Eq, PartialEq, Default, Hash)]
pub struct ModuleRef(StaticVec<(String, Position)>, Option<NonZeroUsize>); pub struct ModuleRef(StaticVec<(String, Position)>, Option<NonZeroUsize>);
impl fmt::Debug for ModuleRef { impl fmt::Debug for ModuleRef {

View File

@ -1,9 +1,12 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::engine::{
Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
};
use crate::module::Module; use crate::module::Module;
use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST}; use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST};
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::token::is_valid_identifier;
use crate::utils::StaticVec; use crate::utils::StaticVec;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
@ -397,7 +400,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// All other items can be thrown away. // All other items can be thrown away.
state.set_dirty(); state.set_dirty();
let pos = m.1; let pos = m.1;
m.0.into_iter().find(|((name, _), _)| name.as_str() == prop) m.0.into_iter().find(|((name, _), _)| name.as_str() == prop.as_str())
.map(|(_, expr)| expr.set_position(pos)) .map(|(_, expr)| expr.set_position(pos))
.unwrap_or_else(|| Expr::Unit(pos)) .unwrap_or_else(|| Expr::Unit(pos))
} }
@ -431,7 +434,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
(Expr::StringConstant(s), Expr::IntegerConstant(i)) if i.0 >= 0 && (i.0 as usize) < s.0.chars().count() => { (Expr::StringConstant(s), Expr::IntegerConstant(i)) if i.0 >= 0 && (i.0 as usize) < s.0.chars().count() => {
// String literal indexing - get the character // String literal indexing - get the character
state.set_dirty(); state.set_dirty();
Expr::CharConstant(Box::new((s.0.chars().nth(i.0 as usize).expect("should get char"), s.1))) Expr::CharConstant(Box::new((s.0.chars().nth(i.0 as usize).unwrap(), s.1)))
} }
// lhs[rhs] // lhs[rhs]
(lhs, rhs) => Expr::Index(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))), (lhs, rhs) => Expr::Index(Box::new((optimize_expr(lhs, state), optimize_expr(rhs, state), x.2))),
@ -528,6 +531,19 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
Expr::FnCall(x) Expr::FnCall(x)
} }
// Fn("...")
Expr::FnCall(x)
if x.1.is_none()
&& (x.0).0 == KEYWORD_FN_PTR
&& x.3.len() == 1
&& matches!(x.3[0], Expr::StringConstant(_))
=> {
match &x.3[0] {
Expr::StringConstant(s) if is_valid_identifier(s.0.chars()) => Expr::FnPointer(s.clone()),
_ => Expr::FnCall(x)
}
}
// Eagerly call functions // Eagerly call functions
Expr::FnCall(mut x) Expr::FnCall(mut x)
if x.1.is_none() // Non-qualified if x.1.is_none() // Non-qualified
@ -571,7 +587,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
Some(arg_for_type_of.to_string().into()) Some(arg_for_type_of.to_string().into())
} else { } else {
// Otherwise use the default value, if any // Otherwise use the default value, if any
def_value.clone() def_value.map(|v| v.into())
} }
}) })
.and_then(|result| map_dynamic_to_expr(result, *pos)) .and_then(|result| map_dynamic_to_expr(result, *pos))
@ -598,7 +614,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
state.set_dirty(); state.set_dirty();
// Replace constant with value // Replace constant with value
state.find_constant(&name).expect("should find constant in scope!").clone().set_position(pos) state.find_constant(&name).unwrap().clone().set_position(pos)
} }
// Custom syntax // Custom syntax
@ -639,10 +655,7 @@ fn optimize(
&& expr.as_ref().map(|v| v.is_constant()).unwrap_or(false) && expr.as_ref().map(|v| v.is_constant()).unwrap_or(false)
}) })
.for_each(|ScopeEntry { name, expr, .. }| { .for_each(|ScopeEntry { name, expr, .. }| {
state.push_constant( state.push_constant(name.as_ref(), expr.as_ref().unwrap().as_ref().clone())
name.as_ref(),
(**expr.as_ref().expect("should be Some(expr)")).clone(),
)
}); });
let orig_constants_len = state.constants.len(); let orig_constants_len = state.constants.len();

View File

@ -7,6 +7,10 @@ use crate::token::Position;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT; use crate::parser::FLOAT;
#[cfg(not(feature = "no_float"))]
#[cfg(feature = "no_std")]
use num_traits::*;
use num_traits::{ use num_traits::{
identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl,
CheckedShr, CheckedSub, CheckedShr, CheckedSub,

View File

@ -6,6 +6,10 @@ use crate::token::Position;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT; use crate::parser::FLOAT;
#[cfg(not(feature = "no_float"))]
#[cfg(feature = "no_std")]
use num_traits::*;
use crate::stdlib::{boxed::Box, format, i32, i64}; use crate::stdlib::{boxed::Box, format, i32, i64};
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]

View File

@ -2,7 +2,7 @@
use crate::any::{Dynamic, Union}; use crate::any::{Dynamic, Union};
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{make_getter, make_setter, Engine, KEYWORD_THIS}; use crate::engine::{make_getter, make_setter, Engine, FN_ANONYMOUS, KEYWORD_THIS};
use crate::error::{LexError, ParseError, ParseErrorType}; use crate::error::{LexError, ParseError, ParseErrorType};
use crate::module::{Module, ModuleRef}; use crate::module::{Module, ModuleRef};
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::{optimize_into_ast, OptimizationLevel};
@ -35,6 +35,12 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
#[cfg(not(feature = "no_std"))]
use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(feature = "no_std")]
use ahash::AHasher;
/// The system integer type. /// The system integer type.
/// ///
/// If the `only_i32` feature is enabled, this will be `i32` instead. /// If the `only_i32` feature is enabled, this will be `i32` instead.
@ -598,21 +604,35 @@ impl Hash for CustomExpr {
} }
} }
#[cfg(not(feature = "no_float"))]
#[derive(Debug, PartialEq, PartialOrd, Clone)]
pub struct FloatWrapper(pub FLOAT, pub Position);
#[cfg(not(feature = "no_float"))]
impl Hash for FloatWrapper {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write(&self.0.to_le_bytes());
self.1.hash(state);
}
}
/// An expression. /// An expression.
/// ///
/// Each variant is at most one pointer in size (for speed), /// Each variant is at most one pointer in size (for speed),
/// with everything being allocated together in one single tuple. /// with everything being allocated together in one single tuple.
#[derive(Debug, Clone)] #[derive(Debug, Clone, Hash)]
pub enum Expr { pub enum Expr {
/// Integer constant. /// Integer constant.
IntegerConstant(Box<(INT, Position)>), IntegerConstant(Box<(INT, Position)>),
/// Floating-point constant. /// Floating-point constant.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
FloatConstant(Box<(FLOAT, Position)>), FloatConstant(Box<FloatWrapper>),
/// Character constant. /// Character constant.
CharConstant(Box<(char, Position)>), CharConstant(Box<(char, Position)>),
/// String constant. /// String constant.
StringConstant(Box<(ImmutableString, Position)>), StringConstant(Box<(ImmutableString, Position)>),
/// FnPtr constant.
FnPointer(Box<(ImmutableString, Position)>),
/// Variable access - ((variable name, position), optional modules, hash, optional index) /// Variable access - ((variable name, position), optional modules, hash, optional index)
Variable( Variable(
Box<( Box<(
@ -623,7 +643,7 @@ pub enum Expr {
)>, )>,
), ),
/// Property access. /// Property access.
Property(Box<((String, String, String), Position)>), Property(Box<((ImmutableString, String, String), Position)>),
/// { stmt } /// { stmt }
Stmt(Box<(Stmt, Position)>), Stmt(Box<(Stmt, Position)>),
/// Wrapped expression - should not be optimized away. /// Wrapped expression - should not be optimized away.
@ -637,7 +657,7 @@ pub enum Expr {
Option<Box<ModuleRef>>, Option<Box<ModuleRef>>,
u64, u64,
StaticVec<Expr>, StaticVec<Expr>,
Option<Dynamic>, Option<bool>,
)>, )>,
), ),
/// expr op= expr /// expr op= expr
@ -673,18 +693,6 @@ impl Default for Expr {
} }
} }
impl Hash for Expr {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::FloatConstant(x) => {
state.write(&x.0.to_le_bytes());
x.1.hash(state);
}
_ => self.hash(state),
}
}
}
impl Expr { impl Expr {
/// Get the `Dynamic` value of a constant expression. /// Get the `Dynamic` value of a constant expression.
/// ///
@ -758,6 +766,7 @@ impl Expr {
Self::IntegerConstant(x) => x.1, Self::IntegerConstant(x) => x.1,
Self::CharConstant(x) => x.1, Self::CharConstant(x) => x.1,
Self::StringConstant(x) => x.1, Self::StringConstant(x) => x.1,
Self::FnPointer(x) => x.1,
Self::Array(x) => x.1, Self::Array(x) => x.1,
Self::Map(x) => x.1, Self::Map(x) => x.1,
Self::Property(x) => x.1, Self::Property(x) => x.1,
@ -791,6 +800,7 @@ impl Expr {
Self::IntegerConstant(x) => x.1 = new_pos, Self::IntegerConstant(x) => x.1 = new_pos,
Self::CharConstant(x) => x.1 = new_pos, Self::CharConstant(x) => x.1 = new_pos,
Self::StringConstant(x) => x.1 = new_pos, Self::StringConstant(x) => x.1 = new_pos,
Self::FnPointer(x) => x.1 = new_pos,
Self::Array(x) => x.1 = new_pos, Self::Array(x) => x.1 = new_pos,
Self::Map(x) => x.1 = new_pos, Self::Map(x) => x.1 = new_pos,
Self::Variable(x) => (x.0).1 = new_pos, Self::Variable(x) => (x.0).1 = new_pos,
@ -847,6 +857,7 @@ impl Expr {
Self::IntegerConstant(_) Self::IntegerConstant(_)
| Self::CharConstant(_) | Self::CharConstant(_)
| Self::StringConstant(_) | Self::StringConstant(_)
| Self::FnPointer(_)
| Self::True(_) | Self::True(_)
| Self::False(_) | Self::False(_)
| Self::Unit(_) => true, | Self::Unit(_) => true,
@ -878,6 +889,7 @@ impl Expr {
Self::IntegerConstant(_) Self::IntegerConstant(_)
| Self::CharConstant(_) | Self::CharConstant(_)
| Self::FnPointer(_)
| Self::In(_) | Self::In(_)
| Self::And(_) | Self::And(_)
| Self::Or(_) | Self::Or(_)
@ -925,7 +937,7 @@ impl Expr {
let (name, pos) = x.0; let (name, pos) = x.0;
let getter = make_getter(&name); let getter = make_getter(&name);
let setter = make_setter(&name); let setter = make_setter(&name);
Self::Property(Box::new(((name.clone(), getter, setter), pos))) Self::Property(Box::new(((name.into(), getter, setter), pos)))
} }
_ => self, _ => self,
} }
@ -1476,7 +1488,7 @@ fn parse_primary(
let mut root_expr = match token { let mut root_expr = match token {
Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))), Token::IntegerConstant(x) => Expr::IntegerConstant(Box::new((x, settings.pos))),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, settings.pos))), Token::FloatConstant(x) => Expr::FloatConstant(Box::new(FloatWrapper(x, settings.pos))),
Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))), Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))),
Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))), Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))),
Token::Identifier(s) => { Token::Identifier(s) => {
@ -1616,7 +1628,10 @@ fn parse_unary(
.map(|i| Expr::IntegerConstant(Box::new((i, pos)))) .map(|i| Expr::IntegerConstant(Box::new((i, pos))))
.or_else(|| { .or_else(|| {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
return Some(Expr::FloatConstant(Box::new((-(x.0 as FLOAT), pos)))); return Some(Expr::FloatConstant(Box::new(FloatWrapper(
-(x.0 as FLOAT),
pos,
))));
#[cfg(feature = "no_float")] #[cfg(feature = "no_float")]
return None; return None;
}) })
@ -1625,7 +1640,9 @@ fn parse_unary(
// Negative float // Negative float
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(x) => Ok(Expr::FloatConstant(Box::new((-x.0, x.1)))), Expr::FloatConstant(x) => {
Ok(Expr::FloatConstant(Box::new(FloatWrapper(-x.0, x.1))))
}
// Call negative function // Call negative function
expr => { expr => {
@ -1664,9 +1681,38 @@ fn parse_unary(
None, None,
hash, hash,
args, args,
Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false Some(false), // NOT operator, when operating on invalid operand, defaults to false
)))) ))))
} }
// | ...
#[cfg(not(feature = "no_function"))]
Token::Pipe | Token::Or => {
let mut state = ParseState::new(
state.engine,
state.max_function_expr_depth,
state.max_function_expr_depth,
);
let settings = ParseSettings {
allow_if_expr: true,
allow_stmt_expr: true,
allow_anonymous_fn: true,
is_global: false,
is_function_scope: true,
is_breakable: false,
level: 0,
pos: *token_pos,
};
let (expr, func) = parse_anon_fn(input, &mut state, lib, settings)?;
// Qualifiers (none) + function name + number of arguments.
let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty());
lib.insert(hash, func);
Ok(expr)
}
// <EOF> // <EOF>
Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)), Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)),
// All other tokens // All other tokens
@ -1788,7 +1834,7 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseEr
let getter = make_getter(&name); let getter = make_getter(&name);
let setter = make_setter(&name); let setter = make_setter(&name);
let rhs = Expr::Property(Box::new(((name, getter, setter), pos))); let rhs = Expr::Property(Box::new(((name.into(), getter, setter), pos)));
Expr::Dot(Box::new((lhs, rhs, op_pos))) Expr::Dot(Box::new((lhs, rhs, op_pos)))
} }
@ -2018,7 +2064,7 @@ fn parse_binary_op(
settings.pos = pos; settings.pos = pos;
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let cmp_def = Some(false.into()); let cmp_def = Some(false);
let op = op_token.syntax(); let op = op_token.syntax();
let hash = calc_fn_hash(empty(), &op, 2, empty()); let hash = calc_fn_hash(empty(), &op, 2, empty());
let op = (op, true, pos); let op = (op, true, pos);
@ -2041,7 +2087,7 @@ fn parse_binary_op(
| Token::XOr => Expr::FnCall(Box::new((op, None, hash, args, None))), | Token::XOr => Expr::FnCall(Box::new((op, None, hash, args, None))),
// '!=' defaults to true when passed invalid operands // '!=' defaults to true when passed invalid operands
Token::NotEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, Some(true.into())))), Token::NotEqualsTo => Expr::FnCall(Box::new((op, None, hash, args, Some(true)))),
// Comparison operators default to false when passed invalid operands // Comparison operators default to false when passed invalid operands
Token::EqualsTo Token::EqualsTo
@ -2762,32 +2808,28 @@ fn parse_fn(
let mut params = Vec::new(); let mut params = Vec::new();
if !match_token(input, Token::RightParen)? { if !match_token(input, Token::RightParen)? {
let end_err = format!("to close the parameters list of function '{}'", name);
let sep_err = format!("to separate the parameters of function '{}'", name); let sep_err = format!("to separate the parameters of function '{}'", name);
loop { loop {
match input.peek().unwrap() { match input.next().unwrap() {
(Token::RightParen, _) => (), (Token::RightParen, _) => break,
_ => match input.next().unwrap() {
(Token::Identifier(s), pos) => { (Token::Identifier(s), pos) => {
state.stack.push((s.clone(), ScopeEntryType::Normal)); state.stack.push((s.clone(), ScopeEntryType::Normal));
params.push((s, pos)) params.push((s, pos))
} }
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => { (_, pos) => {
return Err( return Err(PERR::MissingToken(
PERR::MissingToken(Token::RightParen.into(), end_err).into_err(pos) Token::RightParen.into(),
format!("to close the parameters list of function '{}'", name),
) )
.into_err(pos))
} }
},
} }
match input.next().unwrap() { match input.next().unwrap() {
(Token::RightParen, _) => break, (Token::RightParen, _) => break,
(Token::Comma, _) => (), (Token::Comma, _) => (),
(Token::Identifier(_), pos) => {
return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos))
}
(Token::LexError(err), pos) => return Err(err.into_err(pos)), (Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => { (_, pos) => {
return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos)) return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos))
@ -2831,6 +2873,101 @@ fn parse_fn(
}) })
} }
/// Parse an anonymous function definition.
#[cfg(not(feature = "no_function"))]
fn parse_anon_fn(
input: &mut TokenStream,
state: &mut ParseState,
lib: &mut FunctionsLib,
mut settings: ParseSettings,
) -> Result<(Expr, ScriptFnDef), ParseError> {
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let mut params = Vec::new();
if input.next().unwrap().0 != Token::Or {
if !match_token(input, Token::Pipe)? {
loop {
match input.next().unwrap() {
(Token::Pipe, _) => break,
(Token::Identifier(s), pos) => {
state.stack.push((s.clone(), ScopeEntryType::Normal));
params.push((s, pos))
}
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => {
return Err(PERR::MissingToken(
Token::Pipe.into(),
"to close the parameters list of anonymous function".into(),
)
.into_err(pos))
}
}
match input.next().unwrap() {
(Token::Pipe, _) => break,
(Token::Comma, _) => (),
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
(_, pos) => {
return Err(PERR::MissingToken(
Token::Comma.into(),
"to separate the parameters of anonymous function".into(),
)
.into_err(pos))
}
}
}
}
}
// Check for duplicating parameters
params
.iter()
.enumerate()
.try_for_each(|(i, (p1, _))| {
params
.iter()
.skip(i + 1)
.find(|(p2, _)| p2 == p1)
.map_or_else(|| Ok(()), |(p2, pos)| Err((p2, *pos)))
})
.map_err(|(p, pos)| PERR::FnDuplicatedParam("".to_string(), p.to_string()).into_err(pos))?;
// Parse function body
settings.is_breakable = false;
let pos = input.peek().unwrap().1;
let body = parse_stmt(input, state, lib, settings.level_up())
.map(|stmt| stmt.unwrap_or_else(|| Stmt::Noop(pos)))?;
let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect();
// Calculate hash
#[cfg(feature = "no_std")]
let mut s: AHasher = Default::default();
#[cfg(not(feature = "no_std"))]
let mut s = DefaultHasher::new();
s.write_usize(params.len());
params.iter().for_each(|a| a.hash(&mut s));
body.hash(&mut s);
let hash = s.finish();
// Create unique function name
let fn_name = format!("{}{}", FN_ANONYMOUS, hash);
let script = ScriptFnDef {
name: fn_name.clone(),
access: FnAccess::Public,
params,
body,
pos: settings.pos,
};
let expr = Expr::FnPointer(Box::new((fn_name.into(), settings.pos)));
Ok((expr, script))
}
impl Engine { impl Engine {
pub(crate) fn parse_global_expr( pub(crate) fn parse_global_expr(
&self, &self,
@ -2951,7 +3088,7 @@ impl Engine {
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> { pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
match value.0 { match value.0 {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value) => Some(Expr::FloatConstant(Box::new((value, pos)))), Union::Float(value) => Some(Expr::FloatConstant(Box::new(FloatWrapper(value, pos)))),
Union::Unit(_) => Some(Expr::Unit(pos)), Union::Unit(_) => Some(Expr::Unit(pos)),
Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))), Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))),

View File

@ -22,26 +22,13 @@ use crate::stdlib::{
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type FnCustomSyntaxEval = dyn Fn( pub type FnCustomSyntaxEval = dyn Fn(
&Engine, &Engine,
&mut EvalContext,
&mut Scope, &mut Scope,
&mut Imports,
&mut State,
&Module,
&mut Option<&mut Dynamic>,
&[Expression], &[Expression],
usize,
) -> Result<Dynamic, Box<EvalAltResult>>; ) -> Result<Dynamic, Box<EvalAltResult>>;
/// A general function trail object. /// A general function trail object.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type FnCustomSyntaxEval = dyn Fn( pub type FnCustomSyntaxEval = dyn Fn(&Engine, &mut EvalContext, &mut Scope, &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
&Engine,
&mut Scope,
&mut Imports,
&mut State,
&Module,
&mut Option<&mut Dynamic>,
&[Expression],
usize,
) -> Result<Dynamic, Box<EvalAltResult>>
+ Send + Send
+ Sync; + Sync;
@ -58,6 +45,14 @@ impl fmt::Debug for CustomSyntax {
} }
} }
pub struct EvalContext<'a, 'b: 'a, 's, 'm, 't, 'd: 't> {
pub(crate) mods: &'a mut Imports<'b>,
pub(crate) state: &'s mut State,
pub(crate) lib: &'m Module,
pub(crate) this_ptr: &'t mut Option<&'d mut Dynamic>,
pub(crate) level: usize,
}
impl Engine { impl Engine {
pub fn register_custom_syntax<S: AsRef<str> + ToString>( pub fn register_custom_syntax<S: AsRef<str> + ToString>(
&mut self, &mut self,
@ -65,13 +60,9 @@ impl Engine {
scope_delta: isize, scope_delta: isize,
func: impl Fn( func: impl Fn(
&Engine, &Engine,
&mut EvalContext,
&mut Scope, &mut Scope,
&mut Imports,
&mut State,
&Module,
&mut Option<&mut Dynamic>,
&[Expression], &[Expression],
usize,
) -> Result<Dynamic, Box<EvalAltResult>> ) -> Result<Dynamic, Box<EvalAltResult>>
+ SendSync + SendSync
+ 'static, + 'static,

View File

@ -12,6 +12,7 @@ use crate::stdlib::{
}; };
/// Cast a type into another type. /// Cast a type into another type.
#[inline(always)]
pub fn unsafe_try_cast<A: Any, B: Any>(a: A) -> Option<B> { pub fn unsafe_try_cast<A: Any, B: Any>(a: A) -> Option<B> {
if TypeId::of::<B>() == a.type_id() { if TypeId::of::<B>() == a.type_id() {
// SAFETY: Just checked we have the right type. We explicitly forget the // SAFETY: Just checked we have the right type. We explicitly forget the
@ -28,6 +29,7 @@ pub fn unsafe_try_cast<A: Any, B: Any>(a: A) -> Option<B> {
} }
/// Cast a Boxed type into another type. /// Cast a Boxed type into another type.
#[inline(always)]
pub fn unsafe_cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, Box<X>> { pub fn unsafe_cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, Box<X>> {
// Only allow casting to the exact same type // Only allow casting to the exact same type
if TypeId::of::<X>() == TypeId::of::<T>() { if TypeId::of::<X>() == TypeId::of::<T>() {
@ -51,6 +53,7 @@ pub fn unsafe_cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, B
/// ///
/// Force-casting a local variable's lifetime to the current `Scope`'s larger lifetime saves /// Force-casting a local variable's lifetime to the current `Scope`'s larger lifetime saves
/// on allocations and string cloning, thus avoids us having to maintain a chain of `Scope`'s. /// on allocations and string cloning, thus avoids us having to maintain a chain of `Scope`'s.
#[inline]
pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str, state: &State) -> Cow<'s, str> { pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str, state: &State) -> Cow<'s, str> {
// If not at global level, we can force-cast // If not at global level, we can force-cast
if state.scope_level > 0 { if state.scope_level > 0 {

View File

@ -36,11 +36,9 @@ use ahash::AHasher;
pub struct StraightHasher(u64); pub struct StraightHasher(u64);
impl Hasher for StraightHasher { impl Hasher for StraightHasher {
#[inline(always)]
fn finish(&self) -> u64 { fn finish(&self) -> u64 {
self.0 self.0
} }
#[inline]
fn write(&mut self, bytes: &[u8]) { fn write(&mut self, bytes: &[u8]) {
let mut key = [0_u8; 8]; let mut key = [0_u8; 8];
key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes
@ -50,7 +48,6 @@ impl Hasher for StraightHasher {
impl StraightHasher { impl StraightHasher {
/// Create a `StraightHasher`. /// Create a `StraightHasher`.
#[inline(always)]
pub fn new() -> Self { pub fn new() -> Self {
Self(0) Self(0)
} }
@ -63,7 +60,6 @@ pub struct StraightHasherBuilder;
impl BuildHasher for StraightHasherBuilder { impl BuildHasher for StraightHasherBuilder {
type Hasher = StraightHasher; type Hasher = StraightHasher;
#[inline(always)]
fn build_hasher(&self) -> Self::Hasher { fn build_hasher(&self) -> Self::Hasher {
StraightHasher::new() StraightHasher::new()
} }
@ -150,7 +146,6 @@ 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();
} }
@ -233,7 +228,6 @@ impl<T: 'static> IntoIterator 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()
} }
@ -249,7 +243,6 @@ 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() }
} }
@ -311,7 +304,6 @@ 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
} }
@ -418,12 +410,10 @@ impl<T> StaticVec<T> {
} }
} }
/// 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
} }
@ -664,48 +654,41 @@ 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()
} }
@ -714,49 +697,42 @@ 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)
} }
@ -891,7 +867,6 @@ 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);
} }
@ -906,7 +881,6 @@ 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

@ -1,7 +1,6 @@
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
use rhai::{ use rhai::{
Dynamic, Engine, EvalAltResult, FnPtr, Func, ImmutableString, Module, ParseError, Dynamic, Engine, EvalAltResult, FnPtr, Func, Module, ParseError, ParseErrorType, Scope, INT,
ParseErrorType, Scope, INT,
}; };
#[test] #[test]

View File

@ -1,8 +1,5 @@
#![cfg(feature = "internals")] #![cfg(feature = "internals")]
use rhai::{ use rhai::{Engine, EvalAltResult, EvalContext, Expression, LexError, ParseErrorType, Scope, INT};
Dynamic, Engine, EvalAltResult, EvalState, Expression, Imports, LexError, Module, ParseError,
ParseErrorType, Scope, INT,
};
#[test] #[test]
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> { fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
@ -28,13 +25,9 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
], ],
1, 1,
|engine: &Engine, |engine: &Engine,
context: &mut EvalContext,
scope: &mut Scope, scope: &mut Scope,
mods: &mut Imports, inputs: &[Expression]| {
state: &mut EvalState,
lib: &Module,
this_ptr: &mut Option<&mut Dynamic>,
inputs: &[Expression],
level: usize| {
let var_name = inputs[0].get_variable_name().unwrap().to_string(); let var_name = inputs[0].get_variable_name().unwrap().to_string();
let stmt = inputs.get(1).unwrap(); let stmt = inputs.get(1).unwrap();
let expr = inputs.get(2).unwrap(); let expr = inputs.get(2).unwrap();
@ -42,10 +35,10 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
scope.push(var_name, 0 as INT); scope.push(var_name, 0 as INT);
loop { loop {
engine.eval_expression_tree(scope, mods, state, lib, this_ptr, stmt, level)?; engine.eval_expression_tree(context, scope, stmt)?;
if !engine if !engine
.eval_expression_tree(scope, mods, state, lib, this_ptr, expr, level)? .eval_expression_tree(context, scope, expr)?
.as_bool() .as_bool()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch( EvalAltResult::ErrorBooleanArgMismatch(
@ -78,7 +71,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
// The first symbol must be an identifier // The first symbol must be an identifier
assert!(matches!( assert!(matches!(
*engine.register_custom_syntax(&["!"], 0, |_, _, _, _, _, _, _, _| Ok(().into())).expect_err("should error"), *engine.register_custom_syntax(&["!"], 0, |_, _, _, _| Ok(().into())).expect_err("should error"),
LexError::ImproperSymbol(s) if s == "!" LexError::ImproperSymbol(s) if s == "!"
)); ));