Merge pull request #108 from schungx/master
Constants and aggressive optimizations.
This commit is contained in:
commit
bffa3ed636
@ -18,14 +18,16 @@ include = [
|
|||||||
num-traits = "*"
|
num-traits = "*"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked"]
|
#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"]
|
||||||
default = []
|
default = [ "optimize_full" ]
|
||||||
debug_msgs = [] # print debug messages on function registrations and calls
|
debug_msgs = [] # print debug messages on function registrations and calls
|
||||||
unchecked = [] # unchecked arithmetic
|
unchecked = [] # unchecked arithmetic
|
||||||
no_stdlib = [] # no standard library of utility functions
|
no_stdlib = [] # no standard library of utility functions
|
||||||
no_index = [] # no arrays and indexing
|
no_index = [] # no arrays and indexing
|
||||||
no_float = [] # no floating-point
|
no_float = [] # no floating-point
|
||||||
no_function = [] # no script-defined functions
|
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_i32 = [] # set INT=i32 (useful for 32-bit systems)
|
||||||
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
|
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
|
||||||
|
|
||||||
|
532
README.md
532
README.md
@ -1,23 +1,26 @@
|
|||||||
Rhai - Embedded Scripting for Rust
|
Rhai - Embedded Scripting for Rust
|
||||||
=================================
|
=================================
|
||||||
|
|
||||||
Rhai is an embedded scripting language for Rust that gives you a safe and easy way to add scripting to your applications.
|
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 feature set:
|
||||||
|
|
||||||
* Easy integration with Rust functions and data types
|
* Easy integration with Rust functions and data types, supporting getter/setter methods
|
||||||
* Fairly efficient (1 mil iterations in 0.75 sec on my 5 year old laptop)
|
* Easily call a script-defined function from Rust
|
||||||
|
* Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop)
|
||||||
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
|
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
|
||||||
* Easy-to-use language similar to JS+Rust
|
* Easy-to-use language similar to JS+Rust
|
||||||
* Support for overloaded functions
|
* Support for overloaded functions
|
||||||
* Very few additional dependencies (right now only [`num-traits`] to do checked arithmetic operations)
|
* Compiled script is optimized for repeat evaluations
|
||||||
|
* Very few additional dependencies (right now only [`num-traits`] to do checked arithmetic operations);
|
||||||
|
For [`no_std`] builds, a number of additional dependencies are pulled in to provide for basic library functionalities.
|
||||||
|
|
||||||
**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.10.2, so the language and API's may change before they stabilize.
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
You can install Rhai using crates by adding this line to your dependencies:
|
Install the Rhai crate by adding this line to `dependencies`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@ -33,7 +36,7 @@ rhai = "*"
|
|||||||
|
|
||||||
to use the latest version.
|
to use the latest version.
|
||||||
|
|
||||||
Beware that in order to use pre-releases (alpha and beta) you need to specify the exact version in your `Cargo.toml`.
|
Beware that in order to use pre-releases (e.g. alpha and beta), the exact version must be specified in the `Cargo.toml`.
|
||||||
|
|
||||||
Optional features
|
Optional features
|
||||||
-----------------
|
-----------------
|
||||||
@ -43,13 +46,14 @@ Optional features
|
|||||||
| `debug_msgs` | Print debug messages to stdout related to function registrations and calls. |
|
| `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 you don't need them. |
|
| `no_function` | Disable script-defined functions if not needed. |
|
||||||
| `no_index` | Disable arrays and indexing features if you don't need them. |
|
| `no_index` | Disable arrays and indexing features if not needed. |
|
||||||
| `no_float` | Disable floating-point numbers and math if you don't need them. |
|
| `no_float` | Disable floating-point numbers and math if not needed. |
|
||||||
|
| `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. |
|
||||||
| `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. |
|
||||||
|
|
||||||
By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here for you to opt-**out** of certain functionalities that you do not need.
|
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.
|
||||||
|
|
||||||
Related
|
Related
|
||||||
@ -57,8 +61,8 @@ Related
|
|||||||
|
|
||||||
Other cool projects to check out:
|
Other cool projects to check out:
|
||||||
|
|
||||||
* [ChaiScript] - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin.
|
* [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin.
|
||||||
* You can also check out the list of [scripting languages for Rust] on [awesome-rust].
|
* Check out the list of [scripting languages for Rust](https://github.com/rust-unofficial/awesome-rust#scripting) on [awesome-rust](https://github.com/rust-unofficial/awesome-rust)
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
@ -66,13 +70,13 @@ 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` | 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` | shows how to register a type and methods for it |
|
||||||
| `hello` | simple example that evaluates an expression and prints the result |
|
| `hello` | 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 |
|
| `reuse_scope` | evaluates two pieces of code in separate runs, but using a common [`Scope`] |
|
||||||
| `rhai_runner` | runs each filename passed to it as a Rhai script |
|
| `rhai_runner` | runs each filename passed to it as a Rhai script |
|
||||||
| `simple_fn` | shows how to register a Rust function to a Rhai engine |
|
| `simple_fn` | shows how to register a Rust function to a Rhai [`Engine`] |
|
||||||
| `repl` | a simple REPL, interactively evaluate statements from stdin |
|
| `repl` | a simple REPL, interactively evaluate statements from stdin |
|
||||||
|
|
||||||
Examples can be run with the following command:
|
Examples can be run with the following command:
|
||||||
@ -107,8 +111,8 @@ There are also a number of examples scripts that showcase Rhai's features, all i
|
|||||||
| `while.rhai` | while loop |
|
| `while.rhai` | while loop |
|
||||||
|
|
||||||
| Example scripts | Description |
|
| Example scripts | Description |
|
||||||
| ----------------- | ----------------------------------------------------------------- |
|
| ----------------- | ---------------------------------------------------------------------------------- |
|
||||||
| `speed_test.rhai` | a simple program to measure the speed of Rhai's interpreter |
|
| `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` | 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:
|
||||||
@ -137,13 +141,24 @@ fn main() -> Result<(), EvalAltResult>
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also evaluate a script file:
|
The type parameter is used to specify the type of the return value, which _must_ match the actual type or an error is returned.
|
||||||
|
Rhai is very strict here. There are two ways to specify the return type - _turbofish_ notation, or type inference.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let result = engine.eval_file::<i64>("hello_world.rhai")?;
|
let result = engine.eval::<i64>("40 + 2")?; // return type is i64, specified using 'turbofish' notation
|
||||||
|
|
||||||
|
let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64
|
||||||
|
|
||||||
|
let result = engine.eval<String>("40 + 2")?; // returns an error because the actual return type is i64, not String
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to repeatedly evaluate a script, you can _compile_ it first into an AST (abstract syntax tree) form:
|
Evaluate a script file directly:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let result = engine.eval_file::<i64>("hello_world.rhai".into())?; // 'eval_file' takes a 'PathBuf'
|
||||||
|
```
|
||||||
|
|
||||||
|
To repeatedly evaluate a script, _compile_ it first into an AST (abstract syntax tree) form:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::Engine;
|
use rhai::Engine;
|
||||||
@ -154,7 +169,7 @@ let mut engine = Engine::new();
|
|||||||
let ast = engine.compile("40 + 2")?;
|
let ast = engine.compile("40 + 2")?;
|
||||||
|
|
||||||
for _ in 0..42 {
|
for _ in 0..42 {
|
||||||
let result = engine.eval_ast::<i64>(&ast)?;
|
let result: i64 = engine.eval_ast(&ast)?;
|
||||||
|
|
||||||
println!("Answer: {}", result); // prints 42
|
println!("Answer: {}", result); // prints 42
|
||||||
}
|
}
|
||||||
@ -167,11 +182,10 @@ use rhai::Engine;
|
|||||||
|
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
let ast = engine.compile_file("hello_world.rhai".into()).unwrap();
|
let ast = engine.compile_file("hello_world.rhai".into())?;
|
||||||
```
|
```
|
||||||
|
|
||||||
Rhai also allows you to work _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust.
|
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - via `call_fn`:
|
||||||
You do this via `call_fn`:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::Engine;
|
use rhai::Engine;
|
||||||
@ -179,8 +193,8 @@ 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(
|
engine.consume(true, // pass true to 'retain_functions' otherwise these functions
|
||||||
r"
|
r" // will be cleared at the end of consume()
|
||||||
fn hello(x, y) { // a function with two parameters: String and i64
|
fn hello(x, y) { // a function with two parameters: String and i64
|
||||||
x.len() + y // returning i64
|
x.len() + y // returning i64
|
||||||
}
|
}
|
||||||
@ -188,12 +202,11 @@ engine.consume(
|
|||||||
fn hello(x) { // functions can be overloaded: this one takes only one parameter
|
fn hello(x) { // functions can be overloaded: this one takes only one parameter
|
||||||
x * 2 // returning i64
|
x * 2 // returning i64
|
||||||
}
|
}
|
||||||
", true)?; // pass true to 'retain_functions' otherwise these functions
|
")?;
|
||||||
// will be cleared at the end of consume()
|
|
||||||
|
|
||||||
// Evaluate the function in the AST, passing arguments into the script as a tuple
|
// Evaluate the function in the AST, passing arguments into the script as a tuple
|
||||||
// if there are more than one. Beware, arguments must be of the correct types because
|
// if there are more than one. Beware, arguments must be of the correct types because
|
||||||
// Rhai does not have built-in type conversions. If you pass in arguments of the wrong type,
|
// Rhai does not have built-in type conversions. If arguments of the wrong types are passed,
|
||||||
// the Engine will not find the function.
|
// the Engine will not find the function.
|
||||||
|
|
||||||
let result: i64 = engine.call_fn("hello", &ast, ( String::from("abc"), 123_i64 ) )?;
|
let result: i64 = engine.call_fn("hello", &ast, ( String::from("abc"), 123_i64 ) )?;
|
||||||
@ -218,19 +231,20 @@ The following primitive types are supported natively:
|
|||||||
| **Dynamic** (i.e. can be anything) | `rhai::Dynamic` |
|
| **Dynamic** (i.e. can be anything) | `rhai::Dynamic` |
|
||||||
| **System** (current configuration) | `rhai::INT` (`i32` or `i64`),<br/>`rhai::FLOAT` (`f32` or `f64`) |
|
| **System** (current configuration) | `rhai::INT` (`i32` or `i64`),<br/>`rhai::FLOAT` (`f32` or `f64`) |
|
||||||
|
|
||||||
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different; you cannot even add them together.
|
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 you do not need any other integer type, you can enable 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 you only need 32-bit integers, you can enable the [`only_i32`] feature and 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 incurs a performance penalty.
|
This is useful on 32-bit systems where using 64-bit integers incur a performance penalty.
|
||||||
|
|
||||||
If you do not need floating-point, enable the [`no_float`] feature to remove support.
|
If no floating-point is needed, use the [`no_float`] feature to remove support.
|
||||||
|
|
||||||
Value conversions
|
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. For other conversions you can register your own conversion functions.
|
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.
|
||||||
|
|
||||||
There is also a `type_of` function to detect the type of a value.
|
There is also a `type_of` function to detect the type of a value.
|
||||||
|
|
||||||
@ -256,7 +270,8 @@ if z.type_of() == "string" {
|
|||||||
Working with functions
|
Working with functions
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
Rhai's scripting engine is very lightweight. It gets its ability from the functions in your program. To call these functions, you need to register them with the scripting engine.
|
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.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult};
|
||||||
@ -294,7 +309,7 @@ fn main() -> Result<(), EvalAltResult>
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To return a `Dynamic` value, simply `Box` it and return it.
|
To return a [`Dynamic`] value, simply `Box` it and return it.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn decide(yes_no: bool) -> Dynamic {
|
fn decide(yes_no: bool) -> Dynamic {
|
||||||
@ -309,7 +324,7 @@ fn decide(yes_no: bool) -> Dynamic {
|
|||||||
Generic functions
|
Generic functions
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Generic functions can be used in Rhai, but you'll need to register separate instances for each concrete type:
|
Generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
@ -330,14 +345,15 @@ fn main()
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also see in this example how you can register multiple functions (or in this case multiple instances of the same function) to the same name in script. This gives you a way to overload functions the correct one, based on the types of the parameters, from your script.
|
This example shows how to register multiple functions (or, in this case, multiple instances of the same function) to the same name in script.
|
||||||
|
This enables function overloading based on the number and types of parameters.
|
||||||
|
|
||||||
Fallible functions
|
Fallible functions
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
If your function is _fallible_ (i.e. it returns a `Result<_, Error>`), you can register it with `register_result_fn` (using the `RegisterResultFn` trait).
|
If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be registered with `register_result_fn` (using the `RegisterResultFn` trait).
|
||||||
|
|
||||||
Your function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements `From<&str>` and `From<String>` etc. and the error text gets converted into `EvalAltResult::ErrorRuntime`.
|
The function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements `From<&str>` and `From<String>` etc. and the error text gets converted into `EvalAltResult::ErrorRuntime`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, EvalAltResult, Position};
|
use rhai::{Engine, EvalAltResult, Position};
|
||||||
@ -421,7 +437,7 @@ fn main() -> Result<(), EvalAltResult>
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
First, for each type we use with the engine, we need to be able to Clone. This allows the engine to pass by value and still keep its own state.
|
All custom types must implement `Clone`. This allows the [`Engine`] to pass by value.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -430,7 +446,7 @@ struct TestStruct {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, we create a few methods that we'll later use in our scripts. Notice that we register our custom type with the engine.
|
Next, we create a few methods that we'll later use in our scripts. Notice that we register our custom type with the [`Engine`].
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
impl TestStruct {
|
impl TestStruct {
|
||||||
@ -448,9 +464,9 @@ let mut engine = Engine::new();
|
|||||||
engine.register_type::<TestStruct>();
|
engine.register_type::<TestStruct>();
|
||||||
```
|
```
|
||||||
|
|
||||||
To use methods and functions with the engine, we need to register them. There are some convenience functions to help with this. Below I register update and new with the engine.
|
To use methods and functions with the [`Engine`], we need to register them. There are some convenience functions to help with this. Below I register update and new with the [`Engine`].
|
||||||
|
|
||||||
*Note: the 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);
|
||||||
@ -486,12 +502,12 @@ let x = new_ts();
|
|||||||
print(x.type_of()); // prints "foo::bar::TestStruct"
|
print(x.type_of()); // prints "foo::bar::TestStruct"
|
||||||
```
|
```
|
||||||
|
|
||||||
If you use `register_type_with_name` to register the custom type with a special pretty-print name, `type_of` will return that instead.
|
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.
|
||||||
|
|
||||||
Getters and setters
|
Getters and setters
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
Similarly, you can work with members of your custom types. This works by registering a 'get' or a 'set' function for working with your struct.
|
Similarly, custom types can expose members by registering a `get` and/or `set` function.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
@ -530,9 +546,11 @@ println!("result: {}", result);
|
|||||||
Initializing and maintaining state
|
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 top-level state. This gives each one a fairly clean starting place. Sometimes, though, you want to continue using the same top-level state from one invocation to the next.
|
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,
|
||||||
|
such a state must be manually created and passed in.
|
||||||
|
|
||||||
In this example, we first create a state with a few initialized variables, then thread the same state through multiple invocations:
|
In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is threaded through multiple invocations:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, Scope, EvalAltResult};
|
use rhai::{Engine, Scope, EvalAltResult};
|
||||||
@ -568,22 +586,25 @@ fn main() -> Result<(), EvalAltResult>
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Rhai Language guide
|
Rhai Language Guide
|
||||||
===================
|
===================
|
||||||
|
|
||||||
Comments
|
Comments
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
Comments are C-style, including '`/*` ... `*/`' pairs and '`//`' for comments to the end of the line.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let /* intruder comment */ name = "Bob";
|
let /* intruder comment */ name = "Bob";
|
||||||
|
|
||||||
// This is a very important comment
|
// This is a very important comment
|
||||||
|
|
||||||
/* This comment spans
|
/* This comment spans
|
||||||
multiple lines, so it
|
multiple lines, so it
|
||||||
only makes sense that
|
only makes sense that
|
||||||
it is even more important */
|
it is even more important */
|
||||||
|
|
||||||
/* Fear not, Rhai satisfies all your nesting
|
/* Fear not, Rhai satisfies all nesting needs with nested comments:
|
||||||
needs with nested comments:
|
|
||||||
/*/*/*/*/**/*/*/*/*/
|
/*/*/*/*/**/*/*/*/*/
|
||||||
*/
|
*/
|
||||||
```
|
```
|
||||||
@ -591,28 +612,85 @@ let /* intruder comment */ name = "Bob";
|
|||||||
Variables
|
Variables
|
||||||
---------
|
---------
|
||||||
|
|
||||||
Variables in Rhai follow normal 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.
|
||||||
|
Therefore, names like '`_`', '`_42`' etc. are not legal variable names. Variable names are also case _sensitive_.
|
||||||
|
|
||||||
|
Variables are defined using the `let` keyword.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let x = 3;
|
let x = 3; // ok
|
||||||
|
let _x = 42; // ok
|
||||||
|
let x_ = 42; // also ok
|
||||||
|
let _x_ = 42; // still ok
|
||||||
|
|
||||||
|
let _ = 123; // syntax error - illegal variable name
|
||||||
|
let _9 = 9; // syntax error - illegal variable name
|
||||||
|
|
||||||
|
let x = 42; // variable is 'x', lower case
|
||||||
|
let X = 123; // variable is 'X', upper case
|
||||||
|
x == 42;
|
||||||
|
X == 123;
|
||||||
|
```
|
||||||
|
|
||||||
|
Constants
|
||||||
|
---------
|
||||||
|
|
||||||
|
Constants can be defined using the `const` keyword and are immutable. Constants follow the same naming rules as [variables](#variables).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const x = 42;
|
||||||
|
print(x * 2); // prints 84
|
||||||
|
x = 123; // syntax error - cannot assign to constant
|
||||||
|
```
|
||||||
|
|
||||||
|
Constants must be assigned a _value_ not an expression.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const x = 40 + 2; // syntax error - cannot assign expression to constant
|
||||||
```
|
```
|
||||||
|
|
||||||
Numbers
|
Numbers
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
Integer numbers follow C-style format with support for decimal, binary ('`0b`'), octal ('`0o`') and hex ('`0x`') notations.
|
||||||
|
|
||||||
|
The default system integer type (also aliased to `INT`) is `i64`. It can be turned into `i32` via the [`only_i32`] feature.
|
||||||
|
|
||||||
|
Floating-point numbers are also supported if not disabled with [`no_float`]. The default system floating-point type is `i64` (also aliased to `FLOAT`).
|
||||||
|
|
||||||
|
'`_`' separators can be added freely and are ignored within a number.
|
||||||
|
|
||||||
| Format | Type |
|
| Format | Type |
|
||||||
| ---------------- | ---------------------------------------------- |
|
| ---------------- | ---------------- |
|
||||||
| `123_345`, `-42` | `i64` in decimal, '`_`' separators are ignored |
|
| `123_345`, `-42` | `i64` in decimal |
|
||||||
| `0o07_76` | `i64` in octal, '`_`' separators are ignored |
|
| `0o07_76` | `i64` in octal |
|
||||||
| `0xabcd_ef` | `i64` in hex, '`_`' separators are ignored |
|
| `0xabcd_ef` | `i64` in hex |
|
||||||
| `0b0101_1001` | `i64` in binary, '`_`' separators are ignored |
|
| `0b0101_1001` | `i64` in binary |
|
||||||
| `123_456.789` | `f64`, '`_`' separators are ignored |
|
| `123_456.789` | `f64` |
|
||||||
|
|
||||||
Numeric operators
|
Numeric operators
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
Numeric operators generally follow C styles.
|
||||||
|
|
||||||
|
| Operator | Description | Integers only |
|
||||||
|
| -------- | ----------------------------------------------------------- | :-----------: |
|
||||||
|
| `+` | Plus | |
|
||||||
|
| `-` | Minus | |
|
||||||
|
| `*` | Multiply | |
|
||||||
|
| `/` | Divide (C-style integer division if acted on integer types) | |
|
||||||
|
| `%` | Modulo (remainder) | |
|
||||||
|
| `~` | Power | |
|
||||||
|
| `&` | Binary _And_ bit-mask | Yes |
|
||||||
|
| `|` | Binary _Or_ bit-mask | Yes |
|
||||||
|
| `^` | Binary _Xor_ bit-mask | Yes |
|
||||||
|
| `<<` | Left bit-shift | Yes |
|
||||||
|
| `>>` | Right bit-shift | Yes |
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let x = (1 + 2) * (6 - 4) / 2; // arithmetic
|
let x = (1 + 2) * (6 - 4) / 2; // arithmetic, with parentheses
|
||||||
let reminder = 42 % 10; // modulo
|
let reminder = 42 % 10; // modulo
|
||||||
let power = 42 ~ 2; // power (i64 and f64 only)
|
let power = 42 ~ 2; // power (i64 and f64 only)
|
||||||
let left_shifted = 42 << 3; // left shift
|
let left_shifted = 42 << 3; // left shift
|
||||||
@ -623,10 +701,14 @@ let bit_op = 42 | 99; // bit masking
|
|||||||
Unary operators
|
Unary operators
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
| Operator | Description |
|
||||||
|
| -------- | ----------- |
|
||||||
|
| `+` | Plus |
|
||||||
|
| `-` | Negative |
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let number = -5;
|
let number = -5;
|
||||||
number = -5 - +5;
|
number = -5 - +5;
|
||||||
let boolean = !true;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Numeric functions
|
Numeric functions
|
||||||
@ -658,6 +740,17 @@ The following standard functions (defined in the standard library but excluded i
|
|||||||
Strings and Chars
|
Strings and Chars
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
String and char literals follow C-style formatting, with support for Unicode ('`\u`') and hex ('`\x`') escape sequences.
|
||||||
|
|
||||||
|
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).
|
||||||
|
Individual characters within a Rhai string can be replaced. In Rhai, there is no separate concepts of `String` and `&str` as in Rust.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
`type_of()` a string returns `"string"`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let name = "Bob";
|
let name = "Bob";
|
||||||
let middle_initial = 'C';
|
let middle_initial = 'C';
|
||||||
@ -671,27 +764,28 @@ let age = 42;
|
|||||||
let record = full_name + ": age " + age;
|
let record = full_name + ": age " + age;
|
||||||
record == "Bob C. Davis: age 42";
|
record == "Bob C. Davis: age 42";
|
||||||
|
|
||||||
// Strings can be indexed to get a character
|
// Unlike Rust, Rhai strings can be indexed to get a character
|
||||||
// (disabled with the 'no_index' feature)
|
// (disabled with 'no_index')
|
||||||
let c = record[4];
|
let c = record[4];
|
||||||
c == 'C';
|
c == 'C';
|
||||||
|
|
||||||
ts.s = record;
|
ts.s = record; // custom type properties can take strings
|
||||||
|
|
||||||
let c = ts.s[4];
|
let c = ts.s[4];
|
||||||
c == 'C';
|
c == 'C';
|
||||||
|
|
||||||
let c = "foo"[0];
|
let c = "foo"[0]; // indexing also works on string literals...
|
||||||
c == 'f';
|
c == 'f';
|
||||||
|
|
||||||
let c = ("foo" + "bar")[5];
|
let c = ("foo" + "bar")[5]; // ... and expressions returning strings
|
||||||
c == 'r';
|
c == 'r';
|
||||||
|
|
||||||
// Escape sequences in strings
|
// Escape sequences in strings
|
||||||
record += " \u2764\n"; // escape sequence of '❤' in Unicode
|
record += " \u2764\n"; // escape sequence of '❤' in Unicode
|
||||||
record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line
|
record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line
|
||||||
|
|
||||||
// Unlike Rust, Rhai strings can be modified
|
// Unlike Rust, Rhai strings can be directly modified character-by-character
|
||||||
|
// (disabled with 'no_index')
|
||||||
record[4] = '\x58'; // 0x58 = 'X'
|
record[4] = '\x58'; // 0x58 = 'X'
|
||||||
record == "Bob X. Davis: age 42 ❤\n";
|
record == "Bob X. Davis: age 42 ❤\n";
|
||||||
```
|
```
|
||||||
@ -741,7 +835,12 @@ full_name.len() == 0;
|
|||||||
Arrays
|
Arrays
|
||||||
------
|
------
|
||||||
|
|
||||||
You can create arrays of values, and then access them with numeric 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 '`,`'.
|
||||||
|
|
||||||
|
The type of a Rhai array is `rhai::Array`. `type_of()` returns `"array"`.
|
||||||
|
|
||||||
|
Arrays are disabled via the [`no_index`] feature.
|
||||||
|
|
||||||
The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on arrays:
|
The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on arrays:
|
||||||
|
|
||||||
@ -758,7 +857,7 @@ The following functions (defined in the standard library but excluded if [`no_st
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let y = [1, 2, 3]; // 3 elements
|
let y = [1, 2, 3]; // array literal with 3 elements
|
||||||
y[1] = 42;
|
y[1] = 42;
|
||||||
|
|
||||||
print(y[1]); // prints 42
|
print(y[1]); // prints 42
|
||||||
@ -770,7 +869,9 @@ foo == 42;
|
|||||||
let foo = [1, 2, 3][0];
|
let foo = [1, 2, 3][0];
|
||||||
foo == 1;
|
foo == 1;
|
||||||
|
|
||||||
fn abc() { [42, 43, 44] }
|
fn abc() {
|
||||||
|
[42, 43, 44] // a function returning an array literal
|
||||||
|
}
|
||||||
|
|
||||||
let foo = abc()[0];
|
let foo = abc()[0];
|
||||||
foo == 42;
|
foo == 42;
|
||||||
@ -804,8 +905,7 @@ y.clear(); // empty the array
|
|||||||
print(y.len()); // prints 0
|
print(y.len()); // prints 0
|
||||||
```
|
```
|
||||||
|
|
||||||
`push` and `pad` are only defined for standard built-in types. If you want to use them with
|
`push` and `pad` are only defined for standard built-in types. For custom types, type-specific versions must be registered:
|
||||||
your own custom type, you need to register a type-specific version:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
engine.register_fn("push",
|
engine.register_fn("push",
|
||||||
@ -813,27 +913,45 @@ engine.register_fn("push",
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
The type of a Rhai array is `rhai::Array`. `type_of()` returns `"array"`.
|
|
||||||
|
|
||||||
Arrays are disabled via the [`no_index`] feature.
|
|
||||||
|
|
||||||
Comparison operators
|
Comparison operators
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
You can compare most values of the same data type. If you compare two values of _different_ data types, the result is always `false`.
|
Comparing most values of the same data type work out-of-the-box for standard types supported by the system.
|
||||||
|
|
||||||
|
However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system
|
||||||
|
types - `INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
42 == 42; // true
|
42 == 42; // true
|
||||||
42 > 42; // false
|
42 > 42; // false
|
||||||
"hello" > "foo"; // true
|
"hello" > "foo"; // true
|
||||||
"42" == 42; // false
|
"42" == 42; // false
|
||||||
|
```
|
||||||
|
|
||||||
|
Comparing two values of _different_ data types, or of unknown data types, always results in `false`.
|
||||||
|
|
||||||
|
```rust
|
||||||
42 == 42.0; // false - i64 is different from f64
|
42 == 42.0; // false - i64 is different from f64
|
||||||
|
42 > "42"; // false - i64 is different from string
|
||||||
|
42 <= "42"; // false again
|
||||||
|
|
||||||
|
let ts = new_ts(); // custom type
|
||||||
|
ts == 42; // false - types are not the same
|
||||||
```
|
```
|
||||||
|
|
||||||
Boolean operators
|
Boolean operators
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Double boolean operators `&&` and `||` _short-circuit_, meaning that the second operand will not be evaluated if the first one already proves the condition wrong.
|
| Operator | Description |
|
||||||
|
| -------- | ------------------------------- |
|
||||||
|
| `!` | Boolean _Not_ |
|
||||||
|
| `&&` | Boolean _And_ (short-circuits) |
|
||||||
|
| `||` | Boolean _Or_ (short-circuits) |
|
||||||
|
| `&` | Boolean _And_ (full evaluation) |
|
||||||
|
| `|` | Boolean _Or_ (full evaluation) |
|
||||||
|
|
||||||
|
Double boolean operators `&&` and `||` _short-circuit_, meaning that the second operand will not be evaluated
|
||||||
|
if the first one already proves the condition wrong.
|
||||||
|
|
||||||
Single boolean operators `&` and `|` always evaluate both operands.
|
Single boolean operators `&` and `|` always evaluate both operands.
|
||||||
|
|
||||||
@ -869,34 +987,45 @@ my_str += 12345;
|
|||||||
my_str == "abcABC12345"
|
my_str == "abcABC12345"
|
||||||
```
|
```
|
||||||
|
|
||||||
If
|
If statements
|
||||||
--
|
-------------
|
||||||
|
|
||||||
|
All branches of an `if` statement must be enclosed within braces '`{`' .. '`}`', even when there is only one statement.
|
||||||
|
|
||||||
|
Like Rust, there is no ambiguity regarding which `if` clause a statement belongs to.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
if true {
|
if true {
|
||||||
print("It's true!");
|
print("It's true!");
|
||||||
} else if true {
|
} else if true {
|
||||||
print("It's true again!");
|
print("It's true again!");
|
||||||
|
} else if ... {
|
||||||
|
:
|
||||||
|
} else if ... {
|
||||||
|
:
|
||||||
} else {
|
} else {
|
||||||
print("It's false!");
|
print("It's finally false!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (decision) print("I've decided!");
|
||||||
|
// ^ syntax error, expecting '{' in statement block
|
||||||
```
|
```
|
||||||
|
|
||||||
While
|
While loops
|
||||||
-----
|
-----------
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let x = 10;
|
let x = 10;
|
||||||
|
|
||||||
while x > 0 {
|
while x > 0 {
|
||||||
print(x);
|
print(x);
|
||||||
if x == 5 { break; }
|
if x == 5 { break; } // break out of while loop
|
||||||
x = x - 1;
|
x = x - 1;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Loop
|
Infinite loops
|
||||||
----
|
--------------
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let x = 10;
|
let x = 10;
|
||||||
@ -904,12 +1033,14 @@ let x = 10;
|
|||||||
loop {
|
loop {
|
||||||
print(x);
|
print(x);
|
||||||
x = x - 1;
|
x = x - 1;
|
||||||
if x == 0 { break; }
|
if x == 0 { break; } // break out of loop
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For
|
For loops
|
||||||
---
|
---------
|
||||||
|
|
||||||
|
Iterating through a range or an array is provided by the `for` ... `in` loop.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let array = [1, 3, 5, 7, 9, 42];
|
let array = [1, 3, 5, 7, 9, 42];
|
||||||
@ -927,32 +1058,34 @@ for x in range(0, 50) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Return
|
Returning values
|
||||||
------
|
----------------
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
return; // equivalent to return ();
|
return; // equivalent to return ();
|
||||||
|
|
||||||
return 123 + 456;
|
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.
|
||||||
|
To deliberately return an error during an evaluation, use the `throw` keyword.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
if some_bad_condition_has_happened {
|
if some_bad_condition_has_happened {
|
||||||
throw error; // 'throw' takes a string to form the exception text
|
throw error; // 'throw' takes a string to form the exception text
|
||||||
}
|
}
|
||||||
|
|
||||||
throw; // no exception text
|
throw; // empty exception text: ""
|
||||||
```
|
```
|
||||||
|
|
||||||
All of `Engine`'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult` holding error information.
|
Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(`_reason_`, `_position_`))`
|
||||||
|
with the exception text captured by the first parameter.
|
||||||
Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(reason, position))` with the exception text captured by the `reason` parameter.
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let result = engine.eval::<i64>(&mut scope, r#"
|
let result = engine.eval::<i64>(r#"
|
||||||
let x = 42;
|
let x = 42;
|
||||||
|
|
||||||
if x > 0 {
|
if x > 0 {
|
||||||
@ -976,34 +1109,44 @@ fn add(x, y) {
|
|||||||
print(add(2, 3));
|
print(add(2, 3));
|
||||||
```
|
```
|
||||||
|
|
||||||
Just like in Rust, you can also use an implicit return.
|
Just like in Rust, an implicit return can be used. In fact, the last statement of a block is _always_ the block's return value
|
||||||
|
regardless of whether it is terminated with a semicolon `;`. This is different from Rust.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn add(x, y) {
|
fn add(x, y) {
|
||||||
x + y
|
x + y; // value of the last statement is used as the function's return value
|
||||||
}
|
}
|
||||||
|
|
||||||
print(add(2, 3));
|
fn add2(x) {
|
||||||
|
return x + 2; // explicit return
|
||||||
|
}
|
||||||
|
|
||||||
|
print(add(2, 3)); // prints 5
|
||||||
|
print(add2(42)); // prints 44
|
||||||
```
|
```
|
||||||
|
|
||||||
Functions defined in script always take `Dynamic` parameters (i.e. the parameter can be of any type).
|
### Passing arguments by value
|
||||||
It is important to remember that all parameters are passed by _value_, so all functions are _pure_ (i.e. they never modify their parameters).
|
|
||||||
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if you are not careful.
|
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
|
||||||
|
It is important to remember that all arguments are passed by _value_, so all functions are _pure_ (i.e. they never modify their arguments).
|
||||||
|
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if not careful.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn change(s) {
|
fn change(s) { // 's' is passed by value
|
||||||
s = 42; // only a COPY of 'x' is changed
|
s = 42; // only a COPY of 's' is changed
|
||||||
}
|
}
|
||||||
|
|
||||||
let x = 500;
|
let x = 500;
|
||||||
x.change();
|
x.change(); // desugars to change(x)
|
||||||
x == 500; // 'x' is NOT changed!
|
x == 500; // 'x' is NOT changed!
|
||||||
```
|
```
|
||||||
|
|
||||||
Functions can only be defined at the top level, never inside a block or another function.
|
### Global definitions only
|
||||||
|
|
||||||
|
Functions can only be defined at the global level, never inside a block or another function.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Top level is OK
|
// Global level is OK
|
||||||
fn add(x, y) {
|
fn add(x, y) {
|
||||||
x + y
|
x + y
|
||||||
}
|
}
|
||||||
@ -1018,7 +1161,9 @@ fn do_addition(x) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Functions can be _overloaded_ based on the number of parameters (but not parameter types, since all parameters are `Dynamic`).
|
### Functions overloading
|
||||||
|
|
||||||
|
Functions can be _overloaded_ based on the _number_ of parameters (but not parameter _types_, since all parameters are the same type - [`Dynamic`]).
|
||||||
New definitions of the same name and number of parameters overwrite previous definitions.
|
New definitions of the same name and number of parameters overwrite previous definitions.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -1037,15 +1182,19 @@ 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:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let a = new_ts();
|
let a = new_ts(); // constructor function
|
||||||
a.x = 500;
|
a.x = 500; // property access
|
||||||
a.update();
|
a.update(); // method call
|
||||||
```
|
```
|
||||||
|
|
||||||
`print` and `debug`
|
`print` and `debug`
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
The `print` and `debug` functions default to printing to `stdout`, with `debug` using standard debug formatting.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
print("hello"); // prints hello to stdout
|
print("hello"); // prints hello to stdout
|
||||||
print(1 + 2 + 3); // prints 6 to stdout
|
print(1 + 2 + 3); // prints 6 to stdout
|
||||||
@ -1055,6 +1204,9 @@ debug("world!"); // prints "world!" to stdout using debug formatting
|
|||||||
|
|
||||||
### Overriding `print` and `debug` with callback functions
|
### Overriding `print` and `debug` with callback functions
|
||||||
|
|
||||||
|
When embedding Rhai into an application, it is usually necessary to trap `print` and `debug` output
|
||||||
|
(for logging into a tracking log, for example).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Any function or closure that takes an &str argument can be used to override
|
// Any function or closure that takes an &str argument can be used to override
|
||||||
// print and debug
|
// print and debug
|
||||||
@ -1077,10 +1229,11 @@ for entry in log {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Optimizations
|
Script optimization
|
||||||
=============
|
===================
|
||||||
|
|
||||||
Rhai includes an _optimizer_ that tries to optimize a script after parsing. This can reduce resource utilization and increase execution speed.
|
Rhai includes an _optimizer_ that tries to optimize a script after parsing. This can reduce resource utilization and increase execution speed.
|
||||||
|
Script optimization can be turned off via the [`no_optimize`] feature.
|
||||||
|
|
||||||
For example, in the following:
|
For example, in the following:
|
||||||
|
|
||||||
@ -1108,47 +1261,132 @@ The above script optimizes to:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Constant propagation is used to remove dead code:
|
Constants propagation is used to remove dead code:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
const ABC = true;
|
||||||
|
if ABC || some_work() { print("done!"); } // 'ABC' is constant so it is replaced by 'true'...
|
||||||
if true || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called
|
if true || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called
|
||||||
if true { print("done!"); } // <-- the line above is equivalent to this
|
if true { print("done!"); } // <- the line above is equivalent to this
|
||||||
print("done!"); // <-- the line above is further simplified to this
|
print("done!"); // <- the line above is further simplified to this
|
||||||
// because the condition is always true
|
// because the condition is always true
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
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.
|
||||||
|
|
||||||
Beware, however, that most operators are actually function calls, and those functions can be overridden, so they are not optimized away:
|
Beware, however, that most operators are actually function calls, and those functions can be overridden,
|
||||||
|
so they are not optimized away:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
if 1 == 1 { ... } // '1==1' is NOT optimized away because you can define
|
const DECISION = 1;
|
||||||
// your own '==' function to override the built-in default!
|
|
||||||
|
if DECISION == 1 { // NOT optimized away because you can define
|
||||||
|
: // your own '==' function to override the built-in default!
|
||||||
|
:
|
||||||
|
} else if DECISION == 2 { // same here, NOT optimized away
|
||||||
|
:
|
||||||
|
} else if DECISION == 3 { // same here, NOT optimized away
|
||||||
|
:
|
||||||
|
} else {
|
||||||
|
:
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Here be dragons!
|
because no operator functions will be run (in order not to trigger side effects) during the optimization process
|
||||||
|
(unless the optimization level is set to [`OptimizationLevel::Full`]). So, instead, do this:
|
||||||
Some optimizations can be quite aggressive and can alter subtle semantics of the script. For example:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
if true { // <-- condition always true
|
const DECISION_1 = true;
|
||||||
123.456; // <-- eliminated
|
const DECISION_2 = false;
|
||||||
hello; // <-- eliminated, EVEN THOUGH the variable doesn't exist!
|
const DECISION_3 = false;
|
||||||
foo(42) // <-- promoted up-level
|
|
||||||
|
if DECISION_1 {
|
||||||
|
: // this branch is kept and promoted to the parent level
|
||||||
|
} else if DECISION_2 {
|
||||||
|
: // this branch is eliminated
|
||||||
|
} else if DECISION_3 {
|
||||||
|
: // this branch is eliminated
|
||||||
|
} else {
|
||||||
|
: // this branch is eliminated
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In general, boolean constants are most effective for the optimizer to automatically prune
|
||||||
|
large `if`-`else` branches because they do not depend on operators.
|
||||||
|
|
||||||
|
Alternatively, turn the optimizer to [`OptimizationLevel::Full`]
|
||||||
|
|
||||||
|
Here be dragons!
|
||||||
|
----------------
|
||||||
|
|
||||||
|
### Optimization levels
|
||||||
|
|
||||||
|
There are actually three levels of optimizations: `None`, `Simple` and `Full`.
|
||||||
|
|
||||||
|
* `None` is obvious - no optimization on the AST is performed.
|
||||||
|
|
||||||
|
* `Simple` (default) performs relatively _safe_ optimizations without causing side effects
|
||||||
|
(i.e. it only relies on static analysis and will not actually perform any function calls).
|
||||||
|
|
||||||
|
* `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.
|
||||||
|
|
||||||
|
An engine's optimization level is set via a call to `set_optimization_level`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Turn on aggressive optimizations
|
||||||
|
engine.set_optimization_level(rhai::OptimizationLevel::Full);
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// When compiling the following with OptimizationLevel::Full...
|
||||||
|
|
||||||
|
const DECISION = 1;
|
||||||
|
// this condition is now eliminated because 'DECISION == 1'
|
||||||
|
if DECISION == 1 { // is a function call to the '==' function, and it returns 'true'
|
||||||
|
print("hello!"); // this block is promoted to the parent level
|
||||||
|
} else {
|
||||||
|
print("boo!"); // this block is eliminated because it is never reached
|
||||||
}
|
}
|
||||||
|
|
||||||
// The above optimizes to:
|
print("hello!"); // <- the above is equivalent to this
|
||||||
|
|
||||||
foo(42)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Nevertheless, if you would be evaluating the original script, it would have been an error - the variable `hello` doesn't exist, so the script would have been terminated at that point with an error return.
|
### Side effect considerations
|
||||||
|
|
||||||
In fact, any errors inside a statement that has been eliminated will silently _go away_:
|
All built-in operators have _pure_ functions (i.e. they do not cause side effects) so using [`OptimizationLevel::Full`] is usually quite safe.
|
||||||
|
Beware, however, that if custom functions are registered, they'll also be called.
|
||||||
|
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
|
||||||
|
until _after_ the compilation process.
|
||||||
|
|
||||||
|
### Subtle semantic changes
|
||||||
|
|
||||||
|
Some optimizations can alter subtle semantics of the script. For example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if true { // condition always true
|
||||||
|
123.456; // eliminated
|
||||||
|
hello; // eliminated, EVEN THOUGH the variable doesn't exist!
|
||||||
|
foo(42) // promoted up-level
|
||||||
|
}
|
||||||
|
|
||||||
|
foo(42) // <- the above optimizes to this
|
||||||
|
```
|
||||||
|
|
||||||
|
Nevertheless, if the original script were evaluated instead, it would have been an error - the variable `hello` doesn't exist,
|
||||||
|
so the script would have been terminated at that point with an error return.
|
||||||
|
|
||||||
|
In fact, any errors inside a statement that has been eliminated will silently _disappear_:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
print("start!");
|
print("start!");
|
||||||
if my_decision { /* do nothing... */ } // <-- eliminated due to no effect
|
if my_decision { /* do nothing... */ } // eliminated due to no effect
|
||||||
print("end!");
|
print("end!");
|
||||||
|
|
||||||
// The above optimizes to:
|
// The above optimizes to:
|
||||||
@ -1158,21 +1396,22 @@ print("end!");
|
|||||||
```
|
```
|
||||||
|
|
||||||
In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to a type error.
|
In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to a type error.
|
||||||
However, after optimization, the entire `if` statement is removed, thus the script silently runs to completion without errors.
|
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.
|
||||||
|
|
||||||
|
### 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),
|
||||||
there is a setting in `Engine` to turn off optimizations.
|
turn it off by setting the optimization level to `OptimizationLevel::None`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let engine = rhai::Engine::new();
|
let engine = rhai::Engine::new();
|
||||||
engine.set_optimization(false); // turn off the optimizer
|
|
||||||
|
// Turn off the optimizer
|
||||||
|
engine.set_optimization_level(rhai::OptimizationLevel::None);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
[ChaiScript]: http://chaiscript.com/
|
|
||||||
[scripting languages for Rust]: https://github.com/rust-unofficial/awesome-rust#scripting
|
|
||||||
[awesome-rust]: https://github.com/rust-unofficial/awesome-rust
|
|
||||||
|
|
||||||
[`num-traits`]: https://crates.io/crates/num-traits/
|
[`num-traits`]: https://crates.io/crates/num-traits/
|
||||||
[`debug_msgs`]: #optional-features
|
[`debug_msgs`]: #optional-features
|
||||||
[`unchecked`]: #optional-features
|
[`unchecked`]: #optional-features
|
||||||
@ -1180,5 +1419,12 @@ engine.set_optimization(false); // turn off the optimizer
|
|||||||
[`no_index`]: #optional-features
|
[`no_index`]: #optional-features
|
||||||
[`no_float`]: #optional-features
|
[`no_float`]: #optional-features
|
||||||
[`no_function`]: #optional-features
|
[`no_function`]: #optional-features
|
||||||
|
[`no_optimize`]: #optional-features
|
||||||
[`only_i32`]: #optional-features
|
[`only_i32`]: #optional-features
|
||||||
[`only_i64`]: #optional-features
|
[`only_i64`]: #optional-features
|
||||||
|
|
||||||
|
[`Engine`]: #hello-world
|
||||||
|
[`Scope`]: #initializing-and-maintaining-state
|
||||||
|
[`Dynamic`]: #values-and-types
|
||||||
|
|
||||||
|
[`OptimizationLevel::Full`]: #optimization-levels
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
use rhai::{Engine, EvalAltResult, Scope, AST};
|
use rhai::{Engine, EvalAltResult, Scope, AST};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
use rhai::OptimizationLevel;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
io::{stdin, stdout, Write},
|
io::{stdin, stdout, Write},
|
||||||
iter,
|
iter,
|
||||||
@ -43,6 +47,10 @@ fn print_error(input: &str, err: EvalAltResult) {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
|
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
@ -62,10 +70,11 @@ fn main() {
|
|||||||
match input.as_str().trim() {
|
match input.as_str().trim() {
|
||||||
"exit" | "quit" => break, // quit
|
"exit" | "quit" => break, // quit
|
||||||
"ast" => {
|
"ast" => {
|
||||||
|
if matches!(&ast, Some(_)) {
|
||||||
// print the last AST
|
// print the last AST
|
||||||
match &ast {
|
println!("{:#?}", ast.as_ref().unwrap());
|
||||||
Some(ast) => println!("{:#?}", ast),
|
} else {
|
||||||
None => println!("()"),
|
println!("()");
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -73,7 +82,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = engine
|
if let Err(err) = engine
|
||||||
.compile(&input)
|
.compile_with_scope(&scope, &input)
|
||||||
.map_err(EvalAltResult::ErrorParsing)
|
.map_err(EvalAltResult::ErrorParsing)
|
||||||
.and_then(|r| {
|
.and_then(|r| {
|
||||||
ast = Some(r);
|
ast = Some(r);
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
use rhai::OptimizationLevel;
|
||||||
|
|
||||||
use std::{env, fs::File, io::Read, iter, process::exit};
|
use std::{env, fs::File, io::Read, iter, process::exit};
|
||||||
|
|
||||||
fn padding(pad: &str, len: usize) -> String {
|
fn padding(pad: &str, len: usize) -> String {
|
||||||
@ -49,6 +53,9 @@ fn main() {
|
|||||||
for filename in env::args().skip(1) {
|
for filename in env::args().skip(1) {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
|
|
||||||
let mut f = match File::open(&filename) {
|
let mut f = match File::open(&filename) {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Error reading script file: {}\n{}", filename, err);
|
eprintln!("Error reading script file: {}\n{}", filename, err);
|
||||||
@ -67,7 +74,7 @@ fn main() {
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = engine.consume(&contents, false) {
|
if let Err(err) = engine.consume(false, &contents) {
|
||||||
eprintln!("{}", padding("=", filename.len()));
|
eprintln!("{}", padding("=", filename.len()));
|
||||||
eprintln!("{}", filename);
|
eprintln!("{}", filename);
|
||||||
eprintln!("{}", padding("=", filename.len()));
|
eprintln!("{}", padding("=", filename.len()));
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
// This is a script to calculate prime numbers.
|
// This is a script to calculate prime numbers.
|
||||||
|
|
||||||
let MAX_NUMBER_TO_CHECK = 10000; // 1229 primes
|
const MAX_NUMBER_TO_CHECK = 10000; // 1229 primes
|
||||||
|
|
||||||
let prime_mask = [];
|
let prime_mask = [];
|
||||||
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
|
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
|
||||||
|
|
||||||
prime_mask[0] = false;
|
prime_mask[0] = prime_mask[1] = false;
|
||||||
prime_mask[1] = false;
|
|
||||||
|
|
||||||
let total_primes_found = 0;
|
let total_primes_found = 0;
|
||||||
|
|
||||||
|
116
src/api.rs
116
src/api.rs
@ -8,6 +8,10 @@ use crate::fn_register::RegisterFn;
|
|||||||
use crate::parser::{lex, parse, FnDef, Position, AST};
|
use crate::parser::{lex, parse, FnDef, Position, AST};
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
use crate::optimize::optimize_ast;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
any::{type_name, TypeId},
|
any::{type_name, TypeId},
|
||||||
fs::File,
|
fs::File,
|
||||||
@ -101,8 +105,14 @@ impl<'e> Engine<'e> {
|
|||||||
|
|
||||||
/// Compile a string into an AST.
|
/// Compile a string into an AST.
|
||||||
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile a string into an AST using own scope.
|
||||||
|
/// The scope is useful for passing constants into the script for optimization.
|
||||||
|
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.optimize)
|
parse(&mut tokens_stream.peekable(), self, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_file(path: PathBuf) -> Result<String, EvalAltResult> {
|
fn read_file(path: PathBuf) -> Result<String, EvalAltResult> {
|
||||||
@ -118,8 +128,20 @@ impl<'e> Engine<'e> {
|
|||||||
|
|
||||||
/// Compile a file into an AST.
|
/// Compile a file into an AST.
|
||||||
pub fn compile_file(&self, path: PathBuf) -> Result<AST, EvalAltResult> {
|
pub fn compile_file(&self, path: PathBuf) -> Result<AST, EvalAltResult> {
|
||||||
Self::read_file(path)
|
self.compile_file_with_scope(&Scope::new(), path)
|
||||||
.and_then(|contents| self.compile(&contents).map_err(|err| err.into()))
|
}
|
||||||
|
|
||||||
|
/// Compile a file into an AST using own scope.
|
||||||
|
/// The scope is useful for passing constants into the script for optimization.
|
||||||
|
pub fn compile_file_with_scope(
|
||||||
|
&self,
|
||||||
|
scope: &Scope,
|
||||||
|
path: PathBuf,
|
||||||
|
) -> Result<AST, EvalAltResult> {
|
||||||
|
Self::read_file(path).and_then(|contents| {
|
||||||
|
self.compile_with_scope(scope, &contents)
|
||||||
|
.map_err(|err| err.into())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a file.
|
/// Evaluate a file.
|
||||||
@ -127,6 +149,15 @@ impl<'e> Engine<'e> {
|
|||||||
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.
|
||||||
|
pub fn eval_file_with_scope<T: Any + Clone>(
|
||||||
|
&mut self,
|
||||||
|
scope: &mut Scope,
|
||||||
|
path: PathBuf,
|
||||||
|
) -> Result<T, EvalAltResult> {
|
||||||
|
Self::read_file(path).and_then(|contents| self.eval_with_scope::<T>(scope, &contents))
|
||||||
|
}
|
||||||
|
|
||||||
/// Evaluate a string.
|
/// Evaluate a string.
|
||||||
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();
|
||||||
@ -162,23 +193,21 @@ impl<'e> Engine<'e> {
|
|||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
engine.clear_functions();
|
engine.clear_functions();
|
||||||
|
|
||||||
#[cfg(feature = "no_function")]
|
|
||||||
let AST(statements) = ast;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
let statements = {
|
let statements = {
|
||||||
let AST(statements, functions) = ast;
|
let AST(statements, functions) = ast;
|
||||||
engine.load_script_functions(functions);
|
engine.load_script_functions(functions);
|
||||||
statements
|
statements
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = statements
|
let mut result = ().into_dynamic();
|
||||||
.iter()
|
|
||||||
.try_fold(().into_dynamic(), |_, stmt| engine.eval_stmt(scope, stmt));
|
for stmt in statements {
|
||||||
|
result = engine.eval_stmt(scope, stmt)?;
|
||||||
|
}
|
||||||
|
|
||||||
engine.clear_functions();
|
engine.clear_functions();
|
||||||
|
|
||||||
result
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
match eval_ast_internal(self, scope, ast) {
|
match eval_ast_internal(self, scope, ast) {
|
||||||
@ -207,10 +236,25 @@ impl<'e> Engine<'e> {
|
|||||||
/// and not cleared from run to run.
|
/// and not cleared from run to run.
|
||||||
pub fn consume_file(
|
pub fn consume_file(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: PathBuf,
|
|
||||||
retain_functions: bool,
|
retain_functions: bool,
|
||||||
|
path: PathBuf,
|
||||||
) -> Result<(), EvalAltResult> {
|
) -> Result<(), EvalAltResult> {
|
||||||
Self::read_file(path).and_then(|contents| self.consume(&contents, retain_functions))
|
Self::read_file(path).and_then(|contents| self.consume(retain_functions, &contents))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||||
|
/// and not cleared from run to run.
|
||||||
|
pub fn consume_file_with_scope(
|
||||||
|
&mut self,
|
||||||
|
scope: &mut Scope,
|
||||||
|
retain_functions: bool,
|
||||||
|
path: PathBuf,
|
||||||
|
) -> Result<(), EvalAltResult> {
|
||||||
|
Self::read_file(path)
|
||||||
|
.and_then(|contents| self.consume_with_scope(scope, retain_functions, &contents))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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).
|
||||||
@ -218,11 +262,11 @@ impl<'e> Engine<'e> {
|
|||||||
///
|
///
|
||||||
/// 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, input: &str, retain_functions: bool) -> 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a string, 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_
|
||||||
@ -235,7 +279,7 @@ impl<'e> Engine<'e> {
|
|||||||
) -> Result<(), EvalAltResult> {
|
) -> Result<(), EvalAltResult> {
|
||||||
let tokens_stream = lex(input);
|
let tokens_stream = lex(input);
|
||||||
|
|
||||||
let ast = parse(&mut tokens_stream.peekable(), self.optimize)
|
let ast = parse(&mut tokens_stream.peekable(), self, scope)
|
||||||
.map_err(EvalAltResult::ErrorParsing)?;
|
.map_err(EvalAltResult::ErrorParsing)?;
|
||||||
|
|
||||||
self.consume_ast_with_scope(scope, retain_functions, &ast)
|
self.consume_ast_with_scope(scope, retain_functions, &ast)
|
||||||
@ -246,6 +290,15 @@ impl<'e> Engine<'e> {
|
|||||||
///
|
///
|
||||||
/// 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> {
|
||||||
|
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).
|
||||||
|
/// 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_
|
||||||
|
/// 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,
|
||||||
@ -256,10 +309,6 @@ impl<'e> Engine<'e> {
|
|||||||
self.clear_functions();
|
self.clear_functions();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "no_function")]
|
|
||||||
let AST(statements) = ast;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
let statements = {
|
let statements = {
|
||||||
let AST(ref statements, ref functions) = ast;
|
let AST(ref statements, ref functions) = ast;
|
||||||
self.load_script_functions(functions);
|
self.load_script_functions(functions);
|
||||||
@ -307,7 +356,7 @@ impl<'e> Engine<'e> {
|
|||||||
///
|
///
|
||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// engine.consume("fn add(x, y) { x.len() + y }", true)?;
|
/// engine.consume(true, "fn add(x, y) { x.len() + y }")?;
|
||||||
///
|
///
|
||||||
/// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?;
|
/// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?;
|
||||||
///
|
///
|
||||||
@ -345,6 +394,27 @@ impl<'e> Engine<'e> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Optimize the AST with constants defined in an external Scope.
|
||||||
|
/// 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
|
||||||
|
/// _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
|
||||||
|
/// of the new constants.
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
|
/// (i.e. with `scope.push_constant(...)`). Then, the AST is cloned and the copy re-optimized before running.
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
pub fn optimize_ast(&self, scope: &Scope, ast: &AST) -> AST {
|
||||||
|
optimize_ast(
|
||||||
|
self,
|
||||||
|
scope,
|
||||||
|
ast.0.clone(),
|
||||||
|
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
|
||||||
@ -359,7 +429,7 @@ impl<'e> Engine<'e> {
|
|||||||
///
|
///
|
||||||
/// // Override action of 'print' function
|
/// // Override action of 'print' function
|
||||||
/// engine.on_print(|s| result.push_str(s));
|
/// engine.on_print(|s| result.push_str(s));
|
||||||
/// engine.consume("print(40 + 2);", false)?;
|
/// engine.consume(false, "print(40 + 2);")?;
|
||||||
/// }
|
/// }
|
||||||
/// assert_eq!(result, "42");
|
/// assert_eq!(result, "42");
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
@ -383,7 +453,7 @@ impl<'e> Engine<'e> {
|
|||||||
///
|
///
|
||||||
/// // Override action of 'debug' function
|
/// // Override action of 'debug' function
|
||||||
/// engine.on_debug(|s| result.push_str(s));
|
/// engine.on_debug(|s| result.push_str(s));
|
||||||
/// engine.consume(r#"debug("hello");"#, false)?;
|
/// engine.consume(false, r#"debug("hello");"#)?;
|
||||||
/// }
|
/// }
|
||||||
/// assert_eq!(result, "\"hello\"");
|
/// assert_eq!(result, "\"hello\"");
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
|
@ -8,7 +8,9 @@ use crate::engine::Engine;
|
|||||||
use crate::fn_register::{RegisterFn, RegisterResultFn};
|
use crate::fn_register::{RegisterFn, RegisterResultFn};
|
||||||
use crate::parser::{Position, INT};
|
use crate::parser::{Position, INT};
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::FLOAT;
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
use crate::parser::FLOAT;
|
||||||
|
|
||||||
use num_traits::{
|
use num_traits::{
|
||||||
identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl,
|
identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl,
|
||||||
@ -578,7 +580,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn range<T>(from: T, to: T) -> Range<T> {
|
fn range<T>(from: T, to: T) -> Range<T> {
|
||||||
(from..to)
|
from..to
|
||||||
}
|
}
|
||||||
|
|
||||||
reg_iterator::<INT>(self);
|
reg_iterator::<INT>(self);
|
||||||
@ -771,9 +773,12 @@ impl Engine<'_> {
|
|||||||
self.register_dynamic_fn("pop", |list: &mut Array| {
|
self.register_dynamic_fn("pop", |list: &mut Array| {
|
||||||
list.pop().unwrap_or_else(|| ().into_dynamic())
|
list.pop().unwrap_or_else(|| ().into_dynamic())
|
||||||
});
|
});
|
||||||
self.register_dynamic_fn("shift", |list: &mut Array| match list.len() {
|
self.register_dynamic_fn("shift", |list: &mut Array| {
|
||||||
0 => ().into_dynamic(),
|
if !list.is_empty() {
|
||||||
_ => list.remove(0),
|
().into_dynamic()
|
||||||
|
} else {
|
||||||
|
list.remove(0)
|
||||||
|
}
|
||||||
});
|
});
|
||||||
self.register_fn("len", |list: &mut Array| list.len() as INT);
|
self.register_fn("len", |list: &mut Array| list.len() as INT);
|
||||||
self.register_fn("clear", |list: &mut Array| list.clear());
|
self.register_fn("clear", |list: &mut Array| list.clear());
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
//! Helper module which defines `FnArgs` to make function calling easier.
|
//! Helper module which defines `FnArgs` to make function calling easier.
|
||||||
|
|
||||||
use crate::any::{Any, Dynamic};
|
use crate::any::{Any, Dynamic};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
use crate::engine::Array;
|
use crate::engine::Array;
|
||||||
|
|
||||||
/// Trait that represent arguments to a function call.
|
/// Trait that represent arguments to a function call.
|
||||||
|
249
src/engine.rs
249
src/engine.rs
@ -3,7 +3,10 @@
|
|||||||
use crate::any::{Any, AnyExt, Dynamic, Variant};
|
use crate::any::{Any, AnyExt, Dynamic, Variant};
|
||||||
use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt};
|
use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt};
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::Scope;
|
use crate::scope::{Scope, VariableType};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
use crate::optimize::OptimizationLevel;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
use crate::INT;
|
use crate::INT;
|
||||||
@ -63,17 +66,20 @@ pub struct FnSpec<'a> {
|
|||||||
/// ```
|
/// ```
|
||||||
pub struct Engine<'e> {
|
pub struct Engine<'e> {
|
||||||
/// Optimize the AST after compilation
|
/// Optimize the AST after compilation
|
||||||
pub(crate) optimize: bool,
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
pub(crate) optimization_level: OptimizationLevel,
|
||||||
/// A hashmap containing all compiled functions known to the engine
|
/// A hashmap containing all compiled functions known to the engine
|
||||||
pub(crate) ext_functions: HashMap<FnSpec<'e>, Box<FnAny>>,
|
pub(crate) ext_functions: HashMap<FnSpec<'e>, Box<FnAny>>,
|
||||||
/// A hashmap containing all script-defined functions
|
/// A hashmap containing all script-defined functions
|
||||||
pub(crate) script_functions: Vec<Arc<FnDef>>,
|
pub(crate) script_functions: Vec<Arc<FnDef>>,
|
||||||
/// A hashmap containing all iterators known to the engine
|
/// A hashmap containing all iterators known to the engine
|
||||||
pub(crate) type_iterators: HashMap<TypeId, Box<IteratorFn>>,
|
pub(crate) type_iterators: HashMap<TypeId, Box<IteratorFn>>,
|
||||||
|
/// A hashmap mapping type names to pretty-print names
|
||||||
pub(crate) type_names: HashMap<String, String>,
|
pub(crate) type_names: HashMap<String, String>,
|
||||||
|
|
||||||
// Closures for implementing the print/debug commands
|
/// Closure for implementing the print commands
|
||||||
pub(crate) on_print: Box<dyn FnMut(&str) + 'e>,
|
pub(crate) on_print: Box<dyn FnMut(&str) + 'e>,
|
||||||
|
/// Closure for implementing the debug commands
|
||||||
pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>,
|
pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,13 +99,20 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
// Create the new scripting Engine
|
// Create the new scripting Engine
|
||||||
let mut engine = Engine {
|
let mut engine = Engine {
|
||||||
optimize: true,
|
|
||||||
ext_functions: HashMap::new(),
|
ext_functions: HashMap::new(),
|
||||||
script_functions: Vec::new(),
|
script_functions: Vec::new(),
|
||||||
type_iterators: HashMap::new(),
|
type_iterators: HashMap::new(),
|
||||||
type_names,
|
type_names,
|
||||||
on_print: Box::new(default_print), // default print/debug implementations
|
on_print: Box::new(default_print), // default print/debug implementations
|
||||||
on_debug: Box::new(default_print),
|
on_debug: Box::new(default_print),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
#[cfg(not(feature = "optimize_full"))]
|
||||||
|
optimization_level: OptimizationLevel::Simple,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
#[cfg(feature = "optimize_full")]
|
||||||
|
optimization_level: OptimizationLevel::Full,
|
||||||
};
|
};
|
||||||
|
|
||||||
engine.register_core_lib();
|
engine.register_core_lib();
|
||||||
@ -110,9 +123,32 @@ impl Engine<'_> {
|
|||||||
engine
|
engine
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Control whether the `Engine` will optimize an AST after compilation
|
/// Control whether and how the `Engine` will optimize an AST after compilation
|
||||||
pub fn set_optimization(&mut self, optimize: bool) {
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
self.optimize = optimize
|
pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) {
|
||||||
|
self.optimization_level = optimization_level
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a registered function
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
pub(crate) fn call_ext_fn_raw(
|
||||||
|
&self,
|
||||||
|
fn_name: &str,
|
||||||
|
args: FnCallArgs,
|
||||||
|
pos: Position,
|
||||||
|
) -> Result<Option<Dynamic>, EvalAltResult> {
|
||||||
|
let spec = FnSpec {
|
||||||
|
name: fn_name.into(),
|
||||||
|
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Search built-in's and external functions
|
||||||
|
if let Some(func) = self.ext_functions.get(&spec) {
|
||||||
|
// Run external function
|
||||||
|
Ok(Some(func(args, pos)?))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Universal method for calling functions, that are either
|
/// Universal method for calling functions, that are either
|
||||||
@ -148,12 +184,13 @@ impl Engine<'_> {
|
|||||||
fn_def
|
fn_def
|
||||||
.params
|
.params
|
||||||
.iter()
|
.iter()
|
||||||
.zip(args.iter().map(|x| (*x).into_dynamic())),
|
.zip(args.iter().map(|x| (*x).into_dynamic()))
|
||||||
|
.map(|(name, value)| (name, VariableType::Normal, value)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Evaluate
|
// Evaluate
|
||||||
return match self.eval_stmt(&mut scope, &fn_def.body) {
|
|
||||||
// Convert return statement to return value
|
// Convert return statement to return value
|
||||||
|
return match self.eval_stmt(&mut scope, &fn_def.body) {
|
||||||
Err(EvalAltResult::Return(x, _)) => Ok(x),
|
Err(EvalAltResult::Return(x, _)) => Ok(x),
|
||||||
other => other,
|
other => other,
|
||||||
};
|
};
|
||||||
@ -164,13 +201,13 @@ impl Engine<'_> {
|
|||||||
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()),
|
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Then search built-in's and external functions
|
// Search built-in's and external functions
|
||||||
if let Some(func) = self.ext_functions.get(&spec) {
|
if let Some(func) = self.ext_functions.get(&spec) {
|
||||||
// Run external function
|
// Run external function
|
||||||
let result = func(args, pos)?;
|
let result = func(args, pos)?;
|
||||||
|
|
||||||
// See if the function match print/debug (which requires special processing)
|
// See if the function match print/debug (which requires special processing)
|
||||||
let callback = match spec.name.as_ref() {
|
let callback = match fn_name {
|
||||||
KEYWORD_PRINT => self.on_print.as_mut(),
|
KEYWORD_PRINT => self.on_print.as_mut(),
|
||||||
KEYWORD_DEBUG => self.on_debug.as_mut(),
|
KEYWORD_DEBUG => self.on_debug.as_mut(),
|
||||||
_ => return Ok(result),
|
_ => return Ok(result),
|
||||||
@ -184,7 +221,7 @@ impl Engine<'_> {
|
|||||||
return Ok(callback(val).into_dynamic());
|
return Ok(callback(val).into_dynamic());
|
||||||
}
|
}
|
||||||
|
|
||||||
if spec.name == KEYWORD_TYPE_OF && args.len() == 1 {
|
if fn_name == KEYWORD_TYPE_OF && args.len() == 1 {
|
||||||
// Handle `type_of` function
|
// Handle `type_of` function
|
||||||
return Ok(self
|
return Ok(self
|
||||||
.map_type_name(args[0].type_name())
|
.map_type_name(args[0].type_name())
|
||||||
@ -192,23 +229,23 @@ impl Engine<'_> {
|
|||||||
.into_dynamic());
|
.into_dynamic());
|
||||||
}
|
}
|
||||||
|
|
||||||
if spec.name.starts_with(FUNC_GETTER) {
|
if fn_name.starts_with(FUNC_GETTER) {
|
||||||
// Getter function not found
|
// Getter function not found
|
||||||
return Err(EvalAltResult::ErrorDotExpr(
|
return Err(EvalAltResult::ErrorDotExpr(
|
||||||
format!(
|
format!(
|
||||||
"- property '{}' unknown or write-only",
|
"- property '{}' unknown or write-only",
|
||||||
&spec.name[FUNC_GETTER.len()..]
|
&fn_name[FUNC_GETTER.len()..]
|
||||||
),
|
),
|
||||||
pos,
|
pos,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if spec.name.starts_with(FUNC_SETTER) {
|
if fn_name.starts_with(FUNC_SETTER) {
|
||||||
// Setter function not found
|
// Setter function not found
|
||||||
return Err(EvalAltResult::ErrorDotExpr(
|
return Err(EvalAltResult::ErrorDotExpr(
|
||||||
format!(
|
format!(
|
||||||
"- property '{}' unknown or read-only",
|
"- property '{}' unknown or read-only",
|
||||||
&spec.name[FUNC_SETTER.len()..]
|
&fn_name[FUNC_SETTER.len()..]
|
||||||
),
|
),
|
||||||
pos,
|
pos,
|
||||||
));
|
));
|
||||||
@ -227,7 +264,7 @@ impl Engine<'_> {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Err(EvalAltResult::ErrorFunctionNotFound(
|
Err(EvalAltResult::ErrorFunctionNotFound(
|
||||||
format!("{} ({})", spec.name, types_list.join(", ")),
|
format!("{} ({})", fn_name, types_list.join(", ")),
|
||||||
pos,
|
pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -248,14 +285,14 @@ impl Engine<'_> {
|
|||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
let args = once(this_ptr)
|
let args = once(this_ptr)
|
||||||
.chain(values.iter_mut().map(|b| b.as_mut()))
|
.chain(values.iter_mut().map(Dynamic::as_mut))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
self.call_fn_raw(fn_name, args, def_val.as_ref(), *pos)
|
self.call_fn_raw(fn_name, args, def_val.as_ref(), *pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
// xxx.id
|
// xxx.id
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
|
|
||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||||
@ -266,7 +303,7 @@ impl Engine<'_> {
|
|||||||
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
||||||
let (val, _) = match idx_lhs.as_ref() {
|
let (val, _) = match idx_lhs.as_ref() {
|
||||||
// xxx.id[idx_expr]
|
// xxx.id[idx_expr]
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
(
|
(
|
||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
|
||||||
@ -294,7 +331,7 @@ impl Engine<'_> {
|
|||||||
// xxx.dot_lhs.rhs
|
// xxx.dot_lhs.rhs
|
||||||
Expr::Dot(dot_lhs, rhs, _) => match dot_lhs.as_ref() {
|
Expr::Dot(dot_lhs, rhs, _) => match dot_lhs.as_ref() {
|
||||||
// xxx.id.rhs
|
// xxx.id.rhs
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
|
|
||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||||
@ -305,7 +342,7 @@ impl Engine<'_> {
|
|||||||
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
||||||
let (val, _) = match idx_lhs.as_ref() {
|
let (val, _) = match idx_lhs.as_ref() {
|
||||||
// xxx.id[idx_expr].rhs
|
// xxx.id[idx_expr].rhs
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
(
|
(
|
||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?,
|
||||||
@ -353,8 +390,8 @@ impl Engine<'_> {
|
|||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
match dot_lhs {
|
match dot_lhs {
|
||||||
// id.???
|
// id.???
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Variable(id, pos) => {
|
||||||
let (src_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
let (src_idx, _, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
||||||
let val = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs);
|
let val = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs);
|
||||||
|
|
||||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||||
@ -371,7 +408,15 @@ impl Engine<'_> {
|
|||||||
let val = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs);
|
let val = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs);
|
||||||
|
|
||||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||||
if let Some((id, src_idx)) = src {
|
if let Some((id, var_type, src_idx)) = src {
|
||||||
|
match var_type {
|
||||||
|
VariableType::Constant => {
|
||||||
|
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||||
|
id.to_string(),
|
||||||
|
idx_lhs.position(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
VariableType::Normal => {
|
||||||
Self::update_indexed_var_in_scope(
|
Self::update_indexed_var_in_scope(
|
||||||
src_type,
|
src_type,
|
||||||
scope,
|
scope,
|
||||||
@ -379,9 +424,11 @@ impl Engine<'_> {
|
|||||||
src_idx,
|
src_idx,
|
||||||
idx,
|
idx,
|
||||||
target,
|
target,
|
||||||
idx_lhs.position(),
|
dot_rhs.position(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val
|
val
|
||||||
}
|
}
|
||||||
@ -400,11 +447,11 @@ impl Engine<'_> {
|
|||||||
id: &str,
|
id: &str,
|
||||||
map: impl FnOnce(Dynamic) -> Result<T, EvalAltResult>,
|
map: impl FnOnce(Dynamic) -> Result<T, EvalAltResult>,
|
||||||
begin: Position,
|
begin: Position,
|
||||||
) -> Result<(usize, T), EvalAltResult> {
|
) -> Result<(usize, VariableType, T), EvalAltResult> {
|
||||||
scope
|
scope
|
||||||
.get(id)
|
.get(id)
|
||||||
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin))
|
.ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin))
|
||||||
.and_then(move |(idx, _, val)| map(val).map(|v| (idx, v)))
|
.and_then(move |(idx, _, var_type, val)| map(val).map(|v| (idx, var_type, v)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate the value of an index (must evaluate to INT)
|
/// Evaluate the value of an index (must evaluate to INT)
|
||||||
@ -476,19 +523,32 @@ impl Engine<'_> {
|
|||||||
lhs: &'a Expr,
|
lhs: &'a Expr,
|
||||||
idx_expr: &Expr,
|
idx_expr: &Expr,
|
||||||
idx_pos: Position,
|
idx_pos: Position,
|
||||||
) -> Result<(IndexSourceType, Option<(&'a str, usize)>, usize, Dynamic), EvalAltResult> {
|
) -> Result<
|
||||||
|
(
|
||||||
|
IndexSourceType,
|
||||||
|
Option<(&'a str, VariableType, usize)>,
|
||||||
|
usize,
|
||||||
|
Dynamic,
|
||||||
|
),
|
||||||
|
EvalAltResult,
|
||||||
|
> {
|
||||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||||
|
|
||||||
match lhs {
|
match lhs {
|
||||||
// id[idx_expr]
|
// id[idx_expr]
|
||||||
Expr::Identifier(id, _) => Self::search_scope(
|
Expr::Variable(id, _) => Self::search_scope(
|
||||||
scope,
|
scope,
|
||||||
&id,
|
&id,
|
||||||
|val| self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos),
|
|val| self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos),
|
||||||
lhs.position(),
|
lhs.position(),
|
||||||
)
|
)
|
||||||
.map(|(src_idx, (val, src_type))| {
|
.map(|(src_idx, var_type, (val, src_type))| {
|
||||||
(src_type, Some((id.as_str(), src_idx)), idx as usize, val)
|
(
|
||||||
|
src_type,
|
||||||
|
Some((id.as_str(), var_type, src_idx)),
|
||||||
|
idx as usize,
|
||||||
|
val,
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// (expr)[idx_expr]
|
// (expr)[idx_expr]
|
||||||
@ -543,8 +603,7 @@ impl Engine<'_> {
|
|||||||
Ok(Self::str_replace_char(s, idx as usize, ch).into_dynamic())
|
Ok(Self::str_replace_char(s, idx as usize, ch).into_dynamic())
|
||||||
}
|
}
|
||||||
|
|
||||||
// All other variable types should be an error
|
IndexSourceType::Expression => panic!("expression cannot be indexed for update"),
|
||||||
_ => panic!("array or string source type expected for indexing"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -585,7 +644,7 @@ impl Engine<'_> {
|
|||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
match dot_rhs {
|
match dot_rhs {
|
||||||
// xxx.id
|
// xxx.id
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
||||||
|
|
||||||
self.call_fn_raw(&set_fn_name, vec![this_ptr, new_val.as_mut()], None, *pos)
|
self.call_fn_raw(&set_fn_name, vec![this_ptr, new_val.as_mut()], None, *pos)
|
||||||
@ -596,7 +655,7 @@ impl Engine<'_> {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
|
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
|
||||||
// xxx.id[idx_expr]
|
// xxx.id[idx_expr]
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
|
|
||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||||
@ -620,7 +679,7 @@ impl Engine<'_> {
|
|||||||
// xxx.lhs.{...}
|
// xxx.lhs.{...}
|
||||||
Expr::Dot(lhs, rhs, _) => match lhs.as_ref() {
|
Expr::Dot(lhs, rhs, _) => match lhs.as_ref() {
|
||||||
// xxx.id.rhs
|
// xxx.id.rhs
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
|
|
||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||||
@ -640,7 +699,7 @@ impl Engine<'_> {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
|
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
|
||||||
// xxx.id[idx_expr].rhs
|
// xxx.id[idx_expr].rhs
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Property(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
|
|
||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||||
@ -702,11 +761,23 @@ impl Engine<'_> {
|
|||||||
dot_rhs: &Expr,
|
dot_rhs: &Expr,
|
||||||
new_val: Dynamic,
|
new_val: Dynamic,
|
||||||
val_pos: Position,
|
val_pos: Position,
|
||||||
|
op_pos: Position,
|
||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
match dot_lhs {
|
match dot_lhs {
|
||||||
// id.???
|
// id.???
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Variable(id, pos) => {
|
||||||
let (src_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
let (src_idx, var_type, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
||||||
|
|
||||||
|
match var_type {
|
||||||
|
VariableType::Constant => {
|
||||||
|
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||||
|
id.to_string(),
|
||||||
|
op_pos,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
let val =
|
let val =
|
||||||
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos);
|
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos);
|
||||||
|
|
||||||
@ -726,17 +797,21 @@ impl Engine<'_> {
|
|||||||
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos);
|
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos);
|
||||||
|
|
||||||
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
|
||||||
if let Some((id, src_idx)) = src {
|
if let Some((id, var_type, src_idx)) = src {
|
||||||
Self::update_indexed_var_in_scope(
|
match var_type {
|
||||||
src_type,
|
VariableType::Constant => {
|
||||||
scope,
|
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||||
id,
|
id.to_string(),
|
||||||
src_idx,
|
|
||||||
idx,
|
|
||||||
target,
|
|
||||||
lhs.position(),
|
lhs.position(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
VariableType::Normal => {
|
||||||
|
Self::update_indexed_var_in_scope(
|
||||||
|
src_type, scope, id, src_idx, idx, target, val_pos,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val
|
val
|
||||||
}
|
}
|
||||||
@ -758,9 +833,10 @@ impl Engine<'_> {
|
|||||||
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
|
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
|
||||||
Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
|
Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
|
||||||
Expr::CharConstant(c, _) => Ok(c.into_dynamic()),
|
Expr::CharConstant(c, _) => Ok(c.into_dynamic()),
|
||||||
Expr::Identifier(id, pos) => {
|
Expr::Variable(id, pos) => {
|
||||||
Self::search_scope(scope, id, Ok, *pos).map(|(_, val)| val)
|
Self::search_scope(scope, id, Ok, *pos).map(|(_, _, val)| val)
|
||||||
}
|
}
|
||||||
|
Expr::Property(_, _) => panic!("unexpected property."),
|
||||||
|
|
||||||
// lhs[idx_expr]
|
// lhs[idx_expr]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -768,26 +844,25 @@ impl Engine<'_> {
|
|||||||
.eval_index_expr(scope, lhs, idx_expr, *idx_pos)
|
.eval_index_expr(scope, lhs, idx_expr, *idx_pos)
|
||||||
.map(|(_, _, _, x)| x),
|
.map(|(_, _, _, x)| x),
|
||||||
|
|
||||||
#[cfg(feature = "no_index")]
|
|
||||||
Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
|
|
||||||
|
|
||||||
// Statement block
|
// Statement block
|
||||||
Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt),
|
Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt),
|
||||||
|
|
||||||
// lhs = rhs
|
// lhs = rhs
|
||||||
Expr::Assignment(lhs, rhs, _) => {
|
Expr::Assignment(lhs, rhs, op_pos) => {
|
||||||
let rhs_val = self.eval_expr(scope, rhs)?;
|
let rhs_val = self.eval_expr(scope, rhs)?;
|
||||||
|
|
||||||
match lhs.as_ref() {
|
match lhs.as_ref() {
|
||||||
// name = rhs
|
// name = rhs
|
||||||
Expr::Identifier(name, pos) => {
|
Expr::Variable(name, pos) => match scope.get(name) {
|
||||||
if let Some((idx, _, _)) = scope.get(name) {
|
Some((idx, _, VariableType::Normal, _)) => {
|
||||||
*scope.get_mut(name, idx) = rhs_val;
|
*scope.get_mut(name, idx) = rhs_val.clone();
|
||||||
Ok(().into_dynamic())
|
Ok(rhs_val)
|
||||||
} else {
|
|
||||||
Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Some((_, _, VariableType::Constant, _)) => Err(
|
||||||
|
EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos),
|
||||||
|
),
|
||||||
|
_ => Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos)),
|
||||||
|
},
|
||||||
|
|
||||||
// idx_lhs[idx_expr] = rhs
|
// idx_lhs[idx_expr] = rhs
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -795,8 +870,15 @@ impl Engine<'_> {
|
|||||||
let (src_type, src, idx, _) =
|
let (src_type, src, idx, _) =
|
||||||
self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?;
|
self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?;
|
||||||
|
|
||||||
if let Some((id, src_idx)) = src {
|
if let Some((id, var_type, src_idx)) = src {
|
||||||
Ok(Self::update_indexed_var_in_scope(
|
match var_type {
|
||||||
|
VariableType::Constant => {
|
||||||
|
return Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||||
|
id.to_string(),
|
||||||
|
idx_lhs.position(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
VariableType::Normal => Ok(Self::update_indexed_var_in_scope(
|
||||||
src_type,
|
src_type,
|
||||||
scope,
|
scope,
|
||||||
&id,
|
&id,
|
||||||
@ -804,7 +886,8 @@ impl Engine<'_> {
|
|||||||
idx,
|
idx,
|
||||||
rhs_val,
|
rhs_val,
|
||||||
rhs.position(),
|
rhs.position(),
|
||||||
)?)
|
)?),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(EvalAltResult::ErrorAssignmentToUnknownLHS(
|
Err(EvalAltResult::ErrorAssignmentToUnknownLHS(
|
||||||
idx_lhs.position(),
|
idx_lhs.position(),
|
||||||
@ -814,9 +897,15 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
// dot_lhs.dot_rhs = rhs
|
// dot_lhs.dot_rhs = rhs
|
||||||
Expr::Dot(dot_lhs, dot_rhs, _) => {
|
Expr::Dot(dot_lhs, dot_rhs, _) => {
|
||||||
self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val, rhs.position())
|
self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val, rhs.position(), *op_pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Error assignment to constant
|
||||||
|
expr if expr.is_constant() => Err(EvalAltResult::ErrorAssignmentToConstant(
|
||||||
|
expr.get_constant_str(),
|
||||||
|
lhs.position(),
|
||||||
|
)),
|
||||||
|
|
||||||
// Syntax error
|
// Syntax error
|
||||||
_ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(lhs.position())),
|
_ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(lhs.position())),
|
||||||
}
|
}
|
||||||
@ -834,8 +923,6 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
Ok(Box::new(arr))
|
Ok(Box::new(arr))
|
||||||
}
|
}
|
||||||
#[cfg(feature = "no_index")]
|
|
||||||
Expr::Array(_, _) => panic!("encountered an array during no_index!"),
|
|
||||||
|
|
||||||
// Dump AST
|
// Dump AST
|
||||||
Expr::FunctionCall(fn_name, args_expr_list, _, pos) if fn_name == KEYWORD_DUMP_AST => {
|
Expr::FunctionCall(fn_name, args_expr_list, _, pos) if fn_name == KEYWORD_DUMP_AST => {
|
||||||
@ -925,7 +1012,16 @@ impl Engine<'_> {
|
|||||||
Stmt::Noop(_) => Ok(().into_dynamic()),
|
Stmt::Noop(_) => Ok(().into_dynamic()),
|
||||||
|
|
||||||
// Expression as statement
|
// Expression as statement
|
||||||
Stmt::Expr(expr) => self.eval_expr(scope, expr),
|
Stmt::Expr(expr) => {
|
||||||
|
let result = self.eval_expr(scope, expr)?;
|
||||||
|
|
||||||
|
Ok(if !matches!(expr.as_ref(), Expr::Assignment(_, _, _)) {
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
// If it is an assignment, erase the result at the root
|
||||||
|
().into_dynamic()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Block scope
|
// Block scope
|
||||||
Stmt::Block(block, _) => {
|
Stmt::Block(block, _) => {
|
||||||
@ -967,9 +1063,9 @@ impl Engine<'_> {
|
|||||||
Ok(guard_val) => {
|
Ok(guard_val) => {
|
||||||
if *guard_val {
|
if *guard_val {
|
||||||
match self.eval_stmt(scope, body) {
|
match self.eval_stmt(scope, body) {
|
||||||
|
Ok(_) => (),
|
||||||
Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()),
|
Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()),
|
||||||
Err(x) => return Err(x),
|
Err(x) => return Err(x),
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Ok(().into_dynamic());
|
return Ok(().into_dynamic());
|
||||||
@ -982,9 +1078,9 @@ impl Engine<'_> {
|
|||||||
// Loop statement
|
// Loop statement
|
||||||
Stmt::Loop(body) => loop {
|
Stmt::Loop(body) => loop {
|
||||||
match self.eval_stmt(scope, body) {
|
match self.eval_stmt(scope, body) {
|
||||||
|
Ok(_) => (),
|
||||||
Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()),
|
Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()),
|
||||||
Err(x) => return Err(x),
|
Err(x) => return Err(x),
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1001,9 +1097,9 @@ impl Engine<'_> {
|
|||||||
*scope.get_mut(name, idx) = a;
|
*scope.get_mut(name, idx) = a;
|
||||||
|
|
||||||
match self.eval_stmt(scope, body) {
|
match self.eval_stmt(scope, body) {
|
||||||
|
Ok(_) => (),
|
||||||
Err(EvalAltResult::LoopBreak) => break,
|
Err(EvalAltResult::LoopBreak) => break,
|
||||||
Err(x) => return Err(x),
|
Err(x) => return Err(x),
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scope.pop();
|
scope.pop();
|
||||||
@ -1045,7 +1141,7 @@ impl Engine<'_> {
|
|||||||
// Let statement
|
// Let statement
|
||||||
Stmt::Let(name, Some(expr), _) => {
|
Stmt::Let(name, Some(expr), _) => {
|
||||||
let val = self.eval_expr(scope, expr)?;
|
let val = self.eval_expr(scope, expr)?;
|
||||||
scope.push_dynamic(name.clone(), val);
|
scope.push_dynamic(name.clone(), VariableType::Normal, val);
|
||||||
Ok(().into_dynamic())
|
Ok(().into_dynamic())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1053,6 +1149,15 @@ impl Engine<'_> {
|
|||||||
scope.push(name.clone(), ());
|
scope.push(name.clone(), ());
|
||||||
Ok(().into_dynamic())
|
Ok(().into_dynamic())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Const statement
|
||||||
|
Stmt::Const(name, expr, _) if expr.is_constant() => {
|
||||||
|
let val = self.eval_expr(scope, expr)?;
|
||||||
|
scope.push_dynamic(name.clone(), VariableType::Constant, val);
|
||||||
|
Ok(().into_dynamic())
|
||||||
|
}
|
||||||
|
|
||||||
|
Stmt::Const(_, _, _) => panic!("constant expression not constant!"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
97
src/error.rs
97
src/error.rs
@ -18,20 +18,11 @@ pub enum LexError {
|
|||||||
MalformedChar(String),
|
MalformedChar(String),
|
||||||
/// Error in the script text.
|
/// Error in the script text.
|
||||||
InputError(String),
|
InputError(String),
|
||||||
|
/// An identifier is in an invalid format.
|
||||||
|
MalformedIdentifier(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for LexError {
|
impl Error for LexError {}
|
||||||
fn description(&self) -> &str {
|
|
||||||
match *self {
|
|
||||||
Self::UnexpectedChar(_) => "Unexpected character",
|
|
||||||
Self::UnterminatedString => "Open string is not terminated",
|
|
||||||
Self::MalformedEscapeSequence(_) => "Unexpected values in escape sequence",
|
|
||||||
Self::MalformedNumber(_) => "Unexpected characters in number",
|
|
||||||
Self::MalformedChar(_) => "Char constant not a single character",
|
|
||||||
Self::InputError(_) => "Input error",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for LexError {
|
impl fmt::Display for LexError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
@ -40,8 +31,11 @@ impl fmt::Display for LexError {
|
|||||||
Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s),
|
Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s),
|
||||||
Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s),
|
Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s),
|
||||||
Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s),
|
Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s),
|
||||||
|
Self::MalformedIdentifier(s) => {
|
||||||
|
write!(f, "Variable name is not in a legal format: '{}'", s)
|
||||||
|
}
|
||||||
Self::InputError(s) => write!(f, "{}", s),
|
Self::InputError(s) => write!(f, "{}", s),
|
||||||
_ => write!(f, "{}", self.description()),
|
Self::UnterminatedString => write!(f, "Open string is not terminated"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,21 +56,34 @@ pub enum ParseErrorType {
|
|||||||
/// An open `{` is missing the corresponding closing `}`.
|
/// An open `{` is missing the corresponding closing `}`.
|
||||||
MissingRightBrace(String),
|
MissingRightBrace(String),
|
||||||
/// An open `[` is missing the corresponding closing `]`.
|
/// An open `[` is missing the corresponding closing `]`.
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
MissingRightBracket(String),
|
MissingRightBracket(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.
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
MalformedIndexExpr(String),
|
MalformedIndexExpr(String),
|
||||||
/// Missing a variable name after the `let` keyword.
|
/// Invalid expression assigned to constant.
|
||||||
VarExpectsIdentifier,
|
ForbiddenConstantExpr(String),
|
||||||
|
/// Missing a variable name after the `let`, `const` or `for` keywords.
|
||||||
|
VariableExpected,
|
||||||
|
/// A `for` statement is missing the `in` keyword.
|
||||||
|
MissingIn,
|
||||||
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
|
/// Defining a function `fn` in an appropriate place (e.g. inside another function).
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
WrongFnDefinition,
|
WrongFnDefinition,
|
||||||
/// Missing a function name after the `fn` keyword.
|
/// Missing a function name after the `fn` keyword.
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
FnMissingName,
|
FnMissingName,
|
||||||
/// 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"))]
|
||||||
FnMissingParams(String),
|
FnMissingParams(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.
|
||||||
|
AssignmentToCopy,
|
||||||
|
/// Assignment to an a constant variable.
|
||||||
|
AssignmentToConstant(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error when parsing a script.
|
/// Error when parsing a script.
|
||||||
@ -98,10 +105,8 @@ impl ParseError {
|
|||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
self.1
|
self.1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for ParseError {
|
pub(crate) fn desc(&self) -> &str {
|
||||||
fn description(&self) -> &str {
|
|
||||||
match self.0 {
|
match self.0 {
|
||||||
ParseErrorType::BadInput(ref p) => p,
|
ParseErrorType::BadInput(ref p) => p,
|
||||||
ParseErrorType::InputPastEndOfFile => "Script is incomplete",
|
ParseErrorType::InputPastEndOfFile => "Script is incomplete",
|
||||||
@ -109,40 +114,64 @@ impl Error for ParseError {
|
|||||||
ParseErrorType::MissingRightParen(_) => "Expecting ')'",
|
ParseErrorType::MissingRightParen(_) => "Expecting ')'",
|
||||||
ParseErrorType::MissingLeftBrace => "Expecting '{'",
|
ParseErrorType::MissingLeftBrace => "Expecting '{'",
|
||||||
ParseErrorType::MissingRightBrace(_) => "Expecting '}'",
|
ParseErrorType::MissingRightBrace(_) => "Expecting '}'",
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
ParseErrorType::MissingRightBracket(_) => "Expecting ']'",
|
ParseErrorType::MissingRightBracket(_) => "Expecting ']'",
|
||||||
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
|
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
|
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
|
||||||
ParseErrorType::VarExpectsIdentifier => "Expecting name of a variable",
|
ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant",
|
||||||
|
ParseErrorType::MissingIn => "Expecting 'in'",
|
||||||
|
ParseErrorType::VariableExpected => "Expecting name of a variable",
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
ParseErrorType::FnMissingName => "Expecting name in function declaration",
|
ParseErrorType::FnMissingName => "Expecting name in function declaration",
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
|
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
ParseErrorType::WrongFnDefinition => "Function definitions must be at top level and cannot be inside a block or another function",
|
ParseErrorType::WrongFnDefinition => "Function definitions must be at top level and cannot be inside a block or another function",
|
||||||
ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression because it will only be changing a copy of the value"
|
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::AssignmentToConstant(_) => "Cannot assign to a constant variable."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cause(&self) -> Option<&dyn Error> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Error for ParseError {}
|
||||||
|
|
||||||
impl fmt::Display for ParseError {
|
impl fmt::Display for ParseError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
ParseErrorType::BadInput(ref s)
|
ParseErrorType::BadInput(ref s) | ParseErrorType::MalformedCallExpr(ref s) => {
|
||||||
| ParseErrorType::MalformedIndexExpr(ref s)
|
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
|
||||||
| ParseErrorType::MalformedCallExpr(ref s) => {
|
|
||||||
write!(f, "{}", if s.is_empty() { self.description() } else { s })?
|
|
||||||
}
|
}
|
||||||
ParseErrorType::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.description(), s)?,
|
ParseErrorType::ForbiddenConstantExpr(ref s) => {
|
||||||
|
write!(f, "Expecting a constant to assign to '{}'", s)?
|
||||||
|
}
|
||||||
|
ParseErrorType::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.desc(), s)?,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
ParseErrorType::MalformedIndexExpr(ref s) => {
|
||||||
|
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
ParseErrorType::FnMissingParams(ref s) => {
|
ParseErrorType::FnMissingParams(ref s) => {
|
||||||
write!(f, "Expecting parameters for function '{}'", s)?
|
write!(f, "Expecting parameters for function '{}'", s)?
|
||||||
}
|
}
|
||||||
ParseErrorType::MissingRightParen(ref s)
|
|
||||||
| ParseErrorType::MissingRightBrace(ref s)
|
ParseErrorType::MissingRightParen(ref s) | ParseErrorType::MissingRightBrace(ref s) => {
|
||||||
| ParseErrorType::MissingRightBracket(ref s) => {
|
write!(f, "{} for {}", self.desc(), s)?
|
||||||
write!(f, "{} for {}", self.description(), s)?
|
|
||||||
}
|
}
|
||||||
_ => write!(f, "{}", self.description())?,
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
ParseErrorType::MissingRightBracket(ref s) => write!(f, "{} for {}", self.desc(), s)?,
|
||||||
|
|
||||||
|
ParseErrorType::AssignmentToConstant(ref s) if s.is_empty() => {
|
||||||
|
write!(f, "{}", self.desc())?
|
||||||
|
}
|
||||||
|
ParseErrorType::AssignmentToConstant(ref s) => {
|
||||||
|
write!(f, "Cannot assign to constant '{}'", s)?
|
||||||
|
}
|
||||||
|
_ => write!(f, "{}", self.desc())?,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.1.is_eof() {
|
if !self.1.is_eof() {
|
||||||
|
@ -196,13 +196,10 @@ macro_rules! def_register {
|
|||||||
|
|
||||||
// Call the user-supplied function using ($clone) to
|
// Call the user-supplied function using ($clone) to
|
||||||
// potentially clone the value, otherwise pass the reference.
|
// potentially clone the value, otherwise pass the reference.
|
||||||
match f($(($clone)($par)),*) {
|
f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic).map_err(|mut err| {
|
||||||
Ok(r) => Ok(Box::new(r) as Dynamic),
|
|
||||||
Err(mut err) => {
|
|
||||||
err.set_position(pos);
|
err.set_position(pos);
|
||||||
Err(err)
|
err
|
||||||
}
|
})
|
||||||
}
|
|
||||||
};
|
};
|
||||||
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
|
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
|
||||||
}
|
}
|
||||||
|
10
src/lib.rs
10
src/lib.rs
@ -78,9 +78,15 @@ pub use call::FuncArgs;
|
|||||||
pub use engine::Engine;
|
pub use engine::Engine;
|
||||||
pub use error::{ParseError, ParseErrorType};
|
pub use error::{ParseError, ParseErrorType};
|
||||||
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
||||||
pub use parser::{Position, AST, FLOAT, INT};
|
pub use parser::{Position, AST, INT};
|
||||||
pub use result::EvalAltResult;
|
pub use result::EvalAltResult;
|
||||||
pub use scope::Scope;
|
pub use scope::{Scope, ScopeEntry, VariableType};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub use engine::Array;
|
pub use engine::Array;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
pub use parser::FLOAT;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
pub use optimize::OptimizationLevel;
|
||||||
|
371
src/optimize.rs
371
src/optimize.rs
@ -1,17 +1,78 @@
|
|||||||
use crate::engine::KEYWORD_DUMP_AST;
|
#![cfg(not(feature = "no_optimize"))]
|
||||||
use crate::parser::{Expr, Stmt};
|
|
||||||
|
|
||||||
fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt {
|
use crate::any::Dynamic;
|
||||||
|
use crate::engine::{Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_PRINT};
|
||||||
|
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, Stmt, AST};
|
||||||
|
use crate::scope::{Scope, ScopeEntry, VariableType};
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Level of optimization performed
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||||
|
pub enum OptimizationLevel {
|
||||||
|
/// No optimization performed
|
||||||
|
None,
|
||||||
|
/// Only perform simple optimizations without evaluating functions
|
||||||
|
Simple,
|
||||||
|
/// Full optimizations performed, including evaluating functions.
|
||||||
|
/// Take care that this may cause side effects.
|
||||||
|
Full,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State<'a> {
|
||||||
|
changed: bool,
|
||||||
|
constants: Vec<(String, Expr)>,
|
||||||
|
engine: Option<&'a Engine<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State<'_> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
State {
|
||||||
|
changed: false,
|
||||||
|
constants: vec![],
|
||||||
|
engine: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.changed = false;
|
||||||
|
}
|
||||||
|
pub fn set_dirty(&mut self) {
|
||||||
|
self.changed = true;
|
||||||
|
}
|
||||||
|
pub fn is_dirty(&self) -> bool {
|
||||||
|
self.changed
|
||||||
|
}
|
||||||
|
pub fn contains_constant(&self, name: &str) -> bool {
|
||||||
|
self.constants.iter().any(|(n, _)| n == name)
|
||||||
|
}
|
||||||
|
pub fn restore_constants(&mut self, len: usize) {
|
||||||
|
self.constants.truncate(len)
|
||||||
|
}
|
||||||
|
pub fn push_constant(&mut self, name: &str, value: Expr) {
|
||||||
|
self.constants.push((name.to_string(), value))
|
||||||
|
}
|
||||||
|
pub fn find_constant(&self, name: &str) -> Option<&Expr> {
|
||||||
|
for (n, expr) in self.constants.iter().rev() {
|
||||||
|
if n == name {
|
||||||
|
return Some(expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() => {
|
Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
|
|
||||||
let pos = expr.position();
|
let pos = expr.position();
|
||||||
let expr = optimize_expr(*expr, changed);
|
let expr = optimize_expr(*expr, state);
|
||||||
|
|
||||||
match expr {
|
if matches!(expr, Expr::False(_) | Expr::True(_)) {
|
||||||
Expr::False(_) | Expr::True(_) => Stmt::Noop(stmt1.position()),
|
Stmt::Noop(stmt1.position())
|
||||||
expr => {
|
} else {
|
||||||
let stmt = Stmt::Expr(Box::new(expr));
|
let stmt = Stmt::Expr(Box::new(expr));
|
||||||
|
|
||||||
if preserve_result {
|
if preserve_result {
|
||||||
@ -21,28 +82,27 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Stmt::IfElse(expr, stmt1, None) => match *expr {
|
Stmt::IfElse(expr, stmt1, None) => match *expr {
|
||||||
Expr::False(pos) => {
|
Expr::False(pos) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
Expr::True(_) => optimize_stmt(*stmt1, changed, true),
|
Expr::True(_) => optimize_stmt(*stmt1, state, true),
|
||||||
expr => Stmt::IfElse(
|
expr => Stmt::IfElse(
|
||||||
Box::new(optimize_expr(expr, changed)),
|
Box::new(optimize_expr(expr, state)),
|
||||||
Box::new(optimize_stmt(*stmt1, changed, true)),
|
Box::new(optimize_stmt(*stmt1, state, true)),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr {
|
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr {
|
||||||
Expr::False(_) => optimize_stmt(*stmt2, changed, true),
|
Expr::False(_) => optimize_stmt(*stmt2, state, true),
|
||||||
Expr::True(_) => optimize_stmt(*stmt1, changed, true),
|
Expr::True(_) => optimize_stmt(*stmt1, state, true),
|
||||||
expr => Stmt::IfElse(
|
expr => Stmt::IfElse(
|
||||||
Box::new(optimize_expr(expr, changed)),
|
Box::new(optimize_expr(expr, state)),
|
||||||
Box::new(optimize_stmt(*stmt1, changed, true)),
|
Box::new(optimize_stmt(*stmt1, state, true)),
|
||||||
match optimize_stmt(*stmt2, changed, true) {
|
match optimize_stmt(*stmt2, state, true) {
|
||||||
stmt if stmt.is_noop() => None,
|
stmt if stmt.is_noop() => None,
|
||||||
stmt => Some(Box::new(stmt)),
|
stmt => Some(Box::new(stmt)),
|
||||||
},
|
},
|
||||||
@ -51,47 +111,51 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt
|
|||||||
|
|
||||||
Stmt::While(expr, stmt) => match *expr {
|
Stmt::While(expr, stmt) => match *expr {
|
||||||
Expr::False(pos) => {
|
Expr::False(pos) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed, false))),
|
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))),
|
||||||
expr => Stmt::While(
|
expr => Stmt::While(
|
||||||
Box::new(optimize_expr(expr, changed)),
|
Box::new(optimize_expr(expr, state)),
|
||||||
Box::new(optimize_stmt(*stmt, changed, false)),
|
Box::new(optimize_stmt(*stmt, state, false)),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed, false))),
|
Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))),
|
||||||
Stmt::For(id, expr, stmt) => Stmt::For(
|
Stmt::For(id, expr, stmt) => Stmt::For(
|
||||||
id,
|
id,
|
||||||
Box::new(optimize_expr(*expr, changed)),
|
Box::new(optimize_expr(*expr, state)),
|
||||||
Box::new(optimize_stmt(*stmt, changed, false)),
|
Box::new(optimize_stmt(*stmt, state, false)),
|
||||||
),
|
),
|
||||||
Stmt::Let(id, Some(expr), pos) => {
|
Stmt::Let(id, Some(expr), pos) => {
|
||||||
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, changed))), pos)
|
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos)
|
||||||
}
|
}
|
||||||
Stmt::Let(_, None, _) => stmt,
|
Stmt::Let(_, None, _) => stmt,
|
||||||
|
|
||||||
Stmt::Block(statements, pos) => {
|
Stmt::Block(statements, pos) => {
|
||||||
let orig_len = statements.len();
|
let orig_len = statements.len();
|
||||||
|
let orig_constants_len = state.constants.len();
|
||||||
|
|
||||||
let mut result: Vec<_> = statements
|
let mut result: Vec<_> = statements
|
||||||
.into_iter() // For each statement
|
.into_iter() // For each statement
|
||||||
.rev() // Scan in reverse
|
.map(|stmt| {
|
||||||
.map(|s| optimize_stmt(s, changed, preserve_result)) // Optimize the statement
|
if let Stmt::Const(name, value, pos) = stmt {
|
||||||
|
state.push_constant(&name, *value);
|
||||||
|
state.set_dirty();
|
||||||
|
Stmt::Noop(pos) // No need to keep constants
|
||||||
|
} else {
|
||||||
|
optimize_stmt(stmt, state, preserve_result) // Optimize the statement
|
||||||
|
}
|
||||||
|
})
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(i, s)| s.is_op() || (preserve_result && *i == 0)) // Remove no-op's but leave the last one if we need the result
|
.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(|(_, s)| s)
|
.map(|(_, stmt)| stmt)
|
||||||
.rev()
|
|
||||||
.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| match stmt {
|
result.retain(|stmt| !matches!(stmt, Stmt::Expr(expr) if expr.is_pure()));
|
||||||
Stmt::Expr(expr) if expr.is_pure() => false,
|
|
||||||
_ => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(stmt) = last_stmt {
|
if let Some(stmt) = last_stmt {
|
||||||
result.push(stmt);
|
result.push(stmt);
|
||||||
@ -106,7 +170,6 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt
|
|||||||
match expr {
|
match expr {
|
||||||
Stmt::Let(_, None, _) => removed = true,
|
Stmt::Let(_, None, _) => removed = true,
|
||||||
Stmt::Let(_, Some(val_expr), _) if val_expr.is_pure() => removed = true,
|
Stmt::Let(_, Some(val_expr), _) if val_expr.is_pure() => removed = true,
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
result.push(expr);
|
result.push(expr);
|
||||||
break;
|
break;
|
||||||
@ -123,59 +186,82 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.rev()
|
.rev()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, s)| optimize_stmt(s, changed, i == 0)) // Optimize all other statements again
|
.map(|(i, s)| optimize_stmt(s, state, i == 0)) // Optimize all other statements again
|
||||||
.rev()
|
.rev()
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
*changed = *changed || orig_len != result.len();
|
if orig_len != result.len() {
|
||||||
|
state.set_dirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.restore_constants(orig_constants_len);
|
||||||
|
|
||||||
match result[..] {
|
match result[..] {
|
||||||
// No statements in block - change to No-op
|
// No statements in block - change to No-op
|
||||||
[] => {
|
[] => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
// Only one statement - promote
|
// Only one statement - promote
|
||||||
[_] => {
|
[_] => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
result.remove(0)
|
result.remove(0)
|
||||||
}
|
}
|
||||||
_ => Stmt::Block(result, pos),
|
_ => Stmt::Block(result, pos),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, changed))),
|
Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))),
|
||||||
|
|
||||||
Stmt::ReturnWithVal(Some(expr), is_return, pos) => Stmt::ReturnWithVal(
|
Stmt::ReturnWithVal(Some(expr), is_return, pos) => {
|
||||||
Some(Box::new(optimize_expr(*expr, changed))),
|
Stmt::ReturnWithVal(Some(Box::new(optimize_expr(*expr, state))), is_return, pos)
|
||||||
is_return,
|
}
|
||||||
pos,
|
|
||||||
),
|
|
||||||
|
|
||||||
stmt => stmt,
|
stmt => stmt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed, true) {
|
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, state, true) {
|
||||||
Stmt::Noop(_) => {
|
Stmt::Noop(_) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
Expr::Unit(pos)
|
Expr::Unit(pos)
|
||||||
}
|
}
|
||||||
Stmt::Expr(expr) => {
|
Stmt::Expr(expr) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
*expr
|
*expr
|
||||||
}
|
}
|
||||||
stmt => Expr::Stmt(Box::new(stmt), pos),
|
stmt => Expr::Stmt(Box::new(stmt), pos),
|
||||||
},
|
},
|
||||||
Expr::Assignment(id, expr, pos) => {
|
Expr::Assignment(id1, expr1, pos1) => match *expr1 {
|
||||||
Expr::Assignment(id, Box::new(optimize_expr(*expr, changed)), pos)
|
Expr::Assignment(id2, expr2, pos2) => match (*id1, *id2) {
|
||||||
|
(Expr::Variable(var1, _), Expr::Variable(var2, _)) if var1 == var2 => {
|
||||||
|
// Assignment to the same variable - fold
|
||||||
|
state.set_dirty();
|
||||||
|
|
||||||
|
Expr::Assignment(
|
||||||
|
Box::new(Expr::Variable(var1, pos1)),
|
||||||
|
Box::new(optimize_expr(*expr2, state)),
|
||||||
|
pos1,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
(id1, id2) => Expr::Assignment(
|
||||||
|
Box::new(id1),
|
||||||
|
Box::new(Expr::Assignment(
|
||||||
|
Box::new(id2),
|
||||||
|
Box::new(optimize_expr(*expr2, state)),
|
||||||
|
pos2,
|
||||||
|
)),
|
||||||
|
pos1,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expr => Expr::Assignment(id1, Box::new(optimize_expr(expr, state)), pos1),
|
||||||
|
},
|
||||||
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
||||||
Box::new(optimize_expr(*lhs, changed)),
|
Box::new(optimize_expr(*lhs, state)),
|
||||||
Box::new(optimize_expr(*rhs, changed)),
|
Box::new(optimize_expr(*rhs, state)),
|
||||||
pos,
|
pos,
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -184,19 +270,25 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
|||||||
(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()) =>
|
||||||
{
|
{
|
||||||
// Array where everything is a pure - promote the indexed item.
|
// Array literal where everything is pure - promote the indexed item.
|
||||||
// All other items can be thrown away.
|
// All other items can be thrown away.
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
items.remove(i as usize)
|
items.remove(i as usize)
|
||||||
}
|
}
|
||||||
|
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _))
|
||||||
|
if i >= 0 && (i as usize) < s.chars().count() =>
|
||||||
|
{
|
||||||
|
// String literal indexing - get the character
|
||||||
|
state.set_dirty();
|
||||||
|
Expr::CharConstant(s.chars().nth(i as usize).expect("should get char"), pos)
|
||||||
|
}
|
||||||
|
|
||||||
(lhs, rhs) => Expr::Index(
|
(lhs, rhs) => Expr::Index(
|
||||||
Box::new(optimize_expr(lhs, changed)),
|
Box::new(optimize_expr(lhs, state)),
|
||||||
Box::new(optimize_expr(rhs, changed)),
|
Box::new(optimize_expr(rhs, state)),
|
||||||
pos,
|
pos,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
#[cfg(feature = "no_index")]
|
|
||||||
Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(items, pos) => {
|
Expr::Array(items, pos) => {
|
||||||
@ -204,95 +296,164 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
|||||||
|
|
||||||
let items: Vec<_> = items
|
let items: Vec<_> = items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|expr| optimize_expr(expr, changed))
|
.map(|expr| optimize_expr(expr, state))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
*changed = *changed || orig_len != items.len();
|
if orig_len != items.len() {
|
||||||
|
state.set_dirty();
|
||||||
|
}
|
||||||
|
|
||||||
Expr::Array(items, pos)
|
Expr::Array(items, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "no_index")]
|
|
||||||
Expr::Array(_, _) => panic!("encountered an array during no_index!"),
|
|
||||||
|
|
||||||
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
||||||
(Expr::True(_), rhs) => {
|
(Expr::True(_), rhs) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
rhs
|
rhs
|
||||||
}
|
}
|
||||||
(Expr::False(pos), _) => {
|
(Expr::False(pos), _) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
Expr::False(pos)
|
Expr::False(pos)
|
||||||
}
|
}
|
||||||
(lhs, Expr::True(_)) => {
|
(lhs, Expr::True(_)) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
lhs
|
lhs
|
||||||
}
|
}
|
||||||
(lhs, rhs) => Expr::And(
|
(lhs, rhs) => Expr::And(
|
||||||
Box::new(optimize_expr(lhs, changed)),
|
Box::new(optimize_expr(lhs, state)),
|
||||||
Box::new(optimize_expr(rhs, changed)),
|
Box::new(optimize_expr(rhs, state)),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
|
Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
|
||||||
(Expr::False(_), rhs) => {
|
(Expr::False(_), rhs) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
rhs
|
rhs
|
||||||
}
|
}
|
||||||
(Expr::True(pos), _) => {
|
(Expr::True(pos), _) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
Expr::True(pos)
|
Expr::True(pos)
|
||||||
}
|
}
|
||||||
(lhs, Expr::False(_)) => {
|
(lhs, Expr::False(_)) => {
|
||||||
*changed = true;
|
state.set_dirty();
|
||||||
lhs
|
lhs
|
||||||
}
|
}
|
||||||
(lhs, rhs) => Expr::Or(
|
(lhs, rhs) => Expr::Or(
|
||||||
Box::new(optimize_expr(lhs, changed)),
|
Box::new(optimize_expr(lhs, state)),
|
||||||
Box::new(optimize_expr(rhs, changed)),
|
Box::new(optimize_expr(rhs, state)),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Do not optimize anything within `dump_ast`
|
||||||
Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST => {
|
Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST => {
|
||||||
Expr::FunctionCall(id, args, def_value, pos)
|
Expr::FunctionCall(id, args, def_value, pos)
|
||||||
}
|
}
|
||||||
|
// 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
|
||||||
|
=>
|
||||||
|
{
|
||||||
|
let engine = state.engine.expect("engine should be Some");
|
||||||
|
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();
|
||||||
|
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)
|
||||||
|
.map(|expr| {
|
||||||
|
state.set_dirty();
|
||||||
|
expr
|
||||||
|
})).flatten()
|
||||||
|
.unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos))
|
||||||
|
}
|
||||||
|
// Optimize the function call arguments
|
||||||
Expr::FunctionCall(id, args, def_value, pos) => {
|
Expr::FunctionCall(id, args, def_value, pos) => {
|
||||||
let orig_len = args.len();
|
let orig_len = args.len();
|
||||||
|
|
||||||
let args: Vec<_> = args
|
let args: Vec<_> = args.into_iter().map(|a| optimize_expr(a, state)).collect();
|
||||||
.into_iter()
|
|
||||||
.map(|a| optimize_expr(a, changed))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
*changed = *changed || orig_len != args.len();
|
if orig_len != args.len() {
|
||||||
|
state.set_dirty();
|
||||||
|
}
|
||||||
|
|
||||||
Expr::FunctionCall(id, args, def_value, pos)
|
Expr::FunctionCall(id, args, def_value, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expr::Variable(ref name, _) if state.contains_constant(name) => {
|
||||||
|
state.set_dirty();
|
||||||
|
|
||||||
|
// Replace constant with value
|
||||||
|
state
|
||||||
|
.find_constant(name)
|
||||||
|
.expect("should find constant in scope!")
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
expr => expr,
|
expr => expr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn optimize(statements: Vec<Stmt>) -> Vec<Stmt> {
|
pub(crate) fn optimize<'a>(
|
||||||
|
statements: Vec<Stmt>,
|
||||||
|
engine: Option<&Engine<'a>>,
|
||||||
|
scope: &Scope,
|
||||||
|
) -> Vec<Stmt> {
|
||||||
|
// If optimization level is None then skip optimizing
|
||||||
|
if engine
|
||||||
|
.map(|eng| eng.optimization_level == OptimizationLevel::None)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
return statements;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the state
|
||||||
|
let mut state = State::new();
|
||||||
|
state.engine = engine;
|
||||||
|
|
||||||
|
scope
|
||||||
|
.iter()
|
||||||
|
.filter(|ScopeEntry { var_type, expr, .. }| {
|
||||||
|
// Get all the constants with definite constant expressions
|
||||||
|
*var_type == VariableType::Constant
|
||||||
|
&& expr.as_ref().map(Expr::is_constant).unwrap_or(false)
|
||||||
|
})
|
||||||
|
.for_each(|ScopeEntry { name, expr, .. }| {
|
||||||
|
state.push_constant(
|
||||||
|
name.as_ref(),
|
||||||
|
expr.as_ref().expect("should be Some(expr)").clone(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let orig_constants_len = state.constants.len();
|
||||||
|
|
||||||
|
// Optimization loop
|
||||||
let mut result = statements;
|
let mut result = statements;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut changed = false;
|
state.reset();
|
||||||
|
state.restore_constants(orig_constants_len);
|
||||||
|
|
||||||
|
let num_statements = result.len();
|
||||||
|
|
||||||
result = result
|
result = result
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.rev() // Scan in reverse
|
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, stmt)| {
|
.map(|(i, stmt)| {
|
||||||
|
if let Stmt::Const(name, value, _) = &stmt {
|
||||||
|
// Load constants
|
||||||
|
state.push_constant(name, value.as_ref().clone());
|
||||||
|
stmt // Keep it in the top scope
|
||||||
|
} else {
|
||||||
// Keep all variable declarations at this level
|
// Keep all variable declarations at this level
|
||||||
let keep = stmt.is_var();
|
// and always keep the last return value
|
||||||
|
let keep = stmt.is_var() || i == num_statements - 1;
|
||||||
|
|
||||||
// Always keep the last return value
|
optimize_stmt(stmt, &mut state, keep)
|
||||||
optimize_stmt(stmt, &mut changed, keep || i == 0)
|
}
|
||||||
})
|
})
|
||||||
.rev()
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !changed {
|
if !state.is_dirty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,10 +462,7 @@ pub(crate) fn optimize(statements: Vec<Stmt>) -> Vec<Stmt> {
|
|||||||
let last_stmt = result.pop();
|
let last_stmt = result.pop();
|
||||||
|
|
||||||
// Remove all pure statements at top level
|
// Remove all pure statements at top level
|
||||||
result.retain(|stmt| match stmt {
|
result.retain(|stmt| !matches!(stmt, Stmt::Expr(expr) if expr.is_pure()));
|
||||||
Stmt::Expr(expr) if expr.is_pure() => false,
|
|
||||||
_ => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(stmt) = last_stmt {
|
if let Some(stmt) = last_stmt {
|
||||||
result.push(stmt); // Add back the last statement
|
result.push(stmt); // Add back the last statement
|
||||||
@ -312,3 +470,32 @@ pub(crate) fn optimize(statements: Vec<Stmt>) -> Vec<Stmt> {
|
|||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn optimize_ast(
|
||||||
|
engine: &Engine,
|
||||||
|
scope: &Scope,
|
||||||
|
statements: Vec<Stmt>,
|
||||||
|
functions: Vec<FnDef>,
|
||||||
|
) -> AST {
|
||||||
|
AST(
|
||||||
|
match engine.optimization_level {
|
||||||
|
OptimizationLevel::None => statements,
|
||||||
|
OptimizationLevel::Simple => optimize(statements, None, &scope),
|
||||||
|
OptimizationLevel::Full => optimize(statements, Some(engine), &scope),
|
||||||
|
},
|
||||||
|
functions
|
||||||
|
.into_iter()
|
||||||
|
.map(|mut fn_def| {
|
||||||
|
match engine.optimization_level {
|
||||||
|
OptimizationLevel::None => (),
|
||||||
|
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Arc::new(fn_def)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
509
src/parser.rs
509
src/parser.rs
@ -1,8 +1,12 @@
|
|||||||
//! Main module defining the lexer and parser.
|
//! Main module defining the lexer and parser.
|
||||||
|
|
||||||
use crate::any::Dynamic;
|
use crate::any::{Any, AnyExt, Dynamic};
|
||||||
|
use crate::engine::Engine;
|
||||||
use crate::error::{LexError, ParseError, ParseErrorType};
|
use crate::error::{LexError, ParseError, ParseErrorType};
|
||||||
use crate::optimize::optimize;
|
use crate::scope::{Scope, VariableType};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
use crate::optimize::optimize_ast;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow, char, cmp::Ordering, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc,
|
borrow::Cow, char, cmp::Ordering, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc,
|
||||||
@ -22,6 +26,7 @@ pub type INT = i64;
|
|||||||
pub type INT = i32;
|
pub type INT = i32;
|
||||||
|
|
||||||
/// The system floating-point type
|
/// The system floating-point type
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
pub type FLOAT = f64;
|
pub type FLOAT = f64;
|
||||||
|
|
||||||
type LERR = LexError;
|
type LERR = LexError;
|
||||||
@ -143,12 +148,9 @@ impl fmt::Debug for Position {
|
|||||||
|
|
||||||
/// Compiled AST (abstract syntax tree) of a Rhai script.
|
/// Compiled AST (abstract syntax tree) of a Rhai script.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AST(
|
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<Arc<FnDef>>);
|
||||||
pub(crate) Vec<Stmt>,
|
|
||||||
#[cfg(not(feature = "no_function"))] pub(crate) Vec<Arc<FnDef>>,
|
|
||||||
);
|
|
||||||
|
|
||||||
#[derive(Debug)] // Do not derive Clone because it is expensive
|
#[derive(Debug, Clone)]
|
||||||
pub struct FnDef {
|
pub struct FnDef {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub params: Vec<String>,
|
pub params: Vec<String>,
|
||||||
@ -179,6 +181,7 @@ pub enum Stmt {
|
|||||||
Loop(Box<Stmt>),
|
Loop(Box<Stmt>),
|
||||||
For(String, Box<Expr>, Box<Stmt>),
|
For(String, Box<Expr>, Box<Stmt>),
|
||||||
Let(String, Option<Box<Expr>>, Position),
|
Let(String, Option<Box<Expr>>, Position),
|
||||||
|
Const(String, Box<Expr>, Position),
|
||||||
Block(Vec<Stmt>, Position),
|
Block(Vec<Stmt>, Position),
|
||||||
Expr(Box<Expr>),
|
Expr(Box<Expr>),
|
||||||
Break(Position),
|
Break(Position),
|
||||||
@ -187,30 +190,22 @@ pub enum Stmt {
|
|||||||
|
|
||||||
impl Stmt {
|
impl Stmt {
|
||||||
pub fn is_noop(&self) -> bool {
|
pub fn is_noop(&self) -> bool {
|
||||||
match self {
|
matches!(self, Stmt::Noop(_))
|
||||||
Stmt::Noop(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_op(&self) -> bool {
|
pub fn is_op(&self) -> bool {
|
||||||
match self {
|
!matches!(self, Stmt::Noop(_))
|
||||||
Stmt::Noop(_) => false,
|
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_var(&self) -> bool {
|
pub fn is_var(&self) -> bool {
|
||||||
match self {
|
matches!(self, Stmt::Let(_, _, _))
|
||||||
Stmt::Let(_, _, _) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
| Stmt::Let(_, _, pos)
|
| Stmt::Let(_, _, pos)
|
||||||
|
| Stmt::Const(_, _, pos)
|
||||||
| Stmt::Block(_, pos)
|
| Stmt::Block(_, pos)
|
||||||
| Stmt::Break(pos)
|
| Stmt::Break(pos)
|
||||||
| Stmt::ReturnWithVal(_, _, pos) => *pos,
|
| Stmt::ReturnWithVal(_, _, pos) => *pos,
|
||||||
@ -225,14 +220,17 @@ pub enum Expr {
|
|||||||
IntegerConstant(INT, Position),
|
IntegerConstant(INT, Position),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
FloatConstant(FLOAT, Position),
|
FloatConstant(FLOAT, Position),
|
||||||
Identifier(String, Position),
|
Variable(String, Position),
|
||||||
|
Property(String, Position),
|
||||||
CharConstant(char, Position),
|
CharConstant(char, Position),
|
||||||
StringConstant(String, Position),
|
StringConstant(String, Position),
|
||||||
Stmt(Box<Stmt>, Position),
|
Stmt(Box<Stmt>, Position),
|
||||||
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
|
FunctionCall(String, Vec<Expr>, Option<Dynamic>, Position),
|
||||||
Assignment(Box<Expr>, Box<Expr>, Position),
|
Assignment(Box<Expr>, Box<Expr>, Position),
|
||||||
Dot(Box<Expr>, Box<Expr>, Position),
|
Dot(Box<Expr>, Box<Expr>, Position),
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Index(Box<Expr>, Box<Expr>, Position),
|
Index(Box<Expr>, Box<Expr>, Position),
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Array(Vec<Expr>, Position),
|
Array(Vec<Expr>, Position),
|
||||||
And(Box<Expr>, Box<Expr>),
|
And(Box<Expr>, Box<Expr>),
|
||||||
Or(Box<Expr>, Box<Expr>),
|
Or(Box<Expr>, Box<Expr>),
|
||||||
@ -242,27 +240,73 @@ pub enum Expr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
|
pub fn get_constant_value(&self) -> Dynamic {
|
||||||
|
match self {
|
||||||
|
Expr::IntegerConstant(i, _) => i.into_dynamic(),
|
||||||
|
Expr::CharConstant(c, _) => c.into_dynamic(),
|
||||||
|
Expr::StringConstant(s, _) => s.into_dynamic(),
|
||||||
|
Expr::True(_) => true.into_dynamic(),
|
||||||
|
Expr::False(_) => false.into_dynamic(),
|
||||||
|
Expr::Unit(_) => ().into_dynamic(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Array(items, _) if items.iter().all(Expr::is_constant) => items
|
||||||
|
.iter()
|
||||||
|
.map(Expr::get_constant_value)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_dynamic(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
Expr::FloatConstant(f, _) => f.into_dynamic(),
|
||||||
|
|
||||||
|
_ => panic!("cannot get value of non-constant expression"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_constant_str(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Expr::IntegerConstant(i, _) => i.to_string(),
|
||||||
|
Expr::CharConstant(c, _) => c.to_string(),
|
||||||
|
Expr::StringConstant(_, _) => "string".to_string(),
|
||||||
|
Expr::True(_) => "true".to_string(),
|
||||||
|
Expr::False(_) => "false".to_string(),
|
||||||
|
Expr::Unit(_) => "()".to_string(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Array(items, _) if items.iter().all(Expr::is_constant) => "array".to_string(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
Expr::FloatConstant(f, _) => f.to_string(),
|
||||||
|
|
||||||
|
_ => panic!("cannot get value of non-constant expression"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
Expr::IntegerConstant(_, pos)
|
Expr::IntegerConstant(_, pos)
|
||||||
| Expr::Identifier(_, pos)
|
|
||||||
| Expr::CharConstant(_, pos)
|
| Expr::CharConstant(_, pos)
|
||||||
| Expr::StringConstant(_, pos)
|
| Expr::StringConstant(_, pos)
|
||||||
|
| Expr::Variable(_, pos)
|
||||||
|
| Expr::Property(_, pos)
|
||||||
| Expr::Stmt(_, pos)
|
| Expr::Stmt(_, pos)
|
||||||
| Expr::FunctionCall(_, _, _, pos)
|
| Expr::FunctionCall(_, _, _, pos)
|
||||||
| Expr::Array(_, pos)
|
|
||||||
| Expr::True(pos)
|
| Expr::True(pos)
|
||||||
| Expr::False(pos)
|
| Expr::False(pos)
|
||||||
| Expr::Unit(pos) => *pos,
|
| Expr::Unit(pos) => *pos,
|
||||||
|
|
||||||
Expr::Assignment(e, _, _)
|
Expr::Assignment(e, _, _) | Expr::Dot(e, _, _) | Expr::And(e, _) | Expr::Or(e, _) => {
|
||||||
| Expr::Dot(e, _, _)
|
e.position()
|
||||||
| Expr::Index(e, _, _)
|
}
|
||||||
| Expr::And(e, _)
|
|
||||||
| Expr::Or(e, _) => e.position(),
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(_, pos) => *pos,
|
Expr::FloatConstant(_, pos) => *pos,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Array(_, pos) => *pos,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Index(e, _, _) => e.position(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,9 +315,15 @@ impl Expr {
|
|||||||
/// A pure expression has no side effects.
|
/// A pure expression has no side effects.
|
||||||
pub fn is_pure(&self) -> bool {
|
pub fn is_pure(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure),
|
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure),
|
||||||
Expr::And(x, y) | Expr::Or(x, y) | Expr::Index(x, y, _) => x.is_pure() && y.is_pure(),
|
|
||||||
expr => expr.is_constant() || expr.is_identifier(),
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Index(x, y, _) => x.is_pure() && y.is_pure(),
|
||||||
|
|
||||||
|
Expr::And(x, y) | Expr::Or(x, y) => x.is_pure() && y.is_pure(),
|
||||||
|
|
||||||
|
expr => expr.is_constant() || matches!(expr, Expr::Variable(_, _)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,12 +339,9 @@ impl Expr {
|
|||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(_, _) => true,
|
Expr::FloatConstant(_, _) => true,
|
||||||
|
|
||||||
_ => false,
|
#[cfg(not(feature = "no_index"))]
|
||||||
}
|
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant),
|
||||||
}
|
|
||||||
pub fn is_identifier(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Expr::Identifier(_, _) => true,
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,7 +359,9 @@ pub enum Token {
|
|||||||
RightBrace,
|
RightBrace,
|
||||||
LeftParen,
|
LeftParen,
|
||||||
RightParen,
|
RightParen,
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
LeftBracket,
|
LeftBracket,
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
RightBracket,
|
RightBracket,
|
||||||
Plus,
|
Plus,
|
||||||
UnaryPlus,
|
UnaryPlus,
|
||||||
@ -328,6 +377,7 @@ pub enum Token {
|
|||||||
True,
|
True,
|
||||||
False,
|
False,
|
||||||
Let,
|
Let,
|
||||||
|
Const,
|
||||||
If,
|
If,
|
||||||
Else,
|
Else,
|
||||||
While,
|
While,
|
||||||
@ -343,6 +393,7 @@ pub enum Token {
|
|||||||
Or,
|
Or,
|
||||||
Ampersand,
|
Ampersand,
|
||||||
And,
|
And,
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
Fn,
|
Fn,
|
||||||
Break,
|
Break,
|
||||||
Return,
|
Return,
|
||||||
@ -386,7 +437,9 @@ impl Token {
|
|||||||
RightBrace => "}",
|
RightBrace => "}",
|
||||||
LeftParen => "(",
|
LeftParen => "(",
|
||||||
RightParen => ")",
|
RightParen => ")",
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
LeftBracket => "[",
|
LeftBracket => "[",
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
RightBracket => "]",
|
RightBracket => "]",
|
||||||
Plus => "+",
|
Plus => "+",
|
||||||
UnaryPlus => "+",
|
UnaryPlus => "+",
|
||||||
@ -402,6 +455,7 @@ impl Token {
|
|||||||
True => "true",
|
True => "true",
|
||||||
False => "false",
|
False => "false",
|
||||||
Let => "let",
|
Let => "let",
|
||||||
|
Const => "const",
|
||||||
If => "if",
|
If => "if",
|
||||||
Else => "else",
|
Else => "else",
|
||||||
While => "while",
|
While => "while",
|
||||||
@ -417,6 +471,7 @@ impl Token {
|
|||||||
Or => "||",
|
Or => "||",
|
||||||
Ampersand => "&",
|
Ampersand => "&",
|
||||||
And => "&&",
|
And => "&&",
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
Fn => "fn",
|
Fn => "fn",
|
||||||
Break => "break",
|
Break => "break",
|
||||||
Return => "return",
|
Return => "return",
|
||||||
@ -456,8 +511,6 @@ impl Token {
|
|||||||
// RightBrace | {expr} - expr not unary & is closing
|
// RightBrace | {expr} - expr not unary & is closing
|
||||||
LeftParen | // {-expr} - is unary
|
LeftParen | // {-expr} - is unary
|
||||||
// RightParen | (expr) - expr not unary & is closing
|
// RightParen | (expr) - expr not unary & is closing
|
||||||
LeftBracket | // [-expr] - is unary
|
|
||||||
// RightBracket | [expr] - expr not unary & is closing
|
|
||||||
Plus |
|
Plus |
|
||||||
UnaryPlus |
|
UnaryPlus |
|
||||||
Minus |
|
Minus |
|
||||||
@ -501,6 +554,10 @@ impl Token {
|
|||||||
In |
|
In |
|
||||||
PowerOfAssign => true,
|
PowerOfAssign => true,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
LeftBracket => true, // [-expr] - is unary
|
||||||
|
// RightBracket | [expr] - expr not unary & is closing
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -510,9 +567,12 @@ impl Token {
|
|||||||
use self::Token::*;
|
use self::Token::*;
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
RightBrace | RightParen | RightBracket | Plus | Minus | Multiply | Divide | Comma
|
RightParen | Plus | Minus | Multiply | Divide | Comma | Equals | LessThan
|
||||||
| Equals | LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo
|
| GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo | NotEqualsTo
|
||||||
| EqualsTo | NotEqualsTo | Pipe | Or | Ampersand | And | PowerOf => true,
|
| Pipe | Or | Ampersand | And | PowerOf => true,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
RightBrace | RightBracket => true,
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@ -822,13 +882,15 @@ impl<'a> TokenIterator<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let out: String = result.iter().collect();
|
let has_letter = result.iter().any(char::is_ascii_alphabetic);
|
||||||
|
let identifier: String = result.iter().collect();
|
||||||
|
|
||||||
return Some((
|
return Some((
|
||||||
match out.as_str() {
|
match identifier.as_str() {
|
||||||
"true" => Token::True,
|
"true" => Token::True,
|
||||||
"false" => Token::False,
|
"false" => Token::False,
|
||||||
"let" => Token::Let,
|
"let" => Token::Let,
|
||||||
|
"const" => Token::Const,
|
||||||
"if" => Token::If,
|
"if" => Token::If,
|
||||||
"else" => Token::Else,
|
"else" => Token::Else,
|
||||||
"while" => Token::While,
|
"while" => Token::While,
|
||||||
@ -836,10 +898,15 @@ impl<'a> TokenIterator<'a> {
|
|||||||
"break" => Token::Break,
|
"break" => Token::Break,
|
||||||
"return" => Token::Return,
|
"return" => Token::Return,
|
||||||
"throw" => Token::Throw,
|
"throw" => Token::Throw,
|
||||||
"fn" => Token::Fn,
|
|
||||||
"for" => Token::For,
|
"for" => Token::For,
|
||||||
"in" => Token::In,
|
"in" => Token::In,
|
||||||
_ => Token::Identifier(out),
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
"fn" => Token::Fn,
|
||||||
|
|
||||||
|
_ if has_letter => Token::Identifier(identifier),
|
||||||
|
|
||||||
|
_ => Token::LexError(LERR::MalformedIdentifier(identifier)),
|
||||||
},
|
},
|
||||||
pos,
|
pos,
|
||||||
));
|
));
|
||||||
@ -873,8 +940,12 @@ impl<'a> TokenIterator<'a> {
|
|||||||
'}' => return Some((Token::RightBrace, pos)),
|
'}' => return Some((Token::RightBrace, pos)),
|
||||||
'(' => return Some((Token::LeftParen, pos)),
|
'(' => return Some((Token::LeftParen, pos)),
|
||||||
')' => return Some((Token::RightParen, pos)),
|
')' => return Some((Token::RightParen, pos)),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
'[' => return Some((Token::LeftBracket, pos)),
|
'[' => return Some((Token::LeftBracket, pos)),
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
']' => return Some((Token::RightBracket, pos)),
|
']' => return Some((Token::RightBracket, pos)),
|
||||||
|
|
||||||
'+' => {
|
'+' => {
|
||||||
return Some((
|
return Some((
|
||||||
match self.char_stream.peek() {
|
match self.char_stream.peek() {
|
||||||
@ -1146,7 +1217,7 @@ pub fn lex(input: &str) -> TokenIterator<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_precedence(token: &Token) -> i8 {
|
fn get_precedence(token: &Token) -> u8 {
|
||||||
match *token {
|
match *token {
|
||||||
Token::Equals
|
Token::Equals
|
||||||
| Token::PlusAssign
|
| Token::PlusAssign
|
||||||
@ -1161,28 +1232,49 @@ fn get_precedence(token: &Token) -> i8 {
|
|||||||
| Token::ModuloAssign
|
| Token::ModuloAssign
|
||||||
| Token::PowerOfAssign => 10,
|
| Token::PowerOfAssign => 10,
|
||||||
|
|
||||||
Token::Or | Token::XOr | Token::Pipe => 11,
|
Token::Or | Token::XOr | Token::Pipe => 50,
|
||||||
|
|
||||||
Token::And | Token::Ampersand => 12,
|
Token::And | Token::Ampersand => 60,
|
||||||
|
|
||||||
Token::LessThan
|
Token::LessThan
|
||||||
| Token::LessThanEqualsTo
|
| Token::LessThanEqualsTo
|
||||||
| Token::GreaterThan
|
| Token::GreaterThan
|
||||||
| Token::GreaterThanEqualsTo
|
| Token::GreaterThanEqualsTo
|
||||||
| Token::EqualsTo
|
| Token::EqualsTo
|
||||||
| Token::NotEqualsTo => 15,
|
| Token::NotEqualsTo => 70,
|
||||||
|
|
||||||
Token::Plus | Token::Minus => 20,
|
Token::Plus | Token::Minus => 80,
|
||||||
|
|
||||||
Token::Divide | Token::Multiply | Token::PowerOf => 40,
|
Token::Divide | Token::Multiply | Token::PowerOf => 90,
|
||||||
|
|
||||||
Token::LeftShift | Token::RightShift => 50,
|
Token::LeftShift | Token::RightShift => 100,
|
||||||
|
|
||||||
Token::Modulo => 60,
|
Token::Modulo => 110,
|
||||||
|
|
||||||
Token::Period => 100,
|
Token::Period => 120,
|
||||||
|
|
||||||
_ => -1,
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_bind_right(token: &Token) -> bool {
|
||||||
|
match *token {
|
||||||
|
Token::Equals
|
||||||
|
| Token::PlusAssign
|
||||||
|
| Token::MinusAssign
|
||||||
|
| Token::MultiplyAssign
|
||||||
|
| Token::DivideAssign
|
||||||
|
| Token::LeftShiftAssign
|
||||||
|
| Token::RightShiftAssign
|
||||||
|
| Token::AndAssign
|
||||||
|
| Token::OrAssign
|
||||||
|
| Token::XOrAssign
|
||||||
|
| Token::ModuloAssign
|
||||||
|
| Token::PowerOfAssign => true,
|
||||||
|
|
||||||
|
Token::Period => true,
|
||||||
|
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1360,10 +1452,10 @@ fn parse_ident_expr<'a>(
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Some(&(Token::LeftBracket, pos)) => {
|
Some(&(Token::LeftBracket, pos)) => {
|
||||||
input.next();
|
input.next();
|
||||||
parse_index_expr(Box::new(Expr::Identifier(id, begin)), input, pos)
|
parse_index_expr(Box::new(Expr::Variable(id, begin)), input, pos)
|
||||||
}
|
}
|
||||||
Some(_) => Ok(Expr::Identifier(id, begin)),
|
Some(_) => Ok(Expr::Variable(id, begin)),
|
||||||
None => Ok(Expr::Identifier(id, Position::eof())),
|
None => Ok(Expr::Variable(id, Position::eof())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1522,37 +1614,75 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> {
|
fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> {
|
||||||
fn valid_assignment_chain(expr: &Expr) -> (bool, Position) {
|
fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option<ParseError> {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Identifier(_, pos) => (true, *pos),
|
Expr::Variable(_, _) => {
|
||||||
|
assert!(is_top, "property expected but gets variable");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Expr::Property(_, _) => {
|
||||||
|
assert!(!is_top, "variable expected but gets property");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, _) if idx_lhs.is_identifier() => (true, idx_lhs.position()),
|
Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) => {
|
||||||
|
assert!(is_top, "property expected but gets variable");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, _) => (false, idx_lhs.position()),
|
Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) => {
|
||||||
|
assert!(!is_top, "variable expected but gets property");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Index(idx_lhs, _, pos) => Some(ParseError::new(
|
||||||
|
match idx_lhs.as_ref() {
|
||||||
|
Expr::Index(_, _, _) => ParseErrorType::AssignmentToCopy,
|
||||||
|
_ => ParseErrorType::AssignmentToInvalidLHS,
|
||||||
|
},
|
||||||
|
*pos,
|
||||||
|
)),
|
||||||
|
|
||||||
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
|
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
|
||||||
Expr::Identifier(_, _) => valid_assignment_chain(dot_rhs),
|
Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false),
|
||||||
|
Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, _) if idx_lhs.is_identifier() => {
|
Expr::Index(idx_lhs, _, _)
|
||||||
valid_assignment_chain(dot_rhs)
|
if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) && is_top =>
|
||||||
|
{
|
||||||
|
valid_assignment_chain(dot_rhs, false)
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, _, _) => (false, idx_lhs.position()),
|
Expr::Index(idx_lhs, _, _)
|
||||||
|
if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) && !is_top =>
|
||||||
|
{
|
||||||
|
valid_assignment_chain(dot_rhs, false)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Index(idx_lhs, _, _) => Some(ParseError::new(
|
||||||
|
ParseErrorType::AssignmentToCopy,
|
||||||
|
idx_lhs.position(),
|
||||||
|
)),
|
||||||
|
|
||||||
_ => (false, dot_lhs.position()),
|
expr => panic!("unexpected dot expression {:#?}", expr),
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => (false, expr.position()),
|
_ => Some(ParseError::new(
|
||||||
|
ParseErrorType::AssignmentToInvalidLHS,
|
||||||
|
expr.position(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//println!("{:#?} = {:#?}", lhs, rhs);
|
//println!("{:#?} = {:#?}", lhs, rhs);
|
||||||
|
|
||||||
match valid_assignment_chain(&lhs) {
|
match valid_assignment_chain(&lhs, true) {
|
||||||
(true, _) => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
|
None => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
|
||||||
(false, pos) => Err(ParseError::new(PERR::AssignmentToInvalidLHS, pos)),
|
Some(err) => Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1573,39 +1703,47 @@ fn parse_op_assignment(
|
|||||||
|
|
||||||
fn parse_binary_op<'a>(
|
fn parse_binary_op<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
precedence: i8,
|
parent_precedence: u8,
|
||||||
lhs: Expr,
|
lhs: Expr,
|
||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
let mut current_lhs = lhs;
|
let mut current_lhs = lhs;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut current_precedence = -1;
|
let (current_precedence, bind_right) = if let Some(&(ref current_op, _)) = input.peek() {
|
||||||
|
(get_precedence(current_op), is_bind_right(current_op))
|
||||||
|
} else {
|
||||||
|
(0, false)
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(&(ref current_op, _)) = input.peek() {
|
// Bind left to the parent lhs expression if precedence is higher
|
||||||
current_precedence = get_precedence(current_op);
|
// If same precedence, then check if the operator binds right
|
||||||
}
|
if current_precedence < parent_precedence
|
||||||
|
|| (current_precedence == parent_precedence && !bind_right)
|
||||||
if current_precedence < precedence {
|
{
|
||||||
return Ok(current_lhs);
|
return Ok(current_lhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((op_token, pos)) = input.next() {
|
if let Some((op_token, pos)) = input.next() {
|
||||||
input.peek();
|
input.peek();
|
||||||
|
|
||||||
let mut rhs = parse_unary(input)?;
|
let rhs = parse_unary(input)?;
|
||||||
|
|
||||||
let mut next_precedence = -1;
|
let next_precedence = if let Some(&(ref next_op, _)) = input.peek() {
|
||||||
|
get_precedence(next_op)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(&(ref next_op, _)) = input.peek() {
|
// Bind to right if the next operator has higher precedence
|
||||||
next_precedence = get_precedence(next_op);
|
// If same precedence, then check if the operator binds right
|
||||||
}
|
let rhs = if (current_precedence == next_precedence && bind_right)
|
||||||
|
|| current_precedence < next_precedence
|
||||||
if current_precedence < next_precedence {
|
{
|
||||||
rhs = parse_binary_op(input, current_precedence + 1, rhs)?;
|
parse_binary_op(input, current_precedence, rhs)?
|
||||||
} else if current_precedence >= 100 {
|
} else {
|
||||||
// Always bind right to left for precedence over 100
|
// Otherwise bind to left (even if next operator has the same precedence)
|
||||||
rhs = parse_binary_op(input, current_precedence, rhs)?;
|
rhs
|
||||||
}
|
};
|
||||||
|
|
||||||
current_lhs = match op_token {
|
current_lhs = match op_token {
|
||||||
Token::Plus => Expr::FunctionCall("+".into(), vec![current_lhs, rhs], None, pos),
|
Token::Plus => Expr::FunctionCall("+".into(), vec![current_lhs, rhs], None, pos),
|
||||||
@ -1619,7 +1757,29 @@ fn parse_binary_op<'a>(
|
|||||||
Token::PlusAssign => parse_op_assignment("+", current_lhs, rhs, pos)?,
|
Token::PlusAssign => parse_op_assignment("+", current_lhs, rhs, pos)?,
|
||||||
Token::MinusAssign => parse_op_assignment("-", current_lhs, rhs, pos)?,
|
Token::MinusAssign => parse_op_assignment("-", current_lhs, rhs, pos)?,
|
||||||
|
|
||||||
Token::Period => Expr::Dot(Box::new(current_lhs), Box::new(rhs), pos),
|
Token::Period => {
|
||||||
|
fn change_var_to_property(expr: Expr) -> Expr {
|
||||||
|
match expr {
|
||||||
|
Expr::Dot(lhs, rhs, pos) => Expr::Dot(
|
||||||
|
Box::new(change_var_to_property(*lhs)),
|
||||||
|
Box::new(change_var_to_property(*rhs)),
|
||||||
|
pos,
|
||||||
|
),
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Index(lhs, idx, pos) => {
|
||||||
|
Expr::Index(Box::new(change_var_to_property(*lhs)), idx, pos)
|
||||||
|
}
|
||||||
|
Expr::Variable(s, pos) => Expr::Property(s, pos),
|
||||||
|
expr => expr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr::Dot(
|
||||||
|
Box::new(current_lhs),
|
||||||
|
Box::new(change_var_to_property(rhs)),
|
||||||
|
pos,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Comparison operators default to false when passed invalid operands
|
// Comparison operators default to false when passed invalid operands
|
||||||
Token::EqualsTo => Expr::FunctionCall(
|
Token::EqualsTo => Expr::FunctionCall(
|
||||||
@ -1696,7 +1856,7 @@ fn parse_binary_op<'a>(
|
|||||||
|
|
||||||
fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
||||||
let lhs = parse_unary(input)?;
|
let lhs = parse_unary(input)?;
|
||||||
parse_binary_op(input, 0, lhs)
|
parse_binary_op(input, 1, lhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_if<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_if<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||||
@ -1709,9 +1869,10 @@ fn parse_if<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseEr
|
|||||||
Some(&(Token::Else, _)) => {
|
Some(&(Token::Else, _)) => {
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
let else_body = match input.peek() {
|
let else_body = if matches!(input.peek(), Some(&(Token::If, _))) {
|
||||||
Some(&(Token::If, _)) => parse_if(input)?,
|
parse_if(input)?
|
||||||
_ => parse_block(input)?,
|
} else {
|
||||||
|
parse_block(input)?
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Stmt::IfElse(
|
Ok(Stmt::IfElse(
|
||||||
@ -1746,14 +1907,17 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
|||||||
|
|
||||||
let name = match input.next() {
|
let name = match input.next() {
|
||||||
Some((Token::Identifier(s), _)) => s,
|
Some((Token::Identifier(s), _)) => s,
|
||||||
Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)),
|
Some((Token::LexError(s), pos)) => {
|
||||||
None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())),
|
return Err(ParseError::new(PERR::BadInput(s.to_string()), pos))
|
||||||
|
}
|
||||||
|
Some((_, pos)) => return Err(ParseError::new(PERR::VariableExpected, pos)),
|
||||||
|
None => return Err(ParseError::new(PERR::VariableExpected, Position::eof())),
|
||||||
};
|
};
|
||||||
|
|
||||||
match input.next() {
|
match input.next() {
|
||||||
Some((Token::In, _)) => {}
|
Some((Token::In, _)) => (),
|
||||||
Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)),
|
Some((_, pos)) => return Err(ParseError::new(PERR::MissingIn, pos)),
|
||||||
None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())),
|
None => return Err(ParseError::new(PERR::MissingIn, Position::eof())),
|
||||||
}
|
}
|
||||||
|
|
||||||
let expr = parse_expr(input)?;
|
let expr = parse_expr(input)?;
|
||||||
@ -1763,7 +1927,10 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
|||||||
Ok(Stmt::For(name, Box::new(expr), Box::new(body)))
|
Ok(Stmt::For(name, Box::new(expr), Box::new(body)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_var<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_var<'a>(
|
||||||
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
var_type: VariableType,
|
||||||
|
) -> Result<Stmt, ParseError> {
|
||||||
let pos = match input.next() {
|
let pos = match input.next() {
|
||||||
Some((_, tok_pos)) => tok_pos,
|
Some((_, tok_pos)) => tok_pos,
|
||||||
_ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())),
|
_ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())),
|
||||||
@ -1771,17 +1938,31 @@ fn parse_var<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
|||||||
|
|
||||||
let name = match input.next() {
|
let name = match input.next() {
|
||||||
Some((Token::Identifier(s), _)) => s,
|
Some((Token::Identifier(s), _)) => s,
|
||||||
Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)),
|
Some((Token::LexError(s), pos)) => {
|
||||||
None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())),
|
return Err(ParseError::new(PERR::BadInput(s.to_string()), pos))
|
||||||
|
}
|
||||||
|
Some((_, pos)) => return Err(ParseError::new(PERR::VariableExpected, pos)),
|
||||||
|
None => return Err(ParseError::new(PERR::VariableExpected, Position::eof())),
|
||||||
};
|
};
|
||||||
|
|
||||||
match input.peek() {
|
if matches!(input.peek(), Some(&(Token::Equals, _))) {
|
||||||
Some(&(Token::Equals, _)) => {
|
|
||||||
input.next();
|
input.next();
|
||||||
let init_value = parse_expr(input)?;
|
let init_value = parse_expr(input)?;
|
||||||
Ok(Stmt::Let(name, Some(Box::new(init_value)), pos))
|
|
||||||
|
match var_type {
|
||||||
|
VariableType::Normal => Ok(Stmt::Let(name, Some(Box::new(init_value)), pos)),
|
||||||
|
|
||||||
|
VariableType::Constant if init_value.is_constant() => {
|
||||||
|
Ok(Stmt::Const(name, Box::new(init_value), pos))
|
||||||
}
|
}
|
||||||
_ => Ok(Stmt::Let(name, None, pos)),
|
// Constants require a constant expression
|
||||||
|
VariableType::Constant => Err(ParseError(
|
||||||
|
PERR::ForbiddenConstantExpr(name.to_string()),
|
||||||
|
init_value.position(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(Stmt::Let(name, None, pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1796,6 +1977,8 @@ fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
|
|||||||
|
|
||||||
match input.peek() {
|
match input.peek() {
|
||||||
Some(&(Token::RightBrace, _)) => (), // empty block
|
Some(&(Token::RightBrace, _)) => (), // empty block
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
Some(&(Token::Fn, pos)) => return Err(ParseError::new(PERR::WrongFnDefinition, pos)),
|
Some(&(Token::Fn, pos)) => return Err(ParseError::new(PERR::WrongFnDefinition, pos)),
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
@ -1849,7 +2032,7 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
|
|||||||
let return_type = match token {
|
let return_type = match token {
|
||||||
Token::Return => ReturnType::Return,
|
Token::Return => ReturnType::Return,
|
||||||
Token::Throw => ReturnType::Exception,
|
Token::Throw => ReturnType::Exception,
|
||||||
_ => panic!("unexpected token!"),
|
_ => panic!("token should be return or throw"),
|
||||||
};
|
};
|
||||||
|
|
||||||
input.next();
|
input.next();
|
||||||
@ -1867,7 +2050,8 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(&(Token::LeftBrace, _)) => parse_block(input),
|
Some(&(Token::LeftBrace, _)) => parse_block(input),
|
||||||
Some(&(Token::Let, _)) => parse_var(input),
|
Some(&(Token::Let, _)) => parse_var(input, VariableType::Normal),
|
||||||
|
Some(&(Token::Const, _)) => parse_var(input, VariableType::Constant),
|
||||||
_ => parse_expr_stmt(input),
|
_ => parse_expr_stmt(input),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1900,11 +2084,10 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
|
|||||||
|
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|
||||||
match input.peek() {
|
if matches!(input.peek(), Some(&(Token::RightParen, _))) {
|
||||||
Some(&(Token::RightParen, _)) => {
|
|
||||||
input.next();
|
input.next();
|
||||||
}
|
} else {
|
||||||
_ => loop {
|
loop {
|
||||||
match input.next() {
|
match input.next() {
|
||||||
Some((Token::RightParen, _)) => break,
|
Some((Token::RightParen, _)) => break,
|
||||||
Some((Token::Comma, _)) => (),
|
Some((Token::Comma, _)) => (),
|
||||||
@ -1928,7 +2111,7 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = parse_block(input)?;
|
let body = parse_block(input)?;
|
||||||
@ -1941,13 +2124,10 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_top_level<'a>(
|
fn parse_top_level<'a, 'e>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
optimize_ast: bool,
|
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
|
||||||
) -> Result<AST, ParseError> {
|
|
||||||
let mut statements = Vec::<Stmt>::new();
|
let mut statements = Vec::<Stmt>::new();
|
||||||
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
let mut functions = Vec::<FnDef>::new();
|
let mut functions = Vec::<FnDef>::new();
|
||||||
|
|
||||||
while input.peek().is_some() {
|
while input.peek().is_some() {
|
||||||
@ -1971,30 +2151,79 @@ fn parse_top_level<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(AST(
|
Ok((statements, functions))
|
||||||
if optimize_ast {
|
|
||||||
optimize(statements)
|
|
||||||
} else {
|
|
||||||
statements
|
|
||||||
},
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
functions
|
|
||||||
.into_iter()
|
|
||||||
.map(|mut fn_def| {
|
|
||||||
if optimize_ast {
|
|
||||||
let pos = fn_def.body.position();
|
|
||||||
let mut body = optimize(vec![fn_def.body]);
|
|
||||||
fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos));
|
|
||||||
}
|
|
||||||
Arc::new(fn_def)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse<'a>(
|
pub fn parse<'a, 'e>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
optimize_ast: bool,
|
engine: &Engine<'e>,
|
||||||
|
scope: &Scope,
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
parse_top_level(input, optimize_ast)
|
let (statements, functions) = parse_top_level(input)?;
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
optimize_ast(engine, scope, statements, functions),
|
||||||
|
#[cfg(feature = "no_optimize")]
|
||||||
|
AST(statements, functions.into_iter().map(Arc::new).collect()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option<Expr>, Dynamic) {
|
||||||
|
if value.is::<INT>() {
|
||||||
|
let value2 = value.clone();
|
||||||
|
(
|
||||||
|
Some(Expr::IntegerConstant(
|
||||||
|
*value.downcast::<INT>().expect("value should be INT"),
|
||||||
|
pos,
|
||||||
|
)),
|
||||||
|
value2,
|
||||||
|
)
|
||||||
|
} else if value.is::<char>() {
|
||||||
|
let value2 = value.clone();
|
||||||
|
(
|
||||||
|
Some(Expr::CharConstant(
|
||||||
|
*value.downcast::<char>().expect("value should be char"),
|
||||||
|
pos,
|
||||||
|
)),
|
||||||
|
value2,
|
||||||
|
)
|
||||||
|
} else if value.is::<String>() {
|
||||||
|
let value2 = value.clone();
|
||||||
|
(
|
||||||
|
Some(Expr::StringConstant(
|
||||||
|
*value.downcast::<String>().expect("value should be String"),
|
||||||
|
pos,
|
||||||
|
)),
|
||||||
|
value2,
|
||||||
|
)
|
||||||
|
} else if value.is::<bool>() {
|
||||||
|
let value2 = value.clone();
|
||||||
|
(
|
||||||
|
Some(
|
||||||
|
if *value.downcast::<bool>().expect("value should be bool") {
|
||||||
|
Expr::True(pos)
|
||||||
|
} else {
|
||||||
|
Expr::False(pos)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
value2,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
{
|
||||||
|
if value.is::<FLOAT>() {
|
||||||
|
let value2 = value.clone();
|
||||||
|
return (
|
||||||
|
Some(Expr::FloatConstant(
|
||||||
|
*value.downcast::<FLOAT>().expect("value should be FLOAT"),
|
||||||
|
pos,
|
||||||
|
)),
|
||||||
|
value2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(None, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,8 @@ pub enum EvalAltResult {
|
|||||||
ErrorVariableNotFound(String, Position),
|
ErrorVariableNotFound(String, Position),
|
||||||
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
||||||
ErrorAssignmentToUnknownLHS(Position),
|
ErrorAssignmentToUnknownLHS(Position),
|
||||||
|
/// Assignment to a constant variable.
|
||||||
|
ErrorAssignmentToConstant(String, Position),
|
||||||
/// Returned type is not the same as the required output type.
|
/// Returned type is not the same as the required output type.
|
||||||
/// Wrapped value is the type of the actual result.
|
/// Wrapped value is the type of the actual result.
|
||||||
ErrorMismatchOutputType(String, Position),
|
ErrorMismatchOutputType(String, Position),
|
||||||
@ -59,10 +61,10 @@ pub enum EvalAltResult {
|
|||||||
Return(Dynamic, Position),
|
Return(Dynamic, Position),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for EvalAltResult {
|
impl EvalAltResult {
|
||||||
fn description(&self) -> &str {
|
pub(crate) fn desc(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Self::ErrorParsing(p) => p.description(),
|
Self::ErrorParsing(p) => p.desc(),
|
||||||
Self::ErrorFunctionNotFound(_, _) => "Function not found",
|
Self::ErrorFunctionNotFound(_, _) => "Function not found",
|
||||||
Self::ErrorFunctionArgsMismatch(_, _, _, _) => {
|
Self::ErrorFunctionArgsMismatch(_, _, _, _) => {
|
||||||
"Function call with wrong number of arguments"
|
"Function call with wrong number of arguments"
|
||||||
@ -89,6 +91,7 @@ impl Error for EvalAltResult {
|
|||||||
Self::ErrorAssignmentToUnknownLHS(_) => {
|
Self::ErrorAssignmentToUnknownLHS(_) => {
|
||||||
"Assignment to an unsupported left-hand side expression"
|
"Assignment to an unsupported left-hand side expression"
|
||||||
}
|
}
|
||||||
|
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
|
||||||
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
|
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
|
||||||
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
|
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
|
||||||
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
||||||
@ -98,15 +101,13 @@ impl Error for EvalAltResult {
|
|||||||
Self::Return(_, _) => "[Not Error] Function returns value",
|
Self::Return(_, _) => "[Not Error] Function returns value",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cause(&self) -> Option<&dyn Error> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Error for EvalAltResult {}
|
||||||
|
|
||||||
impl fmt::Display for EvalAltResult {
|
impl fmt::Display for EvalAltResult {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let desc = self.description();
|
let desc = self.desc();
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
|
Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
|
||||||
@ -116,6 +117,7 @@ impl fmt::Display for EvalAltResult {
|
|||||||
Self::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos),
|
Self::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos),
|
||||||
Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos),
|
Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos),
|
||||||
Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos),
|
Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos),
|
||||||
|
Self::ErrorAssignmentToConstant(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos),
|
||||||
Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
|
Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
|
||||||
Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos),
|
Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos),
|
||||||
Self::ErrorDotExpr(_, pos) => write!(f, "{} ({})", desc, pos),
|
Self::ErrorDotExpr(_, pos) => write!(f, "{} ({})", desc, pos),
|
||||||
@ -213,6 +215,7 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorFor(pos)
|
| Self::ErrorFor(pos)
|
||||||
| Self::ErrorVariableNotFound(_, pos)
|
| Self::ErrorVariableNotFound(_, pos)
|
||||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||||
|
| Self::ErrorAssignmentToConstant(_, pos)
|
||||||
| Self::ErrorMismatchOutputType(_, pos)
|
| Self::ErrorMismatchOutputType(_, pos)
|
||||||
| Self::ErrorDotExpr(_, pos)
|
| Self::ErrorDotExpr(_, pos)
|
||||||
| Self::ErrorArithmetic(_, pos)
|
| Self::ErrorArithmetic(_, pos)
|
||||||
@ -238,6 +241,7 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorFor(ref mut pos)
|
| Self::ErrorFor(ref mut pos)
|
||||||
| Self::ErrorVariableNotFound(_, ref mut pos)
|
| Self::ErrorVariableNotFound(_, ref mut pos)
|
||||||
| Self::ErrorAssignmentToUnknownLHS(ref mut pos)
|
| Self::ErrorAssignmentToUnknownLHS(ref mut pos)
|
||||||
|
| Self::ErrorAssignmentToConstant(_, ref mut pos)
|
||||||
| Self::ErrorMismatchOutputType(_, ref mut pos)
|
| Self::ErrorMismatchOutputType(_, ref mut pos)
|
||||||
| Self::ErrorDotExpr(_, ref mut pos)
|
| Self::ErrorDotExpr(_, ref mut pos)
|
||||||
| Self::ErrorArithmetic(_, ref mut pos)
|
| Self::ErrorArithmetic(_, ref mut pos)
|
||||||
|
156
src/scope.rs
156
src/scope.rs
@ -1,11 +1,33 @@
|
|||||||
//! Module that defines the `Scope` type representing a function call-stack scope.
|
//! Module that defines the `Scope` type representing a function call-stack scope.
|
||||||
|
|
||||||
use crate::any::{Any, Dynamic};
|
use crate::any::{Any, Dynamic};
|
||||||
|
use crate::parser::{map_dynamic_to_expr, Expr, Position};
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// A type containing information about current scope.
|
/// Type of a variable in the Scope.
|
||||||
/// Useful for keeping state between `Engine` runs.
|
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
|
||||||
|
pub enum VariableType {
|
||||||
|
/// Normal variable.
|
||||||
|
Normal,
|
||||||
|
/// Immutable constant value.
|
||||||
|
Constant,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An entry in the Scope.
|
||||||
|
pub struct ScopeEntry<'a> {
|
||||||
|
/// Name of the variable.
|
||||||
|
pub name: Cow<'a, str>,
|
||||||
|
/// Type of the variable.
|
||||||
|
pub var_type: VariableType,
|
||||||
|
/// Current value of the variable.
|
||||||
|
pub value: Dynamic,
|
||||||
|
/// A constant expression if the initial value matches one of the recognized types.
|
||||||
|
pub expr: Option<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type containing information about the current scope.
|
||||||
|
/// Useful for keeping state between `Engine` evaluation runs.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -25,7 +47,7 @@ use std::borrow::Cow;
|
|||||||
///
|
///
|
||||||
/// When searching for variables, newly-added variables are found before similarly-named but older variables,
|
/// When searching for variables, newly-added variables are found before similarly-named but older variables,
|
||||||
/// allowing for automatic _shadowing_ of variables.
|
/// allowing for automatic _shadowing_ of variables.
|
||||||
pub struct Scope<'a>(Vec<(Cow<'a, str>, Dynamic)>);
|
pub struct Scope<'a>(Vec<ScopeEntry<'a>>);
|
||||||
|
|
||||||
impl<'a> Scope<'a> {
|
impl<'a> Scope<'a> {
|
||||||
/// Create a new Scope.
|
/// Create a new Scope.
|
||||||
@ -44,18 +66,67 @@ impl<'a> Scope<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add (push) a new variable to the Scope.
|
/// Add (push) a new variable to the Scope.
|
||||||
pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, key: K, value: T) {
|
pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, name: K, value: T) {
|
||||||
self.0.push((key.into(), Box::new(value)));
|
let value = value.into_dynamic();
|
||||||
|
|
||||||
|
// Map into constant expressions
|
||||||
|
//let (expr, value) = map_dynamic_to_expr(value, Position::none());
|
||||||
|
|
||||||
|
self.0.push(ScopeEntry {
|
||||||
|
name: name.into(),
|
||||||
|
var_type: VariableType::Normal,
|
||||||
|
value,
|
||||||
|
expr: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add (push) a new variable to the Scope.
|
/// Add (push) a new constant to the Scope.
|
||||||
pub(crate) fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, key: K, value: Dynamic) {
|
///
|
||||||
self.0.push((key.into(), value));
|
/// Constants are immutable and cannot be assigned to. Their values never change.
|
||||||
|
/// Constants propagation is a technique used to optimize an AST.
|
||||||
|
/// However, in order to be used for optimization, constants must be in one of the recognized types:
|
||||||
|
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
|
||||||
|
pub fn push_constant<K: Into<Cow<'a, str>>, T: Any>(&mut self, name: K, value: T) {
|
||||||
|
let value = value.into_dynamic();
|
||||||
|
|
||||||
|
// Map into constant expressions
|
||||||
|
let (expr, value) = map_dynamic_to_expr(value, Position::none());
|
||||||
|
|
||||||
|
self.0.push(ScopeEntry {
|
||||||
|
name: name.into(),
|
||||||
|
var_type: VariableType::Constant,
|
||||||
|
value,
|
||||||
|
expr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add (push) a new variable with a `Dynamic` value to the Scope.
|
||||||
|
pub(crate) fn push_dynamic<K: Into<Cow<'a, str>>>(
|
||||||
|
&mut self,
|
||||||
|
name: K,
|
||||||
|
var_type: VariableType,
|
||||||
|
value: Dynamic,
|
||||||
|
) {
|
||||||
|
let (expr, value) = map_dynamic_to_expr(value, Position::none());
|
||||||
|
|
||||||
|
self.0.push(ScopeEntry {
|
||||||
|
name: name.into(),
|
||||||
|
var_type,
|
||||||
|
value,
|
||||||
|
expr,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove (pop) the last variable from the Scope.
|
/// Remove (pop) the last variable from the Scope.
|
||||||
pub fn pop(&mut self) -> Option<(String, Dynamic)> {
|
pub fn pop(&mut self) -> Option<(String, VariableType, Dynamic)> {
|
||||||
self.0.pop().map(|(key, value)| (key.to_string(), value))
|
self.0.pop().map(
|
||||||
|
|ScopeEntry {
|
||||||
|
name,
|
||||||
|
var_type,
|
||||||
|
value,
|
||||||
|
..
|
||||||
|
}| (name.to_string(), var_type, value),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Truncate (rewind) the Scope to a previous size.
|
/// Truncate (rewind) the Scope to a previous size.
|
||||||
@ -64,13 +135,23 @@ impl<'a> Scope<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Find a variable in the Scope, starting from the last.
|
/// Find a variable in the Scope, starting from the last.
|
||||||
pub fn get(&self, key: &str) -> Option<(usize, &str, Dynamic)> {
|
pub fn get(&self, key: &str) -> Option<(usize, &str, VariableType, Dynamic)> {
|
||||||
self.0
|
self.0
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.rev() // Always search a Scope in reverse order
|
.rev() // Always search a Scope in reverse order
|
||||||
.find(|(_, (name, _))| name == key)
|
.find(|(_, ScopeEntry { name, .. })| name == key)
|
||||||
.map(|(i, (name, value))| (i, name.as_ref(), value.clone()))
|
.map(
|
||||||
|
|(
|
||||||
|
i,
|
||||||
|
ScopeEntry {
|
||||||
|
name,
|
||||||
|
var_type,
|
||||||
|
value,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
)| (i, name.as_ref(), *var_type, value.clone()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of a variable in the Scope, starting from the last.
|
/// Get the value of a variable in the Scope, starting from the last.
|
||||||
@ -79,53 +160,50 @@ impl<'a> Scope<'a> {
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.rev() // Always search a Scope in reverse order
|
.rev() // Always search a Scope in reverse order
|
||||||
.find(|(_, (name, _))| name == key)
|
.find(|(_, ScopeEntry { name, .. })| name == key)
|
||||||
.and_then(|(_, (_, value))| value.downcast_ref::<T>())
|
.and_then(|(_, ScopeEntry { value, .. })| value.downcast_ref::<T>())
|
||||||
.map(|value| value.clone())
|
.map(T::clone)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to a variable in the Scope.
|
/// Get a mutable reference to a variable in the Scope.
|
||||||
pub(crate) fn get_mut(&mut self, key: &str, index: usize) -> &mut Dynamic {
|
pub(crate) fn get_mut(&mut self, name: &str, index: usize) -> &mut Dynamic {
|
||||||
let entry = self.0.get_mut(index).expect("invalid index in Scope");
|
let entry = self.0.get_mut(index).expect("invalid index in Scope");
|
||||||
|
|
||||||
assert_eq!(entry.0, key, "incorrect key at Scope entry");
|
assert_ne!(
|
||||||
|
entry.var_type,
|
||||||
|
VariableType::Constant,
|
||||||
|
"get mut of constant variable"
|
||||||
|
);
|
||||||
|
assert_eq!(entry.name, name, "incorrect key at Scope entry");
|
||||||
|
|
||||||
&mut entry.1
|
&mut entry.value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to a variable in the Scope and downcast it to a specific type
|
/// Get a mutable reference to a variable in the Scope and downcast it to a specific type
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, key: &str, index: usize) -> &mut T {
|
pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, name: &str, index: usize) -> &mut T {
|
||||||
self.get_mut(key, index)
|
self.get_mut(name, index)
|
||||||
.downcast_mut::<T>()
|
.downcast_mut::<T>()
|
||||||
.expect("wrong type cast")
|
.expect("wrong type cast")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an iterator to variables in the Scope.
|
/// Get an iterator to variables in the Scope.
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&str, &Dynamic)> {
|
pub fn iter(&self) -> impl Iterator<Item = &ScopeEntry> {
|
||||||
self.0
|
self.0.iter().rev() // Always search a Scope in reverse order
|
||||||
.iter()
|
|
||||||
.rev() // Always search a Scope in reverse order
|
|
||||||
.map(|(key, value)| (key.as_ref(), value))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Get a mutable iterator to variables in the Scope.
|
|
||||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Dynamic)> {
|
|
||||||
self.0
|
|
||||||
.iter_mut()
|
|
||||||
.rev() // Always search a Scope in reverse order
|
|
||||||
.map(|(key, value)| (key.as_ref(), value))
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, K> std::iter::Extend<(K, Dynamic)> for Scope<'a>
|
impl<'a, K> std::iter::Extend<(K, VariableType, Dynamic)> for Scope<'a>
|
||||||
where
|
where
|
||||||
K: Into<Cow<'a, str>>,
|
K: Into<Cow<'a, str>>,
|
||||||
{
|
{
|
||||||
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
|
fn extend<T: IntoIterator<Item = (K, VariableType, Dynamic)>>(&mut self, iter: T) {
|
||||||
self.0
|
self.0
|
||||||
.extend(iter.into_iter().map(|(key, value)| (key.into(), value)));
|
.extend(iter.into_iter().map(|(name, var_type, value)| ScopeEntry {
|
||||||
|
name: name.into(),
|
||||||
|
var_type,
|
||||||
|
value,
|
||||||
|
expr: None,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,10 @@ fn test_arrays() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("let x = [1, 2, 3]; x[1]")?, 2);
|
assert_eq!(engine.eval::<INT>("let x = [1, 2, 3]; x[1]")?, 2);
|
||||||
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5);
|
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<char>(r#"let y = [1, [ 42, 88, "93" ], 3]; y[1][2][1]"#)?,
|
||||||
|
'3'
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -48,10 +52,12 @@ fn test_array_with_structs() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
"let a = [new_ts()]; \
|
r"
|
||||||
a[0].x = 100; \
|
let a = [new_ts()];
|
||||||
a[0].update(); \
|
a[0].x = 100;
|
||||||
a[0].x",
|
a[0].update();
|
||||||
|
a[0].x
|
||||||
|
"
|
||||||
)?,
|
)?,
|
||||||
1100
|
1100
|
||||||
);
|
);
|
||||||
|
@ -1,25 +1,24 @@
|
|||||||
#![cfg(not(feature = "no_stdlib"))]
|
|
||||||
#![cfg(not(feature = "no_function"))]
|
#![cfg(not(feature = "no_function"))]
|
||||||
use rhai::{Engine, EvalAltResult, INT};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_engine_call_fn() -> Result<(), EvalAltResult> {
|
fn test_call_fn() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
engine.consume(
|
engine.consume(
|
||||||
|
true,
|
||||||
r"
|
r"
|
||||||
fn hello(x, y) {
|
fn hello(x, y) {
|
||||||
x.len() + y
|
x + y
|
||||||
}
|
}
|
||||||
fn hello(x) {
|
fn hello(x) {
|
||||||
x * 2
|
x * 2
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
true,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let r: i64 = engine.call_fn("hello", (String::from("abc"), 123 as INT))?;
|
let r: i64 = engine.call_fn("hello", (42 as INT, 123 as INT))?;
|
||||||
assert_eq!(r, 126);
|
assert_eq!(r, 165);
|
||||||
|
|
||||||
let r: i64 = engine.call_fn("hello", 123 as INT)?;
|
let r: i64 = engine.call_fn("hello", 123 as INT)?;
|
||||||
assert_eq!(r, 246);
|
assert_eq!(r, 246);
|
@ -11,12 +11,12 @@ fn test_chars() -> Result<(), EvalAltResult> {
|
|||||||
{
|
{
|
||||||
assert_eq!(engine.eval::<char>(r#"let x="hello"; x[2]"#)?, 'l');
|
assert_eq!(engine.eval::<char>(r#"let x="hello"; x[2]"#)?, 'l');
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<String>(r#"let x="hello"; x[2]='$'; x"#)?,
|
engine.eval::<String>(r#"let y="hello"; y[2]='$'; y"#)?,
|
||||||
"he$lo".to_string()
|
"he$lo".to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(engine.eval::<char>("'\\uhello'").is_err());
|
assert!(engine.eval::<char>(r"'\uhello'").is_err());
|
||||||
assert!(engine.eval::<char>("''").is_err());
|
assert!(engine.eval::<char>("''").is_err());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
21
tests/constants.rs
Normal file
21
tests/constants.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_constant() -> Result<(), EvalAltResult> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<INT>("const x = 123; x")?, 123);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
matches!(engine.eval::<INT>("const x = 123; x = 42;").expect_err("expects error"),
|
||||||
|
EvalAltResult::ErrorAssignmentToConstant(var, _) if var == "x")
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
assert!(
|
||||||
|
matches!(engine.eval::<INT>("const x = [1, 2, 3, 4, 5]; x[2] = 42;").expect_err("expects error"),
|
||||||
|
EvalAltResult::ErrorAssignmentToConstant(var, _) if var == "x")
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -6,12 +6,9 @@ fn test_decrement() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("let x = 10; x -= 7; x")?, 3);
|
assert_eq!(engine.eval::<INT>("let x = 10; x -= 7; x")?, 3);
|
||||||
|
|
||||||
let r = engine.eval::<String>("let s = \"test\"; s -= \"ing\"; s");
|
assert!(matches!(engine
|
||||||
|
.eval::<String>(r#"let s = "test"; s -= "ing"; s"#)
|
||||||
match r {
|
.expect_err("expects error"), EvalAltResult::ErrorFunctionNotFound(err, _) if err == "- (string, string)"));
|
||||||
Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "- (string, string)" => (),
|
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
110
tests/math.rs
110
tests/math.rs
@ -24,54 +24,76 @@ fn test_math() -> Result<(), EvalAltResult> {
|
|||||||
{
|
{
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
{
|
{
|
||||||
match engine.eval::<INT>("(-9223372036854775808).abs()") {
|
assert!(matches!(
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
engine
|
||||||
r => panic!("should return overflow error: {:?}", r),
|
.eval::<INT>("(-9223372036854775808).abs()")
|
||||||
}
|
.expect_err("expects negation overflow"),
|
||||||
match engine.eval::<INT>("9223372036854775807 + 1") {
|
EvalAltResult::ErrorArithmetic(_, _)
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
));
|
||||||
r => panic!("should return overflow error: {:?}", r),
|
assert!(matches!(
|
||||||
}
|
engine
|
||||||
match engine.eval::<INT>("-9223372036854775808 - 1") {
|
.eval::<INT>("9223372036854775807 + 1")
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
.expect_err("expects overflow"),
|
||||||
r => panic!("should return underflow error: {:?}", r),
|
EvalAltResult::ErrorArithmetic(_, _)
|
||||||
}
|
));
|
||||||
match engine.eval::<INT>("9223372036854775807 * 9223372036854775807") {
|
assert!(matches!(
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
engine
|
||||||
r => panic!("should return overflow error: {:?}", r),
|
.eval::<INT>("-9223372036854775808 - 1")
|
||||||
}
|
.expect_err("expects underflow"),
|
||||||
match engine.eval::<INT>("9223372036854775807 / 0") {
|
EvalAltResult::ErrorArithmetic(_, _)
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
));
|
||||||
r => panic!("should return division by zero error: {:?}", r),
|
assert!(matches!(
|
||||||
}
|
engine
|
||||||
match engine.eval::<INT>("9223372036854775807 % 0") {
|
.eval::<INT>("9223372036854775807 * 9223372036854775807")
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
.expect_err("expects overflow"),
|
||||||
r => panic!("should return division by zero error: {:?}", r),
|
EvalAltResult::ErrorArithmetic(_, _)
|
||||||
}
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
engine
|
||||||
|
.eval::<INT>("9223372036854775807 / 0")
|
||||||
|
.expect_err("expects division by zero"),
|
||||||
|
EvalAltResult::ErrorArithmetic(_, _)
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
engine
|
||||||
|
.eval::<INT>("9223372036854775807 % 0")
|
||||||
|
.expect_err("expects division by zero"),
|
||||||
|
EvalAltResult::ErrorArithmetic(_, _)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "only_i32")]
|
#[cfg(feature = "only_i32")]
|
||||||
{
|
{
|
||||||
match engine.eval::<INT>("2147483647 + 1") {
|
assert!(matches!(
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
engine
|
||||||
r => panic!("should return overflow error: {:?}", r),
|
.eval::<INT>("2147483647 + 1")
|
||||||
}
|
.expect_err("expects overflow"),
|
||||||
match engine.eval::<INT>("-2147483648 - 1") {
|
EvalAltResult::ErrorArithmetic(_, _)
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
));
|
||||||
r => panic!("should return underflow error: {:?}", r),
|
assert!(matches!(
|
||||||
}
|
engine
|
||||||
match engine.eval::<INT>("2147483647 * 2147483647") {
|
.eval::<INT>("-2147483648 - 1")
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
.expect_err("expects underflow"),
|
||||||
r => panic!("should return overflow error: {:?}", r),
|
EvalAltResult::ErrorArithmetic(_, _)
|
||||||
}
|
));
|
||||||
match engine.eval::<INT>("2147483647 / 0") {
|
assert!(matches!(
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
engine
|
||||||
r => panic!("should return division by zero error: {:?}", r),
|
.eval::<INT>("2147483647 * 2147483647")
|
||||||
}
|
.expect_err("expects overflow"),
|
||||||
match engine.eval::<INT>("2147483647 % 0") {
|
EvalAltResult::ErrorArithmetic(_, _)
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
));
|
||||||
r => panic!("should return division by zero error: {:?}", r),
|
assert!(matches!(
|
||||||
}
|
engine
|
||||||
|
.eval::<INT>("2147483647 / 0")
|
||||||
|
.expect_err("expects division by zero"),
|
||||||
|
EvalAltResult::ErrorArithmetic(_, _)
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
engine
|
||||||
|
.eval::<INT>("2147483647 % 0")
|
||||||
|
.expect_err("expects division by zero"),
|
||||||
|
EvalAltResult::ErrorArithmetic(_, _)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,10 @@ use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
|||||||
fn test_mismatched_op() {
|
fn test_mismatched_op() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
let r = engine.eval::<INT>("60 + \"hello\"");
|
assert!(
|
||||||
|
matches!(engine.eval::<INT>(r#"60 + "hello""#).expect_err("expects error"),
|
||||||
match r {
|
EvalAltResult::ErrorMismatchOutputType(err, _) if err == "string")
|
||||||
Err(EvalAltResult::ErrorMismatchOutputType(err, _)) if err == "string" => (),
|
);
|
||||||
_ => panic!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -30,15 +28,17 @@ fn test_mismatched_op_custom_type() {
|
|||||||
engine.register_type_with_name::<TestStruct>("TestStruct");
|
engine.register_type_with_name::<TestStruct>("TestStruct");
|
||||||
engine.register_fn("new_ts", TestStruct::new);
|
engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
|
||||||
let r = engine.eval::<INT>("60 + new_ts()");
|
let r = engine
|
||||||
|
.eval::<INT>("60 + new_ts()")
|
||||||
|
.expect_err("expects error");
|
||||||
|
|
||||||
match r {
|
|
||||||
#[cfg(feature = "only_i32")]
|
#[cfg(feature = "only_i32")]
|
||||||
Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "+ (i32, TestStruct)" => (),
|
assert!(
|
||||||
|
matches!(r, EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i32, TestStruct)")
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "+ (i64, TestStruct)" => (),
|
assert!(
|
||||||
|
matches!(r, EvalAltResult::ErrorFunctionNotFound(err, _) if err == "+ (i64, TestStruct)")
|
||||||
_ => panic!(),
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
32
tests/optimizer.rs
Normal file
32
tests/optimizer.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#![cfg(not(feature = "no_optimize"))]
|
||||||
|
|
||||||
|
use rhai::{Engine, EvalAltResult, OptimizationLevel, INT};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_optimizer() -> Result<(), EvalAltResult> {
|
||||||
|
fn run_test(engine: &mut Engine) -> Result<(), EvalAltResult> {
|
||||||
|
assert_eq!(engine.eval::<INT>(r"if true { 42 } else { 123 }")?, 42);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(r"if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(r#"const abc = "hello"; if abc < "foo" { 42 } else { 123 }"#)?,
|
||||||
|
123
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
engine.set_optimization_level(OptimizationLevel::None);
|
||||||
|
run_test(&mut engine)?;
|
||||||
|
|
||||||
|
engine.set_optimization_level(OptimizationLevel::Simple);
|
||||||
|
run_test(&mut engine)?;
|
||||||
|
|
||||||
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
|
run_test(&mut engine)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,4 +1,10 @@
|
|||||||
use rhai::{Engine, EvalAltResult, FLOAT, INT};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
use rhai::FLOAT;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
const EPSILON: FLOAT = 0.0000000001;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_power_of() -> Result<(), EvalAltResult> {
|
fn test_power_of() -> Result<(), EvalAltResult> {
|
||||||
@ -9,9 +15,8 @@ fn test_power_of() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
{
|
{
|
||||||
assert_eq!(
|
assert!(
|
||||||
engine.eval::<FLOAT>("2.2 ~ 3.3")?,
|
(engine.eval::<FLOAT>("2.2 ~ 3.3")? - 13.489468760533386 as FLOAT).abs() <= EPSILON
|
||||||
13.489468760533386 as FLOAT
|
|
||||||
);
|
);
|
||||||
assert_eq!(engine.eval::<FLOAT>("2.0~-2.0")?, 0.25 as FLOAT);
|
assert_eq!(engine.eval::<FLOAT>("2.0~-2.0")?, 0.25 as FLOAT);
|
||||||
assert_eq!(engine.eval::<FLOAT>("(-2.0~-2.0)")?, 0.25 as FLOAT);
|
assert_eq!(engine.eval::<FLOAT>("(-2.0~-2.0)")?, 0.25 as FLOAT);
|
||||||
@ -31,9 +36,9 @@ fn test_power_of_equals() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
{
|
{
|
||||||
assert_eq!(
|
assert!(
|
||||||
engine.eval::<FLOAT>("let x = 2.2; x ~= 3.3; x")?,
|
(engine.eval::<FLOAT>("let x = 2.2; x ~= 3.3; x")? - 13.489468760533386 as FLOAT).abs()
|
||||||
13.489468760533386 as FLOAT
|
<= EPSILON
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<FLOAT>("let x = 2.0; x ~= -2.0; x")?,
|
engine.eval::<FLOAT>("let x = 2.0; x ~= -2.0; x")?,
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
use rhai::{Engine, EvalAltResult, INT};
|
use rhai::{Engine, EvalAltResult};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_throw() {
|
fn test_throw() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
match engine.eval::<INT>(r#"if true { throw "hello" }"#) {
|
assert!(matches!(
|
||||||
Ok(_) => panic!("not an error"),
|
engine.eval::<()>(r#"if true { throw "hello" }"#).expect_err("expects error"),
|
||||||
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "hello" => (),
|
EvalAltResult::ErrorRuntime(s, _) if s == "hello"));
|
||||||
Err(err) => panic!("wrong error: {}", err),
|
|
||||||
}
|
|
||||||
|
|
||||||
match engine.eval::<INT>(r#"throw;"#) {
|
assert!(matches!(
|
||||||
Ok(_) => panic!("not an error"),
|
engine.eval::<()>(r#"throw;"#).expect_err("expects error"),
|
||||||
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "" => (),
|
EvalAltResult::ErrorRuntime(s, _) if s == ""));
|
||||||
Err(err) => panic!("wrong error: {}", err),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user