diff --git a/Cargo.toml b/Cargo.toml index 43339787..87cd6436 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai" -version = "0.11.0" +version = "0.12.0" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] description = "Embedded scripting for Rust" @@ -17,19 +17,17 @@ keywords = [ "scripting" ] categories = [ "no-std", "embedded", "parser-implementations" ] [dependencies] -num-traits = "*" +num-traits = { version = "0.2.11", default-features = false } [features] -#default = ["no_function", "no_index", "no_object", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize", "sync"] +#default = ["no_stdlib", "no_function", "no_index", "no_object", "no_float", "only_i32", "unchecked", "no_optimize", "sync"] default = [] 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 no_object = [] # no custom objects no_optimize = [] # no script optimizer -optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i64 = [] # set INT=i64 (default) and disable support for all other integer types sync = [] # restrict to only types that implement Send + Sync @@ -37,6 +35,10 @@ sync = [] # restrict to only types that implement Send + Sync # compiling for no-std no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm" ] +# other developer features +no_stdlib = [] # do not register the standard library +optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing + [profile.release] lto = "fat" codegen-units = 1 @@ -44,21 +46,21 @@ codegen-units = 1 #panic = 'abort' # remove stack backtrace for no-std [dependencies.libm] -version = "*" +version = "0.2.1" optional = true [dependencies.core-error] -version = "*" +version = "0.0.0" features = ["alloc"] optional = true [dependencies.hashbrown] -version = "*" +version = "0.7.1" default-features = false features = ["ahash", "nightly", "inline-more"] optional = true [dependencies.ahash] -version = "*" +version = "0.3.2" default-features = false optional = true diff --git a/README.md b/README.md index 51b6d637..c073d244 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Rhai's current features set: to do checked arithmetic operations); for [`no_std`] builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. -**Note:** Currently, the version is 0.11.0, so the language and API's may change before they stabilize. +**Note:** Currently, the version is 0.12.0, so the language and API's may change before they stabilize. Installation ------------ @@ -36,7 +36,7 @@ Install the Rhai crate by adding this line to `dependencies`: ```toml [dependencies] -rhai = "0.11.0" +rhai = "0.12.0" ``` Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/): @@ -59,26 +59,24 @@ Beware that in order to use pre-releases (e.g. alpha and beta), the exact versio Optional features ----------------- -| Feature | Description | -| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `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 not needed. | -| `no_index` | Disable arrays and indexing features if not needed. | -| `no_object` | Disable support for custom types and objects. | -| `no_float` | Disable floating-point numbers and math if not needed. | -| `no_optimize` | Disable the script optimizer. | -| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | -| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | -| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | -| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Engine`], [`Scope`] and `AST` are all `Send + Sync`. | +| Feature | Description | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `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 not needed. | +| `no_index` | Disable [arrays] and indexing features if not needed. | +| `no_object` | Disable support for custom types and objects. | +| `no_float` | Disable floating-point numbers and math if not needed. | +| `no_optimize` | Disable the script optimizer. | +| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | +| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | +| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | +| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Engine`], [`Scope`] and `AST` are all `Send + Sync`. | By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here to opt-**out** of certain functionalities that are not needed. Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. [`unchecked`]: #optional-features -[`no_stdlib`]: #optional-features [`no_index`]: #optional-features [`no_float`]: #optional-features [`no_function`]: #optional-features @@ -104,7 +102,7 @@ A number of examples can be found in the `examples` folder: | Example | Description | | ------------------------------------------------------------------ | --------------------------------------------------------------------------- | -| [`arrays_and_structs`](examples/arrays_and_structs.rs) | demonstrates registering a new type to Rhai and the usage of arrays on it | +| [`arrays_and_structs`](examples/arrays_and_structs.rs) | demonstrates registering a new type to Rhai and the usage of [arrays] on it | | [`custom_types_and_methods`](examples/custom_types_and_methods.rs) | shows how to register a type and methods for it | | [`hello`](examples/hello.rs) | simple example that evaluates an expression and prints the result | | [`no_std`](examples/no_std.rs) | example to test out `no-std` builds | @@ -129,7 +127,7 @@ There are also a number of examples scripts that showcase Rhai's features, all i | Language feature scripts | Description | | ---------------------------------------------------- | ------------------------------------------------------------- | -| [`array.rhai`](scripts/array.rhai) | arrays in Rhai | +| [`array.rhai`](scripts/array.rhai) | [arrays] in Rhai | | [`assignment.rhai`](scripts/assignment.rhai) | variable declarations | | [`comments.rhai`](scripts/comments.rhai) | just comments | | [`for1.rhai`](scripts/for1.rhai) | for loops | @@ -141,7 +139,7 @@ There are also a number of examples scripts that showcase Rhai's features, all i | [`op1.rhai`](scripts/op1.rhai) | just a simple addition | | [`op2.rhai`](scripts/op2.rhai) | simple addition and multiplication | | [`op3.rhai`](scripts/op3.rhai) | change evaluation order with parenthesis | -| [`string.rhai`](scripts/string.rhai) | string operations | +| [`string.rhai`](scripts/string.rhai) | [string] operations | | [`while.rhai`](scripts/while.rhai) | while loop | | Example scripts | Description | @@ -160,23 +158,27 @@ Hello world [`Engine`]: #hello-world -To get going with Rhai, create an instance of the scripting engine and then call `eval`: +To get going with Rhai, create an instance of the scripting engine via `Engine::new` and then call the `eval` method: ```rust use rhai::{Engine, EvalAltResult}; fn main() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); let result = engine.eval::("40 + 2")?; - println!("Answer: {}", result); // prints 42 + println!("Answer: {}", result); // prints 42 Ok(()) } ``` +`EvalAltResult` is a Rust `enum` containing all errors encountered during the parsing or evaluation process. + +### Script evaluation + The type parameter is used to specify the type of the return value, which _must_ match the actual type or an error is returned. Rhai is very strict here. There are two ways to specify the return type - _turbofish_ notation, or type inference. @@ -194,6 +196,8 @@ Evaluate a script file directly: let result = engine.eval_file::("hello_world.rhai".into())?; // 'eval_file' takes a 'PathBuf' ``` +### Compiling scripts (to AST) + To repeatedly evaluate a script, _compile_ it first into an AST (abstract syntax tree) form: ```rust @@ -203,7 +207,7 @@ let ast = engine.compile("40 + 2")?; for _ in 0..42 { let result: i64 = engine.eval_ast(&ast)?; - println!("Answer #{}: {}", i, result); // prints 42 + println!("Answer #{}: {}", i, result); // prints 42 } ``` @@ -213,8 +217,9 @@ Compiling a script file is also supported: let ast = engine.compile_file("hello_world.rhai".into())?; ``` -Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - -via `call_fn` or its cousins `call_fn1` (one argument) and `call_fn0` (no argument). +### Calling Rhai functions from Rust + +Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust via `call_fn`. ```rust // Define functions in a script. @@ -239,25 +244,89 @@ let ast = engine.compile(true, // A custom scope can also contain any variables/constants available to the functions let mut scope = Scope::new(); -// Evaluate a function defined in the script, passing arguments into the script as a tuple -// if there are more than one. Beware, arguments must be of the correct types because -// Rhai does not have built-in type conversions. If arguments of the wrong types are passed, -// the Engine will not find the function. +// Evaluate a function defined in the script, passing arguments into the script as a tuple. +// Beware, arguments must be of the correct types because Rhai does not have built-in type conversions. +// If arguments of the wrong types are passed, the Engine will not find the function. let result: i64 = engine.call_fn(&mut scope, &ast, "hello", ( String::from("abc"), 123_i64 ) )?; // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // put arguments in a tuple -let result: i64 = engine.call_fn1(&mut scope, &ast, "hello", 123_i64)? -// ^^^^^^^^ use 'call_fn1' for one argument +let result: i64 = engine.call_fn(&mut scope, &ast, "hello", (123_i64,) )? +// ^^^^^^^^^^ tuple of one -let result: i64 = engine.call_fn0(&mut scope, &ast, "hello")? -// ^^^^^^^^ use 'call_fn0' for no arguments +let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )? +// ^^ unit = tuple of zero +``` + +### Creating Rust anonymous functions from Rhai script + +[`Func`]: #creating-rust-anonymous-functions-from-rhai-script + +It is possible to further encapsulate a script in Rust such that it becomes a normal Rust function. +Such an _anonymous function_ is basically a boxed closure, very useful as call-back functions. +Creating them is accomplished via the `Func` trait which contains `create_from_script` +(as well as its companion method `create_from_ast`): + +```rust +use rhai::{Engine, Func}; // use 'Func' for 'create_from_script' + +let engine = Engine::new(); // create a new 'Engine' just for this + +let script = "fn calc(x, y) { x + y.len() < 42 }"; + +// Func takes two type parameters: +// 1) a tuple made up of the types of the script function's parameters +// 2) the return type of the script function +// +// 'func' will have type Box Result> and is callable! +let func = Func::<(i64, String), bool>::create_from_script( +// ^^^^^^^^^^^^^ function parameter types in tuple + + engine, // the 'Engine' is consumed into the closure + script, // the script, notice number of parameters must match + "calc" // the entry-point function name +)?; + +func(123, "hello".to_string())? == false; // call the anonymous function + +schedule_callback(func); // pass it as a callback to another function + +// Although there is nothing you can't do by manually writing out the closure yourself... +let engine = Engine::new(); +let ast = engine.compile(script)?; +schedule_callback(Box::new(move |x: i64, y: String| -> Result { + engine.call_fn(&mut Scope::new(), &ast, "calc", (x, y)) +})); +``` + +Raw `Engine` +------------ + +[raw `Engine`]: #raw-engine + +`Engine::new` creates a scripting [`Engine`] with common functionalities (e.g. printing to the console via `print`). +In many controlled embedded environments, however, these are not needed. + +Use `Engine::new_raw` to create a _raw_ `Engine`, in which: + +* the `print` and `debug` statements do nothing instead of displaying to the console (see [`print` and `debug`](#print-and-debug) below) +* the _standard library_ of utility functions is _not_ loaded by default (load it using the `register_stdlib` method). + +```rust +let mut engine = Engine::new_raw(); // create a 'raw' Engine + +engine.register_stdlib(); // register the standard library manually + +engine. ``` Evaluate expressions only ------------------------- +[`eval_expression`]: #evaluate-expressions-only +[`eval_expression_with_scope`]: #evaluate-expressions-only + Sometimes a use case does not require a full-blown scripting _language_, but only needs to evaluate _expressions_. In these cases, use the `compile_expression` and `eval_expression` methods or their `_with_scope` variants. @@ -265,8 +334,8 @@ In these cases, use the `compile_expression` and `eval_expression` methods or th let result = engine.eval_expression::("2 + (10 + 10) * 2")?; ``` -When evaluation _expressions_, no control-flow statement (e.g. `if`, `while`, `for`) is not supported and will be -parse errors when encountered - not even variable assignments. +When evaluation _expressions_, no full-blown statement (e.g. `if`, `while`, `for`) - not even variable assignments - +is supported and will be considered parse errors when encountered. ```rust // The following are all syntax errors because the script is not an expression. @@ -280,6 +349,7 @@ Values and types [`type_of()`]: #values-and-types [`to_string()`]: #values-and-types +[`()`]: #values-and-types The following primitive types are supported natively: @@ -292,25 +362,24 @@ The following primitive types are supported natively: | **Unicode string** | `String` (_not_ `&str`) | `"string"` | `"hello"` etc. | | **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ? ? ? ]"` | | **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` | +| **Timestamp** (implemented in standard library) | `std::time::Instant` | `"timestamp"` | _not supported_ | | **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | | **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | | **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | | **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` | `""` _(empty string)_ | -[`()`]: #values-and-types - All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust. The default integer type is `i64`. If other integer types are not needed, it is possible to exclude them and make a smaller build with the [`only_i64`] feature. -If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, -including `i64`. This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty. +If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`. +This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty. If no floating-point is needed or supported, use the [`no_float`] feature to remove it. -The `to_string` function converts a standard type into a string for display purposes. +The `to_string` function converts a standard type into a [string] for display purposes. The `type_of` function detects the actual type of a value. This is useful because all variables are [`Dynamic`] in nature. @@ -364,8 +433,8 @@ if type_of(mystery) == "i64" { } ``` -In Rust, sometimes a `Dynamic` forms part of a returned value - a good example is an array with `Dynamic` elements, -or an object map with `Dynamic` property values. To get the _real_ values, the actual value types _must_ be known in advance. +In Rust, sometimes a `Dynamic` forms part of a returned value - a good example is an [array] with `Dynamic` elements, +or an [object map] with `Dynamic` property values. To get the _real_ values, the actual value types _must_ be known in advance. There is no easy way for Rust to decide, at run-time, what type the `Dynamic` value is (short of using the `type_name` function and match against the name). @@ -374,7 +443,7 @@ The `cast` method (from the `rhai::AnyExt` trait) then converts the value into a Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails. ```rust -use rhai::AnyExt; // Pull in the trait. +use rhai::AnyExt; // pull in the trait. let list: Array = engine.eval("...")?; // return type is 'Array' let item = list[0]; // an element in an 'Array' is 'Dynamic' @@ -390,14 +459,14 @@ let value = item.try_cast::()?; // 'try_cast' does not panic whe The `type_name` method gets the name of the actual type as a static string slice, which you may match against. ```rust -use rhai::Any; // Pull in the trait. +use rhai::Any; // pull in the trait. let list: Array = engine.eval("...")?; // return type is 'Array' let item = list[0]; // an element in an 'Array' is 'Dynamic' match item.type_name() { // 'type_name' returns the name of the actual Rust type "i64" => ... - "std::string::String" => ... + "alloc::string::String" => ... "bool" => ... "path::to::module::TestStruct" => ... } @@ -423,6 +492,20 @@ let c = 'X'; // character print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88" ``` +Traits +------ + +A number of traits, under the `rhai::` module namespace, provide additional functionalities. + +| Trait | Description | Methods | +| ------------------- | --------------------------------------------------------------------------------- | --------------------------------------- | +| `Any` | Generic trait that represents a [`Dynamic`] type | `type_id`, `type_name`, `into_dynamic` | +| `AnyExt` | Extension trait to allows casting of a [`Dynamic`] value to Rust types | `cast`, `try_cast` | +| `RegisterFn` | Trait for registering functions | `register_fn` | +| `RegisterDynamicFn` | Trait for registering functions returning [`Dynamic`] | `register_dynamic_fn` | +| `RegisterResultFn` | Trait for registering fallible functions returning `Result<`_T_`, EvalAltResult>` | `register_result_fn` | +| `Func` | Trait for creating anonymous functions from script | `create_from_ast`, `create_from_script` | + Working with functions ---------------------- @@ -431,8 +514,8 @@ To call these functions, they need to be registered with the [`Engine`]. ```rust use rhai::{Engine, EvalAltResult}; -use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn` -use rhai::{Any, Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn` +use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn' +use rhai::{Any, Dynamic, RegisterDynamicFn}; // use 'RegisterDynamicFn' trait for 'register_dynamic_fn' // Normal function fn add(x: i64, y: i64) -> i64 { @@ -446,7 +529,7 @@ fn get_an_any() -> Dynamic { fn main() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); engine.register_fn("add", add); @@ -469,7 +552,7 @@ To return a [`Dynamic`] value from a Rust function, use the `into_dynamic()` met (under the `rhai::Any` trait) to convert it. ```rust -use rhai::Any; // Pull in the trait +use rhai::Any; // pull in the trait fn decide(yes_no: bool) -> Dynamic { if yes_no { @@ -497,7 +580,7 @@ fn show_it(x: &mut T) -> () { fn main() { - let mut engine = Engine::new(); + let engine = Engine::new(); engine.register_fn("print", show_it as fn(x: &mut i64)->()); engine.register_fn("print", show_it as fn(x: &mut bool)->()); @@ -519,13 +602,13 @@ and the error text gets converted into `EvalAltResult::ErrorRuntime`. ```rust use rhai::{Engine, EvalAltResult, Position}; -use rhai::RegisterResultFn; // use `RegisterResultFn` trait for `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!".into()) // short-cut to create EvalAltResult } else { Ok(x / y) } @@ -533,13 +616,13 @@ fn safe_divide(x: i64, y: i64) -> Result { fn main() { - let mut engine = Engine::new(); + let engine = Engine::new(); // Fallible functions that return Result values must use register_result_fn() engine.register_result_fn("divide", safe_divide); if let Err(error) = engine.eval::("divide(40, 0)") { - println!("Error: {:?}", error); // prints ErrorRuntime("Division by zero detected!", (1, 1)") + println!("Error: {:?}", error); // prints ErrorRuntime("Division by zero detected!", (1, 1)") } } ``` @@ -584,7 +667,7 @@ impl TestStruct { fn main() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); engine.register_type::(); @@ -593,7 +676,7 @@ fn main() -> Result<(), EvalAltResult> let result = engine.eval::("let x = new_ts(); x.update(); x")?; - println!("result: {}", result.field); // prints 42 + println!("result: {}", result.field); // prints 42 Ok(()) } @@ -622,7 +705,7 @@ impl TestStruct { } } -let mut engine = Engine::new(); +let engine = Engine::new(); engine.register_type::(); ``` @@ -712,7 +795,7 @@ impl TestStruct { } } -let mut engine = Engine::new(); +let engine = Engine::new(); engine.register_type::(); @@ -748,7 +831,7 @@ use rhai::{Engine, Scope, EvalAltResult}; fn main() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); // First create the state let mut scope = Scope::new(); @@ -957,7 +1040,7 @@ number = -5 - +5; Numeric functions ----------------- -The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on +The following standard functions (defined in the standard library but excluded if using a [raw `Engine`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: | Function | Description | @@ -968,7 +1051,7 @@ The following standard functions (defined in the standard library but excluded i 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 using a [raw `Engine`]) operate on `f64` only: | Category | Functions | | ---------------- | ------------------------------------------------------------ | @@ -984,13 +1067,31 @@ The following standard functions (defined in the standard library but excluded i Strings and Chars ----------------- +[string]: #strings-and-chars +[strings]: #strings-and-chars +[char]: #strings-and-chars + String and char literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') and hex ('`\x`_xx_') escape sequences. Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full, 32-bit extended Unicode code points. -Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`s!), but there are major differences. +Standard escape sequences: + +| Escape sequence | Meaning | +| --------------- | ------------------------------ | +| `\\` | back-slash `\` | +| `\t` | tab | +| `\r` | carriage-return `CR` | +| `\n` | line-feed `LF` | +| `\"` | double-quote `"` in strings | +| `\'` | single-quote `'` in characters | +| `\x`_xx_ | Unicode in 2-digit hex | +| `\u`_xxxx_ | Unicode in 4-digit hex | +| `\U`_xxxxxxxx_ | Unicode in 8-digit hex | + +Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`'s!), but there are major differences. In Rhai a string is the same as an array of Unicode characters and can be directly indexed (unlike Rust). This is similar to most other languages where strings are internally represented not as UTF-8 but as arrays of multi-byte Unicode characters. @@ -998,7 +1099,7 @@ Individual characters within a Rhai string can also be replaced just as if the s In Rhai, there is also no separate concepts of `String` and `&str` as in Rust. Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded -if [`no_stdlib`]). This is particularly useful when printing output. +if using a [raw `Engine`]). This is particularly useful when printing output. [`type_of()`] a string returns `"string"`. @@ -1039,22 +1140,29 @@ record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line // (disabled with 'no_index') record[4] = '\x58'; // 0x58 = 'X' record == "Bob X. Davis: age 42 ❤\n"; + +// Use 'in' to test if a substring (or character) exists in a string +"Davis" in record == true; +'X' in record == true; +'C' in record == false; ``` -The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on strings: +### Built-in functions -| Function | Description | -| ---------- | ------------------------------------------------------------------------ | -| `len` | returns the number of characters (not number of bytes) in the string | -| `pad` | pads the string with an character until a specified number of characters | -| `append` | Adds a character or a string to the end of another string | -| `clear` | empties the string | -| `truncate` | cuts off the string at exactly a specified number of characters | -| `contains` | checks if a certain character or sub-string occurs in the string | -| `replace` | replaces a substring with another | -| `trim` | trims the string | +The following standard methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on strings: -Examples: +| Function | Parameter(s) | Description | +| ---------- | ------------------------------------- | -------------------------------------------------------------------- | +| `len` | _none_ | returns the number of characters (not number of bytes) in the string | +| `pad` | character to pad, target length | pads the string with an character to a specified length | +| `append` | character/string to append | Adds a character or a string to the end of another string | +| `clear` | _none_ | empties the string | +| `truncate` | target length | cuts off the string at exactly a specified number of characters | +| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string | +| `replace` | target sub-string, replacement string | replaces a substring with another | +| `trim` | _none_ | trims the string of whitespace at the beginning and end | + +### Examples ```rust let full_name == " Bob C. Davis "; @@ -1086,6 +1194,10 @@ full_name.len() == 0; Arrays ------ +[array]: #arrays +[arrays]: #arrays +[`Array`]: #arrays + Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices. Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`'. All elements stored in an array are [`Dynamic`], and the array can freely grow or shrink with elements added or removed. @@ -1094,27 +1206,51 @@ The Rust type of a Rhai array is `rhai::Array`. [`type_of()`] an array returns ` Arrays are disabled via the [`no_index`] feature. -The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on arrays: +### Built-in functions -| Function | Description | -| ------------ | ------------------------------------------------------------------------------------- | -| `push` | inserts an element at the end | -| `append` | concatenates the second array to the end of the first | -| `+` operator | concatenates the first array with the second | -| `pop` | removes the last element and returns it ([`()`] if empty) | -| `shift` | removes the first element and returns it ([`()`] if empty) | -| `len` | returns the number of elements | -| `pad` | pads the array with an element until a specified length | -| `clear` | empties the array | -| `truncate` | cuts off the array at exactly a specified length (discarding all subsequent elements) | +The following methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on arrays: -Examples: +| Function | Parameter(s) | Description | +| ------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `push` | element to insert | inserts an element at the end | +| `append` | array to append | concatenates the second array to the end of the first | +| `+` operator | first array, second array | concatenates the first array with the second | +| `insert` | element to insert, position
(beginning if <= 0, end if >= length) | insert an element at a certain index | +| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | +| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | +| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid | +| `len` | _none_ | returns the number of elements | +| `pad` | element to pad, target length | pads the array with an element until a specified length | +| `clear` | _none_ | empties the array | +| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | + +### Examples ```rust -let y = [1, 2, 3]; // array literal with 3 elements -y[1] = 42; +let y = [2, 3]; // array literal with 2 elements -print(y[1]); // prints 42 +y.insert(0, 1); // insert element at the beginning +y.insert(999, 4); // insert element at the end + +y.len() == 4; + +y[0] == 1; +y[1] == 2; +y[2] == 3; +y[3] == 4; + +(1 in y) == true; // use 'in' to test if an item exists in the array +(42 in y) == false; + +y[1] = 42; // array elements can be reassigned + +(42 in y) == true; + +y.remove(2) == 3; // remove element + +y.len() == 3; + +y[2] == 4; // elements after the removed element are shifted ts.list = y; // arrays can be assigned completely (by value copy) let foo = ts.list[1]; @@ -1136,7 +1272,7 @@ foo == 1; y.push(4); // 4 elements y.push(5); // 5 elements -print(y.len()); // prints 5 +y.len() == 5; let first = y.shift(); // remove the first element, 4 elements remaining first == 1; @@ -1144,7 +1280,7 @@ first == 1; let last = y.pop(); // remove the last element, 3 elements remaining last == 5; -print(y.len()); // prints 3 +y.len() == 3; for item in y { // arrays can be iterated with a 'for' statement print(item); @@ -1152,15 +1288,15 @@ for item in y { // arrays can be iterated with a 'for' statement y.pad(10, "hello"); // pad the array up to 10 elements -print(y.len()); // prints 10 +y.len() == 10; y.truncate(5); // truncate the array to 5 elements -print(y.len()); // prints 5 +y.len() == 5; y.clear(); // empty the array -print(y.len()); // prints 0 +y.len() == 0; ``` `push` and `pad` are only defined for standard built-in types. For custom types, type-specific versions must be registered: @@ -1172,34 +1308,40 @@ engine.register_fn("push", |list: &mut Array, item: MyType| list.push(Box::new(i Object maps ----------- +[object map]: #object-maps +[object maps]: #object-maps + Object maps are dictionaries. Properties are all [`Dynamic`] and can be freely added and retrieved. Object map literals are built within braces '`#{`' ... '`}`' (_name_ `:` _value_ syntax similar to Rust) and separated by commas '`,`'. The property _name_ can be a simple variable name following the same -naming rules as [variables], or an arbitrary string literal. +naming rules as [variables], or an arbitrary [string] literal. Property values can be accessed via the dot notation (_object_ `.` _property_) or index notation (_object_ `[` _property_ `]`). The dot notation allows only property names that follow the same naming rules as [variables]. -The index notation allows setting/getting properties of arbitrary names (even the empty string). +The index notation allows setting/getting properties of arbitrary names (even the empty [string]). -**Important:** Trying to read a non-existent property returns `()` instead of causing an error. +**Important:** Trying to read a non-existent property returns [`()`] instead of causing an error. The Rust type of a Rhai object map is `rhai::Map`. [`type_of()`] an object map returns `"map"`. Object maps are disabled via the [`no_object`] feature. -The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on object maps: +### Built-in functions -| Function | Description | -| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `has` | does the object map contain a property of a particular name? | -| `len` | returns the number of properties | -| `clear` | empties the object map | -| `mixin` | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) | -| `+` operator | merges the first object map with the second | -| `keys` | returns an array of all the property names (in random order) | -| `values` | returns an array of all the property values (in random order) | +The following methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on object maps: -Examples: +| Function | Parameter(s) | Description | +| ------------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `has` | property name | does the object map contain a property of a particular name? | +| `len` | _none_ | returns the number of properties | +| `clear` | _none_ | empties the object map | +| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) | +| `mixin` | second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) | +| `+` operator | first object map, second object map | merges the first object map with the second | +| `keys` | _none_ | returns an [array] of all the property names (in random order) | +| `values` | _none_ | returns an [array] of all the property values (in random order) | + +### Examples ```rust let y = #{ // object map literal with 3 properties @@ -1215,9 +1357,12 @@ y.a = 42; // access via dot notation y.baz!$@ = 42; // <- syntax error: only proper variable names allowed in dot notation y."baz!$@" = 42; // <- syntax error: strings not allowed in dot notation -print(y.a); // prints 42 +y.a == 42; -print(y["baz!$@"]); // prints 123.456 - access via index notation +y["baz!$@"] == 123.456; // access via index notation + +"baz!$@" in y == true; // use 'in' to test if a property exists in the object map, prints true +("z" in y) == false; ts.obj = y; // object maps can be assigned completely (by value copy) let foo = ts.list.a; @@ -1239,10 +1384,15 @@ foo == 42; y.has("a") == true; y.has("xyz") == false; -y.xyz == (); // A non-existing property returns '()' +y.xyz == (); // a non-existing property returns '()' y["xyz"] == (); -print(y.len()); // prints 3 +y.len() == 3; + +y.remove("a") == 1; // remove property + +y.len() == 2; +y.has("a") == false; for name in keys(y) { // get an array of all the property names via the 'keys' function print(name); @@ -1254,7 +1404,79 @@ for val in values(y) { // get an array of all the property values via the 'valu y.clear(); // empty the object map -print(y.len()); // prints 0 +y.len() == 0; +``` + +### Parsing from JSON + +The syntax for an object map is extremely similar to JSON, with the exception of `null` values which can +technically be mapped to [`()`]. A valid JSON string does not start with a hash character `#` while a +Rhai object map does - that's the major difference! + +JSON numbers are all floating-point while Rhai supports integers (`INT`) and floating-point (`FLOAT`) if +the [`no_float`] feature is not turned on. Most common generators of JSON data distinguish between +integer and floating-point values by always serializing a floating-point number with a decimal point +(i.e. `123.0` instead of `123` which is assumed to be an integer). This style can be used successfully +with Rhai object maps. + +Use the `parse_json` method to parse a piece of JSON into an object map: + +```rust +// JSON string - notice that JSON property names are always quoted +// notice also that comments are acceptable within the JSON string +let json = r#"{ + "a": 1, // <- this is an integer number + "b": true, + "c": 123.0, // <- this is a floating-point number + "$d e f!": "hello", // <- any text can be a property name + "^^^!!!": [1,42,"999"], // <- value can be array or another hash + "z": null // <- JSON 'null' value + } +"#; + +// Parse the JSON expression as an object map +// Set the second boolean parameter to true in order to map 'null' to '()' +let map = engine.parse_json(json, true)?; + +map.len() == 6; // 'map' contains all properties int the JSON string + +// Put the object map into a 'Scope' +let mut scope = Scope::new(); +scope.push("map", map); + +let result = engine.eval_with_scope::(r#"map["^^^!!!"].len()"#)?; + +result == 3; // the object map is successfully used in the script +``` + +`timestamp`'s +------------- +[`timestamp`]: #timestamp-s + +Timestamps are provided by the standard library (excluded if using a [raw `Engine`]) via the `timestamp` +function. + +The Rust type of a timestamp is `std::time::Instant`. [`type_of()`] a timestamp returns `"timestamp"`. + +### Built-in functions + +The following methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on timestamps: + +| Function | Parameter(s) | Description | +| ------------ | ---------------------------------- | -------------------------------------------------------- | +| `elapsed` | _none_ | returns the number of seconds since the timestamp | +| `-` operator | later timestamp, earlier timestamp | returns the number of seconds between the two timestamps | + +### Examples + +```rust +let now = timestamp(); + +// Do some lengthy operation... + +if now.elapsed() > 30.0 { + print("takes too long (over 30 seconds)!") +} ``` Comparison operators @@ -1262,8 +1484,8 @@ Comparison operators Comparing most values of the same data type work out-of-the-box for standard types supported by the system. -However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system types - -`INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`. +However, if using a [raw `Engine`], comparisons can only be made between restricted system types - +`INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), [string], [array], `bool`, `char`. ```rust 42 == 42; // true @@ -1321,7 +1543,7 @@ number <<= 2; // number = number << 2 number >>= 1; // number = number >> 1 ``` -The `+=` operator can also be used to build strings: +The `+=` operator can also be used to build [strings]: ```rust let my_str = "abc"; @@ -1359,10 +1581,11 @@ if (decision) print("I've decided!"); Like Rust, `if` statements can also be used as _expressions_, replacing the `? :` conditional operators in other C-like languages. ```rust -let x = 1 + if true { 42 } else { 123 } / 2; +// The following is equivalent to C: int x = 1 + (decision ? 42 : 123) / 2; +let x = 1 + if decision { 42 } else { 123 } / 2; x == 22; -let x = if false { 42 }; // No else branch defaults to '()' +let x = if decision { 42 }; // no else branch defaults to '()' x == (); ``` @@ -1397,7 +1620,7 @@ loop { `for` loops ----------- -Iterating through a range or an array is provided by the `for` ... `in` loop. +Iterating through a range or an [array] is provided by the `for` ... `in` loop. ```rust let array = [1, 3, 5, 7, 9, 42]; @@ -1416,7 +1639,6 @@ for x in range(0, 50) { if x == 42 { break; } // break out of for loop } - // The 'range' function also takes a step for x in range(0, 50, 3) { // step by 3 if x > 10 { continue; } // skip to the next iteration @@ -1424,15 +1646,20 @@ for x in range(0, 50, 3) { // step by 3 if x == 42 { break; } // break out of for loop } -// Iterate through the values of an object map +// Iterate through object map let map = #{a:1, b:3, c:5, d:7, e:9}; -// Remember that keys are returned in random order +// Property names are returned in random order for x in keys(map) { if x > 10 { continue; } // skip to the next iteration print(x); if x == 42 { break; } // break out of for loop } + +// Property values are returned in random order +for val in values(map) { + print(val); +} ``` `return`-ing values @@ -1553,6 +1780,10 @@ fn do_addition(x) { } ``` +Unlike C/C++, functions can be defined _anywhere_ within the global level. A function does not need to be defined +prior to being used in a script; a statement in the script can freely call a function defined afterwards. +This is similar to Rust and many other modern languages. + ### Functions overloading Functions can be _overloaded_ and are resolved purely upon the function's _name_ and the _number_ of parameters @@ -1744,6 +1975,19 @@ An [`Engine`]'s optimization level is set via a call to `set_optimization_level` engine.set_optimization_level(rhai::OptimizationLevel::Full); ``` +If it is ever needed to _re_-optimize an `AST`, use the `optimize_ast` method: + +```rust +// Compile script to AST +let ast = engine.compile("40 + 2")?; + +// Create a new 'Scope' - put constants in it to aid optimization if using 'OptimizationLevel::Full' +let scope = Scope::new(); + +// Re-optimize the AST +let ast = engine.optimize_ast(&scope, &ast, OptimizationLevel::Full); +``` + When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_ and will _eagerly_ evaluated all function calls with constant arguments, using the result to replace the call. This also applies to all operators (which are implemented as functions). For instance, the same example above: @@ -1874,7 +2118,8 @@ print("z = " + z); // <- error: variable 'z' not found Script segments passed to `eval` execute inside the current [`Scope`], so they can access and modify _everything_, including all variables that are visible at that position in code! It is almost as if the script segments were -physically pasted in at the position of the `eval` call. +physically pasted in at the position of the `eval` call. But because of this, new functions cannot be defined +within an `eval` call, since functions can only be defined at the global level, not inside a function call! ```rust let script = "x += 32"; diff --git a/examples/hello.rs b/examples/hello.rs index 8527d466..c72a38b5 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -1,7 +1,7 @@ use rhai::{Engine, EvalAltResult, INT}; fn main() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); let result = engine.eval::("40 + 2")?; diff --git a/examples/no_std.rs b/examples/no_std.rs index 32cf14a2..ad9d9a8a 100644 --- a/examples/no_std.rs +++ b/examples/no_std.rs @@ -3,7 +3,7 @@ use rhai::{Engine, EvalAltResult, INT}; fn main() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); let result = engine.eval::("40 + 2")?; diff --git a/examples/repl.rs b/examples/repl.rs index d4679842..56f0ac69 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -145,9 +145,7 @@ fn main() { #[cfg(not(feature = "no_optimize"))] { - engine.set_optimization_level(OptimizationLevel::Full); - ast = engine.optimize_ast(&scope, r); - engine.set_optimization_level(OptimizationLevel::None); + ast = engine.optimize_ast(&scope, r, OptimizationLevel::Full); } #[cfg(feature = "no_optimize")] diff --git a/examples/reuse_scope.rs b/examples/reuse_scope.rs index 549ccea1..ab936656 100644 --- a/examples/reuse_scope.rs +++ b/examples/reuse_scope.rs @@ -1,7 +1,7 @@ use rhai::{Engine, EvalAltResult, Scope, INT}; fn main() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); let mut scope = Scope::new(); engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; diff --git a/scripts/primes.rhai b/scripts/primes.rhai index d09418e8..6d5a49d5 100644 --- a/scripts/primes.rhai +++ b/scripts/primes.rhai @@ -1,5 +1,7 @@ // This script uses the Sieve of Eratosthenes to calculate prime numbers. +let now = timestamp(); + const MAX_NUMBER_TO_CHECK = 10_000; // 1229 primes <= 10000 let prime_mask = []; @@ -24,3 +26,4 @@ for p in range(2, MAX_NUMBER_TO_CHECK) { } print("Total " + total_primes_found + " primes."); +print("Run time = " + now.elapsed() + " seconds."); diff --git a/scripts/speed_test.rhai b/scripts/speed_test.rhai index 1aa67276..5709d6ac 100644 --- a/scripts/speed_test.rhai +++ b/scripts/speed_test.rhai @@ -1,6 +1,7 @@ // This script runs 1 million iterations // to test the speed of the scripting engine. +let now = timestamp(); let x = 1_000_000; print("Ready... Go!"); @@ -9,4 +10,4 @@ while x > 0 { x = x - 1; } -print("Finished."); +print("Finished. Run time = " + now.elapsed() + " seconds."); diff --git a/src/api.rs b/src/api.rs index 047cf525..fde9b5d4 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,17 +1,15 @@ //! Module that defines the extern API of `Engine`. use crate::any::{Any, AnyExt, Dynamic}; -use crate::call::FuncArgs; -use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec}; +use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec, Map}; use crate::error::ParseError; +use crate::fn_call::FuncArgs; use crate::fn_register::RegisterFn; +use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::parser::{lex, parse, parse_global_expr, Position, AST}; use crate::result::EvalAltResult; use crate::scope::Scope; -#[cfg(not(feature = "no_optimize"))] -use crate::optimize::optimize_into_ast; - use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, @@ -334,7 +332,7 @@ impl<'e> Engine<'e> { /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::Engine; /// - /// let mut engine = Engine::new(); + /// let engine = Engine::new(); /// /// // Compile a script to an AST and store it for later evaluation /// let ast = engine.compile("40 + 2")?; @@ -345,8 +343,8 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn compile(&self, input: &str) -> Result { - self.compile_with_scope(&Scope::new(), input) + pub fn compile(&self, script: &str) -> Result { + self.compile_with_scope(&Scope::new(), script) } /// Compile a string into an `AST` using own scope, which can be used later for evaluation. @@ -386,9 +384,20 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn compile_with_scope(&self, scope: &Scope, input: &str) -> Result { - let tokens_stream = lex(input); - parse(&mut tokens_stream.peekable(), self, scope) + pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result { + self.compile_with_scope_and_optimization_level(scope, script, self.optimization_level) + } + + /// Compile a string into an `AST` using own scope at a specific optimization level. + pub(crate) fn compile_with_scope_and_optimization_level( + &self, + scope: &Scope, + script: &str, + optimization_level: OptimizationLevel, + ) -> Result { + let scripts = [script]; + let stream = lex(&scripts); + parse(&mut stream.peekable(), self, scope, optimization_level) } /// Read the contents of a file into a string. @@ -413,7 +422,7 @@ impl<'e> Engine<'e> { /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::Engine; /// - /// let mut engine = Engine::new(); + /// let engine = Engine::new(); /// /// // Compile a script file to an AST and store it for later evaluation. /// // Notice that a PathBuf is required which can easily be constructed from a string. @@ -466,10 +475,52 @@ impl<'e> Engine<'e> { scope: &Scope, path: PathBuf, ) -> Result { - Self::read_file(path).and_then(|contents| { - self.compile_with_scope(scope, &contents) - .map_err(|err| err.into()) - }) + Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?)) + } + + /// Parse a JSON string into a map. + /// + /// Set `has_null` to `true` in order to map `null` values to `()`. + /// Setting it to `false` will cause a _variable not found_ error during parsing. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// use rhai::{Engine, AnyExt}; + /// + /// let engine = Engine::new(); + /// + /// let map = engine.parse_json(r#"{"a":123, "b":42, "c":false, "d":null}"#, true)?; + /// + /// assert_eq!(map.len(), 4); + /// assert_eq!(map.get("a").cloned().unwrap().cast::(), 123); + /// assert_eq!(map.get("b").cloned().unwrap().cast::(), 42); + /// assert_eq!(map.get("c").cloned().unwrap().cast::(), false); + /// assert_eq!(map.get("d").cloned().unwrap().cast::<()>(), ()); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + pub fn parse_json(&self, json: &str, has_null: bool) -> Result { + let mut scope = Scope::new(); + + // Trims the JSON string and add a '#' in front + let scripts = ["#", json.trim()]; + let stream = lex(&scripts); + let ast = parse_global_expr( + &mut stream.peekable(), + self, + &scope, + OptimizationLevel::None, + )?; + + // Handle null - map to () + if has_null { + scope.push_constant("null", ()); + } + + self.eval_ast_with_scope(&mut scope, &ast) } /// Compile a string containing an expression into an `AST`, @@ -481,7 +532,7 @@ impl<'e> Engine<'e> { /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::Engine; /// - /// let mut engine = Engine::new(); + /// let engine = Engine::new(); /// /// // Compile a script to an AST and store it for later evaluation /// let ast = engine.compile_expression("40 + 2")?; @@ -492,8 +543,8 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn compile_expression(&self, input: &str) -> Result { - self.compile_expression_with_scope(&Scope::new(), input) + pub fn compile_expression(&self, script: &str) -> Result { + self.compile_expression_with_scope(&Scope::new(), script) } /// Compile a string containing an expression into an `AST` using own scope, @@ -538,10 +589,11 @@ impl<'e> Engine<'e> { pub fn compile_expression_with_scope( &self, scope: &Scope, - input: &str, + script: &str, ) -> Result { - let tokens_stream = lex(input); - parse_global_expr(&mut tokens_stream.peekable(), self, scope) + let scripts = [script]; + let stream = lex(&scripts); + parse_global_expr(&mut stream.peekable(), self, scope, self.optimization_level) } /// Evaluate a script file. @@ -552,7 +604,7 @@ impl<'e> Engine<'e> { /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::Engine; /// - /// let mut engine = Engine::new(); + /// let engine = Engine::new(); /// /// // Notice that a PathBuf is required which can easily be constructed from a string. /// let result = engine.eval_file::("script.rhai".into())?; @@ -560,7 +612,7 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_std"))] - pub fn eval_file(&mut self, path: PathBuf) -> Result { + pub fn eval_file(&self, path: PathBuf) -> Result { Self::read_file(path).and_then(|contents| self.eval::(&contents)) } @@ -572,7 +624,7 @@ impl<'e> Engine<'e> { /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::{Engine, Scope}; /// - /// let mut engine = Engine::new(); + /// let engine = Engine::new(); /// /// // Create initialized scope /// let mut scope = Scope::new(); @@ -585,7 +637,7 @@ impl<'e> Engine<'e> { /// ``` #[cfg(not(feature = "no_std"))] pub fn eval_file_with_scope( - &mut self, + &self, scope: &mut Scope, path: PathBuf, ) -> Result { @@ -600,14 +652,14 @@ impl<'e> Engine<'e> { /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::Engine; /// - /// let mut engine = Engine::new(); + /// let engine = Engine::new(); /// /// assert_eq!(engine.eval::("40 + 2")?, 42); /// # Ok(()) /// # } /// ``` - pub fn eval(&mut self, input: &str) -> Result { - self.eval_with_scope(&mut Scope::new(), input) + pub fn eval(&self, script: &str) -> Result { + self.eval_with_scope(&mut Scope::new(), script) } /// Evaluate a string with own scope. @@ -618,7 +670,7 @@ impl<'e> Engine<'e> { /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::{Engine, Scope}; /// - /// let mut engine = Engine::new(); + /// let engine = Engine::new(); /// /// // Create initialized scope /// let mut scope = Scope::new(); @@ -633,11 +685,13 @@ impl<'e> Engine<'e> { /// # } /// ``` pub fn eval_with_scope( - &mut self, + &self, scope: &mut Scope, - input: &str, + script: &str, ) -> Result { - let ast = self.compile(input).map_err(EvalAltResult::ErrorParsing)?; + // Since the AST will be thrown away afterwards, don't bother to optimize it + let ast = + self.compile_with_scope_and_optimization_level(scope, script, OptimizationLevel::None)?; self.eval_ast_with_scope(scope, &ast) } @@ -649,14 +703,14 @@ impl<'e> Engine<'e> { /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::Engine; /// - /// let mut engine = Engine::new(); + /// let engine = Engine::new(); /// /// assert_eq!(engine.eval_expression::("40 + 2")?, 42); /// # Ok(()) /// # } /// ``` - pub fn eval_expression(&mut self, input: &str) -> Result { - self.eval_expression_with_scope(&mut Scope::new(), input) + pub fn eval_expression(&self, script: &str) -> Result { + self.eval_expression_with_scope(&mut Scope::new(), script) } /// Evaluate a string containing an expression with own scope. @@ -667,7 +721,7 @@ impl<'e> Engine<'e> { /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::{Engine, Scope}; /// - /// let mut engine = Engine::new(); + /// let engine = Engine::new(); /// /// // Create initialized scope /// let mut scope = Scope::new(); @@ -678,14 +732,14 @@ impl<'e> Engine<'e> { /// # } /// ``` pub fn eval_expression_with_scope( - &mut self, + &self, scope: &mut Scope, - input: &str, + script: &str, ) -> Result { - let ast = self - .compile_expression(input) - .map_err(EvalAltResult::ErrorParsing)?; - + let scripts = [script]; + let stream = lex(&scripts); + // Since the AST will be thrown away afterwards, don't bother to optimize it + let ast = parse_global_expr(&mut stream.peekable(), self, scope, OptimizationLevel::None)?; self.eval_ast_with_scope(scope, &ast) } @@ -697,7 +751,7 @@ impl<'e> Engine<'e> { /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::Engine; /// - /// let mut engine = Engine::new(); + /// let engine = Engine::new(); /// /// // Compile a script to an AST and store it for later evaluation /// let ast = engine.compile("40 + 2")?; @@ -707,7 +761,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn eval_ast(&mut self, ast: &AST) -> Result { + pub fn eval_ast(&self, ast: &AST) -> Result { self.eval_ast_with_scope(&mut Scope::new(), ast) } @@ -719,7 +773,7 @@ impl<'e> Engine<'e> { /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::{Engine, Scope}; /// - /// let mut engine = Engine::new(); + /// let engine = Engine::new(); /// /// // Compile a script to an AST and store it for later evaluation /// let ast = engine.compile("x + 2")?; @@ -741,7 +795,7 @@ impl<'e> Engine<'e> { /// # } /// ``` pub fn eval_ast_with_scope( - &mut self, + &self, scope: &mut Scope, ast: &AST, ) -> Result { @@ -756,32 +810,25 @@ impl<'e> Engine<'e> { } pub(crate) fn eval_ast_with_scope_raw( - &mut self, + &self, scope: &mut Scope, ast: &AST, ) -> Result { - let statements = { - let AST(statements, functions) = ast; - self.fn_lib = Some(functions.clone()); - statements - }; - - let result = statements + ast.0 .iter() - .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0)); - - self.fn_lib = None; - - result.or_else(|err| match err { - EvalAltResult::Return(out, _) => Ok(out), - _ => Err(err), - }) + .try_fold(().into_dynamic(), |_, stmt| { + self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0) + }) + .or_else(|err| match err { + EvalAltResult::Return(out, _) => Ok(out), + _ => Err(err), + }) } /// 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. #[cfg(not(feature = "no_std"))] - pub fn consume_file(&mut self, path: PathBuf) -> Result<(), EvalAltResult> { + pub fn consume_file(&self, path: PathBuf) -> Result<(), EvalAltResult> { Self::read_file(path).and_then(|contents| self.consume(&contents)) } @@ -789,7 +836,7 @@ impl<'e> Engine<'e> { /// Useful for when you don't need the result, but still need to keep track of possible errors. #[cfg(not(feature = "no_std"))] pub fn consume_file_with_scope( - &mut self, + &self, scope: &mut Scope, path: PathBuf, ) -> Result<(), EvalAltResult> { @@ -798,127 +845,44 @@ impl<'e> Engine<'e> { /// 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(), input) + pub fn consume(&self, script: &str) -> Result<(), EvalAltResult> { + self.consume_with_scope(&mut Scope::new(), script) } /// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - pub fn consume_with_scope( - &mut self, - scope: &mut Scope, - input: &str, - ) -> Result<(), EvalAltResult> { - let tokens_stream = lex(input); - - let ast = parse(&mut tokens_stream.peekable(), self, scope) - .map_err(EvalAltResult::ErrorParsing)?; + pub fn consume_with_scope(&self, scope: &mut Scope, script: &str) -> Result<(), EvalAltResult> { + let scripts = [script]; + let stream = lex(&scripts); + // Since the AST will be thrown away afterwards, don't bother to optimize it + let ast = parse(&mut stream.peekable(), self, scope, OptimizationLevel::None)?; self.consume_ast_with_scope(scope, &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. - pub fn consume_ast(&mut self, ast: &AST) -> Result<(), EvalAltResult> { + pub fn consume_ast(&self, ast: &AST) -> Result<(), EvalAltResult> { self.consume_ast_with_scope(&mut Scope::new(), ast) } /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. pub fn consume_ast_with_scope( - &mut self, + &self, scope: &mut Scope, ast: &AST, ) -> Result<(), EvalAltResult> { - let statements = { - let AST(statements, functions) = ast; - self.fn_lib = Some(functions.clone()); - statements - }; - - let result = statements + ast.0 .iter() - .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0)); - - self.fn_lib = None; - - result.map(|_| ()).or_else(|err| match err { - EvalAltResult::Return(_, _) => Ok(()), - _ => Err(err), - }) - } - - /// Call a script function defined in an `AST` with no argument. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), rhai::EvalAltResult> { - /// # #[cfg(not(feature = "no_stdlib"))] - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::{Engine, Scope}; - /// - /// let mut engine = Engine::new(); - /// - /// let ast = engine.compile("fn num() { 42 + foo }")?; - /// - /// let mut scope = Scope::new(); - /// scope.push("foo", 42_i64); - /// - /// // Call the script-defined function - /// let result: i64 = engine.call_fn0(&mut scope, &ast, "num")?; - /// - /// assert_eq!(result, 84); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_function"))] - pub fn call_fn0( - &mut self, - scope: &mut Scope, - ast: &AST, - name: &str, - ) -> Result { - self.call_fn_internal(scope, ast, name, vec![]) - } - - /// Call a script function defined in an `AST` with one argument. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), rhai::EvalAltResult> { - /// # #[cfg(not(feature = "no_stdlib"))] - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::{Engine, Scope}; - /// - /// let mut engine = Engine::new(); - /// - /// let ast = engine.compile("fn inc(x) { x + foo }")?; - /// - /// let mut scope = Scope::new(); - /// scope.push("foo", 42_i64); - /// - /// // Call the script-defined function - /// let result: i64 = engine.call_fn1(&mut scope, &ast, "inc", 123_i64)?; - /// - /// assert_eq!(result, 165); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_function"))] - pub fn call_fn1( - &mut self, - scope: &mut Scope, - ast: &AST, - name: &str, - arg: A, - ) -> Result { - self.call_fn_internal(scope, ast, name, vec![arg.into_dynamic()]) + .try_fold(().into_dynamic(), |_, stmt| { + self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0) + }) + .map(|_| ()) + .or_else(|err| match err { + EvalAltResult::Return(_, _) => Ok(()), + _ => Err(err), + }) } /// Call a script function defined in an `AST` with multiple arguments. @@ -927,62 +891,56 @@ impl<'e> Engine<'e> { /// /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { - /// # #[cfg(not(feature = "no_stdlib"))] /// # #[cfg(not(feature = "no_function"))] /// # { /// use rhai::{Engine, Scope}; /// - /// let mut engine = Engine::new(); + /// let engine = Engine::new(); /// - /// let ast = engine.compile("fn add(x, y) { len(x) + y + foo }")?; + /// let ast = engine.compile(r" + /// fn add(x, y) { len(x) + y + foo } + /// fn add1(x) { len(x) + 1 + foo } + /// fn bar() { foo/2 } + /// ")?; /// /// let mut scope = Scope::new(); /// scope.push("foo", 42_i64); /// /// // Call the script-defined function - /// let result: i64 = engine.call_fn(&mut scope, &ast, "add", (String::from("abc"), 123_i64))?; - /// + /// let result: i64 = engine.call_fn(&mut scope, &ast, "add", ( String::from("abc"), 123_i64 ) )?; /// assert_eq!(result, 168); + /// + /// let result: i64 = engine.call_fn(&mut scope, &ast, "add1", ( String::from("abc"), ) )?; + /// // ^^^^^^^^^^^^^^^^^^^^^^^^ tuple of one + /// assert_eq!(result, 46); + /// + /// let result: i64 = engine.call_fn(&mut scope, &ast, "bar", () )?; + /// assert_eq!(result, 21); /// # } /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "no_function"))] pub fn call_fn( - &mut self, + &self, scope: &mut Scope, ast: &AST, name: &str, args: A, ) -> Result { - self.call_fn_internal(scope, ast, name, args.into_vec()) - } - - #[cfg(not(feature = "no_function"))] - fn call_fn_internal( - &mut self, - scope: &mut Scope, - ast: &AST, - name: &str, - mut arg_values: Vec, - ) -> Result { + let mut arg_values = args.into_vec(); let mut args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); + let fn_lib = Some(ast.1.as_ref()); + let pos = Position::none(); - self.fn_lib = Some(ast.1.clone()); - - let result = self - .call_fn_raw(Some(scope), name, &mut args, None, Position::none(), 0)? + self.call_fn_raw(Some(scope), fn_lib, name, &mut args, None, pos, 0)? .try_cast() .map_err(|a| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name((*a).type_name()).into(), - Position::none(), + pos, ) - }); - - self.fn_lib = None; - - result + }) } /// Optimize the `AST` with constants defined in an external Scope. @@ -997,13 +955,14 @@ impl<'e> Engine<'e> { /// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope /// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running. #[cfg(not(feature = "no_optimize"))] - pub fn optimize_ast(&self, scope: &Scope, ast: AST) -> AST { - optimize_into_ast( - self, - scope, - ast.0, - ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect(), - ) + pub fn optimize_ast( + &self, + scope: &Scope, + ast: AST, + optimization_level: OptimizationLevel, + ) -> AST { + let fn_lib = ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect(); + optimize_into_ast(self, scope, ast.0, fn_lib, optimization_level) } /// Override default action of `print` (print to stdout using `println!`) @@ -1012,22 +971,24 @@ impl<'e> Engine<'e> { /// /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// # use std::sync::RwLock; /// use rhai::Engine; /// - /// let mut result = String::from(""); + /// let result = RwLock::new(String::from("")); /// { - /// let mut engine = Engine::new(); + /// let mut engine = Engine::new(); /// - /// // Override action of 'print' function - /// engine.on_print(|s| result.push_str(s)); - /// engine.consume("print(40 + 2);")?; + /// // Override action of 'print' function + /// engine.on_print(|s| result.write().unwrap().push_str(s)); + /// + /// engine.consume("print(40 + 2);")?; /// } - /// assert_eq!(result, "42"); + /// assert_eq!(*result.read().unwrap(), "42"); /// # Ok(()) /// # } /// ``` #[cfg(feature = "sync")] - pub fn on_print(&mut self, callback: impl FnMut(&str) + Send + Sync + 'e) { + pub fn on_print(&mut self, callback: impl Fn(&str) + Send + Sync + 'e) { self.on_print = Some(Box::new(callback)); } /// Override default action of `print` (print to stdout using `println!`) @@ -1036,22 +997,24 @@ impl<'e> Engine<'e> { /// /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// # use std::sync::RwLock; /// use rhai::Engine; /// - /// let mut result = String::from(""); + /// let result = RwLock::new(String::from("")); /// { - /// let mut engine = Engine::new(); + /// let mut engine = Engine::new(); /// - /// // Override action of 'print' function - /// engine.on_print(|s| result.push_str(s)); - /// engine.consume("print(40 + 2);")?; + /// // Override action of 'print' function + /// engine.on_print(|s| result.write().unwrap().push_str(s)); + /// + /// engine.consume("print(40 + 2);")?; /// } - /// assert_eq!(result, "42"); + /// assert_eq!(*result.read().unwrap(), "42"); /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "sync"))] - pub fn on_print(&mut self, callback: impl FnMut(&str) + 'e) { + pub fn on_print(&mut self, callback: impl Fn(&str) + 'e) { self.on_print = Some(Box::new(callback)); } @@ -1061,22 +1024,24 @@ impl<'e> Engine<'e> { /// /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// # use std::sync::RwLock; /// use rhai::Engine; /// - /// let mut result = String::from(""); + /// let result = RwLock::new(String::from("")); /// { - /// let mut engine = Engine::new(); + /// let mut engine = Engine::new(); /// - /// // Override action of 'debug' function - /// engine.on_debug(|s| result.push_str(s)); - /// engine.consume(r#"debug("hello");"#)?; + /// // Override action of 'print' function + /// engine.on_debug(|s| result.write().unwrap().push_str(s)); + /// + /// engine.consume(r#"debug("hello");"#)?; /// } - /// assert_eq!(result, "\"hello\""); + /// assert_eq!(*result.read().unwrap(), r#""hello""#); /// # Ok(()) /// # } /// ``` #[cfg(feature = "sync")] - pub fn on_debug(&mut self, callback: impl FnMut(&str) + Send + Sync + 'e) { + pub fn on_debug(&mut self, callback: impl Fn(&str) + Send + Sync + 'e) { self.on_debug = Some(Box::new(callback)); } /// Override default action of `debug` (print to stdout using `println!`) @@ -1085,22 +1050,24 @@ impl<'e> Engine<'e> { /// /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// # use std::sync::RwLock; /// use rhai::Engine; /// - /// let mut result = String::from(""); + /// let result = RwLock::new(String::from("")); /// { - /// let mut engine = Engine::new(); + /// let mut engine = Engine::new(); /// - /// // Override action of 'debug' function - /// engine.on_debug(|s| result.push_str(s)); - /// engine.consume(r#"debug("hello");"#)?; + /// // Override action of 'print' function + /// engine.on_debug(|s| result.write().unwrap().push_str(s)); + /// + /// engine.consume(r#"debug("hello");"#)?; /// } - /// assert_eq!(result, "\"hello\""); + /// assert_eq!(*result.read().unwrap(), r#""hello""#); /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "sync"))] - pub fn on_debug(&mut self, callback: impl FnMut(&str) + 'e) { + pub fn on_debug(&mut self, callback: impl Fn(&str) + 'e) { self.on_debug = Some(Box::new(callback)); } } diff --git a/src/builtin.rs b/src/builtin.rs index b6baed63..f77b4f4a 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -27,6 +27,7 @@ use crate::stdlib::{ format, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}, string::{String, ToString}, + time::Instant, vec::Vec, {i32, i64, u32}, }; @@ -57,6 +58,34 @@ macro_rules! reg_op_result1 { ) } +macro_rules! reg_cmp { + ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + $( + $self.register_fn($x, $op as fn(x: $y, y: $y)->bool); + )* + ) +} + +// Comparison operators +fn lt(x: T, y: T) -> bool { + x < y +} +fn lte(x: T, y: T) -> bool { + x <= y +} +fn gt(x: T, y: T) -> bool { + x > y +} +fn gte(x: T, y: T) -> bool { + x >= y +} +fn eq(x: T, y: T) -> bool { + x == y +} +fn ne(x: T, y: T) -> bool { + x != y +} + impl Engine<'_> { /// Register the core built-in library. pub(crate) fn register_core_lib(&mut self) { @@ -176,26 +205,6 @@ impl Engine<'_> { } } - // Comparison operators - fn lt(x: T, y: T) -> bool { - x < y - } - fn lte(x: T, y: T) -> bool { - x <= y - } - fn gt(x: T, y: T) -> bool { - x > y - } - fn gte(x: T, y: T) -> bool { - x >= y - } - fn eq(x: T, y: T) -> bool { - x == y - } - fn ne(x: T, y: T) -> bool { - x != y - } - // Logic operators fn and(x: bool, y: bool) -> bool { x && y @@ -395,14 +404,6 @@ impl Engine<'_> { } { - 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); @@ -433,7 +434,7 @@ impl Engine<'_> { } // `&&` and `||` are treated specially as they short-circuit. - // They are implemented as special `Expr` instances, not function calls. + // They are implemented as special `Expr` Instants, not function calls. //reg_op!(self, "||", or, bool); //reg_op!(self, "&&", and, bool); @@ -620,23 +621,19 @@ impl Engine<'_> { #[cfg(not(feature = "no_object"))] { - self.register_fn(KEYWORD_PRINT, |x: &mut Map| -> String { - format!("#{:?}", x) - }); - self.register_fn(FUNC_TO_STRING, |x: &mut Map| -> String { - format!("#{:?}", x) - }); - self.register_fn(KEYWORD_DEBUG, |x: &mut Map| -> String { - format!("#{:?}", x) - }); + self.register_fn(KEYWORD_PRINT, |x: &mut Map| format!("#{:?}", x)); + self.register_fn(FUNC_TO_STRING, |x: &mut Map| format!("#{:?}", x)); + self.register_fn(KEYWORD_DEBUG, |x: &mut Map| format!("#{:?}", x)); // Register map access functions + #[cfg(not(feature = "no_index"))] self.register_fn("keys", |map: Map| { map.into_iter() .map(|(k, _)| k.into_dynamic()) .collect::>() }); + #[cfg(not(feature = "no_index"))] self.register_fn("values", |map: Map| { map.into_iter().map(|(_, v)| v).collect::>() }); @@ -757,8 +754,7 @@ macro_rules! reg_fn2y { /// Register the built-in library. impl Engine<'_> { - #[cfg(not(feature = "no_stdlib"))] - pub(crate) fn register_stdlib(&mut self) { + pub fn register_stdlib(&mut self) { #[cfg(not(feature = "no_float"))] { // Advanced math functions @@ -873,6 +869,15 @@ impl Engine<'_> { fn push(list: &mut Array, item: T) { list.push(Box::new(item)); } + fn ins(list: &mut Array, position: INT, item: T) { + if position <= 0 { + list.insert(0, Box::new(item)); + } else if (position as usize) >= list.len() - 1 { + push(list, item); + } else { + list.insert(position as usize, Box::new(item)); + } + } fn pad(list: &mut Array, len: INT, item: T) { if len >= 0 { while list.len() < len as usize { @@ -885,6 +890,7 @@ impl Engine<'_> { 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, ()); + reg_fn3!(self, "insert", ins, &mut Array, INT, (), String, Array, ()); self.register_fn("append", |list: &mut Array, array: Array| { list.extend(array) @@ -901,12 +907,15 @@ impl Engine<'_> { 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); + reg_fn3!(self, "insert", ins, &mut Array, INT, (), i8, u8, i16, u16); + reg_fn3!(self, "insert", ins, &mut Array, INT, (), i32, i64, u32, 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); + reg_fn3!(self, "insert", ins, &mut Array, INT, (), f32, f64); } self.register_dynamic_fn("pop", |list: &mut Array| { @@ -919,6 +928,13 @@ impl Engine<'_> { list.remove(0) } }); + self.register_dynamic_fn("remove", |list: &mut Array, len: INT| { + if len < 0 || (len as usize) >= list.len() { + ().into_dynamic() + } else { + list.remove(len as usize) + } + }); 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| { @@ -934,6 +950,9 @@ impl Engine<'_> { self.register_fn("has", |map: &mut Map, prop: String| map.contains_key(&prop)); self.register_fn("len", |map: &mut Map| map.len() as INT); self.register_fn("clear", |map: &mut Map| map.clear()); + self.register_dynamic_fn("remove", |x: &mut Map, name: String| { + x.remove(&name).unwrap_or(().into_dynamic()) + }); self.register_fn("mixin", |map1: &mut Map, map2: Map| { map2.into_iter().for_each(|(key, value)| { map1.insert(key, value); @@ -1013,5 +1032,39 @@ impl Engine<'_> { *s = trimmed.to_string(); } }); + + // Register date/time functions + self.register_fn("timestamp", || Instant::now()); + + self.register_fn("-", |ts1: Instant, ts2: Instant| { + if ts2 > ts1 { + #[cfg(not(feature = "no_float"))] + return -(ts2 - ts1).as_secs_f64(); + + #[cfg(feature = "no_float")] + return -((ts2 - ts1).as_secs() as INT); + } else { + #[cfg(not(feature = "no_float"))] + return (ts1 - ts2).as_secs_f64(); + + #[cfg(feature = "no_float")] + return (ts1 - ts2).as_secs() as INT; + } + }); + + reg_cmp!(self, "<", lt, Instant); + reg_cmp!(self, "<=", lte, Instant); + reg_cmp!(self, ">", gt, Instant); + reg_cmp!(self, ">=", gte, Instant); + reg_cmp!(self, "==", eq, Instant); + reg_cmp!(self, "!=", ne, Instant); + + self.register_fn("elapsed", |timestamp: Instant| { + #[cfg(not(feature = "no_float"))] + return timestamp.elapsed().as_secs_f64(); + + #[cfg(feature = "no_float")] + return timestamp.elapsed().as_secs() as INT; + }); } } diff --git a/src/engine.rs b/src/engine.rs index 8f4f418f..73b1e800 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,13 +1,12 @@ //! Main module defining the script evaluation `Engine`. use crate::any::{Any, AnyExt, Dynamic, Variant}; -use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, AST, INT}; +use crate::error::ParseErrorType; +use crate::optimize::OptimizationLevel; +use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, INT}; use crate::result::EvalAltResult; use crate::scope::{EntryRef as ScopeSource, EntryType as ScopeEntryType, Scope}; -#[cfg(not(feature = "no_optimize"))] -use crate::optimize::OptimizationLevel; - use crate::stdlib::{ any::{type_name, TypeId}, borrow::Cow, @@ -20,6 +19,7 @@ use crate::stdlib::{ rc::Rc, string::{String, ToString}, sync::Arc, + time::Instant, vec, vec::Vec, }; @@ -27,13 +27,11 @@ use crate::stdlib::{ /// An dynamic array of `Dynamic` values. /// /// Not available under the `no_index` feature. -#[cfg(not(feature = "no_index"))] pub type Array = Vec; /// An dynamic hash map of `Dynamic` values with `String` keys. /// /// Not available under the `no_object` feature. -#[cfg(not(feature = "no_object"))] pub type Map = HashMap; pub type FnCallArgs<'a> = [&'a mut Variant]; @@ -48,10 +46,14 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box> + Send + #[cfg(not(feature = "sync"))] type IteratorFn = dyn Fn(&Dynamic) -> Box>; -pub const MAX_CALL_STACK_DEPTH: usize = 64; +#[cfg(debug_assertions)] +pub const MAX_CALL_STACK_DEPTH: usize = 32; + +#[cfg(not(debug_assertions))] +pub const MAX_CALL_STACK_DEPTH: usize = 256; + pub const KEYWORD_PRINT: &str = "print"; pub const KEYWORD_DEBUG: &str = "debug"; -pub const KEYWORD_DUMP_AST: &str = "dump_ast"; pub const KEYWORD_TYPE_OF: &str = "type_of"; pub const KEYWORD_EVAL: &str = "eval"; pub const FUNC_TO_STRING: &str = "to_string"; @@ -59,12 +61,10 @@ pub const FUNC_GETTER: &str = "get$"; pub const FUNC_SETTER: &str = "set$"; #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] -#[cfg(not(feature = "no_index"))] enum IndexSourceType { Expression, String, Array, - #[cfg(not(feature = "no_object"))] Map, } @@ -172,11 +172,10 @@ impl FunctionsLib { } /// Get a function definition from the `FunctionsLib`. pub fn get_function(&self, name: &str, params: usize) -> Option<&FnDef> { - if let Ok(n) = self.0.binary_search_by(|f| f.compare(name, params)) { - Some(&self.0[n]) - } else { - None - } + self.0 + .binary_search_by(|f| f.compare(name, params)) + .ok() + .map(|n| self.0[n].as_ref()) } /// Merge another `FunctionsLib` into this `FunctionsLib`. pub fn merge(&self, other: &Self) -> Self { @@ -232,7 +231,7 @@ impl DerefMut for FunctionsLib { /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::Engine; /// -/// let mut engine = Engine::new(); +/// let engine = Engine::new(); /// /// let result = engine.eval::("40 + 2")?; /// @@ -246,13 +245,6 @@ pub struct Engine<'e> { /// A hashmap containing all compiled functions known to the engine. pub(crate) functions: Option, Box>>, - /// A hashmap containing all script-defined functions. - #[cfg(feature = "sync")] - pub(crate) fn_lib: Option>, - /// A hashmap containing all script-defined functions. - #[cfg(not(feature = "sync"))] - pub(crate) fn_lib: Option>, - /// A hashmap containing all iterators known to the engine. pub(crate) type_iterators: Option>>, /// A hashmap mapping type names to pretty-print names. @@ -260,50 +252,43 @@ pub struct Engine<'e> { /// Closure for implementing the `print` command. #[cfg(feature = "sync")] - pub(crate) on_print: Option>, + pub(crate) on_print: Option>, /// Closure for implementing the `print` command. #[cfg(not(feature = "sync"))] - pub(crate) on_print: Option>, + pub(crate) on_print: Option>, /// Closure for implementing the `debug` command. #[cfg(feature = "sync")] - pub(crate) on_debug: Option>, + pub(crate) on_debug: Option>, /// Closure for implementing the `debug` command. #[cfg(not(feature = "sync"))] - pub(crate) on_debug: Option>, + pub(crate) on_debug: Option>, /// Optimize the AST after compilation. - #[cfg(not(feature = "no_optimize"))] pub(crate) optimization_level: OptimizationLevel, /// Maximum levels of call-stack to prevent infinite recursion. + /// + /// Defaults to 32 for debug builds and 256 for non-debug builds. pub(crate) max_call_stack_depth: usize, } impl Default for Engine<'_> { fn default() -> Self { - // User-friendly names for built-in types - let type_names = [ - #[cfg(not(feature = "no_index"))] - (type_name::(), "array"), - #[cfg(not(feature = "no_object"))] - (type_name::(), "map"), - (type_name::(), "string"), - (type_name::(), "dynamic"), - ] - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect(); - // Create the new scripting Engine let mut engine = Engine { functions: None, - fn_lib: None, type_iterators: None, - type_names: Some(type_names), - on_print: Some(Box::new(default_print)), // default print/debug implementations + type_names: None, + + // default print/debug implementations + on_print: Some(Box::new(default_print)), on_debug: Some(Box::new(default_print)), + // optimization level + #[cfg(feature = "no_optimize")] + optimization_level: OptimizationLevel::None, + #[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "optimize_full"))] optimization_level: OptimizationLevel::Simple, @@ -315,10 +300,11 @@ impl Default for Engine<'_> { max_call_stack_depth: MAX_CALL_STACK_DEPTH, }; + engine.fill_type_names(); engine.register_core_lib(); #[cfg(not(feature = "no_stdlib"))] - engine.register_stdlib(); // Register the standard library when no_stdlib is not set + engine.register_stdlib(); engine } @@ -331,9 +317,16 @@ pub fn make_getter(id: &str) -> String { /// Extract the property name from a getter function name. fn extract_prop_from_getter(fn_name: &str) -> Option<&str> { - if fn_name.starts_with(FUNC_GETTER) { - Some(&fn_name[FUNC_GETTER.len()..]) - } else { + #[cfg(not(feature = "no_object"))] + { + if fn_name.starts_with(FUNC_GETTER) { + Some(&fn_name[FUNC_GETTER.len()..]) + } else { + None + } + } + #[cfg(feature = "no_object")] + { None } } @@ -345,34 +338,57 @@ pub fn make_setter(id: &str) -> String { /// Extract the property name from a setter function name. fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { - if fn_name.starts_with(FUNC_SETTER) { - Some(&fn_name[FUNC_SETTER.len()..]) - } else { + #[cfg(not(feature = "no_object"))] + { + if fn_name.starts_with(FUNC_SETTER) { + Some(&fn_name[FUNC_SETTER.len()..]) + } else { + None + } + } + #[cfg(feature = "no_object")] + { None } } impl Engine<'_> { + fn fill_type_names(&mut self) { + // User-friendly names for built-in types + self.type_names = Some( + [ + #[cfg(not(feature = "no_index"))] + (type_name::(), "array"), + #[cfg(not(feature = "no_object"))] + (type_name::(), "map"), + (type_name::(), "string"), + (type_name::(), "timestamp"), + (type_name::(), "dynamic"), + (type_name::(), "variant"), + ] + .iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(), + ); + } + /// Create a new `Engine` pub fn new() -> Self { - // fn abc(f: F) { - // f(); - // } - // abc(|| ()); - Default::default() } - /// Create a new `Engine` with minimal configurations - i.e. without pretty-print type names etc. + /// Create a new `Engine` with minimal configurations without the standard library etc. pub fn new_raw() -> Self { let mut engine = Engine { functions: None, - fn_lib: None, type_iterators: None, type_names: None, on_print: None, on_debug: None, + #[cfg(feature = "no_optimize")] + optimization_level: OptimizationLevel::None, + #[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "optimize_full"))] optimization_level: OptimizationLevel::Simple, @@ -384,11 +400,9 @@ impl Engine<'_> { max_call_stack_depth: MAX_CALL_STACK_DEPTH, }; + engine.fill_type_names(); engine.register_core_lib(); - #[cfg(not(feature = "no_stdlib"))] - engine.register_stdlib(); // Register the standard library when no_stdlib is not set - engine } @@ -406,102 +420,83 @@ impl Engine<'_> { self.max_call_stack_depth = levels } - /// Call a registered function - #[cfg(not(feature = "no_optimize"))] - pub(crate) fn call_ext_fn_raw( - &self, - fn_name: &str, - args: &mut FnCallArgs, - pos: Position, - ) -> Result, EvalAltResult> { - let spec = FnSpec { - name: fn_name.into(), - args: args.iter().map(|a| Any::type_id(&**a)).collect(), - }; - - // Search built-in's and external functions - if let Some(functions) = &self.functions { - if let Some(func) = functions.get(&spec) { - // Run external function - Ok(Some(func(args, pos)?)) - } else { - Ok(None) - } - } else { - Ok(None) - } - } - /// Universal method for calling functions either registered with the `Engine` or written in Rhai pub(crate) fn call_fn_raw( - &mut self, + &self, scope: Option<&mut Scope>, + fn_lib: Option<&FunctionsLib>, fn_name: &str, args: &mut FnCallArgs, def_val: Option<&Dynamic>, pos: Position, level: usize, ) -> Result { + // Check for stack overflow + if level > self.max_call_stack_depth { + return Err(EvalAltResult::ErrorStackOverflow(pos)); + } + + #[cfg(feature = "no_function")] + const fn_lib: Option<&FunctionsLib> = None; + // First search in script-defined functions (can override built-in) - if let Some(fn_lib_arc) = &self.fn_lib { - if let Some(fn_def) = fn_lib_arc.clone().get_function(fn_name, args.len()) { - match scope { - // Extern scope passed in which is not empty - Some(scope) if scope.len() > 0 => { - let scope_len = scope.len(); + if let Some(fn_def) = fn_lib.and_then(|lib| lib.get_function(fn_name, args.len())) { + match scope { + // Extern scope passed in which is not empty + Some(scope) if scope.len() > 0 => { + let scope_len = scope.len(); - scope.extend( - // Put arguments into scope as variables - variable name is copied - // TODO - avoid copying variable name - fn_def - .params - .iter() - .zip(args.into_iter().map(|x| (*x).into_dynamic())) - .map(|(name, value)| (name.clone(), ScopeEntryType::Normal, value)), - ); + scope.extend( + // Put arguments into scope as variables - variable name is copied + // TODO - avoid copying variable name + fn_def + .params + .iter() + .zip(args.into_iter().map(|x| (*x).into_dynamic())) + .map(|(name, value)| (name.clone(), ScopeEntryType::Normal, value)), + ); - // Evaluate the function at one higher level of call depth - let result = self.eval_stmt(scope, &fn_def.body, level + 1).or_else( - |err| match err { - // Convert return statement to return value - EvalAltResult::Return(x, _) => Ok(x), - err => Err(err.set_position(pos)), - }, - ); + // Evaluate the function at one higher level of call depth + let result = self + .eval_stmt(scope, fn_lib, &fn_def.body, level + 1) + .or_else(|err| match err { + // Convert return statement to return value + EvalAltResult::Return(x, _) => Ok(x), + err => Err(err.set_position(pos)), + }); - scope.rewind(scope_len); + scope.rewind(scope_len); - return result; - } - // No new scope - create internal scope - _ => { - let mut scope = Scope::new(); + return result; + } + // No new scope - create internal scope + _ => { + let mut scope = Scope::new(); - scope.extend( - // Put arguments into scope as variables - fn_def - .params - .iter() - .zip(args.into_iter().map(|x| (*x).into_dynamic())) - .map(|(name, value)| (name, ScopeEntryType::Normal, value)), - ); + scope.extend( + // Put arguments into scope as variables + fn_def + .params + .iter() + .zip(args.into_iter().map(|x| (*x).into_dynamic())) + .map(|(name, value)| (name, ScopeEntryType::Normal, value)), + ); - // Evaluate the function at one higher level of call depth - return self.eval_stmt(&mut scope, &fn_def.body, level + 1).or_else( - |err| match err { - // Convert return statement to return value - EvalAltResult::Return(x, _) => Ok(x), - err => Err(err.set_position(pos)), - }, - ); - } + // Evaluate the function at one higher level of call depth + return self + .eval_stmt(&mut scope, fn_lib, &fn_def.body, level + 1) + .or_else(|err| match err { + // Convert return statement to return value + EvalAltResult::Return(x, _) => Ok(x), + err => Err(err.set_position(pos)), + }); } } } let spec = FnSpec { name: fn_name.into(), - args: args.iter().map(|a| Any::type_id(&**a)).collect(), + args: args.iter().map(|a| Any::type_id(*a)).collect(), }; // Argument must be a string @@ -512,34 +507,29 @@ impl Engine<'_> { } // Search built-in's and external functions - if let Some(functions) = &self.functions { - if let Some(func) = functions.get(&spec) { - // Run external function - let result = func(args, pos)?; + if let Some(func) = self.functions.as_ref().and_then(|f| f.get(&spec)) { + // Run external function + let result = func(args, pos)?; - // See if the function match print/debug (which requires special processing) - return Ok(match fn_name { - KEYWORD_PRINT if self.on_print.is_some() => { - self.on_print.as_deref_mut().unwrap()(cast_to_string(result.as_ref(), pos)?) - .into_dynamic() - } - KEYWORD_DEBUG if self.on_debug.is_some() => { - self.on_debug.as_deref_mut().unwrap()(cast_to_string(result.as_ref(), pos)?) - .into_dynamic() - } - KEYWORD_PRINT | KEYWORD_DEBUG => ().into_dynamic(), - _ => result, - }); - } + // See if the function match print/debug (which requires special processing) + return match fn_name { + KEYWORD_PRINT if self.on_print.is_some() => Ok(self.on_print.as_ref().unwrap()( + cast_to_string(result.as_ref(), pos)?, + ) + .into_dynamic()), + KEYWORD_DEBUG if self.on_debug.is_some() => Ok(self.on_debug.as_ref().unwrap()( + cast_to_string(result.as_ref(), pos)?, + ) + .into_dynamic()), + KEYWORD_PRINT | KEYWORD_DEBUG => Ok(().into_dynamic()), + _ => Ok(result), + }; } if let Some(prop) = extract_prop_from_getter(fn_name) { - #[cfg(not(feature = "no_object"))] - { - // Map property access - if let Some(map) = args[0].downcast_ref::() { - return Ok(map.get(prop).cloned().unwrap_or_else(|| ().into_dynamic())); - } + // Map property access + if let Some(map) = args[0].downcast_ref::() { + return Ok(map.get(prop).cloned().unwrap_or_else(|| ().into_dynamic())); } // Getter function not found @@ -550,15 +540,12 @@ impl Engine<'_> { } if let Some(prop) = extract_prop_from_setter(fn_name) { - #[cfg(not(feature = "no_object"))] - { - let value = args[1].into_dynamic(); + let value = args[1].into_dynamic(); - // Map property update - if let Some(map) = args[0].downcast_mut::() { - map.insert(prop.to_string(), value); - return Ok(().into_dynamic()); - } + // Map property update + if let Some(map) = args[0].downcast_mut::() { + map.insert(prop.to_string(), value); + return Ok(().into_dynamic()); } // Setter function not found @@ -587,10 +574,10 @@ impl Engine<'_> { } /// Chain-evaluate a dot setter. - #[cfg(not(feature = "no_object"))] fn get_dot_val_helper( - &mut self, + &self, scope: &mut Scope, + fn_lib: Option<&FunctionsLib>, target: Target, dot_rhs: &Expr, level: usize, @@ -600,7 +587,7 @@ impl Engine<'_> { Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => { let mut values = arg_expr_list .iter() - .map(|arg_expr| self.eval_expr(scope, arg_expr, level)) + .map(|arg_expr| self.eval_expr(scope, fn_lib, arg_expr, level)) .collect::, _>>()?; let this_ptr = target.get_mut(scope); @@ -611,27 +598,26 @@ impl Engine<'_> { let def_val = def_val.as_ref(); - self.call_fn_raw(None, fn_name, &mut args, def_val, *pos, 0) + self.call_fn_raw(None, fn_lib, fn_name, &mut args, def_val, *pos, 0) } // xxx.id Expr::Property(id, pos) => { let mut args = [target.get_mut(scope)]; - self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0) + self.call_fn_raw(None, fn_lib, &make_getter(id), &mut args, None, *pos, 0) } // xxx.idx_lhs[idx_expr] - #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, op_pos) => { let value = match idx_lhs.as_ref() { // xxx.id[idx_expr] Expr::Property(id, pos) => { let mut args = [target.get_mut(scope)]; - self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0)? + self.call_fn_raw(None, fn_lib, &make_getter(id), &mut args, None, *pos, 0)? } // xxx.???[???][idx_expr] Expr::Index(_, _, _) => { - self.get_dot_val_helper(scope, target, idx_lhs, level)? + self.get_dot_val_helper(scope, fn_lib, target, idx_lhs, level)? } // Syntax error _ => { @@ -642,7 +628,7 @@ impl Engine<'_> { } }; - self.get_indexed_value(scope, &value, idx_expr, *op_pos, level) + self.get_indexed_value(scope, fn_lib, &value, idx_expr, *op_pos, level) .map(|(val, _, _)| val) } @@ -651,23 +637,24 @@ impl Engine<'_> { // xxx.id.rhs Expr::Property(id, pos) => { let mut args = [target.get_mut(scope)]; - self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0) + self.call_fn_raw(None, fn_lib, &make_getter(id), &mut args, None, *pos, 0) .and_then(|mut val| { - self.get_dot_val_helper(scope, Target::from(val.as_mut()), rhs, level) + let target = Target::from(val.as_mut()); + self.get_dot_val_helper(scope, fn_lib, target, rhs, level) }) } // xxx.idx_lhs[idx_expr].rhs - #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, op_pos) => { let val = match idx_lhs.as_ref() { // xxx.id[idx_expr].rhs Expr::Property(id, pos) => { + let fn_name = make_getter(id); let mut args = [target.get_mut(scope)]; - self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0)? + self.call_fn_raw(None, fn_lib, &fn_name, &mut args, None, *pos, 0)? } // xxx.???[???][idx_expr].rhs Expr::Index(_, _, _) => { - self.get_dot_val_helper(scope, target, idx_lhs, level)? + self.get_dot_val_helper(scope, fn_lib, target, idx_lhs, level)? } // Syntax error _ => { @@ -678,9 +665,10 @@ impl Engine<'_> { } }; - self.get_indexed_value(scope, &val, idx_expr, *op_pos, level) + self.get_indexed_value(scope, fn_lib, &val, idx_expr, *op_pos, level) .and_then(|(mut val, _, _)| { - self.get_dot_val_helper(scope, Target::from(val.as_mut()), rhs, level) + let target = Target::from(val.as_mut()); + self.get_dot_val_helper(scope, fn_lib, target, rhs, level) }) } // Syntax error @@ -699,10 +687,10 @@ impl Engine<'_> { } /// Evaluate a dot chain getter - #[cfg(not(feature = "no_object"))] fn get_dot_val( - &mut self, + &self, scope: &mut Scope, + fn_lib: Option<&FunctionsLib>, dot_lhs: &Expr, dot_rhs: &Expr, level: usize, @@ -717,35 +705,35 @@ impl Engine<'_> { // This is a variable property access (potential function call). // Use a direct index into `scope` to directly mutate the variable value. - self.get_dot_val_helper(scope, Target::from_src(entry), dot_rhs, level) + self.get_dot_val_helper(scope, fn_lib, Target::from_src(entry), dot_rhs, level) } // idx_lhs[idx_expr].??? - #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, op_pos) => { - let (idx_src_type, src, idx, mut val) = - self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?; - let value = - self.get_dot_val_helper(scope, Target::from(val.as_mut()), dot_rhs, level); + let (idx_src_type, src, index, mut val) = + self.eval_index_expr(scope, fn_lib, idx_lhs, idx_expr, *op_pos, level)?; + let target = Target::from(val.as_mut()); + let value = self.get_dot_val_helper(scope, fn_lib, target, dot_rhs, level); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - if let Some(src) = src { - match src.typ { - ScopeEntryType::Constant => { - return Err(EvalAltResult::ErrorAssignmentToConstant( - src.name.to_string(), - idx_lhs.position(), - )); - } - ScopeEntryType::Normal => { - Self::update_indexed_var_in_scope( - idx_src_type, - scope, - src, - idx, - (val, dot_rhs.position()), - )?; - } + match src.map(|s| s.typ) { + None => (), + + Some(ScopeEntryType::Constant) => { + return Err(EvalAltResult::ErrorAssignmentToConstant( + src.unwrap().name.to_string(), + idx_lhs.position(), + )); + } + + Some(ScopeEntryType::Normal) => { + Self::update_indexed_var_in_scope( + idx_src_type, + scope, + src.unwrap(), + index, + (val, dot_rhs.position()), + )?; } } @@ -754,8 +742,8 @@ impl Engine<'_> { // {expr}.??? expr => { - let mut val = self.eval_expr(scope, expr, level)?; - self.get_dot_val_helper(scope, Target::from(val.as_mut()), dot_rhs, level) + let mut val = self.eval_expr(scope, fn_lib, expr, level)?; + self.get_dot_val_helper(scope, fn_lib, Target::from(val.as_mut()), dot_rhs, level) } } } @@ -772,10 +760,10 @@ impl Engine<'_> { } /// Get the value at the indexed position of a base type - #[cfg(not(feature = "no_index"))] fn get_indexed_value( - &mut self, + &self, scope: &mut Scope, + fn_lib: Option<&FunctionsLib>, val: &Dynamic, idx_expr: &Expr, op_pos: Position, @@ -785,62 +773,61 @@ impl Engine<'_> { // val_array[idx] if let Some(arr) = val.downcast_ref::() { - let idx = self - .eval_expr(scope, idx_expr, level)? + let index = self + .eval_expr(scope, fn_lib, idx_expr, level)? .try_cast::() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; - return if idx >= 0 { - arr.get(idx as usize) + return if index >= 0 { + arr.get(index as usize) .cloned() - .map(|v| (v, IndexSourceType::Array, IndexValue::from_num(idx))) - .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) + .map(|v| (v, IndexSourceType::Array, IndexValue::from_num(index))) + .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) } else { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) + Err(EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) }; } - #[cfg(not(feature = "no_object"))] - { - // val_map[idx] - if let Some(map) = val.downcast_ref::() { - let idx = self - .eval_expr(scope, idx_expr, level)? - .try_cast::() - .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?; + // val_map[idx] + if let Some(map) = val.downcast_ref::() { + let index = self + .eval_expr(scope, fn_lib, idx_expr, level)? + .try_cast::() + .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?; - return Ok(( - map.get(&idx).cloned().unwrap_or_else(|| ().into_dynamic()), - IndexSourceType::Map, - IndexValue::from_str(idx), - )); - } + return Ok(( + map.get(&index) + .cloned() + .unwrap_or_else(|| ().into_dynamic()), + IndexSourceType::Map, + IndexValue::from_str(index), + )); } // val_string[idx] if let Some(s) = val.downcast_ref::() { - let idx = self - .eval_expr(scope, idx_expr, level)? + let index = self + .eval_expr(scope, fn_lib, idx_expr, level)? .try_cast::() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; - return if idx >= 0 { + return if index >= 0 { s.chars() - .nth(idx as usize) + .nth(index as usize) .map(|ch| { ( ch.into_dynamic(), IndexSourceType::String, - IndexValue::from_num(idx), + IndexValue::from_num(index), ) }) .ok_or_else(|| { - EvalAltResult::ErrorStringBounds(s.chars().count(), idx, idx_pos) + EvalAltResult::ErrorStringBounds(s.chars().count(), index, idx_pos) }) } else { Err(EvalAltResult::ErrorStringBounds( s.chars().count(), - idx, + index, idx_pos, )) }; @@ -854,10 +841,10 @@ impl Engine<'_> { } /// Evaluate an index expression - #[cfg(not(feature = "no_index"))] fn eval_index_expr<'a>( - &mut self, + &self, scope: &mut Scope, + fn_lib: Option<&FunctionsLib>, lhs: &'a Expr, idx_expr: &Expr, op_pos: Position, @@ -883,8 +870,8 @@ impl Engine<'_> { val, ) = Self::search_scope(scope, &id, lhs.position())?; - let (val, idx_src_type, idx) = - self.get_indexed_value(scope, &val, idx_expr, op_pos, level)?; + let (val, idx_src_type, index) = + self.get_indexed_value(scope, fn_lib, &val, idx_expr, op_pos, level)?; Ok(( idx_src_type, @@ -893,23 +880,22 @@ impl Engine<'_> { typ: src_type, index: src_idx, }), - idx, + index, val, )) } // (expr)[idx_expr] expr => { - let val = self.eval_expr(scope, expr, level)?; + let val = self.eval_expr(scope, fn_lib, expr, level)?; - self.get_indexed_value(scope, &val, idx_expr, op_pos, level) - .map(|(val, _, idx)| (IndexSourceType::Expression, None, idx, val)) + self.get_indexed_value(scope, fn_lib, &val, idx_expr, op_pos, level) + .map(|(val, _, index)| (IndexSourceType::Expression, None, index, val)) } } } /// 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"); @@ -923,7 +909,6 @@ 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( idx_src_type: IndexSourceType, scope: &mut Scope, @@ -940,7 +925,6 @@ impl Engine<'_> { } // map_id[idx] = val - #[cfg(not(feature = "no_object"))] IndexSourceType::Map => { let arr = scope.get_mut_by_type::(src); arr.insert(idx.as_str(), new_val.0); @@ -965,7 +949,6 @@ impl Engine<'_> { } /// Update the value at an index position - #[cfg(not(feature = "no_index"))] fn update_indexed_value( mut target: Dynamic, idx: IndexValue, @@ -977,12 +960,9 @@ impl Engine<'_> { return Ok(target); } - #[cfg(not(feature = "no_object"))] - { - if let Some(map) = target.downcast_mut::() { - map.insert(idx.as_str(), new_val); - return Ok(target); - } + if let Some(map) = target.downcast_mut::() { + map.insert(idx.as_str(), new_val); + return Ok(target); } if let Some(s) = target.downcast_mut::() { @@ -999,10 +979,10 @@ impl Engine<'_> { } /// Chain-evaluate a dot setter - #[cfg(not(feature = "no_object"))] fn set_dot_val_helper( - &mut self, + &self, scope: &mut Scope, + fn_lib: Option<&FunctionsLib>, this_ptr: &mut Variant, dot_rhs: &Expr, new_val: (&mut Dynamic, Position), @@ -1012,26 +992,28 @@ impl Engine<'_> { // xxx.id Expr::Property(id, pos) => { let mut args = [this_ptr, new_val.0.as_mut()]; - self.call_fn_raw(None, &make_setter(id), &mut args, None, *pos, 0) + self.call_fn_raw(None, fn_lib, &make_setter(id), &mut args, None, *pos, 0) } // xxx.lhs[idx_expr] // TODO - Allow chaining of indexing! - #[cfg(not(feature = "no_index"))] Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() { // xxx.id[idx_expr] - Expr::Property(id, pos) => self - .call_fn_raw(None, &make_getter(id), &mut [this_ptr], None, *pos, 0) - .and_then(|val| { - let (_, _, idx) = - self.get_indexed_value(scope, &val, idx_expr, *op_pos, level)?; + Expr::Property(id, pos) => { + let fn_name = make_getter(id); + self.call_fn_raw(None, fn_lib, &fn_name, &mut [this_ptr], None, *pos, 0) + .and_then(|val| { + let (_, _, index) = self + .get_indexed_value(scope, fn_lib, &val, idx_expr, *op_pos, level)?; - Self::update_indexed_value(val, idx, new_val.0.clone(), new_val.1) - }) - .and_then(|mut val| { - let mut args = [this_ptr, val.as_mut()]; - self.call_fn_raw(None, &make_setter(id), &mut args, None, *pos, 0) - }), + Self::update_indexed_value(val, index, new_val.0.clone(), new_val.1) + }) + .and_then(|mut val| { + let fn_name = make_setter(id); + let mut args = [this_ptr, val.as_mut()]; + self.call_fn_raw(None, fn_lib, &fn_name, &mut args, None, *pos, 0) + }) + } // All others - syntax error for setters chain _ => Err(EvalAltResult::ErrorDotExpr( @@ -1044,38 +1026,45 @@ impl Engine<'_> { Expr::Dot(lhs, rhs, _) => match lhs.as_ref() { // xxx.id.rhs Expr::Property(id, pos) => { - self.call_fn_raw(None, &make_getter(id), &mut [this_ptr], None, *pos, 0) + let fn_name = make_getter(id); + self.call_fn_raw(None, fn_lib, &fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|mut val| { - self.set_dot_val_helper(scope, val.as_mut(), rhs, new_val, level) + let value = val.as_mut(); + self.set_dot_val_helper(scope, fn_lib, value, rhs, new_val, level) .map(|_| val) // Discard Ok return value }) .and_then(|mut val| { + let fn_name = make_setter(id); let mut args = [this_ptr, val.as_mut()]; - self.call_fn_raw(None, &make_setter(id), &mut args, None, *pos, 0) + self.call_fn_raw(None, fn_lib, &fn_name, &mut args, None, *pos, 0) }) } // xxx.lhs[idx_expr].rhs // TODO - Allow chaining of indexing! - #[cfg(not(feature = "no_index"))] Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() { // xxx.id[idx_expr].rhs Expr::Property(id, pos) => { - self.call_fn_raw(None, &make_getter(id), &mut [this_ptr], None, *pos, 0) + let fn_name = make_getter(id); + self.call_fn_raw(None, fn_lib, &fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|v| { - let (mut value, _, idx) = - self.get_indexed_value(scope, &v, idx_expr, *op_pos, level)?; + let (mut value, _, index) = self.get_indexed_value( + scope, fn_lib, &v, idx_expr, *op_pos, level, + )?; let val_pos = new_val.1; let this_ptr = value.as_mut(); - self.set_dot_val_helper(scope, this_ptr, rhs, new_val, level)?; + self.set_dot_val_helper( + scope, fn_lib, this_ptr, rhs, new_val, level, + )?; // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - Self::update_indexed_value(v, idx, value, val_pos) + Self::update_indexed_value(v, index, value, val_pos) }) .and_then(|mut v| { + let fn_name = make_setter(id); let mut args = [this_ptr, v.as_mut()]; - self.call_fn_raw(None, &make_setter(id), &mut args, None, *pos, 0) + self.call_fn_raw(None, fn_lib, &fn_name, &mut args, None, *pos, 0) }) } @@ -1102,10 +1091,10 @@ impl Engine<'_> { } // Evaluate a dot chain setter - #[cfg(not(feature = "no_object"))] fn set_dot_val( - &mut self, + &self, scope: &mut Scope, + fn_lib: Option<&FunctionsLib>, dot_lhs: &Expr, dot_rhs: &Expr, new_val: (&mut Dynamic, Position), @@ -1115,19 +1104,19 @@ impl Engine<'_> { match dot_lhs { // id.??? Expr::Variable(id, pos) => { - let (entry, mut target) = Self::search_scope(scope, id, *pos)?; + let (src, mut target) = Self::search_scope(scope, id, *pos)?; - match entry.typ { + match src.typ { ScopeEntryType::Constant => Err(EvalAltResult::ErrorAssignmentToConstant( id.to_string(), op_pos, )), _ => { // Avoid referencing scope which is used below as mut - let entry = ScopeSource { name: id, ..entry }; + let entry = ScopeSource { name: id, ..src }; let this_ptr = target.as_mut(); - let value = - self.set_dot_val_helper(scope, this_ptr, dot_rhs, new_val, level); + let value = self + .set_dot_val_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, level); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. *scope.get_mut(entry) = target; @@ -1139,32 +1128,33 @@ impl Engine<'_> { // lhs[idx_expr].??? // TODO - Allow chaining of indexing! - #[cfg(not(feature = "no_index"))] Expr::Index(lhs, idx_expr, op_pos) => { - let (idx_src_type, src, idx, mut target) = - self.eval_index_expr(scope, lhs, idx_expr, *op_pos, level)?; + let (idx_src_type, src, index, mut target) = + self.eval_index_expr(scope, fn_lib, lhs, idx_expr, *op_pos, level)?; let val_pos = new_val.1; let this_ptr = target.as_mut(); - let value = self.set_dot_val_helper(scope, this_ptr, dot_rhs, new_val, level); + let value = + self.set_dot_val_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, level); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - if let Some(src) = src { - match src.typ { - ScopeEntryType::Constant => { - return Err(EvalAltResult::ErrorAssignmentToConstant( - src.name.to_string(), - lhs.position(), - )); - } - ScopeEntryType::Normal => { - Self::update_indexed_var_in_scope( - idx_src_type, - scope, - src, - idx, - (target, val_pos), - )?; - } + match src.map(|x| x.typ) { + None => (), + + Some(ScopeEntryType::Constant) => { + return Err(EvalAltResult::ErrorAssignmentToConstant( + src.unwrap().name.to_string(), + lhs.position(), + )); + } + + Some(ScopeEntryType::Normal) => { + Self::update_indexed_var_in_scope( + idx_src_type, + scope, + src.unwrap(), + index, + (target, val_pos), + )?; } } @@ -1179,60 +1169,126 @@ impl Engine<'_> { } } + // Evaluate an 'in' expression + fn eval_in_expr( + &self, + scope: &mut Scope, + fn_lib: Option<&FunctionsLib>, + lhs: &Expr, + rhs: &Expr, + level: usize, + ) -> Result { + let mut lhs_value = self.eval_expr(scope, fn_lib, lhs, level)?; + let rhs_value = self.eval_expr(scope, fn_lib, rhs, level)?; + + if rhs_value.is::() { + let mut rhs_value = rhs_value.cast::(); + let def_value = false.into_dynamic(); + let mut result = false; + + // Call the '==' operator to compare each value + for value in rhs_value.iter_mut() { + let args = &mut [lhs_value.as_mut(), value.as_mut()]; + let def_value = Some(&def_value); + if self + .call_fn_raw(None, fn_lib, "==", args, def_value, rhs.position(), level)? + .try_cast::() + .unwrap_or(false) + { + result = true; + break; + } + } + + Ok(result.into_dynamic()) + } else if rhs_value.is::() { + let rhs_value = rhs_value.cast::(); + + // Only allows String or char + if lhs_value.is::() { + Ok(rhs_value + .contains_key(&lhs_value.cast::()) + .into_dynamic()) + } else if lhs_value.is::() { + Ok(rhs_value + .contains_key(&lhs_value.cast::().to_string()) + .into_dynamic()) + } else { + Err(EvalAltResult::ErrorInExpr(lhs.position())) + } + } else if rhs_value.is::() { + let rhs_value = rhs_value.cast::(); + + // Only allows String or char + if lhs_value.is::() { + Ok(rhs_value + .contains(&lhs_value.cast::()) + .into_dynamic()) + } else if lhs_value.is::() { + Ok(rhs_value.contains(lhs_value.cast::()).into_dynamic()) + } else { + Err(EvalAltResult::ErrorInExpr(lhs.position())) + } + } else { + Err(EvalAltResult::ErrorInExpr(rhs.position())) + } + } + /// Evaluate an expression fn eval_expr( - &mut self, + &self, scope: &mut Scope, + fn_lib: Option<&FunctionsLib>, expr: &Expr, level: usize, ) -> Result { match expr { - #[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::FloatConstant(f, _) => Ok(f.into_dynamic()), + Expr::StringConstant(s, _) => Ok(s.clone().into_owned().into_dynamic()), Expr::CharConstant(c, _) => Ok(c.into_dynamic()), Expr::Variable(id, pos) => Self::search_scope(scope, id, *pos).map(|(_, val)| val), Expr::Property(_, _) => panic!("unexpected property."), - // lhs[idx_expr] - #[cfg(not(feature = "no_index"))] - Expr::Index(lhs, idx_expr, op_pos) => self - .eval_index_expr(scope, lhs, idx_expr, *op_pos, level) - .map(|(_, _, _, x)| x), - // Statement block - Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt, level), + Expr::Stmt(stmt, _) => self.eval_stmt(scope, fn_lib, stmt, level), // lhs = rhs Expr::Assignment(lhs, rhs, op_pos) => { - let mut rhs_val = self.eval_expr(scope, rhs, level)?; + let mut rhs_val = self.eval_expr(scope, fn_lib, rhs, level)?; match lhs.as_ref() { // name = rhs - Expr::Variable(name, pos) => match scope - .get(name) - .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(name.clone(), *pos))? - .0 - { - entry - @ - ScopeSource { - typ: ScopeEntryType::Normal, - .. - } => { + Expr::Variable(name, pos) => match scope.get(name) { + None => { + return Err(EvalAltResult::ErrorVariableNotFound( + name.clone().into_owned(), + *pos, + )) + } + + Some(( + entry + @ + ScopeSource { + typ: ScopeEntryType::Normal, + .. + }, + _, + )) => { // Avoid referencing scope which is used below as mut let entry = ScopeSource { name, ..entry }; - *scope.get_mut(entry) = rhs_val.clone(); Ok(rhs_val) } - ScopeSource { - typ: ScopeEntryType::Constant, - .. - } => Err(EvalAltResult::ErrorAssignmentToConstant( + Some(( + ScopeSource { + typ: ScopeEntryType::Constant, + .. + }, + _, + )) => Err(EvalAltResult::ErrorAssignmentToConstant( name.to_string(), *op_pos, )), @@ -1241,42 +1297,37 @@ impl Engine<'_> { // idx_lhs[idx_expr] = rhs #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, op_pos) => { - let (idx_src_type, src, idx, _) = - self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?; + let (idx_src_type, src, index, _) = + self.eval_index_expr(scope, fn_lib, idx_lhs, idx_expr, *op_pos, level)?; - if let Some(src) = src { - match src.typ { - ScopeEntryType::Constant => { - Err(EvalAltResult::ErrorAssignmentToConstant( - src.name.to_string(), - idx_lhs.position(), - )) - } - ScopeEntryType::Normal => Ok(Self::update_indexed_var_in_scope( - idx_src_type, - scope, - src, - idx, - (rhs_val, rhs.position()), - )?), - } - } else { - Err(EvalAltResult::ErrorAssignmentToUnknownLHS( + match src.map(|x| x.typ) { + None => Err(EvalAltResult::ErrorAssignmentToUnknownLHS( idx_lhs.position(), - )) + )), + + Some(ScopeEntryType::Constant) => { + Err(EvalAltResult::ErrorAssignmentToConstant( + src.unwrap().name.to_string(), + idx_lhs.position(), + )) + } + + Some(ScopeEntryType::Normal) => Ok(Self::update_indexed_var_in_scope( + idx_src_type, + scope, + src.unwrap(), + index, + (rhs_val, rhs.position()), + )?), } } // dot_lhs.dot_rhs = rhs #[cfg(not(feature = "no_object"))] - Expr::Dot(dot_lhs, dot_rhs, _) => self.set_dot_val( - scope, - dot_lhs, - dot_rhs, - (&mut rhs_val, rhs.position()), - *op_pos, - level, - ), + Expr::Dot(dot_lhs, dot_rhs, _) => { + let new_val = (&mut rhs_val, rhs.position()); + self.set_dot_val(scope, fn_lib, dot_lhs, dot_rhs, new_val, *op_pos, level) + } // Error assignment to constant expr if expr.is_constant() => Err(EvalAltResult::ErrorAssignmentToConstant( @@ -1289,18 +1340,25 @@ impl Engine<'_> { } } + // lhs[idx_expr] + #[cfg(not(feature = "no_index"))] + Expr::Index(lhs, idx_expr, op_pos) => self + .eval_index_expr(scope, fn_lib, lhs, idx_expr, *op_pos, level) + .map(|(_, _, _, x)| x), + #[cfg(not(feature = "no_object"))] - Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, lhs, rhs, level), + Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, fn_lib, lhs, rhs, level), #[cfg(not(feature = "no_index"))] Expr::Array(contents, _) => { let mut arr = Array::new(); contents.into_iter().try_for_each(|item| { - self.eval_expr(scope, item, level).map(|val| arr.push(val)) + self.eval_expr(scope, fn_lib, item, level) + .map(|val| arr.push(val)) })?; - Ok(Box::new(arr)) + Ok((arr).into_dynamic()) } #[cfg(not(feature = "no_object"))] @@ -1308,167 +1366,138 @@ impl Engine<'_> { let mut map = Map::new(); contents.into_iter().try_for_each(|item| { - self.eval_expr(scope, &item.1, level).map(|val| { + self.eval_expr(scope, fn_lib, &item.1, level).map(|val| { map.insert(item.0.clone(), val); }) })?; - Ok(Box::new(map)) + Ok((map).into_dynamic()) } Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => { // Has a system function an override? - fn has_override(engine: &Engine, name: &str) -> bool { - (engine.functions.is_some() && { - engine.functions.as_ref().unwrap().contains_key(&FnSpec { + fn has_override( + engine: &Engine, + fn_lib: Option<&FunctionsLib>, + name: &str, + ) -> bool { + engine.functions.as_ref().map_or(false, |lib| { + lib.contains_key(&FnSpec { name: name.into(), args: vec![TypeId::of::()], }) - }) || (engine.fn_lib.is_some() - && engine.fn_lib.as_ref().unwrap().has_function(name, 1)) + }) || fn_lib.map_or(false, |lib| lib.has_function(name, 1)) } - match fn_name.as_str() { - // Dump AST - KEYWORD_DUMP_AST => { - let pos = if args_expr_list.is_empty() { - *pos - } else { - args_expr_list[0].position() - }; - - // Change the argument to a debug dump of the expressions - let mut result = args_expr_list - .iter() - .map(|expr| format!("{:#?}", expr)) - .collect::>() - .join("\n") - .into_dynamic(); - - // Redirect call to `print` - let mut args = [result.as_mut()]; - self.call_fn_raw(None, KEYWORD_PRINT, &mut args, None, pos, level) - } - + match fn_name.as_ref() { // type_of KEYWORD_TYPE_OF - if args_expr_list.len() == 1 && !has_override(self, KEYWORD_TYPE_OF) => + if args_expr_list.len() == 1 + && !has_override(self, fn_lib, KEYWORD_TYPE_OF) => { - let r = self.eval_expr(scope, &args_expr_list[0], level)?; + let result = self.eval_expr(scope, fn_lib, &args_expr_list[0], level)?; Ok(self - .map_type_name((*r).type_name()) + .map_type_name((*result).type_name()) .to_string() .into_dynamic()) } // eval KEYWORD_EVAL - if args_expr_list.len() == 1 && !has_override(self, KEYWORD_EVAL) => + if args_expr_list.len() == 1 + && !has_override(self, fn_lib, KEYWORD_EVAL) => { let pos = args_expr_list[0].position(); - let r = self.eval_expr(scope, &args_expr_list[0], level)?; + let result = self.eval_expr(scope, fn_lib, &args_expr_list[0], level)?; // Get the script text by evaluating the expression - let script = - r.downcast_ref::() - .map(String::as_str) - .ok_or_else(|| { - EvalAltResult::ErrorMismatchOutputType( - r.type_name().into(), - pos, - ) - })?; + let script = result + .downcast_ref::() + .map(String::as_str) + .ok_or_else(|| { + EvalAltResult::ErrorMismatchOutputType( + result.type_name().into(), + pos, + ) + })?; // Compile the script text - #[cfg(not(feature = "no_optimize"))] - let ast = { - let orig_optimization_level = self.optimization_level; + // No optimizations because we only run it once + let mut ast = self + .compile_with_scope_and_optimization_level( + &Scope::new(), + script, + OptimizationLevel::None, + ) + .map_err(EvalAltResult::ErrorParsing)?; - self.set_optimization_level(OptimizationLevel::None); - let ast = self.compile(script); - self.set_optimization_level(orig_optimization_level); + // If new functions are defined within the eval string, it is an error + if ast.1.len() > 0 { + return Err(EvalAltResult::ErrorParsing( + ParseErrorType::WrongFnDefinition.into_err(pos), + )); + } - ast.map_err(EvalAltResult::ErrorParsing)? - }; - - #[cfg(feature = "no_optimize")] - let ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?; - - // If new functions are defined, merge it into the current functions library - let merged = AST( - ast.0, - if let Some(fn_lib) = &self.fn_lib { - #[cfg(feature = "sync")] - { - Arc::new(fn_lib.as_ref().merge(&ast.1)) - } - #[cfg(not(feature = "sync"))] - { - Rc::new(fn_lib.as_ref().merge(&ast.1)) - } - } else { - ast.1 - }, - ); + if let Some(lib) = fn_lib { + #[cfg(feature = "sync")] + { + ast.1 = Arc::new(lib.clone()); + } + #[cfg(not(feature = "sync"))] + { + ast.1 = Rc::new(lib.clone()); + } + } // Evaluate the AST - let result = self - .eval_ast_with_scope_raw(scope, &merged) - .map_err(|err| err.set_position(pos)); - - // Update the new functions library if there are new functions - self.fn_lib = if !merged.1.is_empty() { - Some(merged.1) - } else { - None - }; - - Ok(result?) + self.eval_ast_with_scope_raw(scope, &ast) + .map_err(|err| err.set_position(pos)) } // Normal function call _ => { - let mut values = args_expr_list + let mut arg_values = args_expr_list .iter() - .map(|expr| self.eval_expr(scope, expr, level)) + .map(|expr| self.eval_expr(scope, fn_lib, expr, level)) .collect::, _>>()?; - let mut arg_values: Vec<_> = - values.iter_mut().map(Dynamic::as_mut).collect(); - + let mut args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); let def_val = def_val.as_ref(); - - self.call_fn_raw(None, fn_name, &mut arg_values, def_val, *pos, level) + self.call_fn_raw(None, fn_lib, fn_name, &mut args, def_val, *pos, level) } } } - Expr::And(lhs, rhs) => Ok(Box::new( + Expr::In(lhs, rhs, _) => { + self.eval_in_expr(scope, fn_lib, lhs.as_ref(), rhs.as_ref(), level) + } + + Expr::And(lhs, rhs, _) => Ok(Box::new( self - .eval_expr(scope, &*lhs, level)? + .eval_expr(scope, fn_lib,lhs.as_ref(), level)? .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) })? && // Short-circuit using && self - .eval_expr(scope, &*rhs, level)? + .eval_expr(scope, fn_lib,rhs.as_ref(), level)? .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) })?, )), - Expr::Or(lhs, rhs) => Ok(Box::new( + Expr::Or(lhs, rhs, _) => Ok(Box::new( self - .eval_expr(scope, &*lhs, level)? + .eval_expr(scope,fn_lib, lhs.as_ref(), level)? .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) })? || // Short-circuit using || self - .eval_expr(scope, &*rhs, level)? + .eval_expr(scope,fn_lib, rhs.as_ref(), level)? .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) @@ -1478,13 +1507,16 @@ impl Engine<'_> { Expr::True(_) => Ok(true.into_dynamic()), Expr::False(_) => Ok(false.into_dynamic()), Expr::Unit(_) => Ok(().into_dynamic()), + + _ => panic!("should not appear: {:?}", expr), } } /// Evaluate a statement pub(crate) fn eval_stmt( - &mut self, + &self, scope: &mut Scope, + fn_lib: Option<&FunctionsLib>, stmt: &Stmt, level: usize, ) -> Result { @@ -1494,7 +1526,7 @@ impl Engine<'_> { // Expression as statement Stmt::Expr(expr) => { - let result = self.eval_expr(scope, expr, level)?; + let result = self.eval_expr(scope, fn_lib, expr, level)?; Ok(if !matches!(expr.as_ref(), Expr::Assignment(_, _, _)) { result @@ -1509,7 +1541,7 @@ impl Engine<'_> { let prev_len = scope.len(); let result = block.iter().try_fold(().into_dynamic(), |_, stmt| { - self.eval_stmt(scope, stmt, level) + self.eval_stmt(scope, fn_lib, stmt, level) }); scope.rewind(prev_len); @@ -1519,14 +1551,14 @@ impl Engine<'_> { // If-else statement Stmt::IfThenElse(guard, if_body, else_body) => self - .eval_expr(scope, guard, level)? + .eval_expr(scope, fn_lib, guard, level)? .try_cast::() .map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position())) .and_then(|guard_val| { if guard_val { - self.eval_stmt(scope, if_body, level) + self.eval_stmt(scope, fn_lib, if_body, level) } else if let Some(stmt) = else_body { - self.eval_stmt(scope, stmt.as_ref(), level) + self.eval_stmt(scope, fn_lib, stmt.as_ref(), level) } else { Ok(().into_dynamic()) } @@ -1534,12 +1566,19 @@ impl Engine<'_> { // While loop Stmt::While(guard, body) => loop { - match self.eval_expr(scope, guard, level)?.try_cast::() { - Ok(guard_val) if guard_val => match self.eval_stmt(scope, body, level) { - Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), - Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()), - Err(x) => return Err(x), - }, + match self + .eval_expr(scope, fn_lib, guard, level)? + .try_cast::() + { + Ok(guard_val) if guard_val => { + match self.eval_stmt(scope, fn_lib, body, level) { + Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), + Err(EvalAltResult::ErrorLoopBreak(true, _)) => { + return Ok(().into_dynamic()) + } + Err(x) => return Err(x), + } + } Ok(_) => return Ok(().into_dynamic()), Err(_) => return Err(EvalAltResult::ErrorLogicGuard(guard.position())), } @@ -1547,7 +1586,7 @@ impl Engine<'_> { // Loop statement Stmt::Loop(body) => loop { - match self.eval_stmt(scope, body, level) { + match self.eval_stmt(scope, fn_lib, body, level) { Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()), Err(x) => return Err(x), @@ -1556,36 +1595,32 @@ impl Engine<'_> { // For loop Stmt::For(name, expr, body) => { - let arr = self.eval_expr(scope, expr, level)?; - let tid = Any::type_id(&*arr); + let arr = self.eval_expr(scope, fn_lib, expr, level)?; + let tid = Any::type_id(arr.as_ref()); - if let Some(type_iterators) = &self.type_iterators { - if let Some(iter_fn) = type_iterators.get(&tid) { - // Add the loop variable - variable name is copied - // TODO - avoid copying variable name - scope.push(name.clone(), ()); + if let Some(iter_fn) = self.type_iterators.as_ref().and_then(|t| t.get(&tid)) { + // Add the loop variable - variable name is copied + // TODO - avoid copying variable name + scope.push(name.clone(), ()); - let entry = ScopeSource { - name, - index: scope.len() - 1, - typ: ScopeEntryType::Normal, - }; + let entry = ScopeSource { + name, + index: scope.len() - 1, + typ: ScopeEntryType::Normal, + }; - for a in iter_fn(&arr) { - *scope.get_mut(entry) = a; + for a in iter_fn(&arr) { + *scope.get_mut(entry) = a; - match self.eval_stmt(scope, body, level) { - Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), - Err(EvalAltResult::ErrorLoopBreak(true, _)) => break, - Err(x) => return Err(x), - } + match self.eval_stmt(scope, fn_lib, body, level) { + Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), + Err(EvalAltResult::ErrorLoopBreak(true, _)) => break, + Err(x) => return Err(x), } - - scope.rewind(scope.len() - 1); - Ok(().into_dynamic()) - } else { - Err(EvalAltResult::ErrorFor(expr.position())) } + + scope.rewind(scope.len() - 1); + Ok(().into_dynamic()) } else { Err(EvalAltResult::ErrorFor(expr.position())) } @@ -1604,7 +1639,7 @@ impl Engine<'_> { // Return value Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => Err(EvalAltResult::Return( - self.eval_expr(scope, a, level)?, + self.eval_expr(scope, fn_lib, a, level)?, *pos, )), @@ -1615,7 +1650,7 @@ impl Engine<'_> { // Throw value Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => { - let val = self.eval_expr(scope, a, level)?; + let val = self.eval_expr(scope, fn_lib, a, level)?; Err(EvalAltResult::ErrorRuntime( val.try_cast::().unwrap_or_else(|_| "".to_string()), *pos, @@ -1624,7 +1659,7 @@ impl Engine<'_> { // Let statement Stmt::Let(name, Some(expr), _) => { - let val = self.eval_expr(scope, expr, level)?; + let val = self.eval_expr(scope, fn_lib, expr, level)?; // TODO - avoid copying variable name in inner block? scope.push_dynamic_value(name.clone(), ScopeEntryType::Normal, val, false); Ok(().into_dynamic()) @@ -1638,7 +1673,7 @@ impl Engine<'_> { // Const statement Stmt::Const(name, expr, _) if expr.is_constant() => { - let val = self.eval_expr(scope, expr, level)?; + let val = self.eval_expr(scope, fn_lib, expr, level)?; // TODO - avoid copying variable name in inner block? scope.push_dynamic_value(name.clone(), ScopeEntryType::Constant, val, true); Ok(().into_dynamic()) @@ -1650,31 +1685,15 @@ impl Engine<'_> { /// Map a type_name into a pretty-print name pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { - if self.type_names.is_none() { - name - } else { - self.type_names - .as_ref() - .unwrap() - .get(name) - .map(String::as_str) - .unwrap_or(name) - } - } - - /// Clean up all script-defined functions within the `Engine`. - pub fn clear_functions(&mut self) { - self.fn_lib = None; + self.type_names + .as_ref() + .and_then(|list| list.get(name).map(String::as_str)) + .unwrap_or(name) } } /// Print/debug to stdout -#[cfg(not(feature = "no_std"))] -#[cfg(not(feature = "no_stdlib"))] fn default_print(s: &str) { + #[cfg(not(feature = "no_std"))] println!("{}", s); } - -/// No-op -#[cfg(any(feature = "no_std", feature = "no_stdlib"))] -fn default_print(_: &str) {} diff --git a/src/error.rs b/src/error.rs index 0991a19b..6ad21bbd 100644 --- a/src/error.rs +++ b/src/error.rs @@ -37,6 +37,10 @@ impl fmt::Display for LexError { } /// Type of error encountered when parsing a script. +/// +/// Some errors never appear when certain features are turned on. +/// They still exist so that the application can turn features on and off without going through +/// massive code changes to remove/add back enum variants in match statements. #[derive(Debug, PartialEq, Clone)] pub enum ParseErrorType { /// Error in the script text. Wrapped value is the error message. @@ -51,17 +55,21 @@ pub enum ParseErrorType { MalformedCallExpr(String), /// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error description (if any). /// - /// Not available under the `no_index` feature. - #[cfg(not(feature = "no_index"))] + /// Never appears under the `no_index` feature. MalformedIndexExpr(String), + /// An expression in an `in` expression has syntax error. Wrapped value is the error description (if any). + /// + /// Never appears under the `no_object` and `no_index` features combination. + MalformedInExpr(String), /// A map definition has duplicated property names. Wrapped value is the property name. /// - /// Not available under the `no_object` feature. - #[cfg(not(feature = "no_object"))] + /// Never appears under the `no_object` feature. DuplicatedProperty(String), /// Invalid expression assigned to constant. Wrapped value is the name of the constant. ForbiddenConstantExpr(String), /// Missing a property name for custom types and maps. + /// + /// Never appears under the `no_object` feature. PropertyExpected, /// Missing a variable name after the `let`, `const` or `for` keywords. VariableExpected, @@ -69,28 +77,23 @@ pub enum ParseErrorType { ExprExpected(String), /// Defining a function `fn` in an appropriate place (e.g. inside another function). /// - /// Not available under the `no_function` feature. - #[cfg(not(feature = "no_function"))] + /// Never appears under the `no_function` feature. WrongFnDefinition, /// Missing a function name after the `fn` keyword. /// - /// Not available under the `no_function` feature. - #[cfg(not(feature = "no_function"))] + /// Never appears under the `no_function` feature. FnMissingName, /// A function definition is missing the parameters list. Wrapped value is the function name. /// - /// Not available under the `no_function` feature. - #[cfg(not(feature = "no_function"))] + /// Never appears under the `no_function` feature. FnMissingParams(String), /// A function definition has duplicated parameters. Wrapped values are the function name and parameter name. /// - /// Not available under the `no_function` feature. - #[cfg(not(feature = "no_function"))] + /// Never appears under the `no_function` feature. FnDuplicatedParam(String, String), /// A function definition is missing the body. Wrapped value is the function name. /// - /// Not available under the `no_function` feature. - #[cfg(not(feature = "no_function"))] + /// Never appears under the `no_function` feature. FnMissingBody(String), /// Assignment to an inappropriate LHS (left-hand-side) expression. AssignmentToInvalidLHS, @@ -136,23 +139,17 @@ impl ParseError { ParseErrorType::UnknownOperator(_) => "Unknown operator", ParseErrorType::MissingToken(_, _) => "Expecting a certain token that is missing", ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments", - #[cfg(not(feature = "no_index"))] ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression", - #[cfg(not(feature = "no_object"))] + ParseErrorType::MalformedInExpr(_) => "Invalid 'in' expression", ParseErrorType::DuplicatedProperty(_) => "Duplicated property in object map literal", ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant", ParseErrorType::PropertyExpected => "Expecting name of a property", ParseErrorType::VariableExpected => "Expecting name of a variable", ParseErrorType::ExprExpected(_) => "Expecting an expression", - #[cfg(not(feature = "no_function"))] ParseErrorType::FnMissingName => "Expecting name in function declaration", - #[cfg(not(feature = "no_function"))] ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration", - #[cfg(not(feature = "no_function"))] ParseErrorType::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration", - #[cfg(not(feature = "no_function"))] ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration", - #[cfg(not(feature = "no_function"))] ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function", ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression", ParseErrorType::AssignmentToCopy => "Cannot assign to this expression because it will only be changing a copy of the value", @@ -175,29 +172,28 @@ impl fmt::Display for ParseError { } ParseErrorType::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s)?, - #[cfg(not(feature = "no_index"))] ParseErrorType::MalformedIndexExpr(s) => { write!(f, "{}", if s.is_empty() { self.desc() } else { s })? } - #[cfg(not(feature = "no_object"))] + ParseErrorType::MalformedInExpr(s) => { + write!(f, "{}", if s.is_empty() { self.desc() } else { s })? + } + ParseErrorType::DuplicatedProperty(s) => { write!(f, "Duplicated property '{}' for object map literal", s)? } ParseErrorType::ExprExpected(s) => write!(f, "Expecting {} expression", s)?, - #[cfg(not(feature = "no_function"))] ParseErrorType::FnMissingParams(s) => { write!(f, "Expecting parameters for function '{}'", s)? } - #[cfg(not(feature = "no_function"))] ParseErrorType::FnMissingBody(s) => { write!(f, "Expecting body statement block for function '{}'", s)? } - #[cfg(not(feature = "no_function"))] ParseErrorType::FnDuplicatedParam(s, arg) => { write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)? } diff --git a/src/call.rs b/src/fn_call.rs similarity index 98% rename from src/call.rs rename to src/fn_call.rs index ac33e2c7..c81b25bc 100644 --- a/src/call.rs +++ b/src/fn_call.rs @@ -36,6 +36,7 @@ macro_rules! impl_args { (@pop) => { }; (@pop $head:ident) => { + impl_args!(); }; (@pop $head:ident $(, $tail:ident)+) => { impl_args!($($tail),*); diff --git a/src/fn_func.rs b/src/fn_func.rs new file mode 100644 index 00000000..7350d6d5 --- /dev/null +++ b/src/fn_func.rs @@ -0,0 +1,120 @@ +//! Module which defines the function registration mechanism. +#![cfg(not(feature = "no_function"))] +#![allow(non_snake_case)] + +use crate::any::Any; +use crate::engine::Engine; +use crate::error::ParseError; +use crate::parser::AST; +use crate::result::EvalAltResult; +use crate::scope::Scope; + +use crate::stdlib::{boxed::Box, string::ToString}; + +/// A trait to create a Rust anonymous function from a script. +pub trait Func { + type Output; + + /// Create a Rust anonymous function from an `AST`. + /// The `Engine` and `AST` are consumed and basically embedded into the closure. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// use rhai::{Engine, Func}; // use 'Func' for 'create_from_ast' + /// + /// let engine = Engine::new(); // create a new 'Engine' just for this + /// + /// let ast = engine.compile("fn calc(x, y) { x + y.len() < 42 }")?; + /// + /// // Func takes two type parameters: + /// // 1) a tuple made up of the types of the script function's parameters + /// // 2) the return type of the script function + /// // + /// // 'func' will have type Box Result> and is callable! + /// let func = Func::<(i64, String), bool>::create_from_ast( + /// // ^^^^^^^^^^^^^ function parameter types in tuple + /// + /// engine, // the 'Engine' is consumed into the closure + /// ast, // the 'AST' + /// "calc" // the entry-point function name + /// ); + /// + /// func(123, "hello".to_string())? == false; // call the anonymous function + /// # Ok(()) + /// # } + fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output; + + /// Create a Rust anonymous function from a script. + /// The `Engine` is consumed and basically embedded into the closure. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// use rhai::{Engine, Func}; // use 'Func' for 'create_from_script' + /// + /// let engine = Engine::new(); // create a new 'Engine' just for this + /// + /// let script = "fn calc(x, y) { x + y.len() < 42 }"; + /// + /// // Func takes two type parameters: + /// // 1) a tuple made up of the types of the script function's parameters + /// // 2) the return type of the script function + /// // + /// // 'func' will have type Box Result> and is callable! + /// let func = Func::<(i64, String), bool>::create_from_script( + /// // ^^^^^^^^^^^^^ function parameter types in tuple + /// + /// engine, // the 'Engine' is consumed into the closure + /// script, // the script, notice number of parameters must match + /// "calc" // the entry-point function name + /// )?; + /// + /// func(123, "hello".to_string())? == false; // call the anonymous function + /// # Ok(()) + /// # } + /// ``` + fn create_from_script( + self, + script: &str, + entry_point: &str, + ) -> Result; +} + +macro_rules! def_anonymous_fn { + () => { + def_anonymous_fn!(imp); + }; + (imp $($par:ident),*) => { + impl<'e, $($par: Any + Clone,)* RET: Any + Clone> Func<($($par,)*), RET> for Engine<'e> + { + #[cfg(feature = "sync")] + type Output = Box Result + Send + Sync + 'e>; + + #[cfg(not(feature = "sync"))] + type Output = Box Result + 'e>; + + fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output { + let name = entry_point.to_string(); + + Box::new(move |$($par: $par),*| { + self.call_fn(&mut Scope::new(), &ast, &name, ($($par,)*)) + }) + } + + fn create_from_script(self, script: &str, entry_point: &str) -> Result { + let ast = self.compile(script)?; + Ok(Func::<($($par,)*), RET>::create_from_ast(self, ast, entry_point)) + } + } + }; + ($p0:ident $(, $p:ident)*) => { + def_anonymous_fn!(imp $p0 $(, $p)*); + def_anonymous_fn!($($p),*); + }; +} + +#[rustfmt::skip] +def_anonymous_fn!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); diff --git a/src/fn_register.rs b/src/fn_register.rs index 7ff2cc28..9030066a 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -132,9 +132,9 @@ macro_rules! def_register { def_register!(imp); }; (imp $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => { - // ^ function parameter generic type name + // ^ function parameter generic type name (A, B, C etc.) // ^ function parameter marker type (T, Ref or Mut) - // ^ function parameter actual type + // ^ function parameter actual type (T, &T or &mut T) // ^ dereferencing function impl< $($par: Any + Clone,)* @@ -171,6 +171,7 @@ macro_rules! def_register { let r = f($(($clone)($par)),*); Ok(Box::new(r) as Dynamic) }; + self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func)); } } @@ -255,17 +256,14 @@ macro_rules! def_register { def_register!(imp $p0 => $p0 => $p0 => Clone::clone $(, $p => $p => $p => Clone::clone)*); def_register!(imp $p0 => Mut<$p0> => &mut $p0 => identity $(, $p => $p => $p => Clone::clone)*); // handle the first parameter ^ first parameter passed through - // others passed by value (cloned) ^ + // ^ others passed by value (cloned) - // No support for functions where the first argument is a reference + // Currently does not support first argument which is a reference, as there will be + // conflicting implementations since &T: Any and T: Any cannot be distinguished //def_register!(imp $p0 => Ref<$p0> => &$p0 => identity $(, $p => $p => $p => Clone::clone)*); def_register!($($p),*); }; -// (imp_pop) => {}; -// (imp_pop $head:ident => $head_mark:ty => $head_param:ty $(,$tail:ident => $tail_mark:ty => $tp:ty)*) => { -// def_register!(imp $($tail => $tail_mark => $tp),*); -// }; } #[rustfmt::skip] diff --git a/src/lib.rs b/src/lib.rs index 3321fccc..dd4155ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,15 +3,19 @@ //! Rhai is a tiny, simple and very fast embedded scripting language for Rust //! that gives you a safe and easy way to add scripting to your applications. //! It provides a familiar syntax based on JS and Rust and a simple Rust interface. -//! Here is a quick example. First, the contents of `my_script.rhai`: +//! Here is a quick example. +//! +//! First, the contents of `my_script.rhai`: //! //! ```,ignore +//! // Brute force factorial function //! fn factorial(x) { //! if x == 1 { return 1; } //! x * factorial(x - 1) //! } //! -//! compute_something(factorial(10)) +//! // Calling an external function 'compute' +//! compute(factorial(10)) //! ``` //! //! And the Rust part: @@ -21,16 +25,23 @@ //! //! fn main() -> Result<(), EvalAltResult> //! { +//! // Define external function //! fn compute_something(x: i64) -> bool { //! (x % 40) == 0 //! } //! +//! // Create scripting engine //! let mut engine = Engine::new(); //! -//! engine.register_fn("compute_something", compute_something); +//! // Register external function as 'compute' +//! engine.register_fn("compute", compute_something); //! -//! # #[cfg(not(feature = "no_std"))] -//! assert_eq!(engine.eval_file::("my_script.rhai".into())?, true); +//! # #[cfg(not(feature = "no_std"))] +//! assert_eq!( +//! // Evaluate the script, expects a 'bool' return +//! engine.eval_file::("my_script.rhai".into())?, +//! true +//! ); //! //! Ok(()) //! } @@ -40,7 +51,6 @@ //! //! | Feature | Description | //! | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -//! | `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 not needed. | //! | `no_index` | Disable arrays and indexing features if not needed. | @@ -62,9 +72,10 @@ extern crate alloc; mod any; mod api; mod builtin; -mod call; mod engine; mod error; +mod fn_call; +mod fn_func; mod fn_register; mod optimize; mod parser; @@ -73,14 +84,17 @@ mod scope; mod stdlib; pub use any::{Any, AnyExt, Dynamic, Variant}; -pub use call::FuncArgs; pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; +pub use fn_call::FuncArgs; pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; pub use parser::{Position, AST, INT}; pub use result::EvalAltResult; pub use scope::Scope; +#[cfg(not(feature = "no_function"))] +pub use fn_func::Func; + #[cfg(not(feature = "no_index"))] pub use engine::Array; diff --git a/src/optimize.rs b/src/optimize.rs index ec5d3688..fc5a4577 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,15 +1,15 @@ -#![cfg(not(feature = "no_optimize"))] - use crate::any::{Any, Dynamic}; use crate::engine::{ - Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, + Engine, FnAny, FnCallArgs, FnSpec, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; -use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; +use crate::parser::{map_dynamic_to_expr, Expr, FnDef, Position, ReturnType, Stmt, AST}; +use crate::result::EvalAltResult; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::stdlib::{ boxed::Box, + collections::HashMap, rc::Rc, string::{String, ToString}, sync::Arc, @@ -31,6 +31,17 @@ pub enum OptimizationLevel { Full, } +impl OptimizationLevel { + /// Is the `OptimizationLevel` None. + pub fn is_none(self) -> bool { + self == Self::None + } + /// Is the `OptimizationLevel` Full. + pub fn is_full(self) -> bool { + self == Self::Full + } +} + /// Mutable state throughout an optimization pass. struct State<'a> { /// Has the AST been changed during this pass? @@ -39,15 +50,25 @@ struct State<'a> { constants: Vec<(String, Expr)>, /// An `Engine` instance for eager function evaluation. engine: &'a Engine<'a>, + /// Library of script-defined functions. + fn_lib: &'a [(&'a str, usize)], + /// Optimization level. + optimization_level: OptimizationLevel, } impl<'a> State<'a> { /// Create a new State. - pub fn new(engine: &'a Engine<'a>) -> Self { + pub fn new( + engine: &'a Engine<'a>, + fn_lib: &'a [(&'a str, usize)], + level: OptimizationLevel, + ) -> Self { Self { changed: false, constants: vec![], engine, + fn_lib, + optimization_level: level, } } /// Reset the state from dirty to clean. @@ -86,6 +107,25 @@ impl<'a> State<'a> { } } +/// Call a registered function +fn call_fn( + functions: Option<&HashMap>>, + fn_name: &str, + args: &mut FnCallArgs, + pos: Position, +) -> Result, EvalAltResult> { + let spec = FnSpec { + name: fn_name.into(), + args: args.iter().map(|a| Any::type_id(*a)).collect(), + }; + + // Search built-in's and external functions + functions + .and_then(|f| f.get(&spec)) + .map(|func| func(args, pos)) + .transpose() +} + /// Optimize a statement. fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt { match stmt { @@ -345,25 +385,51 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { // id = expr expr => Expr::Assignment(id, Box::new(optimize_expr(expr, state)), pos), }, + // lhs.rhs #[cfg(not(feature = "no_object"))] - Expr::Dot(lhs, rhs, pos) => Expr::Dot( - Box::new(optimize_expr(*lhs, state)), - Box::new(optimize_expr(*rhs, state)), - pos, - ), + Expr::Dot(lhs, rhs, pos) => match (*lhs, *rhs) { + // map.string + (Expr::Map(items, pos), Expr::Property(s, _)) + if items.iter().all(|(_, x, _)| x.is_pure()) => + { + // Map literal where everything is pure - promote the indexed item. + // All other items can be thrown away. + state.set_dirty(); + items.into_iter().find(|(name, _, _)| name == s.as_ref()) + .map(|(_, expr, _)| expr.set_position(pos)) + .unwrap_or_else(|| Expr::Unit(pos)) + } + // lhs.rhs + (lhs, rhs) => Expr::Dot( + Box::new(optimize_expr(lhs, state)), + Box::new(optimize_expr(rhs, state)), + pos, + ) + } // lhs[rhs] #[cfg(not(feature = "no_index"))] Expr::Index(lhs, rhs, pos) => match (*lhs, *rhs) { // array[int] - (Expr::Array(mut items, _), Expr::IntegerConstant(i, _)) - if i >= 0 && (i as usize) < items.len() && items.iter().all(|x| x.is_pure()) => + (Expr::Array(mut items, pos), Expr::IntegerConstant(i, _)) + if i >= 0 && (i as usize) < items.len() && items.iter().all(Expr::is_pure) => { // Array literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); - items.remove(i as usize) + items.remove(i as usize).set_position(pos) + } + // map[string] + (Expr::Map(items, pos), Expr::StringConstant(s, _)) + if items.iter().all(|(_, x, _)| x.is_pure()) => + { + // Map literal where everything is pure - promote the indexed item. + // All other items can be thrown away. + state.set_dirty(); + items.into_iter().find(|(name, _, _)| name == s.as_ref()) + .map(|(_, expr, _)| expr.set_position(pos)) + .unwrap_or_else(|| Expr::Unit(pos)) } // string[int] (Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) @@ -392,8 +458,55 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { .into_iter() .map(|(key, expr, pos)| (key, optimize_expr(expr, state), pos)) .collect(), pos), + // lhs in rhs + Expr::In(lhs, rhs, pos) => match (*lhs, *rhs) { + // "xxx" in "xxxxx" + (Expr::StringConstant(lhs, pos), Expr::StringConstant(rhs, _)) => { + state.set_dirty(); + if rhs.contains(lhs.as_ref()) { + Expr::True(pos) + } else { + Expr::False(pos) + } + } + // 'x' in "xxxxx" + (Expr::CharConstant(lhs, pos), Expr::StringConstant(rhs, _)) => { + state.set_dirty(); + if rhs.contains(&lhs.to_string()) { + Expr::True(pos) + } else { + Expr::False(pos) + } + } + // "xxx" in #{...} + (Expr::StringConstant(lhs, pos), Expr::Map(items, _)) => { + state.set_dirty(); + if items.iter().find(|(name, _, _)| name == &lhs).is_some() { + Expr::True(pos) + } else { + Expr::False(pos) + } + } + // 'x' in #{...} + (Expr::CharConstant(lhs, pos), Expr::Map(items, _)) => { + state.set_dirty(); + let lhs = lhs.to_string(); + + if items.iter().find(|(name, _, _)| name == &lhs).is_some() { + Expr::True(pos) + } else { + Expr::False(pos) + } + } + // lhs in rhs + (lhs, rhs) => Expr::In( + Box::new(optimize_expr(lhs, state)), + Box::new(optimize_expr(rhs, state)), + pos + ), + }, // lhs && rhs - Expr::And(lhs, rhs) => match (*lhs, *rhs) { + Expr::And(lhs, rhs, pos) => match (*lhs, *rhs) { // true && rhs -> rhs (Expr::True(_), rhs) => { state.set_dirty(); @@ -413,10 +526,11 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { (lhs, rhs) => Expr::And( Box::new(optimize_expr(lhs, state)), Box::new(optimize_expr(rhs, state)), + pos ), }, // lhs || rhs - Expr::Or(lhs, rhs) => match (*lhs, *rhs) { + Expr::Or(lhs, rhs, pos) => match (*lhs, *rhs) { // false || rhs -> rhs (Expr::False(_), rhs) => { state.set_dirty(); @@ -436,28 +550,23 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { (lhs, rhs) => Expr::Or( Box::new(optimize_expr(lhs, state)), Box::new(optimize_expr(rhs, state)), + pos ), }, - // Do not optimize anything within dump_ast - Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST => - Expr::FunctionCall(id, args, def_value, pos), - // Do not call some special keywords - Expr::FunctionCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_str())=> + Expr::FunctionCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref())=> Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos), // Eagerly call functions Expr::FunctionCall(id, args, def_value, pos) - if state.engine.optimization_level == OptimizationLevel::Full // full optimizations + if state.optimization_level == OptimizationLevel::Full // full optimizations && args.iter().all(|expr| expr.is_constant()) // all arguments are constants => { // First search in script-defined functions (can override built-in) - if let Some(fn_lib_arc) = &state.engine.fn_lib { - if fn_lib_arc.has_function(&id, args.len()) { - // A script-defined function overrides the built-in function - do not make the call - return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos); - } + if state.fn_lib.iter().find(|(name, len)| name == &id && *len == args.len()).is_some() { + // A script-defined function overrides the built-in function - do not make the call + return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos); } let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); @@ -471,21 +580,25 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { "" }; - state.engine.call_ext_fn_raw(&id, &mut call_args, pos).ok().map(|result| - result.or_else(|| { - if !arg_for_type_of.is_empty() { - // Handle `type_of()` - Some(arg_for_type_of.to_string().into_dynamic()) - } else { - // Otherwise use the default value, if any - def_value.clone() - } - }).and_then(|result| map_dynamic_to_expr(result, pos)) + call_fn(state.engine.functions.as_ref(), &id, &mut call_args, pos).ok() + .and_then(|result| + result.or_else(|| { + if !arg_for_type_of.is_empty() { + // Handle `type_of()` + Some(arg_for_type_of.to_string().into_dynamic()) + } else { + // Otherwise use the default value, if any + def_value.clone() + } + }).and_then(|result| map_dynamic_to_expr(result, pos)) .map(|expr| { state.set_dirty(); expr }) - ).flatten().unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos)) + ).unwrap_or_else(|| + // Optimize function call arguments + Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos) + ) } // id(args ..) -> optimize function call arguments @@ -493,11 +606,11 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos), // constant-name - Expr::Variable(name, _) if state.contains_constant(&name) => { + Expr::Variable(name, pos) if state.contains_constant(&name) => { state.set_dirty(); // Replace constant with value - state.find_constant(&name).expect("should find constant in scope!").clone() + state.find_constant(&name).expect("should find constant in scope!").clone().set_position(pos) } // All other expressions - skip @@ -505,14 +618,20 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { } } -pub(crate) fn optimize<'a>(statements: Vec, engine: &Engine<'a>, scope: &Scope) -> Vec { +fn optimize<'a>( + statements: Vec, + engine: &Engine<'a>, + scope: &Scope, + fn_lib: &'a [(&'a str, usize)], + level: OptimizationLevel, +) -> Vec { // If optimization level is None then skip optimizing - if engine.optimization_level == OptimizationLevel::None { + if level == OptimizationLevel::None { return statements; } // Set up the state - let mut state = State::new(engine); + let mut state = State::new(engine, fn_lib, level); // Add constants from the scope into the state scope @@ -544,16 +663,18 @@ pub(crate) fn optimize<'a>(statements: Vec, engine: &Engine<'a>, scope: &S .into_iter() .enumerate() .map(|(i, stmt)| { - if let Stmt::Const(name, value, _) = &stmt { - // Load constants - state.push_constant(name, value.as_ref().clone()); - stmt // Keep it in the global scope - } else { - // Keep all variable declarations at this level - // and always keep the last return value - let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1; - - optimize_stmt(stmt, &mut state, keep) + match stmt { + Stmt::Const(ref name, ref value, _) => { + // Load constants + state.push_constant(name.as_ref(), value.as_ref().clone()); + stmt // Keep it in the global scope + } + _ => { + // Keep all variable declarations at this level + // and always keep the last return value + let keep = matches!(stmt, Stmt::Let(_, _, _)) || i == num_statements - 1; + optimize_stmt(stmt, &mut state, keep) + } } }) .collect(); @@ -585,17 +706,27 @@ pub fn optimize_into_ast( scope: &Scope, statements: Vec, functions: Vec, + level: OptimizationLevel, ) -> AST { - let fn_lib = FunctionsLib::from_vec( + #[cfg(feature = "no_optimize")] + const level: OptimizationLevel = OptimizationLevel::None; + + let fn_lib: Vec<_> = functions + .iter() + .map(|fn_def| (fn_def.name.as_str(), fn_def.params.len())) + .collect(); + + let lib = FunctionsLib::from_vec( functions .iter() .cloned() .map(|mut fn_def| { - if engine.optimization_level != OptimizationLevel::None { + if !level.is_none() { let pos = fn_def.body.position(); // Optimize the function body - let mut body = optimize(vec![fn_def.body], engine, &Scope::new()); + let mut body = + optimize(vec![fn_def.body], engine, &Scope::new(), &fn_lib, level); // {} -> Noop fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { @@ -615,15 +746,15 @@ pub fn optimize_into_ast( ); AST( - match engine.optimization_level { + match level { OptimizationLevel::None => statements, OptimizationLevel::Simple | OptimizationLevel::Full => { - optimize(statements, engine, &scope) + optimize(statements, engine, &scope, &fn_lib, level) } }, #[cfg(feature = "sync")] - Arc::new(fn_lib), + Arc::new(lib), #[cfg(not(feature = "sync"))] - Rc::new(fn_lib), + Rc::new(lib), ) } diff --git a/src/parser.rs b/src/parser.rs index 8dcbbc98..9874edcf 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,19 +1,19 @@ //! Main module defining the lexer and parser. use crate::any::{Any, AnyExt, Dynamic}; -use crate::engine::{Engine, FunctionsLib}; +use crate::engine::{Array, Engine, FunctionsLib, Map}; use crate::error::{LexError, ParseError, ParseErrorType}; +use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; -#[cfg(not(feature = "no_optimize"))] -use crate::optimize::optimize_into_ast; - use crate::stdlib::{ borrow::Cow, boxed::Box, char, collections::HashMap, - fmt, format, + fmt, + fmt::Display, + format, iter::Peekable, ops::Add, rc::Rc, @@ -40,7 +40,6 @@ pub type INT = i32; /// The system floating-point type. /// /// Not available under the `no_float` feature. -#[cfg(not(feature = "no_float"))] pub type FLOAT = f64; type LERR = LexError; @@ -197,7 +196,7 @@ impl AST { /// # { /// use rhai::Engine; /// - /// let mut engine = Engine::new(); + /// let engine = Engine::new(); /// /// let ast1 = engine.compile(r#"fn foo(x) { 42 + x } foo(1)"#)?; /// let ast2 = engine.compile(r#"fn foo(n) { "hello" + n } foo("!")"#)?; @@ -316,11 +315,11 @@ pub enum Stmt { /// loop { stmt } Loop(Box), /// for id in expr { stmt } - For(String, Box, Box), + For(Cow<'static, str>, Box, Box), /// let id = expr - Let(String, Option>, Position), + Let(Cow<'static, str>, Option>, Position), /// const id = expr - Const(String, Box, Position), + Const(Cow<'static, str>, Box, Position), /// { stmt; ... } Block(Vec, Position), /// { stmt } @@ -396,38 +395,35 @@ pub enum Expr { /// Integer constant. IntegerConstant(INT, Position), /// Floating-point constant. - #[cfg(not(feature = "no_float"))] FloatConstant(FLOAT, Position), /// Character constant. CharConstant(char, Position), /// String constant. - StringConstant(String, Position), + StringConstant(Cow<'static, str>, Position), /// Variable access. - Variable(String, Position), + Variable(Cow<'static, str>, Position), /// Property access. - Property(String, Position), + Property(Cow<'static, str>, Position), /// { stmt } Stmt(Box, Position), /// func(expr, ... ) - FunctionCall(String, Vec, Option, Position), + FunctionCall(Cow<'static, str>, Vec, Option, Position), /// expr = expr Assignment(Box, Box, Position), /// lhs.rhs - #[cfg(not(feature = "no_object"))] Dot(Box, Box, Position), /// expr[expr] - #[cfg(not(feature = "no_index"))] Index(Box, Box, Position), - #[cfg(not(feature = "no_index"))] /// [ expr, ... ] Array(Vec, Position), - #[cfg(not(feature = "no_object"))] /// #{ name:expr, ... } Map(Vec<(String, Expr, Position)>, Position), + /// lhs in rhs + In(Box, Box, Position), /// lhs && rhs - And(Box, Box), + And(Box, Box, Position), /// lhs || rhs - Or(Box, Box), + Or(Box, Box, Position), /// true True(Position), /// false @@ -444,30 +440,26 @@ impl Expr { /// Panics when the expression is not constant. pub fn get_constant_value(&self) -> Dynamic { match self { - Expr::IntegerConstant(i, _) => i.into_dynamic(), - Expr::CharConstant(c, _) => c.into_dynamic(), - Expr::StringConstant(s, _) => s.into_dynamic(), - Expr::True(_) => true.into_dynamic(), - Expr::False(_) => false.into_dynamic(), - Expr::Unit(_) => ().into_dynamic(), + Self::IntegerConstant(i, _) => i.into_dynamic(), + Self::FloatConstant(f, _) => f.into_dynamic(), + Self::CharConstant(c, _) => c.into_dynamic(), + Self::StringConstant(s, _) => s.clone().into_owned().into_dynamic(), + Self::True(_) => true.into_dynamic(), + Self::False(_) => false.into_dynamic(), + Self::Unit(_) => ().into_dynamic(), - #[cfg(not(feature = "no_index"))] - Expr::Array(items, _) if items.iter().all(Expr::is_constant) => items + Self::Array(items, _) if items.iter().all(Self::is_constant) => items .iter() - .map(Expr::get_constant_value) + .map(Self::get_constant_value) .collect::>() .into_dynamic(), - #[cfg(not(feature = "no_object"))] - Expr::Map(items, _) if items.iter().all(|(_, v, _)| v.is_constant()) => items + Self::Map(items, _) if items.iter().all(|(_, v, _)| v.is_constant()) => items .iter() .map(|(k, v, _)| (k.clone(), v.get_constant_value())) .collect::>() .into_dynamic(), - #[cfg(not(feature = "no_float"))] - Expr::FloatConstant(f, _) => f.into_dynamic(), - _ => panic!("cannot get value of non-constant expression"), } } @@ -479,18 +471,15 @@ impl Expr { /// Panics when the expression is not constant. pub fn get_constant_str(&self) -> String { match self { - Expr::IntegerConstant(i, _) => i.to_string(), - Expr::CharConstant(c, _) => c.to_string(), - Expr::StringConstant(_, _) => "string".to_string(), - Expr::True(_) => "true".to_string(), - Expr::False(_) => "false".to_string(), - Expr::Unit(_) => "()".to_string(), + Self::IntegerConstant(i, _) => i.to_string(), + Self::FloatConstant(f, _) => f.to_string(), + Self::CharConstant(c, _) => c.to_string(), + Self::StringConstant(_, _) => "string".to_string(), + Self::True(_) => "true".to_string(), + Self::False(_) => "false".to_string(), + Self::Unit(_) => "()".to_string(), - #[cfg(not(feature = "no_index"))] - Expr::Array(items, _) if items.iter().all(Expr::is_constant) => "array".to_string(), - - #[cfg(not(feature = "no_float"))] - Expr::FloatConstant(f, _) => f.to_string(), + Self::Array(items, _) if items.iter().all(Self::is_constant) => "array".to_string(), _ => panic!("cannot get value of non-constant expression"), } @@ -499,73 +488,96 @@ impl Expr { /// Get the `Position` of the expression. pub fn position(&self) -> Position { match self { - Expr::IntegerConstant(_, pos) - | Expr::CharConstant(_, pos) - | Expr::StringConstant(_, pos) - | Expr::Variable(_, pos) - | Expr::Property(_, pos) - | Expr::Stmt(_, pos) - | Expr::FunctionCall(_, _, _, pos) - | Expr::True(pos) - | Expr::False(pos) - | Expr::Unit(pos) => *pos, + Self::IntegerConstant(_, pos) + | Self::FloatConstant(_, pos) + | Self::CharConstant(_, pos) + | Self::StringConstant(_, pos) + | Self::Array(_, pos) + | Self::Map(_, pos) + | Self::Variable(_, pos) + | Self::Property(_, pos) + | Self::Stmt(_, pos) + | Self::FunctionCall(_, _, _, pos) + | Self::And(_, _, pos) + | Self::Or(_, _, pos) + | Self::In(_, _, pos) + | Self::True(pos) + | Self::False(pos) + | Self::Unit(pos) => *pos, - Expr::Assignment(expr, _, _) | Expr::And(expr, _) | Expr::Or(expr, _) => { + Self::Assignment(expr, _, _) | Self::Dot(expr, _, _) | Self::Index(expr, _, _) => { expr.position() } - - #[cfg(not(feature = "no_object"))] - Expr::Dot(expr, _, _) => expr.position(), - - #[cfg(not(feature = "no_float"))] - Expr::FloatConstant(_, pos) => *pos, - - #[cfg(not(feature = "no_index"))] - Expr::Array(_, pos) => *pos, - - #[cfg(not(feature = "no_object"))] - Expr::Map(_, pos) => *pos, - - #[cfg(not(feature = "no_index"))] - Expr::Index(expr, _, _) => expr.position(), } } + /// Get the `Position` of the expression. + pub(crate) fn set_position(mut self, new_pos: Position) -> Self { + match &mut self { + Self::IntegerConstant(_, pos) + | Self::FloatConstant(_, pos) + | Self::CharConstant(_, pos) + | Self::StringConstant(_, pos) + | Self::Array(_, pos) + | Self::Map(_, pos) + | Self::Variable(_, pos) + | Self::Property(_, pos) + | Self::Stmt(_, pos) + | Self::FunctionCall(_, _, _, pos) + | Self::And(_, _, pos) + | Self::Or(_, _, pos) + | Self::In(_, _, pos) + | Self::True(pos) + | Self::False(pos) + | Self::Unit(pos) + | Self::Assignment(_, _, pos) + | Self::Dot(_, _, pos) + | Self::Index(_, _, pos) => *pos = new_pos, + } + + self + } + /// Is the expression pure? /// /// A pure expression has no side effects. pub fn is_pure(&self) -> bool { match self { - #[cfg(not(feature = "no_index"))] - Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure), + Self::Array(expressions, _) => expressions.iter().all(Self::is_pure), - #[cfg(not(feature = "no_index"))] - Expr::Index(x, y, _) => x.is_pure() && y.is_pure(), + Self::Index(x, y, _) | Self::And(x, y, _) | Self::Or(x, y, _) | Self::In(x, y, _) => { + x.is_pure() && y.is_pure() + } - Expr::And(x, y) | Expr::Or(x, y) => x.is_pure() && y.is_pure(), + Self::Stmt(stmt, _) => stmt.is_pure(), - Expr::Stmt(stmt, _) => stmt.is_pure(), - - expr => expr.is_constant() || matches!(expr, Expr::Variable(_, _)), + expr => expr.is_constant() || matches!(expr, Self::Variable(_, _)), } } /// Is the expression a constant? pub fn is_constant(&self) -> bool { match self { - Expr::IntegerConstant(_, _) - | Expr::CharConstant(_, _) - | Expr::StringConstant(_, _) - | Expr::True(_) - | Expr::False(_) - | Expr::Unit(_) => true, - - #[cfg(not(feature = "no_float"))] - Expr::FloatConstant(_, _) => true, + Self::IntegerConstant(_, _) + | Self::FloatConstant(_, _) + | Self::CharConstant(_, _) + | Self::StringConstant(_, _) + | Self::True(_) + | Self::False(_) + | Self::Unit(_) => true, // An array literal is constant if all items are constant - #[cfg(not(feature = "no_index"))] - Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant), + Self::Array(expressions, _) => expressions.iter().all(Self::is_constant), + + // An map literal is constant if all items are constant + Self::Map(items, _) => items.iter().map(|(_, expr, _)| expr).all(Self::is_constant), + + // Check in expression + Self::In(lhs, rhs, _) => match (lhs.as_ref(), rhs.as_ref()) { + (Self::StringConstant(_, _), Self::StringConstant(_, _)) + | (Self::CharConstant(_, _), Self::StringConstant(_, _)) => true, + _ => false, + }, _ => false, } @@ -576,7 +588,6 @@ impl Expr { #[derive(Debug, PartialEq, Clone)] pub enum Token { IntegerConstant(INT), - #[cfg(not(feature = "no_float"))] FloatConstant(FLOAT), Identifier(String), CharConstant(char), @@ -585,9 +596,7 @@ pub enum Token { RightBrace, LeftParen, RightParen, - #[cfg(not(feature = "no_index"))] LeftBracket, - #[cfg(not(feature = "no_index"))] RightBracket, Plus, UnaryPlus, @@ -651,11 +660,10 @@ pub enum Token { impl Token { /// Get the syntax of the token. pub fn syntax(&self) -> Cow { - use self::Token::*; + use Token::*; match self { IntegerConstant(i) => i.to_string().into(), - #[cfg(not(feature = "no_float"))] FloatConstant(f) => f.to_string().into(), Identifier(s) => s.into(), CharConstant(c) => c.to_string().into(), @@ -667,9 +675,7 @@ impl Token { RightBrace => "}", LeftParen => "(", RightParen => ")", - #[cfg(not(feature = "no_index"))] LeftBracket => "[", - #[cfg(not(feature = "no_index"))] RightBracket => "]", Plus => "+", UnaryPlus => "+", @@ -736,7 +742,7 @@ impl Token { // If another operator is after these, it's probably an unary operator // (not sure about fn name). pub fn is_next_unary(&self) -> bool { - use self::Token::*; + use Token::*; match self { LexError(_) | @@ -744,6 +750,8 @@ impl Token { // RightBrace | {expr} - expr not unary & is closing LeftParen | // {-expr} - is unary // RightParen | (expr) - expr not unary & is closing + LeftBracket | // [-expr] - is unary + // RightBracket | [expr] - expr not unary & is closing Plus | UnaryPlus | Minus | @@ -787,50 +795,37 @@ impl Token { In | PowerOfAssign => true, - #[cfg(not(feature = "no_index"))] - LeftBracket => true, // [-expr] - is unary - // RightBracket | [expr] - expr not unary & is closing - _ => false, } } /// Get the precedence number of the token. pub fn precedence(&self) -> u8 { + use Token::*; + match self { - Self::Equals - | Self::PlusAssign - | Self::MinusAssign - | Self::MultiplyAssign - | Self::DivideAssign - | Self::LeftShiftAssign - | Self::RightShiftAssign - | Self::AndAssign - | Self::OrAssign - | Self::XOrAssign - | Self::ModuloAssign - | Self::PowerOfAssign => 10, + Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign + | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign + | PowerOfAssign => 10, - Self::Or | Self::XOr | Self::Pipe => 50, + Or | XOr | Pipe => 40, - Self::And | Self::Ampersand => 60, + And | Ampersand => 50, - Self::LessThan - | Self::LessThanEqualsTo - | Self::GreaterThan - | Self::GreaterThanEqualsTo - | Self::EqualsTo - | Self::NotEqualsTo => 70, + LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo | EqualsTo + | NotEqualsTo => 60, - Self::Plus | Self::Minus => 80, + In => 70, - Self::Divide | Self::Multiply | Self::PowerOf => 90, + Plus | Minus => 80, - Self::LeftShift | Self::RightShift => 100, + Divide | Multiply | PowerOf => 90, - Self::Modulo => 110, + LeftShift | RightShift => 100, - Self::Period => 120, + Modulo => 110, + + Period => 120, _ => 0, } @@ -838,23 +833,16 @@ impl Token { /// Does an expression bind to the right (instead of left)? pub fn is_bind_right(&self) -> bool { + use Token::*; + match self { // Assignments bind to the right - Self::Equals - | Self::PlusAssign - | Self::MinusAssign - | Self::MultiplyAssign - | Self::DivideAssign - | Self::LeftShiftAssign - | Self::RightShiftAssign - | Self::AndAssign - | Self::OrAssign - | Self::XOrAssign - | Self::ModuloAssign - | Self::PowerOfAssign => true, + Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign + | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign + | PowerOfAssign => true, // Property access binds to the right - Self::Period => true, + Period => true, _ => false, } @@ -867,16 +855,40 @@ pub struct TokenIterator<'a> { can_be_unary: bool, /// Current position. pos: Position, - /// The input characters stream. - stream: Peekable>, + /// The input character streams. + streams: Vec>>, } impl<'a> TokenIterator<'a> { /// Consume the next character. fn eat_next(&mut self) { - self.stream.next(); + self.get_next(); self.advance(); } + /// Get the next character + fn get_next(&mut self) -> Option { + loop { + if self.streams.is_empty() { + return None; + } else if let Some(ch) = self.streams[0].next() { + return Some(ch); + } else { + let _ = self.streams.remove(0); + } + } + } + /// Peek the next character + fn peek_next(&mut self) -> Option { + loop { + if self.streams.is_empty() { + return None; + } else if let Some(ch) = self.streams[0].peek() { + return Some(*ch); + } else { + let _ = self.streams.remove(0); + } + } + } /// Move the current position one character ahead. fn advance(&mut self) { self.pos.advance(); @@ -903,7 +915,7 @@ impl<'a> TokenIterator<'a> { let mut escape = String::with_capacity(12); loop { - let next_char = self.stream.next(); + let next_char = self.get_next(); self.advance(); match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? { @@ -946,7 +958,7 @@ impl<'a> TokenIterator<'a> { }; for _ in 0..len { - let c = self.stream.next().ok_or_else(|| { + let c = self.get_next().ok_or_else(|| { (LERR::MalformedEscapeSequence(seq.to_string()), self.pos) })?; @@ -966,7 +978,10 @@ impl<'a> TokenIterator<'a> { } // \{enclosing_char} - escaped - ch if enclosing_char == ch && !escape.is_empty() => result.push(ch), + ch if enclosing_char == ch && !escape.is_empty() => { + escape.clear(); + result.push(ch) + } // Close wrapper ch if enclosing_char == ch && escape.is_empty() => break, @@ -997,12 +1012,12 @@ impl<'a> TokenIterator<'a> { fn inner_next(&mut self) -> Option<(Token, Position)> { let mut negated = false; - while let Some(c) = self.stream.next() { + while let Some(c) = self.get_next() { self.advance(); let pos = self.pos; - match (c, self.stream.peek().copied().unwrap_or('\0')) { + match (c, self.peek_next().unwrap_or('\0')) { // \n ('\n', _) => self.new_line(), @@ -1012,7 +1027,7 @@ impl<'a> TokenIterator<'a> { let mut radix_base: Option = None; result.push(c); - while let Some(&next_char) = self.stream.peek() { + while let Some(next_char) = self.peek_next() { match next_char { '0'..='9' | '_' => { result.push(next_char); @@ -1022,7 +1037,7 @@ impl<'a> TokenIterator<'a> { '.' => { result.push(next_char); self.eat_next(); - while let Some(&next_char_in_float) = self.stream.peek() { + while let Some(next_char_in_float) = self.peek_next() { match next_char_in_float { '0'..='9' | '_' => { result.push(next_char_in_float); @@ -1062,7 +1077,7 @@ impl<'a> TokenIterator<'a> { _ => panic!("unexpected character {}", ch), }); - while let Some(&next_char_in_hex) = self.stream.peek() { + while let Some(next_char_in_hex) = self.peek_next() { if !valid.contains(&next_char_in_hex) { break; } @@ -1118,7 +1133,7 @@ impl<'a> TokenIterator<'a> { let mut result = Vec::new(); result.push(c); - while let Some(&next_char) = self.stream.peek() { + while let Some(next_char) = self.peek_next() { match next_char { x if x.is_ascii_alphanumeric() || x == '_' => { result.push(x); @@ -1209,9 +1224,7 @@ impl<'a> TokenIterator<'a> { (')', _) => return Some((Token::RightParen, pos)), // Indexing - #[cfg(not(feature = "no_index"))] ('[', _) => return Some((Token::LeftBracket, pos)), - #[cfg(not(feature = "no_index"))] (']', _) => return Some((Token::RightBracket, pos)), // Map literal @@ -1248,7 +1261,7 @@ impl<'a> TokenIterator<'a> { ('/', '/') => { self.eat_next(); - while let Some(c) = self.stream.next() { + while let Some(c) = self.get_next() { if c == '\n' { self.new_line(); break; @@ -1262,18 +1275,18 @@ impl<'a> TokenIterator<'a> { self.eat_next(); - while let Some(c) = self.stream.next() { + while let Some(c) = self.get_next() { self.advance(); match c { '/' => { - if self.stream.next() == Some('*') { + if self.get_next() == Some('*') { level += 1; } self.advance(); } '*' => { - if self.stream.next() == Some('/') { + if self.get_next() == Some('/') { level -= 1; } self.advance(); @@ -1313,7 +1326,7 @@ impl<'a> TokenIterator<'a> { self.eat_next(); return Some(( - if self.stream.peek() == Some(&'=') { + if self.peek_next() == Some('=') { self.eat_next(); Token::LeftShiftAssign } else { @@ -1332,7 +1345,7 @@ impl<'a> TokenIterator<'a> { self.eat_next(); return Some(( - if self.stream.peek() == Some(&'=') { + if self.peek_next() == Some('=') { self.eat_next(); Token::RightShiftAssign } else { @@ -1409,11 +1422,11 @@ impl<'a> Iterator for TokenIterator<'a> { } /// Tokenize an input text stream. -pub fn lex(input: &str) -> TokenIterator<'_> { +pub fn lex<'a>(input: &'a [&'a str]) -> TokenIterator<'a> { TokenIterator { can_be_unary: true, pos: Position::new(1, 0), - stream: input.chars().peekable(), + streams: input.iter().map(|s| s.chars().peekable()).collect(), } } @@ -1448,42 +1461,49 @@ fn parse_paren_expr<'a>( } /// Parse a function call. -fn parse_call_expr<'a>( - id: String, +fn parse_call_expr<'a, S: Into> + Display>( + id: S, input: &mut Peekable>, begin: Position, allow_stmt_expr: bool, ) -> Result { let mut args_expr_list = Vec::new(); - // id() - if let (Token::RightParen, _) = input.peek().ok_or_else(|| { - PERR::MissingToken( - ")".into(), - format!("to close the arguments list of this function call '{}'", id), - ) - .into_err_eof() - })? { - input.next(); - return Ok(Expr::FunctionCall(id, args_expr_list, None, begin)); + match input.peek() { + //id {EOF} + None => { + return Err(PERR::MissingToken( + ")".into(), + format!("to close the arguments list of this function call '{}'", id), + ) + .into_err_eof()) + } + // id() + Some((Token::RightParen, _)) => { + input.next(); + return Ok(Expr::FunctionCall(id.into(), args_expr_list, None, begin)); + } + // id... + _ => (), } loop { args_expr_list.push(parse_expr(input, allow_stmt_expr)?); - match input.peek().ok_or_else(|| { - PERR::MissingToken( - ")".into(), - format!("to close the arguments list of this function call '{}'", id), - ) - .into_err_eof() - })? { - (Token::RightParen, _) => { - input.next(); - return Ok(Expr::FunctionCall(id, args_expr_list, None, begin)); + match input.peek() { + None => { + return Err(PERR::MissingToken( + ")".into(), + format!("to close the arguments list of this function call '{}'", id), + ) + .into_err_eof()) } - (Token::Comma, _) => (), - (_, pos) => { + Some((Token::RightParen, _)) => { + input.next(); + return Ok(Expr::FunctionCall(id.into(), args_expr_list, None, begin)); + } + Some((Token::Comma, _)) => (), + Some((_, pos)) => { return Err(PERR::MissingToken( ",".into(), format!("to separate the arguments to function call '{}'", id), @@ -1497,7 +1517,6 @@ fn parse_call_expr<'a>( } /// Parse an indexing expression. -#[cfg(not(feature = "no_index"))] fn parse_index_expr<'a>( lhs: Box, input: &mut Peekable>, @@ -1519,7 +1538,6 @@ fn parse_index_expr<'a>( Expr::IntegerConstant(_, pos) => match *lhs { Expr::Array(_, _) | Expr::StringConstant(_, _) => (), - #[cfg(not(feature = "no_object"))] Expr::Map(_, _) => { return Err(PERR::MalformedIndexExpr( "Object map access expects string index, not a number".into(), @@ -1530,28 +1548,23 @@ fn parse_index_expr<'a>( Expr::FloatConstant(_, pos) | Expr::CharConstant(_, pos) | Expr::Assignment(_, _, pos) - | Expr::Unit(pos) + | Expr::And(_, _, pos) + | Expr::Or(_, _, pos) + | Expr::In(_, _, pos) | Expr::True(pos) - | Expr::False(pos) => { + | Expr::False(pos) + | Expr::Unit(pos) => { return Err(PERR::MalformedIndexExpr( "Only arrays, object maps and strings can be indexed".into(), ) .into_err(pos)) } - Expr::And(lhs, _) | Expr::Or(lhs, _) => { - return Err(PERR::MalformedIndexExpr( - "Only arrays, object maps and strings can be indexed".into(), - ) - .into_err(lhs.position())) - } - _ => (), }, // lhs[string] Expr::StringConstant(_, pos) => match *lhs { - #[cfg(not(feature = "no_object"))] Expr::Map(_, _) => (), Expr::Array(_, _) | Expr::StringConstant(_, _) => { @@ -1563,27 +1576,22 @@ fn parse_index_expr<'a>( Expr::FloatConstant(_, pos) | Expr::CharConstant(_, pos) | Expr::Assignment(_, _, pos) - | Expr::Unit(pos) + | Expr::And(_, _, pos) + | Expr::Or(_, _, pos) + | Expr::In(_, _, pos) | Expr::True(pos) - | Expr::False(pos) => { + | Expr::False(pos) + | Expr::Unit(pos) => { return Err(PERR::MalformedIndexExpr( "Only arrays, object maps and strings can be indexed".into(), ) .into_err(pos)) } - Expr::And(lhs, _) | Expr::Or(lhs, _) => { - return Err(PERR::MalformedIndexExpr( - "Only arrays, object maps and strings can be indexed".into(), - ) - .into_err(lhs.position())) - } - _ => (), }, // lhs[float] - #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_, pos) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not a float".into(), @@ -1604,15 +1612,12 @@ fn parse_index_expr<'a>( ) .into_err(*pos)) } - // lhs[??? && ???], lhs[??? || ???] - Expr::And(lhs, _) | Expr::Or(lhs, _) => { - return Err(PERR::MalformedIndexExpr( - "Array access expects integer index, not a boolean".into(), - ) - .into_err(lhs.position())) - } - // lhs[true], lhs[false] - Expr::True(pos) | Expr::False(pos) => { + // lhs[??? && ???], lhs[??? || ???], lhs[??? in ???], lhs[true], lhs[false] + Expr::And(_, _, pos) + | Expr::Or(_, _, pos) + | Expr::In(_, _, pos) + | Expr::True(pos) + | Expr::False(pos) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not a boolean".into(), ) @@ -1643,8 +1648,8 @@ fn parse_index_expr<'a>( } /// Parse an expression that begins with an identifier. -fn parse_ident_expr<'a>( - id: String, +fn parse_ident_expr<'a, S: Into> + Display>( + id: S, input: &mut Peekable>, begin: Position, allow_stmt_expr: bool, @@ -1661,21 +1666,20 @@ fn parse_ident_expr<'a>( let pos = *pos; input.next(); parse_index_expr( - Box::new(Expr::Variable(id, begin)), + Box::new(Expr::Variable(id.into(), begin)), input, pos, allow_stmt_expr, ) } // id - variable - Some(_) => Ok(Expr::Variable(id, begin)), + Some(_) => Ok(Expr::Variable(id.into(), begin)), // EOF - None => Ok(Expr::Variable(id, begin)), + None => Ok(Expr::Variable(id.into(), begin)), } } /// Parse an array literal. -#[cfg(not(feature = "no_index"))] fn parse_array_literal<'a>( input: &mut Peekable>, begin: Position, @@ -1690,9 +1694,7 @@ fn parse_array_literal<'a>( match input.peek().ok_or_else(|| { PERR::MissingToken("]".into(), "to end this array literal".into()).into_err_eof() })? { - (Token::Comma, _) => { - input.next(); - } + (Token::Comma, _) => input.next(), (Token::RightBracket, _) => break, (_, pos) => { return Err(PERR::MissingToken( @@ -1701,7 +1703,7 @@ fn parse_array_literal<'a>( ) .into_err(*pos)) } - } + }; } } @@ -1719,7 +1721,6 @@ fn parse_array_literal<'a>( } /// Parse a map literal. -#[cfg(not(feature = "no_object"))] fn parse_map_literal<'a>( input: &mut Peekable>, begin: Position, @@ -1844,14 +1845,12 @@ fn parse_primary<'a>( let mut can_be_indexed = false; let mut root_expr = match token { - #[cfg(not(feature = "no_float"))] - (Token::FloatConstant(x), pos) => Ok(Expr::FloatConstant(x, pos)), - (Token::IntegerConstant(x), pos) => Ok(Expr::IntegerConstant(x, pos)), + (Token::FloatConstant(x), pos) => Ok(Expr::FloatConstant(x, pos)), (Token::CharConstant(c), pos) => Ok(Expr::CharConstant(c, pos)), (Token::StringConst(s), pos) => { can_be_indexed = true; - Ok(Expr::StringConstant(s, pos)) + Ok(Expr::StringConstant(s.into(), pos)) } (Token::Identifier(s), pos) => { can_be_indexed = true; @@ -1879,9 +1878,11 @@ fn parse_primary<'a>( } }?; + #[cfg(feature = "no_index")] + let can_be_indexed = false; + if can_be_indexed { // Tail processing all possible indexing - #[cfg(not(feature = "no_index"))] while let Some((Token::LeftBracket, pos)) = input.peek() { let pos = *pos; input.next(); @@ -1922,10 +1923,13 @@ fn parse_unary<'a>( .map(|x| Expr::IntegerConstant(x, pos)) .or_else(|| { #[cfg(not(feature = "no_float"))] - return Some(Expr::FloatConstant(-(i as FLOAT), pos)); - + { + Some(Expr::FloatConstant(-(i as FLOAT), pos)) + } #[cfg(feature = "no_float")] - return None; + { + None + } }) .ok_or_else(|| { PERR::BadInput(LERR::MalformedNumber(format!("-{}", i)).to_string()) @@ -1980,48 +1984,41 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result { assert!(is_top, "property expected but gets variable"); None } // property[...] - #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) => { assert!(!is_top, "variable expected but gets property"); None } // idx_lhs[...] - #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, _, pos) => match idx_lhs.as_ref() { Expr::Index(_, _, _) => Some(ParseErrorType::AssignmentToCopy.into_err(*pos)), _ => Some(ParseErrorType::AssignmentToInvalidLHS.into_err(*pos)), }, // dot_lhs.dot_rhs - #[cfg(not(feature = "no_object"))] Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() { // var.dot_rhs Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false), // property.dot_rhs Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false), // var[...] - #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) && is_top => { valid_assignment_chain(dot_rhs, false) } // property[...] - #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) && !is_top => { valid_assignment_chain(dot_rhs, false) } // idx_lhs[...] - #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, _, _) => { Some(ParseErrorType::AssignmentToCopy.into_err(idx_lhs.position())) } @@ -2040,7 +2037,12 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result Result { +fn parse_op_assignment>>( + op: S, + lhs: Expr, + rhs: Expr, + pos: Position, +) -> Result { let lhs_copy = lhs.clone(); // lhs op= rhs -> lhs = op(lhs, rhs) @@ -2051,6 +2053,135 @@ fn parse_op_assignment(op: &str, lhs: Expr, rhs: Expr, pos: Position) -> Result< ) } +/// Parse an 'in' expression. +fn parse_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { + match (&lhs, &rhs) { + (_, Expr::IntegerConstant(_, pos)) + | (_, Expr::FloatConstant(_, pos)) + | (_, Expr::And(_, _, pos)) + | (_, Expr::Or(_, _, pos)) + | (_, Expr::In(_, _, pos)) + | (_, Expr::True(pos)) + | (_, Expr::False(pos)) + | (_, Expr::Assignment(_, _, pos)) + | (_, Expr::Unit(pos)) => { + return Err(PERR::MalformedInExpr( + "'in' expression expects a string, array or object map".into(), + ) + .into_err(*pos)) + } + + // "xxx" in "xxxx", 'x' in "xxxx" - OK! + (Expr::StringConstant(_, _), Expr::StringConstant(_, _)) + | (Expr::CharConstant(_, _), Expr::StringConstant(_, _)) => (), + + // 123.456 in "xxxx" + (Expr::FloatConstant(_, pos), Expr::StringConstant(_, _)) => { + return Err(PERR::MalformedInExpr( + "'in' expression for a string expects a string, not a float".into(), + ) + .into_err(*pos)) + } + // 123 in "xxxx" + (Expr::IntegerConstant(_, pos), Expr::StringConstant(_, _)) => { + return Err(PERR::MalformedInExpr( + "'in' expression for a string expects a string, not a number".into(), + ) + .into_err(*pos)) + } + // (??? && ???) in "xxxx", (??? || ???) in "xxxx", (??? in ???) in "xxxx", + // true in "xxxx", false in "xxxx" + (Expr::And(_, _, pos), Expr::StringConstant(_, _)) + | (Expr::Or(_, _, pos), Expr::StringConstant(_, _)) + | (Expr::In(_, _, pos), Expr::StringConstant(_, _)) + | (Expr::True(pos), Expr::StringConstant(_, _)) + | (Expr::False(pos), Expr::StringConstant(_, _)) => { + return Err(PERR::MalformedInExpr( + "'in' expression for a string expects a string, not a boolean".into(), + ) + .into_err(*pos)) + } + // [???, ???, ???] in "xxxx" + (Expr::Array(_, pos), Expr::StringConstant(_, _)) => { + return Err(PERR::MalformedInExpr( + "'in' expression for a string expects a string, not an array".into(), + ) + .into_err(*pos)) + } + // #{...} in "xxxx" + (Expr::Map(_, pos), Expr::StringConstant(_, _)) => { + return Err(PERR::MalformedInExpr( + "'in' expression for a string expects a string, not an object map".into(), + ) + .into_err(*pos)) + } + // (??? = ???) in "xxxx", () in "xxxx" + (Expr::Assignment(_, _, pos), Expr::StringConstant(_, _)) + | (Expr::Unit(pos), Expr::StringConstant(_, _)) => { + return Err(PERR::MalformedInExpr( + "'in' expression for a string expects a string, not ()".into(), + ) + .into_err(*pos)) + } + + // "xxx" in #{...}, 'x' in #{...} - OK! + (Expr::StringConstant(_, _), Expr::Map(_, _)) + | (Expr::CharConstant(_, _), Expr::Map(_, _)) => (), + + // 123.456 in #{...} + (Expr::FloatConstant(_, pos), Expr::Map(_, _)) => { + return Err(PERR::MalformedInExpr( + "'in' expression for an object map expects a string, not a float".into(), + ) + .into_err(*pos)) + } + // 123 in #{...} + (Expr::IntegerConstant(_, pos), Expr::Map(_, _)) => { + return Err(PERR::MalformedInExpr( + "'in' expression for an object map expects a string, not a number".into(), + ) + .into_err(*pos)) + } + // (??? && ???) in #{...}, (??? || ???) in #{...}, (??? in ???) in #{...}, + // true in #{...}, false in #{...} + (Expr::And(_, _, pos), Expr::Map(_, _)) + | (Expr::Or(_, _, pos), Expr::Map(_, _)) + | (Expr::In(_, _, pos), Expr::Map(_, _)) + | (Expr::True(pos), Expr::Map(_, _)) + | (Expr::False(pos), Expr::Map(_, _)) => { + return Err(PERR::MalformedInExpr( + "'in' expression for an object map expects a string, not a boolean".into(), + ) + .into_err(*pos)) + } + // [???, ???, ???] in #{..} + (Expr::Array(_, pos), Expr::Map(_, _)) => { + return Err(PERR::MalformedInExpr( + "'in' expression for an object map expects a string, not an array".into(), + ) + .into_err(*pos)) + } + // #{...} in #{..} + (Expr::Map(_, pos), Expr::Map(_, _)) => { + return Err(PERR::MalformedInExpr( + "'in' expression for an object map expects a string, not an object map".into(), + ) + .into_err(*pos)) + } + // (??? = ???) in #{...}, () in #{...} + (Expr::Assignment(_, _, pos), Expr::Map(_, _)) | (Expr::Unit(pos), Expr::Map(_, _)) => { + return Err(PERR::MalformedInExpr( + "'in' expression for an object map expects a string, not ()".into(), + ) + .into_err(*pos)) + } + + _ => (), + } + + Ok(Expr::In(Box::new(lhs), Box::new(rhs), op_pos)) +} + /// Parse a binary expression. fn parse_binary_op<'a>( input: &mut Peekable>, @@ -2061,11 +2192,10 @@ fn parse_binary_op<'a>( let mut current_lhs = lhs; loop { - let (current_precedence, bind_right) = if let Some((current_op, _)) = input.peek() { - (current_op.precedence(), current_op.is_bind_right()) - } else { - (0, false) - }; + let (current_precedence, bind_right) = input.peek().map_or_else( + || (0, false), + |(current_op, _)| (current_op.precedence(), current_op.is_bind_right()), + ); // Bind left to the parent lhs expression if precedence is higher // If same precedence, then check if the operator binds right @@ -2120,7 +2250,6 @@ fn parse_binary_op<'a>( pos, )), // xxx.lhs[idx] - #[cfg(not(feature = "no_index"))] Expr::Index(lhs, idx, pos) => { Ok(Expr::Index(Box::new(check_property(*lhs)?), idx, pos)) } @@ -2141,42 +2270,45 @@ fn parse_binary_op<'a>( Token::EqualsTo => Expr::FunctionCall( "==".into(), vec![current_lhs, rhs], - Some(Box::new(false)), + Some((false).into_dynamic()), pos, ), Token::NotEqualsTo => Expr::FunctionCall( "!=".into(), vec![current_lhs, rhs], - Some(Box::new(false)), + Some((false).into_dynamic()), pos, ), Token::LessThan => Expr::FunctionCall( "<".into(), vec![current_lhs, rhs], - Some(Box::new(false)), + Some((false).into_dynamic()), pos, ), Token::LessThanEqualsTo => Expr::FunctionCall( "<=".into(), vec![current_lhs, rhs], - Some(Box::new(false)), + Some((false).into_dynamic()), pos, ), Token::GreaterThan => Expr::FunctionCall( ">".into(), vec![current_lhs, rhs], - Some(Box::new(false)), + Some((false).into_dynamic()), pos, ), Token::GreaterThanEqualsTo => Expr::FunctionCall( ">=".into(), vec![current_lhs, rhs], - Some(Box::new(false)), + Some((false).into_dynamic()), pos, ), - Token::Or => Expr::Or(Box::new(current_lhs), Box::new(rhs)), - Token::And => Expr::And(Box::new(current_lhs), Box::new(rhs)), + Token::Or => Expr::Or(Box::new(current_lhs), Box::new(rhs), pos), + Token::And => Expr::And(Box::new(current_lhs), Box::new(rhs), pos), + + Token::In => parse_in_expr(current_lhs, rhs, pos)?, + Token::XOr => Expr::FunctionCall("^".into(), vec![current_lhs, rhs], None, pos), Token::OrAssign => parse_op_assignment("|", current_lhs, rhs, pos)?, Token::AndAssign => parse_op_assignment("&", current_lhs, rhs, pos)?, @@ -2336,7 +2468,7 @@ fn parse_for<'a>( let expr = parse_expr(input, allow_stmt_expr)?; let body = parse_block(input, true, allow_stmt_expr)?; - Ok(Stmt::For(name, Box::new(expr), Box::new(body))) + Ok(Stmt::For(name.into(), Box::new(expr), Box::new(body))) } /// Parse a variable definition statement. @@ -2367,10 +2499,10 @@ fn parse_let<'a>( match var_type { // let name = expr - ScopeEntryType::Normal => Ok(Stmt::Let(name, Some(Box::new(init_value)), pos)), + ScopeEntryType::Normal => Ok(Stmt::Let(name.into(), Some(Box::new(init_value)), pos)), // const name = { expr:constant } ScopeEntryType::Constant if init_value.is_constant() => { - Ok(Stmt::Const(name, Box::new(init_value), pos)) + Ok(Stmt::Const(name.into(), Box::new(init_value), pos)) } // const name = expr - error ScopeEntryType::Constant => { @@ -2379,7 +2511,7 @@ fn parse_let<'a>( } } else { // let name - Ok(Stmt::Let(name, None, pos)) + Ok(Stmt::Let(name.into(), None, pos)) } } @@ -2528,7 +2660,6 @@ fn parse_stmt<'a>( } /// Parse a function definition. -#[cfg(not(feature = "no_function"))] fn parse_fn<'a>( input: &mut Peekable>, allow_stmt_expr: bool, @@ -2547,11 +2678,9 @@ fn parse_fn<'a>( .peek() .ok_or_else(|| PERR::FnMissingParams(name.clone()).into_err_eof())? { - (Token::LeftParen, _) => { - input.next(); - } + (Token::LeftParen, _) => input.next(), (_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)), - } + }; let mut params = Vec::new(); @@ -2566,9 +2695,7 @@ fn parse_fn<'a>( .next() .ok_or_else(|| PERR::MissingToken(")".into(), end_err.to_string()).into_err_eof())? { - (Token::Identifier(s), pos) => { - params.push((s, pos)); - } + (Token::Identifier(s), pos) => params.push((s, pos)), (_, pos) => return Err(PERR::MissingToken(")".into(), end_err).into_err(pos)), } @@ -2608,9 +2735,11 @@ fn parse_fn<'a>( None => return Err(PERR::FnMissingBody(name).into_err_eof()), }; + let params = params.into_iter().map(|(p, _)| p).collect(); + Ok(FnDef { name, - params: params.into_iter().map(|(p, _)| p).collect(), + params, body, pos, }) @@ -2620,6 +2749,7 @@ pub fn parse_global_expr<'a, 'e>( input: &mut Peekable>, engine: &Engine<'e>, scope: &Scope, + optimization_level: OptimizationLevel, ) -> Result { let expr = parse_expr(input, false)?; @@ -2630,21 +2760,12 @@ pub fn parse_global_expr<'a, 'e>( Ok( // Optimize AST - #[cfg(not(feature = "no_optimize"))] - optimize_into_ast(engine, scope, vec![Stmt::Expr(Box::new(expr))], vec![]), - // - // Do not optimize AST if `no_optimize` - #[cfg(feature = "no_optimize")] - AST( + optimize_into_ast( + engine, + scope, vec![Stmt::Expr(Box::new(expr))], - #[cfg(feature = "sync")] - { - Arc::new(FunctionsLib::new()) - }, - #[cfg(not(feature = "sync"))] - { - Rc::new(FunctionsLib::new()) - }, + vec![], + optimization_level, ), ) } @@ -2657,9 +2778,9 @@ fn parse_global_level<'a>( let mut functions = Vec::::new(); while input.peek().is_some() { + // Collect all the function definitions #[cfg(not(feature = "no_function"))] { - // Collect all the function definitions if matches!(input.peek().expect("should not be None"), (Token::Fn, _)) { let f = parse_fn(input, true)?; @@ -2710,17 +2831,13 @@ pub fn parse<'a, 'e>( input: &mut Peekable>, engine: &Engine<'e>, scope: &Scope, + optimization_level: OptimizationLevel, ) -> Result { let (statements, functions) = parse_global_level(input)?; Ok( // Optimize AST - #[cfg(not(feature = "no_optimize"))] - optimize_into_ast(engine, scope, statements, functions), - // - // Do not optimize AST if `no_optimize` - #[cfg(feature = "no_optimize")] - AST(statements, Arc::new(FunctionsLib::from_vec(functions))), + optimize_into_ast(engine, scope, statements, functions, optimization_level), ) } @@ -2733,7 +2850,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { } else if value.is::() { Some(Expr::CharConstant(value.cast(), pos)) } else if value.is::() { - Some(Expr::StringConstant(value.cast(), pos)) + Some(Expr::StringConstant(value.cast::().into(), pos)) } else if value.is::() { Some(if value.cast::() { Expr::True(pos) @@ -2741,6 +2858,47 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { Expr::False(pos) }) } else { + #[cfg(not(feature = "no_index"))] + { + if value.is::() { + let array = value.cast::(); + let items: Vec<_> = array + .into_iter() + .map(|x| map_dynamic_to_expr(x, pos)) + .collect(); + if items.iter().all(Option::is_some) { + return Some(Expr::Array( + items.into_iter().map(Option::unwrap).collect(), + pos, + )); + } else { + return None; + } + } + } + + #[cfg(not(feature = "no_object"))] + { + if value.is::() { + let map = value.cast::(); + let items: Vec<_> = map + .into_iter() + .map(|(k, v)| (k, map_dynamic_to_expr(v, pos), pos)) + .collect(); + if items.iter().all(|(_, expr, _)| expr.is_some()) { + return Some(Expr::Map( + items + .into_iter() + .map(|(k, expr, pos)| (k, expr.unwrap(), pos)) + .collect(), + pos, + )); + } else { + return None; + } + } + } + #[cfg(not(feature = "no_float"))] { if value.is::() { diff --git a/src/result.rs b/src/result.rs index 57594ccc..ddb56f0b 100644 --- a/src/result.rs +++ b/src/result.rs @@ -16,6 +16,8 @@ use crate::stdlib::path::PathBuf; /// Evaluation result. /// /// All wrapped `Position` values represent the location in the script where the error occurs. +/// +/// Currently, `EvalAltResult` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. #[derive(Debug)] pub enum EvalAltResult { /// Syntax error. @@ -23,7 +25,7 @@ pub enum EvalAltResult { /// Error reading from a script file. Wrapped value is the path of the script file. /// - /// Not available under the `no_std` feature. + /// Never appears under the `no_std` feature. #[cfg(not(feature = "no_std"))] ErrorReadingScriptFile(PathBuf, std::io::Error), @@ -49,6 +51,8 @@ pub enum EvalAltResult { ErrorNumericIndexExpr(Position), /// Trying to index into a map with an index that is not `String`. ErrorStringIndexExpr(Position), + /// Invalid arguments for `in` operator. + ErrorInExpr(Position), /// The guard expression in an `if` or `while` statement does not return a boolean value. ErrorLogicGuard(Position), /// The `for` statement encounters a type that is not an iterator. @@ -103,21 +107,22 @@ impl EvalAltResult { Self::ErrorArrayBounds(_, index, _) if *index < 0 => { "Array access expects non-negative index" } - Self::ErrorArrayBounds(0, _, _) => "Access of empty array", + Self::ErrorArrayBounds(0, _, _) => "Empty array has nothing to access", Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds", Self::ErrorStringBounds(_, index, _) if *index < 0 => { "Indexing a string expects a non-negative index" } - Self::ErrorStringBounds(0, _, _) => "Indexing of empty string", + Self::ErrorStringBounds(0, _, _) => "Empty string has nothing to index", Self::ErrorStringBounds(_, _, _) => "String index out of bounds", - Self::ErrorLogicGuard(_) => "Boolean expression expected", - Self::ErrorFor(_) => "For loop expects array or range", + Self::ErrorLogicGuard(_) => "Boolean value expected", + Self::ErrorFor(_) => "For loop expects an array, object map, or range", Self::ErrorVariableNotFound(_, _) => "Variable not found", Self::ErrorAssignmentToUnknownLHS(_) => { "Assignment to an unsupported left-hand side expression" } Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable", Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect", + Self::ErrorInExpr(_) => "Malformed 'in' expression", Self::ErrorDotExpr(_, _) => "Malformed dot expression", Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorStackOverflow(_) => "Stack overflow", @@ -154,6 +159,7 @@ impl fmt::Display for EvalAltResult { | Self::ErrorLogicGuard(pos) | Self::ErrorFor(pos) | Self::ErrorAssignmentToUnknownLHS(pos) + | Self::ErrorInExpr(pos) | Self::ErrorDotExpr(_, pos) | Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos), @@ -256,6 +262,7 @@ impl EvalAltResult { | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, pos) + | Self::ErrorInExpr(pos) | Self::ErrorDotExpr(_, pos) | Self::ErrorArithmetic(_, pos) | Self::ErrorStackOverflow(pos) @@ -288,6 +295,7 @@ impl EvalAltResult { | Self::ErrorAssignmentToUnknownLHS(pos) | Self::ErrorAssignmentToConstant(_, pos) | Self::ErrorMismatchOutputType(_, pos) + | Self::ErrorInExpr(pos) | Self::ErrorDotExpr(_, pos) | Self::ErrorArithmetic(_, pos) | Self::ErrorStackOverflow(pos) diff --git a/src/scope.rs b/src/scope.rs index 15eda16c..b5e22170 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -49,7 +49,7 @@ pub(crate) struct EntryRef<'a> { /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::{Engine, Scope}; /// -/// let mut engine = Engine::new(); +/// let engine = Engine::new(); /// let mut my_scope = Scope::new(); /// /// my_scope.push("z", 40_i64); diff --git a/tests/arrays.rs b/tests/arrays.rs index 1a7517ae..a41f0a63 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -3,7 +3,7 @@ use rhai::{Array, Engine, EvalAltResult, RegisterFn, INT}; #[test] fn test_arrays() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let 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); @@ -11,43 +11,47 @@ fn test_arrays() -> Result<(), EvalAltResult> { engine.eval::(r#"let y = [1, [ 42, 88, "93" ], 3]; y[1][2][1]"#)?, '3' ); + assert!(engine.eval::("let y = [1, 2, 3]; 2 in y")?); - #[cfg(not(feature = "no_stdlib"))] - { - assert_eq!( - engine.eval::( + assert_eq!( + engine.eval::( + r" + let x = [2, 9]; + x.insert(-1, 1); + x.insert(999, 3); + + let r = x.remove(2); + + let y = [4, 5]; + x.append(y); + + x.len() + r + " + )?, + 14 + ); + assert_eq!( + engine.eval::( + r" + let x = [1, 2, 3]; + x += [4, 5]; + x.len() + " + )?, + 5 + ); + assert_eq!( + engine + .eval::( r" - let x = [1, 2, 3]; - let y = [4, 5]; - x.append(y); - x.len() + let x = [1, 2, 3]; + let y = [4, 5]; + x + y " - )?, - 5 - ); - assert_eq!( - engine.eval::( - r" - let x = [1, 2, 3]; - x += [4, 5]; - x.len() - " - )?, - 5 - ); - assert_eq!( - engine - .eval::( - r" - let x = [1, 2, 3]; - let y = [4, 5]; - x + y - " - )? - .len(), - 5 - ); - } + )? + .len(), + 5 + ); Ok(()) } diff --git a/tests/binary_ops.rs b/tests/binary_ops.rs index 3e59e8fd..922c6a43 100644 --- a/tests/binary_ops.rs +++ b/tests/binary_ops.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_binary_ops() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("10 % 4")?, 2); assert_eq!(engine.eval::("10 << 4")?, 160); diff --git a/tests/bit_shift.rs b/tests/bit_shift.rs index 7dd3aa76..e8e360f3 100644 --- a/tests/bit_shift.rs +++ b/tests/bit_shift.rs @@ -2,14 +2,14 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_left_shift() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("4 << 2")?, 16); Ok(()) } #[test] fn test_right_shift() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("9 >> 1")?, 4); Ok(()) } diff --git a/tests/bool_op.rs b/tests/bool_op.rs index a39001a2..3bd5859a 100644 --- a/tests/bool_op.rs +++ b/tests/bool_op.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult}; #[test] fn test_bool_op1() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("true && (false || true)")?, true); assert_eq!(engine.eval::("true & (false | true)")?, true); @@ -12,7 +12,7 @@ fn test_bool_op1() -> Result<(), EvalAltResult> { #[test] fn test_bool_op2() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("false && (false || true)")?, false); assert_eq!(engine.eval::("false & (false | true)")?, false); @@ -22,7 +22,7 @@ fn test_bool_op2() -> Result<(), EvalAltResult> { #[test] fn test_bool_op3() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert!(engine.eval::("true && (false || 123)").is_err()); assert_eq!(engine.eval::("true && (true || 123)")?, true); @@ -34,7 +34,7 @@ fn test_bool_op3() -> Result<(), EvalAltResult> { #[test] fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!( engine.eval::( @@ -63,7 +63,7 @@ fn test_bool_op_short_circuit() -> Result<(), EvalAltResult> { #[test] fn test_bool_op_no_short_circuit1() { - let mut engine = Engine::new(); + let engine = Engine::new(); assert!(engine .eval::( @@ -78,7 +78,7 @@ fn test_bool_op_no_short_circuit1() { #[test] fn test_bool_op_no_short_circuit2() { - let mut engine = Engine::new(); + let engine = Engine::new(); assert!(engine .eval::( diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 909e8a90..fc658a15 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, ParseErrorType, Scope, INT}; +use rhai::{Engine, EvalAltResult, Func, ParseErrorType, Scope, INT}; #[test] fn test_fn() -> Result<(), EvalAltResult> { @@ -20,7 +20,7 @@ fn test_fn() -> Result<(), EvalAltResult> { #[test] fn test_call_fn() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); let mut scope = Scope::new(); scope.push("foo", 42 as INT); @@ -44,10 +44,10 @@ fn test_call_fn() -> Result<(), EvalAltResult> { let r: i64 = engine.call_fn(&mut scope, &ast, "hello", (42 as INT, 123 as INT))?; assert_eq!(r, 165); - let r: i64 = engine.call_fn1(&mut scope, &ast, "hello", 123 as INT)?; + let r: i64 = engine.call_fn(&mut scope, &ast, "hello", (123 as INT,))?; assert_eq!(r, 5166); - let r: i64 = engine.call_fn0(&mut scope, &ast, "hello")?; + let r: i64 = engine.call_fn(&mut scope, &ast, "hello", ())?; assert_eq!(r, 42); assert_eq!( @@ -59,3 +59,16 @@ fn test_call_fn() -> Result<(), EvalAltResult> { Ok(()) } + +#[test] +fn test_anonymous_fn() -> Result<(), EvalAltResult> { + let calc_func = Func::<(INT, INT, INT), INT>::create_from_script( + Engine::new(), + "fn calc(x, y, z) { (x + y) * z }", + "calc", + )?; + + assert_eq!(calc_func(42, 123, 9)?, 1485); + + Ok(()) +} diff --git a/tests/chars.rs b/tests/chars.rs index 2739401b..db13f840 100644 --- a/tests/chars.rs +++ b/tests/chars.rs @@ -2,9 +2,11 @@ use rhai::{Engine, EvalAltResult}; #[test] fn test_chars() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("'y'")?, 'y'); + assert_eq!(engine.eval::(r"'\''")?, '\''); + assert_eq!(engine.eval::(r#"'"'"#)?, '"'); assert_eq!(engine.eval::("'\\u2764'")?, '❤'); #[cfg(not(feature = "no_index"))] diff --git a/tests/comments.rs b/tests/comments.rs index 8c071f07..d49b7586 100644 --- a/tests/comments.rs +++ b/tests/comments.rs @@ -2,13 +2,13 @@ use rhai::{Engine, INT}; #[test] fn test_comments() { - let mut engine = Engine::new(); + let engine = Engine::new(); assert!(engine .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 multi-line comment, yay! */ x = 5; x") .is_ok()); } diff --git a/tests/compound_equality.rs b/tests/compound_equality.rs index 569cfd94..b516bb97 100644 --- a/tests/compound_equality.rs +++ b/tests/compound_equality.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_or_equals() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("let x = 16; x |= 74; x")?, 90); assert_eq!(engine.eval::("let x = true; x |= false; x")?, true); @@ -13,7 +13,7 @@ fn test_or_equals() -> Result<(), EvalAltResult> { #[test] fn test_and_equals() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("let x = 16; x &= 31; x")?, 16); assert_eq!(engine.eval::("let x = true; x &= false; x")?, false); @@ -25,42 +25,42 @@ fn test_and_equals() -> Result<(), EvalAltResult> { #[test] fn test_xor_equals() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("let x = 90; x ^= 12; x")?, 86); Ok(()) } #[test] fn test_multiply_equals() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("let x = 2; x *= 3; x")?, 6); Ok(()) } #[test] fn test_divide_equals() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); 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(); + let engine = Engine::new(); 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(); + let engine = Engine::new(); assert_eq!(engine.eval::("let x = 4; x<<= 2; x")?, 16); Ok(()) } #[test] fn test_modulo_equals() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("let x = 10; x %= 4; x")?, 2); Ok(()) } diff --git a/tests/constants.rs b/tests/constants.rs index 16fbf7bd..2dd62000 100644 --- a/tests/constants.rs +++ b/tests/constants.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_constant() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("const x = 123; x")?, 123); diff --git a/tests/decrement.rs b/tests/decrement.rs index d653a7aa..a8cb2634 100644 --- a/tests/decrement.rs +++ b/tests/decrement.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_decrement() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("let x = 10; x -= 7; x")?, 3); diff --git a/tests/eval.rs b/tests/eval.rs index fc4424a4..bf6f750f 100644 --- a/tests/eval.rs +++ b/tests/eval.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, Scope, INT}; #[test] fn test_eval() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!( engine.eval::( @@ -19,7 +19,7 @@ fn test_eval() -> Result<(), EvalAltResult> { #[test] #[cfg(not(feature = "no_function"))] fn test_eval_function() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); let mut scope = Scope::new(); assert_eq!( @@ -62,7 +62,7 @@ fn test_eval_function() -> Result<(), EvalAltResult> { #[test] #[cfg(not(feature = "no_function"))] fn test_eval_override() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!( engine.eval::( diff --git a/tests/expressions.rs b/tests/expressions.rs index 9023f97c..2091fe4e 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, Scope, INT}; #[test] fn test_expressions() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); let mut scope = Scope::new(); scope.push("x", 10 as INT); diff --git a/tests/float.rs b/tests/float.rs index 20349098..94ec7a03 100644 --- a/tests/float.rs +++ b/tests/float.rs @@ -5,7 +5,7 @@ const EPSILON: FLOAT = 0.000_000_000_1; #[test] fn test_float() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!( engine.eval::("let x = 0.0; let y = 1.0; x < y")?, diff --git a/tests/for.rs b/tests/for.rs index 448c7fcb..f36f3ae3 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -3,7 +3,7 @@ use rhai::{Engine, EvalAltResult, INT}; #[cfg(not(feature = "no_index"))] #[test] fn test_for_array() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); let script = r" let sum1 = 0; @@ -33,7 +33,7 @@ fn test_for_array() -> Result<(), EvalAltResult> { #[cfg(not(feature = "no_object"))] #[test] fn test_for_object() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); let script = r#" let sum = 0; diff --git a/tests/if_block.rs b/tests/if_block.rs index 4699ee57..dcde8143 100644 --- a/tests/if_block.rs +++ b/tests/if_block.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_if() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("if true { 55 }")?, 55); assert_eq!(engine.eval::("if false { 55 } else { 44 }")?, 44); @@ -30,7 +30,7 @@ fn test_if() -> Result<(), EvalAltResult> { #[test] fn test_if_expr() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!( engine.eval::( diff --git a/tests/increment.rs b/tests/increment.rs index 72c6eb35..4be9397a 100644 --- a/tests/increment.rs +++ b/tests/increment.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_increment() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("let x = 1; x += 2; x")?, 3); assert_eq!( diff --git a/tests/internal_fn.rs b/tests/internal_fn.rs index 4a6720d5..cc99187a 100644 --- a/tests/internal_fn.rs +++ b/tests/internal_fn.rs @@ -4,9 +4,12 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_internal_fn() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); - assert_eq!(engine.eval::("fn addme(a, b) { a+b } addme(3, 4)")?, 7); + assert_eq!( + engine.eval::("fn add_me(a, b) { a+b } add_me(3, 4)")?, + 7 + ); assert_eq!(engine.eval::("fn bob() { return 4; 5 } bob()")?, 4); Ok(()) @@ -14,15 +17,15 @@ fn test_internal_fn() -> Result<(), EvalAltResult> { #[test] fn test_big_internal_fn() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!( engine.eval::( r" - fn mathme(a, b, c, d, e, f) { + fn math_me(a, b, c, d, e, f) { a - b * c + d * e - f } - mathme(100, 5, 2, 9, 6, 32) + math_me(100, 5, 2, 9, 6, 32) ", )?, 112 @@ -33,7 +36,7 @@ fn test_big_internal_fn() -> Result<(), EvalAltResult> { #[test] fn test_internal_fn_overloading() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!( engine.eval::( diff --git a/tests/looping.rs b/tests/looping.rs index bf2c1a72..c7aace27 100644 --- a/tests/looping.rs +++ b/tests/looping.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_loop() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!( engine.eval::( diff --git a/tests/maps.rs b/tests/maps.rs index 5ed42519..ac591966 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -1,10 +1,10 @@ #![cfg(not(feature = "no_object"))] -use rhai::{AnyExt, Engine, EvalAltResult, Map, INT}; +use rhai::{AnyExt, Engine, EvalAltResult, Map, Scope, INT}; #[test] fn test_map_indexing() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); #[cfg(not(feature = "no_index"))] { @@ -29,97 +29,215 @@ fn test_map_indexing() -> Result<(), EvalAltResult> { ); engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?; - #[cfg(not(feature = "no_stdlib"))] - { - assert_eq!( - engine.eval::( + assert!(engine.eval::(r#"let y = #{a: 1, b: 2, c: 3}; "c" in y"#)?); + assert!(engine.eval::("let y = #{a: 1, b: 2, c: 3}; 'b' in y")?); + assert!(!engine.eval::(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?); + + assert_eq!( + engine.eval::( + r#" + let x = #{a: 1, b: 2, c: 3}; + let c = x.remove("c"); + x.len() + c + "# + )?, + 5 + ); + assert_eq!( + engine.eval::( + r" + let x = #{a: 1, b: 2, c: 3}; + let y = #{b: 42, d: 9}; + x.mixin(y); + x.len() + x.b + " + )?, + 46 + ); + assert_eq!( + engine.eval::( + r" + let x = #{a: 1, b: 2, c: 3}; + x += #{b: 42, d: 9}; + x.len() + x.b + " + )?, + 46 + ); + assert_eq!( + engine + .eval::( r" let x = #{a: 1, b: 2, c: 3}; let y = #{b: 42, d: 9}; - x.mixin(y); - x.len() + x.b + x + y " - )?, - 46 - ); - assert_eq!( - engine.eval::( - r" - let x = #{a: 1, b: 2, c: 3}; - x += #{b: 42, d: 9}; - x.len() + x.b - " - )?, - 46 - ); - assert_eq!( - engine - .eval::( - r" - let x = #{a: 1, b: 2, c: 3}; - let y = #{b: 42, d: 9}; - x + y - " - )? - .len(), - 4 - ); - } + )? + .len(), + 4 + ); Ok(()) } #[test] fn test_map_assign() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); let x = engine.eval::(r#"let x = #{a: 1, b: true, "c$": "hello"}; x"#)?; - let a = x.get("a").cloned().expect("should have property a"); - let b = x.get("b").cloned().expect("should have property b"); - let c = x.get("c$").cloned().expect("should have property c$"); - assert_eq!(a.cast::(), 1); - assert_eq!(b.cast::(), true); - assert_eq!(c.cast::(), "hello"); + assert_eq!( + x.get("a") + .cloned() + .expect("should have property a") + .cast::(), + 1 + ); + assert_eq!( + x.get("b") + .cloned() + .expect("should have property b") + .cast::(), + true + ); + assert_eq!( + x.get("c$") + .cloned() + .expect("should have property c$") + .cast::(), + "hello" + ); Ok(()) } #[test] fn test_map_return() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); let x = engine.eval::(r#"#{a: 1, b: true, "c$": "hello"}"#)?; - let a = x.get("a").cloned().expect("should have property a"); - let b = x.get("b").cloned().expect("should have property b"); - let c = x.get("c$").cloned().expect("should have property c$"); - assert_eq!(a.cast::(), 1); - assert_eq!(b.cast::(), true); - assert_eq!(c.cast::(), "hello"); + assert_eq!( + x.get("a") + .cloned() + .expect("should have property a") + .cast::(), + 1 + ); + assert_eq!( + x.get("b") + .cloned() + .expect("should have property b") + .cast::(), + true + ); + assert_eq!( + x.get("c$") + .cloned() + .expect("should have property c$") + .cast::(), + "hello" + ); Ok(()) } #[test] fn test_map_for() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!( - engine.eval::( - r#" - let map = #{a: 1, b: true, c: 123.456}; - let s = ""; + engine + .eval::( + r#" + let map = #{a: 1, b_x: true, "$c d e!": "hello"}; + let s = ""; - for key in keys(map) { - s += key; - } + for key in keys(map) { + s += key; + } - s.len() + s "# - )?, - 3 + )? + .len(), + 11 ); Ok(()) } + +#[test] +/// Because a Rhai object map literal is almost the same as JSON, +/// it is possible to convert from JSON into a Rhai object map. +fn test_map_json() -> Result<(), EvalAltResult> { + let engine = Engine::new(); + + let json = r#"{"a":1, "b":true, "c":42, "$d e f!":"hello", "z":null}"#; + + let map = engine.parse_json(json, true)?; + + assert!(!map.contains_key("x")); + + assert_eq!( + map.get("a") + .cloned() + .expect("should have property a") + .cast::(), + 1 + ); + assert_eq!( + map.get("b") + .cloned() + .expect("should have property b") + .cast::(), + true + ); + assert_eq!( + map.get("c") + .cloned() + .expect("should have property a") + .cast::(), + 42 + ); + assert_eq!( + map.get("$d e f!") + .cloned() + .expect("should have property $d e f!") + .cast::(), + "hello" + ); + assert_eq!( + map.get("z") + .cloned() + .expect("should have property z") + .cast::<()>(), + () + ); + + #[cfg(not(feature = "no_index"))] + { + let mut scope = Scope::new(); + scope.push_constant("map", map); + + assert_eq!( + engine + .eval_with_scope::( + &mut scope, + r#" + let s = ""; + + for key in keys(map) { + s += key; + } + + s + "# + )? + .len(), + 11 + ); + } + + Ok(()) +} diff --git a/tests/math.rs b/tests/math.rs index 24181c5f..500b35b8 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_math() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("1 + 2")?, 3); assert_eq!(engine.eval::("1 - 2")?, -1); diff --git a/tests/mismatched_op.rs b/tests/mismatched_op.rs index 5629f6cd..e6e6e15d 100644 --- a/tests/mismatched_op.rs +++ b/tests/mismatched_op.rs @@ -1,12 +1,11 @@ use rhai::{Engine, EvalAltResult, RegisterFn, INT}; #[test] -#[cfg(not(feature = "no_stdlib"))] fn test_mismatched_op() { - let mut engine = Engine::new(); + let engine = Engine::new(); assert!( - matches!(engine.eval::(r#"60 + "hello""#).expect_err("expects error"), + matches!(engine.eval::(r#""hello, " + "world!""#).expect_err("expects error"), EvalAltResult::ErrorMismatchOutputType(err, _) if err == "string") ); } diff --git a/tests/not.rs b/tests/not.rs index 52913136..4ce9cd3e 100644 --- a/tests/not.rs +++ b/tests/not.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult}; #[test] fn test_not() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!( engine.eval::("let not_true = !true; not_true")?, diff --git a/tests/number_literals.rs b/tests/number_literals.rs index f6466967..3fe8c78f 100644 --- a/tests/number_literals.rs +++ b/tests/number_literals.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_number_literal() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("65")?, 65); @@ -11,7 +11,7 @@ fn test_number_literal() -> Result<(), EvalAltResult> { #[test] fn test_hex_literal() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("let x = 0xf; x")?, 15); assert_eq!(engine.eval::("let x = 0xff; x")?, 255); @@ -21,7 +21,7 @@ fn test_hex_literal() -> Result<(), EvalAltResult> { #[test] fn test_octal_literal() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("let x = 0o77; x")?, 63); assert_eq!(engine.eval::("let x = 0o1234; x")?, 668); @@ -31,7 +31,7 @@ fn test_octal_literal() -> Result<(), EvalAltResult> { #[test] fn test_binary_literal() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("let x = 0b1111; x")?, 15); assert_eq!( diff --git a/tests/ops.rs b/tests/ops.rs index aaddea8a..c817d65d 100644 --- a/tests/ops.rs +++ b/tests/ops.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_ops() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("60 + 5")?, 65); assert_eq!(engine.eval::("(1 + 2) * (6 - 4) / 2")?, 3); @@ -11,8 +11,8 @@ fn test_ops() -> Result<(), EvalAltResult> { } #[test] -fn test_op_prec() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); +fn test_op_precedence() -> Result<(), EvalAltResult> { + let engine = Engine::new(); assert_eq!( engine.eval::("let x = 0; if x == 10 || true { x = 1} x")?, diff --git a/tests/power_of.rs b/tests/power_of.rs index b8b83f08..3762ff0f 100644 --- a/tests/power_of.rs +++ b/tests/power_of.rs @@ -8,7 +8,7 @@ const EPSILON: FLOAT = 0.000_000_000_1; #[test] fn test_power_of() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("2 ~ 3")?, 8); assert_eq!(engine.eval::("(-2 ~ 3)")?, -8); @@ -29,7 +29,7 @@ fn test_power_of() -> Result<(), EvalAltResult> { #[test] fn test_power_of_equals() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let 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); diff --git a/tests/side_effects.rs b/tests/side_effects.rs index d97b6f33..0375d1af 100644 --- a/tests/side_effects.rs +++ b/tests/side_effects.rs @@ -1,5 +1,3 @@ -#![cfg(not(feature = "no_object"))] - ///! This test simulates an external command object that is driven by a script. use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT}; use std::sync::{Arc, Mutex}; @@ -40,8 +38,9 @@ impl CommandWrapper { } } +#[cfg(not(feature = "no_object"))] #[test] -fn test_side_effects() -> Result<(), EvalAltResult> { +fn test_side_effects_command() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); let mut scope = Scope::new(); @@ -79,3 +78,22 @@ fn test_side_effects() -> Result<(), EvalAltResult> { Ok(()) } + +#[test] +fn test_side_effects_print() -> Result<(), EvalAltResult> { + use std::sync::RwLock; + + let result = RwLock::new(String::from("")); + + { + let mut engine = Engine::new(); + + // Override action of 'print' function + engine.on_print(|s| result.write().unwrap().push_str(s)); + + engine.consume("print(40 + 2);")?; + } + + assert_eq!(*result.read().unwrap(), "42"); + Ok(()) +} diff --git a/tests/stack.rs b/tests/stack.rs new file mode 100644 index 00000000..79a16221 --- /dev/null +++ b/tests/stack.rs @@ -0,0 +1,30 @@ +#![cfg(not(feature = "no_function"))] +use rhai::{Engine, EvalAltResult}; + +#[test] +fn test_stack_overflow() -> Result<(), EvalAltResult> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::( + r" + fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } } + foo(30) + ", + )?, + 465 + ); + + match engine.eval::<()>( + r" + fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } } + foo(1000) + ", + ) { + Ok(_) => panic!("should be stack overflow"), + Err(EvalAltResult::ErrorStackOverflow(_)) => (), + Err(_) => panic!("should be stack overflow"), + } + + Ok(()) +} diff --git a/tests/string.rs b/tests/string.rs index 92310e33..7454dcf2 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult}; #[test] fn test_string() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!( engine.eval::(r#""Test string: \u2764""#)?, @@ -12,18 +12,24 @@ fn test_string() -> Result<(), EvalAltResult> { engine.eval::(r#""Test string: \x58""#)?, "Test string: X" ); + assert_eq!(engine.eval::(r#""\"hello\"""#)?, r#""hello""#); assert_eq!(engine.eval::(r#""foo" + "bar""#)?, "foobar"); + assert!(engine.eval::(r#"let y = "hello, world!"; "world" in y"#)?); + assert!(engine.eval::(r#"let y = "hello, world!"; 'w' in y"#)?); + assert!(!engine.eval::(r#"let y = "hello, world!"; "hey" in y"#)?); + #[cfg(not(feature = "no_stdlib"))] assert_eq!(engine.eval::(r#""foo" + 123"#)?, "foo123"); + #[cfg(not(feature = "no_stdlib"))] + #[cfg(not(feature = "no_object"))] + assert_eq!(engine.eval::("(42).to_string()")?, "42"); + #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_stdlib"))] assert_eq!(engine.eval::(r#""foo" + 123.4556"#)?, "foo123.4556"); - #[cfg(not(feature = "no_stdlib"))] - assert_eq!(engine.eval::("(42).to_string()")?, "42"); - Ok(()) } diff --git a/tests/throw.rs b/tests/throw.rs index 690d9c6e..6e55303c 100644 --- a/tests/throw.rs +++ b/tests/throw.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult}; #[test] fn test_throw() { - let mut engine = Engine::new(); + let engine = Engine::new(); assert!(matches!( engine.eval::<()>(r#"if true { throw "hello" }"#).expect_err("expects error"), diff --git a/tests/time.rs b/tests/time.rs new file mode 100644 index 00000000..10b232fe --- /dev/null +++ b/tests/time.rs @@ -0,0 +1,39 @@ +#![cfg(not(feature = "no_stdlib"))] + +use rhai::{Engine, EvalAltResult, INT}; + +#[cfg(not(feature = "no_float"))] +use rhai::FLOAT; + +#[test] +fn test_timestamp() -> Result<(), EvalAltResult> { + let engine = Engine::new(); + + assert_eq!(engine.eval::("type_of(timestamp())")?, "timestamp"); + + #[cfg(not(feature = "no_float"))] + assert!( + engine.eval::( + r#" + let time = timestamp(); + let x = 10_000; + while x > 0 { x -= 1; } + elapsed(time) + "# + )? < 10.0 + ); + + #[cfg(feature = "no_float")] + assert!( + engine.eval::( + r#" + let time = timestamp(); + let x = 10_000; + while x > 0 { x -= 1; } + elapsed(time) + "# + )? < 10 + ); + + Ok(()) +} diff --git a/tests/unary_after_binary.rs b/tests/unary_after_binary.rs index 16d4730a..ba74c9f9 100644 --- a/tests/unary_after_binary.rs +++ b/tests/unary_after_binary.rs @@ -4,7 +4,7 @@ use rhai::{Engine, EvalAltResult, INT}; // TODO also add test case for unary after compound // Hah, turns out unary + has a good use after all! fn test_unary_after_binary() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("10 % +4")?, 2); assert_eq!(engine.eval::("10 << +4")?, 160); diff --git a/tests/unary_minus.rs b/tests/unary_minus.rs index 80170498..e1a359d7 100644 --- a/tests/unary_minus.rs +++ b/tests/unary_minus.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_unary_minus() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("let x = -5; x")?, -5); diff --git a/tests/unit.rs b/tests/unit.rs index 91e9d9f2..465c7b48 100644 --- a/tests/unit.rs +++ b/tests/unit.rs @@ -2,21 +2,21 @@ use rhai::{Engine, EvalAltResult}; #[test] fn test_unit() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); engine.eval::<()>("let x = (); x")?; Ok(()) } #[test] fn test_unit_eq() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!(engine.eval::("let x = (); let y = (); x == y")?, true); Ok(()) } #[test] fn test_unit_with_spaces() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); engine.eval::<()>("let x = ( ); x")?; Ok(()) } diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 39cf5b76..8d5244c8 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, Scope, INT}; #[test] fn test_var_scope() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); let mut scope = Scope::new(); engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; @@ -21,7 +21,7 @@ fn test_var_scope() -> Result<(), EvalAltResult> { #[test] fn test_scope_eval() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); // First create the state let mut scope = Scope::new(); diff --git a/tests/while_loop.rs b/tests/while_loop.rs index 18d5a03b..3a035039 100644 --- a/tests/while_loop.rs +++ b/tests/while_loop.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_while() -> Result<(), EvalAltResult> { - let mut engine = Engine::new(); + let engine = Engine::new(); assert_eq!( engine.eval::(