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/
|
||||
Cargo.lock
|
||||
.vscode/
|
||||
.vscode/
|
||||
.cargo/
|
||||
|
18
Cargo.toml
18
Cargo.toml
@ -18,6 +18,18 @@ include = [
|
||||
num-traits = "*"
|
||||
|
||||
[features]
|
||||
debug_msgs = []
|
||||
no_stdlib = []
|
||||
unchecked = []
|
||||
#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked"]
|
||||
default = []
|
||||
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)
|
||||
* Easy-to-use language similar to JS+Rust
|
||||
* 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.
|
||||
|
||||
@ -38,19 +38,27 @@ Beware that in order to use pre-releases (alpha and beta) you need to specify th
|
||||
Optional features
|
||||
-----------------
|
||||
|
||||
| Feature | Description |
|
||||
| ------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `debug_msgs` | Print debug messages to stdout (using `println!`) related to function registrations and function calls. |
|
||||
| `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. |
|
||||
| `unchecked` | Exclude arithmetic checking in the standard library. Beware that a bad script may panic the entire system! |
|
||||
| Feature | Description |
|
||||
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `debug_msgs` | Print debug messages to stdout related to function registrations and calls. |
|
||||
| `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. |
|
||||
| `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
|
||||
-------
|
||||
|
||||
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.
|
||||
* 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)
|
||||
* [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] on [awesome-rust].
|
||||
|
||||
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:
|
||||
|
||||
| Script | Description |
|
||||
| --------------------- | ------------------------------------------------------------- |
|
||||
| `array.rhai` | arrays in Rhai |
|
||||
| `assignment.rhai` | variable declarations |
|
||||
| `comments.rhai` | just comments |
|
||||
| `for1.rhai` | for loops |
|
||||
| `function_decl1.rhai` | a function without parameters |
|
||||
| `function_decl2.rhai` | a function with two parameters |
|
||||
| `function_decl3.rhai` | a function with many parameters |
|
||||
| `if1.rhai` | if example |
|
||||
| `loop.rhai` | endless loop in Rhai, this example emulates a do..while cycle |
|
||||
| `op1.rhai` | just a simple addition |
|
||||
| `op2.rhai` | simple addition and multiplication |
|
||||
| `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 |
|
||||
| `while.rhai` | while loop |
|
||||
| Language feature scripts | Description |
|
||||
| ------------------------ | ------------------------------------------------------------- |
|
||||
| `array.rhai` | arrays in Rhai |
|
||||
| `assignment.rhai` | variable declarations |
|
||||
| `comments.rhai` | just comments |
|
||||
| `for1.rhai` | for loops |
|
||||
| `function_decl1.rhai` | a function without parameters |
|
||||
| `function_decl2.rhai` | a function with two parameters |
|
||||
| `function_decl3.rhai` | a function with many parameters |
|
||||
| `if1.rhai` | if example |
|
||||
| `loop.rhai` | endless loop in Rhai, this example emulates a do..while cycle |
|
||||
| `op1.rhai` | just a simple addition |
|
||||
| `op2.rhai` | simple addition and multiplication |
|
||||
| `op3.rhai` | change evaluation order with parenthesis |
|
||||
| `string.rhai` | string operations |
|
||||
| `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:
|
||||
|
||||
@ -120,6 +132,8 @@ fn main() -> Result<(), EvalAltResult>
|
||||
let result = engine.eval::<i64>("40 + 2")?;
|
||||
|
||||
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.
|
||||
You do this via `call_fn`, which takes a compiled AST (output from `compile`) and the
|
||||
function call arguments:
|
||||
You do this via `call_fn`:
|
||||
|
||||
```rust
|
||||
use rhai::Engine;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Define a function in a script and compile to AST
|
||||
let ast = engine.compile("fn hello(x, y) { x.len() + y }")?;
|
||||
// Define a function in a script and load it into the Engine.
|
||||
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
|
||||
// (beware, arguments must be of the correct types because Rhai does not have built-in type conversions)
|
||||
let result: i64 = engine.call_fn("hello", &ast, (&mut String::from("abc"), &mut 123_i64))?;
|
||||
// if there are more than one. Beware, arguments must be of the correct types because
|
||||
// Rhai does not have built-in type conversions. If you pass in arguments of the wrong type,
|
||||
// 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
|
||||
@ -178,20 +208,28 @@ Values and types
|
||||
|
||||
The following primitive types are supported natively:
|
||||
|
||||
| Category | Types |
|
||||
| ------------------------------ | -------------------------------------- |
|
||||
| Integer | `i32`, `u32`, `i64` _(default)_, `u64` |
|
||||
| Floating-point | `f32`, `f64` _(default)_ |
|
||||
| Character | `char` |
|
||||
| Boolean | `bool` |
|
||||
| Array | `rhai::Array` |
|
||||
| Dynamic (i.e. can be anything) | `rhai::Dynamic` |
|
||||
| Category | Types |
|
||||
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| **Integer** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ |
|
||||
| **Floating-point** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ |
|
||||
| **Character** | `char` |
|
||||
| **Boolean** | `bool` |
|
||||
| **Array** (disabled with [`no_index`]) | `rhai::Array` |
|
||||
| **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
|
||||
-----------------
|
||||
|
||||
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 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
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::RegisterFn; // include the `RegisterFn` trait to use `register_fn`
|
||||
use rhai::{Dynamic, RegisterDynamicFn}; // include the `RegisterDynamicFn` trait to use `register_dynamic_fn`
|
||||
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn`
|
||||
use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn`
|
||||
|
||||
// Normal function
|
||||
fn add(x: i64, y: i64) -> i64 {
|
||||
@ -278,21 +316,21 @@ use std::fmt::Display;
|
||||
|
||||
use rhai::{Engine, RegisterFn};
|
||||
|
||||
fn showit<T: Display>(x: &mut T) -> () {
|
||||
println!("{}", x)
|
||||
fn show_it<T: Display>(x: &mut T) -> () {
|
||||
println!("put up a good show: {}!", x)
|
||||
}
|
||||
|
||||
fn main()
|
||||
{
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_fn("print", showit as fn(x: &mut i64)->());
|
||||
engine.register_fn("print", showit 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 i64)->());
|
||||
engine.register_fn("print", show_it as fn(x: &mut bool)->());
|
||||
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
|
||||
------------------
|
||||
@ -303,13 +341,13 @@ Your function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements
|
||||
|
||||
```rust
|
||||
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
|
||||
fn safe_divide(x: i64, y: i64) -> Result<i64, EvalAltResult> {
|
||||
if y == 0 {
|
||||
// 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 {
|
||||
Ok(x / y)
|
||||
}
|
||||
@ -336,10 +374,10 @@ Any similarly-named function defined in a script overrides any built-in function
|
||||
```rust
|
||||
// Override the built-in function 'to_int'
|
||||
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
|
||||
@ -507,21 +545,19 @@ fn main() -> Result<(), EvalAltResult>
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Then push some initialized variables into the state
|
||||
// NOTE: Remember the default numbers used by Rhai are i64 and f64.
|
||||
// Better stick to them or it gets hard to work with other variables in the script.
|
||||
// NOTE: Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64.
|
||||
// Better stick to them or it gets hard working with the script.
|
||||
scope.push("y".into(), 42_i64);
|
||||
scope.push("z".into(), 999_i64);
|
||||
|
||||
// First invocation
|
||||
// (the second boolean argument indicates that we don't need to retain function definitions
|
||||
// because we didn't declare any!)
|
||||
engine.eval_with_scope::<()>(&mut scope, false, r"
|
||||
engine.eval_with_scope::<()>(&mut scope, r"
|
||||
let x = 4 + 5 - y + z;
|
||||
y = 1;
|
||||
")?;
|
||||
|
||||
// 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
|
||||
|
||||
@ -590,24 +626,23 @@ Unary operators
|
||||
```rust
|
||||
let number = -5;
|
||||
number = -5 - +5;
|
||||
let booly = !true;
|
||||
let boolean = !true;
|
||||
```
|
||||
|
||||
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 |
|
||||
| ---------- | ----------------------------------- |
|
||||
| `abs` | absolute value |
|
||||
| `to_int` | converts an `f32` or `f64` to `i64` |
|
||||
| `to_float` | converts an integer type to `f64` |
|
||||
| Function | Description |
|
||||
| ---------- | --------------------------------- |
|
||||
| `abs` | absolute value |
|
||||
| `to_float` | converts an integer type to `f64` |
|
||||
|
||||
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 |
|
||||
| ---------------- | ------------------------------------------------------------ |
|
||||
@ -617,7 +652,8 @@ The following standard functions (defined in the standard library but excluded i
|
||||
| Exponential | `exp` (base _e_) |
|
||||
| Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) |
|
||||
| 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
|
||||
-----------------
|
||||
@ -636,6 +672,7 @@ let record = full_name + ": age " + age;
|
||||
record == "Bob C. Davis: age 42";
|
||||
|
||||
// Strings can be indexed to get a character
|
||||
// (disabled with the 'no_index' feature)
|
||||
let c = record[4];
|
||||
c == 'C';
|
||||
|
||||
@ -659,7 +696,7 @@ record[4] = '\x58'; // 0x58 = 'X'
|
||||
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 |
|
||||
| ---------- | ------------------------------------------------------------------------ |
|
||||
@ -706,7 +743,7 @@ Arrays
|
||||
|
||||
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 |
|
||||
| ---------- | ------------------------------------------------------------------------------------- |
|
||||
@ -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
|
||||
your own custom type, you need to define a specific override:
|
||||
your own custom type, you need to register a type-specific version:
|
||||
|
||||
```rust
|
||||
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"`.
|
||||
|
||||
Arrays are disabled via the [`no_index`] feature.
|
||||
|
||||
Comparison operators
|
||||
--------------------
|
||||
|
||||
@ -881,7 +920,7 @@ for x in array {
|
||||
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) {
|
||||
print(x);
|
||||
if x == 42 { break; }
|
||||
@ -927,7 +966,7 @@ println!(result); // prints "Runtime error: 42 is too large! (line 5, position
|
||||
Functions
|
||||
---------
|
||||
|
||||
Rhai supports defining functions in script:
|
||||
Rhai supports defining functions in script (unless disabled with [`no_function`]):
|
||||
|
||||
```rust
|
||||
fn add(x, y) {
|
||||
@ -947,9 +986,8 @@ fn add(x, y) {
|
||||
print(add(2, 3));
|
||||
```
|
||||
|
||||
Remember that functions defined in script always take `Dynamic` arguments (i.e. the arguments can be of any type).
|
||||
|
||||
However, all arguments are passed by _value_, so all functions are _pure_ (i.e. they never modify their arguments).
|
||||
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).
|
||||
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if you are not careful.
|
||||
|
||||
```rust
|
||||
@ -962,7 +1000,7 @@ x.change();
|
||||
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
|
||||
// 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
|
||||
-------------------
|
||||
|
||||
@ -1002,7 +1056,8 @@ debug("world!"); // prints "world!" to stdout using debug formatting
|
||||
### Overriding `print` and `debug` with callback functions
|
||||
|
||||
```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_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_debug(|s| log.push(format!("DEBUG: {}", s)));
|
||||
|
||||
// Evalulate script
|
||||
// Evaluate script
|
||||
engine.eval::<()>(script)?;
|
||||
|
||||
// 'log' captures all the 'print' and 'debug' output
|
||||
@ -1021,3 +1076,109 @@ for entry in log {
|
||||
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::{
|
||||
io::{stdin, stdout, Write},
|
||||
iter,
|
||||
@ -46,6 +46,7 @@ fn main() {
|
||||
let mut scope = Scope::new();
|
||||
|
||||
let mut input = String::new();
|
||||
let mut ast: Option<AST> = None;
|
||||
|
||||
loop {
|
||||
print!("rhai> ");
|
||||
@ -57,7 +58,28 @@ fn main() {
|
||||
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!("");
|
||||
print_error(&input, err);
|
||||
println!("");
|
||||
|
@ -4,9 +4,9 @@ fn main() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::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);
|
||||
|
||||
|
@ -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!("{}", filename);
|
||||
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::call::FuncArgs;
|
||||
use crate::engine::{Engine, FnAny, FnIntExt, FnSpec};
|
||||
use crate::engine::{Engine, FnAny, FnSpec};
|
||||
use crate::error::ParseError;
|
||||
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::scope::Scope;
|
||||
use std::{
|
||||
@ -42,7 +42,7 @@ impl<'e> Engine<'e> {
|
||||
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`.
|
||||
@ -64,7 +64,7 @@ impl<'e> Engine<'e> {
|
||||
where
|
||||
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`.
|
||||
@ -101,91 +101,87 @@ impl<'e> Engine<'e> {
|
||||
|
||||
/// Compile a string into an AST.
|
||||
pub fn compile(&self, input: &str) -> Result<AST, ParseError> {
|
||||
let tokens = lex(input);
|
||||
parse(&mut tokens.peekable(), self.optimize)
|
||||
let tokens_stream = lex(input);
|
||||
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.
|
||||
pub fn compile_file(&self, path: PathBuf) -> Result<AST, 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))
|
||||
.and_then(|_| self.compile(&contents).map_err(EvalAltResult::ErrorParsing))
|
||||
Self::read_file(path)
|
||||
.and_then(|contents| self.compile(&contents).map_err(|err| err.into()))
|
||||
}
|
||||
|
||||
/// Evaluate a file.
|
||||
pub fn eval_file<T: Any + Clone>(&mut self, path: PathBuf) -> Result<T, 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))
|
||||
.and_then(|_| self.eval::<T>(&contents))
|
||||
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
|
||||
}
|
||||
|
||||
/// Evaluate a string.
|
||||
pub fn eval<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
|
||||
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.
|
||||
///
|
||||
/// 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>(
|
||||
&mut self,
|
||||
scope: &mut Scope,
|
||||
retain_functions: bool,
|
||||
input: &str,
|
||||
) -> Result<T, EvalAltResult> {
|
||||
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.
|
||||
pub fn eval_ast<T: Any + Clone>(&mut self, ast: &AST) -> Result<T, EvalAltResult> {
|
||||
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.
|
||||
///
|
||||
/// 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>(
|
||||
&mut self,
|
||||
scope: &mut Scope,
|
||||
retain_functions: bool,
|
||||
ast: &AST,
|
||||
) -> 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| {
|
||||
self.script_functions.insert(
|
||||
FnSpec {
|
||||
name: f.name.clone().into(),
|
||||
args: None,
|
||||
},
|
||||
Arc::new(FnIntExt::Int(f.clone())),
|
||||
);
|
||||
});
|
||||
#[cfg(feature = "no_function")]
|
||||
let AST(statements) = ast;
|
||||
|
||||
let result = statements
|
||||
.iter()
|
||||
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt));
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let statements = {
|
||||
let AST(statements, functions) = ast;
|
||||
engine.load_script_functions(functions);
|
||||
statements
|
||||
};
|
||||
|
||||
if !retain_functions {
|
||||
self.clear_functions();
|
||||
let result = statements
|
||||
.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| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
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).
|
||||
/// 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())
|
||||
.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.consume(&contents))
|
||||
///
|
||||
/// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_
|
||||
/// and not cleared from run to run.
|
||||
pub fn consume_file(
|
||||
&mut self,
|
||||
path: PathBuf,
|
||||
retain_functions: bool,
|
||||
) -> Result<(), EvalAltResult> {
|
||||
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).
|
||||
/// 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).
|
||||
@ -234,84 +233,116 @@ impl<'e> Engine<'e> {
|
||||
retain_functions: bool,
|
||||
input: &str,
|
||||
) -> Result<(), EvalAltResult> {
|
||||
let tokens = lex(input);
|
||||
let tokens_stream = lex(input);
|
||||
|
||||
parse(&mut tokens.peekable(), self.optimize)
|
||||
.map_err(|err| EvalAltResult::ErrorParsing(err))
|
||||
.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 ast = parse(&mut tokens_stream.peekable(), self.optimize)
|
||||
.map_err(EvalAltResult::ErrorParsing)?;
|
||||
|
||||
let val = statements
|
||||
.iter()
|
||||
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o))
|
||||
.map(|_| ());
|
||||
|
||||
if !retain_functions {
|
||||
self.clear_functions();
|
||||
}
|
||||
|
||||
val
|
||||
})
|
||||
self.consume_ast_with_scope(scope, retain_functions, &ast)
|
||||
}
|
||||
|
||||
/// 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
|
||||
///
|
||||
/// ```rust
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// # #[cfg(not(feature = "no_stdlib"))]
|
||||
/// # #[cfg(not(feature = "no_function"))]
|
||||
/// # {
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// 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);
|
||||
/// # }
|
||||
/// # 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,
|
||||
name: &str,
|
||||
ast: &AST,
|
||||
args: A,
|
||||
) -> 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| {
|
||||
self.script_functions.insert(
|
||||
FnSpec {
|
||||
name: f.name.clone().into(),
|
||||
args: None,
|
||||
},
|
||||
Arc::new(FnIntExt::Int(f.clone())),
|
||||
);
|
||||
});
|
||||
let result = engine.call_fn_raw(name, values, None, Position::none());
|
||||
|
||||
let result = self
|
||||
.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,
|
||||
)
|
||||
})
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
self.clear_functions();
|
||||
|
||||
result
|
||||
call_fn_internal(self, name, args.into_vec()).and_then(|b| {
|
||||
b.downcast().map(|b| *b).map_err(|a| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name((*a).type_name()).into(),
|
||||
Position::none(),
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Override default action of `print` (print to stdout using `println!`)
|
||||
@ -328,7 +359,7 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// // Override action of 'print' function
|
||||
/// engine.on_print(|s| result.push_str(s));
|
||||
/// engine.consume("print(40 + 2);")?;
|
||||
/// engine.consume("print(40 + 2);", false)?;
|
||||
/// }
|
||||
/// assert_eq!(result, "42");
|
||||
/// # Ok(())
|
||||
@ -352,7 +383,7 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// // Override action of 'debug' function
|
||||
/// engine.on_debug(|s| result.push_str(s));
|
||||
/// engine.consume(r#"debug("hello");"#)?;
|
||||
/// engine.consume(r#"debug("hello");"#, false)?;
|
||||
/// }
|
||||
/// assert_eq!(result, "\"hello\"");
|
||||
/// # 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.
|
||||
|
||||
use crate::any::{Any, Variant};
|
||||
use crate::any::{Any, Dynamic};
|
||||
use crate::engine::Array;
|
||||
|
||||
/// Trait that represent arguments to a function call.
|
||||
pub trait FuncArgs<'a> {
|
||||
/// Convert to a `Vec` of `Variant` arguments.
|
||||
fn into_vec(self) -> Vec<&'a mut Variant>;
|
||||
pub trait FuncArgs {
|
||||
/// Convert to a `Vec` of `Dynamic` arguments.
|
||||
fn into_vec(self) -> Vec<Dynamic>;
|
||||
}
|
||||
|
||||
impl<'a> FuncArgs<'a> for Vec<&'a mut Variant> {
|
||||
fn into_vec(self) -> Self {
|
||||
self
|
||||
}
|
||||
macro_rules! impl_std_args {
|
||||
($($p:ty),*) => {
|
||||
$(
|
||||
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> {
|
||||
fn into_vec(self) -> Vec<&'a mut Variant> {
|
||||
self.iter_mut().map(|x| x as &mut Variant).collect()
|
||||
}
|
||||
}
|
||||
impl_std_args!(String, char, bool);
|
||||
|
||||
#[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 {
|
||||
($($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;
|
||||
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
let mut v = Vec::new();
|
||||
$(v.push($p as &mut Variant);)*
|
||||
$(v.push($p.into_dynamic());)*
|
||||
|
||||
v
|
||||
}
|
||||
@ -39,10 +58,12 @@ macro_rules! impl_args {
|
||||
};
|
||||
(@pop) => {
|
||||
};
|
||||
(@pop $head:ident $(, $tail:ident)*) => {
|
||||
(@pop $head:ident) => {
|
||||
};
|
||||
(@pop $head:ident $(, $tail:ident)+) => {
|
||||
impl_args!($($tail),*);
|
||||
};
|
||||
}
|
||||
|
||||
#[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`.
|
||||
|
||||
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::scope::Scope;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::INT;
|
||||
|
||||
use std::{
|
||||
any::{type_name, TypeId},
|
||||
borrow::Cow,
|
||||
cmp::{PartialEq, PartialOrd},
|
||||
collections::HashMap,
|
||||
iter::once,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// An dynamic array of `Dynamic` values.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub type Array = Vec<Dynamic>;
|
||||
|
||||
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_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 FUNC_GETTER: &'static str = "get$";
|
||||
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 {
|
||||
Array,
|
||||
String,
|
||||
Expression,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||
pub struct FnSpec<'a> {
|
||||
pub name: Cow<'a, str>,
|
||||
pub args: Option<Vec<TypeId>>,
|
||||
@ -59,11 +65,11 @@ pub struct Engine<'e> {
|
||||
/// Optimize the AST after compilation
|
||||
pub(crate) optimize: bool,
|
||||
/// 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
|
||||
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
|
||||
pub(crate) type_iterators: HashMap<TypeId, Arc<IteratorFn>>,
|
||||
pub(crate) type_iterators: HashMap<TypeId, Box<IteratorFn>>,
|
||||
pub(crate) type_names: HashMap<String, String>,
|
||||
|
||||
// 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 enum FnIntExt<'a> {
|
||||
Ext(Box<FnAny>),
|
||||
Int(FnDef<'a>),
|
||||
}
|
||||
|
||||
impl Engine<'_> {
|
||||
/// Create a new `Engine`
|
||||
pub fn new() -> Self {
|
||||
// User-friendly names for built-in types
|
||||
let type_names = [
|
||||
(type_name::<String>(), "string"),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
(type_name::<Array>(), "array"),
|
||||
(type_name::<String>(), "string"),
|
||||
(type_name::<Dynamic>(), "dynamic"),
|
||||
]
|
||||
.iter()
|
||||
@ -93,7 +95,7 @@ impl Engine<'_> {
|
||||
let mut engine = Engine {
|
||||
optimize: true,
|
||||
ext_functions: HashMap::new(),
|
||||
script_functions: HashMap::new(),
|
||||
script_functions: Vec::new(),
|
||||
type_iterators: HashMap::new(),
|
||||
type_names,
|
||||
on_print: Box::new(default_print), // default print/debug implementations
|
||||
@ -132,113 +134,102 @@ impl Engine<'_> {
|
||||
.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(),
|
||||
args: None,
|
||||
args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()),
|
||||
};
|
||||
|
||||
// First search in script-defined functions (can override built-in),
|
||||
// then built-in's and external functions
|
||||
let fn_def = self
|
||||
.script_functions
|
||||
.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());
|
||||
// Then search built-in's and external functions
|
||||
if let Some(func) = self.ext_functions.get(&spec) {
|
||||
// Run external function
|
||||
let result = func(args, pos)?;
|
||||
|
||||
if let Some(f) = fn_def {
|
||||
match *f {
|
||||
// Run external function
|
||||
FnIntExt::Ext(ref func) => {
|
||||
let result = func(args, pos)?;
|
||||
// See if the function match print/debug (which requires special processing)
|
||||
let callback = match spec.name.as_ref() {
|
||||
KEYWORD_PRINT => self.on_print.as_mut(),
|
||||
KEYWORD_DEBUG => self.on_debug.as_mut(),
|
||||
_ => return Ok(result),
|
||||
};
|
||||
|
||||
// See if the function match print/debug (which requires special processing)
|
||||
let callback = match spec.name.as_ref() {
|
||||
KEYWORD_PRINT => self.on_print.as_mut(),
|
||||
KEYWORD_DEBUG => self.on_debug.as_mut(),
|
||||
_ => return Ok(result),
|
||||
};
|
||||
let val = &result
|
||||
.downcast::<String>()
|
||||
.map(|s| *s)
|
||||
.unwrap_or("error: not a string".into());
|
||||
|
||||
let val = &result
|
||||
.downcast::<String>()
|
||||
.map(|s| *s)
|
||||
.unwrap_or("error: not a string".into());
|
||||
return Ok(callback(val).into_dynamic());
|
||||
}
|
||||
|
||||
Ok(callback(val).into_dynamic())
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if spec.name == KEYWORD_TYPE_OF && args.len() == 1 {
|
||||
// Handle `type_of` function
|
||||
Ok(self
|
||||
return Ok(self
|
||||
.map_type_name(args[0].type_name())
|
||||
.to_string()
|
||||
.into_dynamic())
|
||||
} else if spec.name.starts_with(FUNC_GETTER) {
|
||||
.into_dynamic());
|
||||
}
|
||||
|
||||
if spec.name.starts_with(FUNC_GETTER) {
|
||||
// Getter function not found
|
||||
Err(EvalAltResult::ErrorDotExpr(
|
||||
return Err(EvalAltResult::ErrorDotExpr(
|
||||
format!(
|
||||
"- property '{}' unknown or write-only",
|
||||
&spec.name[FUNC_GETTER.len()..]
|
||||
),
|
||||
pos,
|
||||
))
|
||||
} else if spec.name.starts_with(FUNC_SETTER) {
|
||||
));
|
||||
}
|
||||
|
||||
if spec.name.starts_with(FUNC_SETTER) {
|
||||
// Setter function not found
|
||||
Err(EvalAltResult::ErrorDotExpr(
|
||||
return Err(EvalAltResult::ErrorDotExpr(
|
||||
format!(
|
||||
"- property '{}' unknown or read-only",
|
||||
&spec.name[FUNC_SETTER.len()..]
|
||||
),
|
||||
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
|
||||
@ -250,14 +241,14 @@ impl Engine<'_> {
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
match dot_rhs {
|
||||
// xxx.fn_name(args)
|
||||
Expr::FunctionCall(fn_name, args, def_val, pos) => {
|
||||
let mut args: Array = args
|
||||
Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => {
|
||||
let mut values = arg_expr_list
|
||||
.iter()
|
||||
.map(|arg| self.eval_expr(scope, arg))
|
||||
.map(|arg_expr| self.eval_expr(scope, arg_expr))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let args = once(this_ptr)
|
||||
.chain(args.iter_mut().map(|b| b.as_mut()))
|
||||
.chain(values.iter_mut().map(|b| b.as_mut()))
|
||||
.collect();
|
||||
|
||||
self.call_fn_raw(fn_name, args, def_val.as_ref(), *pos)
|
||||
@ -271,8 +262,9 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
// xxx.idx_lhs[idx_expr]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
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]
|
||||
Expr::Identifier(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
@ -295,7 +287,7 @@ impl Engine<'_> {
|
||||
};
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -309,8 +301,9 @@ impl Engine<'_> {
|
||||
.and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), rhs))
|
||||
}
|
||||
// xxx.idx_lhs[idx_expr].rhs
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
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
|
||||
Expr::Identifier(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
@ -333,7 +326,7 @@ impl Engine<'_> {
|
||||
};
|
||||
|
||||
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))
|
||||
}
|
||||
// Syntax error
|
||||
@ -371,6 +364,7 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
// idx_lhs[idx_expr].???
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
||||
let (src_type, src, idx, mut target) =
|
||||
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)))
|
||||
}
|
||||
|
||||
/// 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(
|
||||
&mut self,
|
||||
scope: &mut Scope,
|
||||
idx_expr: &Expr,
|
||||
) -> Result<i64, EvalAltResult> {
|
||||
) -> Result<INT, EvalAltResult> {
|
||||
self.eval_expr(scope, idx_expr)?
|
||||
.downcast::<i64>()
|
||||
.downcast::<INT>()
|
||||
.map(|v| *v)
|
||||
.map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))
|
||||
}
|
||||
|
||||
/// Get the value at the indexed position of a base type
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
fn get_indexed_value(
|
||||
&self,
|
||||
val: Dynamic,
|
||||
idx: i64,
|
||||
val: &Dynamic,
|
||||
idx: INT,
|
||||
val_pos: Position,
|
||||
idx_pos: Position,
|
||||
) -> Result<(Dynamic, IndexSourceType), EvalAltResult> {
|
||||
if val.is::<Array>() {
|
||||
// val_array[idx]
|
||||
let arr = val.downcast::<Array>().expect("array expected");
|
||||
let arr = val.downcast_ref::<Array>().expect("array expected");
|
||||
|
||||
if idx >= 0 {
|
||||
arr.get(idx as usize)
|
||||
@ -447,7 +443,7 @@ impl Engine<'_> {
|
||||
}
|
||||
} else if val.is::<String>() {
|
||||
// val_string[idx]
|
||||
let s = val.downcast::<String>().expect("string expected");
|
||||
let s = val.downcast_ref::<String>().expect("string expected");
|
||||
|
||||
if idx >= 0 {
|
||||
s.chars()
|
||||
@ -473,6 +469,7 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
/// Evaluate an index expression
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
fn eval_index_expr<'a>(
|
||||
&mut self,
|
||||
scope: &mut Scope,
|
||||
@ -487,7 +484,7 @@ impl Engine<'_> {
|
||||
Expr::Identifier(id, _) => Self::search_scope(
|
||||
scope,
|
||||
&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(),
|
||||
)
|
||||
.map(|(src_idx, (val, src_type))| {
|
||||
@ -498,13 +495,14 @@ impl Engine<'_> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
let mut chars: Vec<char> = s.chars().collect();
|
||||
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
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
fn update_indexed_var_in_scope(
|
||||
src_type: IndexSourceType,
|
||||
scope: &mut Scope,
|
||||
@ -550,6 +549,7 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
/// Update the value at an index position
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
fn update_indexed_value(
|
||||
mut target: Dynamic,
|
||||
idx: usize,
|
||||
@ -593,6 +593,7 @@ impl Engine<'_> {
|
||||
|
||||
// xxx.lhs[idx_expr]
|
||||
// TODO - Allow chaining of indexing!
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
|
||||
// xxx.id[idx_expr]
|
||||
Expr::Identifier(id, pos) => {
|
||||
@ -636,6 +637,7 @@ impl Engine<'_> {
|
||||
|
||||
// xxx.lhs[idx_expr].rhs
|
||||
// TODO - Allow chaining of indexing!
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
|
||||
// xxx.id[idx_expr].rhs
|
||||
Expr::Identifier(id, pos) => {
|
||||
@ -644,12 +646,8 @@ impl Engine<'_> {
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||
.and_then(|v| {
|
||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||
let (mut target, _) = self.get_indexed_value(
|
||||
v.clone(), // TODO - Avoid cloning this
|
||||
idx,
|
||||
idx_expr.position(),
|
||||
*idx_pos,
|
||||
)?;
|
||||
let (mut target, _) =
|
||||
self.get_indexed_value(&v, idx, idx_expr.position(), *idx_pos)?;
|
||||
|
||||
self.set_dot_val_helper(
|
||||
scope,
|
||||
@ -720,6 +718,7 @@ impl Engine<'_> {
|
||||
|
||||
// lhs[idx_expr].???
|
||||
// TODO - Allow chaining of indexing!
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(lhs, idx_expr, idx_pos) => {
|
||||
let (src_type, src, idx, mut target) =
|
||||
self.eval_index_expr(scope, lhs, idx_expr, *idx_pos)?;
|
||||
@ -753,8 +752,10 @@ impl Engine<'_> {
|
||||
/// Evaluate an expression
|
||||
fn eval_expr(&mut self, scope: &mut Scope, expr: &Expr) -> Result<Dynamic, EvalAltResult> {
|
||||
match expr {
|
||||
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(f, _) => Ok(f.into_dynamic()),
|
||||
|
||||
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
|
||||
Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
|
||||
Expr::CharConstant(c, _) => Ok(c.into_dynamic()),
|
||||
Expr::Identifier(id, pos) => {
|
||||
@ -762,10 +763,14 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
// lhs[idx_expr]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(lhs, idx_expr, idx_pos) => self
|
||||
.eval_index_expr(scope, lhs, idx_expr, *idx_pos)
|
||||
.map(|(_, _, _, x)| x),
|
||||
|
||||
#[cfg(feature = "no_index")]
|
||||
Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"),
|
||||
|
||||
// Statement block
|
||||
Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt),
|
||||
|
||||
@ -785,6 +790,7 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
// idx_lhs[idx_expr] = rhs
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, idx_expr, idx_pos) => {
|
||||
let (src_type, src, idx, _) =
|
||||
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),
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(contents, _) => {
|
||||
let mut arr = Vec::new();
|
||||
|
||||
contents
|
||||
.iter()
|
||||
.try_for_each::<_, Result<_, EvalAltResult>>(|item| {
|
||||
let arg = self.eval_expr(scope, item)?;
|
||||
arr.push(arg);
|
||||
Ok(())
|
||||
})?;
|
||||
for item in contents {
|
||||
arr.push(self.eval_expr(scope, item)?);
|
||||
}
|
||||
|
||||
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) => {
|
||||
let mut args = args
|
||||
// Dump AST
|
||||
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()
|
||||
.map(|expr| self.eval_expr(scope, expr))
|
||||
.collect::<Result<Array, _>>()?;
|
||||
.collect::<Result<Vec<Dynamic>, _>>()?;
|
||||
|
||||
self.call_fn_raw(
|
||||
fn_name,
|
||||
args.iter_mut().map(|b| b.as_mut()).collect(),
|
||||
values.iter_mut().map(|b| b.as_mut()).collect(),
|
||||
def_val.as_ref(),
|
||||
*pos,
|
||||
)
|
||||
@ -987,22 +1017,22 @@ impl Engine<'_> {
|
||||
Stmt::Break(_) => Err(EvalAltResult::LoopBreak),
|
||||
|
||||
// Empty return
|
||||
Stmt::ReturnWithVal(None, true, pos) => {
|
||||
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
|
||||
Err(EvalAltResult::Return(().into_dynamic(), *pos))
|
||||
}
|
||||
|
||||
// Return value
|
||||
Stmt::ReturnWithVal(Some(a), true, pos) => {
|
||||
Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => {
|
||||
Err(EvalAltResult::Return(self.eval_expr(scope, a)?, *pos))
|
||||
}
|
||||
|
||||
// Empty throw
|
||||
Stmt::ReturnWithVal(None, false, pos) => {
|
||||
Stmt::ReturnWithVal(None, ReturnType::Exception, pos) => {
|
||||
Err(EvalAltResult::ErrorRuntime("".into(), *pos))
|
||||
}
|
||||
|
||||
// Throw value
|
||||
Stmt::ReturnWithVal(Some(a), false, pos) => {
|
||||
Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => {
|
||||
let val = self.eval_expr(scope, a)?;
|
||||
Err(EvalAltResult::ErrorRuntime(
|
||||
val.downcast::<String>()
|
||||
@ -1013,13 +1043,14 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
// Let statement
|
||||
Stmt::Let(name, init, _) => {
|
||||
if let Some(v) = init {
|
||||
let val = self.eval_expr(scope, v)?;
|
||||
scope.push_dynamic(name.clone(), val);
|
||||
} else {
|
||||
scope.push(name.clone(), ());
|
||||
}
|
||||
Stmt::Let(name, Some(expr), _) => {
|
||||
let val = self.eval_expr(scope, expr)?;
|
||||
scope.push_dynamic(name.clone(), val);
|
||||
Ok(().into_dynamic())
|
||||
}
|
||||
|
||||
Stmt::Let(name, None, _) => {
|
||||
scope.push(name.clone(), ());
|
||||
Ok(().into_dynamic())
|
||||
}
|
||||
}
|
||||
|
@ -120,20 +120,20 @@ macro_rules! def_register {
|
||||
const NUM_ARGS: usize = count_args!($($par)*);
|
||||
|
||||
if args.len() != NUM_ARGS {
|
||||
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)
|
||||
return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos));
|
||||
}
|
||||
|
||||
#[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));
|
||||
}
|
||||
@ -152,19 +152,19 @@ macro_rules! def_register {
|
||||
const NUM_ARGS: usize = count_args!($($par)*);
|
||||
|
||||
if args.len() != NUM_ARGS {
|
||||
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)),*))
|
||||
return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos));
|
||||
}
|
||||
|
||||
#[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));
|
||||
}
|
||||
@ -184,23 +184,23 @@ macro_rules! def_register {
|
||||
const NUM_ARGS: usize = count_args!($($par)*);
|
||||
|
||||
if args.len() != NUM_ARGS {
|
||||
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();
|
||||
)*
|
||||
return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos));
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
#[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.
|
||||
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 call::FuncArgs;
|
||||
pub use engine::{Array, Engine};
|
||||
pub use engine::Engine;
|
||||
pub use error::{ParseError, ParseErrorType};
|
||||
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 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};
|
||||
|
||||
fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt {
|
||||
fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> 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 {
|
||||
Expr::False(pos) => {
|
||||
*changed = true;
|
||||
Stmt::Noop(pos)
|
||||
}
|
||||
Expr::True(_) => optimize_stmt(*stmt1, changed),
|
||||
Expr::True(_) => optimize_stmt(*stmt1, changed, true),
|
||||
expr => Stmt::IfElse(
|
||||
Box::new(optimize_expr(expr, changed)),
|
||||
Box::new(optimize_stmt(*stmt1, changed)),
|
||||
Box::new(optimize_stmt(*stmt1, changed, true)),
|
||||
None,
|
||||
),
|
||||
},
|
||||
|
||||
Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr {
|
||||
Expr::False(_) => optimize_stmt(*stmt2, changed),
|
||||
Expr::True(_) => optimize_stmt(*stmt1, changed),
|
||||
Expr::False(_) => optimize_stmt(*stmt2, changed, true),
|
||||
Expr::True(_) => optimize_stmt(*stmt1, changed, true),
|
||||
expr => Stmt::IfElse(
|
||||
Box::new(optimize_expr(expr, changed)),
|
||||
Box::new(optimize_stmt(*stmt1, changed)),
|
||||
Some(Box::new(optimize_stmt(*stmt2, changed))),
|
||||
Box::new(optimize_stmt(*stmt1, changed, true)),
|
||||
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;
|
||||
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(
|
||||
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(
|
||||
id,
|
||||
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(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::Block(statements, pos) => {
|
||||
let original_len = statements.len();
|
||||
let orig_len = statements.len();
|
||||
|
||||
let mut result: Vec<_> = statements
|
||||
.into_iter() // For each statement
|
||||
.map(|s| optimize_stmt(s, changed)) // Optimize the statement
|
||||
.filter(Stmt::is_op) // Remove no-op's
|
||||
.rev() // Scan in reverse
|
||||
.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();
|
||||
|
||||
if let Some(last_stmt) = result.pop() {
|
||||
// Remove all raw expression statements that evaluate to constants
|
||||
// except for the very last statement
|
||||
result.retain(|stmt| match stmt {
|
||||
Stmt::Expr(expr) if expr.is_constant() => false,
|
||||
_ => true,
|
||||
});
|
||||
// Remove all raw expression statements that are pure except for the very last statement
|
||||
let last_stmt = if preserve_result { result.pop() } else { None };
|
||||
|
||||
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[..] {
|
||||
// No statements in block - change to No-op
|
||||
[] => {
|
||||
// No statements in block - change to No-op
|
||||
*changed = true;
|
||||
Stmt::Noop(pos)
|
||||
}
|
||||
[Stmt::Let(_, None, _)] => {
|
||||
// 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
|
||||
[_] => {
|
||||
// Only one statement - promote
|
||||
*changed = true;
|
||||
result.remove(0)
|
||||
}
|
||||
@ -103,24 +152,14 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt {
|
||||
is_return,
|
||||
pos,
|
||||
),
|
||||
stmt @ Stmt::ReturnWithVal(None, _, _) => stmt,
|
||||
|
||||
stmt @ Stmt::Noop(_) | stmt @ Stmt::Break(_) => stmt,
|
||||
stmt => stmt,
|
||||
}
|
||||
}
|
||||
|
||||
fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
||||
match expr {
|
||||
Expr::IntegerConstant(_, _)
|
||||
| Expr::FloatConstant(_, _)
|
||||
| Expr::Identifier(_, _)
|
||||
| Expr::CharConstant(_, _)
|
||||
| Expr::StringConstant(_, _)
|
||||
| Expr::True(_)
|
||||
| Expr::False(_)
|
||||
| Expr::Unit(_) => expr,
|
||||
|
||||
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed) {
|
||||
Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed, true) {
|
||||
Stmt::Noop(_) => {
|
||||
*changed = true;
|
||||
Expr::Unit(pos)
|
||||
@ -139,24 +178,43 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr {
|
||||
Box::new(optimize_expr(*rhs, changed)),
|
||||
pos,
|
||||
),
|
||||
Expr::Index(lhs, rhs, pos) => Expr::Index(
|
||||
Box::new(optimize_expr(*lhs, changed)),
|
||||
Box::new(optimize_expr(*rhs, changed)),
|
||||
pos,
|
||||
),
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) {
|
||||
(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) => {
|
||||
let original_len = items.len();
|
||||
let orig_len = items.len();
|
||||
|
||||
let items: Vec<_> = items
|
||||
.into_iter()
|
||||
.map(|expr| optimize_expr(expr, changed))
|
||||
.collect();
|
||||
|
||||
*changed = *changed || original_len != items.len();
|
||||
*changed = *changed || orig_len != items.len();
|
||||
|
||||
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::True(_), rhs) => {
|
||||
*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) => {
|
||||
let original_len = args.len();
|
||||
let orig_len = args.len();
|
||||
|
||||
let args: Vec<_> = args
|
||||
.into_iter()
|
||||
.map(|a| optimize_expr(a, changed))
|
||||
.collect();
|
||||
|
||||
*changed = *changed || original_len != args.len();
|
||||
*changed = *changed || orig_len != args.len();
|
||||
|
||||
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 {
|
||||
let mut changed = false;
|
||||
|
||||
statements = statements
|
||||
result = result
|
||||
.into_iter()
|
||||
.map(|stmt| optimize_stmt(stmt, &mut changed))
|
||||
.filter(Stmt::is_op)
|
||||
.rev() // Scan in reverse
|
||||
.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();
|
||||
|
||||
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::error::{LexError, ParseError, ParseErrorType};
|
||||
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 PERR = ParseErrorType;
|
||||
@ -123,16 +142,35 @@ impl fmt::Debug for Position {
|
||||
}
|
||||
|
||||
/// Compiled AST (abstract syntax tree) of a Rhai script.
|
||||
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<FnDef<'static>>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FnDef<'a> {
|
||||
pub name: Cow<'a, str>,
|
||||
pub params: Vec<Cow<'a, str>>,
|
||||
pub struct AST(
|
||||
pub(crate) Vec<Stmt>,
|
||||
#[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 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)]
|
||||
pub enum Stmt {
|
||||
Noop(Position),
|
||||
@ -144,22 +182,49 @@ pub enum Stmt {
|
||||
Block(Vec<Stmt>, Position),
|
||||
Expr(Box<Expr>),
|
||||
Break(Position),
|
||||
ReturnWithVal(Option<Box<Expr>>, bool, Position),
|
||||
ReturnWithVal(Option<Box<Expr>>, ReturnType, Position),
|
||||
}
|
||||
|
||||
impl Stmt {
|
||||
pub fn is_noop(&self) -> bool {
|
||||
match self {
|
||||
Stmt::Noop(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_op(&self) -> bool {
|
||||
match self {
|
||||
Stmt::Noop(_) => false,
|
||||
_ => 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)]
|
||||
pub enum Expr {
|
||||
IntegerConstant(i64, Position),
|
||||
FloatConstant(f64, Position),
|
||||
IntegerConstant(INT, Position),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
FloatConstant(FLOAT, Position),
|
||||
Identifier(String, Position),
|
||||
CharConstant(char, Position),
|
||||
StringConstant(String, Position),
|
||||
@ -180,36 +245,56 @@ impl Expr {
|
||||
pub fn position(&self) -> Position {
|
||||
match self {
|
||||
Expr::IntegerConstant(_, pos)
|
||||
| Expr::FloatConstant(_, pos)
|
||||
| Expr::Identifier(_, pos)
|
||||
| Expr::CharConstant(_, pos)
|
||||
| Expr::StringConstant(_, pos)
|
||||
| Expr::FunctionCall(_, _, _, pos)
|
||||
| Expr::Stmt(_, pos)
|
||||
| Expr::FunctionCall(_, _, _, pos)
|
||||
| Expr::Array(_, pos)
|
||||
| Expr::True(pos)
|
||||
| Expr::False(pos)
|
||||
| Expr::Unit(pos) => *pos,
|
||||
|
||||
Expr::Index(e, _, _)
|
||||
| Expr::Assignment(e, _, _)
|
||||
Expr::Assignment(e, _, _)
|
||||
| Expr::Dot(e, _, _)
|
||||
| Expr::Index(e, _, _)
|
||||
| Expr::And(e, _)
|
||||
| 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 {
|
||||
match self {
|
||||
Expr::IntegerConstant(_, _)
|
||||
| Expr::FloatConstant(_, _)
|
||||
| Expr::Identifier(_, _)
|
||||
| Expr::CharConstant(_, _)
|
||||
| Expr::StringConstant(_, _)
|
||||
| Expr::True(_)
|
||||
| Expr::False(_)
|
||||
| Expr::Unit(_) => true,
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(_, _) => true,
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn is_identifier(&self) -> bool {
|
||||
match self {
|
||||
Expr::Identifier(_, _) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -217,8 +302,9 @@ impl Expr {
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Token {
|
||||
IntegerConstant(i64),
|
||||
FloatConstant(f64),
|
||||
IntegerConstant(INT),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
FloatConstant(FLOAT),
|
||||
Identifier(String),
|
||||
CharConstant(char),
|
||||
StringConst(String),
|
||||
@ -288,6 +374,7 @@ impl Token {
|
||||
|
||||
match *self {
|
||||
IntegerConstant(ref i) => i.to_string().into(),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
FloatConstant(ref f) => f.to_string().into(),
|
||||
Identifier(ref s) => s.into(),
|
||||
CharConstant(ref c) => c.to_string().into(),
|
||||
@ -468,14 +555,9 @@ impl<'a> TokenIterator<'a> {
|
||||
|
||||
loop {
|
||||
let next_char = self.char_stream.next();
|
||||
|
||||
if next_char.is_none() {
|
||||
return Err((LERR::UnterminatedString, Position::eof()));
|
||||
}
|
||||
|
||||
self.advance();
|
||||
|
||||
match next_char.unwrap() {
|
||||
match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? {
|
||||
'\\' if escape.is_empty() => {
|
||||
escape.push('\\');
|
||||
}
|
||||
@ -617,6 +699,7 @@ impl<'a> TokenIterator<'a> {
|
||||
self.char_stream.next();
|
||||
self.advance();
|
||||
}
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
'.' => {
|
||||
result.push(next_char);
|
||||
self.char_stream.next();
|
||||
@ -692,7 +775,7 @@ impl<'a> TokenIterator<'a> {
|
||||
let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect();
|
||||
|
||||
return Some((
|
||||
i64::from_str_radix(&out, radix)
|
||||
INT::from_str_radix(&out, radix)
|
||||
.map(Token::IntegerConstant)
|
||||
.unwrap_or_else(|_| {
|
||||
Token::LexError(LERR::MalformedNumber(result.iter().collect()))
|
||||
@ -702,10 +785,21 @@ impl<'a> TokenIterator<'a> {
|
||||
} else {
|
||||
let out: String = result.iter().filter(|&&c| c != '_').collect();
|
||||
|
||||
#[cfg(feature = "no_float")]
|
||||
return Some((
|
||||
i64::from_str(&out)
|
||||
INT::from_str(&out)
|
||||
.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(|_| {
|
||||
Token::LexError(LERR::MalformedNumber(result.iter().collect()))
|
||||
}),
|
||||
@ -797,7 +891,8 @@ impl<'a> TokenIterator<'a> {
|
||||
}
|
||||
'-' => match self.char_stream.peek() {
|
||||
// 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('=') => {
|
||||
self.char_stream.next();
|
||||
self.advance();
|
||||
@ -1126,26 +1221,26 @@ fn parse_call_expr<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
begin: Position,
|
||||
) -> Result<Expr, ParseError> {
|
||||
let mut args = Vec::new();
|
||||
let mut args_expr_list = Vec::new();
|
||||
|
||||
if let Some(&(Token::RightParen, _)) = input.peek() {
|
||||
input.next();
|
||||
return Ok(Expr::FunctionCall(id, args, None, begin));
|
||||
return Ok(Expr::FunctionCall(id, args_expr_list, None, begin));
|
||||
}
|
||||
|
||||
loop {
|
||||
args.push(parse_expr(input)?);
|
||||
args_expr_list.push(parse_expr(input)?);
|
||||
|
||||
match input.peek() {
|
||||
Some(&(Token::RightParen, _)) => {
|
||||
input.next();
|
||||
return Ok(Expr::FunctionCall(id, args, None, begin));
|
||||
return Ok(Expr::FunctionCall(id, args_expr_list, None, begin));
|
||||
}
|
||||
Some(&(Token::Comma, _)) => (),
|
||||
Some(&(_, pos)) => {
|
||||
return Err(ParseError::new(
|
||||
PERR::MissingRightParen(format!(
|
||||
"closing the arguments list to function call of '{}'",
|
||||
"closing the parameters list to function call of '{}'",
|
||||
id
|
||||
)),
|
||||
pos,
|
||||
@ -1154,7 +1249,7 @@ fn parse_call_expr<'a>(
|
||||
None => {
|
||||
return Err(ParseError::new(
|
||||
PERR::MissingRightParen(format!(
|
||||
"closing the arguments list to function call of '{}'",
|
||||
"closing the parameters list to function call of '{}'",
|
||||
id
|
||||
)),
|
||||
Position::eof(),
|
||||
@ -1166,6 +1261,7 @@ fn parse_call_expr<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
fn parse_index_expr<'a>(
|
||||
lhs: Box<Expr>,
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
@ -1184,6 +1280,7 @@ fn parse_index_expr<'a>(
|
||||
*pos,
|
||||
))
|
||||
}
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(_, pos) => {
|
||||
return Err(ParseError::new(
|
||||
PERR::MalformedIndexExpr("Array access expects integer index, not a float".into()),
|
||||
@ -1260,6 +1357,7 @@ fn parse_ident_expr<'a>(
|
||||
input.next();
|
||||
parse_call_expr(id, input, begin)
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Some(&(Token::LeftBracket, pos)) => {
|
||||
input.next();
|
||||
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>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
begin: Position,
|
||||
@ -1320,26 +1419,30 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
|
||||
|
||||
let token = input.next();
|
||||
|
||||
let mut follow_on = false;
|
||||
let mut can_be_indexed = false;
|
||||
|
||||
#[allow(unused_mut)]
|
||||
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::IntegerConstant(x), pos)) => Ok(Expr::IntegerConstant(x, pos)),
|
||||
Some((Token::CharConstant(c), pos)) => Ok(Expr::CharConstant(c, pos)),
|
||||
Some((Token::StringConst(s), pos)) => {
|
||||
follow_on = true;
|
||||
can_be_indexed = true;
|
||||
Ok(Expr::StringConstant(s, pos))
|
||||
}
|
||||
Some((Token::Identifier(s), pos)) => {
|
||||
follow_on = true;
|
||||
can_be_indexed = true;
|
||||
parse_ident_expr(s, input, pos)
|
||||
}
|
||||
Some((Token::LeftParen, pos)) => {
|
||||
follow_on = true;
|
||||
can_be_indexed = true;
|
||||
parse_paren_expr(input, pos)
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Some((Token::LeftBracket, pos)) => {
|
||||
follow_on = true;
|
||||
can_be_indexed = true;
|
||||
parse_array_expr(input, 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())),
|
||||
}?;
|
||||
|
||||
if !follow_on {
|
||||
return Ok(root_expr);
|
||||
}
|
||||
|
||||
// Tail processing all possible indexing
|
||||
while let Some(&(Token::LeftBracket, pos)) = input.peek() {
|
||||
input.next();
|
||||
root_expr = parse_index_expr(Box::new(root_expr), input, pos)?;
|
||||
if can_be_indexed {
|
||||
// Tail processing all possible indexing
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
while let Some(&(Token::LeftBracket, pos)) = input.peek() {
|
||||
input.next();
|
||||
root_expr = parse_index_expr(Box::new(root_expr), input, pos)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(root_expr)
|
||||
@ -1374,14 +1476,30 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
||||
|
||||
match parse_unary(input) {
|
||||
// Negative integer
|
||||
Ok(Expr::IntegerConstant(i, pos)) => Ok(i
|
||||
Ok(Expr::IntegerConstant(i, _)) => i
|
||||
.checked_neg()
|
||||
.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
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Ok(Expr::FloatConstant(f, pos)) => Ok(Expr::FloatConstant(-f, pos)),
|
||||
|
||||
// Call negative function
|
||||
Ok(expr) => Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)),
|
||||
|
||||
err @ Err(_) => err,
|
||||
}
|
||||
}
|
||||
@ -1408,17 +1526,21 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseEr
|
||||
match expr {
|
||||
Expr::Identifier(_, pos) => (true, *pos),
|
||||
|
||||
Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() {
|
||||
Expr::Identifier(_, _) => (true, idx_lhs.position()),
|
||||
_ => (false, idx_lhs.position()),
|
||||
},
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(idx_lhs, _, _) if idx_lhs.is_identifier() => (true, 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::Identifier(_, _) => valid_assignment_chain(dot_rhs),
|
||||
Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() {
|
||||
Expr::Identifier(_, _) => valid_assignment_chain(dot_rhs),
|
||||
_ => (false, idx_lhs.position()),
|
||||
},
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
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()),
|
||||
},
|
||||
|
||||
@ -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) {
|
||||
(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),
|
||||
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>(
|
||||
@ -1519,6 +1618,7 @@ fn parse_binary_op<'a>(
|
||||
Token::Equals => parse_assignment(current_lhs, rhs, pos)?,
|
||||
Token::PlusAssign => 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),
|
||||
|
||||
// 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> {
|
||||
match input.peek() {
|
||||
Some(&(Token::LeftBrace, _)) => (),
|
||||
Some(&(_, pos)) => return Err(ParseError::new(PERR::MissingLeftBrace, pos)),
|
||||
let pos = match input.next() {
|
||||
Some((Token::LeftBrace, pos)) => pos,
|
||||
Some((_, pos)) => return Err(ParseError::new(PERR::MissingLeftBrace, pos)),
|
||||
None => return Err(ParseError::new(PERR::MissingLeftBrace, Position::eof())),
|
||||
}
|
||||
|
||||
let pos = input.next().unwrap().1;
|
||||
};
|
||||
|
||||
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))
|
||||
}
|
||||
Some(&(ref token @ Token::Return, _)) | Some(&(ref token @ Token::Throw, _)) => {
|
||||
let is_return = match token {
|
||||
Token::Return => true,
|
||||
Token::Throw => false,
|
||||
_ => panic!(),
|
||||
let return_type = match token {
|
||||
Token::Return => ReturnType::Return,
|
||||
Token::Throw => ReturnType::Exception,
|
||||
_ => panic!("unexpected token!"),
|
||||
};
|
||||
|
||||
input.next();
|
||||
|
||||
match input.peek() {
|
||||
// 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
|
||||
None => Ok(Stmt::ReturnWithVal(None, is_return, Position::eof())),
|
||||
None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())),
|
||||
// return or throw with expression
|
||||
Some(&(_, pos)) => {
|
||||
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() {
|
||||
Some((_, tok_pos)) => tok_pos,
|
||||
_ => 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)?;
|
||||
|
||||
Ok(FnDef {
|
||||
name: name.into(),
|
||||
name,
|
||||
params,
|
||||
body,
|
||||
pos,
|
||||
@ -1846,12 +1945,23 @@ fn parse_top_level<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
optimize_ast: bool,
|
||||
) -> Result<AST, ParseError> {
|
||||
let mut statements = Vec::new();
|
||||
let mut functions = Vec::new();
|
||||
let mut statements = Vec::<Stmt>::new();
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let mut functions = Vec::<FnDef>::new();
|
||||
|
||||
while input.peek().is_some() {
|
||||
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)?),
|
||||
}
|
||||
|
||||
@ -1861,21 +1971,25 @@ fn parse_top_level<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(if optimize_ast {
|
||||
AST(
|
||||
optimize(statements),
|
||||
functions
|
||||
.into_iter()
|
||||
.map(|mut fn_def| {
|
||||
return Ok(AST(
|
||||
if optimize_ast {
|
||||
optimize(statements)
|
||||
} else {
|
||||
statements
|
||||
},
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
functions
|
||||
.into_iter()
|
||||
.map(|mut fn_def| {
|
||||
if optimize_ast {
|
||||
let pos = fn_def.body.position();
|
||||
let mut body = optimize(vec![fn_def.body]);
|
||||
fn_def.body = body.pop().unwrap();
|
||||
fn_def
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
AST(statements, functions)
|
||||
});
|
||||
fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos));
|
||||
}
|
||||
Arc::new(fn_def)
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn parse<'a>(
|
||||
|
@ -2,7 +2,8 @@
|
||||
|
||||
use crate::any::Dynamic;
|
||||
use crate::error::ParseError;
|
||||
use crate::parser::Position;
|
||||
use crate::parser::{Position, INT};
|
||||
|
||||
use std::{error::Error, fmt, path::PathBuf};
|
||||
|
||||
/// Evaluation result.
|
||||
@ -24,10 +25,10 @@ pub enum EvalAltResult {
|
||||
ErrorCharMismatch(Position),
|
||||
/// Array access out-of-bounds.
|
||||
/// 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.
|
||||
/// 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.
|
||||
ErrorIndexingType(String, Position),
|
||||
/// 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 => {
|
||||
"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::ErrorStringBounds(_, index, _) if *index < 0 => {
|
||||
"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::ErrorIfGuard(_) => "If guard expects boolean expression",
|
||||
Self::ErrorFor(_) => "For loop expects array or range",
|
||||
@ -128,6 +129,16 @@ impl fmt::Display for EvalAltResult {
|
||||
write!(f, "{} '{}': {}", desc, path.display(), err)
|
||||
}
|
||||
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!(
|
||||
f,
|
||||
"Function '{}' expects {} argument(s) but {} found ({})",
|
||||
@ -142,26 +153,30 @@ impl fmt::Display for EvalAltResult {
|
||||
Self::ErrorArrayBounds(_, index, pos) if *index < 0 => {
|
||||
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!(
|
||||
f,
|
||||
"Array index {} is out of bounds: only {} element{} in the array ({})",
|
||||
index,
|
||||
max,
|
||||
if *max > 1 { "s" } else { "" },
|
||||
pos
|
||||
"Array index {} is out of bounds: only {} elements in the array ({})",
|
||||
index, max, pos
|
||||
),
|
||||
Self::ErrorStringBounds(_, index, pos) if *index < 0 => {
|
||||
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!(
|
||||
f,
|
||||
"String index {} is out of bounds: only {} character{} in the string ({})",
|
||||
index,
|
||||
max,
|
||||
if *max > 1 { "s" } else { "" },
|
||||
pos
|
||||
"String index {} is out of bounds: only {} characters in the string ({})",
|
||||
index, max, 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 {
|
||||
pub fn position(&self) -> Position {
|
||||
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.
|
||||
|
||||
use crate::any::{Any, Dynamic};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// A type containing information about current scope.
|
||||
@ -15,9 +16,9 @@ use std::borrow::Cow;
|
||||
/// let mut engine = Engine::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(())
|
||||
/// # }
|
||||
/// ```
|
||||
@ -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
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, key: &str, index: usize) -> &mut T {
|
||||
self.get_mut(key, index)
|
||||
.downcast_mut::<T>()
|
||||
|
@ -1,11 +1,12 @@
|
||||
use rhai::{Engine, EvalAltResult, RegisterFn};
|
||||
#![cfg(not(feature = "no_index"))]
|
||||
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||
|
||||
#[test]
|
||||
fn test_arrays() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<i64>("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 x = [1, 2, 3]; x[1]")?, 2);
|
||||
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -14,7 +15,7 @@ fn test_arrays() -> Result<(), EvalAltResult> {
|
||||
fn test_array_with_structs() -> Result<(), EvalAltResult> {
|
||||
#[derive(Clone)]
|
||||
struct TestStruct {
|
||||
x: i64,
|
||||
x: INT,
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
@ -22,11 +23,11 @@ fn test_array_with_structs() -> Result<(), EvalAltResult> {
|
||||
self.x += 1000;
|
||||
}
|
||||
|
||||
fn get_x(&mut self) -> i64 {
|
||||
fn get_x(&mut self) -> INT {
|
||||
self.x
|
||||
}
|
||||
|
||||
fn set_x(&mut self, new_x: i64) {
|
||||
fn set_x(&mut self, new_x: INT) {
|
||||
self.x = new_x;
|
||||
}
|
||||
|
||||
@ -43,10 +44,10 @@ fn test_array_with_structs() -> Result<(), EvalAltResult> {
|
||||
engine.register_fn("update", TestStruct::update);
|
||||
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!(
|
||||
engine.eval::<i64>(
|
||||
engine.eval::<INT>(
|
||||
"let a = [new_ts()]; \
|
||||
a[0].x = 100; \
|
||||
a[0].update(); \
|
||||
|
@ -1,15 +1,15 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_binary_ops() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<i64>("10 % 4")?, 2);
|
||||
assert_eq!(engine.eval::<i64>("10 << 4")?, 160);
|
||||
assert_eq!(engine.eval::<i64>("10 >> 4")?, 0);
|
||||
assert_eq!(engine.eval::<i64>("10 & 4")?, 0);
|
||||
assert_eq!(engine.eval::<i64>("10 | 4")?, 14);
|
||||
assert_eq!(engine.eval::<i64>("10 ^ 4")?, 14);
|
||||
assert_eq!(engine.eval::<INT>("10 % 4")?, 2);
|
||||
assert_eq!(engine.eval::<INT>("10 << 4")?, 160);
|
||||
assert_eq!(engine.eval::<INT>("10 >> 4")?, 0);
|
||||
assert_eq!(engine.eval::<INT>("10 & 4")?, 0);
|
||||
assert_eq!(engine.eval::<INT>("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")?, false);
|
||||
|
@ -1,15 +1,15 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_left_shift() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
assert_eq!(engine.eval::<i64>("4 << 2")?, 16);
|
||||
assert_eq!(engine.eval::<INT>("4 << 2")?, 16);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_right_shift() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
assert_eq!(engine.eval::<i64>("9 >> 1")?, 4);
|
||||
assert_eq!(engine.eval::<INT>("9 >> 1")?, 4);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -39,10 +39,9 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
|
||||
assert_eq!(
|
||||
engine.eval::<bool>(
|
||||
r"
|
||||
fn this() { true }
|
||||
fn that() { 9/0 }
|
||||
let this = true;
|
||||
|
||||
this() || that();
|
||||
this || { throw; };
|
||||
"
|
||||
)?,
|
||||
true
|
||||
@ -51,10 +50,9 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
|
||||
assert_eq!(
|
||||
engine.eval::<bool>(
|
||||
r"
|
||||
fn this() { false }
|
||||
fn that() { 9/0 }
|
||||
let this = false;
|
||||
|
||||
this() && that();
|
||||
this && { throw; };
|
||||
"
|
||||
)?,
|
||||
false
|
||||
@ -64,41 +62,31 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_bool_op_no_short_circuit1() {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine
|
||||
.eval::<bool>(
|
||||
r"
|
||||
fn this() { false }
|
||||
fn that() { 9/0 }
|
||||
assert!(engine
|
||||
.eval::<bool>(
|
||||
r"
|
||||
let this = true;
|
||||
|
||||
this() | that();
|
||||
this | { throw; }
|
||||
"
|
||||
)
|
||||
.unwrap(),
|
||||
false
|
||||
);
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_bool_op_no_short_circuit2() {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine
|
||||
.eval::<bool>(
|
||||
r"
|
||||
fn this() { false }
|
||||
fn that() { 9/0 }
|
||||
assert!(engine
|
||||
.eval::<bool>(
|
||||
r"
|
||||
let this = false;
|
||||
|
||||
this() & that();
|
||||
this & { throw; }
|
||||
"
|
||||
)
|
||||
.unwrap(),
|
||||
false
|
||||
);
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
@ -6,11 +6,15 @@ fn test_chars() -> Result<(), EvalAltResult> {
|
||||
|
||||
assert_eq!(engine.eval::<char>("'y'")?, 'y');
|
||||
assert_eq!(engine.eval::<char>("'\\u2764'")?, '❤');
|
||||
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()
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
{
|
||||
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>("''").is_err());
|
||||
|
@ -1,14 +1,14 @@
|
||||
use rhai::Engine;
|
||||
use rhai::{Engine, INT};
|
||||
|
||||
#[test]
|
||||
fn test_comments() {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
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());
|
||||
|
||||
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());
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_or_equals() -> Result<(), EvalAltResult> {
|
||||
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 = false; x |= true; x")?, true);
|
||||
|
||||
@ -15,7 +15,7 @@ fn test_or_equals() -> Result<(), EvalAltResult> {
|
||||
fn test_and_equals() -> Result<(), EvalAltResult> {
|
||||
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 = false; x &= true; x")?, false);
|
||||
assert_eq!(engine.eval::<bool>("let x = true; x &= true; x")?, true);
|
||||
@ -26,41 +26,41 @@ fn test_and_equals() -> Result<(), EvalAltResult> {
|
||||
#[test]
|
||||
fn test_xor_equals() -> Result<(), EvalAltResult> {
|
||||
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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiply_equals() -> Result<(), EvalAltResult> {
|
||||
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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_divide_equals() -> Result<(), EvalAltResult> {
|
||||
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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_left_shift_equals() -> Result<(), EvalAltResult> {
|
||||
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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_right_shift_equals() -> Result<(), EvalAltResult> {
|
||||
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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_modulo_equals() -> Result<(), EvalAltResult> {
|
||||
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(())
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_decrement() -> Result<(), EvalAltResult> {
|
||||
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");
|
||||
|
||||
|
@ -1,14 +1,28 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
#![cfg(not(feature = "no_stdlib"))]
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_engine_call_fn() -> Result<(), EvalAltResult> {
|
||||
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(())
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
#![cfg(not(feature = "no_float"))]
|
||||
use rhai::{Engine, EvalAltResult, RegisterFn};
|
||||
|
||||
#[test]
|
||||
|
@ -1,4 +1,5 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
#![cfg(not(feature = "no_index"))]
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_for() -> Result<(), EvalAltResult> {
|
||||
@ -20,7 +21,7 @@ fn test_for() -> Result<(), EvalAltResult> {
|
||||
sum1 + sum2
|
||||
";
|
||||
|
||||
assert_eq!(engine.eval::<i64>(script)?, 30);
|
||||
assert_eq!(engine.eval::<INT>(script)?, 30);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
use rhai::{Engine, EvalAltResult, RegisterFn};
|
||||
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||
|
||||
#[test]
|
||||
fn test_get_set() -> Result<(), EvalAltResult> {
|
||||
#[derive(Clone)]
|
||||
struct TestStruct {
|
||||
x: i64,
|
||||
x: INT,
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
fn get_x(&mut self) -> i64 {
|
||||
fn get_x(&mut self) -> INT {
|
||||
self.x
|
||||
}
|
||||
|
||||
fn set_x(&mut self, new_x: i64) {
|
||||
fn set_x(&mut self, new_x: INT) {
|
||||
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_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(())
|
||||
}
|
||||
@ -37,15 +37,15 @@ fn test_get_set() -> Result<(), EvalAltResult> {
|
||||
fn test_big_get_set() -> Result<(), EvalAltResult> {
|
||||
#[derive(Clone)]
|
||||
struct TestChild {
|
||||
x: i64,
|
||||
x: INT,
|
||||
}
|
||||
|
||||
impl TestChild {
|
||||
fn get_x(&mut self) -> i64 {
|
||||
fn get_x(&mut self) -> INT {
|
||||
self.x
|
||||
}
|
||||
|
||||
fn set_x(&mut self, new_x: i64) {
|
||||
fn set_x(&mut self, new_x: INT) {
|
||||
self.x = new_x;
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ fn test_big_get_set() -> Result<(), EvalAltResult> {
|
||||
engine.register_fn("new_tp", TestParent::new);
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
|
@ -1,18 +1,18 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_if() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<i64>("if true { 55 }")?, 55);
|
||||
assert_eq!(engine.eval::<i64>("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 }")?, 55);
|
||||
assert_eq!(engine.eval::<INT>("if false { 55 } else { 44 }")?, 44);
|
||||
assert_eq!(engine.eval::<INT>("if true { 55 } else { 44 }")?, 55);
|
||||
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
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<i64>(
|
||||
engine.eval::<INT>(
|
||||
r"
|
||||
if false { 55 }
|
||||
else if false { 33 }
|
||||
|
@ -1,10 +1,10 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_increment() -> Result<(), EvalAltResult> {
|
||||
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!(
|
||||
engine.eval::<String>("let s = \"test\"; s += \"ing\"; s")?,
|
||||
"testing".to_string()
|
||||
|
@ -1,11 +1,13 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_internal_fn() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<i64>("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 addme(a, b) { a+b } addme(3, 4)")?, 7);
|
||||
assert_eq!(engine.eval::<INT>("fn bob() { return 4; 5 } bob()")?, 4);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -15,7 +17,7 @@ fn test_big_internal_fn() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<i64>(
|
||||
engine.eval::<INT>(
|
||||
r"
|
||||
fn mathme(a, b, c, d, e, f) {
|
||||
a - b * c + d * e - f
|
||||
@ -28,3 +30,25 @@ fn test_big_internal_fn() -> Result<(), EvalAltResult> {
|
||||
|
||||
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]
|
||||
fn test_math() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<i64>("1 + 2")?, 3);
|
||||
assert_eq!(engine.eval::<i64>("1 - 2")?, -1);
|
||||
assert_eq!(engine.eval::<i64>("2 * 3")?, 6);
|
||||
assert_eq!(engine.eval::<i64>("1 / 2")?, 0);
|
||||
assert_eq!(engine.eval::<i64>("3 % 2")?, 1);
|
||||
assert_eq!(engine.eval::<INT>("1 + 2")?, 3);
|
||||
assert_eq!(engine.eval::<INT>("1 - 2")?, -1);
|
||||
assert_eq!(engine.eval::<INT>("2 * 3")?, 6);
|
||||
assert_eq!(engine.eval::<INT>("1 / 2")?, 0);
|
||||
assert_eq!(engine.eval::<INT>("3 % 2")?, 1);
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
assert_eq!(
|
||||
engine.eval::<i64>("(-9223372036854775807).abs()")?,
|
||||
engine.eval::<INT>("(-9223372036854775807).abs()")?,
|
||||
9223372036854775807
|
||||
);
|
||||
|
||||
#[cfg(feature = "only_i32")]
|
||||
assert_eq!(engine.eval::<INT>("(-2147483647).abs()")?, 2147483647);
|
||||
|
||||
// Overflow/underflow/division-by-zero errors
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
match engine.eval::<i64>("9223372036854775807 + 1") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return overflow error: {:?}", r),
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
{
|
||||
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(_, _)) => (),
|
||||
r => panic!("should return underflow error: {:?}", r),
|
||||
}
|
||||
match engine.eval::<i64>("9223372036854775807 * 9223372036854775807") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return overflow error: {:?}", r),
|
||||
}
|
||||
match engine.eval::<i64>("9223372036854775807 / 0") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return division by zero error: {:?}", r),
|
||||
}
|
||||
match engine.eval::<i64>("9223372036854775807 % 0") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return division by zero error: {:?}", r),
|
||||
|
||||
#[cfg(feature = "only_i32")]
|
||||
{
|
||||
match engine.eval::<INT>("2147483647 + 1") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return overflow error: {:?}", r),
|
||||
}
|
||||
match engine.eval::<INT>("-2147483648 - 1") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return underflow error: {:?}", r),
|
||||
}
|
||||
match engine.eval::<INT>("2147483647 * 2147483647") {
|
||||
Err(EvalAltResult::ErrorArithmetic(_, _)) => (),
|
||||
r => panic!("should return overflow 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]
|
||||
fn test_method_call() -> Result<(), EvalAltResult> {
|
||||
#[derive(Clone)]
|
||||
struct TestStruct {
|
||||
x: i64,
|
||||
x: INT,
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
|
@ -1,10 +1,11 @@
|
||||
use rhai::{Engine, EvalAltResult, RegisterFn};
|
||||
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
fn test_mismatched_op() {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
let r = engine.eval::<i64>("60 + \"hello\"");
|
||||
let r = engine.eval::<INT>("60 + \"hello\"");
|
||||
|
||||
match r {
|
||||
Err(EvalAltResult::ErrorMismatchOutputType(err, _)) if err == "string" => (),
|
||||
@ -16,7 +17,7 @@ fn test_mismatched_op() {
|
||||
fn test_mismatched_op_custom_type() {
|
||||
#[derive(Clone)]
|
||||
struct TestStruct {
|
||||
x: i64,
|
||||
x: INT,
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
@ -26,17 +27,18 @@ fn test_mismatched_op_custom_type() {
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.register_type::<TestStruct>();
|
||||
engine.register_type_with_name::<TestStruct>("TestStruct");
|
||||
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 {
|
||||
Err(EvalAltResult::ErrorFunctionNotFound(err, _))
|
||||
if err == "+ (i64, mismatched_op::test_mismatched_op_custom_type::TestStruct)" =>
|
||||
{
|
||||
()
|
||||
}
|
||||
#[cfg(feature = "only_i32")]
|
||||
Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "+ (i32, TestStruct)" => (),
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
Err(EvalAltResult::ErrorFunctionNotFound(err, _)) if err == "+ (i64, TestStruct)" => (),
|
||||
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ fn test_not() -> Result<(), EvalAltResult> {
|
||||
false
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
assert_eq!(engine.eval::<bool>("fn not(x) { !x } not(false)")?, 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]
|
||||
fn test_number_literal() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<i64>("65")?, 65);
|
||||
assert_eq!(engine.eval::<INT>("65")?, 65);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -13,8 +13,8 @@ fn test_number_literal() -> Result<(), EvalAltResult> {
|
||||
fn test_hex_literal() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<i64>("let x = 0xf; x")?, 15);
|
||||
assert_eq!(engine.eval::<i64>("let x = 0xff; x")?, 255);
|
||||
assert_eq!(engine.eval::<INT>("let x = 0xf; x")?, 15);
|
||||
assert_eq!(engine.eval::<INT>("let x = 0xff; x")?, 255);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -23,8 +23,8 @@ fn test_hex_literal() -> Result<(), EvalAltResult> {
|
||||
fn test_octal_literal() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<i64>("let x = 0o77; x")?, 63);
|
||||
assert_eq!(engine.eval::<i64>("let x = 0o1234; x")?, 668);
|
||||
assert_eq!(engine.eval::<INT>("let x = 0o77; x")?, 63);
|
||||
assert_eq!(engine.eval::<INT>("let x = 0o1234; x")?, 668);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -33,9 +33,9 @@ fn test_octal_literal() -> Result<(), EvalAltResult> {
|
||||
fn test_binary_literal() -> Result<(), EvalAltResult> {
|
||||
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!(
|
||||
engine.eval::<i64>("let x = 0b0011_1100_1010_0101; x")?,
|
||||
engine.eval::<INT>("let x = 0b0011_1100_1010_0101; x")?,
|
||||
15525
|
||||
);
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_ops() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<i64>("60 + 5")?, 65);
|
||||
assert_eq!(engine.eval::<i64>("(1 + 2) * (6 - 4) / 2")?, 3);
|
||||
assert_eq!(engine.eval::<INT>("60 + 5")?, 65);
|
||||
assert_eq!(engine.eval::<INT>("(1 + 2) * (6 - 4) / 2")?, 3);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -15,7 +15,7 @@ fn test_op_prec() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
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,16 +1,23 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Engine, EvalAltResult, FLOAT, INT};
|
||||
|
||||
#[test]
|
||||
fn test_power_of() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<i64>("2 ~ 3")?, 8);
|
||||
assert_eq!(engine.eval::<i64>("(-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);
|
||||
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!(engine.eval::<i64>("4~3")?, 64);
|
||||
assert_eq!(engine.eval::<INT>("2 ~ 3")?, 8);
|
||||
assert_eq!(engine.eval::<INT>("(-2 ~ 3)")?, -8);
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
assert_eq!(
|
||||
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(())
|
||||
}
|
||||
@ -19,16 +26,29 @@ fn test_power_of() -> Result<(), EvalAltResult> {
|
||||
fn test_power_of_equals() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<i64>("let x = 2; x ~= 3; x")?, 8);
|
||||
assert_eq!(engine.eval::<i64>("let x = -2; x ~= 3; x")?, -8);
|
||||
assert_eq!(
|
||||
engine.eval::<f64>("let x = 2.2; x ~= 3.3; x")?,
|
||||
13.489468760533386_f64
|
||||
);
|
||||
assert_eq!(engine.eval::<f64>("let x = 2.0; x ~= -2.0; x")?, 0.25_f64);
|
||||
assert_eq!(engine.eval::<f64>("let x = -2.0; x ~= -2.0; x")?, 0.25_f64);
|
||||
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::<INT>("let x = 2; x ~= 3; x")?, 8);
|
||||
assert_eq!(engine.eval::<INT>("let x = -2; x ~= 3; x")?, -8);
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
assert_eq!(
|
||||
engine.eval::<FLOAT>("let x = 2.2; x ~= 3.3; x")?,
|
||||
13.489468760533386 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.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(())
|
||||
}
|
||||
|
@ -12,10 +12,20 @@ fn test_string() -> Result<(), EvalAltResult> {
|
||||
engine.eval::<String>(r#""Test string: \x58""#)?,
|
||||
"Test string: X".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<String>(r#""foo" + "bar""#)?,
|
||||
"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!(
|
||||
engine.eval::<String>(r#""foo" + 123.4556"#)?,
|
||||
"foo123.4556".to_string()
|
||||
|
@ -1,16 +1,16 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_throw() {
|
||||
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"),
|
||||
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "hello" => (),
|
||||
Err(err) => panic!("wrong error: {}", err),
|
||||
}
|
||||
|
||||
match engine.eval::<i64>(r#"throw;"#) {
|
||||
match engine.eval::<INT>(r#"throw;"#) {
|
||||
Ok(_) => panic!("not an error"),
|
||||
Err(EvalAltResult::ErrorRuntime(s, _)) if s == "" => (),
|
||||
Err(err) => panic!("wrong error: {}", err),
|
||||
|
@ -4,14 +4,29 @@ use rhai::{Engine, EvalAltResult};
|
||||
fn test_type_of() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
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");
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
assert_eq!(
|
||||
engine.eval::<String>(r#"type_of([1.0, 2, "hello"])"#)?,
|
||||
"array"
|
||||
);
|
||||
|
||||
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");
|
||||
|
||||
#[cfg(feature = "only_i32")]
|
||||
assert_eq!(engine.eval::<String>("let x = 123; x.type_of()")?, "i32");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
// TODO also add test case for unary after compound
|
||||
@ -6,12 +6,12 @@ use rhai::{Engine, EvalAltResult};
|
||||
fn test_unary_after_binary() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<i64>("10 % +4")?, 2);
|
||||
assert_eq!(engine.eval::<i64>("10 << +4")?, 160);
|
||||
assert_eq!(engine.eval::<i64>("10 >> +4")?, 0);
|
||||
assert_eq!(engine.eval::<i64>("10 & +4")?, 0);
|
||||
assert_eq!(engine.eval::<i64>("10 | +4")?, 14);
|
||||
assert_eq!(engine.eval::<i64>("10 ^ +4")?, 14);
|
||||
assert_eq!(engine.eval::<INT>("10 % +4")?, 2);
|
||||
assert_eq!(engine.eval::<INT>("10 << +4")?, 160);
|
||||
assert_eq!(engine.eval::<INT>("10 >> +4")?, 0);
|
||||
assert_eq!(engine.eval::<INT>("10 & +4")?, 0);
|
||||
assert_eq!(engine.eval::<INT>("10 | +4")?, 14);
|
||||
assert_eq!(engine.eval::<INT>("10 ^ +4")?, 14);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_unary_minus() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(engine.eval::<i64>("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);
|
||||
assert_eq!(engine.eval::<INT>("let x = -5; x")?, -5);
|
||||
|
||||
#[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(())
|
||||
}
|
||||
|
@ -1,19 +1,16 @@
|
||||
use rhai::{Engine, EvalAltResult, Scope};
|
||||
use rhai::{Engine, EvalAltResult, Scope, INT};
|
||||
|
||||
#[test]
|
||||
fn test_var_scope() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
engine.eval_with_scope::<()>(&mut scope, false, "let x = 4 + 5")?;
|
||||
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, false, "x")?, 9);
|
||||
engine.eval_with_scope::<()>(&mut scope, false, "x = x + 1; x = x + 2;")?;
|
||||
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, false, "x")?, 12);
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<()>(&mut scope, false, "{let x = 3}")?,
|
||||
()
|
||||
);
|
||||
assert_eq!(engine.eval_with_scope::<i64>(&mut scope, false, "x")?, 12);
|
||||
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;
|
||||
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 9);
|
||||
engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?;
|
||||
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 12);
|
||||
assert_eq!(engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?, ());
|
||||
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 12);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -26,23 +23,31 @@ fn test_scope_eval() -> Result<(), EvalAltResult> {
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// 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.
|
||||
scope.push("y", 42_i64);
|
||||
scope.push("z", 999_i64);
|
||||
scope.push("y", 42 as INT);
|
||||
scope.push("z", 999 as INT);
|
||||
|
||||
// First invocation
|
||||
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?");
|
||||
|
||||
// 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
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_while() -> Result<(), EvalAltResult> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<i64>(
|
||||
engine.eval::<INT>(
|
||||
"let x = 0; while x < 10 { x = x + 1; if x > 5 { \
|
||||
break } } x",
|
||||
)?,
|
||||
|
Loading…
Reference in New Issue
Block a user