Merge pull request #106 from schungx/master
Add opt-in features to turn off/disable language features.
This commit is contained in:
commit
f59dbfc4e0
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
target/
|
target/
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.cargo/
|
||||||
|
18
Cargo.toml
18
Cargo.toml
@ -18,6 +18,18 @@ include = [
|
|||||||
num-traits = "*"
|
num-traits = "*"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
debug_msgs = []
|
#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked"]
|
||||||
no_stdlib = []
|
default = []
|
||||||
unchecked = []
|
debug_msgs = [] # print debug messages on function registrations and calls
|
||||||
|
unchecked = [] # unchecked arithmetic
|
||||||
|
no_stdlib = [] # no standard library of utility functions
|
||||||
|
no_index = [] # no arrays and indexing
|
||||||
|
no_float = [] # no floating-point
|
||||||
|
no_function = [] # no script-defined functions
|
||||||
|
only_i32 = [] # set INT=i32 (useful for 32-bit systems)
|
||||||
|
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = "fat"
|
||||||
|
codegen-units = 1
|
||||||
|
#opt-level = "z" # optimize for size
|
||||||
|
319
README.md
319
README.md
@ -10,7 +10,7 @@ Rhai's current feature set:
|
|||||||
* 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)
|
* Very few additional dependencies (right now only [`num-traits`] to do checked arithmetic operations)
|
||||||
|
|
||||||
**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.
|
||||||
|
|
||||||
@ -38,19 +38,27 @@ Beware that in order to use pre-releases (alpha and beta) you need to specify th
|
|||||||
Optional features
|
Optional features
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
| Feature | Description |
|
| Feature | Description |
|
||||||
| ------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `debug_msgs` | Print debug messages to stdout (using `println!`) related to function registrations and function 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. |
|
| `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 in the standard library. 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_index` | Disable arrays and indexing features if you don't need them. |
|
||||||
|
| `no_float` | Disable floating-point numbers and math if you don't need them. |
|
||||||
|
| `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. |
|
||||||
|
|
||||||
|
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.
|
||||||
|
Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language.
|
||||||
|
|
||||||
Related
|
Related
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Other cool projects to check out:
|
Other cool projects to check out:
|
||||||
|
|
||||||
* [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.
|
* [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.
|
||||||
* You can also 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)
|
* You can also check out the list of [scripting languages for Rust] on [awesome-rust].
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
@ -81,23 +89,27 @@ Example Scripts
|
|||||||
|
|
||||||
There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` folder:
|
There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` folder:
|
||||||
|
|
||||||
| Script | Description |
|
| Language feature scripts | Description |
|
||||||
| --------------------- | ------------------------------------------------------------- |
|
| ------------------------ | ------------------------------------------------------------- |
|
||||||
| `array.rhai` | arrays in Rhai |
|
| `array.rhai` | arrays in Rhai |
|
||||||
| `assignment.rhai` | variable declarations |
|
| `assignment.rhai` | variable declarations |
|
||||||
| `comments.rhai` | just comments |
|
| `comments.rhai` | just comments |
|
||||||
| `for1.rhai` | for loops |
|
| `for1.rhai` | for loops |
|
||||||
| `function_decl1.rhai` | a function without parameters |
|
| `function_decl1.rhai` | a function without parameters |
|
||||||
| `function_decl2.rhai` | a function with two parameters |
|
| `function_decl2.rhai` | a function with two parameters |
|
||||||
| `function_decl3.rhai` | a function with many parameters |
|
| `function_decl3.rhai` | a function with many parameters |
|
||||||
| `if1.rhai` | if example |
|
| `if1.rhai` | if example |
|
||||||
| `loop.rhai` | endless loop in Rhai, this example emulates a do..while cycle |
|
| `loop.rhai` | endless loop in Rhai, this example emulates a do..while cycle |
|
||||||
| `op1.rhai` | just a simple addition |
|
| `op1.rhai` | just a simple addition |
|
||||||
| `op2.rhai` | simple addition and multiplication |
|
| `op2.rhai` | simple addition and multiplication |
|
||||||
| `op3.rhai` | change evaluation order with parenthesis |
|
| `op3.rhai` | change evaluation order with parenthesis |
|
||||||
| `speed_test.rhai` | a simple program to measure the speed of Rhai's interpreter |
|
| `string.rhai` | string operations |
|
||||||
| `string.rhai` | string operations |
|
| `while.rhai` | while loop |
|
||||||
| `while.rhai` | while loop |
|
|
||||||
|
| Example scripts | Description |
|
||||||
|
| ----------------- | ----------------------------------------------------------------- |
|
||||||
|
| `speed_test.rhai` | a simple program to measure the speed of Rhai's interpreter |
|
||||||
|
| `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:
|
||||||
|
|
||||||
@ -120,6 +132,8 @@ fn main() -> Result<(), EvalAltResult>
|
|||||||
let result = engine.eval::<i64>("40 + 2")?;
|
let result = engine.eval::<i64>("40 + 2")?;
|
||||||
|
|
||||||
println!("Answer: {}", result); // prints 42
|
println!("Answer: {}", result); // prints 42
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -157,20 +171,36 @@ let ast = engine.compile_file("hello_world.rhai".into()).unwrap();
|
|||||||
```
|
```
|
||||||
|
|
||||||
Rhai also allows you to work _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust.
|
Rhai also allows you to work _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust.
|
||||||
You do this via `call_fn`, which takes a compiled AST (output from `compile`) and the
|
You do this via `call_fn`:
|
||||||
function call arguments:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::Engine;
|
use rhai::Engine;
|
||||||
|
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
// Define a function in a script and compile to AST
|
// Define a function in a script and load it into the Engine.
|
||||||
let ast = engine.compile("fn hello(x, y) { x.len() + y }")?;
|
engine.consume(
|
||||||
|
r"
|
||||||
|
fn hello(x, y) { // a function with two parameters: String and i64
|
||||||
|
x.len() + y // returning i64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hello(x) { // functions can be overloaded: this one takes only one parameter
|
||||||
|
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
|
||||||
// (beware, arguments must be of the correct types because Rhai does not have built-in type conversions)
|
// if there are more than one. Beware, arguments must be of the correct types because
|
||||||
let result: i64 = engine.call_fn("hello", &ast, (&mut String::from("abc"), &mut 123_i64))?;
|
// Rhai does not have built-in type conversions. If you pass in arguments of the wrong type,
|
||||||
|
// the Engine will not find the function.
|
||||||
|
|
||||||
|
let result: i64 = engine.call_fn("hello", &ast, ( String::from("abc"), 123_i64 ) )?;
|
||||||
|
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple
|
||||||
|
|
||||||
|
let result: i64 = engine.call_fn("hello", 123_i64)?
|
||||||
|
// ^^^^^^^ calls 'hello' with one parameter (no need for tuple)
|
||||||
```
|
```
|
||||||
|
|
||||||
Values and types
|
Values and types
|
||||||
@ -178,20 +208,28 @@ Values and types
|
|||||||
|
|
||||||
The following primitive types are supported natively:
|
The following primitive types are supported natively:
|
||||||
|
|
||||||
| Category | Types |
|
| Category | Types |
|
||||||
| ------------------------------ | -------------------------------------- |
|
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||||
| Integer | `i32`, `u32`, `i64` _(default)_, `u64` |
|
| **Integer** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ |
|
||||||
| Floating-point | `f32`, `f64` _(default)_ |
|
| **Floating-point** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ |
|
||||||
| Character | `char` |
|
| **Character** | `char` |
|
||||||
| Boolean | `bool` |
|
| **Boolean** | `bool` |
|
||||||
| Array | `rhai::Array` |
|
| **Array** (disabled with [`no_index`]) | `rhai::Array` |
|
||||||
| 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`) |
|
||||||
|
|
||||||
|
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different; you cannot even add them together.
|
||||||
|
|
||||||
|
The default integer type is `i64`. If you do not need any other integer type, you can enable 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`.
|
||||||
|
This is useful on 32-bit systems where using 64-bit integers incurs a performance penalty.
|
||||||
|
|
||||||
|
If you do not need floating-point, enable the [`no_float`] feature to remove support.
|
||||||
|
|
||||||
Value conversions
|
Value conversions
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different; you cannot even add them together.
|
|
||||||
|
|
||||||
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 you can register your own 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.
|
||||||
@ -222,8 +260,8 @@ Rhai's scripting engine is very lightweight. It gets its ability from the funct
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult};
|
||||||
use rhai::RegisterFn; // include the `RegisterFn` trait to use `register_fn`
|
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn`
|
||||||
use rhai::{Dynamic, RegisterDynamicFn}; // include the `RegisterDynamicFn` trait to use `register_dynamic_fn`
|
use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn`
|
||||||
|
|
||||||
// Normal function
|
// Normal function
|
||||||
fn add(x: i64, y: i64) -> i64 {
|
fn add(x: i64, y: i64) -> i64 {
|
||||||
@ -278,21 +316,21 @@ use std::fmt::Display;
|
|||||||
|
|
||||||
use rhai::{Engine, RegisterFn};
|
use rhai::{Engine, RegisterFn};
|
||||||
|
|
||||||
fn showit<T: Display>(x: &mut T) -> () {
|
fn show_it<T: Display>(x: &mut T) -> () {
|
||||||
println!("{}", x)
|
println!("put up a good show: {}!", x)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main()
|
fn main()
|
||||||
{
|
{
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
engine.register_fn("print", showit as fn(x: &mut i64)->());
|
engine.register_fn("print", show_it as fn(x: &mut i64)->());
|
||||||
engine.register_fn("print", showit as fn(x: &mut bool)->());
|
engine.register_fn("print", show_it as fn(x: &mut bool)->());
|
||||||
engine.register_fn("print", showit as fn(x: &mut String)->());
|
engine.register_fn("print", show_it as fn(x: &mut String)->());
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
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 and call the correct one, based on the types of the arguments, from your script.
|
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.
|
||||||
|
|
||||||
Fallible functions
|
Fallible functions
|
||||||
------------------
|
------------------
|
||||||
@ -303,13 +341,13 @@ Your function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::{Engine, EvalAltResult, Position};
|
use rhai::{Engine, EvalAltResult, Position};
|
||||||
use rhai::RegisterResultFn; // include the `RegisterResultFn` trait to use `register_result_fn`
|
use rhai::RegisterResultFn; // use `RegisterResultFn` trait for `register_result_fn`
|
||||||
|
|
||||||
// Function that may fail
|
// Function that may fail
|
||||||
fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
|
fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
|
||||||
if y == 0 {
|
if y == 0 {
|
||||||
// Return an error if y is zero
|
// Return an error if y is zero
|
||||||
Err("Division by zero detected!".into()) // short-cut to create EvalAltResult
|
Err("Division by zero detected!".into()) // short-cut to create EvalAltResult
|
||||||
} else {
|
} else {
|
||||||
Ok(x / y)
|
Ok(x / y)
|
||||||
}
|
}
|
||||||
@ -336,10 +374,10 @@ Any similarly-named function defined in a script overrides any built-in function
|
|||||||
```rust
|
```rust
|
||||||
// Override the built-in function 'to_int'
|
// Override the built-in function 'to_int'
|
||||||
fn to_int(num) {
|
fn to_int(num) {
|
||||||
print("Ha! Gotcha!" + num);
|
print("Ha! Gotcha! " + num);
|
||||||
}
|
}
|
||||||
|
|
||||||
print(to_int(123)); // what will happen?
|
print(to_int(123)); // what happens?
|
||||||
```
|
```
|
||||||
|
|
||||||
Custom types and methods
|
Custom types and methods
|
||||||
@ -507,21 +545,19 @@ fn main() -> Result<(), EvalAltResult>
|
|||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
// Then push some initialized variables into the state
|
// Then push some initialized variables into the state
|
||||||
// NOTE: Remember the default numbers used by Rhai are i64 and f64.
|
// NOTE: Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64.
|
||||||
// Better stick to them or it gets hard to work with other variables in the script.
|
// Better stick to them or it gets hard working with the script.
|
||||||
scope.push("y".into(), 42_i64);
|
scope.push("y".into(), 42_i64);
|
||||||
scope.push("z".into(), 999_i64);
|
scope.push("z".into(), 999_i64);
|
||||||
|
|
||||||
// First invocation
|
// First invocation
|
||||||
// (the second boolean argument indicates that we don't need to retain function definitions
|
engine.eval_with_scope::<()>(&mut scope, r"
|
||||||
// because we didn't declare any!)
|
|
||||||
engine.eval_with_scope::<()>(&mut scope, false, r"
|
|
||||||
let x = 4 + 5 - y + z;
|
let x = 4 + 5 - y + z;
|
||||||
y = 1;
|
y = 1;
|
||||||
")?;
|
")?;
|
||||||
|
|
||||||
// Second invocation using the same state
|
// Second invocation using the same state
|
||||||
let result = engine.eval_with_scope::<i64>(&mut scope, false, "x")?;
|
let result = engine.eval_with_scope::<i64>(&mut scope, "x")?;
|
||||||
|
|
||||||
println!("result: {}", result); // should print 966
|
println!("result: {}", result); // should print 966
|
||||||
|
|
||||||
@ -590,24 +626,23 @@ Unary operators
|
|||||||
```rust
|
```rust
|
||||||
let number = -5;
|
let number = -5;
|
||||||
number = -5 - +5;
|
number = -5 - +5;
|
||||||
let booly = !true;
|
let boolean = !true;
|
||||||
```
|
```
|
||||||
|
|
||||||
Numeric functions
|
Numeric functions
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
The following standard functions (defined in the standard library but excluded if `no_stdlib`) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only:
|
The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only:
|
||||||
|
|
||||||
| Function | Description |
|
| Function | Description |
|
||||||
| ---------- | ----------------------------------- |
|
| ---------- | --------------------------------- |
|
||||||
| `abs` | absolute value |
|
| `abs` | absolute value |
|
||||||
| `to_int` | converts an `f32` or `f64` to `i64` |
|
| `to_float` | converts an integer type to `f64` |
|
||||||
| `to_float` | converts an integer type to `f64` |
|
|
||||||
|
|
||||||
Floating-point functions
|
Floating-point functions
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
The following standard functions (defined in the standard library but excluded if `no_stdlib`) operate on `f64` only:
|
The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `f64` only:
|
||||||
|
|
||||||
| Category | Functions |
|
| Category | Functions |
|
||||||
| ---------------- | ------------------------------------------------------------ |
|
| ---------------- | ------------------------------------------------------------ |
|
||||||
@ -617,7 +652,8 @@ The following standard functions (defined in the standard library but excluded i
|
|||||||
| Exponential | `exp` (base _e_) |
|
| Exponential | `exp` (base _e_) |
|
||||||
| Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) |
|
| Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) |
|
||||||
| Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` |
|
| Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` |
|
||||||
| Tests | `is_nan`, `is_finite`, `is_infinite` |
|
| Conversion | `to_int` |
|
||||||
|
| Testing | `is_nan`, `is_finite`, `is_infinite` |
|
||||||
|
|
||||||
Strings and Chars
|
Strings and Chars
|
||||||
-----------------
|
-----------------
|
||||||
@ -636,6 +672,7 @@ 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
|
// Strings can be indexed to get a character
|
||||||
|
// (disabled with the 'no_index' feature)
|
||||||
let c = record[4];
|
let c = record[4];
|
||||||
c == 'C';
|
c == 'C';
|
||||||
|
|
||||||
@ -659,7 +696,7 @@ record[4] = '\x58'; // 0x58 = 'X'
|
|||||||
record == "Bob X. Davis: age 42 ❤\n";
|
record == "Bob X. Davis: age 42 ❤\n";
|
||||||
```
|
```
|
||||||
|
|
||||||
The following standard functions (defined in the standard library but excluded if `no_stdlib`) operate on strings:
|
The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on strings:
|
||||||
|
|
||||||
| Function | Description |
|
| Function | Description |
|
||||||
| ---------- | ------------------------------------------------------------------------ |
|
| ---------- | ------------------------------------------------------------------------ |
|
||||||
@ -706,7 +743,7 @@ Arrays
|
|||||||
|
|
||||||
You can create arrays of values, and then access them with numeric indices.
|
You can create arrays of values, and then access them with numeric indices.
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
| Function | Description |
|
| Function | Description |
|
||||||
| ---------- | ------------------------------------------------------------------------------------- |
|
| ---------- | ------------------------------------------------------------------------------------- |
|
||||||
@ -768,7 +805,7 @@ 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. If you want to use them with
|
||||||
your own custom type, you need to define a specific override:
|
your own custom type, you need to register a type-specific version:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
engine.register_fn("push",
|
engine.register_fn("push",
|
||||||
@ -778,6 +815,8 @@ engine.register_fn("push",
|
|||||||
|
|
||||||
The type of a Rhai array is `rhai::Array`. `type_of()` returns `"array"`.
|
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
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
@ -881,7 +920,7 @@ for x in array {
|
|||||||
if x == 42 { break; }
|
if x == 42 { break; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// The 'range' function allows iterating from first..last-1
|
// The 'range' function allows iterating from first..last
|
||||||
for x in range(0, 50) {
|
for x in range(0, 50) {
|
||||||
print(x);
|
print(x);
|
||||||
if x == 42 { break; }
|
if x == 42 { break; }
|
||||||
@ -927,7 +966,7 @@ println!(result); // prints "Runtime error: 42 is too large! (line 5, position
|
|||||||
Functions
|
Functions
|
||||||
---------
|
---------
|
||||||
|
|
||||||
Rhai supports defining functions in script:
|
Rhai supports defining functions in script (unless disabled with [`no_function`]):
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn add(x, y) {
|
fn add(x, y) {
|
||||||
@ -947,9 +986,8 @@ fn add(x, y) {
|
|||||||
print(add(2, 3));
|
print(add(2, 3));
|
||||||
```
|
```
|
||||||
|
|
||||||
Remember that functions defined in script always take `Dynamic` arguments (i.e. the arguments can be of any type).
|
Functions defined in script always take `Dynamic` parameters (i.e. the parameter can be of any type).
|
||||||
|
It is important to remember that all parameters are passed by _value_, so all functions are _pure_ (i.e. they never modify their parameters).
|
||||||
However, 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 you are not careful.
|
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if you are not careful.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@ -962,7 +1000,7 @@ x.change();
|
|||||||
x == 500; // 'x' is NOT changed!
|
x == 500; // 'x' is NOT changed!
|
||||||
```
|
```
|
||||||
|
|
||||||
Furthermore, functions can only be defined at the top level, never inside a block or another function.
|
Functions can only be defined at the top level, never inside a block or another function.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Top level is OK
|
// Top level is OK
|
||||||
@ -980,6 +1018,22 @@ fn do_addition(x) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Functions can be _overloaded_ based on the number of parameters (but not parameter types, since all parameters are `Dynamic`).
|
||||||
|
New definitions of the same name and number of parameters overwrite previous definitions.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn abc(x,y,z) { print("Three!!! " + x + "," + y + "," + z) }
|
||||||
|
fn abc(x) { print("One! " + x) }
|
||||||
|
fn abc(x,y) { print("Two! " + x + "," + y) }
|
||||||
|
fn abc() { print("None.") }
|
||||||
|
fn abc(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition
|
||||||
|
|
||||||
|
abc(1,2,3); // prints "Three!!! 1,2,3"
|
||||||
|
abc(42); // prints "HA! NEW ONE! 42"
|
||||||
|
abc(1,2); // prints "Two!! 1,2"
|
||||||
|
abc(); // prints "None."
|
||||||
|
```
|
||||||
|
|
||||||
Members and methods
|
Members and methods
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
@ -1002,7 +1056,8 @@ debug("world!"); // prints "world!" to stdout using debug formatting
|
|||||||
### Overriding `print` and `debug` with callback functions
|
### Overriding `print` and `debug` with callback functions
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Any function or closure that takes an &str argument can be used to override print and debug
|
// Any function or closure that takes an &str argument can be used to override
|
||||||
|
// print and debug
|
||||||
engine.on_print(|x| println!("hello: {}", x));
|
engine.on_print(|x| println!("hello: {}", x));
|
||||||
engine.on_debug(|x| println!("DEBUG: {}", x));
|
engine.on_debug(|x| println!("DEBUG: {}", x));
|
||||||
|
|
||||||
@ -1013,7 +1068,7 @@ let mut log: Vec<String> = Vec::new();
|
|||||||
engine.on_print(|s| log.push(format!("entry: {}", s)));
|
engine.on_print(|s| log.push(format!("entry: {}", s)));
|
||||||
engine.on_debug(|s| log.push(format!("DEBUG: {}", s)));
|
engine.on_debug(|s| log.push(format!("DEBUG: {}", s)));
|
||||||
|
|
||||||
// Evalulate script
|
// Evaluate script
|
||||||
engine.eval::<()>(script)?;
|
engine.eval::<()>(script)?;
|
||||||
|
|
||||||
// 'log' captures all the 'print' and 'debug' output
|
// 'log' captures all the 'print' and 'debug' output
|
||||||
@ -1021,3 +1076,109 @@ for entry in log {
|
|||||||
println!("{}", entry);
|
println!("{}", entry);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Optimizations
|
||||||
|
=============
|
||||||
|
|
||||||
|
Rhai includes an _optimizer_ that tries to optimize a script after parsing. This can reduce resource utilization and increase execution speed.
|
||||||
|
|
||||||
|
For example, in the following:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
{
|
||||||
|
let x = 999; // NOT eliminated - Rhai doesn't check yet whether a variable is used later on
|
||||||
|
123; // eliminated - no effect
|
||||||
|
"hello"; // eliminated - no effect
|
||||||
|
[1, 2, x, x*2, 5]; // eliminated - no effect
|
||||||
|
foo(42); // NOT eliminated - the function 'foo' may have side effects
|
||||||
|
666 // NOT eliminated - this is the return value of the block,
|
||||||
|
// and the block is the last one
|
||||||
|
// so this is the return value of the whole script
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Rhai attempts to eliminate _dead code_ (i.e. code that does nothing, for example an expression by itself as a statement, which is allowed in Rhai).
|
||||||
|
The above script optimizes to:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
{
|
||||||
|
let x = 999;
|
||||||
|
foo(42);
|
||||||
|
666
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Constant propagation is used to remove dead code:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
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
|
||||||
|
print("done!"); // <-- the line above is further simplified to this
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
Beware, however, that most operators are actually function calls, and those functions can be overridden, so they are not optimized away:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
if 1 == 1 { ... } // '1==1' is NOT optimized away because you can define
|
||||||
|
// your own '==' function to override the built-in default!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Here be dragons!
|
||||||
|
|
||||||
|
Some optimizations can be quite aggressive and 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// The above optimizes to:
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
In fact, any errors inside a statement that has been eliminated will silently _go away_:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
print("start!");
|
||||||
|
if my_decision { /* do nothing... */ } // <-- eliminated due to no effect
|
||||||
|
print("end!");
|
||||||
|
|
||||||
|
// The above optimizes to:
|
||||||
|
|
||||||
|
print("start!");
|
||||||
|
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.
|
||||||
|
However, after optimization, the entire `if` statement is removed, thus the script silently runs to completion without errors.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let engine = rhai::Engine::new();
|
||||||
|
engine.set_optimization(false); // turn off the optimizer
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
[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/
|
||||||
|
[`debug_msgs`]: #optional-features
|
||||||
|
[`unchecked`]: #optional-features
|
||||||
|
[`no_stdlib`]: #optional-features
|
||||||
|
[`no_index`]: #optional-features
|
||||||
|
[`no_float`]: #optional-features
|
||||||
|
[`no_function`]: #optional-features
|
||||||
|
[`only_i32`]: #optional-features
|
||||||
|
[`only_i64`]: #optional-features
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::{Engine, EvalAltResult, Scope};
|
use rhai::{Engine, EvalAltResult, Scope, AST};
|
||||||
use std::{
|
use std::{
|
||||||
io::{stdin, stdout, Write},
|
io::{stdin, stdout, Write},
|
||||||
iter,
|
iter,
|
||||||
@ -46,6 +46,7 @@ fn main() {
|
|||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
let mut ast: Option<AST> = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
print!("rhai> ");
|
print!("rhai> ");
|
||||||
@ -57,7 +58,28 @@ fn main() {
|
|||||||
println!("input error: {}", err);
|
println!("input error: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = engine.consume_with_scope(&mut scope, true, &input) {
|
// Implement standard commands
|
||||||
|
match input.as_str().trim() {
|
||||||
|
"exit" | "quit" => break, // quit
|
||||||
|
"ast" => {
|
||||||
|
// print the last AST
|
||||||
|
match &ast {
|
||||||
|
Some(ast) => println!("{:#?}", ast),
|
||||||
|
None => println!("()"),
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(err) = engine
|
||||||
|
.compile(&input)
|
||||||
|
.map_err(EvalAltResult::ErrorParsing)
|
||||||
|
.and_then(|r| {
|
||||||
|
ast = Some(r);
|
||||||
|
engine.consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap())
|
||||||
|
})
|
||||||
|
{
|
||||||
println!("");
|
println!("");
|
||||||
print_error(&input, err);
|
print_error(&input, err);
|
||||||
println!("");
|
println!("");
|
||||||
|
@ -4,9 +4,9 @@ fn main() -> Result<(), EvalAltResult> {
|
|||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
engine.eval_with_scope::<()>(&mut scope, false, "let x = 4 + 5")?;
|
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;
|
||||||
|
|
||||||
let result = engine.eval_with_scope::<i64>(&mut scope, false, "x")?;
|
let result = engine.eval_with_scope::<i64>(&mut scope, "x")?;
|
||||||
|
|
||||||
println!("result: {}", result);
|
println!("result: {}", result);
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ fn main() {
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = engine.consume(&contents) {
|
if let Err(err) = engine.consume(&contents, false) {
|
||||||
eprintln!("{}", padding("=", filename.len()));
|
eprintln!("{}", padding("=", filename.len()));
|
||||||
eprintln!("{}", filename);
|
eprintln!("{}", filename);
|
||||||
eprintln!("{}", padding("=", filename.len()));
|
eprintln!("{}", padding("=", filename.len()));
|
||||||
|
28
scripts/primes.rhai
Normal file
28
scripts/primes.rhai
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// This is a script to calculate prime numbers.
|
||||||
|
|
||||||
|
let MAX_NUMBER_TO_CHECK = 10000; // 1229 primes
|
||||||
|
|
||||||
|
let prime_mask = [];
|
||||||
|
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
|
||||||
|
|
||||||
|
prime_mask[0] = false;
|
||||||
|
prime_mask[1] = false;
|
||||||
|
|
||||||
|
let total_primes_found = 0;
|
||||||
|
|
||||||
|
for p in range(2, MAX_NUMBER_TO_CHECK) {
|
||||||
|
if prime_mask[p] {
|
||||||
|
print(p);
|
||||||
|
|
||||||
|
total_primes_found += 1;
|
||||||
|
let i = 2 * p;
|
||||||
|
|
||||||
|
while i < MAX_NUMBER_TO_CHECK {
|
||||||
|
prime_mask[i] = false;
|
||||||
|
i += p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Total " + total_primes_found + " primes.");
|
||||||
|
|
259
src/api.rs
259
src/api.rs
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
use crate::any::{Any, AnyExt, Dynamic};
|
use crate::any::{Any, AnyExt, Dynamic};
|
||||||
use crate::call::FuncArgs;
|
use crate::call::FuncArgs;
|
||||||
use crate::engine::{Engine, FnAny, FnIntExt, FnSpec};
|
use crate::engine::{Engine, FnAny, FnSpec};
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
use crate::fn_register::RegisterFn;
|
use crate::fn_register::RegisterFn;
|
||||||
use crate::parser::{lex, parse, 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;
|
||||||
use std::{
|
use std::{
|
||||||
@ -42,7 +42,7 @@ impl<'e> Engine<'e> {
|
|||||||
args,
|
args,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.ext_functions.insert(spec, Arc::new(FnIntExt::Ext(f)));
|
self.ext_functions.insert(spec, f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a custom type for use with the `Engine`.
|
/// Register a custom type for use with the `Engine`.
|
||||||
@ -64,7 +64,7 @@ impl<'e> Engine<'e> {
|
|||||||
where
|
where
|
||||||
F: Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static,
|
F: Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static,
|
||||||
{
|
{
|
||||||
self.type_iterators.insert(TypeId::of::<T>(), Arc::new(f));
|
self.type_iterators.insert(TypeId::of::<T>(), Box::new(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a getter function for a member of a registered type with the `Engine`.
|
/// Register a getter function for a member of a registered type with the `Engine`.
|
||||||
@ -101,91 +101,87 @@ 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> {
|
||||||
let tokens = lex(input);
|
let tokens_stream = lex(input);
|
||||||
parse(&mut tokens.peekable(), self.optimize)
|
parse(&mut tokens_stream.peekable(), self.optimize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_file(path: PathBuf) -> Result<String, EvalAltResult> {
|
||||||
|
let mut f = File::open(path.clone())
|
||||||
|
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))?;
|
||||||
|
|
||||||
|
let mut contents = String::new();
|
||||||
|
|
||||||
|
f.read_to_string(&mut contents)
|
||||||
|
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))
|
||||||
|
.map(|_| contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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> {
|
||||||
let mut f = File::open(path.clone())
|
Self::read_file(path)
|
||||||
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))?;
|
.and_then(|contents| self.compile(&contents).map_err(|err| err.into()))
|
||||||
|
|
||||||
let mut contents = String::new();
|
|
||||||
|
|
||||||
f.read_to_string(&mut contents)
|
|
||||||
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))
|
|
||||||
.and_then(|_| self.compile(&contents).map_err(EvalAltResult::ErrorParsing))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a file.
|
/// Evaluate a file.
|
||||||
pub fn eval_file<T: Any + Clone>(&mut self, path: PathBuf) -> Result<T, EvalAltResult> {
|
pub fn eval_file<T: Any + Clone>(&mut self, path: PathBuf) -> Result<T, EvalAltResult> {
|
||||||
let mut f = File::open(path.clone())
|
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
|
||||||
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))?;
|
|
||||||
|
|
||||||
let mut contents = String::new();
|
|
||||||
|
|
||||||
f.read_to_string(&mut contents)
|
|
||||||
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))
|
|
||||||
.and_then(|_| self.eval::<T>(&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();
|
||||||
self.eval_with_scope(&mut scope, false, input)
|
self.eval_with_scope(&mut scope, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a string with own scope.
|
/// Evaluate a string with own scope.
|
||||||
///
|
|
||||||
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
|
||||||
/// and not cleared from run to run.
|
|
||||||
pub fn eval_with_scope<T: Any + Clone>(
|
pub fn eval_with_scope<T: Any + Clone>(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
retain_functions: bool,
|
|
||||||
input: &str,
|
input: &str,
|
||||||
) -> Result<T, EvalAltResult> {
|
) -> Result<T, EvalAltResult> {
|
||||||
let ast = self.compile(input).map_err(EvalAltResult::ErrorParsing)?;
|
let ast = self.compile(input).map_err(EvalAltResult::ErrorParsing)?;
|
||||||
self.eval_ast_with_scope(scope, retain_functions, &ast)
|
self.eval_ast_with_scope(scope, &ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an AST.
|
/// Evaluate an AST.
|
||||||
pub fn eval_ast<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> {
|
pub fn eval_ast<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> {
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
self.eval_ast_with_scope(&mut scope, false, ast)
|
self.eval_ast_with_scope(&mut scope, ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an AST with own scope.
|
/// Evaluate an AST with own scope.
|
||||||
///
|
|
||||||
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
|
||||||
/// and not cleared from run to run.
|
|
||||||
pub fn eval_ast_with_scope<T: Any + Clone>(
|
pub fn eval_ast_with_scope<T: Any + Clone>(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
retain_functions: bool,
|
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<T, EvalAltResult> {
|
) -> Result<T, EvalAltResult> {
|
||||||
let AST(statements, functions) = ast;
|
fn eval_ast_internal(
|
||||||
|
engine: &mut Engine,
|
||||||
|
scope: &mut Scope,
|
||||||
|
ast: &AST,
|
||||||
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
|
engine.clear_functions();
|
||||||
|
|
||||||
functions.iter().for_each(|f| {
|
#[cfg(feature = "no_function")]
|
||||||
self.script_functions.insert(
|
let AST(statements) = ast;
|
||||||
FnSpec {
|
|
||||||
name: f.name.clone().into(),
|
|
||||||
args: None,
|
|
||||||
},
|
|
||||||
Arc::new(FnIntExt::Int(f.clone())),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
let result = statements
|
#[cfg(not(feature = "no_function"))]
|
||||||
.iter()
|
let statements = {
|
||||||
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt));
|
let AST(statements, functions) = ast;
|
||||||
|
engine.load_script_functions(functions);
|
||||||
|
statements
|
||||||
|
};
|
||||||
|
|
||||||
if !retain_functions {
|
let result = statements
|
||||||
self.clear_functions();
|
.iter()
|
||||||
|
.try_fold(().into_dynamic(), |_, stmt| engine.eval_stmt(scope, stmt));
|
||||||
|
|
||||||
|
engine.clear_functions();
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
match result {
|
match eval_ast_internal(self, scope, ast) {
|
||||||
Err(EvalAltResult::Return(out, pos)) => out.downcast::<T>().map(|v| *v).map_err(|a| {
|
Err(EvalAltResult::Return(out, pos)) => out.downcast::<T>().map(|v| *v).map_err(|a| {
|
||||||
EvalAltResult::ErrorMismatchOutputType(
|
EvalAltResult::ErrorMismatchOutputType(
|
||||||
self.map_type_name((*a).type_name()).to_string(),
|
self.map_type_name((*a).type_name()).to_string(),
|
||||||
@ -206,21 +202,24 @@ impl<'e> Engine<'e> {
|
|||||||
|
|
||||||
/// Evaluate a file, but throw away the result and only return error (if any).
|
/// Evaluate a file, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
pub fn consume_file(&mut self, path: PathBuf) -> Result<(), EvalAltResult> {
|
///
|
||||||
let mut f = File::open(path.clone())
|
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||||
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))?;
|
/// and not cleared from run to run.
|
||||||
|
pub fn consume_file(
|
||||||
let mut contents = String::new();
|
&mut self,
|
||||||
|
path: PathBuf,
|
||||||
f.read_to_string(&mut contents)
|
retain_functions: bool,
|
||||||
.map_err(|err| EvalAltResult::ErrorReadingScriptFile(path.clone(), err))
|
) -> Result<(), EvalAltResult> {
|
||||||
.and_then(|_| self.consume(&contents))
|
Self::read_file(path).and_then(|contents| self.consume(&contents, retain_functions))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a string, but throw away the result and only return error (if any).
|
/// Evaluate a string, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> {
|
///
|
||||||
self.consume_with_scope(&mut Scope::new(), false, input)
|
/// 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(&mut self, input: &str, retain_functions: bool) -> Result<(), EvalAltResult> {
|
||||||
|
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, but throw away the result and only return error (if any).
|
||||||
@ -234,84 +233,116 @@ impl<'e> Engine<'e> {
|
|||||||
retain_functions: bool,
|
retain_functions: bool,
|
||||||
input: &str,
|
input: &str,
|
||||||
) -> Result<(), EvalAltResult> {
|
) -> Result<(), EvalAltResult> {
|
||||||
let tokens = lex(input);
|
let tokens_stream = lex(input);
|
||||||
|
|
||||||
parse(&mut tokens.peekable(), self.optimize)
|
let ast = parse(&mut tokens_stream.peekable(), self.optimize)
|
||||||
.map_err(|err| EvalAltResult::ErrorParsing(err))
|
.map_err(EvalAltResult::ErrorParsing)?;
|
||||||
.and_then(|AST(ref statements, ref functions)| {
|
|
||||||
for f in functions {
|
|
||||||
self.script_functions.insert(
|
|
||||||
FnSpec {
|
|
||||||
name: f.name.clone().into(),
|
|
||||||
args: None,
|
|
||||||
},
|
|
||||||
Arc::new(FnIntExt::Int(f.clone())),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let val = statements
|
self.consume_ast_with_scope(scope, retain_functions, &ast)
|
||||||
.iter()
|
|
||||||
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o))
|
|
||||||
.map(|_| ());
|
|
||||||
|
|
||||||
if !retain_functions {
|
|
||||||
self.clear_functions();
|
|
||||||
}
|
|
||||||
|
|
||||||
val
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a script function defined in a compiled AST.
|
/// Evaluate an AST, but throw away the result and only return error (if any).
|
||||||
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
|
///
|
||||||
|
/// 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(
|
||||||
|
&mut self,
|
||||||
|
scope: &mut Scope,
|
||||||
|
retain_functions: bool,
|
||||||
|
ast: &AST,
|
||||||
|
) -> Result<(), EvalAltResult> {
|
||||||
|
if !retain_functions {
|
||||||
|
self.clear_functions();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "no_function")]
|
||||||
|
let AST(statements) = ast;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
let statements = {
|
||||||
|
let AST(ref statements, ref functions) = ast;
|
||||||
|
self.load_script_functions(functions);
|
||||||
|
statements
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = statements
|
||||||
|
.iter()
|
||||||
|
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o))
|
||||||
|
.map(|_| ());
|
||||||
|
|
||||||
|
if !retain_functions {
|
||||||
|
self.clear_functions();
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a list of functions into the Engine.
|
||||||
|
pub(crate) fn load_script_functions<'a>(
|
||||||
|
&mut self,
|
||||||
|
functions: impl IntoIterator<Item = &'a Arc<FnDef>>,
|
||||||
|
) {
|
||||||
|
for f in functions.into_iter() {
|
||||||
|
match self
|
||||||
|
.script_functions
|
||||||
|
.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len()))
|
||||||
|
{
|
||||||
|
Ok(n) => self.script_functions[n] = f.clone(),
|
||||||
|
Err(n) => self.script_functions.insert(n, f.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a script function retained inside the Engine.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
|
/// # #[cfg(not(feature = "no_stdlib"))]
|
||||||
|
/// # #[cfg(not(feature = "no_function"))]
|
||||||
|
/// # {
|
||||||
/// use rhai::Engine;
|
/// use rhai::Engine;
|
||||||
///
|
///
|
||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// let ast = engine.compile("fn add(x, y) { x.len() + y }")?;
|
/// engine.consume("fn add(x, y) { x.len() + y }", true)?;
|
||||||
///
|
///
|
||||||
/// let result: i64 = engine.call_fn("add", &ast, (&mut String::from("abc"), &mut 123_i64))?;
|
/// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(result, 126);
|
/// assert_eq!(result, 126);
|
||||||
|
/// # }
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn call_fn<'f, A: FuncArgs<'f>, T: Any + Clone>(
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
pub fn call_fn<A: FuncArgs, T: Any + Clone>(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &str,
|
name: &str,
|
||||||
ast: &AST,
|
|
||||||
args: A,
|
args: A,
|
||||||
) -> Result<T, EvalAltResult> {
|
) -> Result<T, EvalAltResult> {
|
||||||
let pos = Default::default();
|
// Split out non-generic portion to avoid exploding code size
|
||||||
|
fn call_fn_internal(
|
||||||
|
engine: &mut Engine,
|
||||||
|
name: &str,
|
||||||
|
mut values: Vec<Dynamic>,
|
||||||
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
|
let values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
|
||||||
|
|
||||||
ast.1.iter().for_each(|f| {
|
let result = engine.call_fn_raw(name, values, None, Position::none());
|
||||||
self.script_functions.insert(
|
|
||||||
FnSpec {
|
|
||||||
name: f.name.clone().into(),
|
|
||||||
args: None,
|
|
||||||
},
|
|
||||||
Arc::new(FnIntExt::Int(f.clone())),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
let result = self
|
result
|
||||||
.call_fn_raw(name, args.into_vec(), None, pos)
|
}
|
||||||
.and_then(|b| {
|
|
||||||
b.downcast().map(|b| *b).map_err(|a| {
|
|
||||||
EvalAltResult::ErrorMismatchOutputType(
|
|
||||||
self.map_type_name((*a).type_name()).into(),
|
|
||||||
pos,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
self.clear_functions();
|
call_fn_internal(self, name, args.into_vec()).and_then(|b| {
|
||||||
|
b.downcast().map(|b| *b).map_err(|a| {
|
||||||
result
|
EvalAltResult::ErrorMismatchOutputType(
|
||||||
|
self.map_type_name((*a).type_name()).into(),
|
||||||
|
Position::none(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Override default action of `print` (print to stdout using `println!`)
|
/// Override default action of `print` (print to stdout using `println!`)
|
||||||
@ -328,7 +359,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);")?;
|
/// engine.consume("print(40 + 2);", false)?;
|
||||||
/// }
|
/// }
|
||||||
/// assert_eq!(result, "42");
|
/// assert_eq!(result, "42");
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
@ -352,7 +383,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");"#)?;
|
/// engine.consume(r#"debug("hello");"#, false)?;
|
||||||
/// }
|
/// }
|
||||||
/// assert_eq!(result, "\"hello\"");
|
/// assert_eq!(result, "\"hello\"");
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
|
837
src/builtin.rs
837
src/builtin.rs
File diff suppressed because it is too large
Load Diff
57
src/call.rs
57
src/call.rs
@ -1,35 +1,54 @@
|
|||||||
//! Helper module which defines `FnArgs` to make function calling easier.
|
//! Helper module which defines `FnArgs` to make function calling easier.
|
||||||
|
|
||||||
use crate::any::{Any, Variant};
|
use crate::any::{Any, Dynamic};
|
||||||
|
use crate::engine::Array;
|
||||||
|
|
||||||
/// Trait that represent arguments to a function call.
|
/// Trait that represent arguments to a function call.
|
||||||
pub trait FuncArgs<'a> {
|
pub trait FuncArgs {
|
||||||
/// Convert to a `Vec` of `Variant` arguments.
|
/// Convert to a `Vec` of `Dynamic` arguments.
|
||||||
fn into_vec(self) -> Vec<&'a mut Variant>;
|
fn into_vec(self) -> Vec<Dynamic>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FuncArgs<'a> for Vec<&'a mut Variant> {
|
macro_rules! impl_std_args {
|
||||||
fn into_vec(self) -> Self {
|
($($p:ty),*) => {
|
||||||
self
|
$(
|
||||||
}
|
impl FuncArgs for $p {
|
||||||
|
fn into_vec(self) -> Vec<Dynamic> {
|
||||||
|
vec![self.into_dynamic()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: Any> FuncArgs<'a> for &'a mut Vec<T> {
|
impl_std_args!(String, char, bool);
|
||||||
fn into_vec(self) -> Vec<&'a mut Variant> {
|
|
||||||
self.iter_mut().map(|x| x as &mut Variant).collect()
|
#[cfg(not(feature = "no_index"))]
|
||||||
}
|
impl_std_args!(Array);
|
||||||
}
|
|
||||||
|
#[cfg(not(feature = "only_i32"))]
|
||||||
|
#[cfg(not(feature = "only_i64"))]
|
||||||
|
impl_std_args!(u8, i8, u16, i16, u32, i32, u64, i64);
|
||||||
|
|
||||||
|
#[cfg(feature = "only_i32")]
|
||||||
|
impl_std_args!(i32);
|
||||||
|
|
||||||
|
#[cfg(feature = "only_i64")]
|
||||||
|
impl_std_args!(i64);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
impl_std_args!(f32, f64);
|
||||||
|
|
||||||
macro_rules! impl_args {
|
macro_rules! impl_args {
|
||||||
($($p:ident),*) => {
|
($($p:ident),*) => {
|
||||||
impl<'a, $($p: Any + Clone),*> FuncArgs<'a> for ($(&'a mut $p,)*)
|
impl<$($p: Any + Clone),*> FuncArgs for ($($p,)*)
|
||||||
{
|
{
|
||||||
fn into_vec(self) -> Vec<&'a mut Variant> {
|
fn into_vec(self) -> Vec<Dynamic> {
|
||||||
let ($($p,)*) = self;
|
let ($($p,)*) = self;
|
||||||
|
|
||||||
#[allow(unused_variables, unused_mut)]
|
#[allow(unused_variables, unused_mut)]
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
$(v.push($p as &mut Variant);)*
|
$(v.push($p.into_dynamic());)*
|
||||||
|
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
@ -39,10 +58,12 @@ macro_rules! impl_args {
|
|||||||
};
|
};
|
||||||
(@pop) => {
|
(@pop) => {
|
||||||
};
|
};
|
||||||
(@pop $head:ident $(, $tail:ident)*) => {
|
(@pop $head:ident) => {
|
||||||
|
};
|
||||||
|
(@pop $head:ident $(, $tail:ident)+) => {
|
||||||
impl_args!($($tail),*);
|
impl_args!($($tail),*);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
impl_args!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
|
impl_args!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
|
||||||
|
319
src/engine.rs
319
src/engine.rs
@ -1,19 +1,23 @@
|
|||||||
//! Main module defining the script evaluation `Engine`.
|
//! Main module defining the script evaluation `Engine`.
|
||||||
|
|
||||||
use crate::any::{Any, AnyExt, Dynamic, Variant};
|
use crate::any::{Any, AnyExt, Dynamic, Variant};
|
||||||
use crate::parser::{Expr, FnDef, Position, 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;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
use crate::INT;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
any::{type_name, TypeId},
|
any::{type_name, TypeId},
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp::{PartialEq, PartialOrd},
|
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
iter::once,
|
iter::once,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An dynamic array of `Dynamic` values.
|
/// An dynamic array of `Dynamic` values.
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub type Array = Vec<Dynamic>;
|
pub type Array = Vec<Dynamic>;
|
||||||
|
|
||||||
pub type FnCallArgs<'a> = Vec<&'a mut Variant>;
|
pub type FnCallArgs<'a> = Vec<&'a mut Variant>;
|
||||||
@ -24,18 +28,20 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
|
|||||||
|
|
||||||
pub(crate) const KEYWORD_PRINT: &'static str = "print";
|
pub(crate) const KEYWORD_PRINT: &'static str = "print";
|
||||||
pub(crate) const KEYWORD_DEBUG: &'static str = "debug";
|
pub(crate) const KEYWORD_DEBUG: &'static str = "debug";
|
||||||
|
pub(crate) const KEYWORD_DUMP_AST: &'static str = "dump_ast";
|
||||||
pub(crate) const KEYWORD_TYPE_OF: &'static str = "type_of";
|
pub(crate) const KEYWORD_TYPE_OF: &'static str = "type_of";
|
||||||
pub(crate) const FUNC_GETTER: &'static str = "get$";
|
pub(crate) const FUNC_GETTER: &'static str = "get$";
|
||||||
pub(crate) const FUNC_SETTER: &'static str = "set$";
|
pub(crate) const FUNC_SETTER: &'static str = "set$";
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
enum IndexSourceType {
|
enum IndexSourceType {
|
||||||
Array,
|
Array,
|
||||||
String,
|
String,
|
||||||
Expression,
|
Expression,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||||
pub struct FnSpec<'a> {
|
pub struct FnSpec<'a> {
|
||||||
pub name: Cow<'a, str>,
|
pub name: Cow<'a, str>,
|
||||||
pub args: Option<Vec<TypeId>>,
|
pub args: Option<Vec<TypeId>>,
|
||||||
@ -59,11 +65,11 @@ pub struct Engine<'e> {
|
|||||||
/// Optimize the AST after compilation
|
/// Optimize the AST after compilation
|
||||||
pub(crate) optimize: bool,
|
pub(crate) optimize: bool,
|
||||||
/// 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>, Arc<FnIntExt<'e>>>,
|
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: HashMap<FnSpec<'e>, Arc<FnIntExt<'e>>>,
|
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, Arc<IteratorFn>>,
|
pub(crate) type_iterators: HashMap<TypeId, Box<IteratorFn>>,
|
||||||
pub(crate) type_names: HashMap<String, String>,
|
pub(crate) type_names: HashMap<String, String>,
|
||||||
|
|
||||||
// Closures for implementing the print/debug commands
|
// Closures for implementing the print/debug commands
|
||||||
@ -71,18 +77,14 @@ pub struct Engine<'e> {
|
|||||||
pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>,
|
pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum FnIntExt<'a> {
|
|
||||||
Ext(Box<FnAny>),
|
|
||||||
Int(FnDef<'a>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Engine<'_> {
|
impl Engine<'_> {
|
||||||
/// Create a new `Engine`
|
/// Create a new `Engine`
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
// User-friendly names for built-in types
|
// User-friendly names for built-in types
|
||||||
let type_names = [
|
let type_names = [
|
||||||
(type_name::<String>(), "string"),
|
#[cfg(not(feature = "no_index"))]
|
||||||
(type_name::<Array>(), "array"),
|
(type_name::<Array>(), "array"),
|
||||||
|
(type_name::<String>(), "string"),
|
||||||
(type_name::<Dynamic>(), "dynamic"),
|
(type_name::<Dynamic>(), "dynamic"),
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
@ -93,7 +95,7 @@ impl Engine<'_> {
|
|||||||
let mut engine = Engine {
|
let mut engine = Engine {
|
||||||
optimize: true,
|
optimize: true,
|
||||||
ext_functions: HashMap::new(),
|
ext_functions: HashMap::new(),
|
||||||
script_functions: HashMap::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
|
||||||
@ -132,113 +134,102 @@ impl Engine<'_> {
|
|||||||
.join(", ")
|
.join(", ")
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut spec = FnSpec {
|
// First search in script-defined functions (can override built-in)
|
||||||
|
if let Ok(n) = self
|
||||||
|
.script_functions
|
||||||
|
.binary_search_by(|f| f.compare(fn_name, args.len()))
|
||||||
|
{
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
|
let fn_def = self.script_functions[n].clone();
|
||||||
|
|
||||||
|
scope.extend(
|
||||||
|
// Put arguments into scope as variables
|
||||||
|
fn_def
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.zip(args.iter().map(|x| (*x).into_dynamic())),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Evaluate
|
||||||
|
return match self.eval_stmt(&mut scope, &fn_def.body) {
|
||||||
|
// Convert return statement to return value
|
||||||
|
Err(EvalAltResult::Return(x, _)) => Ok(x),
|
||||||
|
other => other,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let spec = FnSpec {
|
||||||
name: fn_name.into(),
|
name: fn_name.into(),
|
||||||
args: None,
|
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// First search in script-defined functions (can override built-in),
|
// Then search built-in's and external functions
|
||||||
// then built-in's and external functions
|
if let Some(func) = self.ext_functions.get(&spec) {
|
||||||
let fn_def = self
|
// Run external function
|
||||||
.script_functions
|
let result = func(args, pos)?;
|
||||||
.get(&spec)
|
|
||||||
.or_else(|| {
|
|
||||||
spec.args = Some(args.iter().map(|a| Any::type_id(&**a)).collect());
|
|
||||||
self.ext_functions.get(&spec)
|
|
||||||
})
|
|
||||||
.map(|f| f.clone());
|
|
||||||
|
|
||||||
if let Some(f) = fn_def {
|
// See if the function match print/debug (which requires special processing)
|
||||||
match *f {
|
let callback = match spec.name.as_ref() {
|
||||||
// Run external function
|
KEYWORD_PRINT => self.on_print.as_mut(),
|
||||||
FnIntExt::Ext(ref func) => {
|
KEYWORD_DEBUG => self.on_debug.as_mut(),
|
||||||
let result = func(args, pos)?;
|
_ => return Ok(result),
|
||||||
|
};
|
||||||
|
|
||||||
// See if the function match print/debug (which requires special processing)
|
let val = &result
|
||||||
let callback = match spec.name.as_ref() {
|
.downcast::<String>()
|
||||||
KEYWORD_PRINT => self.on_print.as_mut(),
|
.map(|s| *s)
|
||||||
KEYWORD_DEBUG => self.on_debug.as_mut(),
|
.unwrap_or("error: not a string".into());
|
||||||
_ => return Ok(result),
|
|
||||||
};
|
|
||||||
|
|
||||||
let val = &result
|
return Ok(callback(val).into_dynamic());
|
||||||
.downcast::<String>()
|
}
|
||||||
.map(|s| *s)
|
|
||||||
.unwrap_or("error: not a string".into());
|
|
||||||
|
|
||||||
Ok(callback(val).into_dynamic())
|
if spec.name == KEYWORD_TYPE_OF && args.len() == 1 {
|
||||||
}
|
|
||||||
|
|
||||||
// Run script-defined function
|
|
||||||
FnIntExt::Int(ref func) => {
|
|
||||||
// First check number of parameters
|
|
||||||
if func.params.len() != args.len() {
|
|
||||||
return Err(EvalAltResult::ErrorFunctionArgsMismatch(
|
|
||||||
spec.name.into(),
|
|
||||||
func.params.len(),
|
|
||||||
args.len(),
|
|
||||||
pos,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut scope = Scope::new();
|
|
||||||
|
|
||||||
scope.extend(
|
|
||||||
// Put arguments into scope as variables
|
|
||||||
func.params
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.zip(args.iter().map(|x| (*x).into_dynamic())),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Evaluate
|
|
||||||
match self.eval_stmt(&mut scope, &func.body) {
|
|
||||||
// Convert return statement to return value
|
|
||||||
Err(EvalAltResult::Return(x, _)) => Ok(x),
|
|
||||||
other => other,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if spec.name == KEYWORD_TYPE_OF && args.len() == 1 {
|
|
||||||
// Handle `type_of` function
|
// Handle `type_of` function
|
||||||
Ok(self
|
return Ok(self
|
||||||
.map_type_name(args[0].type_name())
|
.map_type_name(args[0].type_name())
|
||||||
.to_string()
|
.to_string()
|
||||||
.into_dynamic())
|
.into_dynamic());
|
||||||
} else if spec.name.starts_with(FUNC_GETTER) {
|
}
|
||||||
|
|
||||||
|
if spec.name.starts_with(FUNC_GETTER) {
|
||||||
// Getter function not found
|
// Getter function not found
|
||||||
Err(EvalAltResult::ErrorDotExpr(
|
return Err(EvalAltResult::ErrorDotExpr(
|
||||||
format!(
|
format!(
|
||||||
"- property '{}' unknown or write-only",
|
"- property '{}' unknown or write-only",
|
||||||
&spec.name[FUNC_GETTER.len()..]
|
&spec.name[FUNC_GETTER.len()..]
|
||||||
),
|
),
|
||||||
pos,
|
pos,
|
||||||
))
|
));
|
||||||
} else if spec.name.starts_with(FUNC_SETTER) {
|
}
|
||||||
|
|
||||||
|
if spec.name.starts_with(FUNC_SETTER) {
|
||||||
// Setter function not found
|
// Setter function not found
|
||||||
Err(EvalAltResult::ErrorDotExpr(
|
return Err(EvalAltResult::ErrorDotExpr(
|
||||||
format!(
|
format!(
|
||||||
"- property '{}' unknown or read-only",
|
"- property '{}' unknown or read-only",
|
||||||
&spec.name[FUNC_SETTER.len()..]
|
&spec.name[FUNC_SETTER.len()..]
|
||||||
),
|
),
|
||||||
pos,
|
pos,
|
||||||
))
|
));
|
||||||
} else if let Some(val) = def_val {
|
|
||||||
// Return default value
|
|
||||||
Ok(val.clone())
|
|
||||||
} else {
|
|
||||||
// Raise error
|
|
||||||
let types_list = args
|
|
||||||
.iter()
|
|
||||||
.map(|x| (*x).type_name())
|
|
||||||
.map(|name| self.map_type_name(name))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
Err(EvalAltResult::ErrorFunctionNotFound(
|
|
||||||
format!("{} ({})", spec.name, types_list.join(", ")),
|
|
||||||
pos,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(val) = def_val {
|
||||||
|
// Return default value
|
||||||
|
return Ok(val.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raise error
|
||||||
|
let types_list = args
|
||||||
|
.iter()
|
||||||
|
.map(|x| (*x).type_name())
|
||||||
|
.map(|name| self.map_type_name(name))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Err(EvalAltResult::ErrorFunctionNotFound(
|
||||||
|
format!("{} ({})", spec.name, types_list.join(", ")),
|
||||||
|
pos,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chain-evaluate a dot setter
|
/// Chain-evaluate a dot setter
|
||||||
@ -250,14 +241,14 @@ impl Engine<'_> {
|
|||||||
) -> Result<Dynamic, EvalAltResult> {
|
) -> Result<Dynamic, EvalAltResult> {
|
||||||
match dot_rhs {
|
match dot_rhs {
|
||||||
// xxx.fn_name(args)
|
// xxx.fn_name(args)
|
||||||
Expr::FunctionCall(fn_name, args, def_val, pos) => {
|
Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => {
|
||||||
let mut args: Array = args
|
let mut values = arg_expr_list
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg| self.eval_expr(scope, arg))
|
.map(|arg_expr| self.eval_expr(scope, arg_expr))
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
let args = once(this_ptr)
|
let args = once(this_ptr)
|
||||||
.chain(args.iter_mut().map(|b| b.as_mut()))
|
.chain(values.iter_mut().map(|b| b.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)
|
||||||
@ -271,8 +262,9 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// xxx.idx_lhs[idx_expr]
|
// xxx.idx_lhs[idx_expr]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
||||||
let (expr, _) = 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::Identifier(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
@ -295,7 +287,7 @@ impl Engine<'_> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||||
self.get_indexed_value(expr, idx, idx_expr.position(), *idx_pos)
|
self.get_indexed_value(&val, idx, idx_expr.position(), *idx_pos)
|
||||||
.map(|(v, _)| v)
|
.map(|(v, _)| v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,8 +301,9 @@ impl Engine<'_> {
|
|||||||
.and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), rhs))
|
.and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), rhs))
|
||||||
}
|
}
|
||||||
// xxx.idx_lhs[idx_expr].rhs
|
// xxx.idx_lhs[idx_expr].rhs
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
||||||
let (expr, _) = 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::Identifier(id, pos) => {
|
||||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||||
@ -333,7 +326,7 @@ impl Engine<'_> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||||
self.get_indexed_value(expr, idx, idx_expr.position(), *idx_pos)
|
self.get_indexed_value(&val, idx, idx_expr.position(), *idx_pos)
|
||||||
.and_then(|(mut v, _)| self.get_dot_val_helper(scope, v.as_mut(), rhs))
|
.and_then(|(mut v, _)| self.get_dot_val_helper(scope, v.as_mut(), rhs))
|
||||||
}
|
}
|
||||||
// Syntax error
|
// Syntax error
|
||||||
@ -371,6 +364,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// idx_lhs[idx_expr].???
|
// idx_lhs[idx_expr].???
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
||||||
let (src_type, src, idx, mut target) =
|
let (src_type, src, idx, mut target) =
|
||||||
self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?;
|
self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?;
|
||||||
@ -413,29 +407,31 @@ impl Engine<'_> {
|
|||||||
.and_then(move |(idx, _, val)| map(val).map(|v| (idx, v)))
|
.and_then(move |(idx, _, val)| map(val).map(|v| (idx, v)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate the value of an index (must evaluate to i64)
|
/// Evaluate the value of an index (must evaluate to INT)
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn eval_index_value(
|
fn eval_index_value(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
idx_expr: &Expr,
|
idx_expr: &Expr,
|
||||||
) -> Result<i64, EvalAltResult> {
|
) -> Result<INT, EvalAltResult> {
|
||||||
self.eval_expr(scope, idx_expr)?
|
self.eval_expr(scope, idx_expr)?
|
||||||
.downcast::<i64>()
|
.downcast::<INT>()
|
||||||
.map(|v| *v)
|
.map(|v| *v)
|
||||||
.map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))
|
.map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value at the indexed position of a base type
|
/// Get the value at the indexed position of a base type
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn get_indexed_value(
|
fn get_indexed_value(
|
||||||
&self,
|
&self,
|
||||||
val: Dynamic,
|
val: &Dynamic,
|
||||||
idx: i64,
|
idx: INT,
|
||||||
val_pos: Position,
|
val_pos: Position,
|
||||||
idx_pos: Position,
|
idx_pos: Position,
|
||||||
) -> Result<(Dynamic, IndexSourceType), EvalAltResult> {
|
) -> Result<(Dynamic, IndexSourceType), EvalAltResult> {
|
||||||
if val.is::<Array>() {
|
if val.is::<Array>() {
|
||||||
// val_array[idx]
|
// val_array[idx]
|
||||||
let arr = val.downcast::<Array>().expect("array expected");
|
let arr = val.downcast_ref::<Array>().expect("array expected");
|
||||||
|
|
||||||
if idx >= 0 {
|
if idx >= 0 {
|
||||||
arr.get(idx as usize)
|
arr.get(idx as usize)
|
||||||
@ -447,7 +443,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
} else if val.is::<String>() {
|
} else if val.is::<String>() {
|
||||||
// val_string[idx]
|
// val_string[idx]
|
||||||
let s = val.downcast::<String>().expect("string expected");
|
let s = val.downcast_ref::<String>().expect("string expected");
|
||||||
|
|
||||||
if idx >= 0 {
|
if idx >= 0 {
|
||||||
s.chars()
|
s.chars()
|
||||||
@ -473,6 +469,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an index expression
|
/// Evaluate an index expression
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn eval_index_expr<'a>(
|
fn eval_index_expr<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
@ -487,7 +484,7 @@ impl Engine<'_> {
|
|||||||
Expr::Identifier(id, _) => Self::search_scope(
|
Expr::Identifier(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, (val, src_type))| {
|
||||||
@ -498,13 +495,14 @@ impl Engine<'_> {
|
|||||||
expr => {
|
expr => {
|
||||||
let val = self.eval_expr(scope, expr)?;
|
let val = self.eval_expr(scope, expr)?;
|
||||||
|
|
||||||
self.get_indexed_value(val, idx, idx_expr.position(), idx_pos)
|
self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos)
|
||||||
.map(|(v, _)| (IndexSourceType::Expression, None, idx as usize, v))
|
.map(|(v, _)| (IndexSourceType::Expression, None, idx as usize, v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace a character at an index position in a mutable string
|
/// Replace a character at an index position in a mutable string
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn str_replace_char(s: &mut String, idx: usize, new_ch: char) {
|
fn str_replace_char(s: &mut String, idx: usize, new_ch: char) {
|
||||||
let mut chars: Vec<char> = s.chars().collect();
|
let mut chars: Vec<char> = s.chars().collect();
|
||||||
let ch = *chars.get(idx).expect("string index out of bounds");
|
let ch = *chars.get(idx).expect("string index out of bounds");
|
||||||
@ -518,6 +516,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Update the value at an index position in a variable inside the scope
|
/// Update the value at an index position in a variable inside the scope
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn update_indexed_var_in_scope(
|
fn update_indexed_var_in_scope(
|
||||||
src_type: IndexSourceType,
|
src_type: IndexSourceType,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
@ -550,6 +549,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Update the value at an index position
|
/// Update the value at an index position
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn update_indexed_value(
|
fn update_indexed_value(
|
||||||
mut target: Dynamic,
|
mut target: Dynamic,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
@ -593,6 +593,7 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
// xxx.lhs[idx_expr]
|
// xxx.lhs[idx_expr]
|
||||||
// TODO - Allow chaining of indexing!
|
// TODO - Allow chaining of indexing!
|
||||||
|
#[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::Identifier(id, pos) => {
|
||||||
@ -636,6 +637,7 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
// xxx.lhs[idx_expr].rhs
|
// xxx.lhs[idx_expr].rhs
|
||||||
// TODO - Allow chaining of indexing!
|
// TODO - Allow chaining of indexing!
|
||||||
|
#[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::Identifier(id, pos) => {
|
||||||
@ -644,12 +646,8 @@ impl Engine<'_> {
|
|||||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||||
.and_then(|v| {
|
.and_then(|v| {
|
||||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||||
let (mut target, _) = self.get_indexed_value(
|
let (mut target, _) =
|
||||||
v.clone(), // TODO - Avoid cloning this
|
self.get_indexed_value(&v, idx, idx_expr.position(), *idx_pos)?;
|
||||||
idx,
|
|
||||||
idx_expr.position(),
|
|
||||||
*idx_pos,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
self.set_dot_val_helper(
|
self.set_dot_val_helper(
|
||||||
scope,
|
scope,
|
||||||
@ -720,6 +718,7 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
// lhs[idx_expr].???
|
// lhs[idx_expr].???
|
||||||
// TODO - Allow chaining of indexing!
|
// TODO - Allow chaining of indexing!
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(lhs, idx_expr, idx_pos) => {
|
Expr::Index(lhs, idx_expr, idx_pos) => {
|
||||||
let (src_type, src, idx, mut target) =
|
let (src_type, src, idx, mut target) =
|
||||||
self.eval_index_expr(scope, lhs, idx_expr, *idx_pos)?;
|
self.eval_index_expr(scope, lhs, idx_expr, *idx_pos)?;
|
||||||
@ -753,8 +752,10 @@ impl Engine<'_> {
|
|||||||
/// Evaluate an expression
|
/// Evaluate an expression
|
||||||
fn eval_expr(&mut self, scope: &mut Scope, expr: &Expr) -> Result<Dynamic, EvalAltResult> {
|
fn eval_expr(&mut self, scope: &mut Scope, expr: &Expr) -> Result<Dynamic, EvalAltResult> {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(f, _) => Ok(f.into_dynamic()),
|
Expr::FloatConstant(f, _) => Ok(f.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::Identifier(id, pos) => {
|
||||||
@ -762,10 +763,14 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// lhs[idx_expr]
|
// lhs[idx_expr]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(lhs, idx_expr, idx_pos) => self
|
Expr::Index(lhs, idx_expr, idx_pos) => self
|
||||||
.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),
|
||||||
|
|
||||||
@ -785,6 +790,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// idx_lhs[idx_expr] = rhs
|
// idx_lhs[idx_expr] = rhs
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
||||||
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)?;
|
||||||
@ -818,29 +824,53 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, lhs, rhs),
|
Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, lhs, rhs),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(contents, _) => {
|
Expr::Array(contents, _) => {
|
||||||
let mut arr = Vec::new();
|
let mut arr = Vec::new();
|
||||||
|
|
||||||
contents
|
for item in contents {
|
||||||
.iter()
|
arr.push(self.eval_expr(scope, item)?);
|
||||||
.try_for_each::<_, Result<_, EvalAltResult>>(|item| {
|
}
|
||||||
let arg = self.eval_expr(scope, item)?;
|
|
||||||
arr.push(arg);
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(Box::new(arr))
|
Ok(Box::new(arr))
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "no_index")]
|
||||||
|
Expr::Array(_, _) => panic!("encountered an array during no_index!"),
|
||||||
|
|
||||||
Expr::FunctionCall(fn_name, args, def_val, pos) => {
|
// Dump AST
|
||||||
let mut args = args
|
Expr::FunctionCall(fn_name, args_expr_list, _, pos) if fn_name == KEYWORD_DUMP_AST => {
|
||||||
|
let pos = if args_expr_list.len() == 0 {
|
||||||
|
*pos
|
||||||
|
} else {
|
||||||
|
args_expr_list[0].position()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Change the argument to a debug dump of the expressions
|
||||||
|
let result = args_expr_list
|
||||||
|
.into_iter()
|
||||||
|
.map(|expr| format!("{:#?}", expr))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
// Redirect call to `print`
|
||||||
|
self.call_fn_raw(
|
||||||
|
KEYWORD_PRINT,
|
||||||
|
vec![result.into_dynamic().as_mut()],
|
||||||
|
None,
|
||||||
|
pos,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal function call
|
||||||
|
Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => {
|
||||||
|
let mut values = args_expr_list
|
||||||
.iter()
|
.iter()
|
||||||
.map(|expr| self.eval_expr(scope, expr))
|
.map(|expr| self.eval_expr(scope, expr))
|
||||||
.collect::<Result<Array, _>>()?;
|
.collect::<Result<Vec<Dynamic>, _>>()?;
|
||||||
|
|
||||||
self.call_fn_raw(
|
self.call_fn_raw(
|
||||||
fn_name,
|
fn_name,
|
||||||
args.iter_mut().map(|b| b.as_mut()).collect(),
|
values.iter_mut().map(|b| b.as_mut()).collect(),
|
||||||
def_val.as_ref(),
|
def_val.as_ref(),
|
||||||
*pos,
|
*pos,
|
||||||
)
|
)
|
||||||
@ -987,22 +1017,22 @@ impl Engine<'_> {
|
|||||||
Stmt::Break(_) => Err(EvalAltResult::LoopBreak),
|
Stmt::Break(_) => Err(EvalAltResult::LoopBreak),
|
||||||
|
|
||||||
// Empty return
|
// Empty return
|
||||||
Stmt::ReturnWithVal(None, true, pos) => {
|
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
|
||||||
Err(EvalAltResult::Return(().into_dynamic(), *pos))
|
Err(EvalAltResult::Return(().into_dynamic(), *pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return value
|
// Return value
|
||||||
Stmt::ReturnWithVal(Some(a), true, pos) => {
|
Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => {
|
||||||
Err(EvalAltResult::Return(self.eval_expr(scope, a)?, *pos))
|
Err(EvalAltResult::Return(self.eval_expr(scope, a)?, *pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty throw
|
// Empty throw
|
||||||
Stmt::ReturnWithVal(None, false, pos) => {
|
Stmt::ReturnWithVal(None, ReturnType::Exception, pos) => {
|
||||||
Err(EvalAltResult::ErrorRuntime("".into(), *pos))
|
Err(EvalAltResult::ErrorRuntime("".into(), *pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throw value
|
// Throw value
|
||||||
Stmt::ReturnWithVal(Some(a), false, pos) => {
|
Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => {
|
||||||
let val = self.eval_expr(scope, a)?;
|
let val = self.eval_expr(scope, a)?;
|
||||||
Err(EvalAltResult::ErrorRuntime(
|
Err(EvalAltResult::ErrorRuntime(
|
||||||
val.downcast::<String>()
|
val.downcast::<String>()
|
||||||
@ -1013,13 +1043,14 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Let statement
|
// Let statement
|
||||||
Stmt::Let(name, init, _) => {
|
Stmt::Let(name, Some(expr), _) => {
|
||||||
if let Some(v) = init {
|
let val = self.eval_expr(scope, expr)?;
|
||||||
let val = self.eval_expr(scope, v)?;
|
scope.push_dynamic(name.clone(), val);
|
||||||
scope.push_dynamic(name.clone(), val);
|
Ok(().into_dynamic())
|
||||||
} else {
|
}
|
||||||
scope.push(name.clone(), ());
|
|
||||||
}
|
Stmt::Let(name, None, _) => {
|
||||||
|
scope.push(name.clone(), ());
|
||||||
Ok(().into_dynamic())
|
Ok(().into_dynamic())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,20 +120,20 @@ macro_rules! def_register {
|
|||||||
const NUM_ARGS: usize = count_args!($($par)*);
|
const NUM_ARGS: usize = count_args!($($par)*);
|
||||||
|
|
||||||
if args.len() != NUM_ARGS {
|
if args.len() != NUM_ARGS {
|
||||||
Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos))
|
return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos));
|
||||||
} else {
|
|
||||||
#[allow(unused_variables, unused_mut)]
|
|
||||||
let mut drain = args.drain(..);
|
|
||||||
$(
|
|
||||||
// Downcast every element, return in case of a type mismatch
|
|
||||||
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
|
|
||||||
)*
|
|
||||||
|
|
||||||
// Call the user-supplied function using ($clone) to
|
|
||||||
// potentially clone the value, otherwise pass the reference.
|
|
||||||
let r = f($(($clone)($par)),*);
|
|
||||||
Ok(Box::new(r) as Dynamic)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables, unused_mut)]
|
||||||
|
let mut drain = args.drain(..);
|
||||||
|
$(
|
||||||
|
// Downcast every element, return in case of a type mismatch
|
||||||
|
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
|
||||||
|
)*
|
||||||
|
|
||||||
|
// Call the user-supplied function using ($clone) to
|
||||||
|
// potentially clone the value, otherwise pass the reference.
|
||||||
|
let r = f($(($clone)($par)),*);
|
||||||
|
Ok(Box::new(r) as Dynamic)
|
||||||
};
|
};
|
||||||
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));
|
||||||
}
|
}
|
||||||
@ -152,19 +152,19 @@ macro_rules! def_register {
|
|||||||
const NUM_ARGS: usize = count_args!($($par)*);
|
const NUM_ARGS: usize = count_args!($($par)*);
|
||||||
|
|
||||||
if args.len() != NUM_ARGS {
|
if args.len() != NUM_ARGS {
|
||||||
Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos))
|
return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos));
|
||||||
} else {
|
|
||||||
#[allow(unused_variables, unused_mut)]
|
|
||||||
let mut drain = args.drain(..);
|
|
||||||
$(
|
|
||||||
// Downcast every element, return in case of a type mismatch
|
|
||||||
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
|
|
||||||
)*
|
|
||||||
|
|
||||||
// Call the user-supplied function using ($clone) to
|
|
||||||
// potentially clone the value, otherwise pass the reference.
|
|
||||||
Ok(f($(($clone)($par)),*))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables, unused_mut)]
|
||||||
|
let mut drain = args.drain(..);
|
||||||
|
$(
|
||||||
|
// Downcast every element, return in case of a type mismatch
|
||||||
|
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
|
||||||
|
)*
|
||||||
|
|
||||||
|
// Call the user-supplied function using ($clone) to
|
||||||
|
// potentially clone the value, otherwise pass the reference.
|
||||||
|
Ok(f($(($clone)($par)),*))
|
||||||
};
|
};
|
||||||
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));
|
||||||
}
|
}
|
||||||
@ -184,23 +184,23 @@ macro_rules! def_register {
|
|||||||
const NUM_ARGS: usize = count_args!($($par)*);
|
const NUM_ARGS: usize = count_args!($($par)*);
|
||||||
|
|
||||||
if args.len() != NUM_ARGS {
|
if args.len() != NUM_ARGS {
|
||||||
Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos))
|
return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos));
|
||||||
} else {
|
}
|
||||||
#[allow(unused_variables, unused_mut)]
|
|
||||||
let mut drain = args.drain(..);
|
|
||||||
$(
|
|
||||||
// Downcast every element, return in case of a type mismatch
|
|
||||||
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
|
|
||||||
)*
|
|
||||||
|
|
||||||
// Call the user-supplied function using ($clone) to
|
#[allow(unused_variables, unused_mut)]
|
||||||
// potentially clone the value, otherwise pass the reference.
|
let mut drain = args.drain(..);
|
||||||
match f($(($clone)($par)),*) {
|
$(
|
||||||
Ok(r) => Ok(Box::new(r) as Dynamic),
|
// Downcast every element, return in case of a type mismatch
|
||||||
Err(mut err) => {
|
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap();
|
||||||
err.set_position(pos);
|
)*
|
||||||
Err(err)
|
|
||||||
}
|
// Call the user-supplied function using ($clone) to
|
||||||
|
// potentially clone the value, otherwise pass the reference.
|
||||||
|
match f($(($clone)($par)),*) {
|
||||||
|
Ok(r) => Ok(Box::new(r) as Dynamic),
|
||||||
|
Err(mut err) => {
|
||||||
|
err.set_position(pos);
|
||||||
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -75,9 +75,12 @@ mod scope;
|
|||||||
|
|
||||||
pub use any::{Any, AnyExt, Dynamic, Variant};
|
pub use any::{Any, AnyExt, Dynamic, Variant};
|
||||||
pub use call::FuncArgs;
|
pub use call::FuncArgs;
|
||||||
pub use engine::{Array, 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};
|
pub use parser::{Position, AST, FLOAT, INT};
|
||||||
pub use result::EvalAltResult;
|
pub use result::EvalAltResult;
|
||||||
pub use scope::Scope;
|
pub use scope::Scope;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
pub use engine::Array;
|
||||||
|
210
src/optimize.rs
210
src/optimize.rs
@ -1,27 +1,51 @@
|
|||||||
|
use crate::engine::KEYWORD_DUMP_AST;
|
||||||
use crate::parser::{Expr, Stmt};
|
use crate::parser::{Expr, Stmt};
|
||||||
|
|
||||||
fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt {
|
fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt {
|
||||||
match stmt {
|
match stmt {
|
||||||
|
Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => {
|
||||||
|
*changed = true;
|
||||||
|
|
||||||
|
let pos = expr.position();
|
||||||
|
let expr = optimize_expr(*expr, changed);
|
||||||
|
|
||||||
|
match expr {
|
||||||
|
Expr::False(_) | Expr::True(_) => Stmt::Noop(stmt1.position()),
|
||||||
|
expr => {
|
||||||
|
let stmt = Stmt::Expr(Box::new(expr));
|
||||||
|
|
||||||
|
if preserve_result {
|
||||||
|
Stmt::Block(vec![stmt, *stmt1], pos)
|
||||||
|
} else {
|
||||||
|
stmt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Stmt::IfElse(expr, stmt1, None) => match *expr {
|
Stmt::IfElse(expr, stmt1, None) => match *expr {
|
||||||
Expr::False(pos) => {
|
Expr::False(pos) => {
|
||||||
*changed = true;
|
*changed = true;
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
Expr::True(_) => optimize_stmt(*stmt1, changed),
|
Expr::True(_) => optimize_stmt(*stmt1, changed, true),
|
||||||
expr => Stmt::IfElse(
|
expr => Stmt::IfElse(
|
||||||
Box::new(optimize_expr(expr, changed)),
|
Box::new(optimize_expr(expr, changed)),
|
||||||
Box::new(optimize_stmt(*stmt1, changed)),
|
Box::new(optimize_stmt(*stmt1, changed, 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),
|
Expr::False(_) => optimize_stmt(*stmt2, changed, true),
|
||||||
Expr::True(_) => optimize_stmt(*stmt1, changed),
|
Expr::True(_) => optimize_stmt(*stmt1, changed, true),
|
||||||
expr => Stmt::IfElse(
|
expr => Stmt::IfElse(
|
||||||
Box::new(optimize_expr(expr, changed)),
|
Box::new(optimize_expr(expr, changed)),
|
||||||
Box::new(optimize_stmt(*stmt1, changed)),
|
Box::new(optimize_stmt(*stmt1, changed, true)),
|
||||||
Some(Box::new(optimize_stmt(*stmt2, changed))),
|
match optimize_stmt(*stmt2, changed, true) {
|
||||||
|
stmt if stmt.is_noop() => None,
|
||||||
|
stmt => Some(Box::new(stmt)),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -30,18 +54,18 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt {
|
|||||||
*changed = true;
|
*changed = true;
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed))),
|
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed, false))),
|
||||||
expr => Stmt::While(
|
expr => Stmt::While(
|
||||||
Box::new(optimize_expr(expr, changed)),
|
Box::new(optimize_expr(expr, changed)),
|
||||||
Box::new(optimize_stmt(*stmt, changed)),
|
Box::new(optimize_stmt(*stmt, changed, false)),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed))),
|
Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed, 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, changed)),
|
||||||
Box::new(optimize_stmt(*stmt, changed)),
|
Box::new(optimize_stmt(*stmt, changed, 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, changed))), pos)
|
||||||
@ -49,46 +73,71 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt {
|
|||||||
Stmt::Let(_, None, _) => stmt,
|
Stmt::Let(_, None, _) => stmt,
|
||||||
|
|
||||||
Stmt::Block(statements, pos) => {
|
Stmt::Block(statements, pos) => {
|
||||||
let original_len = statements.len();
|
let orig_len = statements.len();
|
||||||
|
|
||||||
let mut result: Vec<_> = statements
|
let mut result: Vec<_> = statements
|
||||||
.into_iter() // For each statement
|
.into_iter() // For each statement
|
||||||
.map(|s| optimize_stmt(s, changed)) // Optimize the statement
|
.rev() // Scan in reverse
|
||||||
.filter(Stmt::is_op) // Remove no-op's
|
.map(|s| optimize_stmt(s, changed, preserve_result)) // Optimize the statement
|
||||||
|
.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
|
||||||
|
.map(|(_, s)| s)
|
||||||
|
.rev()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if let Some(last_stmt) = result.pop() {
|
// Remove all raw expression statements that are pure except for the very last statement
|
||||||
// Remove all raw expression statements that evaluate to constants
|
let last_stmt = if preserve_result { result.pop() } else { None };
|
||||||
// except for the very last statement
|
|
||||||
result.retain(|stmt| match stmt {
|
|
||||||
Stmt::Expr(expr) if expr.is_constant() => false,
|
|
||||||
_ => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
result.push(last_stmt);
|
result.retain(|stmt| match stmt {
|
||||||
|
Stmt::Expr(expr) if expr.is_pure() => false,
|
||||||
|
_ => true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(stmt) = last_stmt {
|
||||||
|
result.push(stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
*changed = *changed || original_len != result.len();
|
// Remove all let statements at the end of a block - the new variables will go away anyway.
|
||||||
|
// But be careful only remove ones that have no initial values or have values that are pure expressions,
|
||||||
|
// otherwise there may be side effects.
|
||||||
|
let mut removed = false;
|
||||||
|
|
||||||
|
while let Some(expr) = result.pop() {
|
||||||
|
match expr {
|
||||||
|
Stmt::Let(_, None, _) => removed = true,
|
||||||
|
Stmt::Let(_, Some(val_expr), _) if val_expr.is_pure() => removed = true,
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
result.push(expr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if preserve_result {
|
||||||
|
if removed {
|
||||||
|
result.push(Stmt::Noop(pos))
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result
|
||||||
|
.into_iter()
|
||||||
|
.rev()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, s)| optimize_stmt(s, changed, i == 0)) // Optimize all other statements again
|
||||||
|
.rev()
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
*changed = *changed || orig_len != result.len();
|
||||||
|
|
||||||
match result[..] {
|
match result[..] {
|
||||||
|
// No statements in block - change to No-op
|
||||||
[] => {
|
[] => {
|
||||||
// No statements in block - change to No-op
|
|
||||||
*changed = true;
|
*changed = true;
|
||||||
Stmt::Noop(pos)
|
Stmt::Noop(pos)
|
||||||
}
|
}
|
||||||
[Stmt::Let(_, None, _)] => {
|
// Only one statement - promote
|
||||||
// Only one empty variable declaration - change to No-op
|
|
||||||
*changed = true;
|
|
||||||
Stmt::Noop(pos)
|
|
||||||
}
|
|
||||||
[Stmt::Let(_, Some(_), _)] => {
|
|
||||||
// Only one let statement, but cannot promote
|
|
||||||
// (otherwise the variable gets declared in the scope above)
|
|
||||||
// and still need to run just in case there are side effects
|
|
||||||
Stmt::Block(result, pos)
|
|
||||||
}
|
|
||||||
[_] => {
|
[_] => {
|
||||||
// Only one statement - promote
|
|
||||||
*changed = true;
|
*changed = true;
|
||||||
result.remove(0)
|
result.remove(0)
|
||||||
}
|
}
|
||||||
@ -103,24 +152,14 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt {
|
|||||||
is_return,
|
is_return,
|
||||||
pos,
|
pos,
|
||||||
),
|
),
|
||||||
stmt @ Stmt::ReturnWithVal(None, _, _) => stmt,
|
|
||||||
|
|
||||||
stmt @ Stmt::Noop(_) | stmt @ Stmt::Break(_) => stmt,
|
stmt => stmt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::IntegerConstant(_, _)
|
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed, true) {
|
||||||
| Expr::FloatConstant(_, _)
|
|
||||||
| Expr::Identifier(_, _)
|
|
||||||
| Expr::CharConstant(_, _)
|
|
||||||
| Expr::StringConstant(_, _)
|
|
||||||
| Expr::True(_)
|
|
||||||
| Expr::False(_)
|
|
||||||
| Expr::Unit(_) => expr,
|
|
||||||
|
|
||||||
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed) {
|
|
||||||
Stmt::Noop(_) => {
|
Stmt::Noop(_) => {
|
||||||
*changed = true;
|
*changed = true;
|
||||||
Expr::Unit(pos)
|
Expr::Unit(pos)
|
||||||
@ -139,24 +178,43 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
|||||||
Box::new(optimize_expr(*rhs, changed)),
|
Box::new(optimize_expr(*rhs, changed)),
|
||||||
pos,
|
pos,
|
||||||
),
|
),
|
||||||
Expr::Index(lhs, rhs, pos) => Expr::Index(
|
|
||||||
Box::new(optimize_expr(*lhs, changed)),
|
#[cfg(not(feature = "no_index"))]
|
||||||
Box::new(optimize_expr(*rhs, changed)),
|
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
|
||||||
pos,
|
(Expr::Array(mut items, _), Expr::IntegerConstant(i, _))
|
||||||
),
|
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.
|
||||||
|
// All other items can be thrown away.
|
||||||
|
*changed = true;
|
||||||
|
items.remove(i as usize)
|
||||||
|
}
|
||||||
|
(lhs, rhs) => Expr::Index(
|
||||||
|
Box::new(optimize_expr(lhs, changed)),
|
||||||
|
Box::new(optimize_expr(rhs, changed)),
|
||||||
|
pos,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
#[cfg(feature = "no_index")]
|
||||||
|
Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(items, pos) => {
|
Expr::Array(items, pos) => {
|
||||||
let original_len = items.len();
|
let orig_len = items.len();
|
||||||
|
|
||||||
let items: Vec<_> = items
|
let items: Vec<_> = items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|expr| optimize_expr(expr, changed))
|
.map(|expr| optimize_expr(expr, changed))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
*changed = *changed || original_len != items.len();
|
*changed = *changed || orig_len != items.len();
|
||||||
|
|
||||||
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;
|
*changed = true;
|
||||||
@ -194,29 +252,44 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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) => {
|
Expr::FunctionCall(id, args, def_value, pos) => {
|
||||||
let original_len = args.len();
|
let orig_len = args.len();
|
||||||
|
|
||||||
let args: Vec<_> = args
|
let args: Vec<_> = args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|a| optimize_expr(a, changed))
|
.map(|a| optimize_expr(a, changed))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
*changed = *changed || original_len != args.len();
|
*changed = *changed || orig_len != args.len();
|
||||||
|
|
||||||
Expr::FunctionCall(id, args, def_value, pos)
|
Expr::FunctionCall(id, args, def_value, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expr => expr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn optimize(mut statements: Vec<Stmt>) -> Vec<Stmt> {
|
pub(crate) fn optimize(statements: Vec<Stmt>) -> Vec<Stmt> {
|
||||||
|
let mut result = statements;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut changed = false;
|
let mut changed = false;
|
||||||
|
|
||||||
statements = statements
|
result = result
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|stmt| optimize_stmt(stmt, &mut changed))
|
.rev() // Scan in reverse
|
||||||
.filter(Stmt::is_op)
|
.enumerate()
|
||||||
|
.map(|(i, stmt)| {
|
||||||
|
// Keep all variable declarations at this level
|
||||||
|
let keep = stmt.is_var();
|
||||||
|
|
||||||
|
// Always keep the last return value
|
||||||
|
optimize_stmt(stmt, &mut changed, keep || i == 0)
|
||||||
|
})
|
||||||
|
.rev()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if !changed {
|
if !changed {
|
||||||
@ -224,5 +297,18 @@ pub(crate) fn optimize(mut statements: Vec<Stmt>) -> Vec<Stmt> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statements
|
// Eliminate code that is pure but always keep the last statement
|
||||||
|
let last_stmt = result.pop();
|
||||||
|
|
||||||
|
// Remove all pure statements at top level
|
||||||
|
result.retain(|stmt| match stmt {
|
||||||
|
Stmt::Expr(expr) if expr.is_pure() => false,
|
||||||
|
_ => true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(stmt) = last_stmt {
|
||||||
|
result.push(stmt); // Add back the last statement
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
340
src/parser.rs
340
src/parser.rs
@ -3,7 +3,26 @@
|
|||||||
use crate::any::Dynamic;
|
use crate::any::Dynamic;
|
||||||
use crate::error::{LexError, ParseError, ParseErrorType};
|
use crate::error::{LexError, ParseError, ParseErrorType};
|
||||||
use crate::optimize::optimize;
|
use crate::optimize::optimize;
|
||||||
use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, usize};
|
|
||||||
|
use std::{
|
||||||
|
borrow::Cow, char, cmp::Ordering, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc,
|
||||||
|
usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The system integer type.
|
||||||
|
///
|
||||||
|
/// If the `only_i32` feature is enabled, this will be `i32` instead.
|
||||||
|
#[cfg(not(feature = "only_i32"))]
|
||||||
|
pub type INT = i64;
|
||||||
|
|
||||||
|
/// The system integer type
|
||||||
|
///
|
||||||
|
/// If the `only_i32` feature is not enabled, this will be `i64` instead.
|
||||||
|
#[cfg(feature = "only_i32")]
|
||||||
|
pub type INT = i32;
|
||||||
|
|
||||||
|
/// The system floating-point type
|
||||||
|
pub type FLOAT = f64;
|
||||||
|
|
||||||
type LERR = LexError;
|
type LERR = LexError;
|
||||||
type PERR = ParseErrorType;
|
type PERR = ParseErrorType;
|
||||||
@ -123,16 +142,35 @@ impl fmt::Debug for Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compiled AST (abstract syntax tree) of a Rhai script.
|
/// Compiled AST (abstract syntax tree) of a Rhai script.
|
||||||
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<FnDef<'static>>);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FnDef<'a> {
|
pub struct AST(
|
||||||
pub name: Cow<'a, str>,
|
pub(crate) Vec<Stmt>,
|
||||||
pub params: Vec<Cow<'a, str>>,
|
#[cfg(not(feature = "no_function"))] pub(crate) Vec<Arc<FnDef>>,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Debug)] // Do not derive Clone because it is expensive
|
||||||
|
pub struct FnDef {
|
||||||
|
pub name: String,
|
||||||
|
pub params: Vec<String>,
|
||||||
pub body: Stmt,
|
pub body: Stmt,
|
||||||
pub pos: Position,
|
pub pos: Position,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FnDef {
|
||||||
|
pub fn compare(&self, name: &str, params_len: usize) -> Ordering {
|
||||||
|
match self.name.as_str().cmp(name) {
|
||||||
|
Ordering::Equal => self.params.len().cmp(¶ms_len),
|
||||||
|
order => order,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||||
|
pub enum ReturnType {
|
||||||
|
Return,
|
||||||
|
Exception,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Stmt {
|
pub enum Stmt {
|
||||||
Noop(Position),
|
Noop(Position),
|
||||||
@ -144,22 +182,49 @@ pub enum Stmt {
|
|||||||
Block(Vec<Stmt>, Position),
|
Block(Vec<Stmt>, Position),
|
||||||
Expr(Box<Expr>),
|
Expr(Box<Expr>),
|
||||||
Break(Position),
|
Break(Position),
|
||||||
ReturnWithVal(Option<Box<Expr>>, bool, Position),
|
ReturnWithVal(Option<Box<Expr>>, ReturnType, Position),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stmt {
|
impl Stmt {
|
||||||
|
pub fn is_noop(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Stmt::Noop(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_op(&self) -> bool {
|
pub fn is_op(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Stmt::Noop(_) => false,
|
Stmt::Noop(_) => false,
|
||||||
_ => true,
|
_ => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_var(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Stmt::Let(_, _, _) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn position(&self) -> Position {
|
||||||
|
match self {
|
||||||
|
Stmt::Noop(pos)
|
||||||
|
| Stmt::Let(_, _, pos)
|
||||||
|
| Stmt::Block(_, pos)
|
||||||
|
| Stmt::Break(pos)
|
||||||
|
| Stmt::ReturnWithVal(_, _, pos) => *pos,
|
||||||
|
Stmt::IfElse(expr, _, _) | Stmt::Expr(expr) => expr.position(),
|
||||||
|
Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Expr {
|
pub enum Expr {
|
||||||
IntegerConstant(i64, Position),
|
IntegerConstant(INT, Position),
|
||||||
FloatConstant(f64, Position),
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
FloatConstant(FLOAT, Position),
|
||||||
Identifier(String, Position),
|
Identifier(String, Position),
|
||||||
CharConstant(char, Position),
|
CharConstant(char, Position),
|
||||||
StringConstant(String, Position),
|
StringConstant(String, Position),
|
||||||
@ -180,36 +245,56 @@ impl Expr {
|
|||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
Expr::IntegerConstant(_, pos)
|
Expr::IntegerConstant(_, pos)
|
||||||
| Expr::FloatConstant(_, pos)
|
|
||||||
| Expr::Identifier(_, pos)
|
| Expr::Identifier(_, pos)
|
||||||
| Expr::CharConstant(_, pos)
|
| Expr::CharConstant(_, pos)
|
||||||
| Expr::StringConstant(_, pos)
|
| Expr::StringConstant(_, pos)
|
||||||
| Expr::FunctionCall(_, _, _, pos)
|
|
||||||
| Expr::Stmt(_, pos)
|
| Expr::Stmt(_, pos)
|
||||||
|
| Expr::FunctionCall(_, _, _, pos)
|
||||||
| Expr::Array(_, 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::Index(e, _, _)
|
Expr::Assignment(e, _, _)
|
||||||
| Expr::Assignment(e, _, _)
|
|
||||||
| Expr::Dot(e, _, _)
|
| Expr::Dot(e, _, _)
|
||||||
|
| Expr::Index(e, _, _)
|
||||||
| Expr::And(e, _)
|
| Expr::And(e, _)
|
||||||
| Expr::Or(e, _) => e.position(),
|
| Expr::Or(e, _) => e.position(),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
Expr::FloatConstant(_, pos) => *pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is this expression pure?
|
||||||
|
///
|
||||||
|
/// A pure expression has no side effects.
|
||||||
|
pub fn is_pure(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_constant(&self) -> bool {
|
pub fn is_constant(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Expr::IntegerConstant(_, _)
|
Expr::IntegerConstant(_, _)
|
||||||
| Expr::FloatConstant(_, _)
|
|
||||||
| Expr::Identifier(_, _)
|
|
||||||
| Expr::CharConstant(_, _)
|
| Expr::CharConstant(_, _)
|
||||||
| Expr::StringConstant(_, _)
|
| Expr::StringConstant(_, _)
|
||||||
| Expr::True(_)
|
| Expr::True(_)
|
||||||
| Expr::False(_)
|
| Expr::False(_)
|
||||||
| Expr::Unit(_) => true,
|
| Expr::Unit(_) => true,
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
Expr::FloatConstant(_, _) => true,
|
||||||
|
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn is_identifier(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Expr::Identifier(_, _) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,8 +302,9 @@ impl Expr {
|
|||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Token {
|
pub enum Token {
|
||||||
IntegerConstant(i64),
|
IntegerConstant(INT),
|
||||||
FloatConstant(f64),
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
FloatConstant(FLOAT),
|
||||||
Identifier(String),
|
Identifier(String),
|
||||||
CharConstant(char),
|
CharConstant(char),
|
||||||
StringConst(String),
|
StringConst(String),
|
||||||
@ -288,6 +374,7 @@ impl Token {
|
|||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
IntegerConstant(ref i) => i.to_string().into(),
|
IntegerConstant(ref i) => i.to_string().into(),
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
FloatConstant(ref f) => f.to_string().into(),
|
FloatConstant(ref f) => f.to_string().into(),
|
||||||
Identifier(ref s) => s.into(),
|
Identifier(ref s) => s.into(),
|
||||||
CharConstant(ref c) => c.to_string().into(),
|
CharConstant(ref c) => c.to_string().into(),
|
||||||
@ -468,14 +555,9 @@ impl<'a> TokenIterator<'a> {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
let next_char = self.char_stream.next();
|
let next_char = self.char_stream.next();
|
||||||
|
|
||||||
if next_char.is_none() {
|
|
||||||
return Err((LERR::UnterminatedString, Position::eof()));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.advance();
|
self.advance();
|
||||||
|
|
||||||
match next_char.unwrap() {
|
match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? {
|
||||||
'\\' if escape.is_empty() => {
|
'\\' if escape.is_empty() => {
|
||||||
escape.push('\\');
|
escape.push('\\');
|
||||||
}
|
}
|
||||||
@ -617,6 +699,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
self.char_stream.next();
|
self.char_stream.next();
|
||||||
self.advance();
|
self.advance();
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
'.' => {
|
'.' => {
|
||||||
result.push(next_char);
|
result.push(next_char);
|
||||||
self.char_stream.next();
|
self.char_stream.next();
|
||||||
@ -692,7 +775,7 @@ impl<'a> TokenIterator<'a> {
|
|||||||
let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect();
|
let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect();
|
||||||
|
|
||||||
return Some((
|
return Some((
|
||||||
i64::from_str_radix(&out, radix)
|
INT::from_str_radix(&out, radix)
|
||||||
.map(Token::IntegerConstant)
|
.map(Token::IntegerConstant)
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
Token::LexError(LERR::MalformedNumber(result.iter().collect()))
|
Token::LexError(LERR::MalformedNumber(result.iter().collect()))
|
||||||
@ -702,10 +785,21 @@ impl<'a> TokenIterator<'a> {
|
|||||||
} else {
|
} else {
|
||||||
let out: String = result.iter().filter(|&&c| c != '_').collect();
|
let out: String = result.iter().filter(|&&c| c != '_').collect();
|
||||||
|
|
||||||
|
#[cfg(feature = "no_float")]
|
||||||
return Some((
|
return Some((
|
||||||
i64::from_str(&out)
|
INT::from_str(&out)
|
||||||
.map(Token::IntegerConstant)
|
.map(Token::IntegerConstant)
|
||||||
.or_else(|_| f64::from_str(&out).map(Token::FloatConstant))
|
.unwrap_or_else(|_| {
|
||||||
|
Token::LexError(LERR::MalformedNumber(result.iter().collect()))
|
||||||
|
}),
|
||||||
|
pos,
|
||||||
|
));
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
return Some((
|
||||||
|
INT::from_str(&out)
|
||||||
|
.map(Token::IntegerConstant)
|
||||||
|
.or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant))
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
Token::LexError(LERR::MalformedNumber(result.iter().collect()))
|
Token::LexError(LERR::MalformedNumber(result.iter().collect()))
|
||||||
}),
|
}),
|
||||||
@ -797,7 +891,8 @@ impl<'a> TokenIterator<'a> {
|
|||||||
}
|
}
|
||||||
'-' => match self.char_stream.peek() {
|
'-' => match self.char_stream.peek() {
|
||||||
// Negative number?
|
// Negative number?
|
||||||
Some('0'..='9') => negated = true,
|
Some('0'..='9') if self.last.is_next_unary() => negated = true,
|
||||||
|
Some('0'..='9') => return Some((Token::Minus, pos)),
|
||||||
Some('=') => {
|
Some('=') => {
|
||||||
self.char_stream.next();
|
self.char_stream.next();
|
||||||
self.advance();
|
self.advance();
|
||||||
@ -1126,26 +1221,26 @@ fn parse_call_expr<'a>(
|
|||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
begin: Position,
|
begin: Position,
|
||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
let mut args = Vec::new();
|
let mut args_expr_list = Vec::new();
|
||||||
|
|
||||||
if let Some(&(Token::RightParen, _)) = input.peek() {
|
if let Some(&(Token::RightParen, _)) = input.peek() {
|
||||||
input.next();
|
input.next();
|
||||||
return Ok(Expr::FunctionCall(id, args, None, begin));
|
return Ok(Expr::FunctionCall(id, args_expr_list, None, begin));
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
args.push(parse_expr(input)?);
|
args_expr_list.push(parse_expr(input)?);
|
||||||
|
|
||||||
match input.peek() {
|
match input.peek() {
|
||||||
Some(&(Token::RightParen, _)) => {
|
Some(&(Token::RightParen, _)) => {
|
||||||
input.next();
|
input.next();
|
||||||
return Ok(Expr::FunctionCall(id, args, None, begin));
|
return Ok(Expr::FunctionCall(id, args_expr_list, None, begin));
|
||||||
}
|
}
|
||||||
Some(&(Token::Comma, _)) => (),
|
Some(&(Token::Comma, _)) => (),
|
||||||
Some(&(_, pos)) => {
|
Some(&(_, pos)) => {
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
PERR::MissingRightParen(format!(
|
PERR::MissingRightParen(format!(
|
||||||
"closing the arguments list to function call of '{}'",
|
"closing the parameters list to function call of '{}'",
|
||||||
id
|
id
|
||||||
)),
|
)),
|
||||||
pos,
|
pos,
|
||||||
@ -1154,7 +1249,7 @@ fn parse_call_expr<'a>(
|
|||||||
None => {
|
None => {
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
PERR::MissingRightParen(format!(
|
PERR::MissingRightParen(format!(
|
||||||
"closing the arguments list to function call of '{}'",
|
"closing the parameters list to function call of '{}'",
|
||||||
id
|
id
|
||||||
)),
|
)),
|
||||||
Position::eof(),
|
Position::eof(),
|
||||||
@ -1166,6 +1261,7 @@ fn parse_call_expr<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn parse_index_expr<'a>(
|
fn parse_index_expr<'a>(
|
||||||
lhs: Box<Expr>,
|
lhs: Box<Expr>,
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
@ -1184,6 +1280,7 @@ fn parse_index_expr<'a>(
|
|||||||
*pos,
|
*pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(_, pos) => {
|
Expr::FloatConstant(_, pos) => {
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
PERR::MalformedIndexExpr("Array access expects integer index, not a float".into()),
|
PERR::MalformedIndexExpr("Array access expects integer index, not a float".into()),
|
||||||
@ -1260,6 +1357,7 @@ fn parse_ident_expr<'a>(
|
|||||||
input.next();
|
input.next();
|
||||||
parse_call_expr(id, input, begin)
|
parse_call_expr(id, input, begin)
|
||||||
}
|
}
|
||||||
|
#[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::Identifier(id, begin)), input, pos)
|
||||||
@ -1269,6 +1367,7 @@ fn parse_ident_expr<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
fn parse_array_expr<'a>(
|
fn parse_array_expr<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
begin: Position,
|
begin: Position,
|
||||||
@ -1320,26 +1419,30 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
|
|||||||
|
|
||||||
let token = input.next();
|
let token = input.next();
|
||||||
|
|
||||||
let mut follow_on = false;
|
let mut can_be_indexed = false;
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
let mut root_expr = match token {
|
let mut root_expr = match token {
|
||||||
Some((Token::IntegerConstant(x), pos)) => Ok(Expr::IntegerConstant(x, pos)),
|
#[cfg(not(feature = "no_float"))]
|
||||||
Some((Token::FloatConstant(x), pos)) => Ok(Expr::FloatConstant(x, pos)),
|
Some((Token::FloatConstant(x), pos)) => Ok(Expr::FloatConstant(x, pos)),
|
||||||
|
|
||||||
|
Some((Token::IntegerConstant(x), pos)) => Ok(Expr::IntegerConstant(x, pos)),
|
||||||
Some((Token::CharConstant(c), pos)) => Ok(Expr::CharConstant(c, pos)),
|
Some((Token::CharConstant(c), pos)) => Ok(Expr::CharConstant(c, pos)),
|
||||||
Some((Token::StringConst(s), pos)) => {
|
Some((Token::StringConst(s), pos)) => {
|
||||||
follow_on = true;
|
can_be_indexed = true;
|
||||||
Ok(Expr::StringConstant(s, pos))
|
Ok(Expr::StringConstant(s, pos))
|
||||||
}
|
}
|
||||||
Some((Token::Identifier(s), pos)) => {
|
Some((Token::Identifier(s), pos)) => {
|
||||||
follow_on = true;
|
can_be_indexed = true;
|
||||||
parse_ident_expr(s, input, pos)
|
parse_ident_expr(s, input, pos)
|
||||||
}
|
}
|
||||||
Some((Token::LeftParen, pos)) => {
|
Some((Token::LeftParen, pos)) => {
|
||||||
follow_on = true;
|
can_be_indexed = true;
|
||||||
parse_paren_expr(input, pos)
|
parse_paren_expr(input, pos)
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Some((Token::LeftBracket, pos)) => {
|
Some((Token::LeftBracket, pos)) => {
|
||||||
follow_on = true;
|
can_be_indexed = true;
|
||||||
parse_array_expr(input, pos)
|
parse_array_expr(input, pos)
|
||||||
}
|
}
|
||||||
Some((Token::True, pos)) => Ok(Expr::True(pos)),
|
Some((Token::True, pos)) => Ok(Expr::True(pos)),
|
||||||
@ -1354,14 +1457,13 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
|
|||||||
None => Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())),
|
None => Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
if !follow_on {
|
if can_be_indexed {
|
||||||
return Ok(root_expr);
|
// Tail processing all possible indexing
|
||||||
}
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
while let Some(&(Token::LeftBracket, pos)) = input.peek() {
|
||||||
// Tail processing all possible indexing
|
input.next();
|
||||||
while let Some(&(Token::LeftBracket, pos)) = input.peek() {
|
root_expr = parse_index_expr(Box::new(root_expr), input, pos)?;
|
||||||
input.next();
|
}
|
||||||
root_expr = parse_index_expr(Box::new(root_expr), input, pos)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(root_expr)
|
Ok(root_expr)
|
||||||
@ -1374,14 +1476,30 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
|||||||
|
|
||||||
match parse_unary(input) {
|
match parse_unary(input) {
|
||||||
// Negative integer
|
// Negative integer
|
||||||
Ok(Expr::IntegerConstant(i, pos)) => Ok(i
|
Ok(Expr::IntegerConstant(i, _)) => i
|
||||||
.checked_neg()
|
.checked_neg()
|
||||||
.map(|x| Expr::IntegerConstant(x, pos))
|
.map(|x| Expr::IntegerConstant(x, pos))
|
||||||
.unwrap_or_else(|| Expr::FloatConstant(-(i as f64), pos))),
|
.or_else(|| {
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
return Some(Expr::FloatConstant(-(i as FLOAT), pos));
|
||||||
|
|
||||||
|
#[cfg(feature = "no_float")]
|
||||||
|
return None;
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ParseError::new(
|
||||||
|
PERR::BadInput(LERR::MalformedNumber(format!("-{}", i)).to_string()),
|
||||||
|
pos,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
|
||||||
// Negative float
|
// Negative float
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
Ok(Expr::FloatConstant(f, pos)) => Ok(Expr::FloatConstant(-f, pos)),
|
Ok(Expr::FloatConstant(f, pos)) => Ok(Expr::FloatConstant(-f, pos)),
|
||||||
|
|
||||||
// Call negative function
|
// Call negative function
|
||||||
Ok(expr) => Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)),
|
Ok(expr) => Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)),
|
||||||
|
|
||||||
err @ Err(_) => err,
|
err @ Err(_) => err,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1408,17 +1526,21 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
|
|||||||
match expr {
|
match expr {
|
||||||
Expr::Identifier(_, pos) => (true, *pos),
|
Expr::Identifier(_, pos) => (true, *pos),
|
||||||
|
|
||||||
Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() {
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Identifier(_, _) => (true, idx_lhs.position()),
|
Expr::Index(idx_lhs, _, _) if idx_lhs.is_identifier() => (true, idx_lhs.position()),
|
||||||
_ => (false, idx_lhs.position()),
|
#[cfg(not(feature = "no_index"))]
|
||||||
},
|
Expr::Index(idx_lhs, _, _) => (false, idx_lhs.position()),
|
||||||
|
|
||||||
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::Identifier(_, _) => valid_assignment_chain(dot_rhs),
|
||||||
Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() {
|
|
||||||
Expr::Identifier(_, _) => valid_assignment_chain(dot_rhs),
|
#[cfg(not(feature = "no_index"))]
|
||||||
_ => (false, idx_lhs.position()),
|
Expr::Index(idx_lhs, _, _) if idx_lhs.is_identifier() => {
|
||||||
},
|
valid_assignment_chain(dot_rhs)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
Expr::Index(idx_lhs, _, _) => (false, idx_lhs.position()),
|
||||||
|
|
||||||
_ => (false, dot_lhs.position()),
|
_ => (false, dot_lhs.position()),
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1426,7 +1548,7 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//println!("{:?} = {:?}", lhs, rhs);
|
//println!("{:#?} = {:#?}", lhs, rhs);
|
||||||
|
|
||||||
match valid_assignment_chain(&lhs) {
|
match valid_assignment_chain(&lhs) {
|
||||||
(true, _) => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
|
(true, _) => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
|
||||||
@ -1447,29 +1569,6 @@ fn parse_op_assignment(
|
|||||||
Expr::FunctionCall(function.into(), vec![lhs_copy, rhs], None, pos),
|
Expr::FunctionCall(function.into(), vec![lhs_copy, rhs], None, pos),
|
||||||
pos,
|
pos,
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
|
||||||
const LHS_VALUE: &'static str = "@LHS_VALUE@";
|
|
||||||
|
|
||||||
let lhs_pos = lhs.position();
|
|
||||||
|
|
||||||
Ok(Expr::Block(
|
|
||||||
Box::new(Stmt::Block(vec![
|
|
||||||
Stmt::Let(LHS_VALUE.to_string(), Some(Box::new(lhs)), lhs_pos),
|
|
||||||
Stmt::Expr(Box::new(parse_assignment(
|
|
||||||
lhs,
|
|
||||||
Expr::FunctionCall(
|
|
||||||
function.into(),
|
|
||||||
vec![Expr::Identifier(LHS_VALUE.to_string(), lhs_pos), rhs],
|
|
||||||
None,
|
|
||||||
pos,
|
|
||||||
),
|
|
||||||
pos,
|
|
||||||
)?)),
|
|
||||||
])),
|
|
||||||
pos,
|
|
||||||
))
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_binary_op<'a>(
|
fn parse_binary_op<'a>(
|
||||||
@ -1519,6 +1618,7 @@ fn parse_binary_op<'a>(
|
|||||||
Token::Equals => parse_assignment(current_lhs, rhs, pos)?,
|
Token::Equals => parse_assignment(current_lhs, rhs, pos)?,
|
||||||
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 => Expr::Dot(Box::new(current_lhs), Box::new(rhs), pos),
|
||||||
|
|
||||||
// Comparison operators default to false when passed invalid operands
|
// Comparison operators default to false when passed invalid operands
|
||||||
@ -1686,13 +1786,11 @@ fn parse_var<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
||||||
match input.peek() {
|
let pos = match input.next() {
|
||||||
Some(&(Token::LeftBrace, _)) => (),
|
Some((Token::LeftBrace, pos)) => pos,
|
||||||
Some(&(_, pos)) => return Err(ParseError::new(PERR::MissingLeftBrace, pos)),
|
Some((_, pos)) => return Err(ParseError::new(PERR::MissingLeftBrace, pos)),
|
||||||
None => return Err(ParseError::new(PERR::MissingLeftBrace, Position::eof())),
|
None => return Err(ParseError::new(PERR::MissingLeftBrace, Position::eof())),
|
||||||
}
|
};
|
||||||
|
|
||||||
let pos = input.next().unwrap().1;
|
|
||||||
|
|
||||||
let mut statements = Vec::new();
|
let mut statements = Vec::new();
|
||||||
|
|
||||||
@ -1748,23 +1846,23 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
|
|||||||
Ok(Stmt::Break(pos))
|
Ok(Stmt::Break(pos))
|
||||||
}
|
}
|
||||||
Some(&(ref token @ Token::Return, _)) | Some(&(ref token @ Token::Throw, _)) => {
|
Some(&(ref token @ Token::Return, _)) | Some(&(ref token @ Token::Throw, _)) => {
|
||||||
let is_return = match token {
|
let return_type = match token {
|
||||||
Token::Return => true,
|
Token::Return => ReturnType::Return,
|
||||||
Token::Throw => false,
|
Token::Throw => ReturnType::Exception,
|
||||||
_ => panic!(),
|
_ => panic!("unexpected token!"),
|
||||||
};
|
};
|
||||||
|
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
match input.peek() {
|
match input.peek() {
|
||||||
// return; or throw;
|
// return; or throw;
|
||||||
Some(&(Token::SemiColon, pos)) => Ok(Stmt::ReturnWithVal(None, is_return, pos)),
|
Some(&(Token::SemiColon, pos)) => Ok(Stmt::ReturnWithVal(None, return_type, pos)),
|
||||||
// Just a return/throw without anything at the end of script
|
// Just a return/throw without anything at the end of script
|
||||||
None => Ok(Stmt::ReturnWithVal(None, is_return, Position::eof())),
|
None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())),
|
||||||
// return or throw with expression
|
// return or throw with expression
|
||||||
Some(&(_, pos)) => {
|
Some(&(_, pos)) => {
|
||||||
let ret = parse_expr(input)?;
|
let ret = parse_expr(input)?;
|
||||||
Ok(Stmt::ReturnWithVal(Some(Box::new(ret)), is_return, pos))
|
Ok(Stmt::ReturnWithVal(Some(Box::new(ret)), return_type, pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1774,7 +1872,8 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef<'static>, ParseError> {
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, 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())),
|
||||||
@ -1835,7 +1934,7 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef<'static
|
|||||||
let body = parse_block(input)?;
|
let body = parse_block(input)?;
|
||||||
|
|
||||||
Ok(FnDef {
|
Ok(FnDef {
|
||||||
name: name.into(),
|
name,
|
||||||
params,
|
params,
|
||||||
body,
|
body,
|
||||||
pos,
|
pos,
|
||||||
@ -1846,12 +1945,23 @@ fn parse_top_level<'a>(
|
|||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
optimize_ast: bool,
|
optimize_ast: bool,
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
let mut statements = Vec::new();
|
let mut statements = Vec::<Stmt>::new();
|
||||||
let mut functions = Vec::new();
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
let mut functions = Vec::<FnDef>::new();
|
||||||
|
|
||||||
while input.peek().is_some() {
|
while input.peek().is_some() {
|
||||||
match input.peek() {
|
match input.peek() {
|
||||||
Some(&(Token::Fn, _)) => functions.push(parse_fn(input)?),
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
Some(&(Token::Fn, _)) => {
|
||||||
|
let f = parse_fn(input)?;
|
||||||
|
|
||||||
|
// Ensure list is sorted
|
||||||
|
match functions.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) {
|
||||||
|
Ok(n) => functions[n] = f, // Override previous definition
|
||||||
|
Err(n) => functions.insert(n, f), // New function definition
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => statements.push(parse_stmt(input)?),
|
_ => statements.push(parse_stmt(input)?),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1861,21 +1971,25 @@ fn parse_top_level<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(if optimize_ast {
|
return Ok(AST(
|
||||||
AST(
|
if optimize_ast {
|
||||||
optimize(statements),
|
optimize(statements)
|
||||||
functions
|
} else {
|
||||||
.into_iter()
|
statements
|
||||||
.map(|mut fn_def| {
|
},
|
||||||
|
#[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]);
|
let mut body = optimize(vec![fn_def.body]);
|
||||||
fn_def.body = body.pop().unwrap();
|
fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos));
|
||||||
fn_def
|
}
|
||||||
})
|
Arc::new(fn_def)
|
||||||
.collect(),
|
})
|
||||||
)
|
.collect(),
|
||||||
} else {
|
));
|
||||||
AST(statements, functions)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse<'a>(
|
pub fn parse<'a>(
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
use crate::any::Dynamic;
|
use crate::any::Dynamic;
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
use crate::parser::Position;
|
use crate::parser::{Position, INT};
|
||||||
|
|
||||||
use std::{error::Error, fmt, path::PathBuf};
|
use std::{error::Error, fmt, path::PathBuf};
|
||||||
|
|
||||||
/// Evaluation result.
|
/// Evaluation result.
|
||||||
@ -24,10 +25,10 @@ pub enum EvalAltResult {
|
|||||||
ErrorCharMismatch(Position),
|
ErrorCharMismatch(Position),
|
||||||
/// Array access out-of-bounds.
|
/// Array access out-of-bounds.
|
||||||
/// Wrapped values are the current number of elements in the array and the index number.
|
/// Wrapped values are the current number of elements in the array and the index number.
|
||||||
ErrorArrayBounds(usize, i64, Position),
|
ErrorArrayBounds(usize, INT, Position),
|
||||||
/// String indexing out-of-bounds.
|
/// String indexing out-of-bounds.
|
||||||
/// Wrapped values are the current number of characters in the string and the index number.
|
/// Wrapped values are the current number of characters in the string and the index number.
|
||||||
ErrorStringBounds(usize, i64, Position),
|
ErrorStringBounds(usize, INT, Position),
|
||||||
/// Trying to index into a type that is not an array and not a string.
|
/// Trying to index into a type that is not an array and not a string.
|
||||||
ErrorIndexingType(String, Position),
|
ErrorIndexingType(String, Position),
|
||||||
/// Trying to index into an array or string with an index that is not `i64`.
|
/// Trying to index into an array or string with an index that is not `i64`.
|
||||||
@ -75,12 +76,12 @@ impl Error for EvalAltResult {
|
|||||||
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
|
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
|
||||||
"Array access expects non-negative index"
|
"Array access expects non-negative index"
|
||||||
}
|
}
|
||||||
Self::ErrorArrayBounds(max, _, _) if *max == 0 => "Access of empty array",
|
Self::ErrorArrayBounds(0, _, _) => "Access of empty array",
|
||||||
Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds",
|
Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds",
|
||||||
Self::ErrorStringBounds(_, index, _) if *index < 0 => {
|
Self::ErrorStringBounds(_, index, _) if *index < 0 => {
|
||||||
"Indexing a string expects a non-negative index"
|
"Indexing a string expects a non-negative index"
|
||||||
}
|
}
|
||||||
Self::ErrorStringBounds(max, _, _) if *max == 0 => "Indexing of empty string",
|
Self::ErrorStringBounds(0, _, _) => "Indexing of empty string",
|
||||||
Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
|
Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
|
||||||
Self::ErrorIfGuard(_) => "If guard expects boolean expression",
|
Self::ErrorIfGuard(_) => "If guard expects boolean expression",
|
||||||
Self::ErrorFor(_) => "For loop expects array or range",
|
Self::ErrorFor(_) => "For loop expects array or range",
|
||||||
@ -128,6 +129,16 @@ impl fmt::Display for EvalAltResult {
|
|||||||
write!(f, "{} '{}': {}", desc, path.display(), err)
|
write!(f, "{} '{}': {}", desc, path.display(), err)
|
||||||
}
|
}
|
||||||
Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p),
|
Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p),
|
||||||
|
Self::ErrorFunctionArgsMismatch(fun, 0, n, pos) => write!(
|
||||||
|
f,
|
||||||
|
"Function '{}' expects no argument but {} found ({})",
|
||||||
|
fun, n, pos
|
||||||
|
),
|
||||||
|
Self::ErrorFunctionArgsMismatch(fun, 1, n, pos) => write!(
|
||||||
|
f,
|
||||||
|
"Function '{}' expects one argument but {} found ({})",
|
||||||
|
fun, n, pos
|
||||||
|
),
|
||||||
Self::ErrorFunctionArgsMismatch(fun, need, n, pos) => write!(
|
Self::ErrorFunctionArgsMismatch(fun, need, n, pos) => write!(
|
||||||
f,
|
f,
|
||||||
"Function '{}' expects {} argument(s) but {} found ({})",
|
"Function '{}' expects {} argument(s) but {} found ({})",
|
||||||
@ -142,26 +153,30 @@ impl fmt::Display for EvalAltResult {
|
|||||||
Self::ErrorArrayBounds(_, index, pos) if *index < 0 => {
|
Self::ErrorArrayBounds(_, index, pos) if *index < 0 => {
|
||||||
write!(f, "{}: {} < 0 ({})", desc, index, pos)
|
write!(f, "{}: {} < 0 ({})", desc, index, pos)
|
||||||
}
|
}
|
||||||
Self::ErrorArrayBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos),
|
Self::ErrorArrayBounds(0, _, pos) => write!(f, "{} ({})", desc, pos),
|
||||||
|
Self::ErrorArrayBounds(1, index, pos) => write!(
|
||||||
|
f,
|
||||||
|
"Array index {} is out of bounds: only one element in the array ({})",
|
||||||
|
index, pos
|
||||||
|
),
|
||||||
Self::ErrorArrayBounds(max, index, pos) => write!(
|
Self::ErrorArrayBounds(max, index, pos) => write!(
|
||||||
f,
|
f,
|
||||||
"Array index {} is out of bounds: only {} element{} in the array ({})",
|
"Array index {} is out of bounds: only {} elements in the array ({})",
|
||||||
index,
|
index, max, pos
|
||||||
max,
|
|
||||||
if *max > 1 { "s" } else { "" },
|
|
||||||
pos
|
|
||||||
),
|
),
|
||||||
Self::ErrorStringBounds(_, index, pos) if *index < 0 => {
|
Self::ErrorStringBounds(_, index, pos) if *index < 0 => {
|
||||||
write!(f, "{}: {} < 0 ({})", desc, index, pos)
|
write!(f, "{}: {} < 0 ({})", desc, index, pos)
|
||||||
}
|
}
|
||||||
Self::ErrorStringBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos),
|
Self::ErrorStringBounds(0, _, pos) => write!(f, "{} ({})", desc, pos),
|
||||||
|
Self::ErrorStringBounds(1, index, pos) => write!(
|
||||||
|
f,
|
||||||
|
"String index {} is out of bounds: only one character in the string ({})",
|
||||||
|
index, pos
|
||||||
|
),
|
||||||
Self::ErrorStringBounds(max, index, pos) => write!(
|
Self::ErrorStringBounds(max, index, pos) => write!(
|
||||||
f,
|
f,
|
||||||
"String index {} is out of bounds: only {} character{} in the string ({})",
|
"String index {} is out of bounds: only {} characters in the string ({})",
|
||||||
index,
|
index, max, pos
|
||||||
max,
|
|
||||||
if *max > 1 { "s" } else { "" },
|
|
||||||
pos
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,6 +188,12 @@ impl From<ParseError> for EvalAltResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<str>> From<T> for EvalAltResult {
|
||||||
|
fn from(err: T) -> Self {
|
||||||
|
Self::ErrorRuntime(err.as_ref().to_string(), Position::none())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl EvalAltResult {
|
impl EvalAltResult {
|
||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
@ -225,9 +246,3 @@ impl EvalAltResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsRef<str>> From<T> for EvalAltResult {
|
|
||||||
fn from(err: T) -> Self {
|
|
||||||
Self::ErrorRuntime(err.as_ref().to_string(), Position::none())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! 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 std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// A type containing information about current scope.
|
/// A type containing information about current scope.
|
||||||
@ -15,9 +16,9 @@ use std::borrow::Cow;
|
|||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
/// let mut my_scope = Scope::new();
|
/// let mut my_scope = Scope::new();
|
||||||
///
|
///
|
||||||
/// engine.eval_with_scope::<()>(&mut my_scope, false, "let x = 5;")?;
|
/// engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;")?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, false, "x + 1")?, 6);
|
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, "x + 1")?, 6);
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
@ -93,6 +94,7 @@ impl<'a> Scope<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 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"))]
|
||||||
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, key: &str, index: usize) -> &mut T {
|
||||||
self.get_mut(key, index)
|
self.get_mut(key, index)
|
||||||
.downcast_mut::<T>()
|
.downcast_mut::<T>()
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
use rhai::{Engine, EvalAltResult, RegisterFn};
|
#![cfg(not(feature = "no_index"))]
|
||||||
|
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_arrays() -> Result<(), EvalAltResult> {
|
fn test_arrays() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("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::<i64>("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);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -14,7 +15,7 @@ fn test_arrays() -> Result<(), EvalAltResult> {
|
|||||||
fn test_array_with_structs() -> Result<(), EvalAltResult> {
|
fn test_array_with_structs() -> Result<(), EvalAltResult> {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
x: i64,
|
x: INT,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestStruct {
|
impl TestStruct {
|
||||||
@ -22,11 +23,11 @@ fn test_array_with_structs() -> Result<(), EvalAltResult> {
|
|||||||
self.x += 1000;
|
self.x += 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_x(&mut self) -> i64 {
|
fn get_x(&mut self) -> INT {
|
||||||
self.x
|
self.x
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_x(&mut self, new_x: i64) {
|
fn set_x(&mut self, new_x: INT) {
|
||||||
self.x = new_x;
|
self.x = new_x;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,10 +44,10 @@ fn test_array_with_structs() -> Result<(), EvalAltResult> {
|
|||||||
engine.register_fn("update", TestStruct::update);
|
engine.register_fn("update", TestStruct::update);
|
||||||
engine.register_fn("new_ts", TestStruct::new);
|
engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("let a = [new_ts()]; a[0].x")?, 1);
|
assert_eq!(engine.eval::<INT>("let a = [new_ts()]; a[0].x")?, 1);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<i64>(
|
engine.eval::<INT>(
|
||||||
"let a = [new_ts()]; \
|
"let a = [new_ts()]; \
|
||||||
a[0].x = 100; \
|
a[0].x = 100; \
|
||||||
a[0].update(); \
|
a[0].update(); \
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_binary_ops() -> Result<(), EvalAltResult> {
|
fn test_binary_ops() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("10 % 4")?, 2);
|
assert_eq!(engine.eval::<INT>("10 % 4")?, 2);
|
||||||
assert_eq!(engine.eval::<i64>("10 << 4")?, 160);
|
assert_eq!(engine.eval::<INT>("10 << 4")?, 160);
|
||||||
assert_eq!(engine.eval::<i64>("10 >> 4")?, 0);
|
assert_eq!(engine.eval::<INT>("10 >> 4")?, 0);
|
||||||
assert_eq!(engine.eval::<i64>("10 & 4")?, 0);
|
assert_eq!(engine.eval::<INT>("10 & 4")?, 0);
|
||||||
assert_eq!(engine.eval::<i64>("10 | 4")?, 14);
|
assert_eq!(engine.eval::<INT>("10 | 4")?, 14);
|
||||||
assert_eq!(engine.eval::<i64>("10 ^ 4")?, 14);
|
assert_eq!(engine.eval::<INT>("10 ^ 4")?, 14);
|
||||||
|
|
||||||
assert_eq!(engine.eval::<bool>("42 == 42")?, true);
|
assert_eq!(engine.eval::<bool>("42 == 42")?, true);
|
||||||
assert_eq!(engine.eval::<bool>("42 > 42")?, false);
|
assert_eq!(engine.eval::<bool>("42 > 42")?, false);
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_left_shift() -> Result<(), EvalAltResult> {
|
fn test_left_shift() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
assert_eq!(engine.eval::<i64>("4 << 2")?, 16);
|
assert_eq!(engine.eval::<INT>("4 << 2")?, 16);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_right_shift() -> Result<(), EvalAltResult> {
|
fn test_right_shift() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
assert_eq!(engine.eval::<i64>("9 >> 1")?, 4);
|
assert_eq!(engine.eval::<INT>("9 >> 1")?, 4);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -39,10 +39,9 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<bool>(
|
engine.eval::<bool>(
|
||||||
r"
|
r"
|
||||||
fn this() { true }
|
let this = true;
|
||||||
fn that() { 9/0 }
|
|
||||||
|
|
||||||
this() || that();
|
this || { throw; };
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
true
|
true
|
||||||
@ -51,10 +50,9 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<bool>(
|
engine.eval::<bool>(
|
||||||
r"
|
r"
|
||||||
fn this() { false }
|
let this = false;
|
||||||
fn that() { 9/0 }
|
|
||||||
|
|
||||||
this() && that();
|
this && { throw; };
|
||||||
"
|
"
|
||||||
)?,
|
)?,
|
||||||
false
|
false
|
||||||
@ -64,41 +62,31 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
|
||||||
fn test_bool_op_no_short_circuit1() {
|
fn test_bool_op_no_short_circuit1() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(
|
assert!(engine
|
||||||
engine
|
.eval::<bool>(
|
||||||
.eval::<bool>(
|
r"
|
||||||
r"
|
let this = true;
|
||||||
fn this() { false }
|
|
||||||
fn that() { 9/0 }
|
|
||||||
|
|
||||||
this() | that();
|
this | { throw; }
|
||||||
"
|
"
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.is_err());
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
|
||||||
fn test_bool_op_no_short_circuit2() {
|
fn test_bool_op_no_short_circuit2() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(
|
assert!(engine
|
||||||
engine
|
.eval::<bool>(
|
||||||
.eval::<bool>(
|
r"
|
||||||
r"
|
let this = false;
|
||||||
fn this() { false }
|
|
||||||
fn that() { 9/0 }
|
|
||||||
|
|
||||||
this() & that();
|
this & { throw; }
|
||||||
"
|
"
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.is_err());
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,15 @@ fn test_chars() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
assert_eq!(engine.eval::<char>("'y'")?, 'y');
|
assert_eq!(engine.eval::<char>("'y'")?, 'y');
|
||||||
assert_eq!(engine.eval::<char>("'\\u2764'")?, '❤');
|
assert_eq!(engine.eval::<char>("'\\u2764'")?, '❤');
|
||||||
assert_eq!(engine.eval::<char>(r#"let x="hello"; x[2]"#)?, 'l');
|
|
||||||
assert_eq!(
|
#[cfg(not(feature = "no_index"))]
|
||||||
engine.eval::<String>(r#"let x="hello"; x[2]='$'; x"#)?,
|
{
|
||||||
"he$lo".to_string()
|
assert_eq!(engine.eval::<char>(r#"let x="hello"; x[2]"#)?, 'l');
|
||||||
);
|
assert_eq!(
|
||||||
|
engine.eval::<String>(r#"let x="hello"; x[2]='$'; x"#)?,
|
||||||
|
"he$lo".to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
assert!(engine.eval::<char>("'\\uhello'").is_err());
|
assert!(engine.eval::<char>("'\\uhello'").is_err());
|
||||||
assert!(engine.eval::<char>("''").is_err());
|
assert!(engine.eval::<char>("''").is_err());
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
use rhai::Engine;
|
use rhai::{Engine, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_comments() {
|
fn test_comments() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert!(engine
|
assert!(engine
|
||||||
.eval::<i64>("let x = 5; x // I am a single line comment, yay!")
|
.eval::<INT>("let x = 5; x // I am a single line comment, yay!")
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
|
||||||
assert!(engine
|
assert!(engine
|
||||||
.eval::<i64>("let /* I am a multiline comment, yay! */ x = 5; x")
|
.eval::<INT>("let /* I am a multiline comment, yay! */ x = 5; x")
|
||||||
.is_ok());
|
.is_ok());
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_or_equals() -> Result<(), EvalAltResult> {
|
fn test_or_equals() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("let x = 16; x |= 74; x")?, 90);
|
assert_eq!(engine.eval::<INT>("let x = 16; x |= 74; x")?, 90);
|
||||||
assert_eq!(engine.eval::<bool>("let x = true; x |= false; x")?, true);
|
assert_eq!(engine.eval::<bool>("let x = true; x |= false; x")?, true);
|
||||||
assert_eq!(engine.eval::<bool>("let x = false; x |= true; x")?, true);
|
assert_eq!(engine.eval::<bool>("let x = false; x |= true; x")?, true);
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ fn test_or_equals() -> Result<(), EvalAltResult> {
|
|||||||
fn test_and_equals() -> Result<(), EvalAltResult> {
|
fn test_and_equals() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("let x = 16; x &= 31; x")?, 16);
|
assert_eq!(engine.eval::<INT>("let x = 16; x &= 31; x")?, 16);
|
||||||
assert_eq!(engine.eval::<bool>("let x = true; x &= false; x")?, false);
|
assert_eq!(engine.eval::<bool>("let x = true; x &= false; x")?, false);
|
||||||
assert_eq!(engine.eval::<bool>("let x = false; x &= true; x")?, false);
|
assert_eq!(engine.eval::<bool>("let x = false; x &= true; x")?, false);
|
||||||
assert_eq!(engine.eval::<bool>("let x = true; x &= true; x")?, true);
|
assert_eq!(engine.eval::<bool>("let x = true; x &= true; x")?, true);
|
||||||
@ -26,41 +26,41 @@ fn test_and_equals() -> Result<(), EvalAltResult> {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_xor_equals() -> Result<(), EvalAltResult> {
|
fn test_xor_equals() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
assert_eq!(engine.eval::<i64>("let x = 90; x ^= 12; x")?, 86);
|
assert_eq!(engine.eval::<INT>("let x = 90; x ^= 12; x")?, 86);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiply_equals() -> Result<(), EvalAltResult> {
|
fn test_multiply_equals() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
assert_eq!(engine.eval::<i64>("let x = 2; x *= 3; x")?, 6);
|
assert_eq!(engine.eval::<INT>("let x = 2; x *= 3; x")?, 6);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_divide_equals() -> Result<(), EvalAltResult> {
|
fn test_divide_equals() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
assert_eq!(engine.eval::<i64>("let x = 6; x /= 2; x")?, 3);
|
assert_eq!(engine.eval::<INT>("let x = 6; x /= 2; x")?, 3);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_left_shift_equals() -> Result<(), EvalAltResult> {
|
fn test_left_shift_equals() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
assert_eq!(engine.eval::<i64>("let x = 9; x >>=1; x")?, 4);
|
assert_eq!(engine.eval::<INT>("let x = 9; x >>=1; x")?, 4);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_right_shift_equals() -> Result<(), EvalAltResult> {
|
fn test_right_shift_equals() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
assert_eq!(engine.eval::<i64>("let x = 4; x<<= 2; x")?, 16);
|
assert_eq!(engine.eval::<INT>("let x = 4; x<<= 2; x")?, 16);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_modulo_equals() -> Result<(), EvalAltResult> {
|
fn test_modulo_equals() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
assert_eq!(engine.eval::<i64>("let x = 10; x %= 4; x")?, 2);
|
assert_eq!(engine.eval::<INT>("let x = 10; x %= 4; x")?, 2);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_decrement() -> Result<(), EvalAltResult> {
|
fn test_decrement() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("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");
|
let r = engine.eval::<String>("let s = \"test\"; s -= \"ing\"; s");
|
||||||
|
|
||||||
|
@ -1,14 +1,28 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
#![cfg(not(feature = "no_stdlib"))]
|
||||||
|
#![cfg(not(feature = "no_function"))]
|
||||||
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_engine_call_fn() -> Result<(), EvalAltResult> {
|
fn test_engine_call_fn() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
let ast = engine.compile("fn hello(x, y) { x.len() + y }")?;
|
engine.consume(
|
||||||
|
r"
|
||||||
|
fn hello(x, y) {
|
||||||
|
x.len() + y
|
||||||
|
}
|
||||||
|
fn hello(x) {
|
||||||
|
x * 2
|
||||||
|
}
|
||||||
|
",
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
|
||||||
let result: i64 = engine.call_fn("hello", &ast, (&mut String::from("abc"), &mut 123_i64))?;
|
let r: i64 = engine.call_fn("hello", (String::from("abc"), 123 as INT))?;
|
||||||
|
assert_eq!(r, 126);
|
||||||
|
|
||||||
assert_eq!(result, 126);
|
let r: i64 = engine.call_fn("hello", 123 as INT)?;
|
||||||
|
assert_eq!(r, 246);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#![cfg(not(feature = "no_float"))]
|
||||||
use rhai::{Engine, EvalAltResult, RegisterFn};
|
use rhai::{Engine, EvalAltResult, RegisterFn};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
#![cfg(not(feature = "no_index"))]
|
||||||
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_for() -> Result<(), EvalAltResult> {
|
fn test_for() -> Result<(), EvalAltResult> {
|
||||||
@ -20,7 +21,7 @@ fn test_for() -> Result<(), EvalAltResult> {
|
|||||||
sum1 + sum2
|
sum1 + sum2
|
||||||
";
|
";
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>(script)?, 30);
|
assert_eq!(engine.eval::<INT>(script)?, 30);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
use rhai::{Engine, EvalAltResult, RegisterFn};
|
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_set() -> Result<(), EvalAltResult> {
|
fn test_get_set() -> Result<(), EvalAltResult> {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
x: i64,
|
x: INT,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestStruct {
|
impl TestStruct {
|
||||||
fn get_x(&mut self) -> i64 {
|
fn get_x(&mut self) -> INT {
|
||||||
self.x
|
self.x
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_x(&mut self, new_x: i64) {
|
fn set_x(&mut self, new_x: INT) {
|
||||||
self.x = new_x;
|
self.x = new_x;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ fn test_get_set() -> Result<(), EvalAltResult> {
|
|||||||
engine.register_get_set("x", TestStruct::get_x, TestStruct::set_x);
|
engine.register_get_set("x", TestStruct::get_x, TestStruct::set_x);
|
||||||
engine.register_fn("new_ts", TestStruct::new);
|
engine.register_fn("new_ts", TestStruct::new);
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("let a = new_ts(); a.x = 500; a.x")?, 500);
|
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x = 500; a.x")?, 500);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -37,15 +37,15 @@ fn test_get_set() -> Result<(), EvalAltResult> {
|
|||||||
fn test_big_get_set() -> Result<(), EvalAltResult> {
|
fn test_big_get_set() -> Result<(), EvalAltResult> {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TestChild {
|
struct TestChild {
|
||||||
x: i64,
|
x: INT,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestChild {
|
impl TestChild {
|
||||||
fn get_x(&mut self) -> i64 {
|
fn get_x(&mut self) -> INT {
|
||||||
self.x
|
self.x
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_x(&mut self, new_x: i64) {
|
fn set_x(&mut self, new_x: INT) {
|
||||||
self.x = new_x;
|
self.x = new_x;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ fn test_big_get_set() -> Result<(), EvalAltResult> {
|
|||||||
engine.register_fn("new_tp", TestParent::new);
|
engine.register_fn("new_tp", TestParent::new);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<i64>("let a = new_tp(); a.child.x = 500; a.child.x")?,
|
engine.eval::<INT>("let a = new_tp(); a.child.x = 500; a.child.x")?,
|
||||||
500
|
500
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_if() -> Result<(), EvalAltResult> {
|
fn test_if() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("if true { 55 }")?, 55);
|
assert_eq!(engine.eval::<INT>("if true { 55 }")?, 55);
|
||||||
assert_eq!(engine.eval::<i64>("if false { 55 } else { 44 }")?, 44);
|
assert_eq!(engine.eval::<INT>("if false { 55 } else { 44 }")?, 44);
|
||||||
assert_eq!(engine.eval::<i64>("if true { 55 } else { 44 }")?, 55);
|
assert_eq!(engine.eval::<INT>("if true { 55 } else { 44 }")?, 55);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<i64>("if false { 55 } else if true { 33 } else { 44 }")?,
|
engine.eval::<INT>("if false { 55 } else if true { 33 } else { 44 }")?,
|
||||||
33
|
33
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<i64>(
|
engine.eval::<INT>(
|
||||||
r"
|
r"
|
||||||
if false { 55 }
|
if false { 55 }
|
||||||
else if false { 33 }
|
else if false { 33 }
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_increment() -> Result<(), EvalAltResult> {
|
fn test_increment() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("let x = 1; x += 2; x")?, 3);
|
assert_eq!(engine.eval::<INT>("let x = 1; x += 2; x")?, 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<String>("let s = \"test\"; s += \"ing\"; s")?,
|
engine.eval::<String>("let s = \"test\"; s += \"ing\"; s")?,
|
||||||
"testing".to_string()
|
"testing".to_string()
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
#![cfg(not(feature = "no_function"))]
|
||||||
|
|
||||||
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_internal_fn() -> Result<(), EvalAltResult> {
|
fn test_internal_fn() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("fn addme(a, b) { a+b } addme(3, 4)")?, 7);
|
assert_eq!(engine.eval::<INT>("fn addme(a, b) { a+b } addme(3, 4)")?, 7);
|
||||||
assert_eq!(engine.eval::<i64>("fn bob() { return 4; 5 } bob()")?, 4);
|
assert_eq!(engine.eval::<INT>("fn bob() { return 4; 5 } bob()")?, 4);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -15,7 +17,7 @@ fn test_big_internal_fn() -> Result<(), EvalAltResult> {
|
|||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<i64>(
|
engine.eval::<INT>(
|
||||||
r"
|
r"
|
||||||
fn mathme(a, b, c, d, e, f) {
|
fn mathme(a, b, c, d, e, f) {
|
||||||
a - b * c + d * e - f
|
a - b * c + d * e - f
|
||||||
@ -28,3 +30,25 @@ fn test_big_internal_fn() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_internal_fn_overloading() -> Result<(), EvalAltResult> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>(
|
||||||
|
r#"
|
||||||
|
fn abc(x,y,z) { 2*x + 3*y + 4*z + 888 }
|
||||||
|
fn abc(x) { x + 42 }
|
||||||
|
fn abc(x,y) { x + 2*y + 88 }
|
||||||
|
fn abc() { 42 }
|
||||||
|
fn abc(x) { x - 42 } // should override previous definition
|
||||||
|
|
||||||
|
abc() + abc(1) + abc(1,2) + abc(1,2,3)
|
||||||
|
"#
|
||||||
|
)?,
|
||||||
|
1002
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -1,41 +1,77 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_math() -> Result<(), EvalAltResult> {
|
fn test_math() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("1 + 2")?, 3);
|
assert_eq!(engine.eval::<INT>("1 + 2")?, 3);
|
||||||
assert_eq!(engine.eval::<i64>("1 - 2")?, -1);
|
assert_eq!(engine.eval::<INT>("1 - 2")?, -1);
|
||||||
assert_eq!(engine.eval::<i64>("2 * 3")?, 6);
|
assert_eq!(engine.eval::<INT>("2 * 3")?, 6);
|
||||||
assert_eq!(engine.eval::<i64>("1 / 2")?, 0);
|
assert_eq!(engine.eval::<INT>("1 / 2")?, 0);
|
||||||
assert_eq!(engine.eval::<i64>("3 % 2")?, 1);
|
assert_eq!(engine.eval::<INT>("3 % 2")?, 1);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "only_i32"))]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<i64>("(-9223372036854775807).abs()")?,
|
engine.eval::<INT>("(-9223372036854775807).abs()")?,
|
||||||
9223372036854775807
|
9223372036854775807
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "only_i32")]
|
||||||
|
assert_eq!(engine.eval::<INT>("(-2147483647).abs()")?, 2147483647);
|
||||||
|
|
||||||
// Overflow/underflow/division-by-zero errors
|
// Overflow/underflow/division-by-zero errors
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
{
|
{
|
||||||
match engine.eval::<i64>("9223372036854775807 + 1") {
|
#[cfg(not(feature = "only_i32"))]
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
{
|
||||||
r => panic!("should return overflow error: {:?}", r),
|
match engine.eval::<INT>("(-9223372036854775808).abs()") {
|
||||||
|
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||||
|
r => panic!("should return overflow error: {:?}", r),
|
||||||
|
}
|
||||||
|
match engine.eval::<INT>("9223372036854775807 + 1") {
|
||||||
|
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||||
|
r => panic!("should return overflow error: {:?}", r),
|
||||||
|
}
|
||||||
|
match engine.eval::<INT>("-9223372036854775808 - 1") {
|
||||||
|
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||||
|
r => panic!("should return underflow error: {:?}", r),
|
||||||
|
}
|
||||||
|
match engine.eval::<INT>("9223372036854775807 * 9223372036854775807") {
|
||||||
|
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||||
|
r => panic!("should return overflow error: {:?}", r),
|
||||||
|
}
|
||||||
|
match engine.eval::<INT>("9223372036854775807 / 0") {
|
||||||
|
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||||
|
r => panic!("should return division by zero error: {:?}", r),
|
||||||
|
}
|
||||||
|
match engine.eval::<INT>("9223372036854775807 % 0") {
|
||||||
|
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||||
|
r => panic!("should return division by zero error: {:?}", r),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
match engine.eval::<i64>("-9223372036854775808 - 1") {
|
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
#[cfg(feature = "only_i32")]
|
||||||
r => panic!("should return underflow error: {:?}", r),
|
{
|
||||||
}
|
match engine.eval::<INT>("2147483647 + 1") {
|
||||||
match engine.eval::<i64>("9223372036854775807 * 9223372036854775807") {
|
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
r => panic!("should return overflow error: {:?}", r),
|
||||||
r => panic!("should return overflow error: {:?}", r),
|
}
|
||||||
}
|
match engine.eval::<INT>("-2147483648 - 1") {
|
||||||
match engine.eval::<i64>("9223372036854775807 / 0") {
|
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
r => panic!("should return underflow error: {:?}", r),
|
||||||
r => panic!("should return division by zero error: {:?}", r),
|
}
|
||||||
}
|
match engine.eval::<INT>("2147483647 * 2147483647") {
|
||||||
match engine.eval::<i64>("9223372036854775807 % 0") {
|
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
r => panic!("should return overflow error: {:?}", r),
|
||||||
r => panic!("should return division by zero error: {:?}", r),
|
}
|
||||||
|
match engine.eval::<INT>("2147483647 / 0") {
|
||||||
|
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||||
|
r => panic!("should return division by zero error: {:?}", r),
|
||||||
|
}
|
||||||
|
match engine.eval::<INT>("2147483647 % 0") {
|
||||||
|
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||||
|
r => panic!("should return division by zero error: {:?}", r),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use rhai::{Engine, EvalAltResult, RegisterFn};
|
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_method_call() -> Result<(), EvalAltResult> {
|
fn test_method_call() -> Result<(), EvalAltResult> {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
x: i64,
|
x: INT,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestStruct {
|
impl TestStruct {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use rhai::{Engine, EvalAltResult, RegisterFn};
|
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(not(feature = "no_stdlib"))]
|
||||||
fn test_mismatched_op() {
|
fn test_mismatched_op() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
let r = engine.eval::<i64>("60 + \"hello\"");
|
let r = engine.eval::<INT>("60 + \"hello\"");
|
||||||
|
|
||||||
match r {
|
match r {
|
||||||
Err(EvalAltResult::ErrorMismatchOutputType(err, _)) if err == "string" => (),
|
Err(EvalAltResult::ErrorMismatchOutputType(err, _)) if err == "string" => (),
|
||||||
@ -16,7 +17,7 @@ fn test_mismatched_op() {
|
|||||||
fn test_mismatched_op_custom_type() {
|
fn test_mismatched_op_custom_type() {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TestStruct {
|
struct TestStruct {
|
||||||
x: i64,
|
x: INT,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestStruct {
|
impl TestStruct {
|
||||||
@ -26,17 +27,18 @@ fn test_mismatched_op_custom_type() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
engine.register_type::<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::<i64>("60 + new_ts()");
|
let r = engine.eval::<INT>("60 + new_ts()");
|
||||||
|
|
||||||
match r {
|
match r {
|
||||||
Err(EvalAltResult::ErrorFunctionNotFound(err, _))
|
#[cfg(feature = "only_i32")]
|
||||||
if err == "+ (i64, mismatched_op::test_mismatched_op_custom_type::TestStruct)" =>
|
Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "+ (i32, TestStruct)" => (),
|
||||||
{
|
|
||||||
()
|
#[cfg(not(feature = "only_i32"))]
|
||||||
}
|
Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "+ (i64, TestStruct)" => (),
|
||||||
|
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ fn test_not() -> Result<(), EvalAltResult> {
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
assert_eq!(engine.eval::<bool>("fn not(x) { !x } not(false)")?, true);
|
assert_eq!(engine.eval::<bool>("fn not(x) { !x } not(false)")?, true);
|
||||||
|
|
||||||
// TODO - do we allow stacking unary operators directly? e.g '!!!!!!!true'
|
// TODO - do we allow stacking unary operators directly? e.g '!!!!!!!true'
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_number_literal() -> Result<(), EvalAltResult> {
|
fn test_number_literal() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("65")?, 65);
|
assert_eq!(engine.eval::<INT>("65")?, 65);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -13,8 +13,8 @@ fn test_number_literal() -> Result<(), EvalAltResult> {
|
|||||||
fn test_hex_literal() -> Result<(), EvalAltResult> {
|
fn test_hex_literal() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("let x = 0xf; x")?, 15);
|
assert_eq!(engine.eval::<INT>("let x = 0xf; x")?, 15);
|
||||||
assert_eq!(engine.eval::<i64>("let x = 0xff; x")?, 255);
|
assert_eq!(engine.eval::<INT>("let x = 0xff; x")?, 255);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -23,8 +23,8 @@ fn test_hex_literal() -> Result<(), EvalAltResult> {
|
|||||||
fn test_octal_literal() -> Result<(), EvalAltResult> {
|
fn test_octal_literal() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("let x = 0o77; x")?, 63);
|
assert_eq!(engine.eval::<INT>("let x = 0o77; x")?, 63);
|
||||||
assert_eq!(engine.eval::<i64>("let x = 0o1234; x")?, 668);
|
assert_eq!(engine.eval::<INT>("let x = 0o1234; x")?, 668);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -33,9 +33,9 @@ fn test_octal_literal() -> Result<(), EvalAltResult> {
|
|||||||
fn test_binary_literal() -> Result<(), EvalAltResult> {
|
fn test_binary_literal() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("let x = 0b1111; x")?, 15);
|
assert_eq!(engine.eval::<INT>("let x = 0b1111; x")?, 15);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<i64>("let x = 0b0011_1100_1010_0101; x")?,
|
engine.eval::<INT>("let x = 0b0011_1100_1010_0101; x")?,
|
||||||
15525
|
15525
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ops() -> Result<(), EvalAltResult> {
|
fn test_ops() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("60 + 5")?, 65);
|
assert_eq!(engine.eval::<INT>("60 + 5")?, 65);
|
||||||
assert_eq!(engine.eval::<i64>("(1 + 2) * (6 - 4) / 2")?, 3);
|
assert_eq!(engine.eval::<INT>("(1 + 2) * (6 - 4) / 2")?, 3);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -15,7 +15,7 @@ fn test_op_prec() -> Result<(), EvalAltResult> {
|
|||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<i64>("let x = 0; if x == 10 || true { x = 1} x")?,
|
engine.eval::<INT>("let x = 0; if x == 10 || true { x = 1} x")?,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, FLOAT, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_power_of() -> Result<(), EvalAltResult> {
|
fn test_power_of() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("2 ~ 3")?, 8);
|
assert_eq!(engine.eval::<INT>("2 ~ 3")?, 8);
|
||||||
assert_eq!(engine.eval::<i64>("(-2 ~ 3)")?, -8);
|
assert_eq!(engine.eval::<INT>("(-2 ~ 3)")?, -8);
|
||||||
assert_eq!(engine.eval::<f64>("2.2 ~ 3.3")?, 13.489468760533386_f64);
|
|
||||||
assert_eq!(engine.eval::<f64>("2.0~-2.0")?, 0.25_f64);
|
#[cfg(not(feature = "no_float"))]
|
||||||
assert_eq!(engine.eval::<f64>("(-2.0~-2.0)")?, 0.25_f64);
|
{
|
||||||
assert_eq!(engine.eval::<f64>("(-2.0~-2)")?, 0.25_f64);
|
assert_eq!(
|
||||||
assert_eq!(engine.eval::<i64>("4~3")?, 64);
|
engine.eval::<FLOAT>("2.2 ~ 3.3")?,
|
||||||
|
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.25 as FLOAT);
|
||||||
|
assert_eq!(engine.eval::<INT>("4~3")?, 64);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -19,16 +26,29 @@ fn test_power_of() -> Result<(), EvalAltResult> {
|
|||||||
fn test_power_of_equals() -> Result<(), EvalAltResult> {
|
fn test_power_of_equals() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("let x = 2; x ~= 3; x")?, 8);
|
assert_eq!(engine.eval::<INT>("let x = 2; x ~= 3; x")?, 8);
|
||||||
assert_eq!(engine.eval::<i64>("let x = -2; x ~= 3; x")?, -8);
|
assert_eq!(engine.eval::<INT>("let x = -2; x ~= 3; x")?, -8);
|
||||||
assert_eq!(
|
|
||||||
engine.eval::<f64>("let x = 2.2; x ~= 3.3; x")?,
|
#[cfg(not(feature = "no_float"))]
|
||||||
13.489468760533386_f64
|
{
|
||||||
);
|
assert_eq!(
|
||||||
assert_eq!(engine.eval::<f64>("let x = 2.0; x ~= -2.0; x")?, 0.25_f64);
|
engine.eval::<FLOAT>("let x = 2.2; x ~= 3.3; x")?,
|
||||||
assert_eq!(engine.eval::<f64>("let x = -2.0; x ~= -2.0; x")?, 0.25_f64);
|
13.489468760533386 as FLOAT
|
||||||
assert_eq!(engine.eval::<f64>("let x = -2.0; x ~= -2; x")?, 0.25_f64);
|
);
|
||||||
assert_eq!(engine.eval::<i64>("let x =4; x ~= 3; x")?, 64);
|
assert_eq!(
|
||||||
|
engine.eval::<FLOAT>("let x = 2.0; x ~= -2.0; x")?,
|
||||||
|
0.25 as FLOAT
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<FLOAT>("let x = -2.0; x ~= -2.0; x")?,
|
||||||
|
0.25 as FLOAT
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<FLOAT>("let x = -2.0; x ~= -2; x")?,
|
||||||
|
0.25 as FLOAT
|
||||||
|
);
|
||||||
|
assert_eq!(engine.eval::<INT>("let x =4; x ~= 3; x")?, 64);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,20 @@ fn test_string() -> Result<(), EvalAltResult> {
|
|||||||
engine.eval::<String>(r#""Test string: \x58""#)?,
|
engine.eval::<String>(r#""Test string: \x58""#)?,
|
||||||
"Test string: X".to_string()
|
"Test string: X".to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<String>(r#""foo" + "bar""#)?,
|
engine.eval::<String>(r#""foo" + "bar""#)?,
|
||||||
"foobar".to_string()
|
"foobar".to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_stdlib"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<String>(r#""foo" + 123"#)?,
|
||||||
|
"foo123".to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
#[cfg(not(feature = "no_stdlib"))]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<String>(r#""foo" + 123.4556"#)?,
|
engine.eval::<String>(r#""foo" + 123.4556"#)?,
|
||||||
"foo123.4556".to_string()
|
"foo123.4556".to_string()
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_throw() {
|
fn test_throw() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
match engine.eval::<i64>(r#"if true { throw "hello" }"#) {
|
match engine.eval::<INT>(r#"if true { throw "hello" }"#) {
|
||||||
Ok(_) => panic!("not an error"),
|
Ok(_) => panic!("not an error"),
|
||||||
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "hello" => (),
|
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "hello" => (),
|
||||||
Err(err) => panic!("wrong error: {}", err),
|
Err(err) => panic!("wrong error: {}", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
match engine.eval::<i64>(r#"throw;"#) {
|
match engine.eval::<INT>(r#"throw;"#) {
|
||||||
Ok(_) => panic!("not an error"),
|
Ok(_) => panic!("not an error"),
|
||||||
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "" => (),
|
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "" => (),
|
||||||
Err(err) => panic!("wrong error: {}", err),
|
Err(err) => panic!("wrong error: {}", err),
|
||||||
|
@ -4,14 +4,29 @@ use rhai::{Engine, EvalAltResult};
|
|||||||
fn test_type_of() -> Result<(), EvalAltResult> {
|
fn test_type_of() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "only_i32"))]
|
||||||
assert_eq!(engine.eval::<String>("type_of(60 + 5)")?, "i64");
|
assert_eq!(engine.eval::<String>("type_of(60 + 5)")?, "i64");
|
||||||
|
|
||||||
|
#[cfg(feature = "only_i32")]
|
||||||
|
assert_eq!(engine.eval::<String>("type_of(60 + 5)")?, "i32");
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
assert_eq!(engine.eval::<String>("type_of(1.0 + 2.0)")?, "f64");
|
assert_eq!(engine.eval::<String>("type_of(1.0 + 2.0)")?, "f64");
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<String>(r#"type_of([1.0, 2, "hello"])"#)?,
|
engine.eval::<String>(r#"type_of([1.0, 2, "hello"])"#)?,
|
||||||
"array"
|
"array"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(engine.eval::<String>(r#"type_of("hello")"#)?, "string");
|
assert_eq!(engine.eval::<String>(r#"type_of("hello")"#)?, "string");
|
||||||
|
|
||||||
|
#[cfg(not(feature = "only_i32"))]
|
||||||
assert_eq!(engine.eval::<String>("let x = 123; x.type_of()")?, "i64");
|
assert_eq!(engine.eval::<String>("let x = 123; x.type_of()")?, "i64");
|
||||||
|
|
||||||
|
#[cfg(feature = "only_i32")]
|
||||||
|
assert_eq!(engine.eval::<String>("let x = 123; x.type_of()")?, "i32");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
// TODO also add test case for unary after compound
|
// TODO also add test case for unary after compound
|
||||||
@ -6,12 +6,12 @@ use rhai::{Engine, EvalAltResult};
|
|||||||
fn test_unary_after_binary() -> Result<(), EvalAltResult> {
|
fn test_unary_after_binary() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("10 % +4")?, 2);
|
assert_eq!(engine.eval::<INT>("10 % +4")?, 2);
|
||||||
assert_eq!(engine.eval::<i64>("10 << +4")?, 160);
|
assert_eq!(engine.eval::<INT>("10 << +4")?, 160);
|
||||||
assert_eq!(engine.eval::<i64>("10 >> +4")?, 0);
|
assert_eq!(engine.eval::<INT>("10 >> +4")?, 0);
|
||||||
assert_eq!(engine.eval::<i64>("10 & +4")?, 0);
|
assert_eq!(engine.eval::<INT>("10 & +4")?, 0);
|
||||||
assert_eq!(engine.eval::<i64>("10 | +4")?, 14);
|
assert_eq!(engine.eval::<INT>("10 | +4")?, 14);
|
||||||
assert_eq!(engine.eval::<i64>("10 ^ +4")?, 14);
|
assert_eq!(engine.eval::<INT>("10 ^ +4")?, 14);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unary_minus() -> Result<(), EvalAltResult> {
|
fn test_unary_minus() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(engine.eval::<i64>("let x = -5; x")?, -5);
|
assert_eq!(engine.eval::<INT>("let x = -5; x")?, -5);
|
||||||
assert_eq!(engine.eval::<i64>("fn neg(x) { -x } neg(5)")?, -5);
|
|
||||||
assert_eq!(engine.eval::<i64>("5 - -+++--+-5")?, 0);
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
assert_eq!(engine.eval::<INT>("fn neg(x) { -x } neg(5)")?, -5);
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<INT>("5 - -+++--+-5")?, 0);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
use rhai::{Engine, EvalAltResult, Scope};
|
use rhai::{Engine, EvalAltResult, Scope, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_var_scope() -> Result<(), EvalAltResult> {
|
fn test_var_scope() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
engine.eval_with_scope::<()>(&mut scope, false, "let x = 4 + 5")?;
|
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;
|
||||||
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, false, "x")?, 9);
|
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 9);
|
||||||
engine.eval_with_scope::<()>(&mut scope, false, "x = x + 1; x = x + 2;")?;
|
engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?;
|
||||||
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, false, "x")?, 12);
|
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 12);
|
||||||
assert_eq!(
|
assert_eq!(engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?, ());
|
||||||
engine.eval_with_scope::<()>(&mut scope, false, "{let x = 3}")?,
|
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 12);
|
||||||
()
|
|
||||||
);
|
|
||||||
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, false, "x")?, 12);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -26,23 +23,31 @@ fn test_scope_eval() -> Result<(), EvalAltResult> {
|
|||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
// Then push some initialized variables into the state
|
// Then push some initialized variables into the state
|
||||||
// NOTE: Remember the default numbers used by Rhai are i64 and f64.
|
// NOTE: Remember the default numbers used by Rhai are INT and f64.
|
||||||
// Better stick to them or it gets hard to work with other variables in the script.
|
// Better stick to them or it gets hard to work with other variables in the script.
|
||||||
scope.push("y", 42_i64);
|
scope.push("y", 42 as INT);
|
||||||
scope.push("z", 999_i64);
|
scope.push("z", 999 as INT);
|
||||||
|
|
||||||
// First invocation
|
// First invocation
|
||||||
engine
|
engine
|
||||||
.eval_with_scope::<()>(&mut scope, false, " let x = 4 + 5 - y + z; y = 1;")
|
.eval_with_scope::<()>(&mut scope, " let x = 4 + 5 - y + z; y = 1;")
|
||||||
.expect("y and z not found?");
|
.expect("y and z not found?");
|
||||||
|
|
||||||
// Second invocation using the same state
|
// Second invocation using the same state
|
||||||
let result = engine.eval_with_scope::<i64>(&mut scope, false, "x")?;
|
let result = engine.eval_with_scope::<INT>(&mut scope, "x")?;
|
||||||
|
|
||||||
println!("result: {}", result); // should print 966
|
println!("result: {}", result); // should print 966
|
||||||
|
|
||||||
// Variable y is changed in the script
|
// Variable y is changed in the script
|
||||||
assert_eq!(scope.get_value::<i64>("y").unwrap(), 1);
|
assert_eq!(
|
||||||
|
scope
|
||||||
|
.get_value::<INT>("y")
|
||||||
|
.ok_or(EvalAltResult::ErrorRuntime(
|
||||||
|
"variable y not found".into(),
|
||||||
|
Default::default()
|
||||||
|
))?,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_while() -> Result<(), EvalAltResult> {
|
fn test_while() -> Result<(), EvalAltResult> {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<i64>(
|
engine.eval::<INT>(
|
||||||
"let x = 0; while x < 10 { x = x + 1; if x > 5 { \
|
"let x = 0; while x < 10 { x = x + 1; if x > 5 { \
|
||||||
break } } x",
|
break } } x",
|
||||||
)?,
|
)?,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user