Merge pull request #106 from schungx/master

Add opt-in features to turn off/disable language features.
This commit is contained in:
Stephen Chung 2020-03-13 18:36:43 +08:00 committed by GitHub
commit f59dbfc4e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 1976 additions and 1113 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
target/
Cargo.lock
.vscode/
.vscode/
.cargo/

View File

@ -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
View File

@ -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

View File

@ -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!("");

View File

@ -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);

View File

@ -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
View 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.");

View File

@ -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(())

File diff suppressed because it is too large Load Diff

View File

@ -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);

View File

@ -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())
}
}

View File

@ -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)
}
}
};

View File

@ -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;

View File

@ -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
}

View File

@ -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(&params_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>(

View File

@ -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())
}
}

View File

@ -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>()

View File

@ -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(); \

View File

@ -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);

View File

@ -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(())
}

View File

@ -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());
}

View File

@ -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());

View File

@ -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());
}

View File

@ -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(())
}

View File

@ -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");

View File

@ -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(())
}

View File

@ -1,3 +1,4 @@
#![cfg(not(feature = "no_float"))]
use rhai::{Engine, EvalAltResult, RegisterFn};
#[test]

View File

@ -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(())
}

View File

@ -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
);

View File

@ -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 }

View File

@ -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()

View File

@ -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(())
}

View File

@ -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),
}
}
}

View File

@ -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 {

View File

@ -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!(),
}
}

View File

@ -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'

View File

@ -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
);

View File

@ -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
);

View File

@ -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(())
}

View File

@ -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()

View File

@ -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),

View File

@ -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(())
}

View File

@ -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(())
}

View File

@ -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(())
}

View File

@ -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(())
}

View File

@ -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",
)?,