Merge pull request #109 from schungx/master

no-std support
This commit is contained in:
Stephen Chung 2020-03-19 17:08:31 +08:00 committed by GitHub
commit 0bae164764
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 2041 additions and 1138 deletions

View File

@ -7,7 +7,7 @@ cargo test --verbose
if [ "$TRAVIS_RUST_VERSION" = "nightly" ] if [ "$TRAVIS_RUST_VERSION" = "nightly" ]
then then
cargo build --verbose --features no_stdlib cargo build --verbose --features no_std
cargo test --verbose --features no_stdlib cargo test --verbose --features no_std
fi fi

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rhai" name = "rhai"
version = "0.10.2" version = "0.11.0"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"
@ -13,10 +13,33 @@ include = [
"scripts/*.rhai", "scripts/*.rhai",
"Cargo.toml" "Cargo.toml"
] ]
keywords = [ "scripting" ]
[dependencies] [dependencies]
num-traits = "0.2.11" num-traits = "0.2.11"
[features]
#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"]
default = []
unchecked = [] # unchecked arithmetic
no_stdlib = [] # no standard library of utility functions
no_index = [] # no arrays and indexing
no_float = [] # no floating-point
no_function = [] # no script-defined functions
no_optimize = [] # no script optimizer
optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing
only_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
# compiling for no-std
no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm" ]
[profile.release]
lto = "fat"
codegen-units = 1
#opt-level = "z" # optimize for size
#panic = 'abort' # remove stack backtrace for no-std
[dependencies.libm] [dependencies.libm]
version = "0.2.1" version = "0.2.1"
optional = true optional = true
@ -36,22 +59,3 @@ optional = true
version = "0.3.2" version = "0.3.2"
default-features = false default-features = false
optional = true optional = true
[features]
#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"]
default = [ "optimize_full" ]
debug_msgs = [] # print debug messages on function registrations and calls
unchecked = [] # unchecked arithmetic
no_stdlib = ["num-traits/libm", "hashbrown", "core-error", "libm"] # no standard library of utility functions
no_index = [] # no arrays and indexing
no_float = [] # no floating-point
no_function = [] # no script-defined functions
no_optimize = [] # no script optimizer
optimize_full = [] # set optimization level to Full (default is Simple)
only_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
[profile.release]
lto = "fat"
codegen-units = 1
#opt-level = "z" # optimize for size

403
README.md
View File

@ -3,8 +3,9 @@ Rhai - Embedded Scripting for Rust
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application. Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application.
Rhai's current feature set: Rhai's current features set:
* `no-std` support
* Easy integration with Rust functions and data types, supporting getter/setter methods * Easy integration with Rust functions and data types, supporting getter/setter methods
* Easily call a script-defined function from Rust * Easily call a script-defined function from Rust
* Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop) * Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop)
@ -12,10 +13,11 @@ Rhai's current feature set:
* Easy-to-use language similar to JS+Rust * Easy-to-use language similar to JS+Rust
* Support for overloaded functions * Support for overloaded functions
* Compiled script is optimized for repeat evaluations * Compiled script is optimized for repeat evaluations
* Very few additional dependencies (right now only [`num-traits`] to do checked arithmetic operations); * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
For [`no_std`] builds, a number of additional dependencies are pulled in to provide for basic library functionalities. to do checked arithmetic operations); for [`no_std`] builds, a number of additional dependencies are
pulled in to provide for functionalities that used to be in `std`.
**Note:** Currently, the version is 0.10.2, so the language and API's may change before they stabilize. **Note:** Currently, the version is 0.11.0, so the language and API's may change before they stabilize.
Installation Installation
------------ ------------
@ -24,7 +26,7 @@ Install the Rhai crate by adding this line to `dependencies`:
```toml ```toml
[dependencies] [dependencies]
rhai = "0.10.2" rhai = "0.11.0"
``` ```
or simply: or simply:
@ -43,19 +45,29 @@ Optional features
| Feature | Description | | Feature | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `debug_msgs` | Print debug messages to stdout related to function registrations and calls. |
| `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. | | `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. |
| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
| `no_function` | Disable script-defined functions if not needed. | | `no_function` | Disable script-defined functions if not needed. |
| `no_index` | Disable arrays and indexing features if not needed. | | `no_index` | Disable arrays and indexing features if not needed. |
| `no_float` | Disable floating-point numbers and math if not needed. | | `no_float` | Disable floating-point numbers and math if not needed. |
| `no_optimize` | Disable the script optimizer. | | `no_optimize` | Disable the script optimizer. |
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. | | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. | | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here to opt-**out** of certain functionalities that are not needed. By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here to opt-**out** of certain functionalities that are not needed.
Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language.
[`unchecked`]: #optional-features
[`no_stdlib`]: #optional-features
[`no_index`]: #optional-features
[`no_float`]: #optional-features
[`no_function`]: #optional-features
[`no_optimize`]: #optional-features
[`only_i32`]: #optional-features
[`only_i64`]: #optional-features
[`no_std`]: #optional-features
Related Related
------- -------
@ -70,14 +82,15 @@ Examples
A number of examples can be found in the `examples` folder: A number of examples can be found in the `examples` folder:
| Example | Description | | Example | Description |
| -------------------------- | --------------------------------------------------------------------------- | | ------------------------------------------------------------------ | --------------------------------------------------------------------------- |
| `arrays_and_structs` | demonstrates registering a new type to Rhai and the usage of arrays on it | | [`arrays_and_structs`](examples/arrays_and_structs.rs) | demonstrates registering a new type to Rhai and the usage of arrays on it |
| `custom_types_and_methods` | shows how to register a type and methods for it | | [`custom_types_and_methods`](examples/custom_types_and_methods.rs) | shows how to register a type and methods for it |
| `hello` | simple example that evaluates an expression and prints the result | | [`hello`](examples/hello.rs) | simple example that evaluates an expression and prints the result |
| `reuse_scope` | evaluates two pieces of code in separate runs, but using a common [`Scope`] | | [`no_std`](examples/no_std.rs) | example to test out `no-std` builds |
| `rhai_runner` | runs each filename passed to it as a Rhai script | | [`reuse_scope`](examples/reuse_scope.rs) | evaluates two pieces of code in separate runs, but using a common [`Scope`] |
| `simple_fn` | shows how to register a Rust function to a Rhai [`Engine`] | | [`rhai_runner`](examples/rhai_runner.rs) | runs each filename passed to it as a Rhai script |
| `repl` | a simple REPL, interactively evaluate statements from stdin | | [`simple_fn`](examples/simple_fn.rs) | shows how to register a Rust function to a Rhai [`Engine`] |
| [`repl`](examples/repl.rs) | a simple REPL, interactively evaluate statements from stdin |
Examples can be run with the following command: Examples can be run with the following command:
@ -94,26 +107,26 @@ Example Scripts
There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` folder: There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` folder:
| Language feature scripts | Description | | Language feature scripts | Description |
| ------------------------ | ------------------------------------------------------------- | | ---------------------------------------------------- | ------------------------------------------------------------- |
| `array.rhai` | arrays in Rhai | | [`array.rhai`](scripts/array.rhai) | arrays in Rhai |
| `assignment.rhai` | variable declarations | | [`assignment.rhai`](scripts/assignment.rhai) | variable declarations |
| `comments.rhai` | just comments | | [`comments.rhai`](scripts/comments.rhai) | just comments |
| `for1.rhai` | for loops | | [`for1.rhai`](scripts/for1.rhai) | for loops |
| `function_decl1.rhai` | a function without parameters | | [`function_decl1.rhai`](scripts/function_decl1.rhai) | a function without parameters |
| `function_decl2.rhai` | a function with two parameters | | [`function_decl2.rhai`](scripts/function_decl2.rhai) | a function with two parameters |
| `function_decl3.rhai` | a function with many parameters | | [`function_decl3.rhai`](scripts/function_decl3.rhai) | a function with many parameters |
| `if1.rhai` | if example | | [`if1.rhai`](scripts/if1.rhai) | if example |
| `loop.rhai` | endless loop in Rhai, this example emulates a do..while cycle | | [`loop.rhai`](scripts/loop.rhai) | endless loop in Rhai, this example emulates a do..while cycle |
| `op1.rhai` | just a simple addition | | [`op1.rhai`](scripts/op1.rhai) | just a simple addition |
| `op2.rhai` | simple addition and multiplication | | [`op2.rhai`](scripts/op2.rhai) | simple addition and multiplication |
| `op3.rhai` | change evaluation order with parenthesis | | [`op3.rhai`](scripts/op3.rhai) | change evaluation order with parenthesis |
| `string.rhai` | string operations | | [`string.rhai`](scripts/string.rhai) | string operations |
| `while.rhai` | while loop | | [`while.rhai`](scripts/while.rhai) | while loop |
| Example scripts | Description | | Example scripts | Description |
| ----------------- | ---------------------------------------------------------------------------------- | | -------------------------------------------- | ---------------------------------------------------------------------------------- |
| `speed_test.rhai` | a simple program to measure the speed of Rhai's interpreter (1 million iterations) | | [`speed_test.rhai`](scripts/speed_test.rhai) | a simple program to measure the speed of Rhai's interpreter (1 million iterations) |
| `primes.rhai` | use Sieve of Eratosthenes to find all primes smaller than a limit | | [`primes.rhai`](scripts/primes.rhai) | use Sieve of Eratosthenes to find all primes smaller than a limit |
To run the scripts, either make a tiny program or use of the `rhai_runner` example: To run the scripts, either make a tiny program or use of the `rhai_runner` example:
@ -124,6 +137,8 @@ cargo run --example rhai_runner scripts/any_script.rhai
Hello world Hello world
----------- -----------
[`Engine`]: #hello-world
To get going with Rhai, create an instance of the scripting engine and then call `eval`: To get going with Rhai, create an instance of the scripting engine and then call `eval`:
```rust ```rust
@ -171,7 +186,7 @@ let ast = engine.compile("40 + 2")?;
for _ in 0..42 { for _ in 0..42 {
let result: i64 = engine.eval_ast(&ast)?; let result: i64 = engine.eval_ast(&ast)?;
println!("Answer: {}", result); // prints 42 println!("Answer #{}: {}", i, result); // prints 42
} }
``` ```
@ -193,14 +208,17 @@ use rhai::Engine;
let mut engine = Engine::new(); let mut engine = Engine::new();
// Define a function in a script and load it into the Engine. // Define a function in a script and load it into the Engine.
engine.consume(true, // pass true to 'retain_functions' otherwise these functions // Pass true to 'retain_functions' otherwise these functions will be cleared at the end of consume()
r" // will be cleared at the end of consume() engine.consume(true,
fn hello(x, y) { // a function with two parameters: String and i64 r"
x.len() + y // returning i64 // a function with two parameters: String and i64
fn hello(x, y) {
x.len() + y
} }
fn hello(x) { // functions can be overloaded: this one takes only one parameter // functions can be overloaded: this one takes only one parameter
x * 2 // returning i64 fn hello(x) {
x * 2
} }
")?; ")?;
@ -219,35 +237,62 @@ let result: i64 = engine.call_fn("hello", 123_i64)?
Values and types Values and types
---------------- ----------------
[`type_of`]: #values-and-types
The following primitive types are supported natively: The following primitive types are supported natively:
| Category | Types | | Category | Equivalent Rust types | `type_of()` name |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------- |
| **Integer** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ | | **Integer number** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ | _same as type_ |
| **Floating-point** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | | **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | _same as type_ |
| **Character** | `char` | | **Boolean value** | `bool` | `"bool"` |
| **Boolean** | `bool` | | **Unicode character** | `char` | `"char"` |
| **Array** (disabled with [`no_index`]) | `rhai::Array` | | **Unicode string** | `String` (_not_ `&str`) | `"string"` |
| **Dynamic** (i.e. can be anything) | `rhai::Dynamic` | | **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` |
| **System** (current configuration) | `rhai::INT` (`i32` or `i64`),<br/>`rhai::FLOAT` (`f32` or `f64`) | | **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ |
| **System number** (current configuration) | `rhai::INT` (`i32` or `i64`),<br/>`rhai::FLOAT` (`f32` or `f64`) | _same as type_ |
| **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` |
[`Dynamic`]: #values-and-types
[`()`]: #values-and-types
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust. All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust.
The default integer type is `i64`. If other integer types are not needed, it is possible to exclude them and make a smaller build with the [`only_i64`] feature. The default integer type is `i64`. If other integer types are not needed, it is possible to exclude them and make a smaller build with the [`only_i64`] feature.
If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`. If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`.
This is useful on 32-bit systems where using 64-bit integers incur a performance penalty. This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty.
If no floating-point is needed, use the [`no_float`] feature to remove support. If no floating-point is needed or supported, use the [`no_float`] feature to remove it.
There is a `type_of` function to detect the actual type of a value. This is useful because all variables are `Dynamic`.
```rust
// Use 'type_of()' to get the actual types of values
type_of('c') == "char";
type_of(42) == "i64";
let x = 123;
x.type_of() == "i64";
x = 99.999;
x.type_of() == "f64";
x = "hello";
if type_of(x) == "string" {
do_something_with_string(x);
}
```
Value conversions Value conversions
----------------- -----------------
[`to_int`]: #value-conversions
[`to_float`]: #value-conversions
There is a `to_float` function to convert a supported number to an `f64`, and a `to_int` function to convert a supported number to `i64` and that's about it. There is a `to_float` function to convert a supported number to an `f64`, and a `to_int` function to convert a supported number to `i64` and that's about it.
For other conversions, register custom conversion functions. For other conversions, register custom conversion functions.
There is also a `type_of` function to detect the type of a value.
```rust ```rust
let x = 42; let x = 42;
let y = x * 100.0; // error: cannot multiply i64 with f64 let y = x * 100.0; // error: cannot multiply i64 with f64
@ -256,22 +301,13 @@ let z = y.to_int() + x; // works
let c = 'X'; // character let c = 'X'; // character
print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88" print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88"
// Use 'type_of' to get the type of variables
type_of(c) == "char";
type_of(x) == "i64";
y.type_of() == "f64";
if z.type_of() == "string" {
do_something_with_strong(z);
}
``` ```
Working with functions Working with functions
---------------------- ----------------------
Rhai's scripting engine is very lightweight. It gets most of its abilities from functions. Rhai's scripting engine is very lightweight. It gets most of its abilities from functions.
To call these functions, they need to be registered with the engine. To call these functions, they need to be registered with the [`Engine`].
```rust ```rust
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult};
@ -309,7 +345,7 @@ fn main() -> Result<(), EvalAltResult>
} }
``` ```
To return a [`Dynamic`] value, simply `Box` it and return it. To return a [`Dynamic`] value from a Rust function, simply `Box` it and return it.
```rust ```rust
fn decide(yes_no: bool) -> Dynamic { fn decide(yes_no: bool) -> Dynamic {
@ -407,16 +443,16 @@ use rhai::RegisterFn;
#[derive(Clone)] #[derive(Clone)]
struct TestStruct { struct TestStruct {
x: i64 field: i64
} }
impl TestStruct { impl TestStruct {
fn update(&mut self) { fn update(&mut self) {
self.x += 1000; self.field += 41;
} }
fn new() -> TestStruct { fn new() -> Self {
TestStruct { x: 1 } TestStruct { field: 1 }
} }
} }
@ -431,7 +467,7 @@ fn main() -> Result<(), EvalAltResult>
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?; let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
println!("result: {}", result.x); // prints 1001 println!("result: {}", result.field); // prints 42
Ok(()) Ok(())
} }
@ -442,7 +478,7 @@ All custom types must implement `Clone`. This allows the [`Engine`] to pass by
```rust ```rust
#[derive(Clone)] #[derive(Clone)]
struct TestStruct { struct TestStruct {
x: i64 field: i64
} }
``` ```
@ -451,11 +487,11 @@ Next, we create a few methods that we'll later use in our scripts. Notice that
```rust ```rust
impl TestStruct { impl TestStruct {
fn update(&mut self) { fn update(&mut self) {
self.x += 1000; self.field += 41;
} }
fn new() -> TestStruct { fn new() -> Self {
TestStruct { x: 1 } TestStruct { field: 1 }
} }
} }
@ -469,8 +505,8 @@ To use methods and functions with the [`Engine`], we need to register them. The
*Note: [`Engine`] follows the convention that methods use a `&mut` first parameter so that invoking methods can update the value in memory.* *Note: [`Engine`] follows the convention that methods use a `&mut` first parameter so that invoking methods can update the value in memory.*
```rust ```rust
engine.register_fn("update", TestStruct::update); engine.register_fn("update", TestStruct::update); // registers 'update(&mut ts)'
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new); // registers 'new'
``` ```
Finally, we call our script. The script can see the function and method we registered earlier. We need to get the result back out from script land just as before, this time casting to our custom struct type. Finally, we call our script. The script can see the function and method we registered earlier. We need to get the result back out from script land just as before, this time casting to our custom struct type.
@ -478,14 +514,14 @@ Finally, we call our script. The script can see the function and method we regi
```rust ```rust
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?; let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
println!("result: {}", result.x); // prints 1001 println!("result: {}", result.field); // prints 42
``` ```
In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method-call on that type because internally they are the same thing: methods on a type is implemented as a functions taking an first argument. In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method-call on that type because internally they are the same thing: methods on a type is implemented as a functions taking an first argument.
```rust ```rust
fn foo(ts: &mut TestStruct) -> i64 { fn foo(ts: &mut TestStruct) -> i64 {
ts.x ts.field
} }
engine.register_fn("foo", foo); engine.register_fn("foo", foo);
@ -495,39 +531,43 @@ let result = engine.eval::<i64>("let x = new_ts(); x.foo()")?;
println!("result: {}", result); // prints 1 println!("result: {}", result); // prints 1
``` ```
`type_of` works fine with custom types and returns the name of the type: [`type_of`] works fine with custom types and returns the name of the type. If `register_type_with_name` is used to register the custom type
with a special "pretty-print" name, [`type_of`] will return that name instead.
```rust ```rust
engine.register_type::<TestStruct>();
engine.register_fn("new_ts", TestStruct::new);
let x = new_ts(); let x = new_ts();
print(x.type_of()); // prints "foo::bar::TestStruct" print(x.type_of()); // prints "path::to::module::TestStruct"
```
If `register_type_with_name` is used to register the custom type with a special "pretty-print" name, `type_of` will return that name instead. engine.register_type_with_name::<TestStruct>("Hello");
engine.register_fn("new_ts", TestStruct::new);
let x = new_ts();
print(x.type_of()); // prints "Hello"
```
Getters and setters Getters and setters
------------------- -------------------
Similarly, custom types can expose members by registering a `get` and/or `set` function. Similarly, custom types can expose members by registering a `get` and/or `set` function.
For example:
```rust ```rust
#[derive(Clone)] #[derive(Clone)]
struct TestStruct { struct TestStruct {
x: i64 field: i64
} }
impl TestStruct { impl TestStruct {
fn get_x(&mut self) -> i64 { fn get_field(&mut self) -> i64 {
self.x self.field
} }
fn set_x(&mut self, new_x: i64) { fn set_field(&mut self, new_val: i64) {
self.x = new_x; self.field = new_val;
} }
fn new() -> TestStruct { fn new() -> Self {
TestStruct { x: 1 } TestStruct { field: 1 }
} }
} }
@ -535,17 +575,19 @@ let mut engine = Engine::new();
engine.register_type::<TestStruct>(); engine.register_type::<TestStruct>();
engine.register_get_set("x", TestStruct::get_x, TestStruct::set_x); engine.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field);
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
let result = engine.eval::<i64>("let a = new_ts(); a.x = 500; a.x")?; let result = engine.eval::<i64>("let a = new_ts(); a.xyz = 42; a.xyz")?;
println!("result: {}", result); println!("Answer: {}", result); // prints 42
``` ```
Initializing and maintaining state Initializing and maintaining state
--------------------------------- ---------------------------------
[`Scope`]: #initializing-and-maintaining-state
By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined but no global state. By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined but no global state.
This gives each evaluation a clean starting slate. In order to continue using the same global state from one invocation to the next, This gives each evaluation a clean starting slate. In order to continue using the same global state from one invocation to the next,
such a state must be manually created and passed in. such a state must be manually created and passed in.
@ -565,22 +607,23 @@ fn main() -> Result<(), EvalAltResult>
// Then push some initialized variables into the state // Then push some initialized variables into the state
// NOTE: Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64. // NOTE: Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64.
// Better stick to them or it gets hard working with the script. // Better stick to them or it gets hard working with the script.
scope.push("y".into(), 42_i64); scope.push("y", 42_i64);
scope.push("z".into(), 999_i64); scope.push("z", 999_i64);
scope.push("s", "hello, world!".to_string()); // remember to use 'String', not '&str'
// First invocation // First invocation
engine.eval_with_scope::<()>(&mut scope, r" engine.eval_with_scope::<()>(&mut scope, r"
let x = 4 + 5 - y + z; let x = 4 + 5 - y + z + s.len();
y = 1; y = 1;
")?; ")?;
// Second invocation using the same state // Second invocation using the same state
let result = engine.eval_with_scope::<i64>(&mut scope, "x")?; let result = engine.eval_with_scope::<i64>(&mut scope, "x")?;
println!("result: {}", result); // should print 966 println!("result: {}", result); // prints 979
// Variable y is changed in the script // Variable y is changed in the script
assert_eq!(scope.get_value::<i64>("y")?, 1); assert_eq!(scope.get_value::<i64>("y").expect("variable x should exist"), 1);
Ok(()) Ok(())
} }
@ -609,15 +652,38 @@ let /* intruder comment */ name = "Bob";
*/ */
``` ```
Statements
----------
Statements are terminated by semicolons '`;`' - they are mandatory, except for the _last_ statement where it can be omitted.
A statement can be used anywhere where an expression is expected. The _last_ statement of a statement block
(enclosed by '`{`' .. '`}`' pairs) is always the return value of the statement. If a statement has no return value
(e.g. variable definitions, assignments) then the value will be [`()`].
```rust
let a = 42; // normal assignment statement
let a = foo(42); // normal function call statement
foo < 42; // normal expression as statement
let a = { 40 + 2 }; // 'a' is set to the value of the statement block, which is the value of the last statement
// ^ notice that the last statement does not require a terminating semicolon (although it also works with it)
// ^ notice that a semicolon is required here to terminate the assignment statement; it is syntax error without it
4 * 10 + 2 // this is also a statement, which is an expression, with no ending semicolon because
// it is the last statement of the whole block
```
Variables Variables
--------- ---------
Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`'). Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`').
Variable names must start with an ASCII letter or an underscore '`_`', and must contain at least one ASCII letter within. Variable names must start with an ASCII letter or an underscore '`_`', must contain at least one ASCII letter, and must start with an ASCII letter before a digit.
Therefore, names like '`_`', '`_42`' etc. are not legal variable names. Variable names are also case _sensitive_. Therefore, names like '`_`', '`_42`', '`3a`' etc. are not legal variable names, but '`_c3po`' and '`r2d2`' are.
Variable names are also case _sensitive_.
Variables are defined using the `let` keyword. Variables are defined using the `let` keyword. A variable defined within a statement block is _local_ to that block.
```rust ```rust
let x = 3; // ok let x = 3; // ok
@ -632,6 +698,12 @@ let x = 42; // variable is 'x', lower case
let X = 123; // variable is 'X', upper case let X = 123; // variable is 'X', upper case
x == 42; x == 42;
X == 123; X == 123;
{
let x = 999; // local variable 'x' shadows the 'x' in parent block
x == 999; // access to local 'x'
}
x == 42; // the parent block's 'x' is not changed
``` ```
Constants Constants
@ -645,7 +717,7 @@ print(x * 2); // prints 84
x = 123; // syntax error - cannot assign to constant x = 123; // syntax error - cannot assign to constant
``` ```
Constants must be assigned a _value_ not an expression. Constants must be assigned a _value_, not an expression.
```rust ```rust
const x = 40 + 2; // syntax error - cannot assign expression to constant const x = 40 + 2; // syntax error - cannot assign expression to constant
@ -676,15 +748,15 @@ Numeric operators
Numeric operators generally follow C styles. Numeric operators generally follow C styles.
| Operator | Description | Integers only | | Operator | Description | Integers only |
| -------- | ----------------------------------------------------------- | :-----------: | | -------- | ---------------------------------------------------- | :-----------: |
| `+` | Plus | | | `+` | Plus | |
| `-` | Minus | | | `-` | Minus | |
| `*` | Multiply | | | `*` | Multiply | |
| `/` | Divide (C-style integer division if acted on integer types) | | | `/` | Divide (integer division if acting on integer types) | |
| `%` | Modulo (remainder) | | | `%` | Modulo (remainder) | |
| `~` | Power | | | `~` | Power | |
| `&` | Binary _And_ bit-mask | Yes | | `&` | Binary _And_ bit-mask | Yes |
| `|` | Binary _Or_ bit-mask | Yes | | `\|` | Binary _Or_ bit-mask | Yes |
| `^` | Binary _Xor_ bit-mask | Yes | | `^` | Binary _Xor_ bit-mask | Yes |
| `<<` | Left bit-shift | Yes | | `<<` | Left bit-shift | Yes |
| `>>` | Right bit-shift | Yes | | `>>` | Right bit-shift | Yes |
@ -717,9 +789,9 @@ Numeric functions
The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only:
| Function | Description | | Function | Description |
| ---------- | --------------------------------- | | ------------ | --------------------------------- |
| `abs` | absolute value | | `abs` | absolute value |
| `to_float` | converts an integer type to `f64` | | [`to_float`] | converts an integer type to `f64` |
Floating-point functions Floating-point functions
------------------------ ------------------------
@ -734,13 +806,15 @@ The following standard functions (defined in the standard library but excluded i
| Exponential | `exp` (base _e_) | | Exponential | `exp` (base _e_) |
| Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) | | Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) |
| Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` | | Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` |
| Conversion | `to_int` | | Conversion | [`to_int`] |
| Testing | `is_nan`, `is_finite`, `is_infinite` | | Testing | `is_nan`, `is_finite`, `is_infinite` |
Strings and Chars Strings and Chars
----------------- -----------------
String and char literals follow C-style formatting, with support for Unicode ('`\u`') and hex ('`\x`') escape sequences. String and char literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') and hex ('`\x`_xx_') escape sequences.
Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full, 32-bit extended Unicode code points.
Although internally Rhai strings are stored as UTF-8 just like in Rust (they _are_ Rust `String`s), Although internally Rhai strings are stored as UTF-8 just like in Rust (they _are_ Rust `String`s),
in the Rhai language they can be considered a stream of Unicode characters, and can be directly indexed (unlike Rust). in the Rhai language they can be considered a stream of Unicode characters, and can be directly indexed (unlike Rust).
@ -749,7 +823,7 @@ Individual characters within a Rhai string can be replaced. In Rhai, there is no
Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded if [`no_stdlib`]). Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded if [`no_stdlib`]).
This is particularly useful when printing output. This is particularly useful when printing output.
`type_of()` a string returns `"string"`. [`type_of()`] a string returns `"string"`.
```rust ```rust
let name = "Bob"; let name = "Bob";
@ -838,7 +912,7 @@ Arrays
Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices. Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices.
Array literals are built within square brackets '`[`' ,, '`]`' and separated by commas '`,`'. Array literals are built within square brackets '`[`' ,, '`]`' and separated by commas '`,`'.
The type of a Rhai array is `rhai::Array`. `type_of()` returns `"array"`. The type of a Rhai array is `rhai::Array`. [`type_of()`] an array returns `"array"`.
Arrays are disabled via the [`no_index`] feature. Arrays are disabled via the [`no_index`] feature.
@ -847,8 +921,8 @@ The following functions (defined in the standard library but excluded if [`no_st
| Function | Description | | Function | Description |
| ---------- | ------------------------------------------------------------------------------------- | | ---------- | ------------------------------------------------------------------------------------- |
| `push` | inserts an element at the end | | `push` | inserts an element at the end |
| `pop` | removes the last element and returns it (`()` if empty) | | `pop` | removes the last element and returns it ([`()`] if empty) |
| `shift` | removes the first element and returns it (`()` if empty) | | `shift` | removes the first element and returns it ([`()`] if empty) |
| `len` | returns the number of elements | | `len` | returns the number of elements |
| `pad` | pads the array with an element until a specified length | | `pad` | pads the array with an element until a specified length |
| `clear` | empties the array | | `clear` | empties the array |
@ -908,9 +982,7 @@ print(y.len()); // prints 0
`push` and `pad` are only defined for standard built-in types. For custom types, type-specific versions must be registered: `push` and `pad` are only defined for standard built-in types. For custom types, type-specific versions must be registered:
```rust ```rust
engine.register_fn("push", engine.register_fn("push", |list: &mut Array, item: MyType| list.push(Box::new(item)) );
|list: &mut Array, item: MyType| list.push(Box::new(item))
);
``` ```
Comparison operators Comparison operators
@ -946,9 +1018,9 @@ Boolean operators
| -------- | ------------------------------- | | -------- | ------------------------------- |
| `!` | Boolean _Not_ | | `!` | Boolean _Not_ |
| `&&` | Boolean _And_ (short-circuits) | | `&&` | Boolean _And_ (short-circuits) |
| `||` | Boolean _Or_ (short-circuits) | | `\|\|` | Boolean _Or_ (short-circuits) |
| `&` | Boolean _And_ (full evaluation) | | `&` | Boolean _And_ (full evaluation) |
| `|` | Boolean _Or_ (full evaluation) | | `\|` | Boolean _Or_ (full evaluation) |
Double boolean operators `&&` and `||` _short-circuit_, meaning that the second operand will not be evaluated Double boolean operators `&&` and `||` _short-circuit_, meaning that the second operand will not be evaluated
if the first one already proves the condition wrong. if the first one already proves the condition wrong.
@ -1051,7 +1123,7 @@ for x in array {
if x == 42 { break; } if x == 42 { break; }
} }
// The 'range' function allows iterating from first..last // The 'range' function allows iterating from first to last-1
for x in range(0, 50) { for x in range(0, 50) {
print(x); print(x);
if x == 42 { break; } if x == 42 { break; }
@ -1070,7 +1142,7 @@ return 123 + 456; // returns 579
Errors and exceptions Errors and exceptions
--------------------- ---------------------
All of `Engine`'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult` holding error information. All of [`Engine`]'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult` holding error information.
To deliberately return an error during an evaluation, use the `throw` keyword. To deliberately return an error during an evaluation, use the `throw` keyword.
```rust ```rust
@ -1114,7 +1186,7 @@ regardless of whether it is terminated with a semicolon `;`. This is different f
```rust ```rust
fn add(x, y) { fn add(x, y) {
x + y; // value of the last statement is used as the function's return value x + y; // value of the last statement (no need for ending semicolon) is used as the return value
} }
fn add2(x) { fn add2(x) {
@ -1182,11 +1254,11 @@ abc(); // prints "None."
Members and methods Members and methods
------------------- -------------------
Properties and methods in a Rust custom type registered with the engine can be called just like in Rust: Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like in Rust:
```rust ```rust
let a = new_ts(); // constructor function let a = new_ts(); // constructor function
a.x = 500; // property access a.field = 500; // property access
a.update(); // method call a.update(); // method call
``` ```
@ -1274,8 +1346,8 @@ print("done!"); // <- the line above is further simp
These are quite effective for template-based machine-generated scripts where certain constant values These are quite effective for template-based machine-generated scripts where certain constant values
are spliced into the script text in order to turn on/off certain sections. are spliced into the script text in order to turn on/off certain sections.
For fixed script texts, the constant values can be provided in a user-defined `Scope` object For fixed script texts, the constant values can be provided in a user-defined [`Scope`] object
to the `Engine` for use in compilation and evaluation. to the [`Engine`] for use in compilation and evaluation.
Beware, however, that most operators are actually function calls, and those functions can be overridden, Beware, however, that most operators are actually function calls, and those functions can be overridden,
so they are not optimized away: so they are not optimized away:
@ -1320,9 +1392,14 @@ large `if`-`else` branches because they do not depend on operators.
Alternatively, turn the optimizer to [`OptimizationLevel::Full`] Alternatively, turn the optimizer to [`OptimizationLevel::Full`]
Here be dragons! Here be dragons!
---------------- ================
### Optimization levels Optimization levels
-------------------
[`OptimizationLevel::Full`]: #optimization-levels
[`OptimizationLevel::Simple`]: #optimization-levels
[`OptimizationLevel::None`]: #optimization-levels
There are actually three levels of optimizations: `None`, `Simple` and `Full`. There are actually three levels of optimizations: `None`, `Simple` and `Full`.
@ -1334,13 +1411,17 @@ There are actually three levels of optimizations: `None`, `Simple` and `Full`.
* `Full` is _much_ more aggressive, _including_ running functions on constant arguments to determine their result. * `Full` is _much_ more aggressive, _including_ running functions on constant arguments to determine their result.
One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators. One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators.
An engine's optimization level is set via a call to `set_optimization_level`: An [`Engine`]'s optimization level is set via a call to `set_optimization_level`:
```rust ```rust
// Turn on aggressive optimizations // Turn on aggressive optimizations
engine.set_optimization_level(rhai::OptimizationLevel::Full); engine.set_optimization_level(rhai::OptimizationLevel::Full);
``` ```
When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_ and will _eagerly_
evaluated all function calls with constant arguments, using the result to replace the call. This also applies to all operators
(which are implemented as functions). For instance, the same example above:
```rust ```rust
// When compiling the following with OptimizationLevel::Full... // When compiling the following with OptimizationLevel::Full...
@ -1352,20 +1433,43 @@ if DECISION == 1 { // is a function call to the '==' function, and it retur
print("boo!"); // this block is eliminated because it is never reached print("boo!"); // this block is eliminated because it is never reached
} }
print("hello!"); // <- the above is equivalent to this print("hello!"); // <- the above is equivalent to this ('print' and 'debug' are handled specially)
``` ```
### Side effect considerations Because of the eager evaluation of functions, many constant expressions will be evaluated and replaced by the result.
This does not happen with [`OptimizationLevel::Simple`] which doesn't assume all functions to be _pure_.
All built-in operators have _pure_ functions (i.e. they do not cause side effects) so using [`OptimizationLevel::Full`] is usually quite safe. ```rust
Beware, however, that if custom functions are registered, they'll also be called. // When compiling the following with OptimizationLevel::Full...
If custom functions are registered to replace built-in operator functions, the custom functions will be called
and _may_ cause side-effects.
Therefore, when using [`OptimizationLevel::Full`], it is recommended that registrations of custom functions be held off let x = (1 + 2) * 3 - 4 / 5 % 6; // <- will be replaced by 'let x = 9'
until _after_ the compilation process. let y = (1 > 2) || (3 <= 4); // <- will be replaced by 'let y = true'
```
### Subtle semantic changes Function side effect considerations
----------------------------------
All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_ (i.e. they do not mutate state
nor cause side any effects, with the exception of `print` and `debug` which are handled specially) so using [`OptimizationLevel::Full`]
is usually quite safe _unless_ you register your own types and functions.
If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie within a pruned code block).
If custom functions are registered to replace built-in operators, they will also be called when the operators are used (in an `if`
statement, for example) and cause side-effects.
Function volatility considerations
---------------------------------
Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external
environment and not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value!
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments.
This may cause the script to behave differently from the intended semantics because essentially the result of each function call will
always be the same value.
Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions.
Subtle semantic changes
-----------------------
Some optimizations can alter subtle semantics of the script. For example: Some optimizations can alter subtle semantics of the script. For example:
@ -1399,10 +1503,11 @@ In the script above, if `my_decision` holds anything other than a boolean value,
However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces no side effects), However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces no side effects),
thus the script silently runs to completion without errors. thus the script silently runs to completion without errors.
### Turning off optimizations Turning off optimizations
-------------------------
It is usually a bad idea to depend on a script failing or such kind of subtleties, but if it turns out to be necessary (why? I would never guess), It is usually a bad idea to depend on a script failing or such kind of subtleties, but if it turns out to be necessary (why? I would never guess),
turn it off by setting the optimization level to `OptimizationLevel::None`. turn it off by setting the optimization level to [`OptimizationLevel::None`].
```rust ```rust
let engine = rhai::Engine::new(); let engine = rhai::Engine::new();
@ -1410,21 +1515,3 @@ let engine = rhai::Engine::new();
// Turn off the optimizer // Turn off the optimizer
engine.set_optimization_level(rhai::OptimizationLevel::None); engine.set_optimization_level(rhai::OptimizationLevel::None);
``` ```
[`num-traits`]: https://crates.io/crates/num-traits/
[`debug_msgs`]: #optional-features
[`unchecked`]: #optional-features
[`no_stdlib`]: #optional-features
[`no_index`]: #optional-features
[`no_float`]: #optional-features
[`no_function`]: #optional-features
[`no_optimize`]: #optional-features
[`only_i32`]: #optional-features
[`only_i64`]: #optional-features
[`Engine`]: #hello-world
[`Scope`]: #initializing-and-maintaining-state
[`Dynamic`]: #values-and-types
[`OptimizationLevel::Full`]: #optimization-levels

View File

@ -10,7 +10,7 @@ impl TestStruct {
self.x += 1000; self.x += 1000;
} }
fn new() -> TestStruct { fn new() -> Self {
TestStruct { x: 1 } TestStruct { x: 1 }
} }
} }

View File

@ -10,7 +10,7 @@ impl TestStruct {
self.x += 1000; self.x += 1000;
} }
fn new() -> TestStruct { fn new() -> Self {
TestStruct { x: 1 } TestStruct { x: 1 }
} }
} }

13
examples/no_std.rs Normal file
View File

@ -0,0 +1,13 @@
#![cfg_attr(feature = "no_std", no_std)]
use rhai::{Engine, EvalAltResult};
fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
let result = engine.eval::<i64>("40 + 2")?;
assert_eq!(result, 42);
Ok(())
}

View File

@ -36,10 +36,18 @@ fn print_error(input: &str, err: EvalAltResult) {
); );
println!("{}", lines[p.line().unwrap() - 1]); println!("{}", lines[p.line().unwrap() - 1]);
let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
_ => err.to_string(),
};
println!( println!(
"{}^ {}", "{}^ {}",
padding(" ", p.position().unwrap() - 1), padding(" ", p.position().unwrap() - 1),
err.to_string().replace(&pos_text, "") err_text.replace(&pos_text, "")
); );
} }
} }
@ -86,7 +94,12 @@ fn main() {
.map_err(EvalAltResult::ErrorParsing) .map_err(EvalAltResult::ErrorParsing)
.and_then(|r| { .and_then(|r| {
ast = Some(r); ast = Some(r);
engine.consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap()) engine
.consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap())
.or_else(|err| match err {
EvalAltResult::Return(_, _) => Ok(()),
err => Err(err),
})
}) })
{ {
println!(""); println!("");

View File

@ -31,7 +31,13 @@ fn eprint_error(input: &str, err: EvalAltResult) {
// EOF // EOF
let line = lines.len() - 1; let line = lines.len() - 1;
let pos = lines[line - 1].len(); let pos = lines[line - 1].len();
eprint_line(&lines, line, pos, &err.to_string()); let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
_ => err.to_string(),
};
eprint_line(&lines, line, pos, &err_text);
} }
p if p.is_none() => { p if p.is_none() => {
// No position // No position

View File

@ -1,4 +1,7 @@
let x = [1, 2, 3]; let x = [1, 2, 3];
print(x[1]);
print(x[1]); // prints 2
x[1] = 5; x[1] = 5;
print(x[1]);
print(x[1]); // prints 5

View File

@ -1,2 +1,2 @@
let x = 78; let x = 78;
print(x) print(x);

View File

@ -3,8 +3,8 @@
let /* I am a spy in a variable declaration! */ x = 5; let /* I am a spy in a variable declaration! */ x = 5;
/* I am a simple /* I am a simple
multiline comment */ multi-line comment */
/* look /* at /* that, /* multiline */ comments */ can be */ nested */ /* look /* at /* that, /* multi-line */ comments */ can be */ nested */
/* sorrounded by */ x // comments /* surrounded by */ x // comments

View File

@ -1,15 +1,17 @@
let arr = [1,2,3,4] // This script runs for-loops
for a in arr {
for b in [10,20] {
print(a)
print(b)
}
if a == 3 {
break;
}
}
//print(a)
for i in range(0,5) { let arr = [1,2,3,4];
print(i)
for a in arr {
for b in [10, 20] {
print(a + "," + b);
}
if a == 3 { break; }
}
//print(a); // <- if you uncomment this line, the script will fail to run
// because 'a' is not defined here
for i in range(0, 5) { // runs through a range from 1 to 5 exclusive
print(i);
} }

View File

@ -1,5 +1,7 @@
// This script defines a function and calls it
fn bob() { fn bob() {
3 return 3;
} }
print(bob()) print(bob()); // should print 3

View File

@ -1,5 +1,12 @@
// This script defines a function with two parameters
let a = 3;
fn addme(a, b) { fn addme(a, b) {
a+b a = 42; // notice that 'a' is passed by value
a + b; // notice that the last value is returned even if terminated by a semicolon
} }
print(addme(3, 4)) print(addme(a, 4)); // should print 46
print(a); // should print 3 - 'a' is never changed

View File

@ -1,5 +1,7 @@
// This script defines a function with many parameters and calls it
fn f(a, b, c, d, e, f) { fn f(a, b, c, d, e, f) {
a - b * c - d * e - f a - b * c - d * e - f
} }
print(f(100, 5, 2, 9, 6, 32)) print(f(100, 5, 2, 9, 6, 32)); // should print 4

View File

@ -1,5 +1,14 @@
let a = true; let a = 42;
if (a) { let b = 123;
let x = 56; let x = 999;
print(x);
if a > b {
print("a > b");
} else if a < b {
print("a < b");
let x = 0; // this 'x' shadows the global 'x'
print(x); // should print 0
} else {
print("a == b");
} }

View File

@ -1,8 +1,12 @@
// This script runs an infinite loop, ending it with a break statement
let x = 10; let x = 10;
// simulate do..while using loop // simulate do..while using loop
loop { loop {
print(x); print(x);
x = x - 1; x = x - 1;
if x <= 0 { break; } if x <= 0 { break; }
} }

View File

@ -1 +1 @@
print(34 + 12) print(34 + 12); // should be 46

View File

@ -1 +1,2 @@
print(12 + 34 * 5) let x = 12 + 34 * 5;
print(x); // should be 182

View File

@ -1 +1,2 @@
print(0 + (12 + 34) * 5) let x = (12 + 34) * 5;
print(x); // should be 230

View File

@ -1,6 +1,6 @@
// This is a script to calculate prime numbers. // This script uses the Sieve of Eratosthenes to calculate prime numbers.
const MAX_NUMBER_TO_CHECK = 10000; // 1229 primes const MAX_NUMBER_TO_CHECK = 10_000; // 1229 primes <= 10000
let prime_mask = []; let prime_mask = [];
prime_mask.pad(MAX_NUMBER_TO_CHECK, true); prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
@ -24,4 +24,3 @@ for p in range(2, MAX_NUMBER_TO_CHECK) {
} }
print("Total " + total_primes_found + " primes."); print("Total " + total_primes_found + " primes.");

View File

@ -1,5 +1,12 @@
let x = 1000000; // This script runs 1 million iterations
// to test the speed of the scripting engine.
let x = 1_000_000;
print("Ready... Go!");
while x > 0 { while x > 0 {
x = x - 1; x = x - 1;
} }
print(x);
print("Finished.");

View File

@ -1,7 +1,17 @@
// This script tests string operations
print("hello"); print("hello");
print("this\nis \\ nice"); print("this\nis \\ nice"); // escape sequences
print("40 hex is \x40"); print("40 hex is \x40"); // hex escape sequence
print("fun with unicode: \u2764 and \U0001F603"); print("unicode fun: \u2764"); // Unicode escape sequence
print("foo" + " " + "bar"); print("more fun: \U0001F603"); // Unicode escape sequence
print("foo" < "bar"); print("foo" + " " + "bar"); // string building using strings
print("foo" >= "bar"); print("foo" < "bar"); // string comparison
print("foo" >= "bar"); // string comparison
print("the answer is " + 42); // string building using non-string types
let s = "hello, world!"; // string variable
print("length=" + s.len()); // should be 13
s[s.len()-1] = '?'; // change the string
print(s); // should print 'hello, world?'

View File

@ -1,3 +1,5 @@
// This script runs a while loop
let x = 10; let x = 10;
while x > 0 { while x > 0 {

View File

@ -1,7 +1,7 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling. //! Helper module which defines the `Any` trait to to allow dynamic value handling.
use crate::stdlib::{ use crate::stdlib::{
any::{self, type_name, TypeId}, any::{type_name, Any as StdAny, TypeId},
boxed::Box, boxed::Box,
fmt, fmt,
}; };
@ -13,7 +13,7 @@ pub type Variant = dyn Any;
pub type Dynamic = Box<Variant>; pub type Dynamic = Box<Variant>;
/// A trait covering any type. /// A trait covering any type.
pub trait Any: any::Any { pub trait Any: StdAny {
/// Get the `TypeId` of this type. /// Get the `TypeId` of this type.
fn type_id(&self) -> TypeId; fn type_id(&self) -> TypeId;
@ -23,12 +23,12 @@ pub trait Any: any::Any {
/// Convert into `Dynamic`. /// Convert into `Dynamic`.
fn into_dynamic(&self) -> Dynamic; fn into_dynamic(&self) -> Dynamic;
/// This type may only be implemented by `rhai`. /// This trait may only be implemented by `rhai`.
#[doc(hidden)] #[doc(hidden)]
fn _closed(&self) -> _Private; fn _closed(&self) -> _Private;
} }
impl<T: Clone + any::Any + ?Sized> Any for T { impl<T: Clone + StdAny + ?Sized> Any for T {
fn type_id(&self) -> TypeId { fn type_id(&self) -> TypeId {
TypeId::of::<T>() TypeId::of::<T>()
} }
@ -91,7 +91,7 @@ pub trait AnyExt: Sized {
/// Get a copy of a `Dynamic` value as a specific type. /// Get a copy of a `Dynamic` value as a specific type.
fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self>; fn downcast<T: Any + Clone>(self) -> Result<Box<T>, Self>;
/// This type may only be implemented by `rhai`. /// This trait may only be implemented by `rhai`.
#[doc(hidden)] #[doc(hidden)]
fn _closed(&self) -> _Private; fn _closed(&self) -> _Private;
} }
@ -101,7 +101,7 @@ impl AnyExt for Dynamic {
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```
/// use rhai::{Dynamic, Any, AnyExt}; /// use rhai::{Dynamic, Any, AnyExt};
/// ///
/// let x: Dynamic = 42_u32.into_dynamic(); /// let x: Dynamic = 42_u32.into_dynamic();

View File

@ -10,7 +10,7 @@ use crate::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::Scope;
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
use crate::optimize::optimize_ast; use crate::optimize::optimize_into_ast;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
@ -19,30 +19,17 @@ use crate::stdlib::{
sync::Arc, sync::Arc,
vec::Vec, vec::Vec,
}; };
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_std"))]
use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf};
impl<'e> Engine<'e> { impl<'e> Engine<'e> {
/// Register a custom function.
pub(crate) fn register_fn_raw( pub(crate) fn register_fn_raw(
&mut self, &mut self,
fn_name: &str, fn_name: &str,
args: Option<Vec<TypeId>>, args: Option<Vec<TypeId>>,
f: Box<FnAny>, f: Box<FnAny>,
) { ) {
debug_println!(
"Register function: {} with {}",
fn_name,
if let Some(a) = &args {
format!(
"{} parameter{}",
a.len(),
if a.len() > 1 { "s" } else { "" }
)
} else {
"no parameter".to_string()
}
);
let spec = FnSpec { let spec = FnSpec {
name: fn_name.to_string().into(), name: fn_name.to_string().into(),
args, args,
@ -52,13 +39,88 @@ impl<'e> Engine<'e> {
} }
/// Register a custom type for use with the `Engine`. /// Register a custom type for use with the `Engine`.
/// The type must be `Clone`. /// The type must implement `Clone`.
///
/// # Example
///
/// ```
/// #[derive(Clone)]
/// struct TestStruct {
/// field: i64
/// }
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } }
/// fn update(&mut self) { self.field += 41; }
/// }
///
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, RegisterFn};
///
/// let mut engine = Engine::new();
///
/// // Register the custom type.
/// engine.register_type::<TestStruct>();
///
/// engine.register_fn("new_ts", TestStruct::new);
///
/// // Register method on the type.
/// engine.register_fn("update", TestStruct::update);
///
/// assert_eq!(
/// engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?.field,
/// 42
/// );
/// # Ok(())
/// # }
/// ```
pub fn register_type<T: Any + Clone>(&mut self) { pub fn register_type<T: Any + Clone>(&mut self) {
self.register_type_with_name::<T>(type_name::<T>()); self.register_type_with_name::<T>(type_name::<T>());
} }
/// Register a custom type for use with the `Engine` with a name for the `type_of` function. /// Register a custom type for use with the `Engine`, with a pretty-print name
/// The type must be `Clone`. /// for the `type_of` function. The type must implement `Clone`.
///
/// # Example
///
/// ```
/// #[derive(Clone)]
/// struct TestStruct {
/// field: i64
/// }
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } }
/// }
///
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, RegisterFn};
///
/// let mut engine = Engine::new();
///
/// // Register the custom type.
/// engine.register_type::<TestStruct>();
///
/// engine.register_fn("new_ts", TestStruct::new);
///
/// assert_eq!(
/// engine.eval::<String>("let x = new_ts(); type_of(x)")?,
/// "rust_out::TestStruct"
/// );
///
/// // Register the custom type with a name.
/// engine.register_type_with_name::<TestStruct>("Hello");
///
/// // Register methods on the type.
/// engine.register_fn("new_ts", TestStruct::new);
///
/// assert_eq!(
/// engine.eval::<String>("let x = new_ts(); type_of(x)")?,
/// "Hello"
/// );
/// # Ok(())
/// # }
/// ```
pub fn register_type_with_name<T: Any + Clone>(&mut self, name: &str) { pub fn register_type_with_name<T: Any + Clone>(&mut self, name: &str) {
// Add the pretty-print type name into the map // Add the pretty-print type name into the map
self.type_names self.type_names
@ -66,6 +128,7 @@ impl<'e> Engine<'e> {
} }
/// Register an iterator adapter for a type with the `Engine`. /// Register an iterator adapter for a type with the `Engine`.
/// This is an advanced feature.
pub fn register_iterator<T: Any, F>(&mut self, f: F) pub fn register_iterator<T: Any, F>(&mut self, f: F)
where where
F: Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static, F: Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static,
@ -74,6 +137,37 @@ impl<'e> Engine<'e> {
} }
/// Register a getter function for a member of a registered type with the `Engine`. /// Register a getter function for a member of a registered type with the `Engine`.
///
/// # Example
///
/// ```
/// #[derive(Clone)]
/// struct TestStruct {
/// field: i64
/// }
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } }
/// fn get_field(&mut self) -> i64 { self.field }
/// }
///
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, RegisterFn};
///
/// let mut engine = Engine::new();
///
/// // Register the custom type.
/// engine.register_type::<TestStruct>();
///
/// engine.register_fn("new_ts", TestStruct::new);
///
/// // Register a getter on a property (notice it doesn't have to be the same name).
/// engine.register_get("xyz", TestStruct::get_field);
///
/// assert_eq!(engine.eval::<i64>("let a = new_ts(); a.xyz")?, 1);
/// # Ok(())
/// # }
/// ```
pub fn register_get<T: Any + Clone, U: Any + Clone>( pub fn register_get<T: Any + Clone, U: Any + Clone>(
&mut self, &mut self,
name: &str, name: &str,
@ -84,6 +178,38 @@ impl<'e> Engine<'e> {
} }
/// Register a setter function for a member of a registered type with the `Engine`. /// Register a setter function for a member of a registered type with the `Engine`.
///
/// # Example
///
/// ```
/// #[derive(Clone)]
/// struct TestStruct {
/// field: i64
/// }
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } }
/// fn set_field(&mut self, new_val: i64) { self.field = new_val; }
/// }
///
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, RegisterFn};
///
/// let mut engine = Engine::new();
///
/// // Register the custom type.
/// engine.register_type::<TestStruct>();
///
/// engine.register_fn("new_ts", TestStruct::new);
///
/// // Register a setter on a property (notice it doesn't have to be the same name)
/// engine.register_set("xyz", TestStruct::set_field);
///
/// // Notice that, with a getter, there is no way to get the property value
/// engine.eval("let a = new_ts(); a.xyz = 42;")?;
/// # Ok(())
/// # }
/// ```
pub fn register_set<T: Any + Clone, U: Any + Clone>( pub fn register_set<T: Any + Clone, U: Any + Clone>(
&mut self, &mut self,
name: &str, name: &str,
@ -95,6 +221,39 @@ impl<'e> Engine<'e> {
/// Shorthand for registering both getter and setter functions /// Shorthand for registering both getter and setter functions
/// of a registered type with the `Engine`. /// of a registered type with the `Engine`.
///
/// # Example
///
/// ```
/// #[derive(Clone)]
/// struct TestStruct {
/// field: i64
/// }
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } }
/// fn get_field(&mut self) -> i64 { self.field }
/// fn set_field(&mut self, new_val: i64) { self.field = new_val; }
/// }
///
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, RegisterFn};
///
/// let mut engine = Engine::new();
///
/// // Register the custom type.
/// engine.register_type::<TestStruct>();
///
/// engine.register_fn("new_ts", TestStruct::new);
///
/// // Register a getter and a setter on a property
/// // (notice it doesn't have to be the same name)
/// engine.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field);
///
/// assert_eq!(engine.eval::<i64>("let a = new_ts(); a.xyz = 42; a.xyz")?, 42);
/// # Ok(())
/// # }
/// ```
pub fn register_get_set<T: Any + Clone, U: Any + Clone>( pub fn register_get_set<T: Any + Clone, U: Any + Clone>(
&mut self, &mut self,
name: &str, name: &str,
@ -105,19 +264,60 @@ impl<'e> Engine<'e> {
self.register_set(name, set_fn); self.register_set(name, set_fn);
} }
/// Compile a string into an AST. /// Compile a string into an `AST`, which can be used later for evaluations.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine;
///
/// let mut engine = Engine::new();
///
/// // Compile a script to an AST and store it for later evaluations
/// let ast = engine.compile("40 + 2")?;
///
/// for _ in 0..42 {
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
/// }
/// # Ok(())
/// # }
/// ```
pub fn compile(&self, input: &str) -> Result<AST, ParseError> { pub fn compile(&self, input: &str) -> Result<AST, ParseError> {
self.compile_with_scope(&Scope::new(), input) self.compile_with_scope(&Scope::new(), input)
} }
/// Compile a string into an AST using own scope. /// Compile a string into an `AST` using own scope, which can be used later for evaluations.
/// The scope is useful for passing constants into the script for optimization. /// The scope is useful for passing constants into the script for optimization.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope};
///
/// let mut engine = Engine::new();
///
/// // Create initialized scope
/// let mut scope = Scope::new();
/// scope.push_constant("x", 42_i64); // 'x' is a constant
///
/// // Compile a script to an AST and store it for later evaluations
/// let ast = engine.compile_with_scope(&mut scope,
/// "if x > 40 { x } else { 0 }"
/// )?;
///
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
/// # Ok(())
/// # }
/// ```
pub fn compile_with_scope(&self, scope: &Scope, input: &str) -> Result<AST, ParseError> { pub fn compile_with_scope(&self, scope: &Scope, input: &str) -> Result<AST, ParseError> {
let tokens_stream = lex(input); let tokens_stream = lex(input);
parse(&mut tokens_stream.peekable(), self, scope) parse(&mut tokens_stream.peekable(), self, scope)
} }
#[cfg(not(feature = "no_stdlib"))] /// Read the contents of a file into a string.
#[cfg(not(feature = "no_std"))]
fn read_file(path: PathBuf) -> Result<String, EvalAltResult> { fn read_file(path: PathBuf) -> Result<String, EvalAltResult> {
let mut f = File::open(path.clone()) let mut f = File::open(path.clone())
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))?; .map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))?;
@ -129,15 +329,55 @@ impl<'e> Engine<'e> {
.map(|_| contents) .map(|_| contents)
} }
/// Compile a file into an AST. /// Compile a script file into an `AST`, which can be used later for evaluations.
#[cfg(not(feature = "no_stdlib"))] ///
/// # Example
///
/// ```no_run
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine;
///
/// let mut engine = Engine::new();
///
/// // Compile a script file to an AST and store it for later evaluations
/// // Notice that a PathBuf is required which can easily be constructed from a string.
/// let ast = engine.compile_file("script.rhai".into())?;
///
/// for _ in 0..42 {
/// engine.eval_ast::<i64>(&ast)?;
/// }
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_std"))]
pub fn compile_file(&self, path: PathBuf) -> Result<AST, EvalAltResult> { pub fn compile_file(&self, path: PathBuf) -> Result<AST, EvalAltResult> {
self.compile_file_with_scope(&Scope::new(), path) self.compile_file_with_scope(&Scope::new(), path)
} }
/// Compile a file into an AST using own scope. /// Compile a script file into an `AST` using own scope, which can be used later for evaluations.
/// The scope is useful for passing constants into the script for optimization. /// The scope is useful for passing constants into the script for optimization.
#[cfg(not(feature = "no_stdlib"))] ///
/// # Example
///
/// ```no_run
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope};
///
/// let mut engine = Engine::new();
///
/// // Create initialized scope
/// let mut scope = Scope::new();
/// scope.push_constant("x", 42_i64); // 'x' is a constant
///
/// // Compile a script to an AST and store it for later evaluations
/// // Notice that a PathBuf is required which can easily be constructed from a string.
/// let ast = engine.compile_file_with_scope(&mut scope, "script.rhai".into())?;
///
/// let result = engine.eval_ast::<i64>(&ast)?;
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_std"))]
pub fn compile_file_with_scope( pub fn compile_file_with_scope(
&self, &self,
scope: &Scope, scope: &Scope,
@ -149,14 +389,46 @@ impl<'e> Engine<'e> {
}) })
} }
/// Evaluate a file. /// Evaluate a script file.
#[cfg(not(feature = "no_stdlib"))] ///
/// # Example
///
/// ```no_run
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine;
///
/// let mut engine = Engine::new();
///
/// // Notice that a PathBuf is required which can easily be constructed from a string.
/// let result = engine.eval_file::<i64>("script.rhai".into())?;
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_std"))]
pub fn eval_file<T: Any + Clone>(&mut self, path: PathBuf) -> Result<T, EvalAltResult> { pub fn eval_file<T: Any + Clone>(&mut self, path: PathBuf) -> Result<T, EvalAltResult> {
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents)) Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
} }
/// Evaluate a file with own scope. /// Evaluate a script file with own scope.
#[cfg(not(feature = "no_stdlib"))] ///
/// # Example
///
/// ```no_run
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope};
///
/// let mut engine = Engine::new();
///
/// // Create initialized scope
/// let mut scope = Scope::new();
/// scope.push("x", 42_i64);
///
/// // Notice that a PathBuf is required which can easily be constructed from a string.
/// let result = engine.eval_file_with_scope::<i64>(&mut scope, "script.rhai".into())?;
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_std"))]
pub fn eval_file_with_scope<T: Any + Clone>( pub fn eval_file_with_scope<T: Any + Clone>(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
@ -166,12 +438,46 @@ impl<'e> Engine<'e> {
} }
/// Evaluate a string. /// Evaluate a string.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine;
///
/// let mut engine = Engine::new();
///
/// assert_eq!(engine.eval::<i64>("40 + 2")?, 42);
/// # Ok(())
/// # }
/// ```
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> { pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
let mut scope = Scope::new(); let mut scope = Scope::new();
self.eval_with_scope(&mut scope, input) self.eval_with_scope(&mut scope, input)
} }
/// Evaluate a string with own scope. /// Evaluate a string with own scope.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope};
///
/// let mut engine = Engine::new();
///
/// // Create initialized scope
/// let mut scope = Scope::new();
/// scope.push("x", 40_i64);
///
/// assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x = x + 2; x")?, 42);
/// assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x = x + 2; x")?, 44);
///
/// // The variable in the scope is modified
/// assert_eq!(scope.get_value::<i64>("x").expect("variable x should exist"), 44);
/// # Ok(())
/// # }
/// ```
pub fn eval_with_scope<T: Any + Clone>( pub fn eval_with_scope<T: Any + Clone>(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
@ -181,13 +487,55 @@ impl<'e> Engine<'e> {
self.eval_ast_with_scope(scope, &ast) self.eval_ast_with_scope(scope, &ast)
} }
/// Evaluate an AST. /// Evaluate an `AST`.
///
/// # Example
///
/// ```no_run
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine;
///
/// let mut engine = Engine::new();
///
/// // Compile a script to an AST and store it for later evaluations
/// let ast = engine.compile("40 + 2")?;
///
/// // Evaluate it
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
/// # Ok(())
/// # }
/// ```
pub fn eval_ast<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> { pub fn eval_ast<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> {
let mut scope = Scope::new(); let mut scope = Scope::new();
self.eval_ast_with_scope(&mut scope, ast) self.eval_ast_with_scope(&mut scope, ast)
} }
/// Evaluate an AST with own scope. /// Evaluate an `AST` with own scope.
///
/// # Example
///
/// ```no_run
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope};
///
/// let mut engine = Engine::new();
///
/// // Compile a script to an AST and store it for later evaluations
/// let ast = engine.compile("x + 2")?;
///
/// // Create initialized scope
/// let mut scope = Scope::new();
/// scope.push("x", 40_i64);
///
/// // Evaluate it
/// assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x = x + 2; x")?, 42);
/// assert_eq!(engine.eval_with_scope::<i64>(&mut scope, "x = x + 2; x")?, 44);
///
/// // The variable in the scope is modified
/// assert_eq!(scope.get_value::<i64>("x").expect("variable x should exist"), 44);
/// # Ok(())
/// # }
/// ```
pub fn eval_ast_with_scope<T: Any + Clone>( pub fn eval_ast_with_scope<T: Any + Clone>(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
@ -217,31 +565,26 @@ impl<'e> Engine<'e> {
Ok(result) Ok(result)
} }
match eval_ast_internal(self, scope, ast) { eval_ast_internal(self, scope, ast)
Err(EvalAltResult::Return(out, pos)) => out.downcast::<T>().map(|v| *v).map_err(|a| { .or_else(|err| match err {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::Return(out, _) => Ok(out),
self.map_type_name((*a).type_name()).to_string(), _ => Err(err),
pos, })
) .and_then(|out| {
}), out.downcast::<T>().map(|v| *v).map_err(|a| {
Ok(out) => out.downcast::<T>().map(|v| *v).map_err(|a| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).to_string(), self.map_type_name((*a).type_name()).to_string(),
Position::eof(), Position::eof(),
) )
}), })
})
Err(err) => Err(err),
}
} }
/// Evaluate a file, but throw away the result and only return error (if any). /// Evaluate a file, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
/// and not cleared from run to run. #[cfg(not(feature = "no_std"))]
#[cfg(not(feature = "no_stdlib"))]
pub fn consume_file( pub fn consume_file(
&mut self, &mut self,
retain_functions: bool, retain_functions: bool,
@ -253,9 +596,8 @@ impl<'e> Engine<'e> {
/// Evaluate a file with own scope, but throw away the result and only return error (if any). /// Evaluate a file with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run.
/// and not cleared from run to run. #[cfg(not(feature = "no_std"))]
#[cfg(not(feature = "no_stdlib"))]
pub fn consume_file_with_scope( pub fn consume_file_with_scope(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
@ -269,8 +611,7 @@ impl<'e> Engine<'e> {
/// Evaluate a string, but throw away the result and only return error (if any). /// Evaluate a string, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
/// and not cleared from run to run.
pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> { pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> {
self.consume_with_scope(&mut Scope::new(), retain_functions, input) self.consume_with_scope(&mut Scope::new(), retain_functions, input)
} }
@ -278,8 +619,7 @@ impl<'e> Engine<'e> {
/// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Evaluate a string with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
/// and not cleared from run to run.
pub fn consume_with_scope( pub fn consume_with_scope(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
@ -297,17 +637,15 @@ impl<'e> Engine<'e> {
/// Evaluate an AST, but throw away the result and only return error (if any). /// Evaluate an AST, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
/// and not cleared from run to run.
pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> { pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> {
self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast) self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast)
} }
/// Evaluate an AST with own scope, but throw away the result and only return error (if any). /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
/// ///
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run.
/// and not cleared from run to run.
pub fn consume_ast_with_scope( pub fn consume_ast_with_scope(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
@ -333,7 +671,10 @@ impl<'e> Engine<'e> {
self.clear_functions(); self.clear_functions();
} }
result result.or_else(|err| match err {
EvalAltResult::Return(_, _) => Ok(()),
_ => Err(err),
})
} }
/// Load a list of functions into the Engine. /// Load a list of functions into the Engine.
@ -356,7 +697,7 @@ impl<'e> Engine<'e> {
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// # #[cfg(not(feature = "no_stdlib"))] /// # #[cfg(not(feature = "no_stdlib"))]
/// # #[cfg(not(feature = "no_function"))] /// # #[cfg(not(feature = "no_function"))]
@ -365,8 +706,10 @@ impl<'e> Engine<'e> {
/// ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Set 'retain_functions' in 'consume' to keep the function definitions
/// engine.consume(true, "fn add(x, y) { x.len() + y }")?; /// engine.consume(true, "fn add(x, y) { x.len() + y }")?;
/// ///
/// // Call the script-defined function
/// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?; /// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?;
/// ///
/// assert_eq!(result, 126); /// assert_eq!(result, 126);
@ -403,32 +746,30 @@ impl<'e> Engine<'e> {
}) })
} }
/// Optimize the AST with constants defined in an external Scope. /// Optimize the `AST` with constants defined in an external Scope.
/// An optimized copy of the AST is returned while the original AST is untouched. /// An optimized copy of the `AST` is returned while the original `AST` is untouched.
/// ///
/// Although optimization is performed by default during compilation, sometimes it is necessary to /// Although optimization is performed by default during compilation, sometimes it is necessary to
/// _re_-optimize an AST. For example, when working with constants that are passed in via an /// _re_-optimize an AST. For example, when working with constants that are passed in via an
/// external scope, it will be more efficient to optimize the AST once again to take advantage /// external scope, it will be more efficient to optimize the `AST` once again to take advantage
/// of the new constants. /// of the new constants.
/// ///
/// With this method, it is no longer necessary to recompile a large script. The script AST can be /// With this method, it is no longer necessary to recompile a large script. The script `AST` can be
/// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope /// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope
/// (i.e. with `scope.push_constant(...)`). Then, the AST is cloned and the copy re-optimized before running. /// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running.
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
pub fn optimize_ast(&self, scope: &Scope, ast: &AST) -> AST { pub fn optimize_ast(&self, scope: &Scope, ast: &AST) -> AST {
optimize_ast( let statements = ast.0.clone();
self, let functions = ast.1.iter().map(|f| (**f).clone()).collect();
scope,
ast.0.clone(), optimize_into_ast(self, scope, statements, functions)
ast.1.iter().map(|f| (**f).clone()).collect(),
)
} }
/// Override default action of `print` (print to stdout using `println!`) /// Override default action of `print` (print to stdout using `println!`)
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine; /// use rhai::Engine;
/// ///
@ -452,7 +793,7 @@ impl<'e> Engine<'e> {
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine; /// use rhai::Engine;
/// ///

View File

@ -2,13 +2,14 @@
//! _standard library_ of utility functions. //! _standard library_ of utility functions.
use crate::any::Any; use crate::any::Any;
#[cfg(not(feature = "no_index"))]
use crate::engine::Array;
use crate::engine::Engine; use crate::engine::Engine;
use crate::fn_register::{RegisterFn, RegisterResultFn}; use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
use crate::parser::{Position, INT}; use crate::parser::{Position, INT};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
#[cfg(not(feature = "no_index"))]
use crate::engine::Array;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT; use crate::parser::FLOAT;
@ -23,6 +24,7 @@ use crate::stdlib::{
format, format,
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub},
string::{String, ToString}, string::{String, ToString},
vec::Vec,
{i32, i64, u32}, {i32, i64, u32},
}; };
@ -55,6 +57,7 @@ macro_rules! reg_op_result1 {
impl Engine<'_> { impl Engine<'_> {
/// Register the core built-in library. /// Register the core built-in library.
pub(crate) fn register_core_lib(&mut self) { pub(crate) fn register_core_lib(&mut self) {
/// Checked add
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, EvalAltResult> { fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_add(&y).ok_or_else(|| { x.checked_add(&y).ok_or_else(|| {
@ -64,6 +67,7 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked subtract
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, EvalAltResult> { fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_sub(&y).ok_or_else(|| { x.checked_sub(&y).ok_or_else(|| {
@ -73,6 +77,7 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked multiply
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, EvalAltResult> { fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_mul(&y).ok_or_else(|| { x.checked_mul(&y).ok_or_else(|| {
@ -82,11 +87,13 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked divide
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn div<T>(x: T, y: T) -> Result<T, EvalAltResult> fn div<T>(x: T, y: T) -> Result<T, EvalAltResult>
where where
T: Display + CheckedDiv + PartialEq + Zero, T: Display + CheckedDiv + PartialEq + Zero,
{ {
// Detect division by zero
if y == T::zero() { if y == T::zero() {
return Err(EvalAltResult::ErrorArithmetic( return Err(EvalAltResult::ErrorArithmetic(
format!("Division by zero: {} / {}", x, y), format!("Division by zero: {} / {}", x, y),
@ -101,6 +108,7 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, EvalAltResult> { fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, EvalAltResult> {
x.checked_neg().ok_or_else(|| { x.checked_neg().ok_or_else(|| {
@ -110,6 +118,7 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked absolute
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, EvalAltResult> { fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, EvalAltResult> {
// FIX - We don't use Signed::abs() here because, contrary to documentation, it panics // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics
@ -125,26 +134,32 @@ impl Engine<'_> {
}) })
} }
} }
/// Unchecked add - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output { fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output {
x + y x + y
} }
/// Unchecked subtract - may panic on underflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output { fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output {
x - y x - y
} }
/// Unchecked multiply - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output { fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output {
x * y x * y
} }
/// Unchecked divide - may panic when dividing by zero
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output { fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output {
x / y x / y
} }
/// Unchecked negative - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output { fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output {
-x -x
} }
/// Unchecked absolute - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn abs_u<T>(x: T) -> <T as Neg>::Output fn abs_u<T>(x: T) -> <T as Neg>::Output
where where
@ -157,6 +172,9 @@ impl Engine<'_> {
x.into() x.into()
} }
} }
// Comparison operators
fn lt<T: PartialOrd>(x: T, y: T) -> bool { fn lt<T: PartialOrd>(x: T, y: T) -> bool {
x < y x < y
} }
@ -175,6 +193,9 @@ impl Engine<'_> {
fn ne<T: PartialEq>(x: T, y: T) -> bool { fn ne<T: PartialEq>(x: T, y: T) -> bool {
x != y x != y
} }
// Logic operators
fn and(x: bool, y: bool) -> bool { fn and(x: bool, y: bool) -> bool {
x && y x && y
} }
@ -184,6 +205,9 @@ impl Engine<'_> {
fn not(x: bool) -> bool { fn not(x: bool) -> bool {
!x !x
} }
// Bit operators
fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output { fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output {
x & y x & y
} }
@ -193,8 +217,11 @@ impl Engine<'_> {
fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output { fn binary_xor<T: BitXor>(x: T, y: T) -> <T as BitXor>::Output {
x ^ y x ^ y
} }
/// Checked left-shift
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, EvalAltResult> { fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, EvalAltResult> {
// Cannot shift by a negative number of bits
if y < 0 { if y < 0 {
return Err(EvalAltResult::ErrorArithmetic( return Err(EvalAltResult::ErrorArithmetic(
format!("Left-shift by a negative number: {} << {}", x, y), format!("Left-shift by a negative number: {} << {}", x, y),
@ -204,13 +231,15 @@ impl Engine<'_> {
CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| { CheckedShl::checked_shl(&x, y as u32).ok_or_else(|| {
EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Left-shift overflow: {} << {}", x, y), format!("Left-shift by too many bits: {} << {}", x, y),
Position::none(), Position::none(),
) )
}) })
} }
/// Checked right-shift
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, EvalAltResult> { fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, EvalAltResult> {
// Cannot shift by a negative number of bits
if y < 0 { if y < 0 {
return Err(EvalAltResult::ErrorArithmetic( return Err(EvalAltResult::ErrorArithmetic(
format!("Right-shift by a negative number: {} >> {}", x, y), format!("Right-shift by a negative number: {} >> {}", x, y),
@ -220,44 +249,49 @@ impl Engine<'_> {
CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| { CheckedShr::checked_shr(&x, y as u32).ok_or_else(|| {
EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Right-shift overflow: {} % {}", x, y), format!("Right-shift by too many bits: {} % {}", x, y),
Position::none(), Position::none(),
) )
}) })
} }
/// Unchecked left-shift - may panic if shifting by a negative number of bits
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output { fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output {
x.shl(y) x.shl(y)
} }
/// Unchecked right-shift - may panic if shifting by a negative number of bits
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
fn shr_u<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output { fn shr_u<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output {
x.shr(y) x.shr(y)
} }
/// Checked modulo
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, EvalAltResult> { fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_rem(&y).ok_or_else(|| { x.checked_rem(&y).ok_or_else(|| {
EvalAltResult::ErrorArithmetic( EvalAltResult::ErrorArithmetic(
format!("Modulo division overflow: {} % {}", x, y), format!("Modulo division by zero or overflow: {} % {}", x, y),
Position::none(), Position::none(),
) )
}) })
} }
/// Unchecked modulo - may panic if dividing by zero
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output { fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output {
x % y x % y
} }
/// Checked power
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn pow_i_i_u(x: INT, y: INT) -> Result<INT, EvalAltResult> { fn pow_i_i(x: INT, y: INT) -> Result<INT, EvalAltResult> {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
{ {
if y > (u32::MAX as INT) { if y > (u32::MAX as INT) {
Err(EvalAltResult::ErrorArithmetic( Err(EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y), format!("Integer raised to too large an index: {} ~ {}", x, y),
Position::none(), Position::none(),
)) ))
} else if y < 0 { } else if y < 0 {
Err(EvalAltResult::ErrorArithmetic( Err(EvalAltResult::ErrorArithmetic(
format!("Power underflow: {} ~ {}", x, y), format!("Integer raised to a negative index: {} ~ {}", x, y),
Position::none(), Position::none(),
)) ))
} else { } else {
@ -274,7 +308,7 @@ impl Engine<'_> {
{ {
if y < 0 { if y < 0 {
Err(EvalAltResult::ErrorArithmetic( Err(EvalAltResult::ErrorArithmetic(
format!("Power underflow: {} ~ {}", x, y), format!("Integer raised to a negative index: {} ~ {}", x, y),
Position::none(), Position::none(),
)) ))
} else { } else {
@ -287,29 +321,34 @@ impl Engine<'_> {
} }
} }
} }
/// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
fn pow_i_i(x: INT, y: INT) -> INT { fn pow_i_i_u(x: INT, y: INT) -> INT {
x.pow(y as u32) x.pow(y as u32)
} }
/// Floating-point power - always well-defined
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT { fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT {
x.powf(y) x.powf(y)
} }
/// Checked power
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_i_u(x: FLOAT, y: INT) -> Result<FLOAT, EvalAltResult> { fn pow_f_i(x: FLOAT, y: INT) -> Result<FLOAT, EvalAltResult> {
// Raise to power that is larger than an i32
if y > (i32::MAX as INT) { if y > (i32::MAX as INT) {
return Err(EvalAltResult::ErrorArithmetic( return Err(EvalAltResult::ErrorArithmetic(
format!("Power overflow: {} ~ {}", x, y), format!("Number raised to too large an index: {} ~ {}", x, y),
Position::none(), Position::none(),
)); ));
} }
Ok(x.powi(y as i32)) Ok(x.powi(y as i32))
} }
/// Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_i(x: FLOAT, y: INT) -> FLOAT { fn pow_f_i_u(x: FLOAT, y: INT) -> FLOAT {
x.powi(y as i32) x.powi(y as i32)
} }
@ -393,8 +432,10 @@ impl Engine<'_> {
} }
} }
// `&&` and `||` are treated specially as they short-circuit
//reg_op!(self, "||", or, bool); //reg_op!(self, "||", or, bool);
//reg_op!(self, "&&", and, bool); //reg_op!(self, "&&", and, bool);
reg_op!(self, "|", or, bool); reg_op!(self, "|", or, bool);
reg_op!(self, "&", and, bool); reg_op!(self, "&", and, bool);
@ -448,18 +489,18 @@ impl Engine<'_> {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ {
self.register_result_fn("~", pow_i_i_u); self.register_result_fn("~", pow_i_i);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
self.register_result_fn("~", pow_f_i_u); self.register_result_fn("~", pow_f_i);
} }
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
{ {
self.register_fn("~", pow_i_i); self.register_fn("~", pow_i_i_u);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
self.register_fn("~", pow_f_i); self.register_fn("~", pow_f_i_u);
} }
{ {
@ -606,7 +647,6 @@ impl Engine<'_> {
} }
} }
#[cfg(not(feature = "no_stdlib"))]
macro_rules! reg_fn2x { macro_rules! reg_fn2x {
($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => (
$( $(
@ -615,7 +655,6 @@ macro_rules! reg_fn2x {
) )
} }
#[cfg(not(feature = "no_stdlib"))]
macro_rules! reg_fn2y { macro_rules! reg_fn2y {
($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => (
$( $(
@ -628,9 +667,6 @@ macro_rules! reg_fn2y {
impl Engine<'_> { impl Engine<'_> {
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_stdlib"))]
pub(crate) fn register_stdlib(&mut self) { pub(crate) fn register_stdlib(&mut self) {
#[cfg(not(feature = "no_index"))]
use crate::fn_register::RegisterDynamicFn;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
// Advanced math functions // Advanced math functions

View File

@ -1,11 +1,14 @@
//! Helper module which defines `FnArgs` to make function calling easier. //! Helper module which defines `FnArgs` to make function calling easier.
#![allow(non_snake_case)]
use crate::any::{Any, Dynamic}; use crate::any::{Any, Dynamic};
use crate::stdlib::{string::String, vec, vec::Vec};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::engine::Array;
use crate::stdlib::{string::String, vec, vec::Vec};
/// Trait that represent arguments to a function call. /// Trait that represent arguments to a function call.
pub trait FuncArgs { pub trait FuncArgs {
/// Convert to a `Vec` of `Dynamic` arguments. /// Convert to a `Vec` of `Dynamic` arguments.

View File

@ -57,7 +57,7 @@ pub struct FnSpec<'a> {
/// Rhai main scripting engine. /// Rhai main scripting engine.
/// ///
/// ```rust /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::Engine; /// use rhai::Engine;
/// ///
@ -165,16 +165,6 @@ impl Engine<'_> {
def_val: Option<&Dynamic>, def_val: Option<&Dynamic>,
pos: Position, pos: Position,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
debug_println!(
"Calling function: {} ({})",
fn_name,
args.iter()
.map(|x| (*x).type_name())
.map(|name| self.map_type_name(name))
.collect::<Vec<_>>()
.join(", ")
);
// First search in script-defined functions (can override built-in) // First search in script-defined functions (can override built-in)
if let Ok(n) = self if let Ok(n) = self
.script_functions .script_functions
@ -1031,30 +1021,29 @@ impl Engine<'_> {
// Block scope // Block scope
Stmt::Block(block, _) => { Stmt::Block(block, _) => {
let prev_len = scope.len(); let prev_len = scope.len();
let mut last_result: Result<Dynamic, EvalAltResult> = Ok(().into_dynamic()); let mut result: Result<Dynamic, EvalAltResult> = Ok(().into_dynamic());
for block_stmt in block.iter() { for stmt in block.iter() {
last_result = self.eval_stmt(scope, block_stmt); result = self.eval_stmt(scope, stmt);
if let Err(x) = last_result { if result.is_err() {
last_result = Err(x);
break; break;
} }
} }
scope.rewind(prev_len); scope.rewind(prev_len);
last_result result
} }
// If-else statement // If-else statement
Stmt::IfElse(guard, body, else_body) => self Stmt::IfElse(guard, if_body, else_body) => self
.eval_expr(scope, guard)? .eval_expr(scope, guard)?
.downcast::<bool>() .downcast::<bool>()
.map_err(|_| EvalAltResult::ErrorIfGuard(guard.position())) .map_err(|_| EvalAltResult::ErrorIfGuard(guard.position()))
.and_then(|guard_val| { .and_then(|guard_val| {
if *guard_val { if *guard_val {
self.eval_stmt(scope, body) self.eval_stmt(scope, if_body)
} else if let Some(stmt) = else_body { } else if let Some(stmt) = else_body {
self.eval_stmt(scope, stmt.as_ref()) self.eval_stmt(scope, stmt.as_ref())
} else { } else {
@ -1069,7 +1058,9 @@ impl Engine<'_> {
if *guard_val { if *guard_val {
match self.eval_stmt(scope, body) { match self.eval_stmt(scope, body) {
Ok(_) => (), Ok(_) => (),
Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()), Err(EvalAltResult::ErrorLoopBreak(_)) => {
return Ok(().into_dynamic())
}
Err(x) => return Err(x), Err(x) => return Err(x),
} }
} else { } else {
@ -1084,7 +1075,7 @@ impl Engine<'_> {
Stmt::Loop(body) => loop { Stmt::Loop(body) => loop {
match self.eval_stmt(scope, body) { match self.eval_stmt(scope, body) {
Ok(_) => (), Ok(_) => (),
Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()), Err(EvalAltResult::ErrorLoopBreak(_)) => return Ok(().into_dynamic()),
Err(x) => return Err(x), Err(x) => return Err(x),
} }
}, },
@ -1103,7 +1094,7 @@ impl Engine<'_> {
match self.eval_stmt(scope, body) { match self.eval_stmt(scope, body) {
Ok(_) => (), Ok(_) => (),
Err(EvalAltResult::LoopBreak) => break, Err(EvalAltResult::ErrorLoopBreak(_)) => break,
Err(x) => return Err(x), Err(x) => return Err(x),
} }
} }
@ -1115,7 +1106,7 @@ impl Engine<'_> {
} }
// Break statement // Break statement
Stmt::Break(_) => Err(EvalAltResult::LoopBreak), Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(*pos)),
// Empty return // Empty return
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
@ -1181,11 +1172,12 @@ impl Engine<'_> {
} }
/// Print/debug to stdout /// Print/debug to stdout
#[cfg(not(feature = "no_std"))]
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_stdlib"))]
fn default_print(s: &str) { fn default_print(s: &str) {
println!("{}", s); println!("{}", s);
} }
/// No-op /// No-op
#[cfg(feature = "no_stdlib")] #[cfg(any(feature = "no_std", feature = "no_stdlib"))]
fn default_print(_: &str) {} fn default_print(_: &str) {}

View File

@ -1,6 +1,7 @@
//! Module containing error definitions for the parsing process. //! Module containing error definitions for the parsing process.
use crate::parser::Position; use crate::parser::Position;
use crate::stdlib::{char, error::Error, fmt, string::String}; use crate::stdlib::{char, error::Error, fmt, string::String};
/// Error when tokenizing the script text. /// Error when tokenizing the script text.
@ -58,6 +59,10 @@ pub enum ParseErrorType {
/// An open `[` is missing the corresponding closing `]`. /// An open `[` is missing the corresponding closing `]`.
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
MissingRightBracket(String), MissingRightBracket(String),
/// A list of expressions is missing the separating ','.
MissingComma(String),
/// A statement is missing the ending ';'.
MissingSemicolon(String),
/// An expression in function call arguments `()` has syntax error. /// An expression in function call arguments `()` has syntax error.
MalformedCallExpr(String), MalformedCallExpr(String),
/// An expression in indexing brackets `[]` has syntax error. /// An expression in indexing brackets `[]` has syntax error.
@ -78,12 +83,17 @@ pub enum ParseErrorType {
/// A function definition is missing the parameters list. Wrapped value is the function name. /// A function definition is missing the parameters list. Wrapped value is the function name.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
FnMissingParams(String), FnMissingParams(String),
/// A function definition is missing the body. Wrapped value is the function name.
#[cfg(not(feature = "no_function"))]
FnMissingBody(String),
/// Assignment to an inappropriate LHS (left-hand-side) expression. /// Assignment to an inappropriate LHS (left-hand-side) expression.
AssignmentToInvalidLHS, AssignmentToInvalidLHS,
/// Assignment to a copy of a value. /// Assignment to a copy of a value.
AssignmentToCopy, AssignmentToCopy,
/// Assignment to an a constant variable. /// Assignment to an a constant variable.
AssignmentToConstant(String), AssignmentToConstant(String),
/// Break statement not inside a loop.
LoopBreak,
} }
/// Error when parsing a script. /// Error when parsing a script.
@ -116,6 +126,8 @@ impl ParseError {
ParseErrorType::MissingRightBrace(_) => "Expecting '}'", ParseErrorType::MissingRightBrace(_) => "Expecting '}'",
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
ParseErrorType::MissingRightBracket(_) => "Expecting ']'", ParseErrorType::MissingRightBracket(_) => "Expecting ']'",
ParseErrorType::MissingComma(_) => "Expecting ','",
ParseErrorType::MissingSemicolon(_) => "Expecting ';'",
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments", ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression", ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
@ -127,10 +139,13 @@ impl ParseError {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration", ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
ParseErrorType::WrongFnDefinition => "Function definitions must be at top level and cannot be inside a block or another function", ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration",
#[cfg(not(feature = "no_function"))]
ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression", ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression",
ParseErrorType::AssignmentToCopy => "Cannot assign to this expression because it will only be changing a copy of the value", ParseErrorType::AssignmentToCopy => "Cannot assign to this expression because it will only be changing a copy of the value",
ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant variable." ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant variable.",
ParseErrorType::LoopBreak => "Break statement should only be used inside a loop"
} }
} }
} }
@ -158,6 +173,11 @@ impl fmt::Display for ParseError {
write!(f, "Expecting parameters for function '{}'", s)? write!(f, "Expecting parameters for function '{}'", s)?
} }
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingBody(ref s) => {
write!(f, "Expecting body statement block for function '{}'", s)?
}
ParseErrorType::MissingRightParen(ref s) | ParseErrorType::MissingRightBrace(ref s) => { ParseErrorType::MissingRightParen(ref s) | ParseErrorType::MissingRightBrace(ref s) => {
write!(f, "{} for {}", self.desc(), s)? write!(f, "{} for {}", self.desc(), s)?
} }
@ -165,6 +185,10 @@ impl fmt::Display for ParseError {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
ParseErrorType::MissingRightBracket(ref s) => write!(f, "{} for {}", self.desc(), s)?, ParseErrorType::MissingRightBracket(ref s) => write!(f, "{} for {}", self.desc(), s)?,
ParseErrorType::MissingSemicolon(ref s) | ParseErrorType::MissingComma(ref s) => {
write!(f, "{} for {}", self.desc(), s)?
}
ParseErrorType::AssignmentToConstant(ref s) if s.is_empty() => { ParseErrorType::AssignmentToConstant(ref s) if s.is_empty() => {
write!(f, "{}", self.desc())? write!(f, "{}", self.desc())?
} }

View File

@ -1,95 +1,99 @@
//! Module which defines the function registration mechanism. //! Module which defines the function registration mechanism.
#![allow(non_snake_case)]
use crate::any::{Any, Dynamic}; use crate::any::{Any, Dynamic};
use crate::engine::{Engine, FnCallArgs}; use crate::engine::{Engine, FnCallArgs};
use crate::parser::Position; use crate::parser::Position;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::stdlib::{any::TypeId, boxed::Box, string::ToString, vec}; use crate::stdlib::{any::TypeId, boxed::Box, string::ToString, vec};
/// A trait to register custom functions with the `Engine`. /// A trait to register custom functions with the `Engine`.
///
/// # Example
///
/// ```rust
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, RegisterFn};
///
/// // Normal function
/// fn add(x: i64, y: i64) -> i64 {
/// x + y
/// }
///
/// let mut engine = Engine::new();
///
/// // You must use the trait rhai::RegisterFn to get this method.
/// engine.register_fn("add", add);
///
/// let result = engine.eval::<i64>("add(40, 2)")?;
///
/// println!("Answer: {}", result); // prints 42
/// # Ok(())
/// # }
/// ```
pub trait RegisterFn<FN, ARGS, RET> { pub trait RegisterFn<FN, ARGS, RET> {
/// Register a custom function with the `Engine`. /// Register a custom function with the `Engine`.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, RegisterFn};
///
/// // Normal function
/// fn add(x: i64, y: i64) -> i64 {
/// x + y
/// }
///
/// let mut engine = Engine::new();
///
/// // You must use the trait rhai::RegisterFn to get this method.
/// engine.register_fn("add", add);
///
/// assert_eq!(engine.eval::<i64>("add(40, 2)")?, 42);
///
/// // You can also register a closure.
/// engine.register_fn("sub", |x: i64, y: i64| x - y );
///
/// assert_eq!(engine.eval::<i64>("sub(44, 2)")?, 42);
/// # Ok(())
/// # }
/// ```
fn register_fn(&mut self, name: &str, f: FN); fn register_fn(&mut self, name: &str, f: FN);
} }
/// A trait to register custom functions that return `Dynamic` values with the `Engine`. /// A trait to register custom functions that return `Dynamic` values with the `Engine`.
///
/// # Example
///
/// ```rust
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Dynamic, RegisterDynamicFn};
///
/// // Function that returns a Dynamic value
/// fn get_an_any(x: i64) -> Dynamic {
/// Box::new(x)
/// }
///
/// let mut engine = Engine::new();
///
/// // You must use the trait rhai::RegisterDynamicFn to get this method.
/// engine.register_dynamic_fn("get_an_any", get_an_any);
///
/// let result = engine.eval::<i64>("get_an_any(42)")?;
///
/// println!("Answer: {}", result); // prints 42
/// # Ok(())
/// # }
/// ```
pub trait RegisterDynamicFn<FN, ARGS> { pub trait RegisterDynamicFn<FN, ARGS> {
/// Register a custom function returning `Dynamic` values with the `Engine`. /// Register a custom function returning `Dynamic` values with the `Engine`.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Dynamic, RegisterDynamicFn};
///
/// // Function that returns a Dynamic value
/// fn return_the_same_as_dynamic(x: i64) -> Dynamic {
/// Box::new(x)
/// }
///
/// let mut engine = Engine::new();
///
/// // You must use the trait rhai::RegisterDynamicFn to get this method.
/// engine.register_dynamic_fn("get_any_number", return_the_same_as_dynamic);
///
/// assert_eq!(engine.eval::<i64>("get_any_number(42)")?, 42);
/// # Ok(())
/// # }
/// ```
fn register_dynamic_fn(&mut self, name: &str, f: FN); fn register_dynamic_fn(&mut self, name: &str, f: FN);
} }
/// A trait to register fallible custom functions returning Result<_, EvalAltResult> with the `Engine`. /// A trait to register fallible custom functions returning Result<_, EvalAltResult> with the `Engine`.
///
/// # Example
///
/// ```rust
/// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, RegisterFn};
///
/// // Normal function
/// fn add(x: i64, y: i64) -> i64 {
/// x + y
/// }
///
/// let mut engine = Engine::new();
///
/// // You must use the trait rhai::RegisterFn to get this method.
/// engine.register_fn("add", add);
///
/// let result = engine.eval::<i64>("add(40, 2)")?;
///
/// println!("Answer: {}", result); // prints 42
/// # Ok(())
/// # }
/// ```
pub trait RegisterResultFn<FN, ARGS, RET> { pub trait RegisterResultFn<FN, ARGS, RET> {
/// Register a custom function with the `Engine`. /// Register a custom fallible function with the `Engine`.
///
/// # Example
///
/// ```
/// use rhai::{Engine, RegisterResultFn, EvalAltResult};
///
/// // Normal function
/// fn div(x: i64, y: i64) -> Result<i64, EvalAltResult> {
/// if y == 0 {
/// Err("division by zero!".into()) // '.into()' automatically converts to 'EvalAltResult::ErrorRuntime'
/// } else {
/// Ok(x / y)
/// }
/// }
///
/// let mut engine = Engine::new();
///
/// // You must use the trait rhai::RegisterResultFn to get this method.
/// engine.register_result_fn("div", div);
///
/// engine.eval::<i64>("div(42, 0)")
/// .expect_err("expecting division by zero error!");
/// ```
fn register_result_fn(&mut self, name: &str, f: FN); fn register_result_fn(&mut self, name: &str, f: FN);
} }

View File

@ -5,7 +5,7 @@
//! It provides a familiar syntax based on JS and Rust and a simple Rust interface. //! It provides a familiar syntax based on JS and Rust and a simple Rust interface.
//! Here is a quick example. First, the contents of `my_script.rhai`: //! Here is a quick example. First, the contents of `my_script.rhai`:
//! //!
//! ```rust,ignore //! ```,ignore
//! fn factorial(x) { //! fn factorial(x) {
//! if x == 1 { return 1; } //! if x == 1 { return 1; }
//! x * factorial(x - 1) //! x * factorial(x - 1)
@ -16,7 +16,7 @@
//! //!
//! And the Rust part: //! And the Rust part:
//! //!
//! ```rust,ignore //! ```,no_run
//! use rhai::{Engine, EvalAltResult, RegisterFn}; //! use rhai::{Engine, EvalAltResult, RegisterFn};
//! //!
//! fn main() -> Result<(), EvalAltResult> //! fn main() -> Result<(), EvalAltResult>
@ -29,6 +29,7 @@
//! //!
//! engine.register_fn("compute_something", compute_something); //! engine.register_fn("compute_something", compute_something);
//! //!
//! # #[cfg(not(feature = "no_std"))]
//! assert_eq!(engine.eval_file::<bool>("my_script.rhai".into())?, true); //! assert_eq!(engine.eval_file::<bool>("my_script.rhai".into())?, true);
//! //!
//! Ok(()) //! Ok(())
@ -37,36 +38,11 @@
//! //!
//! [Check out the README on GitHub for more information!](https://github.com/jonathandturner/rhai) //! [Check out the README on GitHub for more information!](https://github.com/jonathandturner/rhai)
#![cfg_attr(feature = "no_stdlib", no_std)] #![cfg_attr(feature = "no_std", no_std)]
#![allow(non_snake_case)]
#[cfg(feature = "no_stdlib")] #[cfg(feature = "no_std")]
extern crate alloc; extern crate alloc;
// needs to be here, because order matters for macros
macro_rules! debug_println {
() => (
#[cfg(feature = "debug_msgs")]
{
print!("\n");
}
);
($fmt:expr) => (
#[cfg(feature = "debug_msgs")]
{
print!(concat!($fmt, "\n"));
}
);
($fmt:expr, $($arg:tt)*) => (
#[cfg(feature = "debug_msgs")]
{
print!(concat!($fmt, "\n"), $($arg)*);
}
);
}
#[macro_use]
mod stdlib;
mod any; mod any;
mod api; mod api;
mod builtin; mod builtin;
@ -78,6 +54,7 @@ mod optimize;
mod parser; mod parser;
mod result; mod result;
mod scope; mod scope;
mod stdlib;
pub use any::{Any, AnyExt, Dynamic, Variant}; pub use any::{Any, AnyExt, Dynamic, Variant};
pub use call::FuncArgs; pub use call::FuncArgs;

View File

@ -1,60 +1,68 @@
#![cfg(not(feature = "no_optimize"))] #![cfg(not(feature = "no_optimize"))]
use crate::any::Dynamic; use crate::any::{Any, Dynamic};
use crate::engine::{Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_PRINT}; use crate::engine::{
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, Stmt, AST}; Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_PRINT, KEYWORD_TYPE_OF,
};
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
use crate::scope::{Scope, ScopeEntry, VariableType}; use crate::scope::{Scope, ScopeEntry, VariableType};
use crate::stdlib::{ use crate::stdlib::{
boxed::Box,
string::{String, ToString},
sync::Arc, sync::Arc,
vec::Vec, string::{String, ToString}, vec,
boxed::Box, vec, vec::Vec,
}; };
/// Level of optimization performed /// Level of optimization performed.
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
pub enum OptimizationLevel { pub enum OptimizationLevel {
/// No optimization performed /// No optimization performed.
None, None,
/// Only perform simple optimizations without evaluating functions /// Only perform simple optimizations without evaluating functions.
Simple, Simple,
/// Full optimizations performed, including evaluating functions. /// Full optimizations performed, including evaluating functions.
/// Take care that this may cause side effects. /// Take care that this may cause side effects as it essentially assumes that all functions are pure.
Full, Full,
} }
/// Mutable state throughout an optimization pass.
struct State<'a> { struct State<'a> {
/// Has the AST been changed during this pass?
changed: bool, changed: bool,
/// Collection of constants to use for eager function evaluations.
constants: Vec<(String, Expr)>, constants: Vec<(String, Expr)>,
engine: Option<&'a Engine<'a>>, /// An `Engine` instance for eager function evaluation.
engine: &'a Engine<'a>,
} }
impl State<'_> { impl State<'_> {
pub fn new() -> Self { /// Reset the state from dirty to clean.
State {
changed: false,
constants: vec![],
engine: None,
}
}
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.changed = false; self.changed = false;
} }
/// Set the AST state to be dirty (i.e. changed).
pub fn set_dirty(&mut self) { pub fn set_dirty(&mut self) {
self.changed = true; self.changed = true;
} }
/// Is the AST dirty (i.e. changed)?
pub fn is_dirty(&self) -> bool { pub fn is_dirty(&self) -> bool {
self.changed self.changed
} }
/// Does a constant exist?
pub fn contains_constant(&self, name: &str) -> bool { pub fn contains_constant(&self, name: &str) -> bool {
self.constants.iter().any(|(n, _)| n == name) self.constants.iter().any(|(n, _)| n == name)
} }
/// Prune the list of constants back to a specified size.
pub fn restore_constants(&mut self, len: usize) { pub fn restore_constants(&mut self, len: usize) {
self.constants.truncate(len) self.constants.truncate(len)
} }
/// Add a new constant to the list.
pub fn push_constant(&mut self, name: &str, value: Expr) { pub fn push_constant(&mut self, name: &str, value: Expr) {
self.constants.push((name.to_string(), value)) self.constants.push((name.to_string(), value))
} }
/// Look up a constant from the list.
pub fn find_constant(&self, name: &str) -> Option<&Expr> { pub fn find_constant(&self, name: &str) -> Option<&Expr> {
for (n, expr) in self.constants.iter().rev() { for (n, expr) in self.constants.iter().rev() {
if n == name { if n == name {
@ -66,100 +74,129 @@ impl State<'_> {
} }
} }
/// Optimize a statement.
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt { fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
match stmt { match stmt {
Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => { // if expr { Noop }
Stmt::IfElse(expr, if_block, None) if matches!(*if_block, Stmt::Noop(_)) => {
state.set_dirty(); state.set_dirty();
let pos = expr.position(); let pos = expr.position();
let expr = optimize_expr(*expr, state); let expr = optimize_expr(*expr, state);
if matches!(expr, Expr::False(_) | Expr::True(_)) {
Stmt::Noop(stmt1.position())
} else {
let stmt = Stmt::Expr(Box::new(expr));
if preserve_result { if preserve_result {
Stmt::Block(vec![stmt, *stmt1], pos) // -> { expr, Noop }
Stmt::Block(vec![Stmt::Expr(Box::new(expr)), *if_block], pos)
} else { } else {
stmt // -> expr
Stmt::Expr(Box::new(expr))
} }
} }
} // if expr { if_block }
Stmt::IfElse(expr, if_block, None) => match *expr {
Stmt::IfElse(expr, stmt1, None) => match *expr { // if false { if_block } -> Noop
Expr::False(pos) => { Expr::False(pos) => {
state.set_dirty(); state.set_dirty();
Stmt::Noop(pos) Stmt::Noop(pos)
} }
Expr::True(_) => optimize_stmt(*stmt1, state, true), // if true { if_block } -> if_block
Expr::True(_) => optimize_stmt(*if_block, state, true),
// if expr { if_block }
expr => Stmt::IfElse( expr => Stmt::IfElse(
Box::new(optimize_expr(expr, state)), Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*stmt1, state, true)), Box::new(optimize_stmt(*if_block, state, true)),
None, None,
), ),
}, },
// if expr { if_block } else { else_block }
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr { Stmt::IfElse(expr, if_block, Some(else_block)) => match *expr {
Expr::False(_) => optimize_stmt(*stmt2, state, true), // if false { if_block } else { else_block } -> else_block
Expr::True(_) => optimize_stmt(*stmt1, state, true), Expr::False(_) => optimize_stmt(*else_block, state, true),
// if true { if_block } else { else_block } -> if_block
Expr::True(_) => optimize_stmt(*if_block, state, true),
// if expr { if_block } else { else_block }
expr => Stmt::IfElse( expr => Stmt::IfElse(
Box::new(optimize_expr(expr, state)), Box::new(optimize_expr(expr, state)),
Box::new(optimize_stmt(*stmt1, state, true)), Box::new(optimize_stmt(*if_block, state, true)),
match optimize_stmt(*stmt2, state, true) { match optimize_stmt(*else_block, state, true) {
stmt if stmt.is_noop() => None, stmt if matches!(stmt, Stmt::Noop(_)) => None, // Noop -> no else block
stmt => Some(Box::new(stmt)), stmt => Some(Box::new(stmt)),
}, },
), ),
}, },
// while expr { block }
Stmt::While(expr, stmt) => match *expr { Stmt::While(expr, block) => match *expr {
// while false { block } -> Noop
Expr::False(pos) => { Expr::False(pos) => {
state.set_dirty(); state.set_dirty();
Stmt::Noop(pos) Stmt::Noop(pos)
} }
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))), // while true { block } -> loop { block }
expr => Stmt::While( Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*block, state, false))),
Box::new(optimize_expr(expr, state)), // while expr { block }
Box::new(optimize_stmt(*stmt, state, false)), expr => match optimize_stmt(*block, state, false) {
), // while expr { break; } -> { expr; }
Stmt::Break(pos) => {
// Only a single break statement - turn into running the guard expression once
state.set_dirty();
let mut statements = vec![Stmt::Expr(Box::new(optimize_expr(expr, state)))];
if preserve_result {
statements.push(Stmt::Noop(pos))
}
Stmt::Block(statements, pos)
}
// while expr { block }
stmt => Stmt::While(Box::new(optimize_expr(expr, state)), Box::new(stmt)),
}, },
},
Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))), // loop { block }
Stmt::For(id, expr, stmt) => Stmt::For( Stmt::Loop(block) => match optimize_stmt(*block, state, false) {
// loop { break; } -> Noop
Stmt::Break(pos) => {
// Only a single break statement
state.set_dirty();
Stmt::Noop(pos)
}
// loop { block }
stmt => Stmt::Loop(Box::new(stmt)),
},
// for id in expr { block }
Stmt::For(id, expr, block) => Stmt::For(
id, id,
Box::new(optimize_expr(*expr, state)), Box::new(optimize_expr(*expr, state)),
Box::new(optimize_stmt(*stmt, state, false)), Box::new(optimize_stmt(*block, state, false)),
), ),
// let id = expr;
Stmt::Let(id, Some(expr), pos) => { Stmt::Let(id, Some(expr), pos) => {
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos) Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos)
} }
// let id;
Stmt::Let(_, None, _) => stmt, Stmt::Let(_, None, _) => stmt,
// { block }
Stmt::Block(block, pos) => {
let orig_len = block.len(); // Original number of statements in the block, for change detection
let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later
Stmt::Block(statements, pos) => { // Optimize each statement in the block
let orig_len = statements.len(); let mut result: Vec<_> = block
let orig_constants_len = state.constants.len(); .into_iter()
let mut result: Vec<_> = statements
.into_iter() // For each statement
.map(|stmt| { .map(|stmt| {
if let Stmt::Const(name, value, pos) = stmt { if let Stmt::Const(name, value, pos) = stmt {
// Add constant into the state
state.push_constant(&name, *value); state.push_constant(&name, *value);
state.set_dirty(); state.set_dirty();
Stmt::Noop(pos) // No need to keep constants Stmt::Noop(pos) // No need to keep constants
} else { } else {
optimize_stmt(stmt, state, preserve_result) // Optimize the statement // Optimize the statement
optimize_stmt(stmt, state, preserve_result)
} }
}) })
.enumerate()
.filter(|(i, stmt)| stmt.is_op() || (preserve_result && *i == orig_len - 1)) // Remove no-op's but leave the last one if we need the result
.map(|(_, stmt)| stmt)
.collect(); .collect();
// Remove all raw expression statements that are pure except for the very last statement // Remove all raw expression statements that are pure except for the very last statement
let last_stmt = if preserve_result { result.pop() } else { None }; let last_stmt = if preserve_result { result.pop() } else { None };
result.retain(|stmt| !matches!(stmt, Stmt::Expr(expr) if expr.is_pure())); result.retain(|stmt| !stmt.is_pure());
if let Some(stmt) = last_stmt { if let Some(stmt) = last_stmt {
result.push(stmt); result.push(stmt);
@ -186,19 +223,40 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
result.push(Stmt::Noop(pos)) result.push(Stmt::Noop(pos))
} }
// Optimize all the statements again
result = result result = result
.into_iter() .into_iter()
.rev() .rev()
.enumerate() .enumerate()
.map(|(i, s)| optimize_stmt(s, state, i == 0)) // Optimize all other statements again .map(|(i, s)| optimize_stmt(s, state, i == 0))
.rev() .rev()
.collect(); .collect();
} }
// Remove everything following the the first return/throw
let mut dead_code = false;
result.retain(|stmt| {
if dead_code {
return false;
}
match stmt {
Stmt::ReturnWithVal(_, _, _) | Stmt::Break(_) => {
dead_code = true;
}
_ => (),
}
true
});
// Change detection
if orig_len != result.len() { if orig_len != result.len() {
state.set_dirty(); state.set_dirty();
} }
// Pop the stack and remove all the local constants
state.restore_constants(orig_constants_len); state.restore_constants(orig_constants_len);
match result[..] { match result[..] {
@ -215,42 +273,54 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
_ => Stmt::Block(result, pos), _ => Stmt::Block(result, pos),
} }
} }
// expr;
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))), Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
// return expr;
Stmt::ReturnWithVal(Some(expr), is_return, pos) => { Stmt::ReturnWithVal(Some(expr), is_return, pos) => {
Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos) Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos)
} }
// All other statements - skip
stmt => stmt, stmt => stmt,
} }
} }
/// Optimize an expression.
fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// These keywords are handled specially
const SKIP_FUNC_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_DUMP_AST];
match expr { match expr {
// ( stmt )
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) { Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) {
// ( Noop ) -> ()
Stmt::Noop(_) => { Stmt::Noop(_) => {
state.set_dirty(); state.set_dirty();
Expr::Unit(pos) Expr::Unit(pos)
} }
// ( expr ) -> expr
Stmt::Expr(expr) => { Stmt::Expr(expr) => {
state.set_dirty(); state.set_dirty();
*expr *expr
} }
// ( stmt )
stmt => Expr::Stmt(Box::new(stmt), pos), stmt => Expr::Stmt(Box::new(stmt), pos),
}, },
Expr::Assignment(id1, expr1, pos1) => match *expr1 { // id = expr
Expr::Assignment(id2, expr2, pos2) => match (*id1, *id2) { Expr::Assignment(id, expr, pos) => match *expr {
(Expr::Variable(var1, _), Expr::Variable(var2, _)) if var1 == var2 => { //id = id2 = expr2
Expr::Assignment(id2, expr2, pos2) => match (*id, *id2) {
// var = var = expr2 -> var = expr2
(Expr::Variable(var, _), Expr::Variable(var2, _)) if var == var2 => {
// Assignment to the same variable - fold // Assignment to the same variable - fold
state.set_dirty(); state.set_dirty();
Expr::Assignment( Expr::Assignment(
Box::new(Expr::Variable(var1, pos1)), Box::new(Expr::Variable(var, pos)),
Box::new(optimize_expr(*expr2, state)), Box::new(optimize_expr(*expr2, state)),
pos1, pos,
) )
} }
// id1 = id2 = expr2
(id1, id2) => Expr::Assignment( (id1, id2) => Expr::Assignment(
Box::new(id1), Box::new(id1),
Box::new(Expr::Assignment( Box::new(Expr::Assignment(
@ -258,19 +328,23 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
Box::new(optimize_expr(*expr2, state)), Box::new(optimize_expr(*expr2, state)),
pos2, pos2,
)), )),
pos1, pos,
), ),
}, },
expr => Expr::Assignment(id1, Box::new(optimize_expr(expr, state)), pos1), // id = expr
expr => Expr::Assignment(id, Box::new(optimize_expr(expr, state)), pos),
}, },
// lhs.rhs
Expr::Dot(lhs, rhs, pos) => Expr::Dot( Expr::Dot(lhs, rhs, pos) => Expr::Dot(
Box::new(optimize_expr(*lhs, state)), Box::new(optimize_expr(*lhs, state)),
Box::new(optimize_expr(*rhs, state)), Box::new(optimize_expr(*rhs, state)),
pos, pos,
), ),
// lhs[rhs]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) { Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
// array[int]
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _)) (Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) => if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) =>
{ {
@ -279,6 +353,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
state.set_dirty(); state.set_dirty();
items.remove(i as usize) items.remove(i as usize)
} }
// string[int]
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _))
if i >= 0 && (i as usize) < s.chars().count() => if i >= 0 && (i as usize) < s.chars().count() =>
{ {
@ -286,14 +361,14 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
state.set_dirty(); state.set_dirty();
Expr::CharConstant(s.chars().nth(i as usize).expect("should get char"), pos) Expr::CharConstant(s.chars().nth(i as usize).expect("should get char"), pos)
} }
// lhs[rhs]
(lhs, rhs) => Expr::Index( (lhs, rhs) => Expr::Index(
Box::new(optimize_expr(lhs, state)), Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)), Box::new(optimize_expr(rhs, state)),
pos, pos,
), ),
}, },
// [ items .. ]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(items, pos) => { Expr::Array(items, pos) => {
let orig_len = items.len(); let orig_len = items.len();
@ -309,80 +384,93 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
Expr::Array(items, pos) Expr::Array(items, pos)
} }
// lhs && rhs
Expr::And(lhs, rhs) => match (*lhs, *rhs) { Expr::And(lhs, rhs) => match (*lhs, *rhs) {
// true && rhs -> rhs
(Expr::True(_), rhs) => { (Expr::True(_), rhs) => {
state.set_dirty(); state.set_dirty();
rhs rhs
} }
// false && rhs -> false
(Expr::False(pos), _) => { (Expr::False(pos), _) => {
state.set_dirty(); state.set_dirty();
Expr::False(pos) Expr::False(pos)
} }
// lhs && true -> lhs
(lhs, Expr::True(_)) => { (lhs, Expr::True(_)) => {
state.set_dirty(); state.set_dirty();
lhs optimize_expr(lhs, state)
} }
// lhs && rhs
(lhs, rhs) => Expr::And( (lhs, rhs) => Expr::And(
Box::new(optimize_expr(lhs, state)), Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)), Box::new(optimize_expr(rhs, state)),
), ),
}, },
// lhs || rhs
Expr::Or(lhs, rhs) => match (*lhs, *rhs) { Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
// false || rhs -> rhs
(Expr::False(_), rhs) => { (Expr::False(_), rhs) => {
state.set_dirty(); state.set_dirty();
rhs rhs
} }
// true || rhs -> true
(Expr::True(pos), _) => { (Expr::True(pos), _) => {
state.set_dirty(); state.set_dirty();
Expr::True(pos) Expr::True(pos)
} }
// lhs || false
(lhs, Expr::False(_)) => { (lhs, Expr::False(_)) => {
state.set_dirty(); state.set_dirty();
lhs optimize_expr(lhs, state)
} }
// lhs || rhs
(lhs, rhs) => Expr::Or( (lhs, rhs) => Expr::Or(
Box::new(optimize_expr(lhs, state)), Box::new(optimize_expr(lhs, state)),
Box::new(optimize_expr(rhs, state)), Box::new(optimize_expr(rhs, state)),
), ),
}, },
// Do not optimize anything within `dump_ast` // Do not optimize anything within built-in function keywords
Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST => { Expr::FunctionCall(id, args, def_value, pos) if SKIP_FUNC_KEYWORDS.contains(&id.as_str())=>
Expr::FunctionCall(id, args, def_value, pos),
// Eagerly call functions
Expr::FunctionCall(id, args, def_value, pos) Expr::FunctionCall(id, args, def_value, pos)
} if state.engine.optimization_level == OptimizationLevel::Full // full optimizations
// Actually call function to optimize it
Expr::FunctionCall(id, args, def_value, pos)
if id != KEYWORD_DEBUG // not debug
&& id != KEYWORD_PRINT // not print
&& state.engine.map(|eng| eng.optimization_level == OptimizationLevel::Full).unwrap_or(false) // full optimizations
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants && args.iter().all(|expr| expr.is_constant()) // all arguments are constants
=> => {
{
let engine = state.engine.expect("engine should be Some");
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
let call_args: FnCallArgs = arg_values.iter_mut().map(Dynamic::as_mut).collect(); let call_args: FnCallArgs = arg_values.iter_mut().map(Dynamic::as_mut).collect();
engine.call_ext_fn_raw(&id, call_args, pos).ok().map(|r|
r.or(def_value.clone()).and_then(|result| map_dynamic_to_expr(result, pos).0) // Save the typename of the first argument if it is `type_of()`
// This is to avoid `call_args` being passed into the closure
let arg_for_type_of = if id == KEYWORD_TYPE_OF && call_args.len() == 1 {
state.engine.map_type_name(call_args[0].type_name())
} else {
""
};
state.engine.call_ext_fn_raw(&id, call_args, pos).ok().map(|r|
r.or_else(|| {
if !arg_for_type_of.is_empty() {
// Handle `type_of()`
Some(arg_for_type_of.to_string().into_dynamic())
} else {
// Otherwise use the default value, if any
def_value.clone()
}
}).and_then(|result| map_dynamic_to_expr(result, pos).0)
.map(|expr| { .map(|expr| {
state.set_dirty(); state.set_dirty();
expr expr
})).flatten() })
.unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos)) ).flatten().unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos))
} }
// Optimize the function call arguments // id(args ..) -> optimize function call arguments
Expr::FunctionCall(id, args, def_value, pos) => { Expr::FunctionCall(id, args, def_value, pos) =>
let orig_len = args.len(); Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos),
// constant-name
let args: Vec<_> = args.into_iter().map(|a| optimize_expr(a, state)).collect();
if orig_len != args.len() {
state.set_dirty();
}
Expr::FunctionCall(id, args, def_value, pos)
}
Expr::Variable(ref name, _) if state.contains_constant(name) => { Expr::Variable(ref name, _) if state.contains_constant(name) => {
state.set_dirty(); state.set_dirty();
@ -392,28 +480,25 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
.expect("should find constant in scope!") .expect("should find constant in scope!")
.clone() .clone()
} }
// All other expressions - skip
expr => expr, expr => expr,
} }
} }
pub(crate) fn optimize<'a>( pub(crate) fn optimize<'a>(statements: Vec<Stmt>, engine: &Engine<'a>, scope: &Scope) -> Vec<Stmt> {
statements: Vec<Stmt>,
engine: Option<&Engine<'a>>,
scope: &Scope,
) -> Vec<Stmt> {
// If optimization level is None then skip optimizing // If optimization level is None then skip optimizing
if engine if engine.optimization_level == OptimizationLevel::None {
.map(|eng| eng.optimization_level == OptimizationLevel::None)
.unwrap_or(false)
{
return statements; return statements;
} }
// Set up the state // Set up the state
let mut state = State::new(); let mut state = State {
state.engine = engine; changed: false,
constants: vec![],
engine,
};
// Add constants from the scope into the state
scope scope
.iter() .iter()
.filter(|ScopeEntry { var_type, expr, .. }| { .filter(|ScopeEntry { var_type, expr, .. }| {
@ -430,9 +515,9 @@ pub(crate) fn optimize<'a>(
let orig_constants_len = state.constants.len(); let orig_constants_len = state.constants.len();
// Optimization loop
let mut result = statements; let mut result = statements;
// Optimization loop
loop { loop {
state.reset(); state.reset();
state.restore_constants(orig_constants_len); state.restore_constants(orig_constants_len);
@ -446,11 +531,11 @@ pub(crate) fn optimize<'a>(
if let Stmt::Const(name, value, _) = &stmt { if let Stmt::Const(name, value, _) = &stmt {
// Load constants // Load constants
state.push_constant(name, value.as_ref().clone()); state.push_constant(name, value.as_ref().clone());
stmt // Keep it in the top scope stmt // Keep it in the global scope
} else { } else {
// Keep all variable declarations at this level // Keep all variable declarations at this level
// and always keep the last return value // and always keep the last return value
let keep = stmt.is_var() || i == num_statements - 1; let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1;
optimize_stmt(stmt, &mut state, keep) optimize_stmt(stmt, &mut state, keep)
} }
@ -465,17 +550,21 @@ pub(crate) fn optimize<'a>(
// Eliminate code that is pure but always keep the last statement // Eliminate code that is pure but always keep the last statement
let last_stmt = result.pop(); let last_stmt = result.pop();
// Remove all pure statements at top level // Remove all pure statements at global level
result.retain(|stmt| !matches!(stmt, Stmt::Expr(expr) if expr.is_pure())); result.retain(|stmt| !stmt.is_pure());
// Add back the last statement unless it is a lone No-op
if let Some(stmt) = last_stmt { if let Some(stmt) = last_stmt {
result.push(stmt); // Add back the last statement if result.len() > 0 || !matches!(stmt, Stmt::Noop(_)) {
result.push(stmt);
}
} }
result result
} }
pub fn optimize_ast( /// Optimize an AST.
pub fn optimize_into_ast(
engine: &Engine, engine: &Engine,
scope: &Scope, scope: &Scope,
statements: Vec<Stmt>, statements: Vec<Stmt>,
@ -484,8 +573,8 @@ pub fn optimize_ast(
AST( AST(
match engine.optimization_level { match engine.optimization_level {
OptimizationLevel::None => statements, OptimizationLevel::None => statements,
OptimizationLevel::Simple => optimize(statements, None, &scope), OptimizationLevel::Simple => optimize(statements, engine, &scope),
OptimizationLevel::Full => optimize(statements, Some(engine), &scope), OptimizationLevel::Full => optimize(statements, engine, &scope),
}, },
functions functions
.into_iter() .into_iter()
@ -494,8 +583,23 @@ pub fn optimize_ast(
OptimizationLevel::None => (), OptimizationLevel::None => (),
OptimizationLevel::Simple | OptimizationLevel::Full => { OptimizationLevel::Simple | OptimizationLevel::Full => {
let pos = fn_def.body.position(); let pos = fn_def.body.position();
let mut body = optimize(vec![fn_def.body], None, &Scope::new());
fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos)); // Optimize the function body
let mut body = optimize(vec![fn_def.body], engine, &Scope::new());
// {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val
Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => {
Stmt::Expr(val)
}
// { return; } -> ()
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
Stmt::Expr(Box::new(Expr::Unit(pos)))
}
// All others
stmt => stmt,
};
} }
} }
Arc::new(fn_def) Arc::new(fn_def)

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ use crate::stdlib::{
string::{String, ToString}, string::{String, ToString},
}; };
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_std"))]
use crate::stdlib::path::PathBuf; use crate::stdlib::path::PathBuf;
/// Evaluation result. /// Evaluation result.
@ -54,7 +54,7 @@ pub enum EvalAltResult {
/// Wrapped value is the type of the actual result. /// Wrapped value is the type of the actual result.
ErrorMismatchOutputType(String, Position), ErrorMismatchOutputType(String, Position),
/// Error reading from a script file. Wrapped value is the path of the script file. /// Error reading from a script file. Wrapped value is the path of the script file.
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_std"))]
ErrorReadingScriptFile(PathBuf, std::io::Error), ErrorReadingScriptFile(PathBuf, std::io::Error),
/// Inappropriate member access. /// Inappropriate member access.
ErrorDotExpr(String, Position), ErrorDotExpr(String, Position),
@ -62,8 +62,8 @@ pub enum EvalAltResult {
ErrorArithmetic(String, Position), ErrorArithmetic(String, Position),
/// Run-time error encountered. Wrapped value is the error message. /// Run-time error encountered. Wrapped value is the error message.
ErrorRuntime(String, Position), ErrorRuntime(String, Position),
/// Internal use: Breaking out of loops. /// Breaking out of loops - not an error if within a loop.
LoopBreak, ErrorLoopBreak(Position),
/// Not an error: Value returned from a script via the `return` keyword. /// Not an error: Value returned from a script via the `return` keyword.
/// Wrapped value is the result value. /// Wrapped value is the result value.
Return(Dynamic, Position), Return(Dynamic, Position),
@ -101,12 +101,12 @@ impl EvalAltResult {
} }
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable", Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect", Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file", Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
Self::ErrorDotExpr(_, _) => "Malformed dot expression", Self::ErrorDotExpr(_, _) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorRuntime(_, _) => "Runtime error", Self::ErrorRuntime(_, _) => "Runtime error",
Self::LoopBreak => "[Not Error] Breaks out of loop", Self::ErrorLoopBreak(_) => "Break statement not inside a loop",
Self::Return(_, _) => "[Not Error] Function returns value", Self::Return(_, _) => "[Not Error] Function returns value",
} }
} }
@ -134,9 +134,9 @@ impl fmt::Display for EvalAltResult {
Self::ErrorRuntime(s, pos) => { Self::ErrorRuntime(s, pos) => {
write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos) write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos)
} }
Self::LoopBreak => write!(f, "{}", desc), Self::ErrorLoopBreak(pos) => write!(f, "{} ({})", desc, pos),
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos), Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(path, err) => { Self::ErrorReadingScriptFile(path, err) => {
write!(f, "{} '{}': {}", desc, path.display(), err) write!(f, "{} '{}': {}", desc, path.display(), err)
} }
@ -209,9 +209,8 @@ impl<T: AsRef<str>> From<T> for EvalAltResult {
impl EvalAltResult { impl EvalAltResult {
pub fn position(&self) -> Position { pub fn position(&self) -> Position {
match self { match self {
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(_, _) => Position::none(), Self::ErrorReadingScriptFile(_, _) => Position::none(),
Self::LoopBreak => Position::none(),
Self::ErrorParsing(err) => err.position(), Self::ErrorParsing(err) => err.position(),
@ -232,15 +231,15 @@ impl EvalAltResult {
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos) | Self::ErrorArithmetic(_, pos)
| Self::ErrorRuntime(_, pos) | Self::ErrorRuntime(_, pos)
| Self::ErrorLoopBreak(pos)
| Self::Return(_, pos) => *pos, | Self::Return(_, pos) => *pos,
} }
} }
pub(crate) fn set_position(&mut self, new_position: Position) { pub(crate) fn set_position(&mut self, new_position: Position) {
match self { match self {
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(_, _) => (), Self::ErrorReadingScriptFile(_, _) => (),
Self::LoopBreak => (),
Self::ErrorParsing(ParseError(_, ref mut pos)) Self::ErrorParsing(ParseError(_, ref mut pos))
| Self::ErrorFunctionNotFound(_, ref mut pos) | Self::ErrorFunctionNotFound(_, ref mut pos)
@ -260,6 +259,7 @@ impl EvalAltResult {
| Self::ErrorDotExpr(_, ref mut pos) | Self::ErrorDotExpr(_, ref mut pos)
| Self::ErrorArithmetic(_, ref mut pos) | Self::ErrorArithmetic(_, ref mut pos)
| Self::ErrorRuntime(_, ref mut pos) | Self::ErrorRuntime(_, ref mut pos)
| Self::ErrorLoopBreak(ref mut pos)
| Self::Return(_, ref mut pos) => *pos = new_position, | Self::Return(_, ref mut pos) => *pos = new_position,
} }
} }

View File

@ -36,7 +36,7 @@ pub struct ScopeEntry<'a> {
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, Scope}; /// use rhai::{Engine, Scope};
/// ///

View File

@ -1,4 +1,6 @@
#[cfg(feature = "no_stdlib")] //! Helper module which defines most of the needed features from `std` for `no-std` builds.
#[cfg(feature = "no_std")]
mod inner { mod inner {
pub use core::{ pub use core::{
any, arch, array, ascii, cell, char, clone, cmp, convert, default, f32, f64, ffi, fmt, any, arch, array, ascii, cell, char, clone, cmp, convert, default, f32, f64, ffi, fmt,
@ -15,7 +17,7 @@ mod inner {
} }
} }
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_std"))]
mod inner { mod inner {
pub use std::*; pub use std::*;
} }

View File

@ -35,7 +35,7 @@ fn test_array_with_structs() -> Result<(), EvalAltResult> {
self.x = new_x; self.x = new_x;
} }
fn new() -> TestStruct { fn new() -> Self {
TestStruct { x: 1 } TestStruct { x: 1 }
} }
} }

View File

@ -38,7 +38,7 @@ fn struct_with_float() -> Result<(), EvalAltResult> {
self.x = new_x; self.x = new_x;
} }
fn new() -> TestStruct { fn new() -> Self {
TestStruct { x: 1.0 } TestStruct { x: 1.0 }
} }
} }

View File

@ -16,7 +16,7 @@ fn test_get_set() -> Result<(), EvalAltResult> {
self.x = new_x; self.x = new_x;
} }
fn new() -> TestStruct { fn new() -> Self {
TestStruct { x: 1 } TestStruct { x: 1 }
} }
} }

View File

@ -1,10 +1,11 @@
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_loop() -> Result<(), EvalAltResult> { fn test_loop() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert!(engine.eval::<bool>( assert_eq!(
engine.eval::<INT>(
r" r"
let x = 0; let x = 0;
let i = 0; let i = 0;
@ -13,15 +14,16 @@ fn test_loop() -> Result<(), EvalAltResult> {
if i < 10 { if i < 10 {
x = x + i; x = x + i;
i = i + 1; i = i + 1;
} } else {
else {
break; break;
} }
} }
x == 45 return x;
" "
)?); )?,
45
);
Ok(()) Ok(())
} }

View File

@ -12,7 +12,7 @@ fn test_method_call() -> Result<(), EvalAltResult> {
self.x += 1000; self.x += 1000;
} }
fn new() -> TestStruct { fn new() -> Self {
TestStruct { x: 1 } TestStruct { x: 1 }
} }
} }

View File

@ -19,7 +19,7 @@ fn test_mismatched_op_custom_type() {
} }
impl TestStruct { impl TestStruct {
fn new() -> TestStruct { fn new() -> Self {
TestStruct { x: 1 } TestStruct { x: 1 }
} }
} }

View File

@ -9,6 +9,6 @@ fn test_throw() {
EvalAltResult::ErrorRuntime(s, _) if s == "hello")); EvalAltResult::ErrorRuntime(s, _) if s == "hello"));
assert!(matches!( assert!(matches!(
engine.eval::<()>(r#"throw;"#).expect_err("expects error"), engine.eval::<()>(r#"throw"#).expect_err("expects error"),
EvalAltResult::ErrorRuntime(s, _) if s == "")); EvalAltResult::ErrorRuntime(s, _) if s == ""));
} }