diff --git a/.gitignore b/.gitignore index 1ec7ed7e..90e6863e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target/ Cargo.lock -.vscode/ \ No newline at end of file +.vscode/ +.cargo/ diff --git a/Cargo.toml b/Cargo.toml index fe543553..a5177aa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,18 @@ include = [ num-traits = "*" [features] -debug_msgs = [] -no_stdlib = [] -unchecked = [] +#default = ["no_function", "no_index", "no_float", "only_i32", "no_stdlib", "unchecked"] +default = [] +debug_msgs = [] # print debug messages on function registrations and calls +unchecked = [] # unchecked arithmetic +no_stdlib = [] # no standard library of utility functions +no_index = [] # no arrays and indexing +no_float = [] # no floating-point +no_function = [] # no script-defined functions +only_i32 = [] # set INT=i32 (useful for 32-bit systems) +only_i64 = [] # set INT=i64 (default) and disable support for all other integer types + +[profile.release] +lto = "fat" +codegen-units = 1 +#opt-level = "z" # optimize for size diff --git a/README.md b/README.md index 16e40b54..57f3c463 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Rhai's current feature set: * Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app) * Easy-to-use language similar to JS+Rust * Support for overloaded functions -* Very few additional dependencies (right now only `num-traits` to do checked arithmetic operations) +* Very few additional dependencies (right now only [`num-traits`] to do checked arithmetic operations) **Note:** Currently, the version is 0.10.2, so the language and API's may change before they stabilize. @@ -38,19 +38,27 @@ Beware that in order to use pre-releases (alpha and beta) you need to specify th Optional features ----------------- -| Feature | Description | -| ------------ | ----------------------------------------------------------------------------------------------------------------------- | -| `debug_msgs` | Print debug messages to stdout (using `println!`) related to function registrations and function calls. | -| `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. | -| `unchecked` | Exclude arithmetic checking in the standard library. Beware that a bad script may panic the entire system! | +| Feature | Description | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `debug_msgs` | Print debug messages to stdout related to function registrations and calls. | +| `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. | +| `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | +| `no_function` | Disable script-defined functions if you don't need them. | +| `no_index` | Disable arrays and indexing features if you don't need them. | +| `no_float` | Disable floating-point numbers and math if you don't need them. | +| `only_i32` | Set the system integer type to `i32` and disable all other integer types. | +| `only_i64` | Set the system integer type to `i64` and disable all other integer types. | + +By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here for you to opt-**out** of certain functionalities that you do not need. +Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. Related ------- Other cool projects to check out: -* [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin. -* You can also check out the list of [scripting languages for Rust](https://github.com/rust-unofficial/awesome-rust#scripting) on [awesome-rust](https://github.com/rust-unofficial/awesome-rust) +* [ChaiScript] - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin. +* You can also check out the list of [scripting languages for Rust] on [awesome-rust]. Examples -------- @@ -81,23 +89,27 @@ Example Scripts There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` folder: -| Script | Description | -| --------------------- | ------------------------------------------------------------- | -| `array.rhai` | arrays in Rhai | -| `assignment.rhai` | variable declarations | -| `comments.rhai` | just comments | -| `for1.rhai` | for loops | -| `function_decl1.rhai` | a function without parameters | -| `function_decl2.rhai` | a function with two parameters | -| `function_decl3.rhai` | a function with many parameters | -| `if1.rhai` | if example | -| `loop.rhai` | endless loop in Rhai, this example emulates a do..while cycle | -| `op1.rhai` | just a simple addition | -| `op2.rhai` | simple addition and multiplication | -| `op3.rhai` | change evaluation order with parenthesis | -| `speed_test.rhai` | a simple program to measure the speed of Rhai's interpreter | -| `string.rhai` | string operations | -| `while.rhai` | while loop | +| Language feature scripts | Description | +| ------------------------ | ------------------------------------------------------------- | +| `array.rhai` | arrays in Rhai | +| `assignment.rhai` | variable declarations | +| `comments.rhai` | just comments | +| `for1.rhai` | for loops | +| `function_decl1.rhai` | a function without parameters | +| `function_decl2.rhai` | a function with two parameters | +| `function_decl3.rhai` | a function with many parameters | +| `if1.rhai` | if example | +| `loop.rhai` | endless loop in Rhai, this example emulates a do..while cycle | +| `op1.rhai` | just a simple addition | +| `op2.rhai` | simple addition and multiplication | +| `op3.rhai` | change evaluation order with parenthesis | +| `string.rhai` | string operations | +| `while.rhai` | while loop | + +| Example scripts | Description | +| ----------------- | ----------------------------------------------------------------- | +| `speed_test.rhai` | a simple program to measure the speed of Rhai's interpreter | +| `primes.rhai` | use Sieve of Eratosthenes to find all primes smaller than a limit | To run the scripts, either make a tiny program or use of the `rhai_runner` example: @@ -120,6 +132,8 @@ fn main() -> Result<(), EvalAltResult> let result = engine.eval::("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`,
`u32`, `i32` (default for [`only_i32`]),
`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`),
`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(x: &mut T) -> () { - println!("{}", x) +fn show_it(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 { 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::(&mut scope, false, "x")?; + let result = engine.eval_with_scope::(&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 = 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 diff --git a/examples/repl.rs b/examples/repl.rs index a4221614..381bc265 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -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 = 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!(""); diff --git a/examples/reuse_scope.rs b/examples/reuse_scope.rs index c83d0a72..a6509ffe 100644 --- a/examples/reuse_scope.rs +++ b/examples/reuse_scope.rs @@ -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::(&mut scope, false, "x")?; + let result = engine.eval_with_scope::(&mut scope, "x")?; println!("result: {}", result); diff --git a/examples/rhai_runner.rs b/examples/rhai_runner.rs index d535f078..2955c76e 100644 --- a/examples/rhai_runner.rs +++ b/examples/rhai_runner.rs @@ -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())); diff --git a/scripts/primes.rhai b/scripts/primes.rhai new file mode 100644 index 00000000..fae57bbb --- /dev/null +++ b/scripts/primes.rhai @@ -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."); + diff --git a/src/api.rs b/src/api.rs index d3fc6402..c12d86bf 100644 --- a/src/api.rs +++ b/src/api.rs @@ -2,10 +2,10 @@ use crate::any::{Any, AnyExt, Dynamic}; use crate::call::FuncArgs; -use crate::engine::{Engine, FnAny, FnIntExt, FnSpec}; +use crate::engine::{Engine, FnAny, FnSpec}; use crate::error::ParseError; use crate::fn_register::RegisterFn; -use crate::parser::{lex, parse, Position, AST}; +use crate::parser::{lex, parse, FnDef, Position, AST}; use crate::result::EvalAltResult; use crate::scope::Scope; use std::{ @@ -42,7 +42,7 @@ impl<'e> Engine<'e> { args, }; - self.ext_functions.insert(spec, Arc::new(FnIntExt::Ext(f))); + self.ext_functions.insert(spec, f); } /// Register a custom type for use with the `Engine`. @@ -64,7 +64,7 @@ impl<'e> Engine<'e> { where F: Fn(&Dynamic) -> Box> + 'static, { - self.type_iterators.insert(TypeId::of::(), Arc::new(f)); + self.type_iterators.insert(TypeId::of::(), 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 { - 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 { + 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 { - 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(&mut self, path: PathBuf) -> Result { - 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::(&contents)) + Self::read_file(path).and_then(|contents| self.eval::(&contents)) } /// Evaluate a string. pub fn eval(&mut self, input: &str) -> Result { 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( &mut self, scope: &mut Scope, - retain_functions: bool, input: &str, ) -> Result { 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(&mut self, ast: &AST) -> Result { 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( &mut self, scope: &mut Scope, - retain_functions: bool, ast: &AST, ) -> Result { - let AST(statements, functions) = ast; + fn eval_ast_internal( + engine: &mut Engine, + scope: &mut Scope, + ast: &AST, + ) -> Result { + 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::().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>, + ) { + 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( &mut self, name: &str, - ast: &AST, args: A, ) -> Result { - 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, + ) -> Result { + 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(()) diff --git a/src/builtin.rs b/src/builtin.rs index 6cce507e..da139f60 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -2,27 +2,23 @@ //! _standard library_ of utility functions. use crate::any::Any; -use crate::engine::{Array, Engine}; -use crate::fn_register::RegisterFn; -use std::{ - fmt::{Debug, Display}, - i32, i64, - ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Sub}, - u32, +#[cfg(not(feature = "no_index"))] +use crate::engine::Array; +use crate::engine::Engine; +use crate::fn_register::{RegisterFn, RegisterResultFn}; +use crate::parser::{Position, INT}; +use crate::result::EvalAltResult; +use crate::FLOAT; + +use num_traits::{ + identities::Zero, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, + CheckedShr, CheckedSub, }; -#[cfg(feature = "unchecked")] -use std::ops::{Shl, Shr}; - -#[cfg(not(feature = "unchecked"))] -use crate::{parser::Position, result::EvalAltResult, RegisterResultFn}; - -#[cfg(not(feature = "unchecked"))] -use std::convert::TryFrom; - -#[cfg(not(feature = "unchecked"))] -use num_traits::{ - CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, +use std::{ + fmt::{Debug, Display}, + ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}, + {i32, i64, u32}, }; macro_rules! reg_op { @@ -51,65 +47,6 @@ macro_rules! reg_op_result1 { ) } -macro_rules! reg_un { - ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( - $( - $self.register_fn($x, $op as fn(x: $y)->$y); - )* - ) -} - -#[cfg(not(feature = "unchecked"))] -macro_rules! reg_un_result { - ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( - $( - $self.register_result_fn($x, $op as fn(x: $y)->Result<$y,EvalAltResult>); - )* - ) -} -macro_rules! reg_cmp { - ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( - $( - $self.register_fn($x, $op as fn(x: $y, y: $y)->bool); - )* - ) -} - -macro_rules! reg_func1 { - ($self:expr, $x:expr, $op:expr, $r:ty, $( $y:ty ),*) => ( - $( - $self.register_fn($x, $op as fn(x: $y)->$r); - )* - ) -} - -#[cfg(not(feature = "no_stdlib"))] -macro_rules! reg_func2x { - ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( - $( - $self.register_fn($x, $op as fn(x: $v, y: $y)->$r); - )* - ) -} - -#[cfg(not(feature = "no_stdlib"))] -macro_rules! reg_func2y { - ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( - $( - $self.register_fn($x, $op as fn(y: $y, x: $v)->$r); - )* - ) -} - -#[cfg(not(feature = "no_stdlib"))] -macro_rules! reg_func3 { - ($self:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => ( - $( - $self.register_fn($x, $op as fn(x: $v, y: $w, z: $y)->$r); - )* - ) -} - impl Engine<'_> { /// Register the core built-in library. pub(crate) fn register_core_lib(&mut self) { @@ -143,12 +80,9 @@ impl Engine<'_> { #[cfg(not(feature = "unchecked"))] fn div(x: T, y: T) -> Result where - T: Display + CheckedDiv + PartialEq + TryFrom, + T: Display + CheckedDiv + PartialEq + Zero, { - if y == >::try_from(0) - .map_err(|_| ()) - .expect("zero should always succeed") - { + if y == T::zero() { return Err(EvalAltResult::ErrorArithmetic( format!("Division by zero: {} / {}", x, y), Position::none(), @@ -172,8 +106,10 @@ impl Engine<'_> { }) } #[cfg(not(feature = "unchecked"))] - fn abs>(x: T) -> Result { - if x >= 0.into() { + fn abs(x: T) -> Result { + // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics + // when the number is ::MIN instead of returning ::MIN itself. + if x >= ::zero() { Ok(x) } else { x.checked_neg().ok_or_else(|| { @@ -184,29 +120,36 @@ impl Engine<'_> { }) } } + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn add_u(x: T, y: T) -> ::Output { x + y } + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn sub_u(x: T, y: T) -> ::Output { x - y } + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn mul_u(x: T, y: T) -> ::Output { x * y } + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn div_u(x: T, y: T) -> ::Output { x / y } + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn neg_u(x: T) -> ::Output { -x } - fn abs_u>(x: T) -> T + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] + fn abs_u(x: T) -> ::Output where - ::Output: Into, + T: Neg + PartialOrd + Default + Into<::Output>, { - if x < 0.into() { - (-x).into() + // Numbers should default to zero + if x < Default::default() { + -x } else { - x + x.into() } } fn lt(x: T, y: T) -> bool { @@ -246,7 +189,7 @@ impl Engine<'_> { x ^ y } #[cfg(not(feature = "unchecked"))] - fn shl(x: T, y: i64) -> Result { + fn shl(x: T, y: INT) -> Result { if y < 0 { return Err(EvalAltResult::ErrorArithmetic( format!("Left-shift by a negative number: {} << {}", x, y), @@ -262,7 +205,7 @@ impl Engine<'_> { }) } #[cfg(not(feature = "unchecked"))] - fn shr(x: T, y: i64) -> Result { + fn shr(x: T, y: INT) -> Result { if y < 0 { return Err(EvalAltResult::ErrorArithmetic( format!("Right-shift by a negative number: {} >> {}", x, y), @@ -294,35 +237,63 @@ impl Engine<'_> { ) }) } + #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn modulo_u(x: T, y: T) -> ::Output { x % y } #[cfg(not(feature = "unchecked"))] - fn pow_i64_i64_u(x: i64, y: i64) -> Result { - if y > (u32::MAX as i64) { - return Err(EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), - Position::none(), - )); + fn pow_i_i_u(x: INT, y: INT) -> Result { + #[cfg(not(feature = "only_i32"))] + { + if y > (u32::MAX as INT) { + Err(EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + )) + } else if y < 0 { + Err(EvalAltResult::ErrorArithmetic( + format!("Power underflow: {} ~ {}", x, y), + Position::none(), + )) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + ) + }) + } } - x.checked_pow(y as u32).ok_or_else(|| { - EvalAltResult::ErrorArithmetic( - format!("Power overflow: {} ~ {}", x, y), - Position::none(), - ) - }) + #[cfg(feature = "only_i32")] + { + if y < 0 { + Err(EvalAltResult::ErrorArithmetic( + format!("Power underflow: {} ~ {}", x, y), + Position::none(), + )) + } else { + x.checked_pow(y as u32).ok_or_else(|| { + EvalAltResult::ErrorArithmetic( + format!("Power overflow: {} ~ {}", x, y), + Position::none(), + ) + }) + } + } } #[cfg(feature = "unchecked")] - fn pow_i64_i64(x: i64, y: i64) -> i64 { + fn pow_i_i(x: INT, y: INT) -> INT { x.pow(y as u32) } - fn pow_f64_f64(x: f64, y: f64) -> f64 { + #[cfg(not(feature = "no_float"))] + fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT { x.powf(y) } #[cfg(not(feature = "unchecked"))] - fn pow_f64_i64_u(x: f64, y: i64) -> Result { - if y > (i32::MAX as i64) { + #[cfg(not(feature = "no_float"))] + fn pow_f_i_u(x: FLOAT, y: INT) -> Result { + if y > (i32::MAX as INT) { return Err(EvalAltResult::ErrorArithmetic( format!("Power overflow: {} ~ {}", x, y), Position::none(), @@ -332,257 +303,486 @@ impl Engine<'_> { Ok(x.powi(y as i32)) } #[cfg(feature = "unchecked")] - fn pow_f64_i64(x: f64, y: i64) -> f64 { + #[cfg(not(feature = "no_float"))] + fn pow_f_i(x: FLOAT, y: INT) -> FLOAT { x.powi(y as i32) } #[cfg(not(feature = "unchecked"))] { - reg_op_result!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result!(self, "+", add, INT); + reg_op_result!(self, "-", sub, INT); + reg_op_result!(self, "*", mul, INT); + reg_op_result!(self, "/", div, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op_result!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64); + } } #[cfg(feature = "unchecked")] { - reg_op!(self, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "+", add_u, INT); + reg_op!(self, "-", sub_u, INT); + reg_op!(self, "*", mul_u, INT); + reg_op!(self, "/", div_u, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op!(self, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64); + } } - reg_op!(self, "+", add_u, f32, f64); - reg_op!(self, "-", sub_u, f32, f64); - reg_op!(self, "*", mul_u, f32, f64); - reg_op!(self, "/", div_u, f32, f64); + #[cfg(not(feature = "no_float"))] + { + reg_op!(self, "+", add_u, f32, f64); + reg_op!(self, "-", sub_u, f32, f64); + reg_op!(self, "*", mul_u, f32, f64); + reg_op!(self, "/", div_u, f32, f64); + } - reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char); - reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char); - reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char); - reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, String, char); - reg_cmp!( - self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, bool, f32, f64, String, char - ); - reg_cmp!( - self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, bool, f32, f64, String, char - ); + { + macro_rules! reg_cmp { + ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $y, y: $y)->bool); + )* + ) + } + + reg_cmp!(self, "<", lt, INT, String, char); + reg_cmp!(self, "<=", lte, INT, String, char); + reg_cmp!(self, ">", gt, INT, String, char); + reg_cmp!(self, ">=", gte, INT, String, char); + reg_cmp!(self, "==", eq, INT, String, char, bool); + reg_cmp!(self, "!=", ne, INT, String, char, bool); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64); + } + + #[cfg(not(feature = "no_float"))] + { + reg_cmp!(self, "<", lt, f32, f64); + reg_cmp!(self, "<=", lte, f32, f64); + reg_cmp!(self, ">", gt, f32, f64); + reg_cmp!(self, ">=", gte, f32, f64); + reg_cmp!(self, "==", eq, f32, f64); + reg_cmp!(self, "!=", ne, f32, f64); + } + } //reg_op!(self, "||", or, bool); //reg_op!(self, "&&", and, bool); - reg_op!(self, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "|", or, bool); - reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "&", and, bool); - reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64); - #[cfg(not(feature = "unchecked"))] + reg_op!(self, "|", binary_or, INT); + reg_op!(self, "&", binary_and, INT); + reg_op!(self, "^", binary_xor, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] { - reg_op_result1!(self, "<<", shl, i64, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result1!(self, ">>", shr, i64, i8, u8, i16, u16); - reg_op_result1!(self, ">>", shr, i64, i32, i64, u32, u64); - reg_op_result!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64); - } - - #[cfg(feature = "unchecked")] - { - reg_op!(self, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, ">>", shr_u, i64, i8, u8, i16, u16); - reg_op!(self, ">>", shr_u, i64, i32, i64, u32, u64); - reg_op!(self, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64); - } - - reg_op!(self, "%", modulo_u, f32, f64); - - self.register_fn("~", pow_f64_f64); - - #[cfg(not(feature = "unchecked"))] - { - self.register_result_fn("~", pow_i64_i64_u); - self.register_result_fn("~", pow_f64_i64_u); - } - - #[cfg(feature = "unchecked")] - { - self.register_fn("~", pow_i64_i64); - self.register_fn("~", pow_f64_i64); + reg_op!(self, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64); } #[cfg(not(feature = "unchecked"))] { - reg_un_result!(self, "-", neg, i8, i16, i32, i64); - reg_un_result!(self, "abs", abs, i8, i16, i32, i64); + reg_op_result1!(self, "<<", shl, INT, INT); + reg_op_result1!(self, ">>", shr, INT, INT); + reg_op_result!(self, "%", modulo, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op_result1!(self, "<<", shl, i64, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result1!(self, ">>", shr, i64, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64); + } } #[cfg(feature = "unchecked")] { - reg_un!(self, "-", neg_u, i8, i16, i32, i64); - reg_un!(self, "abs", abs_u, i8, i16, i32, i64); + reg_op!(self, "<<", shl_u, INT, INT); + reg_op!(self, ">>", shr_u, INT, INT); + reg_op!(self, "%", modulo_u, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op!(self, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64); + } } - reg_un!(self, "-", neg_u, f32, f64); - reg_un!(self, "abs", abs_u, f32, f64); - reg_un!(self, "!", not, bool); + #[cfg(not(feature = "no_float"))] + { + reg_op!(self, "%", modulo_u, f32, f64); + self.register_fn("~", pow_f_f); + } + + #[cfg(not(feature = "unchecked"))] + { + self.register_result_fn("~", pow_i_i_u); + + #[cfg(not(feature = "no_float"))] + self.register_result_fn("~", pow_f_i_u); + } + + #[cfg(feature = "unchecked")] + { + self.register_fn("~", pow_i_i); + + #[cfg(not(feature = "no_float"))] + self.register_fn("~", pow_f_i); + } + + { + macro_rules! reg_un { + ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $y)->$y); + )* + ) + } + + #[cfg(not(feature = "unchecked"))] + macro_rules! reg_un_result { + ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + $self.register_result_fn($x, $op as fn(x: $y)->Result<$y,EvalAltResult>); + )* + ) + } + + #[cfg(not(feature = "unchecked"))] + { + reg_un_result!(self, "-", neg, INT); + reg_un_result!(self, "abs", abs, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_un_result!(self, "-", neg, i8, i16, i32, i64); + reg_un_result!(self, "abs", abs, i8, i16, i32, i64); + } + } + + #[cfg(feature = "unchecked")] + { + reg_un!(self, "-", neg_u, INT); + reg_un!(self, "abs", abs_u, INT); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_un!(self, "-", neg_u, i8, i16, i32, i64); + reg_un!(self, "abs", abs_u, i8, i16, i32, i64); + } + } + + #[cfg(not(feature = "no_float"))] + { + reg_un!(self, "-", neg_u, f32, f64); + reg_un!(self, "abs", abs_u, f32, f64); + } + + reg_un!(self, "!", not, bool); + } self.register_fn("+", |x: String, y: String| x + &y); // String + String self.register_fn("==", |_: (), _: ()| true); // () == () // Register print and debug - fn print_debug(x: T) -> String { + fn debug(x: T) -> String { format!("{:?}", x) } fn print(x: T) -> String { format!("{}", x) } - reg_func1!(self, "print", print, String, i8, u8, i16, u16); - reg_func1!(self, "print", print, String, i32, i64, u32, u64); - reg_func1!(self, "print", print, String, f32, f64, bool, char, String); - reg_func1!(self, "print", print_debug, String, Array); - self.register_fn("print", || "".to_string()); - self.register_fn("print", |_: ()| "".to_string()); + { + macro_rules! reg_fn1 { + ($self:expr, $x:expr, $op:expr, $r:ty, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $y)->$r); + )* + ) + } - reg_func1!(self, "debug", print_debug, String, i8, u8, i16, u16); - reg_func1!(self, "debug", print_debug, String, i32, i64, u32, u64); - reg_func1!(self, "debug", print_debug, String, f32, f64, bool, char); - reg_func1!(self, "debug", print_debug, String, String, Array, ()); + reg_fn1!(self, "print", print, String, INT, bool, char, String); + self.register_fn("print", || "".to_string()); + self.register_fn("print", |_: ()| "".to_string()); + reg_fn1!(self, "debug", debug, String, INT, bool, char, String, ()); - // Register array iterator - self.register_iterator::(|a| { - Box::new(a.downcast_ref::().unwrap().clone().into_iter()) - }); + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_fn1!(self, "print", print, String, i8, u8, i16, u16); + reg_fn1!(self, "print", print, String, i32, i64, u32, u64); + reg_fn1!(self, "debug", debug, String, i8, u8, i16, u16); + reg_fn1!(self, "debug", debug, String, i32, i64, u32, u64); + } + + #[cfg(not(feature = "no_float"))] + { + reg_fn1!(self, "print", print, String, f32, f64); + reg_fn1!(self, "debug", debug, String, f32, f64); + } + + #[cfg(not(feature = "no_index"))] + { + reg_fn1!(self, "print", debug, String, Array); + reg_fn1!(self, "debug", debug, String, Array); + + // Register array iterator + self.register_iterator::(|a| { + Box::new(a.downcast_ref::().unwrap().clone().into_iter()) + }); + } + } // Register range function - self.register_iterator::, _>(|a| { - Box::new( - a.downcast_ref::>() - .unwrap() - .clone() - .map(|n| n.into_dynamic()), - ) - }); + fn reg_iterator(engine: &mut Engine) + where + Range: Iterator, + { + engine.register_iterator::, _>(|a| { + Box::new( + a.downcast_ref::>() + .unwrap() + .clone() + .map(|n| n.into_dynamic()), + ) + }); + } - self.register_fn("range", |i1: i64, i2: i64| (i1..i2)); + fn range(from: T, to: T) -> Range { + (from..to) + } + + reg_iterator::(self); + self.register_fn("range", |i1: INT, i2: INT| (i1..i2)); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + macro_rules! reg_range { + ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + reg_iterator::<$y>(self); + $self.register_fn($x, $op as fn(x: $y, y: $y)->Range<$y>); + )* + ) + } + + reg_range!(self, "range", range, i8, u8, i16, u16, i32, i64, u32, u64); + } } +} - /// Register the built-in library. +#[cfg(not(feature = "no_stdlib"))] +macro_rules! reg_fn2x { + ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $v, y: $y)->$r); + )* + ) +} + +#[cfg(not(feature = "no_stdlib"))] +macro_rules! reg_fn2y { + ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(y: $y, x: $v)->$r); + )* + ) +} + +/// Register the built-in library. +impl Engine<'_> { #[cfg(not(feature = "no_stdlib"))] pub(crate) fn register_stdlib(&mut self) { + #[cfg(not(feature = "no_index"))] use crate::fn_register::RegisterDynamicFn; - // Advanced math functions - self.register_fn("sin", |x: f64| x.to_radians().sin()); - self.register_fn("cos", |x: f64| x.to_radians().cos()); - self.register_fn("tan", |x: f64| x.to_radians().tan()); - self.register_fn("sinh", |x: f64| x.to_radians().sinh()); - self.register_fn("cosh", |x: f64| x.to_radians().cosh()); - self.register_fn("tanh", |x: f64| x.to_radians().tanh()); - self.register_fn("asin", |x: f64| x.asin().to_degrees()); - self.register_fn("acos", |x: f64| x.acos().to_degrees()); - self.register_fn("atan", |x: f64| x.atan().to_degrees()); - self.register_fn("asinh", |x: f64| x.asinh().to_degrees()); - self.register_fn("acosh", |x: f64| x.acosh().to_degrees()); - self.register_fn("atanh", |x: f64| x.atanh().to_degrees()); - self.register_fn("sqrt", |x: f64| x.sqrt()); - self.register_fn("exp", |x: f64| x.exp()); - self.register_fn("ln", |x: f64| x.ln()); - self.register_fn("log", |x: f64, base: f64| x.log(base)); - self.register_fn("log10", |x: f64| x.log10()); - self.register_fn("floor", |x: f64| x.floor()); - self.register_fn("ceiling", |x: f64| x.ceil()); - self.register_fn("round", |x: f64| x.ceil()); - self.register_fn("int", |x: f64| x.trunc()); - self.register_fn("fraction", |x: f64| x.fract()); - self.register_fn("is_nan", |x: f64| x.is_nan()); - self.register_fn("is_finite", |x: f64| x.is_finite()); - self.register_fn("is_infinite", |x: f64| x.is_infinite()); - - // Register conversion functions - self.register_fn("to_float", |x: i8| x as f64); - self.register_fn("to_float", |x: u8| x as f64); - self.register_fn("to_float", |x: i16| x as f64); - self.register_fn("to_float", |x: u16| x as f64); - self.register_fn("to_float", |x: i32| x as f64); - self.register_fn("to_float", |x: u32| x as f64); - self.register_fn("to_float", |x: i64| x as f64); - self.register_fn("to_float", |x: u64| x as f64); - self.register_fn("to_float", |x: f32| x as f64); - - self.register_fn("to_int", |x: i8| x as i64); - self.register_fn("to_int", |x: u8| x as i64); - self.register_fn("to_int", |x: i16| x as i64); - self.register_fn("to_int", |x: u16| x as i64); - self.register_fn("to_int", |x: i32| x as i64); - self.register_fn("to_int", |x: u32| x as i64); - self.register_fn("to_int", |x: u64| x as i64); - self.register_fn("to_int", |ch: char| ch as i64); - - #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "no_float"))] { - self.register_result_fn("to_int", |x: f32| { - if x > (i64::MAX as f32) { - return Err(EvalAltResult::ErrorArithmetic( - format!("Integer overflow: to_int({})", x), - Position::none(), - )); - } + // Advanced math functions + self.register_fn("sin", |x: FLOAT| x.to_radians().sin()); + self.register_fn("cos", |x: FLOAT| x.to_radians().cos()); + self.register_fn("tan", |x: FLOAT| x.to_radians().tan()); + self.register_fn("sinh", |x: FLOAT| x.to_radians().sinh()); + self.register_fn("cosh", |x: FLOAT| x.to_radians().cosh()); + self.register_fn("tanh", |x: FLOAT| x.to_radians().tanh()); + self.register_fn("asin", |x: FLOAT| x.asin().to_degrees()); + self.register_fn("acos", |x: FLOAT| x.acos().to_degrees()); + self.register_fn("atan", |x: FLOAT| x.atan().to_degrees()); + self.register_fn("asinh", |x: FLOAT| x.asinh().to_degrees()); + self.register_fn("acosh", |x: FLOAT| x.acosh().to_degrees()); + self.register_fn("atanh", |x: FLOAT| x.atanh().to_degrees()); + self.register_fn("sqrt", |x: FLOAT| x.sqrt()); + self.register_fn("exp", |x: FLOAT| x.exp()); + self.register_fn("ln", |x: FLOAT| x.ln()); + self.register_fn("log", |x: FLOAT, base: FLOAT| x.log(base)); + self.register_fn("log10", |x: FLOAT| x.log10()); + self.register_fn("floor", |x: FLOAT| x.floor()); + self.register_fn("ceiling", |x: FLOAT| x.ceil()); + self.register_fn("round", |x: FLOAT| x.ceil()); + self.register_fn("int", |x: FLOAT| x.trunc()); + self.register_fn("fraction", |x: FLOAT| x.fract()); + self.register_fn("is_nan", |x: FLOAT| x.is_nan()); + self.register_fn("is_finite", |x: FLOAT| x.is_finite()); + self.register_fn("is_infinite", |x: FLOAT| x.is_infinite()); - Ok(x.trunc() as i64) - }); - self.register_result_fn("to_int", |x: f64| { - if x > (i64::MAX as f64) { - return Err(EvalAltResult::ErrorArithmetic( - format!("Integer overflow: to_int({})", x), - Position::none(), - )); - } + // Register conversion functions + self.register_fn("to_float", |x: INT| x as FLOAT); + self.register_fn("to_float", |x: f32| x as FLOAT); - Ok(x.trunc() as i64) - }); - } - - #[cfg(feature = "unchecked")] - { - self.register_fn("to_int", |x: f32| x as i64); - self.register_fn("to_int", |x: f64| x as i64); - } - - // Register array utility functions - fn push(list: &mut Array, item: T) { - list.push(Box::new(item)); - } - fn pad(list: &mut Array, len: i64, item: T) { - if len >= 0 { - while list.len() < len as usize { - push(list, item.clone()); - } + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + self.register_fn("to_float", |x: i8| x as FLOAT); + self.register_fn("to_float", |x: u8| x as FLOAT); + self.register_fn("to_float", |x: i16| x as FLOAT); + self.register_fn("to_float", |x: u16| x as FLOAT); + self.register_fn("to_float", |x: i32| x as FLOAT); + self.register_fn("to_float", |x: u32| x as FLOAT); + self.register_fn("to_float", |x: i64| x as FLOAT); + self.register_fn("to_float", |x: u64| x as FLOAT); } } - reg_func2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16); - reg_func2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); - reg_func2x!(self, "push", push, &mut Array, (), f32, f64, bool, char); - reg_func2x!(self, "push", push, &mut Array, (), String, Array, ()); - reg_func3!(self, "pad", pad, &mut Array, i64, (), i8, u8, i16, u16); - reg_func3!(self, "pad", pad, &mut Array, i64, (), i32, u32, f32); - reg_func3!(self, "pad", pad, &mut Array, i64, (), i64, u64, f64); - reg_func3!(self, "pad", pad, &mut Array, i64, (), bool, char); - reg_func3!(self, "pad", pad, &mut Array, i64, (), String, Array, ()); + self.register_fn("to_int", |ch: char| ch as INT); - self.register_dynamic_fn("pop", |list: &mut Array| { - list.pop().unwrap_or_else(|| ().into_dynamic()) - }); - self.register_dynamic_fn("shift", |list: &mut Array| match list.len() { - 0 => ().into_dynamic(), - _ => list.remove(0), - }); - self.register_fn("len", |list: &mut Array| list.len() as i64); - self.register_fn("clear", |list: &mut Array| list.clear()); - self.register_fn("truncate", |list: &mut Array, len: i64| { - if len >= 0 { - list.truncate(len as usize); + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + self.register_fn("to_int", |x: i8| x as INT); + self.register_fn("to_int", |x: u8| x as INT); + self.register_fn("to_int", |x: i16| x as INT); + self.register_fn("to_int", |x: u16| x as INT); + } + + #[cfg(not(feature = "only_i32"))] + { + self.register_fn("to_int", |x: i32| x as INT); + self.register_fn("to_int", |x: u64| x as INT); + + #[cfg(feature = "only_i64")] + self.register_fn("to_int", |x: u32| x as INT); + } + + #[cfg(not(feature = "no_float"))] + { + #[cfg(not(feature = "unchecked"))] + { + self.register_result_fn("to_int", |x: f32| { + if x > (i64::MAX as f32) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::none(), + )); + } + + Ok(x.trunc() as INT) + }); + self.register_result_fn("to_int", |x: FLOAT| { + if x > (i64::MAX as FLOAT) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::none(), + )); + } + + Ok(x.trunc() as INT) + }); } - }); + + #[cfg(feature = "unchecked")] + { + self.register_fn("to_int", |x: f32| x as INT); + self.register_fn("to_int", |x: f64| x as INT); + } + } + + #[cfg(not(feature = "no_index"))] + { + macro_rules! reg_fn3 { + ($self:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $v, y: $w, z: $y)->$r); + )* + ) + } + + // Register array utility functions + fn push(list: &mut Array, item: T) { + list.push(Box::new(item)); + } + fn pad(list: &mut Array, len: INT, item: T) { + if len >= 0 { + while list.len() < len as usize { + push(list, item.clone()); + } + } + } + + reg_fn2x!(self, "push", push, &mut Array, (), INT, bool, char); + reg_fn2x!(self, "push", push, &mut Array, (), String, Array, ()); + reg_fn3!(self, "pad", pad, &mut Array, INT, (), INT, bool, char); + reg_fn3!(self, "pad", pad, &mut Array, INT, (), String, Array, ()); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_fn2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16); + reg_fn2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); + reg_fn3!(self, "pad", pad, &mut Array, INT, (), i8, u8, i16, u16); + reg_fn3!(self, "pad", pad, &mut Array, INT, (), i32, u32, i64, u64); + } + + #[cfg(not(feature = "no_float"))] + { + reg_fn2x!(self, "push", push, &mut Array, (), f32, f64); + reg_fn3!(self, "pad", pad, &mut Array, INT, (), f32, f64); + } + + self.register_dynamic_fn("pop", |list: &mut Array| { + list.pop().unwrap_or_else(|| ().into_dynamic()) + }); + self.register_dynamic_fn("shift", |list: &mut Array| match list.len() { + 0 => ().into_dynamic(), + _ => list.remove(0), + }); + self.register_fn("len", |list: &mut Array| list.len() as INT); + self.register_fn("clear", |list: &mut Array| list.clear()); + self.register_fn("truncate", |list: &mut Array, len: INT| { + if len >= 0 { + list.truncate(len as usize); + } + }); + } // Register string concatenate functions fn prepend(x: T, y: String) -> String { @@ -592,28 +792,39 @@ impl Engine<'_> { format!("{}{}", x, y) } - reg_func2x!( - self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, - bool, char - ); - self.register_fn("+", |x: String, y: Array| format!("{}{:?}", x, y)); + reg_fn2x!(self, "+", append, String, String, INT, bool, char); self.register_fn("+", |x: String, _: ()| format!("{}", x)); - reg_func2y!( - self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64, - bool, char - ); - self.register_fn("+", |x: Array, y: String| format!("{:?}{}", x, y)); + reg_fn2y!(self, "+", prepend, String, String, INT, bool, char); self.register_fn("+", |_: (), y: String| format!("{}", y)); + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_fn2x!(self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64); + reg_fn2y!(self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64); + } + + #[cfg(not(feature = "no_float"))] + { + reg_fn2x!(self, "+", append, String, String, f32, f64); + reg_fn2y!(self, "+", prepend, String, String, f32, f64); + } + + #[cfg(not(feature = "no_index"))] + { + self.register_fn("+", |x: String, y: Array| format!("{}{:?}", x, y)); + self.register_fn("+", |x: Array, y: String| format!("{:?}{}", x, y)); + } + // Register string utility functions - self.register_fn("len", |s: &mut String| s.chars().count() as i64); + self.register_fn("len", |s: &mut String| s.chars().count() as INT); self.register_fn("contains", |s: &mut String, ch: char| s.contains(ch)); self.register_fn("contains", |s: &mut String, find: String| s.contains(&find)); self.register_fn("clear", |s: &mut String| s.clear()); self.register_fn("append", |s: &mut String, ch: char| s.push(ch)); self.register_fn("append", |s: &mut String, add: String| s.push_str(&add)); - self.register_fn("truncate", |s: &mut String, len: i64| { + self.register_fn("truncate", |s: &mut String, len: INT| { if len >= 0 { let chars: Vec<_> = s.chars().take(len as usize).collect(); s.clear(); @@ -622,7 +833,7 @@ impl Engine<'_> { s.clear(); } }); - self.register_fn("pad", |s: &mut String, len: i64, ch: char| { + self.register_fn("pad", |s: &mut String, len: INT, ch: char| { for _ in 0..s.chars().count() - len as usize { s.push(ch); } diff --git a/src/call.rs b/src/call.rs index 065cc8d5..9a2252c0 100644 --- a/src/call.rs +++ b/src/call.rs @@ -1,35 +1,54 @@ //! Helper module which defines `FnArgs` to make function calling easier. -use crate::any::{Any, Variant}; +use crate::any::{Any, Dynamic}; +use crate::engine::Array; /// Trait that represent arguments to a function call. -pub trait FuncArgs<'a> { - /// Convert to a `Vec` of `Variant` arguments. - fn into_vec(self) -> Vec<&'a mut Variant>; +pub trait FuncArgs { + /// Convert to a `Vec` of `Dynamic` arguments. + fn into_vec(self) -> Vec; } -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 { + vec![self.into_dynamic()] + } + } + )* + }; } -impl<'a, T: Any> FuncArgs<'a> for &'a mut Vec { - 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 { 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); diff --git a/src/engine.rs b/src/engine.rs index 89dac01c..f3fa3ad4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,19 +1,23 @@ //! Main module defining the script evaluation `Engine`. use crate::any::{Any, AnyExt, Dynamic, Variant}; -use crate::parser::{Expr, FnDef, Position, Stmt}; +use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt}; use crate::result::EvalAltResult; use crate::scope::Scope; + +#[cfg(not(feature = "no_index"))] +use crate::INT; + use std::{ any::{type_name, TypeId}, borrow::Cow, - cmp::{PartialEq, PartialOrd}, collections::HashMap, iter::once, sync::Arc, }; /// An dynamic array of `Dynamic` values. +#[cfg(not(feature = "no_index"))] pub type Array = Vec; pub type FnCallArgs<'a> = Vec<&'a mut Variant>; @@ -24,18 +28,20 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box>; 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>, @@ -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, Arc>>, + pub(crate) ext_functions: HashMap, Box>, /// A hashmap containing all script-defined functions - pub(crate) script_functions: HashMap, Arc>>, + pub(crate) script_functions: Vec>, /// A hashmap containing all iterators known to the engine - pub(crate) type_iterators: HashMap>, + pub(crate) type_iterators: HashMap>, pub(crate) type_names: HashMap, // Closures for implementing the print/debug commands @@ -71,18 +77,14 @@ pub struct Engine<'e> { pub(crate) on_debug: Box, } -pub enum FnIntExt<'a> { - Ext(Box), - 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"), + #[cfg(not(feature = "no_index"))] (type_name::(), "array"), + (type_name::(), "string"), (type_name::(), "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::() + .map(|s| *s) + .unwrap_or("error: not a string".into()); - let val = &result - .downcast::() - .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::>(); - - 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::>(); + + Err(EvalAltResult::ErrorFunctionNotFound( + format!("{} ({})", spec.name, types_list.join(", ")), + pos, + )) } /// Chain-evaluate a dot setter @@ -250,14 +241,14 @@ impl Engine<'_> { ) -> Result { 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::, _>>()?; 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 { + ) -> Result { self.eval_expr(scope, idx_expr)? - .downcast::() + .downcast::() .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::() { // val_array[idx] - let arr = val.downcast::().expect("array expected"); + let arr = val.downcast_ref::().expect("array expected"); if idx >= 0 { arr.get(idx as usize) @@ -447,7 +443,7 @@ impl Engine<'_> { } } else if val.is::() { // val_string[idx] - let s = val.downcast::().expect("string expected"); + let s = val.downcast_ref::().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 = 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 { 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::>() + .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::>()?; + .collect::, _>>()?; 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::() @@ -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()) } } diff --git a/src/fn_register.rs b/src/fn_register.rs index 9af9ab82..84dbf150 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -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) } } }; diff --git a/src/lib.rs b/src/lib.rs index 1472657f..0c4bb048 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/optimize.rs b/src/optimize.rs index 4a0b26b1..bf7eab8f 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,27 +1,51 @@ +use crate::engine::KEYWORD_DUMP_AST; use crate::parser::{Expr, Stmt}; -fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt { +fn optimize_stmt(stmt: Stmt, changed: &mut bool, preserve_result: bool) -> Stmt { match stmt { + Stmt::IfElse(expr, stmt1, None) if stmt1.is_noop() => { + *changed = true; + + let pos = expr.position(); + let expr = optimize_expr(*expr, changed); + + match expr { + Expr::False(_) | Expr::True(_) => Stmt::Noop(stmt1.position()), + expr => { + let stmt = Stmt::Expr(Box::new(expr)); + + if preserve_result { + Stmt::Block(vec![stmt, *stmt1], pos) + } else { + stmt + } + } + } + } + Stmt::IfElse(expr, stmt1, None) => match *expr { Expr::False(pos) => { *changed = true; Stmt::Noop(pos) } - Expr::True(_) => optimize_stmt(*stmt1, changed), + Expr::True(_) => optimize_stmt(*stmt1, changed, true), expr => Stmt::IfElse( Box::new(optimize_expr(expr, changed)), - Box::new(optimize_stmt(*stmt1, changed)), + Box::new(optimize_stmt(*stmt1, changed, true)), None, ), }, Stmt::IfElse(expr, stmt1, Some(stmt2)) => match *expr { - Expr::False(_) => optimize_stmt(*stmt2, changed), - Expr::True(_) => optimize_stmt(*stmt1, changed), + Expr::False(_) => optimize_stmt(*stmt2, changed, true), + Expr::True(_) => optimize_stmt(*stmt1, changed, true), expr => Stmt::IfElse( Box::new(optimize_expr(expr, changed)), - Box::new(optimize_stmt(*stmt1, changed)), - Some(Box::new(optimize_stmt(*stmt2, changed))), + Box::new(optimize_stmt(*stmt1, changed, true)), + match optimize_stmt(*stmt2, changed, true) { + stmt if stmt.is_noop() => None, + stmt => Some(Box::new(stmt)), + }, ), }, @@ -30,18 +54,18 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt { *changed = true; Stmt::Noop(pos) } - Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed))), + Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed, false))), expr => Stmt::While( Box::new(optimize_expr(expr, changed)), - Box::new(optimize_stmt(*stmt, changed)), + Box::new(optimize_stmt(*stmt, changed, false)), ), }, - Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed))), + Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, changed, false))), Stmt::For(id, expr, stmt) => Stmt::For( id, Box::new(optimize_expr(*expr, changed)), - Box::new(optimize_stmt(*stmt, changed)), + Box::new(optimize_stmt(*stmt, changed, false)), ), Stmt::Let(id, Some(expr), pos) => { Stmt::Let(id, Some(Box::new(optimize_expr(*expr, changed))), pos) @@ -49,46 +73,71 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt { Stmt::Let(_, None, _) => stmt, Stmt::Block(statements, pos) => { - let original_len = statements.len(); + let orig_len = statements.len(); let mut result: Vec<_> = statements .into_iter() // For each statement - .map(|s| optimize_stmt(s, changed)) // Optimize the statement - .filter(Stmt::is_op) // Remove no-op's + .rev() // Scan in reverse + .map(|s| optimize_stmt(s, changed, preserve_result)) // Optimize the statement + .enumerate() + .filter(|(i, s)| s.is_op() || (preserve_result && *i == 0)) // Remove no-op's but leave the last one if we need the result + .map(|(_, s)| s) + .rev() .collect(); - if let Some(last_stmt) = result.pop() { - // Remove all raw expression statements that evaluate to constants - // except for the very last statement - result.retain(|stmt| match stmt { - Stmt::Expr(expr) if expr.is_constant() => false, - _ => true, - }); + // Remove all raw expression statements that are pure except for the very last statement + let last_stmt = if preserve_result { result.pop() } else { None }; - result.push(last_stmt); + result.retain(|stmt| match stmt { + Stmt::Expr(expr) if expr.is_pure() => false, + _ => true, + }); + + if let Some(stmt) = last_stmt { + result.push(stmt); } - *changed = *changed || original_len != result.len(); + // Remove all let statements at the end of a block - the new variables will go away anyway. + // But be careful only remove ones that have no initial values or have values that are pure expressions, + // otherwise there may be side effects. + let mut removed = false; + + while let Some(expr) = result.pop() { + match expr { + Stmt::Let(_, None, _) => removed = true, + Stmt::Let(_, Some(val_expr), _) if val_expr.is_pure() => removed = true, + + _ => { + result.push(expr); + break; + } + } + } + + if preserve_result { + if removed { + result.push(Stmt::Noop(pos)) + } + + result = result + .into_iter() + .rev() + .enumerate() + .map(|(i, s)| optimize_stmt(s, changed, i == 0)) // Optimize all other statements again + .rev() + .collect(); + } + + *changed = *changed || orig_len != result.len(); match result[..] { + // No statements in block - change to No-op [] => { - // No statements in block - change to No-op *changed = true; Stmt::Noop(pos) } - [Stmt::Let(_, None, _)] => { - // Only one empty variable declaration - change to No-op - *changed = true; - Stmt::Noop(pos) - } - [Stmt::Let(_, Some(_), _)] => { - // Only one let statement, but cannot promote - // (otherwise the variable gets declared in the scope above) - // and still need to run just in case there are side effects - Stmt::Block(result, pos) - } + // Only one statement - promote [_] => { - // Only one statement - promote *changed = true; result.remove(0) } @@ -103,24 +152,14 @@ fn optimize_stmt(stmt: Stmt, changed: &mut bool) -> Stmt { is_return, pos, ), - stmt @ Stmt::ReturnWithVal(None, _, _) => stmt, - stmt @ Stmt::Noop(_) | stmt @ Stmt::Break(_) => stmt, + stmt => stmt, } } fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { match expr { - Expr::IntegerConstant(_, _) - | Expr::FloatConstant(_, _) - | Expr::Identifier(_, _) - | Expr::CharConstant(_, _) - | Expr::StringConstant(_, _) - | Expr::True(_) - | Expr::False(_) - | Expr::Unit(_) => expr, - - Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed) { + Expr::Stmt(stmt, pos) => match optimize_stmt(*stmt, changed, true) { Stmt::Noop(_) => { *changed = true; Expr::Unit(pos) @@ -139,24 +178,43 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { Box::new(optimize_expr(*rhs, changed)), pos, ), - Expr::Index(lhs, rhs, pos) => Expr::Index( - Box::new(optimize_expr(*lhs, changed)), - Box::new(optimize_expr(*rhs, changed)), - pos, - ), + + #[cfg(not(feature = "no_index"))] + Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) { + (Expr::Array(mut items, _), Expr::IntegerConstant(i, _)) + if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) => + { + // Array where everything is a pure - promote the indexed item. + // All other items can be thrown away. + *changed = true; + items.remove(i as usize) + } + (lhs, rhs) => Expr::Index( + Box::new(optimize_expr(lhs, changed)), + Box::new(optimize_expr(rhs, changed)), + pos, + ), + }, + #[cfg(feature = "no_index")] + Expr::Index(_, _, _) => panic!("encountered an index expression during no_index!"), + + #[cfg(not(feature = "no_index"))] Expr::Array(items, pos) => { - let original_len = items.len(); + let orig_len = items.len(); let items: Vec<_> = items .into_iter() .map(|expr| optimize_expr(expr, changed)) .collect(); - *changed = *changed || original_len != items.len(); + *changed = *changed || orig_len != items.len(); Expr::Array(items, pos) } + #[cfg(feature = "no_index")] + Expr::Array(_, _) => panic!("encountered an array during no_index!"), + Expr::And(lhs, rhs) => match (*lhs, *rhs) { (Expr::True(_), rhs) => { *changed = true; @@ -194,29 +252,44 @@ fn optimize_expr(expr: Expr, changed: &mut bool) -> Expr { ), }, + Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST => { + Expr::FunctionCall(id, args, def_value, pos) + } Expr::FunctionCall(id, args, def_value, pos) => { - let original_len = args.len(); + let orig_len = args.len(); let args: Vec<_> = args .into_iter() .map(|a| optimize_expr(a, changed)) .collect(); - *changed = *changed || original_len != args.len(); + *changed = *changed || orig_len != args.len(); Expr::FunctionCall(id, args, def_value, pos) } + + expr => expr, } } -pub(crate) fn optimize(mut statements: Vec) -> Vec { +pub(crate) fn optimize(statements: Vec) -> Vec { + 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) -> Vec { } } - 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 } diff --git a/src/parser.rs b/src/parser.rs index 10e629d0..a02baf0f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,7 +3,26 @@ use crate::any::Dynamic; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::optimize; -use std::{borrow::Cow, char, fmt, iter::Peekable, str::Chars, str::FromStr, usize}; + +use std::{ + borrow::Cow, char, cmp::Ordering, fmt, iter::Peekable, str::Chars, str::FromStr, sync::Arc, + usize, +}; + +/// The system integer type. +/// +/// If the `only_i32` feature is enabled, this will be `i32` instead. +#[cfg(not(feature = "only_i32"))] +pub type INT = i64; + +/// The system integer type +/// +/// If the `only_i32` feature is not enabled, this will be `i64` instead. +#[cfg(feature = "only_i32")] +pub type INT = i32; + +/// The system floating-point type +pub type FLOAT = f64; type LERR = LexError; type PERR = ParseErrorType; @@ -123,16 +142,35 @@ impl fmt::Debug for Position { } /// Compiled AST (abstract syntax tree) of a Rhai script. -pub struct AST(pub(crate) Vec, pub(crate) Vec>); - #[derive(Debug, Clone)] -pub struct FnDef<'a> { - pub name: Cow<'a, str>, - pub params: Vec>, +pub struct AST( + pub(crate) Vec, + #[cfg(not(feature = "no_function"))] pub(crate) Vec>, +); + +#[derive(Debug)] // Do not derive Clone because it is expensive +pub struct FnDef { + pub name: String, + pub params: Vec, pub body: Stmt, pub pos: Position, } +impl FnDef { + pub fn compare(&self, name: &str, params_len: usize) -> Ordering { + match self.name.as_str().cmp(name) { + Ordering::Equal => self.params.len().cmp(¶ms_len), + order => order, + } + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub enum ReturnType { + Return, + Exception, +} + #[derive(Debug, Clone)] pub enum Stmt { Noop(Position), @@ -144,22 +182,49 @@ pub enum Stmt { Block(Vec, Position), Expr(Box), Break(Position), - ReturnWithVal(Option>, bool, Position), + ReturnWithVal(Option>, 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>, begin: Position, ) -> Result { - 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, input: &mut Peekable>, @@ -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>, begin: Position, @@ -1320,26 +1419,30 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result 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>) -> Result 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>) -> Result 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 (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 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>) -> Result(input: &mut Peekable>) -> Result { - 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>) -> Result { - 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>) -> Result(input: &mut Peekable>) -> Result, ParseError> { +#[cfg(not(feature = "no_function"))] +fn parse_fn<'a>(input: &mut Peekable>) -> Result { 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>) -> Result( input: &mut Peekable>, optimize_ast: bool, ) -> Result { - let mut statements = Vec::new(); - let mut functions = Vec::new(); + let mut statements = Vec::::new(); + + #[cfg(not(feature = "no_function"))] + let mut functions = Vec::::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>( diff --git a/src/result.rs b/src/result.rs index 16569a48..c37a7a4a 100644 --- a/src/result.rs +++ b/src/result.rs @@ -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 for EvalAltResult { } } +impl> From 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> From for EvalAltResult { - fn from(err: T) -> Self { - Self::ErrorRuntime(err.as_ref().to_string(), Position::none()) - } -} diff --git a/src/scope.rs b/src/scope.rs index 2e2c492b..cf726d39 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -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::(&mut my_scope, false, "x + 1")?, 6); +/// assert_eq!(engine.eval_with_scope::(&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(&mut self, key: &str, index: usize) -> &mut T { self.get_mut(key, index) .downcast_mut::() diff --git a/tests/arrays.rs b/tests/arrays.rs index cfd74e6e..4bee090e 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -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::("let x = [1, 2, 3]; x[1]")?, 2); - assert_eq!(engine.eval::("let y = [1, 2, 3]; y[1] = 5; y[1]")?, 5); + assert_eq!(engine.eval::("let x = [1, 2, 3]; x[1]")?, 2); + assert_eq!(engine.eval::("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::("let a = [new_ts()]; a[0].x")?, 1); + assert_eq!(engine.eval::("let a = [new_ts()]; a[0].x")?, 1); assert_eq!( - engine.eval::( + engine.eval::( "let a = [new_ts()]; \ a[0].x = 100; \ a[0].update(); \ diff --git a/tests/binary_ops.rs b/tests/binary_ops.rs index edf560aa..3e59e8fd 100644 --- a/tests/binary_ops.rs +++ b/tests/binary_ops.rs @@ -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::("10 % 4")?, 2); - assert_eq!(engine.eval::("10 << 4")?, 160); - assert_eq!(engine.eval::("10 >> 4")?, 0); - assert_eq!(engine.eval::("10 & 4")?, 0); - assert_eq!(engine.eval::("10 | 4")?, 14); - assert_eq!(engine.eval::("10 ^ 4")?, 14); + assert_eq!(engine.eval::("10 % 4")?, 2); + assert_eq!(engine.eval::("10 << 4")?, 160); + assert_eq!(engine.eval::("10 >> 4")?, 0); + assert_eq!(engine.eval::("10 & 4")?, 0); + assert_eq!(engine.eval::("10 | 4")?, 14); + assert_eq!(engine.eval::("10 ^ 4")?, 14); assert_eq!(engine.eval::("42 == 42")?, true); assert_eq!(engine.eval::("42 > 42")?, false); diff --git a/tests/bit_shift.rs b/tests/bit_shift.rs index 7b0a4ab5..7dd3aa76 100644 --- a/tests/bit_shift.rs +++ b/tests/bit_shift.rs @@ -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::("4 << 2")?, 16); + assert_eq!(engine.eval::("4 << 2")?, 16); Ok(()) } #[test] fn test_right_shift() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("9 >> 1")?, 4); + assert_eq!(engine.eval::("9 >> 1")?, 4); Ok(()) } diff --git a/tests/bool_op.rs b/tests/bool_op.rs index 7c1654bb..a39001a2 100644 --- a/tests/bool_op.rs +++ b/tests/bool_op.rs @@ -39,10 +39,9 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> { assert_eq!( engine.eval::( 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::( 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::( - r" - fn this() { false } - fn that() { 9/0 } + assert!(engine + .eval::( + 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::( - r" - fn this() { false } - fn that() { 9/0 } + assert!(engine + .eval::( + r" + let this = false; - this() & that(); + this & { throw; } " - ) - .unwrap(), - false - ); + ) + .is_err()); } diff --git a/tests/chars.rs b/tests/chars.rs index 4a5c2d98..cf207e43 100644 --- a/tests/chars.rs +++ b/tests/chars.rs @@ -6,11 +6,15 @@ fn test_chars() -> Result<(), EvalAltResult> { assert_eq!(engine.eval::("'y'")?, 'y'); assert_eq!(engine.eval::("'\\u2764'")?, '❤'); - assert_eq!(engine.eval::(r#"let x="hello"; x[2]"#)?, 'l'); - assert_eq!( - engine.eval::(r#"let x="hello"; x[2]='$'; x"#)?, - "he$lo".to_string() - ); + + #[cfg(not(feature = "no_index"))] + { + assert_eq!(engine.eval::(r#"let x="hello"; x[2]"#)?, 'l'); + assert_eq!( + engine.eval::(r#"let x="hello"; x[2]='$'; x"#)?, + "he$lo".to_string() + ); + } assert!(engine.eval::("'\\uhello'").is_err()); assert!(engine.eval::("''").is_err()); diff --git a/tests/comments.rs b/tests/comments.rs index 0b871944..8c071f07 100644 --- a/tests/comments.rs +++ b/tests/comments.rs @@ -1,14 +1,14 @@ -use rhai::Engine; +use rhai::{Engine, INT}; #[test] fn test_comments() { let mut engine = Engine::new(); assert!(engine - .eval::("let x = 5; x // I am a single line comment, yay!") + .eval::("let x = 5; x // I am a single line comment, yay!") .is_ok()); assert!(engine - .eval::("let /* I am a multiline comment, yay! */ x = 5; x") + .eval::("let /* I am a multiline comment, yay! */ x = 5; x") .is_ok()); } diff --git a/tests/compound_equality.rs b/tests/compound_equality.rs index 65b3b3e4..569cfd94 100644 --- a/tests/compound_equality.rs +++ b/tests/compound_equality.rs @@ -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::("let x = 16; x |= 74; x")?, 90); + assert_eq!(engine.eval::("let x = 16; x |= 74; x")?, 90); assert_eq!(engine.eval::("let x = true; x |= false; x")?, true); assert_eq!(engine.eval::("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::("let x = 16; x &= 31; x")?, 16); + assert_eq!(engine.eval::("let x = 16; x &= 31; x")?, 16); assert_eq!(engine.eval::("let x = true; x &= false; x")?, false); assert_eq!(engine.eval::("let x = false; x &= true; x")?, false); assert_eq!(engine.eval::("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::("let x = 90; x ^= 12; x")?, 86); + assert_eq!(engine.eval::("let x = 90; x ^= 12; x")?, 86); Ok(()) } #[test] fn test_multiply_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 2; x *= 3; x")?, 6); + assert_eq!(engine.eval::("let x = 2; x *= 3; x")?, 6); Ok(()) } #[test] fn test_divide_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 6; x /= 2; x")?, 3); + assert_eq!(engine.eval::("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::("let x = 9; x >>=1; x")?, 4); + assert_eq!(engine.eval::("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::("let x = 4; x<<= 2; x")?, 16); + assert_eq!(engine.eval::("let x = 4; x<<= 2; x")?, 16); Ok(()) } #[test] fn test_modulo_equals() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::("let x = 10; x %= 4; x")?, 2); + assert_eq!(engine.eval::("let x = 10; x %= 4; x")?, 2); Ok(()) } diff --git a/tests/decrement.rs b/tests/decrement.rs index 75d85076..3547adc4 100644 --- a/tests/decrement.rs +++ b/tests/decrement.rs @@ -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::("let x = 10; x -= 7; x")?, 3); + assert_eq!(engine.eval::("let x = 10; x -= 7; x")?, 3); let r = engine.eval::("let s = \"test\"; s -= \"ing\"; s"); diff --git a/tests/engine.rs b/tests/engine.rs index b55a7c44..cd9f7fb0 100644 --- a/tests/engine.rs +++ b/tests/engine.rs @@ -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(()) } diff --git a/tests/float.rs b/tests/float.rs index 8e291e9e..af33ee34 100644 --- a/tests/float.rs +++ b/tests/float.rs @@ -1,3 +1,4 @@ +#![cfg(not(feature = "no_float"))] use rhai::{Engine, EvalAltResult, RegisterFn}; #[test] diff --git a/tests/for.rs b/tests/for.rs index 63390b31..be476328 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -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::(script)?, 30); + assert_eq!(engine.eval::(script)?, 30); Ok(()) } diff --git a/tests/get_set.rs b/tests/get_set.rs index 0a9fdd16..71886d2d 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -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::("let a = new_ts(); a.x = 500; a.x")?, 500); + assert_eq!(engine.eval::("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::("let a = new_tp(); a.child.x = 500; a.child.x")?, + engine.eval::("let a = new_tp(); a.child.x = 500; a.child.x")?, 500 ); diff --git a/tests/if_block.rs b/tests/if_block.rs index 286aa7fd..e8ddb25b 100644 --- a/tests/if_block.rs +++ b/tests/if_block.rs @@ -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::("if true { 55 }")?, 55); - assert_eq!(engine.eval::("if false { 55 } else { 44 }")?, 44); - assert_eq!(engine.eval::("if true { 55 } else { 44 }")?, 55); + assert_eq!(engine.eval::("if true { 55 }")?, 55); + assert_eq!(engine.eval::("if false { 55 } else { 44 }")?, 44); + assert_eq!(engine.eval::("if true { 55 } else { 44 }")?, 55); assert_eq!( - engine.eval::("if false { 55 } else if true { 33 } else { 44 }")?, + engine.eval::("if false { 55 } else if true { 33 } else { 44 }")?, 33 ); assert_eq!( - engine.eval::( + engine.eval::( r" if false { 55 } else if false { 33 } diff --git a/tests/increment.rs b/tests/increment.rs index 62b1b36c..980dda56 100644 --- a/tests/increment.rs +++ b/tests/increment.rs @@ -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::("let x = 1; x += 2; x")?, 3); + assert_eq!(engine.eval::("let x = 1; x += 2; x")?, 3); assert_eq!( engine.eval::("let s = \"test\"; s += \"ing\"; s")?, "testing".to_string() diff --git a/tests/internal_fn.rs b/tests/internal_fn.rs index 95f584a5..4a6720d5 100644 --- a/tests/internal_fn.rs +++ b/tests/internal_fn.rs @@ -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::("fn addme(a, b) { a+b } addme(3, 4)")?, 7); - assert_eq!(engine.eval::("fn bob() { return 4; 5 } bob()")?, 4); + assert_eq!(engine.eval::("fn addme(a, b) { a+b } addme(3, 4)")?, 7); + assert_eq!(engine.eval::("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::( + engine.eval::( 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::( + 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(()) +} diff --git a/tests/math.rs b/tests/math.rs index 9b92f1fb..e9565b39 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -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::("1 + 2")?, 3); - assert_eq!(engine.eval::("1 - 2")?, -1); - assert_eq!(engine.eval::("2 * 3")?, 6); - assert_eq!(engine.eval::("1 / 2")?, 0); - assert_eq!(engine.eval::("3 % 2")?, 1); + assert_eq!(engine.eval::("1 + 2")?, 3); + assert_eq!(engine.eval::("1 - 2")?, -1); + assert_eq!(engine.eval::("2 * 3")?, 6); + assert_eq!(engine.eval::("1 / 2")?, 0); + assert_eq!(engine.eval::("3 % 2")?, 1); + + #[cfg(not(feature = "only_i32"))] assert_eq!( - engine.eval::("(-9223372036854775807).abs()")?, + engine.eval::("(-9223372036854775807).abs()")?, 9223372036854775807 ); + #[cfg(feature = "only_i32")] + assert_eq!(engine.eval::("(-2147483647).abs()")?, 2147483647); + // Overflow/underflow/division-by-zero errors #[cfg(not(feature = "unchecked"))] { - match engine.eval::("9223372036854775807 + 1") { - Err(EvalAltResult::ErrorArithmetic(_, _)) => (), - r => panic!("should return overflow error: {:?}", r), + #[cfg(not(feature = "only_i32"))] + { + match engine.eval::("(-9223372036854775808).abs()") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return overflow error: {:?}", r), + } + match engine.eval::("9223372036854775807 + 1") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return overflow error: {:?}", r), + } + match engine.eval::("-9223372036854775808 - 1") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return underflow error: {:?}", r), + } + match engine.eval::("9223372036854775807 * 9223372036854775807") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return overflow error: {:?}", r), + } + match engine.eval::("9223372036854775807 / 0") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return division by zero error: {:?}", r), + } + match engine.eval::("9223372036854775807 % 0") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return division by zero error: {:?}", r), + } } - match engine.eval::("-9223372036854775808 - 1") { - Err(EvalAltResult::ErrorArithmetic(_, _)) => (), - r => panic!("should return underflow error: {:?}", r), - } - match engine.eval::("9223372036854775807 * 9223372036854775807") { - Err(EvalAltResult::ErrorArithmetic(_, _)) => (), - r => panic!("should return overflow error: {:?}", r), - } - match engine.eval::("9223372036854775807 / 0") { - Err(EvalAltResult::ErrorArithmetic(_, _)) => (), - r => panic!("should return division by zero error: {:?}", r), - } - match engine.eval::("9223372036854775807 % 0") { - Err(EvalAltResult::ErrorArithmetic(_, _)) => (), - r => panic!("should return division by zero error: {:?}", r), + + #[cfg(feature = "only_i32")] + { + match engine.eval::("2147483647 + 1") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return overflow error: {:?}", r), + } + match engine.eval::("-2147483648 - 1") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return underflow error: {:?}", r), + } + match engine.eval::("2147483647 * 2147483647") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return overflow error: {:?}", r), + } + match engine.eval::("2147483647 / 0") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return division by zero error: {:?}", r), + } + match engine.eval::("2147483647 % 0") { + Err(EvalAltResult::ErrorArithmetic(_, _)) => (), + r => panic!("should return division by zero error: {:?}", r), + } } } diff --git a/tests/method_call.rs b/tests/method_call.rs index f9f09652..e1825094 100644 --- a/tests/method_call.rs +++ b/tests/method_call.rs @@ -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 { diff --git a/tests/mismatched_op.rs b/tests/mismatched_op.rs index 9964189b..1abb3cc1 100644 --- a/tests/mismatched_op.rs +++ b/tests/mismatched_op.rs @@ -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::("60 + \"hello\""); + let r = engine.eval::("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::(); + engine.register_type_with_name::("TestStruct"); engine.register_fn("new_ts", TestStruct::new); - let r = engine.eval::("60 + new_ts()"); + let r = engine.eval::("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!(), } } diff --git a/tests/not.rs b/tests/not.rs index 8bade80a..52913136 100644 --- a/tests/not.rs +++ b/tests/not.rs @@ -9,6 +9,7 @@ fn test_not() -> Result<(), EvalAltResult> { false ); + #[cfg(not(feature = "no_function"))] assert_eq!(engine.eval::("fn not(x) { !x } not(false)")?, true); // TODO - do we allow stacking unary operators directly? e.g '!!!!!!!true' diff --git a/tests/number_literals.rs b/tests/number_literals.rs index 16e12719..f6466967 100644 --- a/tests/number_literals.rs +++ b/tests/number_literals.rs @@ -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::("65")?, 65); + assert_eq!(engine.eval::("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::("let x = 0xf; x")?, 15); - assert_eq!(engine.eval::("let x = 0xff; x")?, 255); + assert_eq!(engine.eval::("let x = 0xf; x")?, 15); + assert_eq!(engine.eval::("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::("let x = 0o77; x")?, 63); - assert_eq!(engine.eval::("let x = 0o1234; x")?, 668); + assert_eq!(engine.eval::("let x = 0o77; x")?, 63); + assert_eq!(engine.eval::("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::("let x = 0b1111; x")?, 15); + assert_eq!(engine.eval::("let x = 0b1111; x")?, 15); assert_eq!( - engine.eval::("let x = 0b0011_1100_1010_0101; x")?, + engine.eval::("let x = 0b0011_1100_1010_0101; x")?, 15525 ); diff --git a/tests/ops.rs b/tests/ops.rs index d1464dfb..aaddea8a 100644 --- a/tests/ops.rs +++ b/tests/ops.rs @@ -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::("60 + 5")?, 65); - assert_eq!(engine.eval::("(1 + 2) * (6 - 4) / 2")?, 3); + assert_eq!(engine.eval::("60 + 5")?, 65); + assert_eq!(engine.eval::("(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::("let x = 0; if x == 10 || true { x = 1} x")?, + engine.eval::("let x = 0; if x == 10 || true { x = 1} x")?, 1 ); diff --git a/tests/power_of.rs b/tests/power_of.rs index ac41a611..8e1ed522 100644 --- a/tests/power_of.rs +++ b/tests/power_of.rs @@ -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::("2 ~ 3")?, 8); - assert_eq!(engine.eval::("(-2 ~ 3)")?, -8); - assert_eq!(engine.eval::("2.2 ~ 3.3")?, 13.489468760533386_f64); - assert_eq!(engine.eval::("2.0~-2.0")?, 0.25_f64); - assert_eq!(engine.eval::("(-2.0~-2.0)")?, 0.25_f64); - assert_eq!(engine.eval::("(-2.0~-2)")?, 0.25_f64); - assert_eq!(engine.eval::("4~3")?, 64); + assert_eq!(engine.eval::("2 ~ 3")?, 8); + assert_eq!(engine.eval::("(-2 ~ 3)")?, -8); + + #[cfg(not(feature = "no_float"))] + { + assert_eq!( + engine.eval::("2.2 ~ 3.3")?, + 13.489468760533386 as FLOAT + ); + assert_eq!(engine.eval::("2.0~-2.0")?, 0.25 as FLOAT); + assert_eq!(engine.eval::("(-2.0~-2.0)")?, 0.25 as FLOAT); + assert_eq!(engine.eval::("(-2.0~-2)")?, 0.25 as FLOAT); + assert_eq!(engine.eval::("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::("let x = 2; x ~= 3; x")?, 8); - assert_eq!(engine.eval::("let x = -2; x ~= 3; x")?, -8); - assert_eq!( - engine.eval::("let x = 2.2; x ~= 3.3; x")?, - 13.489468760533386_f64 - ); - assert_eq!(engine.eval::("let x = 2.0; x ~= -2.0; x")?, 0.25_f64); - assert_eq!(engine.eval::("let x = -2.0; x ~= -2.0; x")?, 0.25_f64); - assert_eq!(engine.eval::("let x = -2.0; x ~= -2; x")?, 0.25_f64); - assert_eq!(engine.eval::("let x =4; x ~= 3; x")?, 64); + assert_eq!(engine.eval::("let x = 2; x ~= 3; x")?, 8); + assert_eq!(engine.eval::("let x = -2; x ~= 3; x")?, -8); + + #[cfg(not(feature = "no_float"))] + { + assert_eq!( + engine.eval::("let x = 2.2; x ~= 3.3; x")?, + 13.489468760533386 as FLOAT + ); + assert_eq!( + engine.eval::("let x = 2.0; x ~= -2.0; x")?, + 0.25 as FLOAT + ); + assert_eq!( + engine.eval::("let x = -2.0; x ~= -2.0; x")?, + 0.25 as FLOAT + ); + assert_eq!( + engine.eval::("let x = -2.0; x ~= -2; x")?, + 0.25 as FLOAT + ); + assert_eq!(engine.eval::("let x =4; x ~= 3; x")?, 64); + } Ok(()) } diff --git a/tests/string.rs b/tests/string.rs index 2032154b..5d63b88f 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -12,10 +12,20 @@ fn test_string() -> Result<(), EvalAltResult> { engine.eval::(r#""Test string: \x58""#)?, "Test string: X".to_string() ); + assert_eq!( engine.eval::(r#""foo" + "bar""#)?, "foobar".to_string() ); + + #[cfg(not(feature = "no_stdlib"))] + assert_eq!( + engine.eval::(r#""foo" + 123"#)?, + "foo123".to_string() + ); + + #[cfg(not(feature = "no_float"))] + #[cfg(not(feature = "no_stdlib"))] assert_eq!( engine.eval::(r#""foo" + 123.4556"#)?, "foo123.4556".to_string() diff --git a/tests/throw.rs b/tests/throw.rs index ca14cb3f..5edecedc 100644 --- a/tests/throw.rs +++ b/tests/throw.rs @@ -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::(r#"if true { throw "hello" }"#) { + match engine.eval::(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::(r#"throw;"#) { + match engine.eval::(r#"throw;"#) { Ok(_) => panic!("not an error"), Err(EvalAltResult::ErrorRuntime(s, _)) if s == "" => (), Err(err) => panic!("wrong error: {}", err), diff --git a/tests/types.rs b/tests/types.rs index 1cf303ad..fd9cda32 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -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::("type_of(60 + 5)")?, "i64"); + + #[cfg(feature = "only_i32")] + assert_eq!(engine.eval::("type_of(60 + 5)")?, "i32"); + + #[cfg(not(feature = "no_float"))] assert_eq!(engine.eval::("type_of(1.0 + 2.0)")?, "f64"); + + #[cfg(not(feature = "no_index"))] + #[cfg(not(feature = "no_float"))] assert_eq!( engine.eval::(r#"type_of([1.0, 2, "hello"])"#)?, "array" ); + assert_eq!(engine.eval::(r#"type_of("hello")"#)?, "string"); + + #[cfg(not(feature = "only_i32"))] assert_eq!(engine.eval::("let x = 123; x.type_of()")?, "i64"); + #[cfg(feature = "only_i32")] + assert_eq!(engine.eval::("let x = 123; x.type_of()")?, "i32"); + Ok(()) } diff --git a/tests/unary_after_binary.rs b/tests/unary_after_binary.rs index f44111d4..16d4730a 100644 --- a/tests/unary_after_binary.rs +++ b/tests/unary_after_binary.rs @@ -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::("10 % +4")?, 2); - assert_eq!(engine.eval::("10 << +4")?, 160); - assert_eq!(engine.eval::("10 >> +4")?, 0); - assert_eq!(engine.eval::("10 & +4")?, 0); - assert_eq!(engine.eval::("10 | +4")?, 14); - assert_eq!(engine.eval::("10 ^ +4")?, 14); + assert_eq!(engine.eval::("10 % +4")?, 2); + assert_eq!(engine.eval::("10 << +4")?, 160); + assert_eq!(engine.eval::("10 >> +4")?, 0); + assert_eq!(engine.eval::("10 & +4")?, 0); + assert_eq!(engine.eval::("10 | +4")?, 14); + assert_eq!(engine.eval::("10 ^ +4")?, 14); Ok(()) } diff --git a/tests/unary_minus.rs b/tests/unary_minus.rs index 37718326..80170498 100644 --- a/tests/unary_minus.rs +++ b/tests/unary_minus.rs @@ -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::("let x = -5; x")?, -5); - assert_eq!(engine.eval::("fn neg(x) { -x } neg(5)")?, -5); - assert_eq!(engine.eval::("5 - -+++--+-5")?, 0); + assert_eq!(engine.eval::("let x = -5; x")?, -5); + + #[cfg(not(feature = "no_function"))] + assert_eq!(engine.eval::("fn neg(x) { -x } neg(5)")?, -5); + + assert_eq!(engine.eval::("5 - -+++--+-5")?, 0); Ok(()) } diff --git a/tests/var_scope.rs b/tests/var_scope.rs index f8371bb3..bdb4674e 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -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::(&mut scope, false, "x")?, 9); - engine.eval_with_scope::<()>(&mut scope, false, "x = x + 1; x = x + 2;")?; - assert_eq!(engine.eval_with_scope::(&mut scope, false, "x")?, 12); - assert_eq!( - engine.eval_with_scope::<()>(&mut scope, false, "{let x = 3}")?, - () - ); - assert_eq!(engine.eval_with_scope::(&mut scope, false, "x")?, 12); + engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; + assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 9); + engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?; + assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 12); + assert_eq!(engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?, ()); + assert_eq!(engine.eval_with_scope::(&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::(&mut scope, false, "x")?; + let result = engine.eval_with_scope::(&mut scope, "x")?; println!("result: {}", result); // should print 966 // Variable y is changed in the script - assert_eq!(scope.get_value::("y").unwrap(), 1); + assert_eq!( + scope + .get_value::("y") + .ok_or(EvalAltResult::ErrorRuntime( + "variable y not found".into(), + Default::default() + ))?, + 1 + ); Ok(()) } diff --git a/tests/while_loop.rs b/tests/while_loop.rs index 8dc1ae85..fa76ea17 100644 --- a/tests/while_loop.rs +++ b/tests/while_loop.rs @@ -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::( + engine.eval::( "let x = 0; while x < 10 { x = x + 1; if x > 5 { \ break } } x", )?,