diff --git a/Cargo.toml b/Cargo.toml index 1164a869..d98dc9ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,21 +42,21 @@ codegen-units = 1 #panic = 'abort' # remove stack backtrace for no-std [dependencies.libm] -version = "0.2.1" +version = "*" optional = true [dependencies.core-error] -version = "0.0.1-rc4" +version = "*" features = ["alloc"] optional = true [dependencies.hashbrown] -version = "0.7.1" +version = "*" default-features = false features = ["ahash", "nightly", "inline-more"] optional = true [dependencies.ahash] -version = "0.3.2" +version = "*" default-features = false optional = true diff --git a/README.md b/README.md index f36e305b..922b0969 100644 --- a/README.md +++ b/README.md @@ -36,14 +36,20 @@ Install the Rhai crate by adding this line to `dependencies`: rhai = "0.11.0" ``` -or simply: +Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/): ```toml [dependencies] rhai = "*" ``` -to use the latest version. +Crate versions are released on [`crates.io`](https::/crates.io/crates/rhai/) infrequently, so if you want to track the +latest features, enhancements and bug fixes, pull directly from GitHub: + +```toml +[dependencies] +rhai = { git = "https://github.com/jonathandturner/rhai" } +``` Beware that in order to use pre-releases (e.g. alpha and beta), the exact version must be specified in the `Cargo.toml`. @@ -80,7 +86,7 @@ Related Other cool projects to check out: -* [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin. +* [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being led by my cousin. * Check out the list of [scripting languages for Rust](https://github.com/rust-unofficial/awesome-rust#scripting) on [awesome-rust](https://github.com/rust-unofficial/awesome-rust) Examples @@ -252,7 +258,7 @@ let result = engine.eval_expression_with_scope::(&mut scope, "if x { 42 } e Values and types ---------------- -[`type_of`]: #values-and-types +[`type_of()`]: #values-and-types The following primitive types are supported natively: @@ -547,8 +553,8 @@ let result = engine.eval::("let x = new_ts(); x.foo()")?; println!("result: {}", result); // prints 1 ``` -[`type_of`] works fine with custom types and returns the name of the type. If `register_type_with_name` is used to register the custom type -with a special "pretty-print" name, [`type_of`] will return that name instead. +[`type_of()`] works fine with custom types and returns the name of the type. If `register_type_with_name` is used to register the custom type +with a special "pretty-print" name, [`type_of()`] will return that name instead. ```rust engine.register_type::(); @@ -645,6 +651,18 @@ fn main() -> Result<(), EvalAltResult> } ``` +Engine configuration options +--------------------------- + +| Method | Description | +| ------------------------ | ---------------------------------------------------------------------------------------- | +| `set_optimization_level` | Set the amount of script _optimizations_ performed. See [`script optimization`]. | +| `set_max_call_levels` | Set the maximum number of function call levels (default 50) to avoid infinite recursion. | + +[`script optimization`]: #script-optimization + +------- + Rhai Language Guide =================== @@ -832,9 +850,11 @@ String and char literals follow C-style formatting, with support for Unicode ('` 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. -Although internally Rhai strings are stored as UTF-8 just like in Rust (they _are_ Rust `String`s), -in the Rhai language they can be considered a stream of Unicode characters, and can be directly indexed (unlike Rust). -Individual characters within a Rhai string can be replaced. In Rhai, there is no separate concepts of `String` and `&str` as in Rust. +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. +Individual characters within a Rhai string can also be replaced just as if the string is an array of Unicode characters. +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. @@ -926,9 +946,11 @@ 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 '`,`'. +Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`'. -The type of a Rhai array is `rhai::Array`. [`type_of()`] an array returns `"array"`. +The Rust type of a Rhai array is `rhai::Array`. + +[`type_of()`] an array returns `"array"`. Arrays are disabled via the [`no_index`] feature. @@ -1075,17 +1097,13 @@ my_str += 12345; my_str == "abcABC12345" ``` -If statements -------------- - -All branches of an `if` statement must be enclosed within braces '`{`' .. '`}`', even when there is only one statement. - -Like Rust, there is no ambiguity regarding which `if` clause a statement belongs to. +`if` statements +--------------- ```rust -if true { +if foo(x) { print("It's true!"); -} else if true { +} else if bar == baz { print("It's true again!"); } else if ... { : @@ -1094,13 +1112,28 @@ if true { } else { print("It's finally false!"); } +``` +All branches of an `if` statement must be enclosed within braces '`{`' .. '`}`', even when there is only one statement. +Like Rust, there is no ambiguity regarding which `if` clause a statement belongs to. + +```rust if (decision) print("I've decided!"); // ^ syntax error, expecting '{' in statement block ``` -While loops ------------ +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; +x == 22; + +let x = if false { 42 }; // No else branch defaults to '()' +x == (); +``` + +`while` loops +------------- ```rust let x = 10; @@ -1112,8 +1145,8 @@ while x > 0 { } ``` -Infinite loops --------------- +Infinite `loop` +--------------- ```rust let x = 10; @@ -1125,8 +1158,8 @@ loop { } ``` -For loops ---------- +`for` loops +----------- Iterating through a range or an array is provided by the `for` ... `in` loop. @@ -1146,8 +1179,8 @@ for x in range(0, 50) { } ``` -Returning values ----------------- +`return`-ing values +------------------- ```rust return; // equivalent to return (); @@ -1155,8 +1188,8 @@ return; // equivalent to return (); return 123 + 456; // returns 579 ``` -Errors and exceptions ---------------------- +Errors and `throw`-ing exceptions +-------------------------------- All of [`Engine`]'s evaluation/consuming methods return `Result` with `EvalAltResult` holding error information. To deliberately return an error during an evaluation, use the `throw` keyword. @@ -1197,8 +1230,10 @@ fn add(x, y) { print(add(2, 3)); ``` +### Implicit return + Just like in Rust, an implicit return can be used. In fact, the last statement of a block is _always_ the block's return value -regardless of whether it is terminated with a semicolon `;`. This is different from Rust. +regardless of whether it is terminated with a semicolon `';'`. This is different from Rust. ```rust fn add(x, y) { @@ -1213,6 +1248,16 @@ print(add(2, 3)); // prints 5 print(add2(42)); // prints 44 ``` +### No access to external scope + +Functions can only access their parameters. They cannot access external variables (even _global_ variables). + +```rust +let x = 42; + +fn foo() { x } // syntax error - variable 'x' doesn't exist +``` + ### Passing arguments by value Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type). @@ -1225,7 +1270,7 @@ fn change(s) { // 's' is passed by value } let x = 500; -x.change(); // desugars to change(x) +x.change(); // de-sugars to change(x) x == 500; // 'x' is NOT changed! ``` @@ -1251,31 +1296,34 @@ fn do_addition(x) { ### Functions overloading -Functions can be _overloaded_ based on the _number_ of parameters (but not parameter _types_, since all parameters are the same type - [`Dynamic`]). -New definitions of the same name and number of parameters overwrite previous definitions. +Functions can be _overloaded_ and are resolved purely upon the function's _name_ and the _number_ of parameters +(but not parameter _types_, since all parameters are the same type - [`Dynamic`]). +New definitions _overwrite_ previous definitions of the same name and number of parameters. ```rust -fn abc(x,y,z) { print("Three!!! " + x + "," + y + "," + z) } -fn abc(x) { print("One! " + x) } -fn abc(x,y) { print("Two! " + x + "," + y) } -fn abc() { print("None.") } -fn abc(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition +fn foo(x,y,z) { print("Three!!! " + x + "," + y + "," + z) } +fn foo(x) { print("One! " + x) } +fn foo(x,y) { print("Two! " + x + "," + y) } +fn foo() { print("None.") } +fn foo(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition -abc(1,2,3); // prints "Three!!! 1,2,3" -abc(42); // prints "HA! NEW ONE! 42" -abc(1,2); // prints "Two!! 1,2" -abc(); // prints "None." +foo(1,2,3); // prints "Three!!! 1,2,3" +foo(42); // prints "HA! NEW ONE! 42" +foo(1,2); // prints "Two!! 1,2" +foo(); // prints "None." ``` Members and methods ------------------- -Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like in Rust: +Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like in Rust. ```rust let a = new_ts(); // constructor function a.field = 500; // property access a.update(); // method call + +update(a); // this works, but 'a' is unchanged because only a COPY of 'a' is passed to 'update' by VALUE ``` `print` and `debug` diff --git a/examples/arrays_and_structs.rs b/examples/arrays_and_structs.rs index cfdd7dc1..8ea7fcf0 100644 --- a/examples/arrays_and_structs.rs +++ b/examples/arrays_and_structs.rs @@ -1,8 +1,8 @@ -use rhai::{Engine, RegisterFn}; +use rhai::{Engine, RegisterFn, INT}; #[derive(Clone, Debug)] struct TestStruct { - x: i64, + x: INT, } impl TestStruct { diff --git a/examples/custom_types_and_methods.rs b/examples/custom_types_and_methods.rs index 3ac644f5..bd20159e 100644 --- a/examples/custom_types_and_methods.rs +++ b/examples/custom_types_and_methods.rs @@ -1,8 +1,8 @@ -use rhai::{Engine, EvalAltResult, RegisterFn}; +use rhai::{Engine, EvalAltResult, RegisterFn, INT}; #[derive(Clone)] struct TestStruct { - x: i64, + x: INT, } impl TestStruct { diff --git a/examples/hello.rs b/examples/hello.rs index 590074e7..8527d466 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -1,9 +1,9 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; fn main() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - let result = engine.eval::("40 + 2")?; + let result = engine.eval::("40 + 2")?; println!("Answer: {}", result); // prints 42 diff --git a/examples/no_std.rs b/examples/no_std.rs index d293dc91..32cf14a2 100644 --- a/examples/no_std.rs +++ b/examples/no_std.rs @@ -1,12 +1,16 @@ #![cfg_attr(feature = "no_std", no_std)] -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; fn main() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - let result = engine.eval::("40 + 2")?; + let result = engine.eval::("40 + 2")?; + #[cfg(not(feature = "no_std"))] + println!("Answer: {}", result); + + #[cfg(feature = "no_std")] assert_eq!(result, 42); Ok(()) diff --git a/examples/repl.rs b/examples/repl.rs index 91bfcfa0..4ba75597 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, Scope, AST}; +use rhai::{Engine, EvalAltResult, Position, Scope, AST}; #[cfg(not(feature = "no_optimize"))] use rhai::OptimizationLevel; @@ -13,7 +13,7 @@ fn print_error(input: &str, err: EvalAltResult) { iter::repeat(pad).take(len).collect::() } - let lines: Vec<_> = input.trim().split("\n").collect(); + let lines: Vec<_> = input.trim().split('\n').collect(); let line_no = if lines.len() > 1 { match err.position() { @@ -26,27 +26,18 @@ fn print_error(input: &str, err: EvalAltResult) { }; // Print error - let pos_text = format!(" ({})", err.position()); + let pos = err.position(); + let pos_text = format!(" ({})", pos); - match err.position() { - p if p.is_eof() => { - // EOF - let last = lines[lines.len() - 1]; - println!("{}{}", line_no, last); + let pos = if pos.is_eof() { + let last = lines[lines.len() - 1]; + Position::new(lines.len(), last.len() + 1) + } else { + pos + }; - let err_text = match err { - EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { - format!("Runtime error: {}", err) - } - _ => err.to_string(), - }; - - println!( - "{}^ {}", - padding(" ", line_no.len() + last.len() - 1), - err_text.replace(&pos_text, "") - ); - } + match pos { + p if p.is_eof() => panic!("should not be EOF"), p if p.is_none() => { // No position println!("{}", err); @@ -59,7 +50,7 @@ fn print_error(input: &str, err: EvalAltResult) { EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { format!("Runtime error: {}", err) } - _ => err.to_string(), + err => err.to_string(), }; println!( @@ -161,7 +152,7 @@ fn main() { #[cfg(not(feature = "no_optimize"))] { engine.set_optimization_level(OptimizationLevel::Full); - ast = Some(engine.optimize_ast(&mut scope, ast_u.as_ref().unwrap())); + ast = Some(engine.optimize_ast(&scope, ast_u.as_ref().unwrap())); engine.set_optimization_level(OptimizationLevel::None); } @@ -178,9 +169,9 @@ fn main() { }) }) { - println!(""); + println!(); print_error(&input, err); - println!(""); + println!(); } } } diff --git a/examples/reuse_scope.rs b/examples/reuse_scope.rs index a6509ffe..549ccea1 100644 --- a/examples/reuse_scope.rs +++ b/examples/reuse_scope.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, Scope}; +use rhai::{Engine, EvalAltResult, Scope, INT}; fn main() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); @@ -6,7 +6,7 @@ fn main() -> Result<(), EvalAltResult> { engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; - let result = engine.eval_with_scope::(&mut scope, "x")?; + let result = engine.eval_with_scope::(&mut scope, "x")?; println!("result: {}", result); diff --git a/examples/rhai_runner.rs b/examples/rhai_runner.rs index 2b9b2077..20f6226e 100644 --- a/examples/rhai_runner.rs +++ b/examples/rhai_runner.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, Position}; #[cfg(not(feature = "no_optimize"))] use rhai::OptimizationLevel; @@ -10,7 +10,7 @@ fn padding(pad: &str, len: usize) -> String { } fn eprint_error(input: &str, err: EvalAltResult) { - fn eprint_line(lines: &Vec<&str>, line: usize, pos: usize, err: &str) { + fn eprint_line(lines: &[&str], line: usize, pos: usize, err: &str) { let line_no = format!("{}: ", line); let pos_text = format!(" (line {}, position {})", line, pos); @@ -23,34 +23,32 @@ fn eprint_error(input: &str, err: EvalAltResult) { eprintln!(""); } - let lines: Vec<_> = input.split("\n").collect(); + let lines: Vec<_> = input.split('\n').collect(); // Print error - match err.position() { - p if p.is_eof() => { - // EOF - let line = lines.len() - 1; - let pos = lines[line - 1].len(); - let err_text = match err { - EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { - format!("Runtime error: {}", err) - } - _ => err.to_string(), - }; - eprint_line(&lines, line, pos, &err_text); - } + let pos = if err.position().is_eof() { + let last = lines[lines.len() - 1]; + Position::new(lines.len(), last.len() + 1) + } else { + err.position() + }; + + match pos { + p if p.is_eof() => panic!("should not be EOF"), p if p.is_none() => { // No position eprintln!("{}", err); } p => { // Specific position - eprint_line( - &lines, - p.line().unwrap(), - p.position().unwrap(), - &err.to_string(), - ) + let err_text = match err { + EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { + format!("Runtime error: {}", err) + } + err => err.to_string(), + }; + + eprint_line(&lines, p.line().unwrap(), p.position().unwrap(), &err_text) } } } @@ -72,12 +70,9 @@ fn main() { let mut contents = String::new(); - match f.read_to_string(&mut contents) { - Err(err) => { - eprintln!("Error reading script file: {}\n{}", filename, err); - exit(1); - } - _ => (), + if let Err(err) = f.read_to_string(&mut contents) { + eprintln!("Error reading script file: {}\n{}", filename, err); + exit(1); } if let Err(err) = engine.consume(false, &contents) { diff --git a/examples/simple_fn.rs b/examples/simple_fn.rs index 994eb7ec..796b8114 100644 --- a/examples/simple_fn.rs +++ b/examples/simple_fn.rs @@ -1,15 +1,15 @@ -use rhai::{Engine, EvalAltResult, RegisterFn}; +use rhai::{Engine, EvalAltResult, RegisterFn, INT}; fn main() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - fn add(x: i64, y: i64) -> i64 { + fn add(x: INT, y: INT) -> INT { x + y } engine.register_fn("add", add); - let result = engine.eval::("add(40, 2)")?; + let result = engine.eval::("add(40, 2)")?; println!("Answer: {}", result); // prints 42 diff --git a/src/api.rs b/src/api.rs index 208f311c..db92aed7 100644 --- a/src/api.rs +++ b/src/api.rs @@ -15,6 +15,7 @@ use crate::optimize::optimize_into_ast; use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, + format, string::{String, ToString}, sync::Arc, vec::Vec, @@ -35,7 +36,7 @@ impl<'e> Engine<'e> { args, }; - self.ext_functions.insert(spec, f); + self.functions.insert(spec, f); } /// Register a custom type for use with the `Engine`. @@ -725,11 +726,9 @@ impl<'e> Engine<'e> { statements }; - let mut result = ().into_dynamic(); - - for stmt in statements { - result = engine.eval_stmt(scope, stmt)?; - } + let result = statements.iter().try_fold(().into_dynamic(), |_, stmt| { + engine.eval_stmt(scope, stmt, 0) + })?; if !retain_functions { engine.clear_functions(); @@ -828,7 +827,7 @@ impl<'e> Engine<'e> { let result = statements .iter() - .try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o)) + .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0)) .map(|_| ()); if !retain_functions { @@ -847,13 +846,7 @@ impl<'e> Engine<'e> { functions: impl IntoIterator>, ) { for f in functions.into_iter() { - match self - .script_functions - .binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) - { - Ok(n) => self.script_functions[n] = f.clone(), - Err(n) => self.script_functions.insert(n, f.clone()), - } + self.fn_lib.add_or_replace_function(f.clone()); } } @@ -887,27 +880,18 @@ impl<'e> Engine<'e> { name: &str, args: A, ) -> Result { - // Split out non-generic portion to avoid exploding code size - fn call_fn_internal( - engine: &mut Engine, - name: &str, - mut values: Vec, - ) -> Result { - let values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); + let mut values = args.into_vec(); + let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); - let result = engine.call_fn_raw(name, values, None, Position::none()); - - result - } - - call_fn_internal(self, name, args.into_vec()).and_then(|b| { - b.downcast().map(|b| *b).map_err(|a| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name((*a).type_name()).into(), - Position::none(), - ) + self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0) + .and_then(|b| { + b.downcast().map(|b| *b).map_err(|a| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name((*a).type_name()).into(), + Position::none(), + ) + }) }) - }) } /// Optimize the `AST` with constants defined in an external Scope. diff --git a/src/builtin.rs b/src/builtin.rs index 952ee213..96aab8c2 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -57,7 +57,7 @@ macro_rules! reg_op_result1 { impl Engine<'_> { /// Register the core built-in library. pub(crate) fn register_core_lib(&mut self) { - /// Checked add + // Checked add #[cfg(not(feature = "unchecked"))] fn add(x: T, y: T) -> Result { x.checked_add(&y).ok_or_else(|| { @@ -67,7 +67,7 @@ impl Engine<'_> { ) }) } - /// Checked subtract + // Checked subtract #[cfg(not(feature = "unchecked"))] fn sub(x: T, y: T) -> Result { x.checked_sub(&y).ok_or_else(|| { @@ -77,7 +77,7 @@ impl Engine<'_> { ) }) } - /// Checked multiply + // Checked multiply #[cfg(not(feature = "unchecked"))] fn mul(x: T, y: T) -> Result { x.checked_mul(&y).ok_or_else(|| { @@ -87,7 +87,7 @@ impl Engine<'_> { ) }) } - /// Checked divide + // Checked divide #[cfg(not(feature = "unchecked"))] fn div(x: T, y: T) -> Result where @@ -108,7 +108,7 @@ impl Engine<'_> { ) }) } - /// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX + // Checked negative - e.g. -(i32::MIN) will overflow i32::MAX #[cfg(not(feature = "unchecked"))] fn neg(x: T) -> Result { x.checked_neg().ok_or_else(|| { @@ -118,7 +118,7 @@ impl Engine<'_> { ) }) } - /// Checked absolute + // Checked absolute #[cfg(not(feature = "unchecked"))] fn abs(x: T) -> Result { // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics @@ -134,32 +134,32 @@ impl Engine<'_> { }) } } - /// Unchecked add - may panic on overflow + // Unchecked add - may panic on overflow #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn add_u(x: T, y: T) -> ::Output { x + y } - /// Unchecked subtract - may panic on underflow + // Unchecked subtract - may panic on underflow #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn sub_u(x: T, y: T) -> ::Output { x - y } - /// Unchecked multiply - may panic on overflow + // Unchecked multiply - may panic on overflow #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn mul_u(x: T, y: T) -> ::Output { x * y } - /// Unchecked divide - may panic when dividing by zero + // Unchecked divide - may panic when dividing by zero #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn div_u(x: T, y: T) -> ::Output { x / y } - /// Unchecked negative - may panic on overflow + // Unchecked negative - may panic on overflow #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn neg_u(x: T) -> ::Output { -x } - /// Unchecked absolute - may panic on overflow + // Unchecked absolute - may panic on overflow #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn abs_u(x: T) -> ::Output where @@ -174,7 +174,6 @@ impl Engine<'_> { } // Comparison operators - fn lt(x: T, y: T) -> bool { x < y } @@ -195,7 +194,6 @@ impl Engine<'_> { } // Logic operators - fn and(x: bool, y: bool) -> bool { x && y } @@ -207,7 +205,6 @@ impl Engine<'_> { } // Bit operators - fn binary_and(x: T, y: T) -> ::Output { x & y } @@ -218,7 +215,7 @@ impl Engine<'_> { x ^ y } - /// Checked left-shift + // Checked left-shift #[cfg(not(feature = "unchecked"))] fn shl(x: T, y: INT) -> Result { // Cannot shift by a negative number of bits @@ -236,7 +233,7 @@ impl Engine<'_> { ) }) } - /// Checked right-shift + // Checked right-shift #[cfg(not(feature = "unchecked"))] fn shr(x: T, y: INT) -> Result { // Cannot shift by a negative number of bits @@ -254,17 +251,17 @@ impl Engine<'_> { ) }) } - /// Unchecked left-shift - may panic if shifting by a negative number of bits + // Unchecked left-shift - may panic if shifting by a negative number of bits #[cfg(feature = "unchecked")] fn shl_u>(x: T, y: T) -> >::Output { x.shl(y) } - /// Unchecked right-shift - may panic if shifting by a negative number of bits + // Unchecked right-shift - may panic if shifting by a negative number of bits #[cfg(feature = "unchecked")] fn shr_u>(x: T, y: T) -> >::Output { x.shr(y) } - /// Checked modulo + // Checked modulo #[cfg(not(feature = "unchecked"))] fn modulo(x: T, y: T) -> Result { x.checked_rem(&y).ok_or_else(|| { @@ -274,12 +271,12 @@ impl Engine<'_> { ) }) } - /// Unchecked modulo - may panic if dividing by zero + // Unchecked modulo - may panic if dividing by zero #[cfg(any(feature = "unchecked", not(feature = "no_float")))] fn modulo_u(x: T, y: T) -> ::Output { x % y } - /// Checked power + // Checked power #[cfg(not(feature = "unchecked"))] fn pow_i_i(x: INT, y: INT) -> Result { #[cfg(not(feature = "only_i32"))] @@ -321,17 +318,17 @@ impl Engine<'_> { } } } - /// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX) + // Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX) #[cfg(feature = "unchecked")] fn pow_i_i_u(x: INT, y: INT) -> INT { x.pow(y as u32) } - /// Floating-point power - always well-defined + // Floating-point power - always well-defined #[cfg(not(feature = "no_float"))] fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT { x.powf(y) } - /// Checked power + // Checked power #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_float"))] fn pow_f_i(x: FLOAT, y: INT) -> Result { @@ -345,7 +342,7 @@ impl Engine<'_> { Ok(x.powi(y as i32)) } - /// Unchecked power - may be incorrect if the power index is too high (> i32::MAX) + // Unchecked power - may be incorrect if the power index is too high (> i32::MAX) #[cfg(feature = "unchecked")] #[cfg(not(feature = "no_float"))] fn pow_f_i_u(x: FLOAT, y: INT) -> FLOAT { @@ -432,7 +429,8 @@ impl Engine<'_> { } } - // `&&` and `||` are treated specially as they short-circuit + // `&&` and `||` are treated specially as they short-circuit. + // They are implemented as special `Expr` instances, not function calls. //reg_op!(self, "||", or, bool); //reg_op!(self, "&&", and, bool); @@ -623,10 +621,6 @@ impl Engine<'_> { }); } - fn range(from: T, to: T) -> Range { - from..to - } - reg_iterator::(self); self.register_fn("range", |i1: INT, i2: INT| (i1..i2)); @@ -634,15 +628,15 @@ impl Engine<'_> { #[cfg(not(feature = "only_i64"))] { macro_rules! reg_range { - ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( + ($self:expr, $x:expr, $( $y:ty ),*) => ( $( reg_iterator::<$y>(self); - $self.register_fn($x, $op as fn(x: $y, y: $y)->Range<$y>); + $self.register_fn($x, (|x: $y, y: $y| x..y) as fn(x: $y, y: $y)->Range<$y>); )* ) } - reg_range!(self, "range", range, i8, u8, i16, u16, i32, i64, u32, u64); + reg_range!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64); } } } @@ -837,10 +831,10 @@ impl Engine<'_> { } reg_fn2x!(self, "+", append, String, String, INT, bool, char); - self.register_fn("+", |x: String, _: ()| format!("{}", x)); + self.register_fn("+", |x: String, _: ()| x); reg_fn2y!(self, "+", prepend, String, String, INT, bool, char); - self.register_fn("+", |_: (), y: String| format!("{}", y)); + self.register_fn("+", |_: (), y: String| y); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] @@ -891,9 +885,7 @@ impl Engine<'_> { let trimmed = s.trim(); if trimmed.len() < s.len() { - let chars: Vec<_> = trimmed.chars().collect(); - s.clear(); - chars.iter().for_each(|&ch| s.push(ch)); + *s = trimmed.to_string(); } }); } diff --git a/src/call.rs b/src/call.rs index f4dadb4b..c1147a53 100644 --- a/src/call.rs +++ b/src/call.rs @@ -11,11 +11,15 @@ use crate::engine::Array; use crate::stdlib::{string::String, vec, vec::Vec}; /// Trait that represent arguments to a function call. +/// Any data type that can be converted into a `Vec` of `Dynamic` values can be used +/// as arguments to a function call. pub trait FuncArgs { /// Convert to a `Vec` of `Dynamic` arguments. fn into_vec(self) -> Vec; } +/// Macro to implement `FuncArgs` for a single standard type that can be converted +/// into `Dynamic`. macro_rules! impl_std_args { ($($p:ty),*) => { $( @@ -43,6 +47,8 @@ impl_std_args!(INT); #[cfg(not(feature = "no_float"))] impl_std_args!(f32, f64); +/// Macro to implement `FuncArgs` for tuples of standard types (each can be +/// converted into `Dynamic`). macro_rules! impl_args { ($($p:ident),*) => { impl<$($p: Any + Clone),*> FuncArgs for ($($p,)*) @@ -69,5 +75,5 @@ macro_rules! impl_args { }; } -#[cfg_attr(rustfmt, rustfmt_skip)] -impl_args!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T); +#[rustfmt::skip] +impl_args!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); diff --git a/src/engine.rs b/src/engine.rs index 24430ea1..9ce74b9d 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,7 +3,7 @@ use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, INT}; use crate::result::EvalAltResult; -use crate::scope::{Scope, ScopeSource, VariableType}; +use crate::scope::{EntryRef as ScopeSource, EntryType as ScopeEntryType, Scope}; #[cfg(not(feature = "no_optimize"))] use crate::optimize::OptimizationLevel; @@ -12,6 +12,7 @@ use crate::stdlib::{ any::{type_name, TypeId}, borrow::Cow, boxed::Box, + cmp::Ordering, collections::HashMap, format, iter::once, @@ -25,19 +26,20 @@ use crate::stdlib::{ #[cfg(not(feature = "no_index"))] pub type Array = Vec; -pub type FnCallArgs<'a> = Vec<&'a mut Variant>; +pub type FnCallArgs<'a> = [&'a mut Variant]; -pub type FnAny = dyn Fn(FnCallArgs, Position) -> Result; +pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result; type IteratorFn = dyn Fn(&Dynamic) -> Box>; -pub(crate) const KEYWORD_PRINT: &'static str = "print"; -pub(crate) const KEYWORD_DEBUG: &'static str = "debug"; -pub(crate) const KEYWORD_DUMP_AST: &'static str = "dump_ast"; -pub(crate) const KEYWORD_TYPE_OF: &'static str = "type_of"; -pub(crate) const KEYWORD_EVAL: &'static str = "eval"; -pub(crate) const FUNC_GETTER: &'static str = "get$"; -pub(crate) const FUNC_SETTER: &'static str = "set$"; +pub(crate) const MAX_CALL_STACK_DEPTH: usize = 64; +pub(crate) const KEYWORD_PRINT: &str = "print"; +pub(crate) const KEYWORD_DEBUG: &str = "debug"; +pub(crate) const KEYWORD_DUMP_AST: &str = "dump_ast"; +pub(crate) const KEYWORD_TYPE_OF: &str = "type_of"; +pub(crate) const KEYWORD_EVAL: &str = "eval"; +pub(crate) const FUNC_GETTER: &str = "get$"; +pub(crate) const FUNC_SETTER: &str = "set$"; #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] #[cfg(not(feature = "no_index"))] @@ -53,6 +55,64 @@ pub struct FnSpec<'a> { pub args: Option>, } +/// A type that holds a library of script-defined functions. +/// +/// Since script-defined functions have `Dynamic` parameters, functions with the same name +/// and number of parameters are considered equivalent. +/// +/// Since the key is a combination of the function name (a String) plus the number of parameters, +/// we cannot use a `HashMap` because we don't want to clone the function name string just +/// to search for it. +/// +/// So instead this is implemented as a sorted list and binary searched. +#[derive(Debug)] +pub struct FunctionsLib(Vec>); + +impl FnDef { + /// Function to order two FnDef records, for binary search. + pub fn compare(&self, name: &str, params_len: usize) -> Ordering { + // First order by name + match self.name.as_str().cmp(name) { + // Then by number of parameters + Ordering::Equal => self.params.len().cmp(¶ms_len), + order => order, + } + } +} + +impl FunctionsLib { + /// Create a new `FunctionsLib`. + pub fn new() -> Self { + FunctionsLib(Vec::new()) + } + /// Clear the `FunctionsLib`. + pub fn clear(&mut self) { + self.0.clear(); + } + /// Does a certain function exist in the `FunctionsLib`? + pub fn has_function(&self, name: &str, params: usize) -> bool { + self.0.binary_search_by(|f| f.compare(name, params)).is_ok() + } + /// Add a function (or replace an existing one) in the `FunctionsLib`. + pub fn add_or_replace_function(&mut self, fn_def: Arc) { + match self + .0 + .binary_search_by(|f| f.compare(&fn_def.name, fn_def.params.len())) + { + Ok(n) => self.0[n] = fn_def, + Err(n) => self.0.insert(n, fn_def), + } + } + /// Get a function definition from the `FunctionsLib`. + pub fn get_function(&self, name: &str, params: usize) -> Option> { + if let Ok(n) = self.0.binary_search_by(|f| f.compare(name, params)) { + Some(self.0[n].clone()) + } else { + None + } + } +} + /// Rhai main scripting engine. /// /// ``` @@ -68,27 +128,30 @@ pub struct FnSpec<'a> { /// # } /// ``` pub struct Engine<'e> { - /// Optimize the AST after compilation - #[cfg(not(feature = "no_optimize"))] - pub(crate) optimization_level: OptimizationLevel, - /// A hashmap containing all compiled functions known to the engine - pub(crate) ext_functions: HashMap, Box>, - /// A hashmap containing all script-defined functions - pub(crate) script_functions: Vec>, - /// A hashmap containing all iterators known to the engine + /// A hashmap containing all compiled functions known to the engine. + pub(crate) functions: HashMap, Box>, + /// A hashmap containing all script-defined functions. + pub(crate) fn_lib: FunctionsLib, + /// A hashmap containing all iterators known to the engine. pub(crate) type_iterators: HashMap>, - /// A hashmap mapping type names to pretty-print names + /// A hashmap mapping type names to pretty-print names. pub(crate) type_names: HashMap, - /// Closure for implementing the print commands + /// Closure for implementing the print commands. pub(crate) on_print: Box, - /// Closure for implementing the debug commands + /// Closure for implementing the debug commands. pub(crate) on_debug: Box, + + /// Optimize the AST after compilation. + #[cfg(not(feature = "no_optimize"))] + pub(crate) optimization_level: OptimizationLevel, + + /// Maximum levels of call-stack to prevent infinite recursion. + pub(crate) max_call_stack_depth: usize, } -impl Engine<'_> { - /// Create a new `Engine` - pub fn new() -> Self { +impl Default for Engine<'_> { + fn default() -> Self { // User-friendly names for built-in types let type_names = [ #[cfg(not(feature = "no_index"))] @@ -97,13 +160,13 @@ impl Engine<'_> { (type_name::(), "dynamic"), ] .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) + .map(|(k, v)| ((*k).to_string(), (*v).to_string())) .collect(); // Create the new scripting Engine let mut engine = Engine { - ext_functions: HashMap::new(), - script_functions: Vec::new(), + functions: HashMap::new(), + fn_lib: FunctionsLib::new(), type_iterators: HashMap::new(), type_names, on_print: Box::new(default_print), // default print/debug implementations @@ -116,6 +179,8 @@ impl Engine<'_> { #[cfg(not(feature = "no_optimize"))] #[cfg(feature = "optimize_full")] optimization_level: OptimizationLevel::Full, + + max_call_stack_depth: MAX_CALL_STACK_DEPTH, }; engine.register_core_lib(); @@ -125,6 +190,13 @@ impl Engine<'_> { engine } +} + +impl Engine<'_> { + /// Create a new `Engine` + pub fn new() -> Self { + Default::default() + } /// Control whether and how the `Engine` will optimize an AST after compilation #[cfg(not(feature = "no_optimize"))] @@ -132,12 +204,18 @@ impl Engine<'_> { self.optimization_level = optimization_level } + /// Set the maximum levels of function calls allowed for a script in order to avoid + /// infinite recursion and stack overflows. + pub fn set_max_call_levels(&mut self, levels: usize) { + 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: FnCallArgs, + args: &mut FnCallArgs, pos: Position, ) -> Result, EvalAltResult> { let spec = FnSpec { @@ -146,7 +224,7 @@ impl Engine<'_> { }; // Search built-in's and external functions - if let Some(func) = self.ext_functions.get(&spec) { + if let Some(func) = self.functions.get(&spec) { // Run external function Ok(Some(func(args, pos)?)) } else { @@ -159,38 +237,31 @@ impl Engine<'_> { pub(crate) fn call_fn_raw( &mut self, fn_name: &str, - args: FnCallArgs, + args: &mut FnCallArgs, def_val: Option<&Dynamic>, pos: Position, + level: usize, ) -> Result { // First search in script-defined functions (can override built-in) - if let Ok(n) = self - .script_functions - .binary_search_by(|f| f.compare(fn_name, args.len())) - { + if let Some(fn_def) = self.fn_lib.get_function(fn_name, args.len()) { let mut scope = Scope::new(); - let fn_def = self.script_functions[n].clone(); - scope.extend( // Put arguments into scope as variables fn_def .params .iter() .zip(args.iter().map(|x| (*x).into_dynamic())) - .map(|(name, value)| (name, VariableType::Normal, value)), + .map(|(name, value)| (name, ScopeEntryType::Normal, value)), ); - // Evaluate - // Convert return statement to return value + // Evaluate the function at one higher level of call depth return self - .eval_stmt(&mut scope, &fn_def.body) - .or_else(|mut err| match err { + .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.set_position(pos); - Err(err) - } + err => Err(err.set_position(pos)), }); } @@ -200,25 +271,29 @@ impl Engine<'_> { }; // Argument must be a string - fn cast_to_string<'a>(r: &'a Variant, pos: Position) -> Result<&'a str, EvalAltResult> { + fn cast_to_string(r: &Variant, pos: Position) -> Result<&str, EvalAltResult> { r.downcast_ref::() .map(String::as_str) .ok_or_else(|| EvalAltResult::ErrorMismatchOutputType(r.type_name().into(), pos)) } // Search built-in's and external functions - if let Some(func) = self.ext_functions.get(&spec) { + if let Some(func) = self.functions.get(&spec) { // Run external function let result = func(args, pos)?; // See if the function match print/debug (which requires special processing) - match fn_name { - KEYWORD_PRINT => self.on_print.as_mut()(cast_to_string(result.as_ref(), pos)?), - KEYWORD_DEBUG => self.on_debug.as_mut()(cast_to_string(result.as_ref(), pos)?), - _ => (), - } - - return Ok(result); + return Ok(match fn_name { + KEYWORD_PRINT => { + self.on_print.as_mut()(cast_to_string(result.as_ref(), pos)?); + ().into_dynamic() + } + KEYWORD_DEBUG => { + self.on_debug.as_mut()(cast_to_string(result.as_ref(), pos)?); + ().into_dynamic() + } + _ => result, + }); } if fn_name.starts_with(FUNC_GETTER) { @@ -249,11 +324,11 @@ impl Engine<'_> { } // Raise error - let types_list = args + let types_list: Vec<_> = args .iter() .map(|x| (*x).type_name()) .map(|name| self.map_type_name(name)) - .collect::>(); + .collect(); Err(EvalAltResult::ErrorFunctionNotFound( format!("{} ({})", fn_name, types_list.join(", ")), @@ -275,6 +350,7 @@ impl Engine<'_> { src: Option, target: Option<&mut Variant>, dot_rhs: &Expr, + level: usize, ) -> Result { // Get the `this` reference. Either `src` or `target` should be `Some`. fn get_this_ptr<'a>( @@ -288,8 +364,9 @@ impl Engine<'_> { } else { // Otherwise, if `src` is `Some`, then it holds a name and index into `scope`; // using `get_mut` on `scope` to retrieve a mutable reference for return. - let ScopeSource { name, idx, .. } = src.expect("expected source in scope"); - scope.get_mut(name, idx).as_mut() + scope + .get_mut(src.expect("expected source in scope")) + .as_mut() } } @@ -298,23 +375,23 @@ 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)) + .map(|arg_expr| self.eval_expr(scope, arg_expr, level)) .collect::, _>>()?; let this_ptr = get_this_ptr(scope, src, target); - let args = once(this_ptr) + let mut arg_values: Vec<_> = once(this_ptr) .chain(values.iter_mut().map(Dynamic::as_mut)) .collect(); - self.call_fn_raw(fn_name, args, def_val.as_ref(), *pos) + self.call_fn_raw(fn_name, &mut arg_values, def_val.as_ref(), *pos, 0) } // xxx.id Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); let this_ptr = get_this_ptr(scope, src, target); - self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) + self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0) } // xxx.idx_lhs[idx_expr] @@ -326,13 +403,13 @@ impl Engine<'_> { let get_fn_name = format!("{}{}", FUNC_GETTER, id); let this_ptr = get_this_ptr(scope, src, target); ( - self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, + self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)?, *pos, ) } // xxx.???[???][idx_expr] Expr::Index(_, _, _) => ( - self.get_dot_val_helper(scope, src, target, idx_lhs)?, + self.get_dot_val_helper(scope, src, target, idx_lhs, level)?, *op_pos, ), // Syntax error @@ -344,7 +421,7 @@ impl Engine<'_> { } }; - let idx = self.eval_index_value(scope, idx_expr)?; + let idx = self.eval_index_value(scope, idx_expr, level)?; self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos) .map(|(v, _)| v) } @@ -356,9 +433,9 @@ impl Engine<'_> { let get_fn_name = format!("{}{}", FUNC_GETTER, id); let this_ptr = get_this_ptr(scope, src, target); - self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) + self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|mut v| { - self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs) + self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs, level) }) } // xxx.idx_lhs[idx_expr].rhs @@ -370,13 +447,13 @@ impl Engine<'_> { let get_fn_name = format!("{}{}", FUNC_GETTER, id); let this_ptr = get_this_ptr(scope, src, target); ( - self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, + self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)?, *pos, ) } // xxx.???[???][idx_expr].rhs Expr::Index(_, _, _) => ( - self.get_dot_val_helper(scope, src, target, idx_lhs)?, + self.get_dot_val_helper(scope, src, target, idx_lhs, level)?, *op_pos, ), // Syntax error @@ -388,10 +465,10 @@ impl Engine<'_> { } }; - let idx = self.eval_index_value(scope, idx_expr)?; + let idx = self.eval_index_value(scope, idx_expr, level)?; self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos) .and_then(|(mut v, _)| { - self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs) + self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs, level) }) } // Syntax error @@ -415,41 +492,39 @@ impl Engine<'_> { scope: &mut Scope, dot_lhs: &Expr, dot_rhs: &Expr, + level: usize, ) -> Result { match dot_lhs { // id.??? Expr::Variable(id, pos) => { - let (ScopeSource { idx, var_type, .. }, _) = - Self::search_scope(scope, id, Ok, *pos)?; + let (entry, _) = Self::search_scope(scope, id, Ok, *pos)?; + + // Avoid referencing scope which is used below as mut + let entry = ScopeSource { name: id, ..entry }; // This is a variable property access (potential function call). // Use a direct index into `scope` to directly mutate the variable value. - let src = ScopeSource { - name: id, - idx, - var_type, - }; - - self.get_dot_val_helper(scope, Some(src), None, dot_rhs) + self.get_dot_val_helper(scope, Some(entry), None, dot_rhs, level) } // idx_lhs[idx_expr].??? #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, op_pos) => { let (src_type, src, idx, mut target) = - self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos)?; - let val = self.get_dot_val_helper(scope, None, Some(target.as_mut()), dot_rhs); + self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?; + let this_ptr = target.as_mut(); + let val = self.get_dot_val_helper(scope, None, Some(this_ptr), 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.var_type { - VariableType::Constant => { + match src.typ { + ScopeEntryType::Constant => { return Err(EvalAltResult::ErrorAssignmentToConstant( src.name.to_string(), idx_lhs.position(), )); } - VariableType::Normal => { + ScopeEntryType::Normal => { Self::update_indexed_var_in_scope( src_type, scope, @@ -466,8 +541,9 @@ impl Engine<'_> { // {expr}.??? expr => { - let mut target = self.eval_expr(scope, expr)?; - self.get_dot_val_helper(scope, None, Some(target.as_mut()), dot_rhs) + let mut target = self.eval_expr(scope, expr, level)?; + let this_ptr = target.as_mut(); + self.get_dot_val_helper(scope, None, Some(this_ptr), dot_rhs, level) } } } @@ -491,8 +567,9 @@ impl Engine<'_> { &mut self, scope: &mut Scope, idx_expr: &Expr, + level: usize, ) -> Result { - self.eval_expr(scope, idx_expr)? + self.eval_expr(scope, idx_expr, level)? .downcast::() .map(|v| *v) .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position())) @@ -554,8 +631,9 @@ impl Engine<'_> { lhs: &'a Expr, idx_expr: &Expr, op_pos: Position, + level: usize, ) -> Result<(IndexSourceType, Option>, usize, Dynamic), EvalAltResult> { - let idx = self.eval_index_value(scope, idx_expr)?; + let idx = self.eval_index_value(scope, idx_expr, level)?; match lhs { // id[idx_expr] @@ -570,8 +648,8 @@ impl Engine<'_> { src_type, Some(ScopeSource { name: &id, - var_type: src.var_type, - idx: src.idx, + typ: src.typ, + index: src.index, }), idx as usize, val, @@ -580,7 +658,7 @@ impl Engine<'_> { // (expr)[idx_expr] expr => { - let val = self.eval_expr(scope, expr)?; + let val = self.eval_expr(scope, expr, level)?; self.get_indexed_value(&val, idx, idx_expr.position(), op_pos) .map(|(v, _)| (IndexSourceType::Expression, None, idx as usize, v)) @@ -614,20 +692,22 @@ impl Engine<'_> { match src_type { // array_id[idx] = val IndexSourceType::Array => { - let arr = scope.get_mut_by_type::(src.name, src.idx); - Ok((arr[idx as usize] = new_val.0).into_dynamic()) + let arr = scope.get_mut_by_type::(src); + arr[idx as usize] = new_val.0; + Ok(().into_dynamic()) } // string_id[idx] = val IndexSourceType::String => { - let s = scope.get_mut_by_type::(src.name, src.idx); + let s = scope.get_mut_by_type::(src); let pos = new_val.1; // Value must be a character let ch = *new_val .0 .downcast::() .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; - Ok(Self::str_replace_char(s, idx as usize, ch).into_dynamic()) + Self::str_replace_char(s, idx as usize, ch); + Ok(().into_dynamic()) } IndexSourceType::Expression => panic!("expression cannot be indexed for update"), @@ -667,13 +747,14 @@ impl Engine<'_> { this_ptr: &mut Variant, dot_rhs: &Expr, new_val: (&mut Dynamic, Position), + level: usize, ) -> Result { match dot_rhs { // xxx.id Expr::Property(id, pos) => { let set_fn_name = format!("{}{}", FUNC_SETTER, id); - - self.call_fn_raw(&set_fn_name, vec![this_ptr, new_val.0.as_mut()], None, *pos) + let mut args = [this_ptr, new_val.0.as_mut()]; + self.call_fn_raw(&set_fn_name, &mut args, None, *pos, 0) } // xxx.lhs[idx_expr] @@ -684,9 +765,9 @@ impl Engine<'_> { Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); - self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) + self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|v| { - let idx = self.eval_index_value(scope, idx_expr)?; + let idx = self.eval_index_value(scope, idx_expr, level)?; Self::update_indexed_value( v, idx as usize, @@ -696,7 +777,8 @@ impl Engine<'_> { }) .and_then(|mut v| { let set_fn_name = format!("{}{}", FUNC_SETTER, id); - self.call_fn_raw(&set_fn_name, vec![this_ptr, v.as_mut()], None, *pos) + let mut args = [this_ptr, v.as_mut()]; + self.call_fn_raw(&set_fn_name, &mut args, None, *pos, 0) }) } @@ -713,15 +795,15 @@ impl Engine<'_> { Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); - self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) + self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|mut v| { - self.set_dot_val_helper(scope, v.as_mut(), rhs, new_val) + self.set_dot_val_helper(scope, v.as_mut(), rhs, new_val, level) .map(|_| v) // Discard Ok return value }) .and_then(|mut v| { let set_fn_name = format!("{}{}", FUNC_SETTER, id); - - self.call_fn_raw(&set_fn_name, vec![this_ptr, v.as_mut()], None, *pos) + let mut args = [this_ptr, v.as_mut()]; + self.call_fn_raw(&set_fn_name, &mut args, None, *pos, 0) }) } @@ -733,27 +815,23 @@ impl Engine<'_> { Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); - self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) + self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|v| { - let idx = self.eval_index_value(scope, idx_expr)?; + let idx = self.eval_index_value(scope, idx_expr, level)?; let (mut target, _) = self.get_indexed_value(&v, idx, idx_expr.position(), *op_pos)?; let val_pos = new_val.1; - self.set_dot_val_helper(scope, target.as_mut(), rhs, new_val)?; + let this_ptr = target.as_mut(); + self.set_dot_val_helper(scope, 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 as usize, target, val_pos) }) .and_then(|mut v| { let set_fn_name = format!("{}{}", FUNC_SETTER, id); - - self.call_fn_raw( - &set_fn_name, - vec![this_ptr, v.as_mut()], - None, - *pos, - ) + let mut args = [this_ptr, v.as_mut()]; + self.call_fn_raw(&set_fn_name, &mut args, None, *pos, 0) }) } @@ -787,29 +865,30 @@ impl Engine<'_> { dot_rhs: &Expr, new_val: (&mut Dynamic, Position), op_pos: Position, + level: usize, ) -> Result { match dot_lhs { // id.??? Expr::Variable(id, pos) => { - let (ScopeSource { idx, var_type, .. }, mut target) = - Self::search_scope(scope, id, Ok, *pos)?; + let (entry, mut target) = Self::search_scope(scope, id, Ok, *pos)?; - match var_type { - VariableType::Constant => { - return Err(EvalAltResult::ErrorAssignmentToConstant( - id.to_string(), - op_pos, - )) + match entry.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 this_ptr = target.as_mut(); + let val = self.set_dot_val_helper(scope, 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; + + val } - _ => (), } - - let val = self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val); - - // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - *scope.get_mut(id, idx) = target; - - val } // lhs[idx_expr].??? @@ -817,20 +896,21 @@ impl Engine<'_> { #[cfg(not(feature = "no_index"))] Expr::Index(lhs, idx_expr, op_pos) => { let (src_type, src, idx, mut target) = - self.eval_index_expr(scope, lhs, idx_expr, *op_pos)?; + self.eval_index_expr(scope, lhs, idx_expr, *op_pos, level)?; let val_pos = new_val.1; - let val = self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val); + let this_ptr = target.as_mut(); + let val = self.set_dot_val_helper(scope, 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.var_type { - VariableType::Constant => { + match src.typ { + ScopeEntryType::Constant => { return Err(EvalAltResult::ErrorAssignmentToConstant( src.name.to_string(), lhs.position(), )); } - VariableType::Normal => { + ScopeEntryType::Normal => { Self::update_indexed_var_in_scope( src_type, scope, @@ -854,7 +934,12 @@ impl Engine<'_> { } /// Evaluate an expression - fn eval_expr(&mut self, scope: &mut Scope, expr: &Expr) -> Result { + fn eval_expr( + &mut self, + scope: &mut Scope, + expr: &Expr, + level: usize, + ) -> Result { match expr { #[cfg(not(feature = "no_float"))] Expr::FloatConstant(f, _) => Ok(f.into_dynamic()), @@ -868,58 +953,59 @@ impl Engine<'_> { // 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) + .eval_index_expr(scope, lhs, idx_expr, *op_pos, level) .map(|(_, _, _, x)| x), // Statement block - Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt), + Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt, level), // lhs = rhs Expr::Assignment(lhs, rhs, op_pos) => { - let mut rhs_val = self.eval_expr(scope, rhs)?; + let mut rhs_val = self.eval_expr(scope, rhs, level)?; match lhs.as_ref() { // name = rhs - Expr::Variable(name, pos) => match scope.get(name) { - Some(( - ScopeSource { - idx, - var_type: VariableType::Normal, - .. - }, - _, - )) => { - *scope.get_mut(name, idx) = rhs_val.clone(); + Expr::Variable(name, pos) => match scope + .get(name) + .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(name.clone(), *pos))? + .0 + { + 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) } - Some(( - ScopeSource { - var_type: VariableType::Constant, - .. - }, - _, - )) => Err(EvalAltResult::ErrorAssignmentToConstant( + ScopeSource { + typ: ScopeEntryType::Constant, + .. + } => Err(EvalAltResult::ErrorAssignmentToConstant( name.to_string(), *op_pos, )), - _ => Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos)), }, // idx_lhs[idx_expr] = rhs #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, op_pos) => { let (src_type, src, idx, _) = - self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos)?; + self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?; if let Some(src) = src { - match src.var_type { - VariableType::Constant => { - return Err(EvalAltResult::ErrorAssignmentToConstant( + match src.typ { + ScopeEntryType::Constant => { + Err(EvalAltResult::ErrorAssignmentToConstant( src.name.to_string(), idx_lhs.position(), - )); + )) } - VariableType::Normal => Ok(Self::update_indexed_var_in_scope( + ScopeEntryType::Normal => Ok(Self::update_indexed_var_in_scope( src_type, scope, src, @@ -941,6 +1027,7 @@ impl Engine<'_> { dot_rhs, (&mut rhs_val, rhs.position()), *op_pos, + level, ), // Error assignment to constant @@ -954,15 +1041,15 @@ impl Engine<'_> { } } - Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, lhs, rhs), + Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, lhs, rhs, level), #[cfg(not(feature = "no_index"))] Expr::Array(contents, _) => { let mut arr = Vec::new(); - for item in contents { - arr.push(self.eval_expr(scope, item)?); - } + contents.into_iter().try_for_each(|item| { + self.eval_expr(scope, item, level).map(|val| arr.push(val)) + })?; Ok(Box::new(arr)) } @@ -975,43 +1062,35 @@ impl Engine<'_> { args: Some(vec![TypeId::of::()]), }; - engine.ext_functions.contains_key(&spec) - || engine - .script_functions - .binary_search_by(|f| f.compare(name, 1)) - .is_ok() + engine.functions.contains_key(&spec) || engine.fn_lib.has_function(name, 1) } match fn_name.as_str() { // Dump AST KEYWORD_DUMP_AST => { - let pos = if args_expr_list.len() == 0 { + 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 result = args_expr_list - .into_iter() + let mut result = args_expr_list + .iter() .map(|expr| format!("{:#?}", expr)) .collect::>() - .join("\n"); + .join("\n") + .into_dynamic(); // Redirect call to `print` - self.call_fn_raw( - KEYWORD_PRINT, - vec![result.into_dynamic().as_mut()], - None, - pos, - ) + self.call_fn_raw(KEYWORD_PRINT, &mut [result.as_mut()], None, pos, level) } // type_of KEYWORD_TYPE_OF if args_expr_list.len() == 1 && !has_override(self, KEYWORD_TYPE_OF) => { - let r = self.eval_expr(scope, &args_expr_list[0])?; + let r = self.eval_expr(scope, &args_expr_list[0], level)?; Ok(self .map_type_name((*r).type_name()) .to_string() @@ -1023,7 +1102,7 @@ impl Engine<'_> { if args_expr_list.len() == 1 && !has_override(self, KEYWORD_EVAL) => { let pos = args_expr_list[0].position(); - let r = self.eval_expr(scope, &args_expr_list[0])?; + let r = self.eval_expr(scope, &args_expr_list[0], level)?; let script = r.downcast_ref::() @@ -1049,41 +1128,36 @@ impl Engine<'_> { #[cfg(feature = "no_optimize")] let ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?; - return Ok(self.eval_ast_with_scope_raw(scope, true, &ast).map_err( - |mut err| { - err.set_position(pos); - err - }, - )?); + Ok(self + .eval_ast_with_scope_raw(scope, true, &ast) + .map_err(|err| err.set_position(pos))?) } // Normal function call _ => { let mut values = args_expr_list .iter() - .map(|expr| self.eval_expr(scope, expr)) - .collect::, _>>()?; + .map(|expr| self.eval_expr(scope, expr, level)) + .collect::, _>>()?; - self.call_fn_raw( - fn_name, - values.iter_mut().map(|b| b.as_mut()).collect(), - def_val.as_ref(), - *pos, - ) + let mut arg_values: Vec<_> = + values.iter_mut().map(Dynamic::as_mut).collect(); + + self.call_fn_raw(fn_name, &mut arg_values, def_val.as_ref(), *pos, level) } } } Expr::And(lhs, rhs) => Ok(Box::new( *self - .eval_expr(scope, &*lhs)? + .eval_expr(scope, &*lhs, level)? .downcast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) })? && // Short-circuit using && *self - .eval_expr(scope, &*rhs)? + .eval_expr(scope, &*rhs, level)? .downcast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) @@ -1092,14 +1166,14 @@ impl Engine<'_> { Expr::Or(lhs, rhs) => Ok(Box::new( *self - .eval_expr(scope, &*lhs)? + .eval_expr(scope, &*lhs, level)? .downcast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) })? || // Short-circuit using || *self - .eval_expr(scope, &*rhs)? + .eval_expr(scope, &*rhs, level)? .downcast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) @@ -1117,6 +1191,7 @@ impl Engine<'_> { &mut self, scope: &mut Scope, stmt: &Stmt, + level: usize, ) -> Result { match stmt { // No-op @@ -1124,7 +1199,7 @@ impl Engine<'_> { // Expression as statement Stmt::Expr(expr) => { - let result = self.eval_expr(scope, expr)?; + let result = self.eval_expr(scope, expr, level)?; Ok(if !matches!(expr.as_ref(), Expr::Assignment(_, _, _)) { result @@ -1137,15 +1212,10 @@ impl Engine<'_> { // Block scope Stmt::Block(block, _) => { let prev_len = scope.len(); - let mut result: Result = Ok(().into_dynamic()); - for stmt in block.iter() { - result = self.eval_stmt(scope, stmt); - - if result.is_err() { - break; - } - } + let result = block.iter().try_fold(().into_dynamic(), |_, stmt| { + self.eval_stmt(scope, stmt, level) + }); scope.rewind(prev_len); @@ -1154,14 +1224,14 @@ impl Engine<'_> { // If-else statement Stmt::IfThenElse(guard, if_body, else_body) => self - .eval_expr(scope, guard)? + .eval_expr(scope, guard, level)? .downcast::() .map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position())) .and_then(|guard_val| { if *guard_val { - self.eval_stmt(scope, if_body) + self.eval_stmt(scope, if_body, level) } else if let Some(stmt) = else_body { - self.eval_stmt(scope, stmt.as_ref()) + self.eval_stmt(scope, stmt.as_ref(), level) } else { Ok(().into_dynamic()) } @@ -1169,10 +1239,10 @@ impl Engine<'_> { // While loop Stmt::While(guard, body) => loop { - match self.eval_expr(scope, guard)?.downcast::() { + match self.eval_expr(scope, guard, level)?.downcast::() { Ok(guard_val) => { if *guard_val { - match self.eval_stmt(scope, body) { + match self.eval_stmt(scope, body, level) { Ok(_) => (), Err(EvalAltResult::ErrorLoopBreak(_)) => { return Ok(().into_dynamic()) @@ -1189,7 +1259,7 @@ impl Engine<'_> { // Loop statement Stmt::Loop(body) => loop { - match self.eval_stmt(scope, body) { + match self.eval_stmt(scope, body, level) { Ok(_) => (), Err(EvalAltResult::ErrorLoopBreak(_)) => return Ok(().into_dynamic()), Err(x) => return Err(x), @@ -1198,26 +1268,32 @@ impl Engine<'_> { // For loop Stmt::For(name, expr, body) => { - let arr = self.eval_expr(scope, expr)?; + let arr = self.eval_expr(scope, expr, level)?; let tid = Any::type_id(&*arr); if let Some(iter_fn) = self.type_iterators.get(&tid) { scope.push(name.clone(), ()); - let idx = scope.len() - 1; + + let entry = ScopeSource { + name, + index: scope.len() - 1, + typ: ScopeEntryType::Normal, + }; for a in iter_fn(&arr) { - *scope.get_mut(name, idx) = a; + *scope.get_mut(entry) = a; - match self.eval_stmt(scope, body) { + match self.eval_stmt(scope, body, level) { Ok(_) => (), Err(EvalAltResult::ErrorLoopBreak(_)) => break, Err(x) => return Err(x), } } - scope.pop(); + + scope.rewind(scope.len() - 1); Ok(().into_dynamic()) } else { - return Err(EvalAltResult::ErrorFor(expr.position())); + Err(EvalAltResult::ErrorFor(expr.position())) } } @@ -1230,9 +1306,10 @@ impl Engine<'_> { } // Return value - Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => { - Err(EvalAltResult::Return(self.eval_expr(scope, a)?, *pos)) - } + Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => Err(EvalAltResult::Return( + self.eval_expr(scope, a, level)?, + *pos, + )), // Empty throw Stmt::ReturnWithVal(None, ReturnType::Exception, pos) => { @@ -1241,19 +1318,19 @@ impl Engine<'_> { // Throw value Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => { - let val = self.eval_expr(scope, a)?; + let val = self.eval_expr(scope, a, level)?; Err(EvalAltResult::ErrorRuntime( val.downcast::() .map(|s| *s) - .unwrap_or("".to_string()), + .unwrap_or_else(|_| "".to_string()), *pos, )) } // Let statement Stmt::Let(name, Some(expr), _) => { - let val = self.eval_expr(scope, expr)?; - scope.push_dynamic(name.clone(), VariableType::Normal, val); + let val = self.eval_expr(scope, expr, level)?; + scope.push_dynamic_value(name.clone(), ScopeEntryType::Normal, val, false); Ok(().into_dynamic()) } @@ -1264,8 +1341,8 @@ impl Engine<'_> { // Const statement Stmt::Const(name, expr, _) if expr.is_constant() => { - let val = self.eval_expr(scope, expr)?; - scope.push_dynamic(name.clone(), VariableType::Constant, val); + let val = self.eval_expr(scope, expr, level)?; + scope.push_dynamic_value(name.clone(), ScopeEntryType::Constant, val, true); Ok(().into_dynamic()) } @@ -1283,7 +1360,7 @@ impl Engine<'_> { /// Clean up all script-defined functions within the `Engine`. pub fn clear_functions(&mut self) { - self.script_functions.clear(); + self.fn_lib.clear(); } } diff --git a/src/error.rs b/src/error.rs index 2826c465..4ec60ec9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,8 +17,6 @@ pub enum LexError { MalformedNumber(String), /// An character literal is in an invalid format. MalformedChar(String), - /// Error in the script text. - InputError(String), /// An identifier is in an invalid format. MalformedIdentifier(String), } @@ -35,7 +33,6 @@ impl fmt::Display for LexError { Self::MalformedIdentifier(s) => { write!(f, "Variable name is not in a legal format: '{}'", s) } - Self::InputError(s) => write!(f, "{}", s), Self::UnterminatedString => write!(f, "Open string is not terminated"), } } @@ -85,6 +82,9 @@ pub enum ParseErrorType { /// A function definition is missing the parameters list. Wrapped value is the function name. #[cfg(not(feature = "no_function"))] FnMissingParams(String), + /// A function definition has duplicated parameters. Wrapped values are the function name and parameter name. + #[cfg(not(feature = "no_function"))] + FnDuplicateParam(String, String), /// A function definition is missing the body. Wrapped value is the function name. #[cfg(not(feature = "no_function"))] FnMissingBody(String), @@ -98,16 +98,23 @@ pub enum ParseErrorType { LoopBreak, } +impl ParseErrorType { + /// Make a `ParseError` using the current type and position. + pub(crate) fn into_err(self, pos: Position) -> ParseError { + ParseError(self, pos) + } + + /// Make a `ParseError` using the current type and EOF position. + pub(crate) fn into_err_eof(self) -> ParseError { + ParseError(self, Position::eof()) + } +} + /// Error when parsing a script. #[derive(Debug, PartialEq, Clone)] pub struct ParseError(pub(crate) ParseErrorType, pub(crate) Position); impl ParseError { - /// Create a new `ParseError`. - pub(crate) fn new(err: ParseErrorType, pos: Position) -> Self { - Self(err, pos) - } - /// Get the parse error. pub fn error_type(&self) -> &ParseErrorType { &self.0 @@ -142,6 +149,8 @@ impl ParseError { #[cfg(not(feature = "no_function"))] ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration", #[cfg(not(feature = "no_function"))] + ParseErrorType::FnDuplicateParam(_,_) => "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", @@ -183,6 +192,11 @@ impl fmt::Display for ParseError { write!(f, "Expecting body statement block for function '{}'", s)? } + #[cfg(not(feature = "no_function"))] + ParseErrorType::FnDuplicateParam(ref s, ref arg) => { + write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)? + } + ParseErrorType::MissingRightParen(ref s) | ParseErrorType::MissingRightBrace(ref s) => { write!(f, "{} for {}", self.desc(), s)? } diff --git a/src/fn_register.rs b/src/fn_register.rs index fd67757a..df2d01d0 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -80,7 +80,8 @@ pub trait RegisterResultFn { /// // Normal function /// fn div(x: i64, y: i64) -> Result { /// if y == 0 { - /// Err("division by zero!".into()) // '.into()' automatically converts to 'EvalAltResult::ErrorRuntime' + /// // '.into()' automatically converts to 'EvalAltResult::ErrorRuntime' + /// Err("division by zero!".into()) /// } else { /// Ok(x / y) /// } @@ -97,9 +98,30 @@ pub trait RegisterResultFn { fn register_result_fn(&mut self, name: &str, f: FN); } -pub struct Ref(A); -pub struct Mut(A); +// These types are used to build a unique _marker_ tuple type for each combination +// of function parameter types in order to make each trait implementation unique. +// That is because stable Rust currently does not allow distinguishing implementations +// based purely on parameter types of traits (Fn, FnOnce and FnMut). +// +// For example: +// +// `RegisterFn, B, Ref), R>` +// +// will have the function prototype constraint to: +// +// `FN: (&mut A, B, &C) -> R` +// +// These types are not actually used anywhere. +pub struct Mut(T); +//pub struct Ref(T); +/// Identity dereferencing function. +#[inline] +fn identity(data: T) -> T { + data +} + +/// This macro counts the number of arguments via recursion. macro_rules! count_args { () => { 0_usize }; ( $head:ident $($tail:ident)* ) => { 1_usize + count_args!($($tail)*) }; @@ -110,6 +132,10 @@ macro_rules! def_register { def_register!(imp); }; (imp $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => { + // ^ function parameter generic type name + // ^ function parameter marker type (T, Ref or Mut) + // ^ function parameter actual type + // ^ dereferencing function impl< $($par: Any + Clone,)* FN: Fn($($param),*) -> RET + 'static, @@ -119,7 +145,7 @@ macro_rules! def_register { fn register_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); - let fun = move |mut args: FnCallArgs, pos: Position| { + let fun = move |args: &mut FnCallArgs, pos: Position| { // Check for length at the beginning to avoid per-element bound checks. const NUM_ARGS: usize = count_args!($($par)*); @@ -128,7 +154,7 @@ macro_rules! def_register { } #[allow(unused_variables, unused_mut)] - let mut drain = args.drain(..); + let mut drain = args.iter_mut(); $( // Downcast every element, return in case of a type mismatch let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); @@ -151,7 +177,7 @@ macro_rules! def_register { fn register_dynamic_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); - let fun = move |mut args: FnCallArgs, pos: Position| { + let fun = move |args: &mut FnCallArgs, pos: Position| { // Check for length at the beginning to avoid per-element bound checks. const NUM_ARGS: usize = count_args!($($par)*); @@ -160,7 +186,7 @@ macro_rules! def_register { } #[allow(unused_variables, unused_mut)] - let mut drain = args.drain(..); + let mut drain = args.iter_mut(); $( // Downcast every element, return in case of a type mismatch let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); @@ -183,7 +209,7 @@ macro_rules! def_register { fn register_result_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); - let fun = move |mut args: FnCallArgs, pos: Position| { + let fun = move |args: &mut FnCallArgs, pos: Position| { // Check for length at the beginning to avoid per-element bound checks. const NUM_ARGS: usize = count_args!($($par)*); @@ -192,7 +218,7 @@ macro_rules! def_register { } #[allow(unused_variables, unused_mut)] - let mut drain = args.drain(..); + let mut drain = args.iter_mut(); $( // Downcast every element, return in case of a type mismatch let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); @@ -200,10 +226,8 @@ macro_rules! def_register { // Call the user-supplied function using ($clone) to // potentially clone the value, otherwise pass the reference. - f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic).map_err(|mut err| { - err.set_position(pos); - err - }) + f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic) + .map_err(|err| err.set_position(pos)) }; self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } @@ -212,9 +236,13 @@ macro_rules! def_register { //def_register!(imp_pop $($par => $mark => $param),*); }; ($p0:ident $(, $p:ident)*) => { - def_register!(imp $p0 => $p0 => $p0 => Clone::clone $(, $p => $p => $p => Clone::clone)*); - def_register!(imp $p0 => Ref<$p0> => &$p0 => |x| { x } $(, $p => $p => $p => Clone::clone)*); - def_register!(imp $p0 => Mut<$p0> => &mut $p0 => |x| { x } $(, $p => $p => $p => Clone::clone)*); + 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) ^ + + // No support for functions where the first argument is a reference + //def_register!(imp $p0 => Ref<$p0> => &$p0 => identity $(, $p => $p => $p => Clone::clone)*); def_register!($($p),*); }; @@ -224,5 +252,5 @@ macro_rules! def_register { // }; } -#[cfg_attr(rustfmt, rustfmt_skip)] -def_register!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T); +#[rustfmt::skip] +def_register!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V); diff --git a/src/lib.rs b/src/lib.rs index 0344a1c7..5d7bc700 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ //! ```,ignore //! fn factorial(x) { //! if x == 1 { return 1; } -//! x * factorial(x - 1) +//! x * factorial(x - 1) //! } //! //! compute_something(factorial(10)) @@ -22,7 +22,7 @@ //! fn main() -> Result<(), EvalAltResult> //! { //! fn compute_something(x: i64) -> bool { -//! (x % 40) == 0 +//! (x % 40) == 0 //! } //! //! let mut engine = Engine::new(); @@ -63,7 +63,7 @@ pub use error::{ParseError, ParseErrorType}; pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; pub use parser::{Position, AST, INT}; pub use result::EvalAltResult; -pub use scope::{Scope, ScopeEntry, VariableType}; +pub use scope::Scope; #[cfg(not(feature = "no_index"))] pub use engine::Array; diff --git a/src/optimize.rs b/src/optimize.rs index e350cba2..c8dc32f7 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -2,11 +2,10 @@ use crate::any::{Any, Dynamic}; use crate::engine::{ - Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, - KEYWORD_TYPE_OF, + Engine, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; -use crate::scope::{Scope, ScopeEntry, VariableType}; +use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::stdlib::{ boxed::Box, @@ -181,16 +180,15 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - // Optimize each statement in the block let mut result: Vec<_> = block .into_iter() - .map(|stmt| { - if let Stmt::Const(name, value, pos) = stmt { - // Add constant into the state + .map(|stmt| match stmt { + // Add constant into the state + Stmt::Const(name, value, pos) => { state.push_constant(&name, *value); state.set_dirty(); Stmt::Noop(pos) // No need to keep constants - } else { - // Optimize the statement - optimize_stmt(stmt, state, preserve_result) } + // Optimize the statement + _ => optimize_stmt(stmt, state, preserve_result), }) .collect(); @@ -446,13 +444,13 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { && args.iter().all(|expr| expr.is_constant()) // all arguments are constants => { // First search in script-defined functions (can override built-in) - if state.engine.script_functions.binary_search_by(|f| f.compare(&id, args.len())).is_ok() { + if state.engine.fn_lib.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); } let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); - let call_args: FnCallArgs = arg_values.iter_mut().map(Dynamic::as_mut).collect(); + let mut call_args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); // Save the typename of the first argument if it is `type_of()` // This is to avoid `call_args` being passed into the closure @@ -462,7 +460,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { "" }; - state.engine.call_ext_fn_raw(&id, call_args, pos).ok().map(|r| + state.engine.call_ext_fn_raw(&id, &mut call_args, pos).ok().map(|r| r.or_else(|| { if !arg_for_type_of.is_empty() { // Handle `type_of()` @@ -512,9 +510,9 @@ pub(crate) fn optimize<'a>(statements: Vec, engine: &Engine<'a>, scope: &S // Add constants from the scope into the state scope .iter() - .filter(|ScopeEntry { var_type, expr, .. }| { + .filter(|ScopeEntry { typ, expr, .. }| { // Get all the constants with definite constant expressions - *var_type == VariableType::Constant + *typ == ScopeEntryType::Constant && expr.as_ref().map(Expr::is_constant).unwrap_or(false) }) .for_each(|ScopeEntry { name, expr, .. }| { @@ -566,7 +564,7 @@ pub(crate) fn optimize<'a>(statements: Vec, engine: &Engine<'a>, scope: &S // Add back the last statement unless it is a lone No-op if let Some(stmt) = last_stmt { - if result.len() > 0 || !matches!(stmt, Stmt::Noop(_)) { + if !result.is_empty() || !matches!(stmt, Stmt::Noop(_)) { result.push(stmt); } } @@ -591,29 +589,25 @@ pub fn optimize_into_ast( functions .into_iter() .map(|mut fn_def| { - match engine.optimization_level { - OptimizationLevel::None => (), - OptimizationLevel::Simple | OptimizationLevel::Full => { - let pos = fn_def.body.position(); + if engine.optimization_level != OptimizationLevel::None { + let pos = fn_def.body.position(); - // Optimize the function body - let mut body = optimize(vec![fn_def.body], engine, &Scope::new()); + // Optimize the function body + let mut body = optimize(vec![fn_def.body], engine, &Scope::new()); - // {} -> Noop - fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { - // { return val; } -> val - Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => { - Stmt::Expr(val) - } - // { return; } -> () - Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { - Stmt::Expr(Box::new(Expr::Unit(pos))) - } - // All others - stmt => stmt, - }; - } + // {} -> Noop + fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { + // { return val; } -> val + Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => Stmt::Expr(val), + // { return; } -> () + Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { + Stmt::Expr(Box::new(Expr::Unit(pos))) + } + // All others + stmt => stmt, + }; } + Arc::new(fn_def) }) .collect(), diff --git a/src/parser.rs b/src/parser.rs index 5d3b8407..5f53e3e7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,7 +3,7 @@ use crate::any::{Any, AnyExt, Dynamic}; use crate::engine::Engine; use crate::error::{LexError, ParseError, ParseErrorType}; -use crate::scope::{Scope, VariableType}; +use crate::scope::{EntryType as ScopeEntryType, Scope}; #[cfg(not(feature = "no_optimize"))] use crate::optimize::optimize_into_ast; @@ -11,10 +11,9 @@ use crate::optimize::optimize_into_ast; use crate::stdlib::{ borrow::Cow, boxed::Box, - char, - cmp::Ordering, - fmt, format, + char, fmt, format, iter::Peekable, + ops::Add, str::Chars, str::FromStr, string::{String, ToString}, @@ -77,9 +76,7 @@ impl Position { /// Get the character position (1-based), or `None` if at beginning of a line. pub fn position(&self) -> Option { - if self.is_none() || self.is_eof() { - None - } else if self.pos == 0 { + if self.is_none() || self.is_eof() || self.pos == 0 { None } else { Some(self.pos) @@ -164,6 +161,75 @@ impl fmt::Debug for Position { #[derive(Debug, Clone)] pub struct AST(pub(crate) Vec, pub(crate) Vec>); +impl AST { + /// Merge two `AST` into one. Both `AST`'s are consumed and a new, merged, version + /// is returned. + /// + /// The second `AST` is simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. + /// Of course, if the first `AST` uses a `return` statement at the end, then + /// the second `AST` will essentially be dead code. + /// + /// All script-defined functions in the second `AST` overwrite similarly-named functions + /// in the first `AST` with the same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// use rhai::Engine; + /// + /// let mut 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(2)"#)?; + /// + /// let ast = ast1.merge(ast2); // Merge 'ast2' into 'ast1' + /// + /// // Notice that using the '+' operator also works: + /// // let ast = ast1 + ast2; + /// + /// // 'ast' is essentially: + /// // + /// // fn foo(n) { "hello" + n } // <- definition of first 'foo' is overwritten + /// // foo(1) // <- notice this will be "hello1" instead of 43, + /// // // but it is no longer the return value + /// // foo(2) // returns "hello2" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast)?, "hello2"); + /// # Ok(()) + /// # } + /// ``` + pub fn merge(self, mut other: Self) -> Self { + let Self(mut ast, mut functions) = self; + + ast.append(&mut other.0); + + for fn_def in other.1 { + if let Some((n, _)) = functions + .iter() + .enumerate() + .find(|(_, f)| f.name == fn_def.name && f.params.len() == fn_def.params.len()) + { + functions[n] = fn_def; + } else { + functions.push(fn_def); + } + } + + Self(ast, functions) + } +} + +impl Add for AST { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + self.merge(rhs) + } +} + /// A script-function definition. #[derive(Debug, Clone)] pub struct FnDef { @@ -177,18 +243,6 @@ pub struct FnDef { pub pos: Position, } -impl FnDef { - /// Function to order two FnDef records, for binary search. - pub fn compare(&self, name: &str, params_len: usize) -> Ordering { - // First order by name - match self.name.as_str().cmp(name) { - // Then by number of parameters - Ordering::Equal => self.params.len().cmp(¶ms_len), - order => order, - } - } -} - /// `return`/`throw` statement. #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum ReturnType { @@ -516,12 +570,12 @@ pub enum Token { XOrAssign, ModuloAssign, PowerOfAssign, - LexError(LexError), + LexError(Box), } impl Token { /// Get the syntax of the token. - pub fn syntax<'a>(&'a self) -> Cow<'a, str> { + pub fn syntax(&self) -> Cow { use self::Token::*; match self { @@ -731,12 +785,12 @@ impl Token { /// An iterator on a `Token` stream. pub struct TokenIterator<'a> { - /// The last token seen. - last: Token, + /// Can the next token be a unary operator? + can_be_unary: bool, /// Current position. pos: Position, /// The input characters stream. - char_stream: Peekable>, + stream: Peekable>, } impl<'a> TokenIterator<'a> { @@ -766,7 +820,7 @@ impl<'a> TokenIterator<'a> { let mut escape = String::with_capacity(12); loop { - let next_char = self.char_stream.next(); + let next_char = self.stream.next(); self.advance(); match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? { @@ -809,7 +863,7 @@ impl<'a> TokenIterator<'a> { }; for _ in 0..len { - let c = self.char_stream.next().ok_or_else(|| { + let c = self.stream.next().ok_or_else(|| { (LERR::MalformedEscapeSequence(seq.to_string()), self.pos) })?; @@ -860,37 +914,38 @@ impl<'a> TokenIterator<'a> { fn inner_next(&mut self) -> Option<(Token, Position)> { let mut negated = false; - while let Some(c) = self.char_stream.next() { + while let Some(c) = self.stream.next() { self.advance(); let pos = self.pos; - match c { + match (c, self.stream.peek().copied().unwrap_or('\0')) { // \n - '\n' => self.new_line(), + ('\n', _) => self.new_line(), + // digit ... - '0'..='9' => { + ('0'..='9', _) => { let mut result = Vec::new(); let mut radix_base: Option = None; result.push(c); - while let Some(&next_char) = self.char_stream.peek() { + while let Some(&next_char) = self.stream.peek() { match next_char { '0'..='9' | '_' => { result.push(next_char); - self.char_stream.next(); + self.stream.next(); self.advance(); } #[cfg(not(feature = "no_float"))] '.' => { result.push(next_char); - self.char_stream.next(); + self.stream.next(); self.advance(); - while let Some(&next_char_in_float) = self.char_stream.peek() { + while let Some(&next_char_in_float) = self.stream.peek() { match next_char_in_float { '0'..='9' | '_' => { result.push(next_char_in_float); - self.char_stream.next(); + self.stream.next(); self.advance(); } _ => break, @@ -902,7 +957,7 @@ impl<'a> TokenIterator<'a> { if c == '0' => { result.push(next_char); - self.char_stream.next(); + self.stream.next(); self.advance(); let valid = match ch { @@ -928,13 +983,13 @@ impl<'a> TokenIterator<'a> { _ => panic!("unexpected character {}", ch), }); - while let Some(&next_char_in_hex) = self.char_stream.peek() { + while let Some(&next_char_in_hex) = self.stream.peek() { if !valid.contains(&next_char_in_hex) { break; } result.push(next_char_in_hex); - self.char_stream.next(); + self.stream.next(); self.advance(); } } @@ -955,7 +1010,9 @@ impl<'a> TokenIterator<'a> { INT::from_str_radix(&out, radix) .map(Token::IntegerConstant) .unwrap_or_else(|_| { - Token::LexError(LERR::MalformedNumber(result.iter().collect())) + Token::LexError(Box::new(LERR::MalformedNumber( + result.iter().collect(), + ))) }), pos, )); @@ -969,22 +1026,25 @@ impl<'a> TokenIterator<'a> { return Some(( num.unwrap_or_else(|_| { - Token::LexError(LERR::MalformedNumber(result.iter().collect())) + Token::LexError(Box::new(LERR::MalformedNumber( + result.iter().collect(), + ))) }), pos, )); } } - // letter ... - 'A'..='Z' | 'a'..='z' | '_' => { + + // letter or underscore ... + ('A'..='Z', _) | ('a'..='z', _) | ('_', _) => { let mut result = Vec::new(); result.push(c); - while let Some(&next_char) = self.char_stream.peek() { + while let Some(&next_char) = self.stream.peek() { match next_char { x if x.is_ascii_alphanumeric() || x == '_' => { result.push(x); - self.char_stream.next(); + self.stream.next(); self.advance(); } _ => break, @@ -1000,7 +1060,10 @@ impl<'a> TokenIterator<'a> { let identifier: String = result.iter().collect(); if !is_valid_identifier { - return Some((Token::LexError(LERR::MalformedIdentifier(identifier)), pos)); + return Some(( + Token::LexError(Box::new(LERR::MalformedIdentifier(identifier))), + pos, + )); } return Some(( @@ -1027,293 +1090,241 @@ impl<'a> TokenIterator<'a> { pos, )); } - // " - string literal - '"' => { - return match self.parse_string_literal('"') { - Ok(out) => Some((Token::StringConst(out), pos)), - Err(e) => Some((Token::LexError(e.0), e.1)), - } - } - // ' - character literal - '\'' => match self.parse_string_literal('\'') { - Ok(result) => { - let mut chars = result.chars(); - return Some(( - if let Some(first_char) = chars.next() { - if chars.count() != 0 { - Token::LexError(LERR::MalformedChar(format!("'{}'", result))) - } else { - Token::CharConstant(first_char) - } + // " - string literal + ('"', _) => { + return self.parse_string_literal('"').map_or_else( + |err| Some((Token::LexError(Box::new(err.0)), err.1)), + |out| Some((Token::StringConst(out), pos)), + ); + } + + // ' - character literal + ('\'', '\'') => { + return Some(( + Token::LexError(Box::new(LERR::MalformedChar("".to_string()))), + pos, + )); + } + ('\'', _) => { + return Some(self.parse_string_literal('\'').map_or_else( + |err| (Token::LexError(Box::new(err.0)), err.1), + |result| { + let mut chars = result.chars(); + let first = chars.next(); + + if chars.next().is_some() { + (Token::LexError(Box::new(LERR::MalformedChar(result))), pos) } else { - Token::LexError(LERR::MalformedChar(format!("'{}'", result))) - }, - pos, - )); - } - Err(e) => return Some((Token::LexError(e.0), e.1)), - }, + (Token::CharConstant(first.expect("should be Some")), pos) + } + }, + )); + } // Braces - '{' => return Some((Token::LeftBrace, pos)), - '}' => return Some((Token::RightBrace, pos)), + ('{', _) => return Some((Token::LeftBrace, pos)), + ('}', _) => return Some((Token::RightBrace, pos)), // Parentheses - '(' => return Some((Token::LeftParen, pos)), - ')' => return Some((Token::RightParen, pos)), + ('(', _) => return Some((Token::LeftParen, pos)), + (')', _) => return Some((Token::RightParen, pos)), // Indexing #[cfg(not(feature = "no_index"))] - '[' => return Some((Token::LeftBracket, pos)), + ('[', _) => return Some((Token::LeftBracket, pos)), #[cfg(not(feature = "no_index"))] - ']' => return Some((Token::RightBracket, pos)), + (']', _) => return Some((Token::RightBracket, pos)), // Operators - '+' => { - return Some(( - match self.char_stream.peek() { - Some(&'=') => { - self.char_stream.next(); - self.advance(); - Token::PlusAssign - } - _ if self.last.is_next_unary() => Token::UnaryPlus, - _ => Token::Plus, - }, - pos, - )) + ('+', '=') => { + self.stream.next(); + self.advance(); + return Some((Token::PlusAssign, pos)); } - '-' => match self.char_stream.peek() { - // Negative number? - Some('0'..='9') if self.last.is_next_unary() => negated = true, - Some('0'..='9') => return Some((Token::Minus, pos)), - Some('=') => { - self.char_stream.next(); + ('+', _) if self.can_be_unary => return Some((Token::UnaryPlus, pos)), + ('+', _) => return Some((Token::Plus, pos)), + + ('-', '0'..='9') if self.can_be_unary => negated = true, + ('-', '0'..='9') => return Some((Token::Minus, pos)), + ('-', '=') => { + self.stream.next(); + self.advance(); + return Some((Token::MinusAssign, pos)); + } + ('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)), + ('-', _) => return Some((Token::Minus, pos)), + + ('*', '=') => { + self.stream.next(); + self.advance(); + return Some((Token::MultiplyAssign, pos)); + } + ('*', _) => return Some((Token::Multiply, pos)), + + // Comments + ('/', '/') => { + self.stream.next(); + self.advance(); + + while let Some(c) = self.stream.next() { + if c == '\n' { + self.new_line(); + break; + } + self.advance(); - return Some((Token::MinusAssign, pos)); } - _ if self.last.is_next_unary() => return Some((Token::UnaryMinus, pos)), - _ => return Some((Token::Minus, pos)), - }, - '*' => { - return Some(( - match self.char_stream.peek() { - Some(&'=') => { - self.char_stream.next(); - self.advance(); - Token::MultiplyAssign - } - _ => Token::Multiply, - }, - pos, - )) } - '/' => match self.char_stream.peek() { - Some(&'/') => { - self.char_stream.next(); + ('/', '*') => { + let mut level = 1; + + self.stream.next(); + self.advance(); + + while let Some(c) = self.stream.next() { self.advance(); - while let Some(c) = self.char_stream.next() { - match c { - '\n' => { - self.new_line(); - break; + + match c { + '/' => { + if self.stream.next() == Some('*') { + level += 1; } - _ => self.advance(), + self.advance(); } + '*' => { + if self.stream.next() == Some('/') { + level -= 1; + } + self.advance(); + } + '\n' => self.new_line(), + _ => (), + } + + if level == 0 { + break; } } - Some(&'*') => { - let mut level = 1; - self.char_stream.next(); - self.advance(); - while let Some(c) = self.char_stream.next() { + } + + ('/', '=') => { + self.stream.next(); + self.advance(); + return Some((Token::DivideAssign, pos)); + } + ('/', _) => return Some((Token::Divide, pos)), + + (';', _) => return Some((Token::SemiColon, pos)), + (':', _) => return Some((Token::Colon, pos)), + (',', _) => return Some((Token::Comma, pos)), + ('.', _) => return Some((Token::Period, pos)), + + ('=', '=') => { + self.stream.next(); + self.advance(); + return Some((Token::EqualsTo, pos)); + } + ('=', _) => return Some((Token::Equals, pos)), + + ('<', '=') => { + self.stream.next(); + self.advance(); + return Some((Token::LessThanEqualsTo, pos)); + } + ('<', '<') => { + self.stream.next(); + self.advance(); + + return Some(( + if self.stream.peek() == Some(&'=') { + self.stream.next(); self.advance(); + Token::LeftShiftAssign + } else { + Token::LeftShift + }, + pos, + )); + } + ('<', _) => return Some((Token::LessThan, pos)), - match c { - '/' => { - if let Some('*') = self.char_stream.next() { - level += 1; - } - self.advance(); - } - '*' => { - if let Some('/') = self.char_stream.next() { - level -= 1; - } - self.advance(); - } - '\n' => self.new_line(), - _ => (), - } + ('>', '=') => { + self.stream.next(); + self.advance(); + return Some((Token::GreaterThanEqualsTo, pos)); + } + ('>', '>') => { + self.stream.next(); + self.advance(); - if level == 0 { - break; - } - } - } - Some(&'=') => { - self.char_stream.next(); - self.advance(); - return Some((Token::DivideAssign, pos)); - } - _ => return Some((Token::Divide, pos)), - }, - ';' => return Some((Token::SemiColon, pos)), - ':' => return Some((Token::Colon, pos)), - ',' => return Some((Token::Comma, pos)), - '.' => return Some((Token::Period, pos)), - '=' => match self.char_stream.peek() { - Some(&'=') => { - self.char_stream.next(); - self.advance(); - return Some((Token::EqualsTo, pos)); - } - _ => return Some((Token::Equals, pos)), - }, - '<' => match self.char_stream.peek() { - Some(&'=') => { - self.char_stream.next(); - self.advance(); - return Some((Token::LessThanEqualsTo, pos)); - } - Some(&'<') => { - self.char_stream.next(); - self.advance(); - return match self.char_stream.peek() { - Some(&'=') => { - self.char_stream.next(); - self.advance(); - Some((Token::LeftShiftAssign, pos)) - } - _ => { - self.char_stream.next(); - self.advance(); - Some((Token::LeftShift, pos)) - } - }; - } - _ => return Some((Token::LessThan, pos)), - }, - '>' => { return Some(( - match self.char_stream.peek() { - Some(&'=') => { - self.char_stream.next(); - self.advance(); - Token::GreaterThanEqualsTo - } - Some(&'>') => { - self.char_stream.next(); - self.advance(); - match self.char_stream.peek() { - Some(&'=') => { - self.char_stream.next(); - self.advance(); - Token::RightShiftAssign - } - _ => { - self.char_stream.next(); - self.advance(); - Token::RightShift - } - } - } - _ => Token::GreaterThan, + if self.stream.peek() == Some(&'=') { + self.stream.next(); + self.advance(); + Token::RightShiftAssign + } else { + Token::RightShift }, pos, - )) + )); } - '!' => { - return Some(( - match self.char_stream.peek() { - Some(&'=') => { - self.char_stream.next(); - self.advance(); - Token::NotEqualsTo - } - _ => Token::Bang, - }, - pos, - )) + ('>', _) => return Some((Token::GreaterThan, pos)), + + ('!', '=') => { + self.stream.next(); + self.advance(); + return Some((Token::NotEqualsTo, pos)); } - '|' => { - return Some(( - match self.char_stream.peek() { - Some(&'|') => { - self.char_stream.next(); - self.advance(); - Token::Or - } - Some(&'=') => { - self.char_stream.next(); - self.advance(); - Token::OrAssign - } - _ => Token::Pipe, - }, - pos, - )) + ('!', _) => return Some((Token::Bang, pos)), + + ('|', '|') => { + self.stream.next(); + self.advance(); + return Some((Token::Or, pos)); } - '&' => { - return Some(( - match self.char_stream.peek() { - Some(&'&') => { - self.char_stream.next(); - self.advance(); - Token::And - } - Some(&'=') => { - self.char_stream.next(); - self.advance(); - Token::AndAssign - } - _ => Token::Ampersand, - }, - pos, - )) + ('|', '=') => { + self.stream.next(); + self.advance(); + return Some((Token::OrAssign, pos)); } - '^' => { - return Some(( - match self.char_stream.peek() { - Some(&'=') => { - self.char_stream.next(); - self.advance(); - Token::XOrAssign - } - _ => Token::XOr, - }, - pos, - )) + ('|', _) => return Some((Token::Pipe, pos)), + + ('&', '&') => { + self.stream.next(); + self.advance(); + return Some((Token::And, pos)); } - '%' => { - return Some(( - match self.char_stream.peek() { - Some(&'=') => { - self.char_stream.next(); - self.advance(); - Token::ModuloAssign - } - _ => Token::Modulo, - }, - pos, - )) + ('&', '=') => { + self.stream.next(); + self.advance(); + return Some((Token::AndAssign, pos)); } - '~' => { - return Some(( - match self.char_stream.peek() { - Some(&'=') => { - self.char_stream.next(); - self.advance(); - Token::PowerOfAssign - } - _ => Token::PowerOf, - }, - pos, - )) + ('&', _) => return Some((Token::Ampersand, pos)), + + ('^', '=') => { + self.stream.next(); + self.advance(); + return Some((Token::XOrAssign, pos)); } - x if x.is_whitespace() => (), - x => return Some((Token::LexError(LERR::UnexpectedChar(x)), pos)), + ('^', _) => return Some((Token::XOr, pos)), + + ('%', '=') => { + self.stream.next(); + self.advance(); + return Some((Token::ModuloAssign, pos)); + } + ('%', _) => return Some((Token::Modulo, pos)), + + ('~', '=') => { + self.stream.next(); + self.advance(); + return Some((Token::PowerOfAssign, pos)); + } + ('~', _) => return Some((Token::PowerOf, pos)), + + (ch, _) if ch.is_whitespace() => (), + (ch, _) => return Some((Token::LexError(Box::new(LERR::UnexpectedChar(ch))), pos)), } } @@ -1324,10 +1335,10 @@ impl<'a> TokenIterator<'a> { impl<'a> Iterator for TokenIterator<'a> { type Item = (Token, Position); - // TODO - perhaps this could be optimized? fn next(&mut self) -> Option { self.inner_next().map(|x| { - self.last = x.0.clone(); + // Save the last token + self.can_be_unary = x.0.is_next_unary(); x }) } @@ -1336,9 +1347,9 @@ impl<'a> Iterator for TokenIterator<'a> { /// Tokenize an input text stream. pub fn lex(input: &str) -> TokenIterator<'_> { TokenIterator { - last: Token::LexError(LERR::InputError("".into())), + can_be_unary: true, pos: Position::new(1, 0), - char_stream: input.chars().peekable(), + stream: input.chars().peekable(), } } @@ -1348,13 +1359,9 @@ fn parse_paren_expr<'a>( begin: Position, allow_stmt_expr: bool, ) -> Result { - match input.peek() { - // () - Some((Token::RightParen, _)) => { - input.next(); - return Ok(Expr::Unit(begin)); - } - _ => (), + if matches!(input.peek(), Some((Token::RightParen, _))) { + input.next(); + return Ok(Expr::Unit(begin)); } let expr = parse_expr(input, allow_stmt_expr)?; @@ -1364,16 +1371,12 @@ fn parse_paren_expr<'a>( Some((Token::RightParen, _)) => Ok(expr), // ( xxx ??? Some((_, pos)) => { - return Err(ParseError::new( - PERR::MissingRightParen("a matching ( in the expression".into()), - pos, - )) + Err(PERR::MissingRightParen("a matching ( in the expression".into()).into_err(pos)) } // ( xxx - None => Err(ParseError::new( - PERR::MissingRightParen("a matching ( in the expression".into()), - Position::eof(), - )), + None => { + Err(PERR::MissingRightParen("a matching ( in the expression".into()).into_err_eof()) + } } } @@ -1388,13 +1391,11 @@ fn parse_call_expr<'a>( // id() if let (Token::RightParen, _) = input.peek().ok_or_else(|| { - ParseError::new( - PERR::MissingRightParen(format!( - "closing the arguments to call of function '{}'", - id - )), - Position::eof(), - ) + PERR::MissingRightParen(format!( + "closing the arguments to call of function '{}'", + id + )) + .into_err_eof() })? { input.next(); return Ok(Expr::FunctionCall(id, args_expr_list, None, begin)); @@ -1404,13 +1405,11 @@ fn parse_call_expr<'a>( args_expr_list.push(parse_expr(input, allow_stmt_expr)?); match input.peek().ok_or_else(|| { - ParseError::new( - PERR::MissingRightParen(format!( - "closing the arguments to call of function '{}'", - id - )), - Position::eof(), - ) + PERR::MissingRightParen(format!( + "closing the arguments to call of function '{}'", + id + )) + .into_err_eof() })? { (Token::RightParen, _) => { input.next(); @@ -1418,13 +1417,11 @@ fn parse_call_expr<'a>( } (Token::Comma, _) => (), (_, pos) => { - return Err(ParseError::new( - PERR::MissingComma(format!( - "separating the arguments to call of function '{}'", - id - )), - *pos, + return Err(PERR::MissingComma(format!( + "separating the arguments to call of function '{}'", + id )) + .into_err(*pos)) } } @@ -1446,84 +1443,69 @@ fn parse_index_expr<'a>( match &idx_expr { // lhs[int] Expr::IntegerConstant(i, pos) if *i < 0 => { - return Err(ParseError::new( - PERR::MalformedIndexExpr(format!( - "Array access expects non-negative index: {} < 0", - i - )), - *pos, + return Err(PERR::MalformedIndexExpr(format!( + "Array access expects non-negative index: {} < 0", + i )) + .into_err(*pos)) } // lhs[float] #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_, pos) => { - return Err(ParseError::new( - PERR::MalformedIndexExpr("Array access expects integer index, not a float".into()), - *pos, - )) + return Err(PERR::MalformedIndexExpr( + "Array access expects integer index, not a float".into(), + ) + .into_err(*pos)) } // lhs[char] Expr::CharConstant(_, pos) => { - return Err(ParseError::new( - PERR::MalformedIndexExpr( - "Array access expects integer index, not a character".into(), - ), - *pos, - )) + return Err(PERR::MalformedIndexExpr( + "Array access expects integer index, not a character".into(), + ) + .into_err(*pos)) } // lhs[string] Expr::StringConstant(_, pos) => { - return Err(ParseError::new( - PERR::MalformedIndexExpr("Array access expects integer index, not a string".into()), - *pos, - )) + return Err(PERR::MalformedIndexExpr( + "Array access expects integer index, not a string".into(), + ) + .into_err(*pos)) } // lhs[??? = ??? ], lhs[()] Expr::Assignment(_, _, pos) | Expr::Unit(pos) => { - return Err(ParseError::new( - PERR::MalformedIndexExpr("Array access expects integer index, not ()".into()), - *pos, - )) + return Err(PERR::MalformedIndexExpr( + "Array access expects integer index, not ()".into(), + ) + .into_err(*pos)) } // lhs[??? && ???], lhs[??? || ???] Expr::And(lhs, _) | Expr::Or(lhs, _) => { - return Err(ParseError::new( - PERR::MalformedIndexExpr( - "Array access expects integer index, not a boolean".into(), - ), - lhs.position(), - )) + 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) => { - return Err(ParseError::new( - PERR::MalformedIndexExpr( - "Array access expects integer index, not a boolean".into(), - ), - *pos, - )) + return Err(PERR::MalformedIndexExpr( + "Array access expects integer index, not a boolean".into(), + ) + .into_err(*pos)) } // All other expressions _ => (), } // Check if there is a closing bracket - match input.peek().ok_or_else(|| { - ParseError::new( - PERR::MissingRightBracket("index expression".into()), - Position::eof(), - ) - })? { + match input + .peek() + .ok_or_else(|| PERR::MissingRightBracket("index expression".into()).into_err_eof())? + { (Token::RightBracket, _) => { input.next(); - return Ok(Expr::Index(lhs, Box::new(idx_expr), pos)); - } - (_, pos) => { - return Err(ParseError::new( - PERR::MissingRightBracket("index expression".into()), - *pos, - )) + Ok(Expr::Index(lhs, Box::new(idx_expr), pos)) } + (_, pos) => Err(PERR::MissingRightBracket("index expression".into()).into_err(*pos)), } } @@ -1573,39 +1555,32 @@ fn parse_array_literal<'a>( arr.push(parse_expr(input, allow_stmt_expr)?); match input.peek().ok_or_else(|| { - ParseError( - PERR::MissingRightBracket("separating items in array literal".into()), - Position::eof(), - ) + PERR::MissingRightBracket("separating items in array literal".into()).into_err_eof() })? { (Token::Comma, _) => { input.next(); } (Token::RightBracket, _) => break, (_, pos) => { - return Err(ParseError( - PERR::MissingComma("separating items in array literal".into()), - *pos, - )) + return Err( + PERR::MissingComma("separating items in array literal".into()) + .into_err(*pos), + ) } } } } match input.peek().ok_or_else(|| { - ParseError::new( - PERR::MissingRightBracket("the end of array literal".into()), - Position::eof(), - ) + PERR::MissingRightBracket("the end of array literal".into()).into_err_eof() })? { (Token::RightBracket, _) => { input.next(); Ok(Expr::Array(arr, begin)) } - (_, pos) => Err(ParseError::new( - PERR::MissingRightBracket("the end of array literal".into()), - *pos, - )), + (_, pos) => { + Err(PERR::MissingRightBracket("the end of array literal".into()).into_err(*pos)) + } } } @@ -1616,7 +1591,7 @@ fn parse_primary<'a>( ) -> Result { let token = match input .peek() - .ok_or_else(|| ParseError::new(PERR::UnexpectedEOF, Position::eof()))? + .ok_or_else(|| PERR::UnexpectedEOF.into_err_eof())? { // { - block statement as expression (Token::LeftBrace, pos) if allow_stmt_expr => { @@ -1654,11 +1629,10 @@ fn parse_primary<'a>( } (Token::True, pos) => Ok(Expr::True(pos)), (Token::False, pos) => Ok(Expr::False(pos)), - (Token::LexError(err), pos) => Err(ParseError::new(PERR::BadInput(err.to_string()), pos)), - (token, pos) => Err(ParseError::new( - PERR::BadInput(format!("Unexpected '{}'", token.syntax())), - pos, - )), + (Token::LexError(err), pos) => Err(PERR::BadInput(err.to_string()).into_err(pos)), + (token, pos) => { + Err(PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(pos)) + } }?; if can_be_indexed { @@ -1681,8 +1655,16 @@ fn parse_unary<'a>( ) -> Result { match input .peek() - .ok_or_else(|| ParseError::new(PERR::UnexpectedEOF, Position::eof()))? + .ok_or_else(|| PERR::UnexpectedEOF.into_err_eof())? { + // If statement is allowed to act as expressions + (Token::If, pos) => { + let pos = *pos; + Ok(Expr::Stmt( + Box::new(parse_if(input, false, allow_stmt_expr)?), + pos, + )) + } // -expr (Token::UnaryMinus, pos) => { let pos = *pos; @@ -1702,10 +1684,8 @@ fn parse_unary<'a>( return None; }) .ok_or_else(|| { - ParseError::new( - PERR::BadInput(LERR::MalformedNumber(format!("-{}", i)).to_string()), - pos, - ) + PERR::BadInput(LERR::MalformedNumber(format!("-{}", i)).to_string()) + .into_err(pos) }), // Negative float @@ -1770,13 +1750,10 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result Some(ParseError::new( - match idx_lhs.as_ref() { - Expr::Index(_, _, _) => ParseErrorType::AssignmentToCopy, - _ => ParseErrorType::AssignmentToInvalidLHS, - }, - *pos, - )), + 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 Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() { @@ -1800,18 +1777,14 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result Some(ParseError::new( - ParseErrorType::AssignmentToCopy, - idx_lhs.position(), - )), + Expr::Index(idx_lhs, _, _) => { + Some(ParseErrorType::AssignmentToCopy.into_err(idx_lhs.position())) + } expr => panic!("unexpected dot expression {:#?}", expr), }, - _ => Some(ParseError::new( - ParseErrorType::AssignmentToInvalidLHS, - expr.position(), - )), + _ => Some(ParseErrorType::AssignmentToInvalidLHS.into_err(expr.position())), } } @@ -1977,12 +1950,7 @@ fn parse_binary_op<'a>( Token::ModuloAssign => parse_op_assignment("%", current_lhs, rhs, pos)?, Token::PowerOf => Expr::FunctionCall("~".into(), vec![current_lhs, rhs], None, pos), Token::PowerOfAssign => parse_op_assignment("~", current_lhs, rhs, pos)?, - token => { - return Err(ParseError::new( - PERR::UnknownOperator(token.syntax().into()), - pos, - )) - } + token => return Err(PERR::UnknownOperator(token.syntax().into()).into_err(pos)), }; } } @@ -1993,6 +1961,7 @@ fn parse_expr<'a>( input: &mut Peekable>, allow_stmt_expr: bool, ) -> Result { + // Parse a real expression let lhs = parse_unary(input, allow_stmt_expr)?; parse_binary_op(input, 1, lhs, allow_stmt_expr) } @@ -2004,10 +1973,10 @@ fn ensure_not_statement_expr<'a>( ) -> Result<(), ParseError> { match input .peek() - .ok_or_else(|| ParseError(PERR::ExprExpected(type_name.to_string()), Position::eof()))? + .ok_or_else(|| PERR::ExprExpected(type_name.to_string()).into_err_eof())? { // Disallow statement expressions - (Token::LeftBrace, pos) => Err(ParseError(PERR::ExprExpected(type_name.to_string()), *pos)), + (Token::LeftBrace, pos) => Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)), // No need to check for others at this time - leave it for the expr parser _ => Ok(()), } @@ -2090,25 +2059,20 @@ fn parse_for<'a>( // for name ... let name = match input .next() - .ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))? + .ok_or_else(|| PERR::VariableExpected.into_err_eof())? { // Variable name (Token::Identifier(s), _) => s, // Bad identifier - (Token::LexError(err), pos) => { - return Err(ParseError::new(PERR::BadInput(err.to_string()), pos)) - } + (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), // Not a variable name - (_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)), + (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; // for name in ... - match input - .next() - .ok_or_else(|| ParseError::new(PERR::MissingIn, Position::eof()))? - { + match input.next().ok_or_else(|| PERR::MissingIn.into_err_eof())? { (Token::In, _) => (), - (_, pos) => return Err(ParseError::new(PERR::MissingIn, pos)), + (_, pos) => return Err(PERR::MissingIn.into_err(pos)), } // for name in expr { body } @@ -2122,7 +2086,7 @@ fn parse_for<'a>( /// Parse a variable definition statement. fn parse_let<'a>( input: &mut Peekable>, - var_type: VariableType, + var_type: ScopeEntryType, allow_stmt_expr: bool, ) -> Result { // let/const... (specified in `var_type`) @@ -2131,13 +2095,11 @@ fn parse_let<'a>( // let name ... let (name, pos) = match input .next() - .ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))? + .ok_or_else(|| PERR::VariableExpected.into_err_eof())? { (Token::Identifier(s), pos) => (s, pos), - (Token::LexError(err), pos) => { - return Err(ParseError::new(PERR::BadInput(err.to_string()), pos)) - } - (_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)), + (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; // let name = ... @@ -2149,16 +2111,15 @@ fn parse_let<'a>( match var_type { // let name = expr - VariableType::Normal => Ok(Stmt::Let(name, Some(Box::new(init_value)), pos)), + ScopeEntryType::Normal => Ok(Stmt::Let(name, Some(Box::new(init_value)), pos)), // const name = { expr:constant } - VariableType::Constant if init_value.is_constant() => { + ScopeEntryType::Constant if init_value.is_constant() => { Ok(Stmt::Const(name, Box::new(init_value), pos)) } // const name = expr - error - VariableType::Constant => Err(ParseError( - PERR::ForbiddenConstantExpr(name.to_string()), - init_value.position(), - )), + ScopeEntryType::Constant => { + Err(PERR::ForbiddenConstantExpr(name).into_err(init_value.position())) + } } } else { // let name @@ -2175,10 +2136,10 @@ fn parse_block<'a>( // Must start with { let pos = match input .next() - .ok_or_else(|| ParseError::new(PERR::MissingLeftBrace, Position::eof()))? + .ok_or_else(|| PERR::MissingLeftBrace.into_err_eof())? { (Token::LeftBrace, pos) => pos, - (_, pos) => return Err(ParseError::new(PERR::MissingLeftBrace, pos)), + (_, pos) => return Err(PERR::MissingLeftBrace.into_err(pos)), }; let mut statements = Vec::new(); @@ -2208,28 +2169,20 @@ fn parse_block<'a>( // { ... stmt ??? - error Some((_, pos)) => { // Semicolons are not optional between statements - return Err(ParseError::new( - PERR::MissingSemicolon("terminating a statement".into()), - *pos, - )); + return Err(PERR::MissingSemicolon("terminating a statement".into()).into_err(*pos)); } } } - match input.peek().ok_or_else(|| { - ParseError::new( - PERR::MissingRightBrace("end of block".into()), - Position::eof(), - ) - })? { + match input + .peek() + .ok_or_else(|| PERR::MissingRightBrace("end of block".into()).into_err_eof())? + { (Token::RightBrace, _) => { input.next(); Ok(Stmt::Block(statements, pos)) } - (_, pos) => Err(ParseError::new( - PERR::MissingRightBrace("end of block".into()), - *pos, - )), + (_, pos) => Err(PERR::MissingRightBrace("end of block".into()).into_err(*pos)), } } @@ -2258,7 +2211,7 @@ fn parse_stmt<'a>( // fn ... #[cfg(not(feature = "no_function"))] - (Token::Fn, pos) => return Err(ParseError::new(PERR::WrongFnDefinition, *pos)), + (Token::Fn, pos) => Err(PERR::WrongFnDefinition.into_err(*pos)), (Token::If, _) => parse_if(input, breakable, allow_stmt_expr), (Token::While, _) => parse_while(input, allow_stmt_expr), @@ -2269,7 +2222,7 @@ fn parse_stmt<'a>( input.next(); Ok(Stmt::Break(pos)) } - (Token::Break, pos) => return Err(ParseError::new(PERR::LoopBreak, *pos)), + (Token::Break, pos) => Err(PERR::LoopBreak.into_err(*pos)), (token @ Token::Return, pos) | (token @ Token::Throw, pos) => { let return_type = match token { Token::Return => ReturnType::Return, @@ -2289,14 +2242,13 @@ fn parse_stmt<'a>( Some((_, _)) => { let expr = parse_expr(input, allow_stmt_expr)?; let pos = expr.position(); - Ok(Stmt::ReturnWithVal(Some(Box::new(expr)), return_type, pos)) } } } (Token::LeftBrace, _) => parse_block(input, breakable, allow_stmt_expr), - (Token::Let, _) => parse_let(input, VariableType::Normal, allow_stmt_expr), - (Token::Const, _) => parse_let(input, VariableType::Constant, allow_stmt_expr), + (Token::Let, _) => parse_let(input, ScopeEntryType::Normal, allow_stmt_expr), + (Token::Const, _) => parse_let(input, ScopeEntryType::Constant, allow_stmt_expr), _ => parse_expr_stmt(input, allow_stmt_expr), } } @@ -2311,20 +2263,20 @@ fn parse_fn<'a>( let name = match input .next() - .ok_or_else(|| ParseError::new(PERR::FnMissingName, Position::eof()))? + .ok_or_else(|| PERR::FnMissingName.into_err_eof())? { (Token::Identifier(s), _) => s, - (_, pos) => return Err(ParseError::new(PERR::FnMissingName, pos)), + (_, pos) => return Err(PERR::FnMissingName.into_err(pos)), }; match input .peek() - .ok_or_else(|| ParseError::new(PERR::FnMissingParams(name.clone()), Position::eof()))? + .ok_or_else(|| PERR::FnMissingParams(name.clone()).into_err_eof())? { (Token::LeftParen, _) => { input.next(); } - (_, pos) => return Err(ParseError::new(PERR::FnMissingParams(name), *pos)), + (_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)), } let mut params = Vec::new(); @@ -2332,72 +2284,57 @@ fn parse_fn<'a>( if matches!(input.peek(), Some((Token::RightParen, _))) { input.next(); } else { + let end_err = format!("closing the parameters list of function '{}'", name); + let sep_err = format!("separating the parameters of function '{}'", name); + loop { - match input.next().ok_or_else(|| { - ParseError::new( - PERR::MissingRightParen(format!( - "closing the parameters list of function '{}'", - name - )), - Position::eof(), - ) - })? { - (Token::Identifier(s), _) => { - params.push(s.into()); - } - (_, pos) => { - return Err(ParseError::new( - PERR::MissingRightParen(format!( - "closing the parameters list of function '{}'", - name - )), - pos, - )) + match input + .next() + .ok_or_else(|| PERR::MissingRightParen(end_err.to_string()).into_err_eof())? + { + (Token::Identifier(s), pos) => { + params.push((s, pos)); } + (_, pos) => return Err(PERR::MissingRightParen(end_err).into_err(pos)), } - match input.next().ok_or_else(|| { - ParseError::new( - PERR::MissingRightParen(format!( - "closing the parameters list of function '{}'", - name - )), - Position::eof(), - ) - })? { + match input + .next() + .ok_or_else(|| PERR::MissingRightParen(end_err.to_string()).into_err_eof())? + { (Token::RightParen, _) => break, (Token::Comma, _) => (), - (Token::Identifier(_), _) => { - return Err(ParseError::new( - PERR::MissingComma(format!( - "separating the parameters of function '{}'", - name - )), - pos, - )) - } - (_, pos) => { - return Err(ParseError::new( - PERR::MissingRightParen(format!( - "closing the parameters list of function '{}'", - name - )), - pos, - )) - } + (Token::Identifier(_), _) => return Err(PERR::MissingComma(sep_err).into_err(pos)), + (_, pos) => return Err(PERR::MissingRightParen(sep_err).into_err(pos)), } } } + // Check for duplicating parameters + params + .iter() + .enumerate() + .try_for_each(|(i, (p1, _))| { + params + .iter() + .skip(i + 1) + .find(|(p2, _)| p2 == p1) + .map_or_else(|| Ok(()), |(p2, pos)| Err((p2, *pos))) + }) + .map_err(|(p, pos)| { + PERR::FnDuplicateParam(name.to_string(), p.to_string()).into_err(pos) + })?; + + // Parse function body let body = match input.peek() { Some((Token::LeftBrace, _)) => parse_block(input, false, allow_stmt_expr)?, - Some((_, pos)) => return Err(ParseError::new(PERR::FnMissingBody(name), *pos)), - None => return Err(ParseError::new(PERR::FnMissingBody(name), Position::eof())), + Some((_, pos)) => return Err(PERR::FnMissingBody(name).into_err(*pos)), + None => return Err(PERR::FnMissingBody(name).into_err_eof()), }; Ok(FnDef { name, - params, + params: params.into_iter().map(|(p, _)| p).collect(), body, pos, }) @@ -2412,10 +2349,7 @@ pub fn parse_global_expr<'a, 'e>( if let Some((token, pos)) = input.peek() { // Return error if the expression doesn't end - return Err(ParseError::new( - PERR::BadInput(format!("Unexpected '{}'", token.syntax())), - *pos, - )); + return Err(PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(*pos)); } Ok( @@ -2474,10 +2408,7 @@ fn parse_global_level<'a>( // stmt ??? - error Some((_, pos)) => { // Semicolons are not optional between statements - return Err(ParseError::new( - PERR::MissingSemicolon("terminating a statement".into()), - *pos, - )); + return Err(PERR::MissingSemicolon("terminating a statement".into()).into_err(*pos)); } } } diff --git a/src/result.rs b/src/result.rs index 06f5f075..e96a29c7 100644 --- a/src/result.rs +++ b/src/result.rs @@ -60,6 +60,8 @@ pub enum EvalAltResult { ErrorDotExpr(String, Position), /// Arithmetic error encountered. Wrapped value is the error message. ErrorArithmetic(String, Position), + /// Call stack over maximum limit. + ErrorStackOverflow(Position), /// Run-time error encountered. Wrapped value is the error message. ErrorRuntime(String, Position), /// Breaking out of loops - not an error if within a loop. @@ -105,6 +107,7 @@ impl EvalAltResult { Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file", Self::ErrorDotExpr(_, _) => "Malformed dot expression", Self::ErrorArithmetic(_, _) => "Arithmetic error", + Self::ErrorStackOverflow(_) => "Stack overflow", Self::ErrorRuntime(_, _) => "Runtime error", Self::ErrorLoopBreak(_) => "Break statement not inside a loop", Self::Return(_, _) => "[Not Error] Function returns value", @@ -131,6 +134,7 @@ impl fmt::Display for EvalAltResult { Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos), Self::ErrorDotExpr(_, pos) => write!(f, "{} ({})", desc, pos), Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", s, pos), + Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos), Self::ErrorRuntime(s, pos) => { write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos) } @@ -230,13 +234,16 @@ impl EvalAltResult { | Self::ErrorMismatchOutputType(_, pos) | Self::ErrorDotExpr(_, pos) | Self::ErrorArithmetic(_, pos) + | Self::ErrorStackOverflow(pos) | Self::ErrorRuntime(_, pos) | Self::ErrorLoopBreak(pos) | Self::Return(_, pos) => *pos, } } - pub(crate) fn set_position(&mut self, new_position: Position) { + /// Consume the current `EvalAltResult` and return a new one + /// with the specified `Position`. + pub(crate) fn set_position(mut self, new_position: Position) -> Self { match self { #[cfg(not(feature = "no_std"))] Self::ErrorReadingScriptFile(_, _) => (), @@ -258,9 +265,12 @@ impl EvalAltResult { | Self::ErrorMismatchOutputType(_, ref mut pos) | Self::ErrorDotExpr(_, ref mut pos) | Self::ErrorArithmetic(_, ref mut pos) + | Self::ErrorStackOverflow(ref mut pos) | Self::ErrorRuntime(_, ref mut pos) | Self::ErrorLoopBreak(ref mut pos) | Self::Return(_, ref mut pos) => *pos = new_position, } + + self } } diff --git a/src/scope.rs b/src/scope.rs index a40a2589..bf66564d 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -10,32 +10,34 @@ use crate::stdlib::{ vec::Vec, }; -/// Type of a variable in the Scope. +/// Type of an entry in the Scope. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] -pub enum VariableType { - /// Normal variable. +pub enum EntryType { + /// Normal value. Normal, /// Immutable constant value. Constant, } /// An entry in the Scope. -pub struct ScopeEntry<'a> { - /// Name of the variable. +#[derive(Debug, Clone)] +pub struct Entry<'a> { + /// Name of the entry. pub name: Cow<'a, str>, - /// Type of the variable. - pub var_type: VariableType, - /// Current value of the variable. + /// Type of the entry. + pub typ: EntryType, + /// Current value of the entry. pub value: Dynamic, /// A constant expression if the initial value matches one of the recognized types. pub expr: Option, } /// Information about a particular entry in the Scope. -pub(crate) struct ScopeSource<'a> { +#[derive(Debug, Hash, Copy, Clone)] +pub(crate) struct EntryRef<'a> { pub name: &'a str, - pub idx: usize, - pub var_type: VariableType, + pub index: usize, + pub typ: EntryType, } /// A type containing information about the current scope. @@ -57,9 +59,9 @@ pub(crate) struct ScopeSource<'a> { /// # } /// ``` /// -/// When searching for variables, newly-added variables are found before similarly-named but older variables, -/// allowing for automatic _shadowing_ of variables. -pub struct Scope<'a>(Vec>); +/// When searching for entries, newly-added entries are found before similarly-named but older entries, +/// allowing for automatic _shadowing_. +pub struct Scope<'a>(Vec>); impl<'a> Scope<'a> { /// Create a new Scope. @@ -72,24 +74,24 @@ impl<'a> Scope<'a> { self.0.clear(); } - /// Get the number of variables inside the Scope. + /// Get the number of entries inside the Scope. pub fn len(&self) -> usize { self.0.len() } - /// Add (push) a new variable to the Scope. + /// Is the Scope empty? + pub fn is_empty(&self) -> bool { + self.0.len() == 0 + } + + /// Add (push) a new entry to the Scope. pub fn push>, T: Any + Clone>(&mut self, name: K, value: T) { - let value = value.into_dynamic(); + self.push_dynamic_value(name, EntryType::Normal, value.into_dynamic(), false); + } - // Map into constant expressions - //let (expr, value) = map_dynamic_to_expr(value, Position::none()); - - self.0.push(ScopeEntry { - name: name.into(), - var_type: VariableType::Normal, - value, - expr: None, - }); + /// Add (push) a new `Dynamic` entry to the Scope. + pub fn push_dynamic>>(&mut self, name: K, value: Dynamic) { + self.push_dynamic_value(name, EntryType::Normal, value, false); } /// Add (push) a new constant to the Scope. @@ -99,85 +101,75 @@ impl<'a> Scope<'a> { /// However, in order to be used for optimization, constants must be in one of the recognized types: /// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`. pub fn push_constant>, T: Any + Clone>(&mut self, name: K, value: T) { - let value = value.into_dynamic(); - - // Map into constant expressions - let (expr, value) = map_dynamic_to_expr(value, Position::none()); - - self.0.push(ScopeEntry { - name: name.into(), - var_type: VariableType::Constant, - value, - expr, - }); + self.push_dynamic_value(name, EntryType::Constant, value.into_dynamic(), true); } - /// Add (push) a new variable with a `Dynamic` value to the Scope. - pub(crate) fn push_dynamic>>( + /// Add (push) a new constant with a `Dynamic` value to the Scope. + /// + /// Constants are immutable and cannot be assigned to. Their values never change. + /// Constants propagation is a technique used to optimize an AST. + /// However, in order to be used for optimization, the `Dynamic` value must be in one of the + /// recognized types: + /// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`. + pub fn push_constant_dynamic>>(&mut self, name: K, value: Dynamic) { + self.push_dynamic_value(name, EntryType::Constant, value, true); + } + + /// Add (push) a new entry with a `Dynamic` value to the Scope. + pub(crate) fn push_dynamic_value>>( &mut self, name: K, - var_type: VariableType, + entry_type: EntryType, value: Dynamic, + map_expr: bool, ) { - let (expr, value) = map_dynamic_to_expr(value, Position::none()); + let (expr, value) = if map_expr { + map_dynamic_to_expr(value, Position::none()) + } else { + (None, value) + }; - self.0.push(ScopeEntry { + self.0.push(Entry { name: name.into(), - var_type, + typ: entry_type, value, expr, }); } - /// Remove (pop) the last variable from the Scope. - pub fn pop(&mut self) -> Option<(String, VariableType, Dynamic)> { - self.0.pop().map( - |ScopeEntry { - name, - var_type, - value, - .. - }| (name.to_string(), var_type, value), - ) - } - /// Truncate (rewind) the Scope to a previous size. pub fn rewind(&mut self, size: usize) { self.0.truncate(size); } - /// Does the scope contain the variable? + /// Does the scope contain the entry? pub fn contains(&self, key: &str) -> bool { self.0 .iter() .enumerate() .rev() // Always search a Scope in reverse order - .find(|(_, ScopeEntry { name, .. })| name == key) - .is_some() + .any(|(_, Entry { name, .. })| name == key) } - /// Find a variable in the Scope, starting from the last. - pub(crate) fn get(&self, key: &str) -> Option<(ScopeSource, Dynamic)> { + /// Find an entry in the Scope, starting from the last. + pub(crate) fn get(&self, key: &str) -> Option<(EntryRef, Dynamic)> { self.0 .iter() .enumerate() .rev() // Always search a Scope in reverse order - .find(|(_, ScopeEntry { name, .. })| name == key) + .find(|(_, Entry { name, .. })| name == key) .map( |( i, - ScopeEntry { - name, - var_type, - value, - .. + Entry { + name, typ, value, .. }, )| { ( - ScopeSource { + EntryRef { name: name.as_ref(), - idx: i, - var_type: *var_type, + index: i, + typ: *typ, }, value.clone(), ) @@ -185,54 +177,60 @@ impl<'a> Scope<'a> { ) } - /// Get the value of a variable in the Scope, starting from the last. + /// Get the value of an entry in the Scope, starting from the last. pub fn get_value(&self, key: &str) -> Option { self.0 .iter() .enumerate() .rev() // Always search a Scope in reverse order - .find(|(_, ScopeEntry { name, .. })| name == key) - .and_then(|(_, ScopeEntry { value, .. })| value.downcast_ref::()) + .find(|(_, Entry { name, .. })| name == key) + .and_then(|(_, Entry { value, .. })| value.downcast_ref::()) .map(T::clone) } - /// Get a mutable reference to a variable in the Scope. - pub(crate) fn get_mut(&mut self, name: &str, index: usize) -> &mut Dynamic { - let entry = self.0.get_mut(index).expect("invalid index in Scope"); + /// Get a mutable reference to an entry in the Scope. + pub(crate) fn get_mut(&mut self, key: EntryRef) -> &mut Dynamic { + let entry = self.0.get_mut(key.index).expect("invalid index in Scope"); + assert_eq!(entry.typ, key.typ, "entry type not matched"); // assert_ne!( - // entry.var_type, - // VariableType::Constant, - // "get mut of constant variable" + // entry.typ, + // EntryType::Constant, + // "get mut of constant entry" // ); - assert_eq!(entry.name, name, "incorrect key at Scope entry"); + assert_eq!(entry.name, key.name, "incorrect key at Scope entry"); &mut entry.value } - /// Get a mutable reference to a variable in the Scope and downcast it to a specific type - #[cfg(not(feature = "no_index"))] - pub(crate) fn get_mut_by_type(&mut self, name: &str, index: usize) -> &mut T { - self.get_mut(name, index) + /// Get a mutable reference to an entry in the Scope and downcast it to a specific type + pub(crate) fn get_mut_by_type(&mut self, key: EntryRef) -> &mut T { + self.get_mut(key) .downcast_mut::() .expect("wrong type cast") } - /// Get an iterator to variables in the Scope. - pub fn iter(&self) -> impl Iterator { + /// Get an iterator to entries in the Scope. + pub fn iter(&self) -> impl Iterator { self.0.iter().rev() // Always search a Scope in reverse order } } -impl<'a, K> iter::Extend<(K, VariableType, Dynamic)> for Scope<'a> +impl Default for Scope<'_> { + fn default() -> Self { + Scope::new() + } +} + +impl<'a, K> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a> where K: Into>, { - fn extend>(&mut self, iter: T) { + fn extend>(&mut self, iter: T) { self.0 - .extend(iter.into_iter().map(|(name, var_type, value)| ScopeEntry { + .extend(iter.into_iter().map(|(name, typ, value)| Entry { name: name.into(), - var_type, + typ, value, expr: None, })); diff --git a/tests/call_fn.rs b/tests/call_fn.rs index ca6303d6..39131fea 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,5 +1,22 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult, ParseErrorType, INT}; + +#[test] +fn test_fn() -> Result<(), EvalAltResult> { + let engine = Engine::new(); + + // Expect duplicated parameters error + match engine + .compile("fn hello(x, x) { x }") + .expect_err("should be error") + .error_type() + { + ParseErrorType::FnDuplicateParam(f, p) if f == "hello" && p == "x" => (), + _ => assert!(false, "wrong error"), + } + + Ok(()) +} #[test] fn test_call_fn() -> Result<(), EvalAltResult> { diff --git a/tests/expressions.rs b/tests/expressions.rs index d43d990a..3d42ed98 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -12,14 +12,64 @@ fn test_expressions() -> Result<(), EvalAltResult> { engine.eval_expression_with_scope::(&mut scope, "2 + (x + 10) * 2")?, 42 ); + assert_eq!( + engine.eval_expression_with_scope::(&mut scope, "if x > 0 { 42 } else { 123 }")?, + 42 + ); assert!(engine.eval_expression::<()>("40 + 2;").is_err()); assert!(engine.eval_expression::<()>("40 + { 2 }").is_err()); assert!(engine.eval_expression::<()>("x = 42").is_err()); assert!(engine.compile_expression("let x = 42").is_err()); - assert!(engine - .eval_expression_with_scope::(&mut scope, "if x > 0 { 42 } else { 123 }") - .is_err()); + + Ok(()) +} + +/// This example taken from https://github.com/jonathandturner/rhai/issues/115 +#[test] +fn test_expressions_eval() -> Result<(), EvalAltResult> { + #[derive(Debug, Clone)] + struct AGENT { + pub gender: String, + pub age: INT, + } + + impl AGENT { + pub fn get_gender(&mut self) -> String { + self.gender.clone() + } + pub fn get_age(&mut self) -> INT { + self.age + } + } + + // This is your agent + let my_agent = AGENT { + gender: "male".into(), + age: 42, + }; + + // Create the engine + let mut engine = Engine::new(); + + // Register your AGENT type + engine.register_type_with_name::("AGENT"); + engine.register_get("gender", AGENT::get_gender); + engine.register_get("age", AGENT::get_age); + + // Create your context, add the agent as a constant + let mut scope = Scope::new(); + scope.push_constant("agent", my_agent); + + // Evaluate the expression + let result: bool = engine.eval_expression_with_scope( + &mut scope, + r#" + agent.age > 10 && agent.gender == "male" + "#, + )?; + + assert_eq!(result, true); Ok(()) } diff --git a/tests/float.rs b/tests/float.rs index 8c57339c..688c095f 100644 --- a/tests/float.rs +++ b/tests/float.rs @@ -1,5 +1,7 @@ #![cfg(not(feature = "no_float"))] -use rhai::{Engine, EvalAltResult, RegisterFn}; +use rhai::{Engine, EvalAltResult, RegisterFn, FLOAT}; + +const EPSILON: FLOAT = 0.000_000_000_1; #[test] fn test_float() -> Result<(), EvalAltResult> { @@ -13,7 +15,7 @@ fn test_float() -> Result<(), EvalAltResult> { engine.eval::("let x = 0.0; let y = 1.0; x > y")?, false ); - assert_eq!(engine.eval::("let x = 9.9999; x")?, 9.9999); + assert!((engine.eval::("let x = 9.9999; x")? - 9.9999 as FLOAT).abs() < EPSILON); Ok(()) } @@ -51,13 +53,12 @@ fn struct_with_float() -> Result<(), EvalAltResult> { engine.register_fn("update", TestStruct::update); engine.register_fn("new_ts", TestStruct::new); - assert_eq!( - engine.eval::("let ts = new_ts(); ts.update(); ts.x")?, - 6.789 + assert!( + (engine.eval::("let ts = new_ts(); ts.update(); ts.x")? - 6.789).abs() < EPSILON ); - assert_eq!( - engine.eval::("let ts = new_ts(); ts.x = 10.1001; ts.x")?, - 10.1001 + assert!( + (engine.eval::("let ts = new_ts(); ts.x = 10.1001; ts.x")? - 10.1001).abs() + < EPSILON ); Ok(()) diff --git a/tests/if_block.rs b/tests/if_block.rs index e8ddb25b..4699ee57 100644 --- a/tests/if_block.rs +++ b/tests/if_block.rs @@ -27,3 +27,21 @@ fn test_if() -> Result<(), EvalAltResult> { Ok(()) } + +#[test] +fn test_if_expr() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + assert_eq!( + engine.eval::( + r" + let x = 42; + let y = 1 + if x > 40 { 100 } else { 0 } / x; + y + " + )?, + 3 + ); + + Ok(()) +} diff --git a/tests/math.rs b/tests/math.rs index 49b06e35..1bfdf521 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -13,7 +13,7 @@ fn test_math() -> Result<(), EvalAltResult> { #[cfg(not(feature = "only_i32"))] assert_eq!( engine.eval::("(-9223372036854775807).abs()")?, - 9223372036854775807 + 9_223_372_036_854_775_807 ); #[cfg(feature = "only_i32")] diff --git a/tests/method_call.rs b/tests/method_call.rs index 89e33138..9e217306 100644 --- a/tests/method_call.rs +++ b/tests/method_call.rs @@ -25,8 +25,10 @@ fn test_method_call() -> Result<(), EvalAltResult> { engine.register_fn("new_ts", TestStruct::new); let ts = engine.eval::("let x = new_ts(); x.update(); x")?; - assert_eq!(ts.x, 1001); + let ts = engine.eval::("let x = new_ts(); update(x); x")?; + assert_eq!(ts.x, 1); + Ok(()) } diff --git a/tests/power_of.rs b/tests/power_of.rs index c82e4d93..b8b83f08 100644 --- a/tests/power_of.rs +++ b/tests/power_of.rs @@ -4,7 +4,7 @@ use rhai::{Engine, EvalAltResult, INT}; use rhai::FLOAT; #[cfg(not(feature = "no_float"))] -const EPSILON: FLOAT = 0.0000000001; +const EPSILON: FLOAT = 0.000_000_000_1; #[test] fn test_power_of() -> Result<(), EvalAltResult> { @@ -16,11 +16,11 @@ fn test_power_of() -> Result<(), EvalAltResult> { #[cfg(not(feature = "no_float"))] { assert!( - (engine.eval::("2.2 ~ 3.3")? - 13.489468760533386 as FLOAT).abs() <= EPSILON + (engine.eval::("2.2 ~ 3.3")? - 13.489_468_760_533_386 as FLOAT).abs() <= EPSILON ); - assert_eq!(engine.eval::("2.0~-2.0")?, 0.25 as FLOAT); - assert_eq!(engine.eval::("(-2.0~-2.0)")?, 0.25 as FLOAT); - assert_eq!(engine.eval::("(-2.0~-2)")?, 0.25 as FLOAT); + assert!((engine.eval::("2.0~-2.0")? - 0.25 as FLOAT).abs() < EPSILON); + assert!((engine.eval::("(-2.0~-2.0)")? - 0.25 as FLOAT).abs() < EPSILON); + assert!((engine.eval::("(-2.0~-2)")? - 0.25 as FLOAT).abs() < EPSILON); assert_eq!(engine.eval::("4~3")?, 64); } @@ -37,20 +37,18 @@ fn test_power_of_equals() -> Result<(), EvalAltResult> { #[cfg(not(feature = "no_float"))] { assert!( - (engine.eval::("let x = 2.2; x ~= 3.3; x")? - 13.489468760533386 as FLOAT).abs() + (engine.eval::("let x = 2.2; x ~= 3.3; x")? - 13.489_468_760_533_386 as FLOAT) + .abs() <= EPSILON ); - assert_eq!( - engine.eval::("let x = 2.0; x ~= -2.0; x")?, - 0.25 as FLOAT + assert!( + (engine.eval::("let x = 2.0; x ~= -2.0; x")? - 0.25 as FLOAT).abs() < EPSILON ); - assert_eq!( - engine.eval::("let x = -2.0; x ~= -2.0; x")?, - 0.25 as FLOAT + assert!( + (engine.eval::("let x = -2.0; x ~= -2.0; x")? - 0.25 as FLOAT).abs() < EPSILON ); - assert_eq!( - engine.eval::("let x = -2.0; x ~= -2; x")?, - 0.25 as FLOAT + assert!( + (engine.eval::("let x = -2.0; x ~= -2; x")? - 0.25 as FLOAT).abs() < EPSILON ); assert_eq!(engine.eval::("let x =4; x ~= 3; x")?, 64); } diff --git a/tests/unit.rs b/tests/unit.rs index 5f54bb15..91e9d9f2 100644 --- a/tests/unit.rs +++ b/tests/unit.rs @@ -3,7 +3,7 @@ use rhai::{Engine, EvalAltResult}; #[test] fn test_unit() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::<()>("let x = (); x")?, ()); + engine.eval::<()>("let x = (); x")?; Ok(()) } @@ -17,6 +17,6 @@ fn test_unit_eq() -> Result<(), EvalAltResult> { #[test] fn test_unit_with_spaces() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert_eq!(engine.eval::<()>("let x = ( ); x")?, ()); + engine.eval::<()>("let x = ( ); x")?; Ok(()) } diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 7b573fd8..6b660d34 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -9,7 +9,7 @@ fn test_var_scope() -> Result<(), EvalAltResult> { assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 9); engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?; assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 12); - assert_eq!(engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?, ()); + engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?; assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 12); Ok(())