From 9aff10aca41d29839a2f6e440b4f74afb8c5d38c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 1 Apr 2020 10:28:07 +0800 Subject: [PATCH 01/20] Remove hard-wired version to num-traits. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 063abd0b..86cedc4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ keywords = [ "scripting" ] categories = [ "no-std", "embedded", "parser-implementations" ] [dependencies] -num-traits = "0.2.11" +num-traits = "*" [features] #default = ["no_function", "no_index", "no_object", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"] From 4ea2fb88ae4099e763a029fa01cf284aa6d3b4e7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 1 Apr 2020 16:22:18 +0800 Subject: [PATCH 02/20] Add continue statement. --- README.md | 19 ++++++++++++------- src/engine.rs | 32 ++++++++++++++------------------ src/parser.rs | 23 ++++++++++++++++++++--- src/result.rs | 13 ++++++++----- tests/looping.rs | 5 +++-- tests/while_loop.rs | 14 ++++++++++++-- 6 files changed, 69 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 969e727a..41776cd5 100644 --- a/README.md +++ b/README.md @@ -1242,9 +1242,10 @@ x == (); let x = 10; while x > 0 { + x = x - 1; + if x < 6 { continue; } // skip to the next iteration print(x); if x == 5 { break; } // break out of while loop - x = x - 1; } ``` @@ -1255,8 +1256,9 @@ Infinite `loop` let x = 10; loop { - print(x); x = x - 1; + if x > 5 { continue; } // skip to the next iteration + print(x); if x == 0 { break; } // break out of loop } ``` @@ -1271,14 +1273,16 @@ let array = [1, 3, 5, 7, 9, 42]; // Iterate through array for x in array { + if x > 10 { continue; } // skip to the next iteration print(x); - if x == 42 { break; } + if x == 42 { break; } // break out of for loop } // The 'range' function allows iterating from first to last-1 for x in range(0, 50) { + if x > 10 { continue; } // skip to the next iteration print(x); - if x == 42 { break; } + if x == 42 { break; } // break out of for loop } ``` @@ -1305,7 +1309,7 @@ if some_bad_condition_has_happened { throw; // defaults to empty exception text: "" ``` -Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(`_reason_`, `_position_`))` +Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(` _reason_ `,` _position_ `))` with the exception text captured by the first parameter. ```rust @@ -1354,7 +1358,8 @@ print(add2(42)); // prints 44 ### No access to external scope -Functions can only access their parameters. They cannot access external variables (even _global_ variables). +Functions are not _closures_. They do not capture the calling environment and can only access their own parameters. +They cannot access variables external to the function itself. ```rust let x = 42; @@ -1390,7 +1395,7 @@ fn add(x, y) { // The following will not compile fn do_addition(x) { - fn add_y(n) { // functions cannot be defined inside another function + fn add_y(n) { // <- syntax error: functions cannot be defined inside another function n + y } diff --git a/src/engine.rs b/src/engine.rs index b20cfabd..2d3e955d 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1349,19 +1349,12 @@ impl Engine<'_> { // While loop Stmt::While(guard, body) => loop { match self.eval_expr(scope, guard, level)?.downcast::() { - Ok(guard_val) => { - if *guard_val { - match self.eval_stmt(scope, body, level) { - Ok(_) => (), - Err(EvalAltResult::ErrorLoopBreak(_)) => { - return Ok(().into_dynamic()) - } - Err(x) => return Err(x), - } - } else { - return Ok(().into_dynamic()); - } - } + Ok(guard_val) if *guard_val => match self.eval_stmt(scope, body, level) { + Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), + Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()), + Err(x) => return Err(x), + }, + Ok(_) => return Ok(().into_dynamic()), Err(_) => return Err(EvalAltResult::ErrorLogicGuard(guard.position())), } }, @@ -1369,8 +1362,8 @@ impl Engine<'_> { // Loop statement Stmt::Loop(body) => loop { match self.eval_stmt(scope, body, level) { - Ok(_) => (), - Err(EvalAltResult::ErrorLoopBreak(_)) => return Ok(().into_dynamic()), + Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), + Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()), Err(x) => return Err(x), } }, @@ -1393,8 +1386,8 @@ impl Engine<'_> { *scope.get_mut(entry) = a; match self.eval_stmt(scope, body, level) { - Ok(_) => (), - Err(EvalAltResult::ErrorLoopBreak(_)) => break, + Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), + Err(EvalAltResult::ErrorLoopBreak(true, _)) => break, Err(x) => return Err(x), } } @@ -1406,8 +1399,11 @@ impl Engine<'_> { } } + // Continue statement + Stmt::Continue(pos) => Err(EvalAltResult::ErrorLoopBreak(false, *pos)), + // Break statement - Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(*pos)), + Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(true, *pos)), // Empty return Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { diff --git a/src/parser.rs b/src/parser.rs index f44660c4..c3d16f2e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -278,6 +278,8 @@ pub enum Stmt { Block(Vec, Position), /// { stmt } Expr(Box), + /// continue + Continue(Position), /// break Break(Position), /// `return`/`throw` @@ -292,6 +294,7 @@ impl Stmt { | Stmt::Let(_, _, pos) | Stmt::Const(_, _, pos) | Stmt::Block(_, pos) + | Stmt::Continue(pos) | Stmt::Break(pos) | Stmt::ReturnWithVal(_, _, pos) => *pos, Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), @@ -314,6 +317,7 @@ impl Stmt { Stmt::Let(_, _, _) | Stmt::Const(_, _, _) | Stmt::Expr(_) + | Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_, _, _) => false, } @@ -334,7 +338,7 @@ impl Stmt { Stmt::For(_, range, block) => range.is_pure() && block.is_pure(), Stmt::Let(_, _, _) | Stmt::Const(_, _, _) => false, Stmt::Block(statements, _) => statements.iter().all(Stmt::is_pure), - Stmt::Break(_) | Stmt::ReturnWithVal(_, _, _) => false, + Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_, _, _) => false, } } } @@ -579,6 +583,7 @@ pub enum Token { And, #[cfg(not(feature = "no_function"))] Fn, + Continue, Break, Return, Throw, @@ -653,6 +658,7 @@ impl Token { And => "&&", #[cfg(not(feature = "no_function"))] Fn => "fn", + Continue => "continue", Break => "break", Return => "return", Throw => "throw", @@ -1100,6 +1106,7 @@ impl<'a> TokenIterator<'a> { "else" => Token::Else, "while" => Token::While, "loop" => Token::Loop, + "continue" => Token::Continue, "break" => Token::Break, "return" => Token::Return, "throw" => Token::Throw, @@ -2419,6 +2426,8 @@ fn parse_stmt<'a>( // Semicolon - empty statement (Token::SemiColon, pos) => Ok(Stmt::Noop(*pos)), + (Token::LeftBrace, _) => parse_block(input, breakable, allow_stmt_expr), + // fn ... #[cfg(not(feature = "no_function"))] (Token::Fn, pos) => Err(PERR::WrongFnDefinition.into_err(*pos)), @@ -2427,12 +2436,19 @@ fn parse_stmt<'a>( (Token::While, _) => parse_while(input, allow_stmt_expr), (Token::Loop, _) => parse_loop(input, allow_stmt_expr), (Token::For, _) => parse_for(input, allow_stmt_expr), + + (Token::Continue, pos) if breakable => { + let pos = *pos; + input.next(); + Ok(Stmt::Continue(pos)) + } (Token::Break, pos) if breakable => { let pos = *pos; input.next(); Ok(Stmt::Break(pos)) } - (Token::Break, pos) => Err(PERR::LoopBreak.into_err(*pos)), + (Token::Continue, 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, @@ -2456,9 +2472,10 @@ fn parse_stmt<'a>( } } } - (Token::LeftBrace, _) => parse_block(input, breakable, 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), } } diff --git a/src/result.rs b/src/result.rs index 0c882609..474ae8b9 100644 --- a/src/result.rs +++ b/src/result.rs @@ -70,7 +70,9 @@ pub enum EvalAltResult { ErrorRuntime(String, Position), /// Breaking out of loops - not an error if within a loop. - ErrorLoopBreak(Position), + /// The wrapped value, if true, means breaking clean out of the loop (i.e. a `break` statement). + /// The wrapped value, if false, means breaking the current context (i.e. a `continue` statement). + ErrorLoopBreak(bool, Position), /// Not an error: Value returned from a script via the `return` keyword. /// Wrapped value is the result value. Return(Dynamic, Position), @@ -118,7 +120,8 @@ impl EvalAltResult { Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorStackOverflow(_) => "Stack overflow", Self::ErrorRuntime(_, _) => "Runtime error", - Self::ErrorLoopBreak(_) => "Break statement not inside a loop", + Self::ErrorLoopBreak(true, _) => "Break statement not inside a loop", + Self::ErrorLoopBreak(false, _) => "Continue statement not inside a loop", Self::Return(_, _) => "[Not Error] Function returns value", } } @@ -160,7 +163,7 @@ impl fmt::Display for EvalAltResult { Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", s, pos), - Self::ErrorLoopBreak(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorLoopBreak(_, pos) => write!(f, "{} ({})", desc, pos), Self::Return(_, pos) => write!(f, "{} ({})", desc, pos), Self::ErrorFunctionArgsMismatch(fn_name, 0, n, pos) => write!( @@ -255,7 +258,7 @@ impl EvalAltResult { | Self::ErrorArithmetic(_, pos) | Self::ErrorStackOverflow(pos) | Self::ErrorRuntime(_, pos) - | Self::ErrorLoopBreak(pos) + | Self::ErrorLoopBreak(_, pos) | Self::Return(_, pos) => *pos, } } @@ -287,7 +290,7 @@ impl EvalAltResult { | Self::ErrorArithmetic(_, ref mut pos) | Self::ErrorStackOverflow(ref mut pos) | Self::ErrorRuntime(_, ref mut pos) - | Self::ErrorLoopBreak(ref mut pos) + | Self::ErrorLoopBreak(_, ref mut pos) | Self::Return(_, ref mut pos) => *pos = new_position, } diff --git a/tests/looping.rs b/tests/looping.rs index 651cbe32..bf2c1a72 100644 --- a/tests/looping.rs +++ b/tests/looping.rs @@ -12,8 +12,9 @@ fn test_loop() -> Result<(), EvalAltResult> { loop { if i < 10 { + i += 1; + if x > 20 { continue; } x = x + i; - i = i + 1; } else { break; } @@ -22,7 +23,7 @@ fn test_loop() -> Result<(), EvalAltResult> { return x; " )?, - 45 + 21 ); Ok(()) diff --git a/tests/while_loop.rs b/tests/while_loop.rs index fa76ea17..18d5a03b 100644 --- a/tests/while_loop.rs +++ b/tests/while_loop.rs @@ -6,8 +6,18 @@ fn test_while() -> Result<(), EvalAltResult> { assert_eq!( engine.eval::( - "let x = 0; while x < 10 { x = x + 1; if x > 5 { \ - break } } x", + r" + let x = 0; + + while x < 10 { + x = x + 1; + if x > 5 { break; } + if x > 3 { continue; } + x = x + 3; + } + + x + ", )?, 6 ); From c4a51b139001cf301ee6dd1755a99a8168578fb7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 1 Apr 2020 22:56:54 +0800 Subject: [PATCH 03/20] Add append/mixin functions for arrays and maps. --- README.md | 32 +++++++++++++---------- src/builtin.rs | 19 ++++++++++++++ tests/arrays.rs | 39 +++++++++++++++++++++++++++- tests/maps.rs | 68 +++++++++++++++++++++++++++++++++++++------------ 4 files changed, 127 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 41776cd5..4a168273 100644 --- a/README.md +++ b/README.md @@ -981,15 +981,17 @@ Arrays are disabled via the [`no_index`] feature. The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on arrays: -| Function | Description | -| ---------- | ------------------------------------------------------------------------------------- | -| `push` | inserts an element at the end | -| `pop` | removes the last element and returns it ([`()`] if empty) | -| `shift` | removes the first element and returns it ([`()`] if empty) | -| `len` | returns the number of elements | -| `pad` | pads the array with an element until a specified length | -| `clear` | empties the array | -| `truncate` | cuts off the array at exactly a specified length (discarding all subsequent elements) | +| Function | Description | +| ------------ | ------------------------------------------------------------------------------------- | +| `push` | inserts an element at the end | +| `append` | concatenates the second array to the end of the first | +| `+` operator | concatenates the first array with the second | +| `pop` | removes the last element and returns it ([`()`] if empty) | +| `shift` | removes the first element and returns it ([`()`] if empty) | +| `len` | returns the number of elements | +| `pad` | pads the array with an element until a specified length | +| `clear` | empties the array | +| `truncate` | cuts off the array at exactly a specified length (discarding all subsequent elements) | Examples: @@ -1070,11 +1072,13 @@ Object maps are disabled via the [`no_object`] feature. The following functions (defined in the standard library but excluded if [`no_stdlib`]) operate on object maps: -| Function | Description | -| -------- | ------------------------------------------------------------ | -| `has` | does the object map contain a property of a particular name? | -| `len` | returns the number of properties | -| `clear` | empties the object map | +| Function | Description | +| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `has` | does the object map contain a property of a particular name? | +| `len` | returns the number of properties | +| `clear` | empties the object map | +| `mixin` | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) | +| `+` operator | merges the first object map with the second | Examples: diff --git a/src/builtin.rs b/src/builtin.rs index ae5feb13..d7f5840e 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -813,6 +813,14 @@ impl Engine<'_> { reg_fn3!(self, "pad", pad, &mut Array, INT, (), INT, bool, char); reg_fn3!(self, "pad", pad, &mut Array, INT, (), String, Array, ()); + self.register_fn("append", |list: &mut Array, array: Array| { + list.extend(array) + }); + self.register_fn("+", |mut list: Array, array: Array| { + list.extend(array); + list + }); + #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { @@ -853,6 +861,17 @@ impl Engine<'_> { self.register_fn("has", |map: &mut Map, prop: String| map.contains_key(&prop)); self.register_fn("len", |map: &mut Map| map.len() as INT); self.register_fn("clear", |map: &mut Map| map.clear()); + self.register_fn("mixin", |map1: &mut Map, map2: Map| { + map2.into_iter().for_each(|(key, value)| { + map1.insert(key, value); + }); + }); + self.register_fn("+", |mut map1: Map, map2: Map| { + map2.into_iter().for_each(|(key, value)| { + map1.insert(key, value); + }); + map1 + }); } // Register string concatenate functions diff --git a/tests/arrays.rs b/tests/arrays.rs index 5cfec145..1a7517ae 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_index"))] -use rhai::{Engine, EvalAltResult, RegisterFn, INT}; +use rhai::{Array, Engine, EvalAltResult, RegisterFn, INT}; #[test] fn test_arrays() -> Result<(), EvalAltResult> { @@ -12,6 +12,43 @@ fn test_arrays() -> Result<(), EvalAltResult> { '3' ); + #[cfg(not(feature = "no_stdlib"))] + { + assert_eq!( + engine.eval::( + r" + let x = [1, 2, 3]; + let y = [4, 5]; + x.append(y); + x.len() + " + )?, + 5 + ); + assert_eq!( + engine.eval::( + r" + let x = [1, 2, 3]; + x += [4, 5]; + x.len() + " + )?, + 5 + ); + assert_eq!( + engine + .eval::( + r" + let x = [1, 2, 3]; + let y = [4, 5]; + x + y + " + )? + .len(), + 5 + ); + } + Ok(()) } diff --git a/tests/maps.rs b/tests/maps.rs index 0da9b589..bb8c90cf 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -7,29 +7,65 @@ fn test_map_indexing() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); #[cfg(not(feature = "no_index"))] - assert_eq!( - engine.eval::(r#"let x = #{a: 1, b: 2, c: 3}; x["b"]"#)?, - 2 - ); + { + assert_eq!( + engine.eval::(r#"let x = #{a: 1, b: 2, c: 3}; x["b"]"#)?, + 2 + ); + assert_eq!( + engine.eval::( + r#" + let y = #{d: 1, "e": #{a: 42, b: 88, "": "hello"}, " 123 xyz": 9}; + y.e[""][4] + "# + )?, + 'o' + ); + } assert_eq!( engine.eval::("let y = #{a: 1, b: 2, c: 3}; y.a = 5; y.a")?, 5 ); - - #[cfg(not(feature = "no_index"))] - assert_eq!( - engine.eval::( - r#" - let y = #{d: 1, "e": #{a: 42, b: 88, "": "hello"}, " 123 xyz": 9}; - y.e[""][4] - "# - )?, - 'o' - ); - engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?; + #[cfg(not(feature = "no_stdlib"))] + { + assert_eq!( + engine.eval::( + r" + let x = #{a: 1, b: 2, c: 3}; + let y = #{b: 42, d: 9}; + x.mixin(y); + x.len() + x.b + " + )?, + 46 + ); + assert_eq!( + engine.eval::( + r" + let x = #{a: 1, b: 2, c: 3}; + x += #{b: 42, d: 9}; + x.len() + x.b + " + )?, + 46 + ); + assert_eq!( + engine + .eval::( + r" + let x = #{a: 1, b: 2, c: 3}; + let y = #{b: 42, d: 9}; + x + y + " + )? + .len(), + 4 + ); + } + Ok(()) } From 246f5fbbe60b21a4b268e1636d5e3654165308ba Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 2 Apr 2020 12:18:22 +0800 Subject: [PATCH 04/20] Rename `downcast` to `try_cast` and add `cast` for Dynamic. --- README.md | 62 +++++++++++++++++++++++++++++++++++++++++++++------ src/any.rs | 34 ++++++++++++++++++++++++---- src/api.rs | 36 ++++++++++++------------------ src/engine.rs | 48 +++++++++++++++++++-------------------- src/parser.rs | 44 ++++++++---------------------------- tests/maps.rs | 28 +++++++++++------------ 6 files changed, 145 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 4a168273..fa033138 100644 --- a/README.md +++ b/README.md @@ -279,7 +279,6 @@ The following primitive types are supported natively: | **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | | **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` | `""` _(empty string)_ | -[`Dynamic`]: #values-and-types [`()`]: #values-and-types All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust. @@ -313,6 +312,55 @@ if type_of(x) == "string" { } ``` +Dynamic values +-------------- + +[`Dynamic`]: #dynamic-values + +A `Dynamic` value can be _any_ type. + +Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific +actions based on the actual value's type. + +```rust +let mystery = get_some_dynamic_value(); + +if type_of(mystery) == "i64" { + print("Hey, I got an integer here!"); +} else if type_of(mystery) == "f64" { + print("Hey, I got a float here!"); +} else if type_of(mystery) == "string" { + print("Hey, I got a string here!"); +} else if type_of(mystery) == "bool" { + print("Hey, I got a boolean here!"); +} else if type_of(mystery) == "array" { + print("Hey, I got an array here!"); +} else if type_of(mystery) == "map" { + print("Hey, I got an object map here!"); +} else if type_of(mystery) == "TestStruct" { + print("Hey, I got the TestStruct custom type here!"); +} else { + print("I don't know what this is: " + type_of(mystery)); +} +``` + +In Rust, sometimes a `Dynamic` forms part of the return value - a good example is elements within an `Array` which are `Dynamic`, +or property values in an object map. In order to get the _real_ value, the actual value type _must_ be known in advance. +There is no easy way for Rust to detect, at run-time, what type the `Dynamic` value is. + +To use a `Dynamic` value in Rust, use the `cast` function to convert the value into a specific, known type. +Alternatively, use the `try_cast` function which does not panic but returns an error when the cast fails. + +```rust +let list: Array = engine.eval("...")?; // return type is 'Array' +let item = list[0]; // an element in an 'Array' is 'Dynamic' + +let value = item.cast::(); // if the element is 'i64', this succeeds; otherwise it panics +let value: i64 = item.cast(); // type can also be inferred + +let value = item.try_cast::()?; // 'try_cast' does not panic when the cast fails, but returns an error +``` + Value conversions ----------------- @@ -325,11 +373,11 @@ That's about it. For other conversions, register custom conversion functions. ```rust let x = 42; -let y = x * 100.0; // <- error: cannot multiply i64 with f64 -let y = x.to_float() * 100.0; // works -let z = y.to_int() + x; // works +let y = x * 100.0; // <- error: cannot multiply i64 with f64 +let y = x.to_float() * 100.0; // works +let z = y.to_int() + x; // works -let c = 'X'; // character +let c = 'X'; // character print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88" ``` @@ -1642,9 +1690,9 @@ Function volatility considerations --------------------------------- Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external -environment and not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value! +environment and is not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value! The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments -it will eagerly run execute the function call. This causes the script to behave differently from the intended semantics because +it will eagerly execute the function call. This causes the script to behave differently from the intended semantics because essentially the result of the function call will always be the same value. Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions. diff --git a/src/any.rs b/src/any.rs index e92b3e9d..befadcce 100644 --- a/src/any.rs +++ b/src/any.rs @@ -89,7 +89,14 @@ impl Clone for Dynamic { /// An extension trait that allows down-casting a `Dynamic` value to a specific type. pub trait AnyExt: Sized { /// Get a copy of a `Dynamic` value as a specific type. - fn downcast(self) -> Result, Self>; + fn try_cast(self) -> Result; + + /// Get a copy of a `Dynamic` value as a specific type. + /// + /// # Panics + /// + /// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type). + fn cast(self) -> T; /// This trait may only be implemented by `rhai`. #[doc(hidden)] @@ -106,19 +113,38 @@ impl AnyExt for Dynamic { /// /// let x: Dynamic = 42_u32.into_dynamic(); /// - /// assert_eq!(*x.downcast::().unwrap(), 42); + /// assert_eq!(x.try_cast::().unwrap(), 42); /// ``` - fn downcast(self) -> Result, Self> { + fn try_cast(self) -> Result { if self.is::() { unsafe { let raw: *mut Variant = Box::into_raw(self); - Ok(Box::from_raw(raw as *mut T)) + Ok(*Box::from_raw(raw as *mut T)) } } else { Err(self) } } + /// Get a copy of the `Dynamic` value as a specific type. + /// + /// # Panics + /// + /// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type). + /// + /// # Example + /// + /// ``` + /// use rhai::{Dynamic, Any, AnyExt}; + /// + /// let x: Dynamic = 42_u32.into_dynamic(); + /// + /// assert_eq!(x.cast::(), 42); + /// ``` + fn cast(self) -> T { + self.try_cast::().expect("cast failed") + } + fn _closed(&self) -> _Private { _Private } diff --git a/src/api.rs b/src/api.rs index 7e010192..b3acfa79 100644 --- a/src/api.rs +++ b/src/api.rs @@ -692,8 +692,7 @@ impl<'e> Engine<'e> { ast: &AST, ) -> Result { self.eval_ast_with_scope_raw(scope, false, ast)? - .downcast::() - .map(|v| *v) + .try_cast::() .map_err(|a| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name((*a).type_name()).to_string(), @@ -735,9 +734,8 @@ impl<'e> Engine<'e> { /// Evaluate a file, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run. + /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. #[cfg(not(feature = "no_std"))] pub fn consume_file( &mut self, @@ -750,9 +748,8 @@ impl<'e> Engine<'e> { /// Evaluate a file with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run. + /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. #[cfg(not(feature = "no_std"))] pub fn consume_file_with_scope( &mut self, @@ -767,9 +764,8 @@ impl<'e> Engine<'e> { /// Evaluate a string, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. + /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> { self.consume_with_scope(&mut Scope::new(), retain_functions, input) } @@ -777,9 +773,8 @@ impl<'e> Engine<'e> { /// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. + /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. pub fn consume_with_scope( &mut self, scope: &mut Scope, @@ -797,9 +792,8 @@ impl<'e> Engine<'e> { /// Evaluate an AST, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. + /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> { self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast) } @@ -807,9 +801,8 @@ impl<'e> Engine<'e> { /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. + /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. pub fn consume_ast_with_scope( &mut self, scope: &mut Scope, @@ -884,8 +877,7 @@ impl<'e> Engine<'e> { let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)? - .downcast() - .map(|b| *b) + .try_cast() .map_err(|a| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name((*a).type_name()).into(), diff --git a/src/engine.rs b/src/engine.rs index 2d3e955d..5230f5b8 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -628,9 +628,9 @@ impl Engine<'_> { // val_array[idx] if let Some(arr) = val.downcast_ref::() { - let idx = *self + let idx = self .eval_expr(scope, idx_expr, level)? - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; return if idx >= 0 { @@ -647,9 +647,9 @@ impl Engine<'_> { { // val_map[idx] if let Some(map) = val.downcast_ref::() { - let idx = *self + let idx = self .eval_expr(scope, idx_expr, level)? - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?; return Ok(( @@ -662,9 +662,9 @@ impl Engine<'_> { // val_string[idx] if let Some(s) = val.downcast_ref::() { - let idx = *self + let idx = self .eval_expr(scope, idx_expr, level)? - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; return if idx >= 0 { @@ -795,9 +795,9 @@ impl Engine<'_> { let s = scope.get_mut_by_type::(src); let pos = new_val.1; // Value must be a character - let ch = *new_val + let ch = new_val .0 - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; Self::str_replace_char(s, idx.as_num(), ch); Ok(().into_dynamic()) @@ -830,8 +830,8 @@ impl Engine<'_> { if let Some(s) = target.downcast_mut::() { // Value must be a character - let ch = *new_val - .downcast::() + let ch = new_val + .try_cast::() .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; Self::str_replace_char(s, idx.as_num(), ch); return Ok(target); @@ -1258,32 +1258,32 @@ impl Engine<'_> { } Expr::And(lhs, rhs) => Ok(Box::new( - *self + self .eval_expr(scope, &*lhs, level)? - .downcast::() + .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) })? && // Short-circuit using && - *self + self .eval_expr(scope, &*rhs, level)? - .downcast::() + .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) })?, )), Expr::Or(lhs, rhs) => Ok(Box::new( - *self + self .eval_expr(scope, &*lhs, level)? - .downcast::() + .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) })? || // Short-circuit using || - *self + self .eval_expr(scope, &*rhs, level)? - .downcast::() + .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) })?, @@ -1334,10 +1334,10 @@ impl Engine<'_> { // If-else statement Stmt::IfThenElse(guard, if_body, else_body) => self .eval_expr(scope, guard, level)? - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position())) .and_then(|guard_val| { - if *guard_val { + if guard_val { self.eval_stmt(scope, if_body, level) } else if let Some(stmt) = else_body { self.eval_stmt(scope, stmt.as_ref(), level) @@ -1348,8 +1348,8 @@ impl Engine<'_> { // While loop Stmt::While(guard, body) => loop { - match self.eval_expr(scope, guard, level)?.downcast::() { - Ok(guard_val) if *guard_val => match self.eval_stmt(scope, body, level) { + match self.eval_expr(scope, guard, level)?.try_cast::() { + Ok(guard_val) if guard_val => match self.eval_stmt(scope, body, level) { Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()), Err(x) => return Err(x), @@ -1425,9 +1425,7 @@ impl Engine<'_> { Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => { let val = self.eval_expr(scope, a, level)?; Err(EvalAltResult::ErrorRuntime( - val.downcast::() - .map(|s| *s) - .unwrap_or_else(|_| "".to_string()), + val.try_cast::().unwrap_or_else(|_| "".to_string()), *pos, )) } diff --git a/src/parser.rs b/src/parser.rs index c3d16f2e..d164d35e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2673,41 +2673,21 @@ pub fn parse<'a, 'e>( pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option, Dynamic) { if value.is::() { let value2 = value.clone(); - ( - Some(Expr::IntegerConstant( - *value.downcast::().expect("value should be INT"), - pos, - )), - value2, - ) + (Some(Expr::IntegerConstant(value.cast(), pos)), value2) } else if value.is::() { let value2 = value.clone(); - ( - Some(Expr::CharConstant( - *value.downcast::().expect("value should be char"), - pos, - )), - value2, - ) + (Some(Expr::CharConstant(value.cast(), pos)), value2) } else if value.is::() { let value2 = value.clone(); - ( - Some(Expr::StringConstant( - *value.downcast::().expect("value should be String"), - pos, - )), - value2, - ) + (Some(Expr::StringConstant(value.cast(), pos)), value2) } else if value.is::() { let value2 = value.clone(); ( - Some( - if *value.downcast::().expect("value should be bool") { - Expr::True(pos) - } else { - Expr::False(pos) - }, - ), + Some(if value.cast::() { + Expr::True(pos) + } else { + Expr::False(pos) + }), value2, ) } else { @@ -2715,13 +2695,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option, Dyna { if value.is::() { let value2 = value.clone(); - return ( - Some(Expr::FloatConstant( - *value.downcast::().expect("value should be FLOAT"), - pos, - )), - value2, - ); + return (Some(Expr::FloatConstant(value.cast(), pos)), value2); } } diff --git a/tests/maps.rs b/tests/maps.rs index bb8c90cf..7a17f1a9 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -73,14 +73,14 @@ fn test_map_indexing() -> Result<(), EvalAltResult> { fn test_map_assign() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - let x = engine.eval::(r#"let x = #{a: 1, b: true, "c#": "hello"}; x"#)?; - let a = x.get("a").cloned().unwrap(); - let b = x.get("b").cloned().unwrap(); - let c = x.get("c#").cloned().unwrap(); + let x = engine.eval::(r#"let x = #{a: 1, b: true, "c$": "hello"}; x"#)?; + let a = x.get("a").cloned().expect("should have property a"); + let b = x.get("b").cloned().expect("should have property b"); + let c = x.get("c$").cloned().expect("should have property c$"); - assert_eq!(*a.downcast::().unwrap(), 1); - assert_eq!(*b.downcast::().unwrap(), true); - assert_eq!(*c.downcast::().unwrap(), "hello"); + assert_eq!(a.cast::(), 1); + assert_eq!(b.cast::(), true); + assert_eq!(c.cast::(), "hello"); Ok(()) } @@ -89,14 +89,14 @@ fn test_map_assign() -> Result<(), EvalAltResult> { fn test_map_return() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - let x = engine.eval::(r#"#{a: 1, b: true, c: "hello"}"#)?; - let a = x.get("a").cloned().unwrap(); - let b = x.get("b").cloned().unwrap(); - let c = x.get("c").cloned().unwrap(); + let x = engine.eval::(r#"#{a: 1, b: true, "c$": "hello"}"#)?; + let a = x.get("a").cloned().expect("should have property a"); + let b = x.get("b").cloned().expect("should have property b"); + let c = x.get("c$").cloned().expect("should have property c$"); - assert_eq!(*a.downcast::().unwrap(), 1); - assert_eq!(*b.downcast::().unwrap(), true); - assert_eq!(*c.downcast::().unwrap(), "hello"); + assert_eq!(a.cast::(), 1); + assert_eq!(b.cast::(), true); + assert_eq!(c.cast::(), "hello"); Ok(()) } From 5e7c9b47d5ef9cb073be8b4b598fd34285a573dc Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 2 Apr 2020 12:35:44 +0800 Subject: [PATCH 05/20] Rename `downcast` to `try_cast` and add `cast` for Dynamic. --- README.md | 94 +++++++++++++++++++++++++++++++++++++++++++-------- src/any.rs | 34 ++++++++++++++++--- src/api.rs | 36 ++++++++------------ src/engine.rs | 48 +++++++++++++------------- src/parser.rs | 44 +++++------------------- tests/maps.rs | 28 +++++++-------- 6 files changed, 169 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index 4a168273..4d4b62aa 100644 --- a/README.md +++ b/README.md @@ -279,7 +279,6 @@ The following primitive types are supported natively: | **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | | **Nothing/void/nil/null** (or whatever you want to call it) | `()` | `"()"` | `""` _(empty string)_ | -[`Dynamic`]: #values-and-types [`()`]: #values-and-types All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust. @@ -293,7 +292,7 @@ If no floating-point is needed or supported, use the [`no_float`] feature to rem The `to_string` function converts a standard type into a string for display purposes. -The `type_of` function detects the actual type of a value. This is useful because all variables are `Dynamic`. +The `type_of` function detects the actual type of a value. This is useful because all variables are [`Dynamic`] in nature. ```rust // Use 'type_of()' to get the actual types of values @@ -313,6 +312,74 @@ if type_of(x) == "string" { } ``` +Dynamic values +-------------- + +[`Dynamic`]: #dynamic-values + +A `Dynamic` value can be _any_ type. + +Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific +actions based on the actual value's type. + +```rust +let mystery = get_some_dynamic_value(); + +if type_of(mystery) == "i64" { + print("Hey, I got an integer here!"); +} else if type_of(mystery) == "f64" { + print("Hey, I got a float here!"); +} else if type_of(mystery) == "string" { + print("Hey, I got a string here!"); +} else if type_of(mystery) == "bool" { + print("Hey, I got a boolean here!"); +} else if type_of(mystery) == "array" { + print("Hey, I got an array here!"); +} else if type_of(mystery) == "map" { + print("Hey, I got an object map here!"); +} else if type_of(mystery) == "TestStruct" { + print("Hey, I got the TestStruct custom type here!"); +} else { + print("I don't know what this is: " + type_of(mystery)); +} +``` + +In Rust, sometimes a `Dynamic` forms part of the return value - a good example is elements within an `Array` which are `Dynamic`, +or property values in an object map. In order to get the _real_ value, the actual value type _must_ be known in advance. +There is no easy way for Rust to detect, at run-time, what type the `Dynamic` value is (short of using the `type_name` +function to get the textual name of the type and then matching on that). + +To use a `Dynamic` value in Rust, use the `cast` method to convert the value into a specific, known type. +Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails. + +```rust +use rhai::AnyExt; // Pull in the trait. + +let list: Array = engine.eval("...")?; // return type is 'Array' +let item = list[0]; // an element in an 'Array' is 'Dynamic' + +let value = item.cast::(); // if the element is 'i64', this succeeds; otherwise it panics +let value: i64 = item.cast(); // type can also be inferred + +let value = item.try_cast::()?; // 'try_cast' does not panic when the cast fails, but returns an error +``` + +The `type_name` method gets the name of the actual type as a string, which you may match against. + +```rust +use rhai::Any; // Pull in the trait. + +let list: Array = engine.eval("...")?; // return type is 'Array' +let item = list[0]; // an element in an 'Array' is 'Dynamic' + +match item.type_name() { // 'type_name' returns the name of the actual Rust type + "i64" => ... + "std::string::String" => ... + "bool" => ... + "path::to::module::TestStruct" => ... +} +``` + Value conversions ----------------- @@ -325,11 +392,11 @@ That's about it. For other conversions, register custom conversion functions. ```rust let x = 42; -let y = x * 100.0; // <- error: cannot multiply i64 with f64 -let y = x.to_float() * 100.0; // works -let z = y.to_int() + x; // works +let y = x * 100.0; // <- error: cannot multiply i64 with f64 +let y = x.to_float() * 100.0; // works +let z = y.to_int() + x; // works -let c = 'X'; // character +let c = 'X'; // character print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88" ``` @@ -972,10 +1039,9 @@ Arrays Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices. Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`'. +All elements stored in an array are [`Dynamic`], and the array can freely grow or shrink with elements added or removed. -The Rust 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. @@ -1053,7 +1119,7 @@ engine.register_fn("push", |list: &mut Array, item: MyType| list.push(Box::new(i Object maps ----------- -Object maps are dictionaries. Properties of any type (`Dynamic`) can be freely added and retrieved. +Object maps are dictionaries. Properties are all [`Dynamic`] and can be freely added and retrieved. Object map literals are built within braces '`#{`' ... '`}`' (_name_ `:` _value_ syntax similar to Rust) and separated by commas '`,`'. The property _name_ can be a simple variable name following the same naming rules as [variables], or an arbitrary string literal. @@ -1064,9 +1130,7 @@ The index notation allows setting/getting properties of arbitrary names (even th **Important:** Trying to read a non-existent property returns `()` instead of causing an error. -The Rust type of a Rhai object map is `rhai::Map`. - -[`type_of()`] an object map returns `"map"`. +The Rust type of a Rhai object map is `rhai::Map`. [`type_of()`] an object map returns `"map"`. Object maps are disabled via the [`no_object`] feature. @@ -1642,9 +1706,9 @@ Function volatility considerations --------------------------------- Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external -environment and not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value! +environment and is not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value! The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments -it will eagerly run execute the function call. This causes the script to behave differently from the intended semantics because +it will eagerly execute the function call. This causes the script to behave differently from the intended semantics because essentially the result of the function call will always be the same value. Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions. diff --git a/src/any.rs b/src/any.rs index e92b3e9d..befadcce 100644 --- a/src/any.rs +++ b/src/any.rs @@ -89,7 +89,14 @@ impl Clone for Dynamic { /// An extension trait that allows down-casting a `Dynamic` value to a specific type. pub trait AnyExt: Sized { /// Get a copy of a `Dynamic` value as a specific type. - fn downcast(self) -> Result, Self>; + fn try_cast(self) -> Result; + + /// Get a copy of a `Dynamic` value as a specific type. + /// + /// # Panics + /// + /// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type). + fn cast(self) -> T; /// This trait may only be implemented by `rhai`. #[doc(hidden)] @@ -106,19 +113,38 @@ impl AnyExt for Dynamic { /// /// let x: Dynamic = 42_u32.into_dynamic(); /// - /// assert_eq!(*x.downcast::().unwrap(), 42); + /// assert_eq!(x.try_cast::().unwrap(), 42); /// ``` - fn downcast(self) -> Result, Self> { + fn try_cast(self) -> Result { if self.is::() { unsafe { let raw: *mut Variant = Box::into_raw(self); - Ok(Box::from_raw(raw as *mut T)) + Ok(*Box::from_raw(raw as *mut T)) } } else { Err(self) } } + /// Get a copy of the `Dynamic` value as a specific type. + /// + /// # Panics + /// + /// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type). + /// + /// # Example + /// + /// ``` + /// use rhai::{Dynamic, Any, AnyExt}; + /// + /// let x: Dynamic = 42_u32.into_dynamic(); + /// + /// assert_eq!(x.cast::(), 42); + /// ``` + fn cast(self) -> T { + self.try_cast::().expect("cast failed") + } + fn _closed(&self) -> _Private { _Private } diff --git a/src/api.rs b/src/api.rs index 7e010192..b3acfa79 100644 --- a/src/api.rs +++ b/src/api.rs @@ -692,8 +692,7 @@ impl<'e> Engine<'e> { ast: &AST, ) -> Result { self.eval_ast_with_scope_raw(scope, false, ast)? - .downcast::() - .map(|v| *v) + .try_cast::() .map_err(|a| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name((*a).type_name()).to_string(), @@ -735,9 +734,8 @@ impl<'e> Engine<'e> { /// Evaluate a file, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run. + /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. #[cfg(not(feature = "no_std"))] pub fn consume_file( &mut self, @@ -750,9 +748,8 @@ impl<'e> Engine<'e> { /// Evaluate a file with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ and not cleared from run to run. + /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. #[cfg(not(feature = "no_std"))] pub fn consume_file_with_scope( &mut self, @@ -767,9 +764,8 @@ impl<'e> Engine<'e> { /// Evaluate a string, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. + /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> { self.consume_with_scope(&mut Scope::new(), retain_functions, input) } @@ -777,9 +773,8 @@ impl<'e> Engine<'e> { /// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. + /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. pub fn consume_with_scope( &mut self, scope: &mut Scope, @@ -797,9 +792,8 @@ impl<'e> Engine<'e> { /// Evaluate an AST, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. + /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> { self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast) } @@ -807,9 +801,8 @@ impl<'e> Engine<'e> { /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. /// - /// # Note - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_and not cleared from run to run. + /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. pub fn consume_ast_with_scope( &mut self, scope: &mut Scope, @@ -884,8 +877,7 @@ impl<'e> Engine<'e> { let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)? - .downcast() - .map(|b| *b) + .try_cast() .map_err(|a| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name((*a).type_name()).into(), diff --git a/src/engine.rs b/src/engine.rs index 2d3e955d..5230f5b8 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -628,9 +628,9 @@ impl Engine<'_> { // val_array[idx] if let Some(arr) = val.downcast_ref::() { - let idx = *self + let idx = self .eval_expr(scope, idx_expr, level)? - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; return if idx >= 0 { @@ -647,9 +647,9 @@ impl Engine<'_> { { // val_map[idx] if let Some(map) = val.downcast_ref::() { - let idx = *self + let idx = self .eval_expr(scope, idx_expr, level)? - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?; return Ok(( @@ -662,9 +662,9 @@ impl Engine<'_> { // val_string[idx] if let Some(s) = val.downcast_ref::() { - let idx = *self + let idx = self .eval_expr(scope, idx_expr, level)? - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; return if idx >= 0 { @@ -795,9 +795,9 @@ impl Engine<'_> { let s = scope.get_mut_by_type::(src); let pos = new_val.1; // Value must be a character - let ch = *new_val + let ch = new_val .0 - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; Self::str_replace_char(s, idx.as_num(), ch); Ok(().into_dynamic()) @@ -830,8 +830,8 @@ impl Engine<'_> { if let Some(s) = target.downcast_mut::() { // Value must be a character - let ch = *new_val - .downcast::() + let ch = new_val + .try_cast::() .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; Self::str_replace_char(s, idx.as_num(), ch); return Ok(target); @@ -1258,32 +1258,32 @@ impl Engine<'_> { } Expr::And(lhs, rhs) => Ok(Box::new( - *self + self .eval_expr(scope, &*lhs, level)? - .downcast::() + .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) })? && // Short-circuit using && - *self + self .eval_expr(scope, &*rhs, level)? - .downcast::() + .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) })?, )), Expr::Or(lhs, rhs) => Ok(Box::new( - *self + self .eval_expr(scope, &*lhs, level)? - .downcast::() + .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) })? || // Short-circuit using || - *self + self .eval_expr(scope, &*rhs, level)? - .downcast::() + .try_cast::() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) })?, @@ -1334,10 +1334,10 @@ impl Engine<'_> { // If-else statement Stmt::IfThenElse(guard, if_body, else_body) => self .eval_expr(scope, guard, level)? - .downcast::() + .try_cast::() .map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position())) .and_then(|guard_val| { - if *guard_val { + if guard_val { self.eval_stmt(scope, if_body, level) } else if let Some(stmt) = else_body { self.eval_stmt(scope, stmt.as_ref(), level) @@ -1348,8 +1348,8 @@ impl Engine<'_> { // While loop Stmt::While(guard, body) => loop { - match self.eval_expr(scope, guard, level)?.downcast::() { - Ok(guard_val) if *guard_val => match self.eval_stmt(scope, body, level) { + match self.eval_expr(scope, guard, level)?.try_cast::() { + Ok(guard_val) if guard_val => match self.eval_stmt(scope, body, level) { Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()), Err(x) => return Err(x), @@ -1425,9 +1425,7 @@ impl Engine<'_> { Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => { let val = self.eval_expr(scope, a, level)?; Err(EvalAltResult::ErrorRuntime( - val.downcast::() - .map(|s| *s) - .unwrap_or_else(|_| "".to_string()), + val.try_cast::().unwrap_or_else(|_| "".to_string()), *pos, )) } diff --git a/src/parser.rs b/src/parser.rs index c3d16f2e..d164d35e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2673,41 +2673,21 @@ pub fn parse<'a, 'e>( pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option, Dynamic) { if value.is::() { let value2 = value.clone(); - ( - Some(Expr::IntegerConstant( - *value.downcast::().expect("value should be INT"), - pos, - )), - value2, - ) + (Some(Expr::IntegerConstant(value.cast(), pos)), value2) } else if value.is::() { let value2 = value.clone(); - ( - Some(Expr::CharConstant( - *value.downcast::().expect("value should be char"), - pos, - )), - value2, - ) + (Some(Expr::CharConstant(value.cast(), pos)), value2) } else if value.is::() { let value2 = value.clone(); - ( - Some(Expr::StringConstant( - *value.downcast::().expect("value should be String"), - pos, - )), - value2, - ) + (Some(Expr::StringConstant(value.cast(), pos)), value2) } else if value.is::() { let value2 = value.clone(); ( - Some( - if *value.downcast::().expect("value should be bool") { - Expr::True(pos) - } else { - Expr::False(pos) - }, - ), + Some(if value.cast::() { + Expr::True(pos) + } else { + Expr::False(pos) + }), value2, ) } else { @@ -2715,13 +2695,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option, Dyna { if value.is::() { let value2 = value.clone(); - return ( - Some(Expr::FloatConstant( - *value.downcast::().expect("value should be FLOAT"), - pos, - )), - value2, - ); + return (Some(Expr::FloatConstant(value.cast(), pos)), value2); } } diff --git a/tests/maps.rs b/tests/maps.rs index bb8c90cf..7a17f1a9 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -73,14 +73,14 @@ fn test_map_indexing() -> Result<(), EvalAltResult> { fn test_map_assign() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - let x = engine.eval::(r#"let x = #{a: 1, b: true, "c#": "hello"}; x"#)?; - let a = x.get("a").cloned().unwrap(); - let b = x.get("b").cloned().unwrap(); - let c = x.get("c#").cloned().unwrap(); + let x = engine.eval::(r#"let x = #{a: 1, b: true, "c$": "hello"}; x"#)?; + let a = x.get("a").cloned().expect("should have property a"); + let b = x.get("b").cloned().expect("should have property b"); + let c = x.get("c$").cloned().expect("should have property c$"); - assert_eq!(*a.downcast::().unwrap(), 1); - assert_eq!(*b.downcast::().unwrap(), true); - assert_eq!(*c.downcast::().unwrap(), "hello"); + assert_eq!(a.cast::(), 1); + assert_eq!(b.cast::(), true); + assert_eq!(c.cast::(), "hello"); Ok(()) } @@ -89,14 +89,14 @@ fn test_map_assign() -> Result<(), EvalAltResult> { fn test_map_return() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - let x = engine.eval::(r#"#{a: 1, b: true, c: "hello"}"#)?; - let a = x.get("a").cloned().unwrap(); - let b = x.get("b").cloned().unwrap(); - let c = x.get("c").cloned().unwrap(); + let x = engine.eval::(r#"#{a: 1, b: true, "c$": "hello"}"#)?; + let a = x.get("a").cloned().expect("should have property a"); + let b = x.get("b").cloned().expect("should have property b"); + let c = x.get("c$").cloned().expect("should have property c$"); - assert_eq!(*a.downcast::().unwrap(), 1); - assert_eq!(*b.downcast::().unwrap(), true); - assert_eq!(*c.downcast::().unwrap(), "hello"); + assert_eq!(a.cast::(), 1); + assert_eq!(b.cast::(), true); + assert_eq!(c.cast::(), "hello"); Ok(()) } From 0873bdc152e91222898ff89031ecdb54439696c6 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 2 Apr 2020 19:40:02 +0800 Subject: [PATCH 06/20] Add `sync` feature to make Dynamic, Scope and AST `Send + Sync`. --- Cargo.toml | 1 + README.md | 11 ++++++++--- src/any.rs | 43 ++++++++++++++++++++++++++++++++++++++++--- tests/side_effects.rs | 15 +++++++-------- 4 files changed, 56 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 86cedc4a..f852d289 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ no_optimize = [] # no script optimizer optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i64 = [] # set INT=i64 (default) and disable support for all other integer types +sync = [] # restrict to only types that implement Send + Sync # compiling for no-std no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm" ] diff --git a/README.md b/README.md index 4d4b62aa..b8b58cf1 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ Optional features | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | +| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Scope`] and `AST` are both `Send + Sync`. | By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here to opt-**out** of certain functionalities that are not needed. Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. @@ -82,6 +83,7 @@ Excluding unneeded functionalities can result in smaller, faster builds as well [`only_i32`]: #optional-features [`only_i64`]: #optional-features [`no_std`]: #optional-features +[`sync`]: #optional-features Related ------- @@ -312,12 +314,12 @@ if type_of(x) == "string" { } ``` -Dynamic values --------------- +`Dynamic` values +---------------- [`Dynamic`]: #dynamic-values -A `Dynamic` value can be _any_ type. +A `Dynamic` value can be _any_ type. However, if the [`sync`] feature is used, then all types must be `Send + Sync`. Because [`type_of()`] a `Dynamic` value returns the type of the actual value, it is usually used to perform type-specific actions based on the actual value's type. @@ -704,6 +706,9 @@ By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting on This gives each evaluation a clean starting slate. In order to continue using the same global state from one invocation to the next, such a state must be manually created and passed in. +All `Scope` variables are [`Dynamic`], meaning they can store values of any type. If the [`sync`] feature is used, however, then only types +that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`. This is extremely useful in multi-threaded applications. + In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is threaded through multiple invocations: ```rust diff --git a/src/any.rs b/src/any.rs index befadcce..3599f972 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,7 +1,7 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. use crate::stdlib::{ - any::{type_name, Any as StdAny, TypeId}, + any::{type_name, TypeId}, boxed::Box, fmt, }; @@ -13,7 +13,8 @@ pub type Variant = dyn Any; pub type Dynamic = Box; /// A trait covering any type. -pub trait Any: StdAny { +#[cfg(feature = "sync")] +pub trait Any: crate::stdlib::any::Any + Send + Sync { /// Get the `TypeId` of this type. fn type_id(&self) -> TypeId; @@ -28,7 +29,43 @@ pub trait Any: StdAny { fn _closed(&self) -> _Private; } -impl Any for T { +#[cfg(feature = "sync")] +impl Any for T { + fn type_id(&self) -> TypeId { + TypeId::of::() + } + + fn type_name(&self) -> &'static str { + type_name::() + } + + fn into_dynamic(&self) -> Dynamic { + Box::new(self.clone()) + } + + fn _closed(&self) -> _Private { + _Private + } +} + +#[cfg(not(feature = "sync"))] +pub trait Any: crate::stdlib::any::Any { + /// Get the `TypeId` of this type. + fn type_id(&self) -> TypeId; + + /// Get the name of this type. + fn type_name(&self) -> &'static str; + + /// Convert into `Dynamic`. + fn into_dynamic(&self) -> Dynamic; + + /// This trait may only be implemented by `rhai`. + #[doc(hidden)] + fn _closed(&self) -> _Private; +} + +#[cfg(not(feature = "sync"))] +impl Any for T { fn type_id(&self) -> TypeId { TypeId::of::() } diff --git a/tests/side_effects.rs b/tests/side_effects.rs index 54b25432..d97b6f33 100644 --- a/tests/side_effects.rs +++ b/tests/side_effects.rs @@ -2,8 +2,7 @@ ///! This test simulates an external command object that is driven by a script. use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT}; -use std::cell::RefCell; -use std::rc::Rc; +use std::sync::{Arc, Mutex}; /// External command. struct Command { @@ -24,19 +23,19 @@ impl Command { /// Wrapper object to wrap a command object. #[derive(Clone)] struct CommandWrapper { - command: Rc>, + command: Arc>, } impl CommandWrapper { /// Delegate command action. pub fn do_action(&mut self, x: i64) { - let mut command = self.command.borrow_mut(); + let mut command = self.command.lock().unwrap(); let val = command.get(); command.action(val + x); } /// Delegate get value action. pub fn get_value(&mut self) -> i64 { - let command = self.command.borrow(); + let command = self.command.lock().unwrap(); command.get() } } @@ -47,8 +46,8 @@ fn test_side_effects() -> Result<(), EvalAltResult> { let mut scope = Scope::new(); // Create the command object with initial state, handled by an `Rc`. - let command = Rc::new(RefCell::new(Command { state: 12 })); - assert_eq!(command.borrow().get(), 12); + let command = Arc::new(Mutex::new(Command { state: 12 })); + assert_eq!(command.lock().unwrap().get(), 12); // Create the wrapper. let wrapper = CommandWrapper { @@ -76,7 +75,7 @@ fn test_side_effects() -> Result<(), EvalAltResult> { ); // Make sure the actions are properly performed - assert_eq!(command.borrow().get(), 42); + assert_eq!(command.lock().unwrap().get(), 42); Ok(()) } From 9d7091ad9db489b4dd646ae7b3b753222593154e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 2 Apr 2020 22:37:35 +0800 Subject: [PATCH 07/20] Use formatting commands for padding. --- examples/repl.rs | 9 +++------ examples/rhai_runner.rs | 13 +++++-------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/examples/repl.rs b/examples/repl.rs index 4ba75597..def1b310 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -9,10 +9,6 @@ use std::{ }; fn print_error(input: &str, err: EvalAltResult) { - fn padding(pad: &str, len: usize) -> String { - iter::repeat(pad).take(len).collect::() - } - let lines: Vec<_> = input.trim().split('\n').collect(); let line_no = if lines.len() > 1 { @@ -54,8 +50,9 @@ fn print_error(input: &str, err: EvalAltResult) { }; println!( - "{}^ {}", - padding(" ", line_no.len() + p.position().unwrap() - 1), + "{0:>1$} {2}", + "^", + line_no.len() + p.position().unwrap(), err_text.replace(&pos_text, "") ); } diff --git a/examples/rhai_runner.rs b/examples/rhai_runner.rs index 20f6226e..180f51d6 100644 --- a/examples/rhai_runner.rs +++ b/examples/rhai_runner.rs @@ -5,10 +5,6 @@ use rhai::OptimizationLevel; use std::{env, fs::File, io::Read, iter, process::exit}; -fn padding(pad: &str, len: usize) -> String { - iter::repeat(pad).take(len).collect::() -} - fn eprint_error(input: &str, err: EvalAltResult) { fn eprint_line(lines: &[&str], line: usize, pos: usize, err: &str) { let line_no = format!("{}: ", line); @@ -16,8 +12,9 @@ fn eprint_error(input: &str, err: EvalAltResult) { eprintln!("{}{}", line_no, lines[line - 1]); eprintln!( - "{}^ {}", - padding(" ", line_no.len() + pos - 1), + "{:>1$} {2}", + "^", + line_no.len() + pos, err.replace(&pos_text, "") ); eprintln!(""); @@ -76,9 +73,9 @@ fn main() { } if let Err(err) = engine.consume(false, &contents) { - eprintln!("{}", padding("=", filename.len())); + eprintln!("{:=<1$}", "", filename.len()); eprintln!("{}", filename); - eprintln!("{}", padding("=", filename.len())); + eprintln!("{:=<1$}", "", filename.len()); eprintln!(""); eprint_error(&contents, err); From a79f2a209ce9d26388cc2d0c710a7f89a2e1f6dd Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 3 Apr 2020 17:17:00 +0800 Subject: [PATCH 08/20] Make Engine Send+Sync. --- README.md | 2 +- src/api.rs | 88 +++++++++++++++++++++++++++++++++++----------- src/builtin.rs | 9 ++--- src/engine.rs | 18 ++++++++++ src/fn_register.rs | 16 +++++++++ 5 files changed, 108 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index b8b58cf1..55acc262 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Optional features | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | -| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Scope`] and `AST` are both `Send + Sync`. | +| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, [`Scope`] and `AST` are all `Send + Sync`. | By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here to opt-**out** of certain functionalities that are not needed. Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. diff --git a/src/api.rs b/src/api.rs index b3acfa79..6b6cf9bd 100644 --- a/src/api.rs +++ b/src/api.rs @@ -22,6 +22,44 @@ use crate::stdlib::{ #[cfg(not(feature = "no_std"))] use crate::stdlib::{fs::File, io::prelude::*, path::PathBuf}; +// Define callback function types +#[cfg(feature = "sync")] +pub trait ObjectGetCallback: Fn(&mut T) -> U + Send + Sync + 'static {} +#[cfg(feature = "sync")] +impl U + Send + Sync + 'static, T, U> ObjectGetCallback for F {} + +#[cfg(not(feature = "sync"))] +pub trait ObjectGetCallback: Fn(&mut T) -> U + 'static {} +#[cfg(not(feature = "sync"))] +impl U + 'static, T, U> ObjectGetCallback for F {} + +#[cfg(feature = "sync")] +pub trait ObjectSetCallback: Fn(&mut T, U) + Send + Sync + 'static {} +#[cfg(feature = "sync")] +impl ObjectSetCallback for F {} + +#[cfg(not(feature = "sync"))] +pub trait ObjectSetCallback: Fn(&mut T, U) + 'static {} +#[cfg(not(feature = "sync"))] +impl ObjectSetCallback for F {} + +#[cfg(feature = "sync")] +pub trait IteratorCallback: + Fn(&Dynamic) -> Box> + Send + Sync + 'static +{ +} +#[cfg(feature = "sync")] +impl Box> + Send + Sync + 'static> IteratorCallback + for F +{ +} + +#[cfg(not(feature = "sync"))] +pub trait IteratorCallback: Fn(&Dynamic) -> Box> + 'static {} +#[cfg(not(feature = "sync"))] +impl Box> + 'static> IteratorCallback for F {} + +/// Engine public API impl<'e> Engine<'e> { /// Register a custom function. pub(crate) fn register_fn_raw(&mut self, fn_name: &str, args: Vec, f: Box) { @@ -126,10 +164,7 @@ impl<'e> Engine<'e> { /// Register an iterator adapter for a type with the `Engine`. /// This is an advanced feature. - pub fn register_iterator(&mut self, f: F) - where - F: Fn(&Dynamic) -> Box> + 'static, - { + pub fn register_iterator(&mut self, f: F) { self.type_iterators.insert(TypeId::of::(), Box::new(f)); } @@ -170,11 +205,12 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_object"))] - pub fn register_get( - &mut self, - name: &str, - callback: impl Fn(&mut T) -> U + 'static, - ) { + pub fn register_get(&mut self, name: &str, callback: F) + where + T: Any + Clone, + U: Any + Clone, + F: ObjectGetCallback, + { self.register_fn(&make_getter(name), callback); } @@ -215,11 +251,12 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_object"))] - pub fn register_set( - &mut self, - name: &str, - callback: impl Fn(&mut T, U) -> () + 'static, - ) { + pub fn register_set(&mut self, name: &str, callback: F) + where + T: Any + Clone, + U: Any + Clone, + F: ObjectSetCallback, + { self.register_fn(&make_setter(name), callback); } @@ -262,12 +299,13 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_object"))] - pub fn register_get_set( - &mut self, - name: &str, - get_fn: impl Fn(&mut T) -> U + 'static, - set_fn: impl Fn(&mut T, U) -> () + 'static, - ) { + pub fn register_get_set(&mut self, name: &str, get_fn: G, set_fn: S) + where + T: Any + Clone, + U: Any + Clone, + G: ObjectGetCallback, + S: ObjectSetCallback, + { self.register_get(name, get_fn); self.register_set(name, set_fn); } @@ -925,6 +963,11 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` + #[cfg(feature = "sync")] + pub fn on_print(&mut self, callback: impl FnMut(&str) + Send + Sync + 'e) { + self.on_print = Box::new(callback); + } + #[cfg(not(feature = "sync"))] pub fn on_print(&mut self, callback: impl FnMut(&str) + 'e) { self.on_print = Box::new(callback); } @@ -949,6 +992,11 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` + #[cfg(feature = "sync")] + pub fn on_debug(&mut self, callback: impl FnMut(&str) + Send + Sync + 'e) { + self.on_debug = Box::new(callback); + } + #[cfg(not(feature = "sync"))] pub fn on_debug(&mut self, callback: impl FnMut(&str) + 'e) { self.on_debug = Box::new(callback); } diff --git a/src/builtin.rs b/src/builtin.rs index d7f5840e..db6b5345 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -1,7 +1,7 @@ //! Helper module that allows registration of the _core library_ and //! _standard library_ of utility functions. -use crate::any::Any; +use crate::any::{Any, Dynamic}; use crate::engine::{Engine, FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; use crate::parser::{Position, INT}; @@ -612,8 +612,9 @@ impl Engine<'_> { reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, Array); // Register array iterator - self.register_iterator::(|a| { + self.register_iterator::(|a: &Dynamic| { Box::new(a.downcast_ref::().unwrap().clone().into_iter()) + as Box> }); } @@ -636,13 +637,13 @@ impl Engine<'_> { where Range: Iterator, { - engine.register_iterator::, _>(|a| { + engine.register_iterator::, _>(|a: &Dynamic| { Box::new( a.downcast_ref::>() .unwrap() .clone() .map(|n| n.into_dynamic()), - ) + ) as Box> }); } diff --git a/src/engine.rs b/src/engine.rs index 5230f5b8..235068a2 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -32,8 +32,14 @@ pub type Map = HashMap; pub type FnCallArgs<'a> = [&'a mut Variant]; +#[cfg(feature = "sync")] +pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result + Send + Sync; +#[cfg(not(feature = "sync"))] pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result; +#[cfg(feature = "sync")] +type IteratorFn = dyn Fn(&Dynamic) -> Box> + Send + Sync; +#[cfg(not(feature = "sync"))] type IteratorFn = dyn Fn(&Dynamic) -> Box>; pub const MAX_CALL_STACK_DEPTH: usize = 64; @@ -193,8 +199,15 @@ pub struct Engine<'e> { pub(crate) type_names: HashMap, /// Closure for implementing the `print` command. + #[cfg(feature = "sync")] + pub(crate) on_print: Box, + #[cfg(not(feature = "sync"))] pub(crate) on_print: Box, + /// Closure for implementing the `debug` command. + #[cfg(feature = "sync")] + pub(crate) on_debug: Box, + #[cfg(not(feature = "sync"))] pub(crate) on_debug: Box, /// Optimize the AST after compilation. @@ -280,6 +293,11 @@ fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { impl Engine<'_> { /// Create a new `Engine` pub fn new() -> Self { + // fn abc(f: F) { + // f(); + // } + // abc(|| ()); + Default::default() } diff --git a/src/fn_register.rs b/src/fn_register.rs index cfc1b345..7ff2cc28 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -138,7 +138,13 @@ macro_rules! def_register { // ^ dereferencing function impl< $($par: Any + Clone,)* + + #[cfg(feature = "sync")] + FN: Fn($($param),*) -> RET + Send + Sync + 'static, + + #[cfg(not(feature = "sync"))] FN: Fn($($param),*) -> RET + 'static, + RET: Any > RegisterFn for Engine<'_> { @@ -171,6 +177,11 @@ macro_rules! def_register { impl< $($par: Any + Clone,)* + + #[cfg(feature = "sync")] + FN: Fn($($param),*) -> Dynamic + Send + Sync + 'static, + + #[cfg(not(feature = "sync"))] FN: Fn($($param),*) -> Dynamic + 'static, > RegisterDynamicFn for Engine<'_> { @@ -202,7 +213,12 @@ macro_rules! def_register { impl< $($par: Any + Clone,)* + + #[cfg(feature = "sync")] + FN: Fn($($param),*) -> Result + Send + Sync + 'static, + #[cfg(not(feature = "sync"))] FN: Fn($($param),*) -> Result + 'static, + RET: Any > RegisterResultFn for Engine<'_> { From c6216c08234d015928bd6ee983fc5d906232e521 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 3 Apr 2020 17:18:30 +0800 Subject: [PATCH 09/20] Add back doc for on_print and on_debug. --- src/api.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/api.rs b/src/api.rs index 6b6cf9bd..2fdcd33e 100644 --- a/src/api.rs +++ b/src/api.rs @@ -967,6 +967,26 @@ impl<'e> Engine<'e> { pub fn on_print(&mut self, callback: impl FnMut(&str) + Send + Sync + 'e) { self.on_print = Box::new(callback); } + /// Override default action of `print` (print to stdout using `println!`) + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// use rhai::Engine; + /// + /// let mut result = String::from(""); + /// { + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// engine.on_print(|s| result.push_str(s)); + /// engine.consume(false, "print(40 + 2);")?; + /// } + /// assert_eq!(result, "42"); + /// # Ok(()) + /// # } + /// ``` #[cfg(not(feature = "sync"))] pub fn on_print(&mut self, callback: impl FnMut(&str) + 'e) { self.on_print = Box::new(callback); @@ -996,6 +1016,26 @@ impl<'e> Engine<'e> { pub fn on_debug(&mut self, callback: impl FnMut(&str) + Send + Sync + 'e) { self.on_debug = Box::new(callback); } + /// Override default action of `debug` (print to stdout using `println!`) + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// use rhai::Engine; + /// + /// let mut result = String::from(""); + /// { + /// let mut engine = Engine::new(); + /// + /// // Override action of 'debug' function + /// engine.on_debug(|s| result.push_str(s)); + /// engine.consume(false, r#"debug("hello");"#)?; + /// } + /// assert_eq!(result, "\"hello\""); + /// # Ok(()) + /// # } + /// ``` #[cfg(not(feature = "sync"))] pub fn on_debug(&mut self, callback: impl FnMut(&str) + 'e) { self.on_debug = Box::new(callback); From 92b549b828afec2668591281b722072935bbc153 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 3 Apr 2020 19:42:01 +0800 Subject: [PATCH 10/20] Add features info in docs. --- src/any.rs | 7 +++++++ src/engine.rs | 10 +++++++++- src/error.rs | 30 +++++++++++++++++++++--------- src/lib.rs | 18 +++++++++++++++++- src/optimize.rs | 2 ++ src/parser.rs | 4 ++++ src/result.rs | 2 ++ src/scope.rs | 2 ++ 8 files changed, 64 insertions(+), 11 deletions(-) diff --git a/src/any.rs b/src/any.rs index 3599f972..814e369a 100644 --- a/src/any.rs +++ b/src/any.rs @@ -7,9 +7,15 @@ use crate::stdlib::{ }; /// An raw value of any type. +/// +/// Currently, `Variant` is not `Send` nor `Sync`, so it can practically be any type. +/// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`. pub type Variant = dyn Any; /// A boxed dynamic type containing any value. +/// +/// Currently, `Dynamic` is not `Send` nor `Sync`, so it can practically be any type. +/// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`. pub type Dynamic = Box; /// A trait covering any type. @@ -48,6 +54,7 @@ impl Any for T { } } +/// A trait covering any type. #[cfg(not(feature = "sync"))] pub trait Any: crate::stdlib::any::Any { /// Get the `TypeId` of this type. diff --git a/src/engine.rs b/src/engine.rs index 235068a2..c60bf0d4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -23,10 +23,14 @@ use crate::stdlib::{ }; /// An dynamic array of `Dynamic` values. +/// +/// Not available under the `no_index` feature. #[cfg(not(feature = "no_index"))] pub type Array = Vec; -/// An dynamic hash map of `Dynamic` values. +/// An dynamic hash map of `Dynamic` values with `String` keys. +/// +/// Not available under the `no_object` feature. #[cfg(not(feature = "no_object"))] pub type Map = HashMap; @@ -188,6 +192,8 @@ impl FunctionsLib { /// # Ok(()) /// # } /// ``` +/// +/// Currently, `Engine` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. pub struct Engine<'e> { /// A hashmap containing all compiled functions known to the engine. pub(crate) functions: HashMap, Box>, @@ -302,6 +308,8 @@ impl Engine<'_> { } /// Control whether and how the `Engine` will optimize an AST after compilation + /// + /// Not available under the `no_optimize` feature. #[cfg(not(feature = "no_optimize"))] pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) { self.optimization_level = optimization_level diff --git a/src/error.rs b/src/error.rs index 2a2986b8..5fa654a5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -30,9 +30,7 @@ impl fmt::Display for LexError { Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s), Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s), Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s), - Self::MalformedIdentifier(s) => { - write!(f, "Variable name is not in a legal format: '{}'", s) - } + Self::MalformedIdentifier(s) => write!(f, "Variable name is not proper: '{}'", s), Self::UnterminatedString => write!(f, "Open string is not terminated"), } } @@ -47,37 +45,51 @@ pub enum ParseErrorType { UnexpectedEOF, /// An unknown operator is encountered. Wrapped value is the operator. UnknownOperator(String), - /// Expecting a particular token but not finding one. Wrapped values are the token and usage. + /// Expecting a particular token but not finding one. Wrapped values are the token and description. MissingToken(String, String), - /// An expression in function call arguments `()` has syntax error. + /// An expression in function call arguments `()` has syntax error. Wrapped value is the error description (if any). MalformedCallExpr(String), - /// An expression in indexing brackets `[]` has syntax error. + /// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error description (if any). + /// + /// Not available under the `no_index` feature. #[cfg(not(feature = "no_index"))] MalformedIndexExpr(String), - /// A map definition has duplicated property names. Wrapped is the property name. + /// A map definition has duplicated property names. Wrapped value is the property name. + /// + /// Not available under the `no_object` feature. #[cfg(not(feature = "no_object"))] DuplicatedProperty(String), - /// Invalid expression assigned to constant. + /// Invalid expression assigned to constant. Wrapped value is the name of the constant. ForbiddenConstantExpr(String), /// Missing a property name for custom types and maps. PropertyExpected, /// Missing a variable name after the `let`, `const` or `for` keywords. VariableExpected, - /// Missing an expression. + /// Missing an expression. Wrapped value is the expression type. ExprExpected(String), /// Defining a function `fn` in an appropriate place (e.g. inside another function). + /// + /// Not available under the `no_function` feature. #[cfg(not(feature = "no_function"))] WrongFnDefinition, /// Missing a function name after the `fn` keyword. + /// + /// Not available under the `no_function` feature. #[cfg(not(feature = "no_function"))] FnMissingName, /// A function definition is missing the parameters list. Wrapped value is the function name. + /// + /// Not available under the `no_function` feature. #[cfg(not(feature = "no_function"))] FnMissingParams(String), /// A function definition has duplicated parameters. Wrapped values are the function name and parameter name. + /// + /// Not available under the `no_function` feature. #[cfg(not(feature = "no_function"))] FnDuplicatedParam(String, String), /// A function definition is missing the body. Wrapped value is the function name. + /// + /// Not available under the `no_function` feature. #[cfg(not(feature = "no_function"))] FnMissingBody(String), /// Assignment to an inappropriate LHS (left-hand-side) expression. diff --git a/src/lib.rs b/src/lib.rs index a553a3a2..3321fccc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,23 @@ //! } //! ``` //! -//! [Check out the README on GitHub for more information!](https://github.com/jonathandturner/rhai) +//! ## Optional features +//! +//! | Feature | Description | +//! | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +//! | `no_stdlib` | Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. Standard types are not affected. | +//! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | +//! | `no_function` | Disable script-defined functions if not needed. | +//! | `no_index` | Disable arrays and indexing features if not needed. | +//! | `no_object` | Disable support for custom types and objects. | +//! | `no_float` | Disable floating-point numbers and math if not needed. | +//! | `no_optimize` | Disable the script optimizer. | +//! | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | +//! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | +//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | +//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and `AST` are all `Send + Sync`. | +//! +//! [Check out the README on GitHub for details on the Rhai language!](https://github.com/jonathandturner/rhai) #![cfg_attr(feature = "no_std", no_std)] diff --git a/src/optimize.rs b/src/optimize.rs index 0651536c..f0e915bf 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -16,6 +16,8 @@ use crate::stdlib::{ }; /// Level of optimization performed. +/// +/// Not available under the `no_optimize` feature. #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] pub enum OptimizationLevel { /// No optimization performed. diff --git a/src/parser.rs b/src/parser.rs index d164d35e..0ec77e86 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -37,6 +37,8 @@ pub type INT = i64; pub type INT = i32; /// The system floating-point type. +/// +/// Not available under the `no_float` feature. #[cfg(not(feature = "no_float"))] pub type FLOAT = f64; @@ -160,6 +162,8 @@ impl fmt::Debug for Position { } /// Compiled AST (abstract syntax tree) of a Rhai script. +/// +/// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. #[derive(Debug, Clone)] pub struct AST(pub(crate) Vec, pub(crate) Vec>); diff --git a/src/result.rs b/src/result.rs index 474ae8b9..dcbade53 100644 --- a/src/result.rs +++ b/src/result.rs @@ -22,6 +22,8 @@ pub enum EvalAltResult { ErrorParsing(ParseError), /// Error reading from a script file. Wrapped value is the path of the script file. + /// + /// Not available under the `no_std` feature. #[cfg(not(feature = "no_std"))] ErrorReadingScriptFile(PathBuf, std::io::Error), diff --git a/src/scope.rs b/src/scope.rs index bf66564d..b76e3904 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -61,6 +61,8 @@ pub(crate) struct EntryRef<'a> { /// /// When searching for entries, newly-added entries are found before similarly-named but older entries, /// allowing for automatic _shadowing_. +/// +/// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. pub struct Scope<'a>(Vec>); impl<'a> Scope<'a> { From 12a379dd5791a2cf98183c265f14e5b06d930b8b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 4 Apr 2020 12:20:24 +0800 Subject: [PATCH 11/20] Add stepped range function and keys/values for maps. --- README.md | 34 ++++++++++++++++++++- src/builtin.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++++--- src/engine.rs | 2 +- tests/for.rs | 35 ++++++++++++++++++++-- tests/maps.rs | 23 +++++++++++++++ 5 files changed, 165 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 55acc262..86a6482f 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Optional features | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | -| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, [`Scope`] and `AST` are all `Send + Sync`. | +| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Engine`], [`Scope`] and `AST` are all `Send + Sync`. | By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here to opt-**out** of certain functionalities that are not needed. Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. @@ -1102,6 +1102,10 @@ last == 5; print(y.len()); // prints 3 +for item in y { // arrays can be iterated with a 'for' statement + print(item); +} + y.pad(10, "hello"); // pad the array up to 10 elements print(y.len()); // prints 10 @@ -1148,6 +1152,8 @@ The following functions (defined in the standard library but excluded if [`no_st | `clear` | empties the object map | | `mixin` | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) | | `+` operator | merges the first object map with the second | +| `keys` | returns an array of all the property names (in random order) | +| `values` | returns an array of all the property values (in random order) | Examples: @@ -1194,6 +1200,14 @@ y["xyz"] == (); print(y.len()); // prints 3 +for name in keys(y) { // get an array of all the property names via the 'keys' function + print(name); +} + +for val in values(y) { // get an array of all the property values via the 'values' function + print(val); +} + y.clear(); // empty the object map print(y.len()); // prints 0 @@ -1357,6 +1371,24 @@ for x in range(0, 50) { print(x); if x == 42 { break; } // break out of for loop } + + +// The 'range' function also takes a step +for x in range(0, 50, 3) { // step by 3 + if x > 10 { continue; } // skip to the next iteration + print(x); + if x == 42 { break; } // break out of for loop +} + +// Iterate through the values of an object map +let map = #{a:1, b:3, c:5, d:7, e:9}; + +// Remember that keys are returned in random order +for x in keys(map) { + if x > 10 { continue; } // skip to the next iteration + print(x); + if x == 42 { break; } // break out of for loop +} ``` `return`-ing values diff --git a/src/builtin.rs b/src/builtin.rs index db6b5345..b6baed63 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -629,11 +629,22 @@ impl Engine<'_> { self.register_fn(KEYWORD_DEBUG, |x: &mut Map| -> String { format!("#{:?}", x) }); + + // Register map access functions + self.register_fn("keys", |map: Map| { + map.into_iter() + .map(|(k, _)| k.into_dynamic()) + .collect::>() + }); + + self.register_fn("values", |map: Map| { + map.into_iter().map(|(_, v)| v).collect::>() + }); } } // Register range function - fn reg_iterator(engine: &mut Engine) + fn reg_range(engine: &mut Engine) where Range: Iterator, { @@ -642,12 +653,12 @@ impl Engine<'_> { a.downcast_ref::>() .unwrap() .clone() - .map(|n| n.into_dynamic()), + .map(|x| x.into_dynamic()), ) as Box> }); } - reg_iterator::(self); + reg_range::(self); self.register_fn("range", |i1: INT, i2: INT| (i1..i2)); #[cfg(not(feature = "only_i32"))] @@ -656,7 +667,7 @@ impl Engine<'_> { macro_rules! reg_range { ($self:expr, $x:expr, $( $y:ty ),*) => ( $( - reg_iterator::<$y>(self); + reg_range::<$y>(self); $self.register_fn($x, (|x: $y, y: $y| x..y) as fn(x: $y, y: $y)->Range<$y>); )* ) @@ -664,6 +675,67 @@ impl Engine<'_> { reg_range!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64); } + + // Register range function with step + #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] + struct StepRange(T, T, T) + where + for<'a> &'a T: Add<&'a T, Output = T>, + T: Any + Clone + PartialOrd; + + impl Iterator for StepRange + where + for<'a> &'a T: Add<&'a T, Output = T>, + T: Any + Clone + PartialOrd, + { + type Item = T; + + fn next(&mut self) -> Option { + if self.0 < self.1 { + let v = self.0.clone(); + self.0 = &v + &self.2; + Some(v) + } else { + None + } + } + } + + fn reg_step(engine: &mut Engine) + where + for<'a> &'a T: Add<&'a T, Output = T>, + T: Any + Clone + PartialOrd, + StepRange: Iterator, + { + engine.register_iterator::, _>(|a: &Dynamic| { + Box::new( + a.downcast_ref::>() + .unwrap() + .clone() + .map(|x| x.into_dynamic()), + ) as Box> + }); + } + + reg_step::(self); + self.register_fn("range", |i1: INT, i2: INT, step: INT| { + StepRange(i1, i2, step) + }); + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + macro_rules! reg_step { + ($self:expr, $x:expr, $( $y:ty ),*) => ( + $( + reg_step::<$y>(self); + $self.register_fn($x, (|x: $y, y: $y, step: $y| StepRange(x,y,step)) as fn(x: $y, y: $y, step: $y)->StepRange<$y>); + )* + ) + } + + reg_step!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64); + } } } diff --git a/src/engine.rs b/src/engine.rs index c60bf0d4..c6f81199 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -236,7 +236,7 @@ impl Default for 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 diff --git a/tests/for.rs b/tests/for.rs index be476328..448c7fcb 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -1,8 +1,8 @@ -#![cfg(not(feature = "no_index"))] use rhai::{Engine, EvalAltResult, INT}; +#[cfg(not(feature = "no_index"))] #[test] -fn test_for() -> Result<(), EvalAltResult> { +fn test_for_array() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); let script = r" @@ -18,10 +18,39 @@ fn test_for() -> Result<(), EvalAltResult> { sum2 += x; } + for x in range(1, 6, 3) { + sum2 += x; + } + sum1 + sum2 "; - assert_eq!(engine.eval::(script)?, 30); + assert_eq!(engine.eval::(script)?, 35); + + Ok(()) +} + +#[cfg(not(feature = "no_object"))] +#[test] +fn test_for_object() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + let script = r#" + let sum = 0; + let keys = ""; + let map = #{a: 1, b: 2, c: 3}; + + for key in keys(map) { + keys += key; + } + for value in values(map) { + sum += value; + } + + keys.len() + sum + "#; + + assert_eq!(engine.eval::(script)?, 9); Ok(()) } diff --git a/tests/maps.rs b/tests/maps.rs index 7a17f1a9..5ed42519 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -100,3 +100,26 @@ fn test_map_return() -> Result<(), EvalAltResult> { Ok(()) } + +#[test] +fn test_map_for() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + assert_eq!( + engine.eval::( + r#" + let map = #{a: 1, b: true, c: 123.456}; + let s = ""; + + for key in keys(map) { + s += key; + } + + s.len() + "# + )?, + 3 + ); + + Ok(()) +} From d1cffac4200178cd2e855e980c8a5913c4301103 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 4 Apr 2020 13:05:20 +0800 Subject: [PATCH 12/20] Reduce overhead of `Engine` by not creating hash maps until used. --- src/api.rs | 37 +++++++-- src/engine.rs | 205 +++++++++++++++++++++++++++++------------------- src/optimize.rs | 8 +- 3 files changed, 160 insertions(+), 90 deletions(-) diff --git a/src/api.rs b/src/api.rs index 2fdcd33e..5e140fef 100644 --- a/src/api.rs +++ b/src/api.rs @@ -2,7 +2,7 @@ use crate::any::{Any, AnyExt, Dynamic}; use crate::call::FuncArgs; -use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec}; +use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec, FunctionsLib}; use crate::error::ParseError; use crate::fn_register::RegisterFn; use crate::parser::{lex, parse, parse_global_expr, FnDef, Position, AST}; @@ -15,6 +15,7 @@ use crate::optimize::optimize_into_ast; use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, + collections::HashMap, string::{String, ToString}, sync::Arc, vec::Vec, @@ -68,7 +69,10 @@ impl<'e> Engine<'e> { args, }; - self.functions.insert(spec, f); + if self.functions.is_none() { + self.functions = Some(HashMap::new()); + } + self.functions.as_mut().unwrap().insert(spec, f); } /// Register a custom type for use with the `Engine`. @@ -157,15 +161,28 @@ impl<'e> Engine<'e> { /// ``` #[cfg(not(feature = "no_object"))] pub fn register_type_with_name(&mut self, name: &str) { + if self.type_names.is_none() { + self.type_names = Some(HashMap::new()); + } + // Add the pretty-print type name into the map self.type_names + .as_mut() + .unwrap() .insert(type_name::().to_string(), name.to_string()); } /// Register an iterator adapter for a type with the `Engine`. /// This is an advanced feature. pub fn register_iterator(&mut self, f: F) { - self.type_iterators.insert(TypeId::of::(), Box::new(f)); + if self.type_iterators.is_none() { + self.type_iterators = Some(HashMap::new()); + } + + self.type_iterators + .as_mut() + .unwrap() + .insert(TypeId::of::(), Box::new(f)); } /// Register a getter function for a member of a registered type with the `Engine`. @@ -876,8 +893,12 @@ impl<'e> Engine<'e> { &mut self, functions: impl IntoIterator>, ) { + if self.fn_lib.is_none() { + self.fn_lib = Some(FunctionsLib::new()); + } + functions.into_iter().cloned().for_each(|f| { - self.fn_lib.add_or_replace_function(f); + self.fn_lib.as_mut().unwrap().add_or_replace_function(f); }); } @@ -965,7 +986,7 @@ impl<'e> Engine<'e> { /// ``` #[cfg(feature = "sync")] pub fn on_print(&mut self, callback: impl FnMut(&str) + Send + Sync + 'e) { - self.on_print = Box::new(callback); + self.on_print = Some(Box::new(callback)); } /// Override default action of `print` (print to stdout using `println!`) /// @@ -989,7 +1010,7 @@ impl<'e> Engine<'e> { /// ``` #[cfg(not(feature = "sync"))] pub fn on_print(&mut self, callback: impl FnMut(&str) + 'e) { - self.on_print = Box::new(callback); + self.on_print = Some(Box::new(callback)); } /// Override default action of `debug` (print to stdout using `println!`) @@ -1014,7 +1035,7 @@ impl<'e> Engine<'e> { /// ``` #[cfg(feature = "sync")] pub fn on_debug(&mut self, callback: impl FnMut(&str) + Send + Sync + 'e) { - self.on_debug = Box::new(callback); + self.on_debug = Some(Box::new(callback)); } /// Override default action of `debug` (print to stdout using `println!`) /// @@ -1038,6 +1059,6 @@ impl<'e> Engine<'e> { /// ``` #[cfg(not(feature = "sync"))] pub fn on_debug(&mut self, callback: impl FnMut(&str) + 'e) { - self.on_debug = Box::new(callback); + self.on_debug = Some(Box::new(callback)); } } diff --git a/src/engine.rs b/src/engine.rs index c6f81199..b4bc949d 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -150,10 +150,6 @@ impl 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() @@ -196,25 +192,25 @@ impl FunctionsLib { /// Currently, `Engine` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. pub struct Engine<'e> { /// A hashmap containing all compiled functions known to the engine. - pub(crate) functions: HashMap, Box>, + pub(crate) functions: Option, Box>>, /// A hashmap containing all script-defined functions. - pub(crate) fn_lib: FunctionsLib, + pub(crate) fn_lib: Option, /// A hashmap containing all iterators known to the engine. - pub(crate) type_iterators: HashMap>, + pub(crate) type_iterators: Option>>, /// A hashmap mapping type names to pretty-print names. - pub(crate) type_names: HashMap, + pub(crate) type_names: Option>, /// Closure for implementing the `print` command. #[cfg(feature = "sync")] - pub(crate) on_print: Box, + pub(crate) on_print: Option>, #[cfg(not(feature = "sync"))] - pub(crate) on_print: Box, + pub(crate) on_print: Option>, /// Closure for implementing the `debug` command. #[cfg(feature = "sync")] - pub(crate) on_debug: Box, + pub(crate) on_debug: Option>, #[cfg(not(feature = "sync"))] - pub(crate) on_debug: Box, + pub(crate) on_debug: Option>, /// Optimize the AST after compilation. #[cfg(not(feature = "no_optimize"))] @@ -241,12 +237,12 @@ impl Default for Engine<'_> { // Create the new scripting Engine let mut engine = Engine { - functions: HashMap::new(), - fn_lib: FunctionsLib::new(), - type_iterators: HashMap::new(), - type_names, - on_print: Box::new(default_print), // default print/debug implementations - on_debug: Box::new(default_print), + functions: None, + fn_lib: None, + type_iterators: None, + type_names: Some(type_names), + on_print: Some(Box::new(default_print)), // default print/debug implementations + on_debug: Some(Box::new(default_print)), #[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "optimize_full"))] @@ -307,6 +303,35 @@ impl Engine<'_> { Default::default() } + /// Create a new `Engine` with minimal configurations - i.e. without pretty-print type names etc. + pub fn new_raw() -> Self { + let mut engine = Engine { + functions: None, + fn_lib: None, + type_iterators: None, + type_names: None, + on_print: None, + on_debug: None, + + #[cfg(not(feature = "no_optimize"))] + #[cfg(not(feature = "optimize_full"))] + optimization_level: OptimizationLevel::Simple, + + #[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(); + + #[cfg(not(feature = "no_stdlib"))] + engine.register_stdlib(); // Register the standard library when no_stdlib is not set + + engine + } + /// Control whether and how the `Engine` will optimize an AST after compilation /// /// Not available under the `no_optimize` feature. @@ -335,9 +360,13 @@ impl Engine<'_> { }; // Search built-in's and external functions - if let Some(func) = self.functions.get(&spec) { - // Run external function - Ok(Some(func(args, pos)?)) + if let Some(ref functions) = self.functions { + if let Some(func) = functions.get(&spec) { + // Run external function + Ok(Some(func(args, pos)?)) + } else { + Ok(None) + } } else { Ok(None) } @@ -353,26 +382,28 @@ impl Engine<'_> { level: usize, ) -> Result { // First search in script-defined functions (can override built-in) - if let Some(fn_def) = self.fn_lib.get_function(fn_name, args.len()) { - let mut scope = Scope::new(); + if let Some(ref fn_lib) = self.fn_lib { + if let Some(fn_def) = fn_lib.get_function(fn_name, args.len()) { + let mut scope = Scope::new(); - scope.extend( - // Put arguments into scope as variables - fn_def - .params - .iter() - .zip(args.iter().map(|x| (*x).into_dynamic())) - .map(|(name, value)| (name, ScopeEntryType::Normal, value)), - ); + scope.extend( + // Put arguments into scope as variables + fn_def + .params + .iter() + .zip(args.iter().map(|x| (*x).into_dynamic())) + .map(|(name, value)| (name, ScopeEntryType::Normal, value)), + ); - // Evaluate the function at one higher level of call depth - return self - .eval_stmt(&mut scope, &fn_def.body, level + 1) - .or_else(|err| match err { - // Convert return statement to return value - EvalAltResult::Return(x, _) => Ok(x), - err => Err(err.set_position(pos)), - }); + // Evaluate the function at one higher level of call depth + return self.eval_stmt(&mut scope, &fn_def.body, level + 1).or_else( + |err| match err { + // Convert return statement to return value + EvalAltResult::Return(x, _) => Ok(x), + err => Err(err.set_position(pos)), + }, + ); + } } let spec = FnSpec { @@ -388,20 +419,25 @@ impl Engine<'_> { } // Search built-in's and external functions - if let Some(func) = self.functions.get(&spec) { - // Run external function - let result = func(args, pos)?; + if let Some(ref functions) = self.functions { + if let Some(func) = functions.get(&spec) { + // Run external function + let result = func(args, pos)?; - // See if the function match print/debug (which requires special processing) - return Ok(match fn_name { - KEYWORD_PRINT => { - 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, - }); + // See if the function match print/debug (which requires special processing) + return Ok(match fn_name { + KEYWORD_PRINT if self.on_print.is_some() => { + self.on_print.as_deref_mut().unwrap()(cast_to_string(result.as_ref(), pos)?) + .into_dynamic() + } + KEYWORD_DEBUG if self.on_debug.is_some() => { + self.on_debug.as_deref_mut().unwrap()(cast_to_string(result.as_ref(), pos)?) + .into_dynamic() + } + KEYWORD_PRINT | KEYWORD_DEBUG => ().into_dynamic(), + _ => result, + }); + } } if let Some(prop) = extract_prop_from_getter(fn_name) { @@ -1192,12 +1228,13 @@ impl Engine<'_> { Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => { // Has a system function an override? fn has_override(engine: &Engine, name: &str) -> bool { - let spec = FnSpec { - name: name.into(), - args: vec![TypeId::of::()], - }; - - engine.functions.contains_key(&spec) || engine.fn_lib.has_function(name, 1) + (engine.functions.is_some() && { + engine.functions.as_ref().unwrap().contains_key(&FnSpec { + name: name.into(), + args: vec![TypeId::of::()], + }) + }) || (engine.fn_lib.is_some() + && engine.fn_lib.as_ref().unwrap().has_function(name, 1)) } match fn_name.as_str() { @@ -1399,27 +1436,31 @@ impl Engine<'_> { 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(), ()); + if let Some(ref type_iterators) = self.type_iterators { + if let Some(iter_fn) = type_iterators.get(&tid) { + scope.push(name.clone(), ()); - let entry = ScopeSource { - name, - index: scope.len() - 1, - typ: ScopeEntryType::Normal, - }; + let entry = ScopeSource { + name, + index: scope.len() - 1, + typ: ScopeEntryType::Normal, + }; - for a in iter_fn(&arr) { - *scope.get_mut(entry) = a; + for a in iter_fn(&arr) { + *scope.get_mut(entry) = a; - match self.eval_stmt(scope, body, level) { - Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), - Err(EvalAltResult::ErrorLoopBreak(true, _)) => break, - Err(x) => return Err(x), + match self.eval_stmt(scope, body, level) { + Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), + Err(EvalAltResult::ErrorLoopBreak(true, _)) => break, + Err(x) => return Err(x), + } } - } - scope.rewind(scope.len() - 1); - Ok(().into_dynamic()) + scope.rewind(scope.len() - 1); + Ok(().into_dynamic()) + } else { + Err(EvalAltResult::ErrorFor(expr.position())) + } } else { Err(EvalAltResult::ErrorFor(expr.position())) } @@ -1481,15 +1522,21 @@ impl Engine<'_> { /// Map a type_name into a pretty-print name pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { - self.type_names - .get(name) - .map(String::as_str) - .unwrap_or(name) + if self.type_names.is_none() { + name + } else { + self.type_names + .as_ref() + .unwrap() + .get(name) + .map(String::as_str) + .unwrap_or(name) + } } /// Clean up all script-defined functions within the `Engine`. pub fn clear_functions(&mut self) { - self.fn_lib.clear(); + self.fn_lib = None; } } diff --git a/src/optimize.rs b/src/optimize.rs index f0e915bf..9f9f101b 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -451,9 +451,11 @@ 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.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); + if let Some(ref fn_lib) = state.engine.fn_lib { + if 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(); From 29150faef2dfd15d261910622b7e4fc76e61d5da Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 4 Apr 2020 22:00:44 +0800 Subject: [PATCH 13/20] Improve AST evaluation efficiency by sharing functions. --- Cargo.toml | 2 +- README.md | 24 +++-- examples/repl.rs | 44 +++++---- examples/rhai_runner.rs | 2 +- src/api.rs | 193 +++++++++++++++++++++++----------------- src/call.rs | 35 +------- src/engine.rs | 135 +++++++++++++++++++++++----- src/optimize.rs | 35 +++++--- src/parser.rs | 101 ++++++++++++++++----- tests/call_fn.rs | 15 ++-- 10 files changed, 374 insertions(+), 212 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f852d289..43339787 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ categories = [ "no-std", "embedded", "parser-implementations" ] num-traits = "*" [features] -#default = ["no_function", "no_index", "no_object", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize"] +#default = ["no_function", "no_index", "no_object", "no_float", "only_i32", "no_stdlib", "unchecked", "no_optimize", "sync"] default = [] unchecked = [] # unchecked arithmetic no_stdlib = [] # no standard library of utility functions diff --git a/README.md b/README.md index 86a6482f..dbf37052 100644 --- a/README.md +++ b/README.md @@ -209,12 +209,12 @@ Compiling a script file is also supported: let ast = engine.compile_file("hello_world.rhai".into())?; ``` -Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - via `call_fn`: +Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - via `call_fn` +or its cousins `call_fn1` (one argument) and `call_fn0` (no argument). ```rust -// Define a function in a script and load it into the Engine. -// Pass true to 'retain_functions' otherwise these functions will be cleared at the end of consume() -engine.consume(true, +// Define functions in a script. +let ast = engine.compile(true, r" // a function with two parameters: String and i64 fn hello(x, y) { @@ -225,18 +225,26 @@ engine.consume(true, fn hello(x) { x * 2 } + + // this one takes no parameters + fn hello() { + 42 + } ")?; -// Evaluate the function in the AST, passing arguments into the script as a tuple +// Evaluate a function defined in the script, passing arguments into the script as a tuple // if there are more than one. Beware, arguments must be of the correct types because // Rhai does not have built-in type conversions. If arguments of the wrong types are passed, // the Engine will not find the function. -let result: i64 = engine.call_fn("hello", &ast, ( String::from("abc"), 123_i64 ) )?; +let result: i64 = engine.call_fn(&ast, "hello", ( String::from("abc"), 123_i64 ) )?; // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple -let result: i64 = engine.call_fn("hello", 123_i64)? -// ^^^^^^^ calls 'hello' with one parameter (no need for tuple) +let result: i64 = engine.call_fn1(&ast, "hello", 123_i64)? +// ^^^^^^^^ use 'call_fn1' for one argument + +let result: i64 = engine.call_fn0(&ast, "hello")? +// ^^^^^^^^ use 'call_fn0' for no arguments ``` Evaluate expressions only diff --git a/examples/repl.rs b/examples/repl.rs index def1b310..e166b54c 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -77,8 +77,9 @@ fn main() { let mut scope = Scope::new(); let mut input = String::new(); - let mut ast_u: Option = None; - let mut ast: Option = None; + let mut main_ast = AST::new(); + let mut ast_u = AST::new(); + let mut ast = AST::new(); println!("Rhai REPL tool"); println!("=============="); @@ -112,6 +113,10 @@ fn main() { let script = input.trim(); + if script.is_empty() { + continue; + } + // Implement standard commands match script { "help" => { @@ -120,21 +125,13 @@ fn main() { } "exit" | "quit" => break, // quit "astu" => { - if matches!(&ast_u, Some(_)) { - // print the last un-optimized AST - println!("{:#?}", ast_u.as_ref().unwrap()); - } else { - println!("()"); - } + // print the last un-optimized AST + println!("{:#?}", &ast_u); continue; } "ast" => { - if matches!(&ast, Some(_)) { - // print the last AST - println!("{:#?}", ast.as_ref().unwrap()); - } else { - println!("()"); - } + // print the last AST + println!("{:#?}", &ast); continue; } _ => (), @@ -144,12 +141,12 @@ fn main() { .compile_with_scope(&scope, &script) .map_err(EvalAltResult::ErrorParsing) .and_then(|r| { - ast_u = Some(r); + ast_u = r; #[cfg(not(feature = "no_optimize"))] { engine.set_optimization_level(OptimizationLevel::Full); - ast = Some(engine.optimize_ast(&scope, ast_u.as_ref().unwrap())); + ast = engine.optimize_ast(&scope, &ast_u); engine.set_optimization_level(OptimizationLevel::None); } @@ -158,12 +155,21 @@ fn main() { ast = ast_u.clone(); } - engine - .consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap()) + // Merge the AST into the main + main_ast = main_ast.merge(&ast); + + // Evaluate + let result = engine + .consume_ast_with_scope(&mut scope, &main_ast) .or_else(|err| match err { EvalAltResult::Return(_, _) => Ok(()), err => Err(err), - }) + }); + + // Throw away all the statements, leaving only the functions + main_ast.retain_functions(); + + result }) { println!(); diff --git a/examples/rhai_runner.rs b/examples/rhai_runner.rs index 180f51d6..9a5cfe59 100644 --- a/examples/rhai_runner.rs +++ b/examples/rhai_runner.rs @@ -72,7 +72,7 @@ fn main() { exit(1); } - if let Err(err) = engine.consume(false, &contents) { + if let Err(err) = engine.consume(&contents) { eprintln!("{:=<1$}", "", filename.len()); eprintln!("{}", filename); eprintln!("{:=<1$}", "", filename.len()); diff --git a/src/api.rs b/src/api.rs index 5e140fef..ed14ada3 100644 --- a/src/api.rs +++ b/src/api.rs @@ -2,10 +2,10 @@ use crate::any::{Any, AnyExt, Dynamic}; use crate::call::FuncArgs; -use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec, FunctionsLib}; +use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec}; use crate::error::ParseError; use crate::fn_register::RegisterFn; -use crate::parser::{lex, parse, parse_global_expr, FnDef, Position, AST}; +use crate::parser::{lex, parse, parse_global_expr, Position, AST}; use crate::result::EvalAltResult; use crate::scope::Scope; @@ -17,7 +17,6 @@ use crate::stdlib::{ boxed::Box, collections::HashMap, string::{String, ToString}, - sync::Arc, vec::Vec, }; #[cfg(not(feature = "no_std"))] @@ -746,7 +745,7 @@ impl<'e> Engine<'e> { scope: &mut Scope, ast: &AST, ) -> Result { - self.eval_ast_with_scope_raw(scope, false, ast)? + self.eval_ast_with_scope_raw(scope, ast)? .try_cast::() .map_err(|a| { EvalAltResult::ErrorMismatchOutputType( @@ -759,16 +758,11 @@ impl<'e> Engine<'e> { pub(crate) fn eval_ast_with_scope_raw( &mut self, scope: &mut Scope, - retain_functions: bool, ast: &AST, ) -> Result { - if !retain_functions { - self.clear_functions(); - } - let statements = { let AST(statements, functions) = ast; - self.load_script_functions(functions); + self.fn_lib = Some(functions.clone()); statements }; @@ -776,9 +770,7 @@ impl<'e> Engine<'e> { .iter() .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0)); - if !retain_functions { - self.clear_functions(); - } + self.fn_lib = None; result.or_else(|err| match err { EvalAltResult::Return(out, _) => Ok(out), @@ -788,52 +780,33 @@ impl<'e> Engine<'e> { /// Evaluate a file, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ - /// and not cleared from run to run. #[cfg(not(feature = "no_std"))] - pub fn consume_file( - &mut self, - retain_functions: bool, - path: PathBuf, - ) -> Result<(), EvalAltResult> { - Self::read_file(path).and_then(|contents| self.consume(retain_functions, &contents)) + pub fn consume_file(&mut self, path: PathBuf) -> Result<(), EvalAltResult> { + Self::read_file(path).and_then(|contents| self.consume(&contents)) } /// Evaluate a file with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ - /// and not cleared from run to run. #[cfg(not(feature = "no_std"))] pub fn consume_file_with_scope( &mut self, scope: &mut Scope, - retain_functions: bool, path: PathBuf, ) -> Result<(), EvalAltResult> { - Self::read_file(path) - .and_then(|contents| self.consume_with_scope(scope, retain_functions, &contents)) + Self::read_file(path).and_then(|contents| self.consume_with_scope(scope, &contents)) } /// Evaluate a string, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ - /// and not cleared from run to run. - pub fn consume(&mut self, retain_functions: bool, input: &str) -> Result<(), EvalAltResult> { - self.consume_with_scope(&mut Scope::new(), retain_functions, input) + pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> { + self.consume_with_scope(&mut Scope::new(), input) } /// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ - /// and not cleared from run to run. pub fn consume_with_scope( &mut self, scope: &mut Scope, - retain_functions: bool, input: &str, ) -> Result<(), EvalAltResult> { let tokens_stream = lex(input); @@ -841,36 +814,25 @@ impl<'e> Engine<'e> { let ast = parse(&mut tokens_stream.peekable(), self, scope) .map_err(EvalAltResult::ErrorParsing)?; - self.consume_ast_with_scope(scope, retain_functions, &ast) + self.consume_ast_with_scope(scope, &ast) } /// Evaluate an AST, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ - /// and not cleared from run to run. - pub fn consume_ast(&mut self, retain_functions: bool, ast: &AST) -> Result<(), EvalAltResult> { - self.consume_ast_with_scope(&mut Scope::new(), retain_functions, ast) + pub fn consume_ast(&mut self, ast: &AST) -> Result<(), EvalAltResult> { + self.consume_ast_with_scope(&mut Scope::new(), ast) } /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - /// - /// If `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ - /// and not cleared from run to run. pub fn consume_ast_with_scope( &mut self, scope: &mut Scope, - retain_functions: bool, ast: &AST, ) -> Result<(), EvalAltResult> { - if !retain_functions { - self.clear_functions(); - } - let statements = { let AST(ref statements, ref functions) = ast; - self.load_script_functions(functions); + self.fn_lib = Some(functions.clone()); statements }; @@ -878,9 +840,7 @@ impl<'e> Engine<'e> { .iter() .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0)); - if !retain_functions { - self.clear_functions(); - } + self.fn_lib = None; result.map(|_| ()).or_else(|err| match err { EvalAltResult::Return(_, _) => Ok(()), @@ -888,21 +848,7 @@ impl<'e> Engine<'e> { }) } - /// Load a list of functions into the Engine. - pub(crate) fn load_script_functions<'a>( - &mut self, - functions: impl IntoIterator>, - ) { - if self.fn_lib.is_none() { - self.fn_lib = Some(FunctionsLib::new()); - } - - functions.into_iter().cloned().for_each(|f| { - self.fn_lib.as_mut().unwrap().add_or_replace_function(f); - }); - } - - /// Call a script function retained inside the Engine. + /// Call a script function defined in an `AST` with no argument. /// /// # Example /// @@ -915,11 +861,71 @@ impl<'e> Engine<'e> { /// /// let mut engine = Engine::new(); /// - /// // Set 'retain_functions' in 'consume' to keep the function definitions - /// engine.consume(true, "fn add(x, y) { len(x) + y }")?; + /// let ast = engine.compile("fn num() { 42 }")?; /// /// // Call the script-defined function - /// let result: i64 = engine.call_fn("add", (String::from("abc"), 123_i64))?; + /// let result: i64 = engine.call_fn0(&ast, "num")?; + /// + /// assert_eq!(result, 42); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + pub fn call_fn0(&mut self, ast: &AST, name: &str) -> Result { + self.call_fn_internal(ast, name, vec![]) + } + + /// Call a script function defined in an `AST` with one argument. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// # #[cfg(not(feature = "no_stdlib"))] + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// let ast = engine.compile("fn inc(x) { x + 1 }")?; + /// + /// // Call the script-defined function + /// let result: i64 = engine.call_fn1(&ast, "inc", 123_i64)?; + /// + /// assert_eq!(result, 124); + /// # } + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_function"))] + pub fn call_fn1( + &mut self, + ast: &AST, + name: &str, + arg: A, + ) -> Result { + self.call_fn_internal(ast, name, vec![arg.into_dynamic()]) + } + + /// Call a script function defined in an `AST` with multiple arguments. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), rhai::EvalAltResult> { + /// # #[cfg(not(feature = "no_stdlib"))] + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// let ast = engine.compile("fn add(x, y) { len(x) + y }")?; + /// + /// // Call the script-defined function + /// let result: i64 = engine.call_fn(&ast, "add", (String::from("abc"), 123_i64))?; /// /// assert_eq!(result, 126); /// # } @@ -929,20 +935,37 @@ impl<'e> Engine<'e> { #[cfg(not(feature = "no_function"))] pub fn call_fn( &mut self, + ast: &AST, name: &str, args: A, ) -> Result { - let mut values = args.into_vec(); + self.call_fn_internal(ast, name, args.into_vec()) + } + + #[cfg(not(feature = "no_function"))] + fn call_fn_internal( + &mut self, + ast: &AST, + name: &str, + mut values: Vec, + ) -> Result { let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); - self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)? + self.fn_lib = Some(ast.1.clone()); + + let result = self + .call_fn_raw(name, &mut arg_values, None, Position::none(), 0)? .try_cast() .map_err(|a| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name((*a).type_name()).into(), Position::none(), ) - }) + }); + + self.fn_lib = None; + + result } /// Optimize the `AST` with constants defined in an external Scope. @@ -958,10 +981,12 @@ impl<'e> Engine<'e> { /// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running. #[cfg(not(feature = "no_optimize"))] pub fn optimize_ast(&self, scope: &Scope, ast: &AST) -> AST { - let statements = ast.0.clone(); - let functions = ast.1.iter().map(|f| (**f).clone()).collect(); - - optimize_into_ast(self, scope, statements, functions) + optimize_into_ast( + self, + scope, + ast.0.clone(), + ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect(), + ) } /// Override default action of `print` (print to stdout using `println!`) @@ -978,7 +1003,7 @@ impl<'e> Engine<'e> { /// /// // Override action of 'print' function /// engine.on_print(|s| result.push_str(s)); - /// engine.consume(false, "print(40 + 2);")?; + /// engine.consume("print(40 + 2);")?; /// } /// assert_eq!(result, "42"); /// # Ok(()) @@ -1002,7 +1027,7 @@ impl<'e> Engine<'e> { /// /// // Override action of 'print' function /// engine.on_print(|s| result.push_str(s)); - /// engine.consume(false, "print(40 + 2);")?; + /// engine.consume("print(40 + 2);")?; /// } /// assert_eq!(result, "42"); /// # Ok(()) @@ -1027,7 +1052,7 @@ impl<'e> Engine<'e> { /// /// // Override action of 'debug' function /// engine.on_debug(|s| result.push_str(s)); - /// engine.consume(false, r#"debug("hello");"#)?; + /// engine.consume(r#"debug("hello");"#)?; /// } /// assert_eq!(result, "\"hello\""); /// # Ok(()) @@ -1051,7 +1076,7 @@ impl<'e> Engine<'e> { /// /// // Override action of 'debug' function /// engine.on_debug(|s| result.push_str(s)); - /// engine.consume(false, r#"debug("hello");"#)?; + /// engine.consume(r#"debug("hello");"#)?; /// } /// assert_eq!(result, "\"hello\""); /// # Ok(()) diff --git a/src/call.rs b/src/call.rs index c1147a53..ac33e2c7 100644 --- a/src/call.rs +++ b/src/call.rs @@ -3,10 +3,6 @@ #![allow(non_snake_case)] use crate::any::{Any, Dynamic}; -use crate::parser::INT; - -#[cfg(not(feature = "no_index"))] -use crate::engine::Array; use crate::stdlib::{string::String, vec, vec::Vec}; @@ -18,36 +14,7 @@ pub trait FuncArgs { 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),*) => { - $( - impl FuncArgs for $p { - fn into_vec(self) -> Vec { - vec![self.into_dynamic()] - } - } - )* - }; -} - -impl_std_args!(String, char, bool); - -#[cfg(not(feature = "no_index"))] -impl_std_args!(Array); - -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] -impl_std_args!(u8, i8, u16, i16, u32, i32, u64, i64); - -#[cfg(any(feature = "only_i32", feature = "only_i64"))] -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 +// Macro to implement `FuncArgs` for tuples of standard types (each can be /// converted into `Dynamic`). macro_rules! impl_args { ($($p:ident),*) => { diff --git a/src/engine.rs b/src/engine.rs index b4bc949d..e1ba5401 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,7 +1,7 @@ //! Main module defining the script evaluation `Engine`. use crate::any::{Any, AnyExt, Dynamic, Variant}; -use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, INT}; +use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, AST, INT}; use crate::result::EvalAltResult; use crate::scope::{EntryRef as ScopeSource, EntryType as ScopeEntryType, Scope}; @@ -16,6 +16,8 @@ use crate::stdlib::{ collections::HashMap, format, iter::once, + ops::{Deref, DerefMut}, + rc::Rc, string::{String, ToString}, sync::Arc, vec, @@ -130,8 +132,11 @@ pub struct FnSpec<'a> { /// to search for it. /// /// So instead this is implemented as a sorted list and binary searched. -#[derive(Debug)] -pub struct FunctionsLib(Vec>); +#[derive(Debug, Clone)] +pub struct FunctionsLib( + #[cfg(feature = "sync")] Vec>, + #[cfg(not(feature = "sync"))] Vec>, +); impl FnDef { /// Function to order two FnDef records, for binary search. @@ -150,28 +155,75 @@ impl FunctionsLib { pub fn new() -> Self { FunctionsLib(Vec::new()) } + /// Create a new `FunctionsLib` from a collection of `FnDef`. + pub fn from_vec(vec: Vec) -> Self { + #[cfg(feature = "sync")] + { + FunctionsLib(vec.into_iter().map(Arc::new).collect()) + } + #[cfg(not(feature = "sync"))] + { + FunctionsLib(vec.into_iter().map(Rc::new).collect()) + } + } /// 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> { + pub fn get_function(&self, name: &str, params: usize) -> Option<&FnDef> { if let Ok(n) = self.0.binary_search_by(|f| f.compare(name, params)) { - Some(self.0[n].clone()) + Some(&self.0[n]) } else { None } } + /// Merge another `FunctionsLib` into this `FunctionsLib`. + pub fn merge(&self, other: &Self) -> Self { + if self.is_empty() { + other.clone() + } else if other.is_empty() { + self.clone() + } else { + let mut functions = self.clone(); + + other.iter().cloned().for_each(|fn_def| { + 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); + } + }); + + functions + } + } +} + +impl Deref for FunctionsLib { + #[cfg(feature = "sync")] + type Target = Vec>; + #[cfg(not(feature = "sync"))] + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for FunctionsLib { + #[cfg(feature = "sync")] + fn deref_mut(&mut self) -> &mut Vec> { + &mut self.0 + } + #[cfg(not(feature = "sync"))] + fn deref_mut(&mut self) -> &mut Vec> { + &mut self.0 + } } /// Rhai main scripting engine. @@ -193,8 +245,14 @@ impl FunctionsLib { pub struct Engine<'e> { /// A hashmap containing all compiled functions known to the engine. pub(crate) functions: Option, Box>>, + /// A hashmap containing all script-defined functions. - pub(crate) fn_lib: Option, + #[cfg(feature = "sync")] + pub(crate) fn_lib: Option>, + /// A hashmap containing all script-defined functions. + #[cfg(not(feature = "sync"))] + pub(crate) fn_lib: Option>, + /// A hashmap containing all iterators known to the engine. pub(crate) type_iterators: Option>>, /// A hashmap mapping type names to pretty-print names. @@ -203,12 +261,14 @@ pub struct Engine<'e> { /// Closure for implementing the `print` command. #[cfg(feature = "sync")] pub(crate) on_print: Option>, + /// Closure for implementing the `print` command. #[cfg(not(feature = "sync"))] pub(crate) on_print: Option>, /// Closure for implementing the `debug` command. #[cfg(feature = "sync")] pub(crate) on_debug: Option>, + /// Closure for implementing the `debug` command. #[cfg(not(feature = "sync"))] pub(crate) on_debug: Option>, @@ -382,8 +442,8 @@ impl Engine<'_> { level: usize, ) -> Result { // First search in script-defined functions (can override built-in) - if let Some(ref fn_lib) = self.fn_lib { - if let Some(fn_def) = fn_lib.get_function(fn_name, args.len()) { + if let Some(ref fn_lib_arc) = self.fn_lib { + if let Some(fn_def) = fn_lib_arc.clone().get_function(fn_name, args.len()) { let mut scope = Scope::new(); scope.extend( @@ -1276,6 +1336,7 @@ impl Engine<'_> { let pos = args_expr_list[0].position(); let r = self.eval_expr(scope, &args_expr_list[0], level)?; + // Get the script text by evaluating the expression let script = r.downcast_ref::() .map(String::as_str) @@ -1286,8 +1347,9 @@ impl Engine<'_> { ) })?; + // Compile the script text #[cfg(not(feature = "no_optimize"))] - let ast = { + let mut ast = { let orig_optimization_level = self.optimization_level; self.set_optimization_level(OptimizationLevel::None); @@ -1298,11 +1360,38 @@ impl Engine<'_> { }; #[cfg(feature = "no_optimize")] - let ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?; + let mut ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?; - Ok(self - .eval_ast_with_scope_raw(scope, true, &ast) - .map_err(|err| err.set_position(pos))?) + // If new functions are defined, merge it into the current functions library + let merged = AST( + ast.0, + if let Some(ref fn_lib) = self.fn_lib { + #[cfg(feature = "sync")] + { + Arc::new(fn_lib.as_ref().merge(&ast.1)) + } + #[cfg(not(feature = "sync"))] + { + Rc::new(fn_lib.as_ref().merge(&ast.1)) + } + } else { + ast.1 + }, + ); + + // Evaluate the AST + let result = self + .eval_ast_with_scope_raw(scope, &merged) + .map_err(|err| err.set_position(pos)); + + // Update the new functions library if there are new functions + self.fn_lib = if !merged.1.is_empty() { + Some(merged.1) + } else { + None + }; + + Ok(result?) } // Normal function call diff --git a/src/optimize.rs b/src/optimize.rs index 9f9f101b..5fa2769b 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -2,13 +2,15 @@ use crate::any::{Any, Dynamic}; use crate::engine::{ - Engine, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF, + Engine, FunctionsLib, 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::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::stdlib::{ boxed::Box, + rc::Rc, string::{String, ToString}, sync::Arc, vec, @@ -451,8 +453,8 @@ 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 let Some(ref fn_lib) = state.engine.fn_lib { - if fn_lib.has_function(&id, args.len()) { + if let Some(ref fn_lib_arc) = state.engine.fn_lib { + if fn_lib_arc.has_function(&id, args.len()) { // A script-defined function overrides the built-in function - do not make the call return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos); } @@ -584,15 +586,10 @@ pub fn optimize_into_ast( statements: Vec, functions: Vec, ) -> AST { - AST( - match engine.optimization_level { - OptimizationLevel::None => statements, - OptimizationLevel::Simple | OptimizationLevel::Full => { - optimize(statements, engine, &scope) - } - }, + let fn_lib = FunctionsLib::from_vec( functions - .into_iter() + .iter() + .cloned() .map(|mut fn_def| { if engine.optimization_level != OptimizationLevel::None { let pos = fn_def.body.position(); @@ -612,9 +609,21 @@ pub fn optimize_into_ast( stmt => stmt, }; } - - Arc::new(fn_def) + fn_def }) .collect(), + ); + + AST( + match engine.optimization_level { + OptimizationLevel::None => statements, + OptimizationLevel::Simple | OptimizationLevel::Full => { + optimize(statements, engine, &scope) + } + }, + #[cfg(feature = "sync")] + Arc::new(fn_lib), + #[cfg(not(feature = "sync"))] + Rc::new(fn_lib), ) } diff --git a/src/parser.rs b/src/parser.rs index 0ec77e86..d125878b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,7 @@ //! Main module defining the lexer and parser. use crate::any::{Any, AnyExt, Dynamic}; -use crate::engine::Engine; +use crate::engine::{Engine, FunctionsLib}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -16,6 +16,7 @@ use crate::stdlib::{ fmt, format, iter::Peekable, ops::Add, + rc::Rc, str::Chars, str::FromStr, string::{String, ToString}, @@ -165,10 +166,19 @@ impl fmt::Debug for Position { /// /// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. #[derive(Debug, Clone)] -pub struct AST(pub(crate) Vec, pub(crate) Vec>); +pub struct AST( + pub(crate) Vec, + #[cfg(feature = "sync")] pub(crate) Arc, + #[cfg(not(feature = "sync"))] pub(crate) Rc, +); impl AST { - /// Merge two `AST` into one. Both `AST`'s are consumed and a new, merged, version + /// Create a new `AST`. + pub fn new() -> Self { + Default::default() + } + + /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version /// is returned. /// /// The second `AST` is simply appended to the end of the first _without any processing_. @@ -192,10 +202,10 @@ impl AST { /// let ast1 = engine.compile(r#"fn foo(x) { 42 + x } foo(1)"#)?; /// let ast2 = engine.compile(r#"fn foo(n) { "hello" + n } foo("!")"#)?; /// - /// let ast = ast1.merge(ast2); // Merge 'ast2' into 'ast1' + /// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1' /// /// // Notice that using the '+' operator also works: - /// // let ast = ast1 + ast2; + /// // let ast = &ast1 + &ast2; /// /// // 'ast' is essentially: /// // @@ -210,29 +220,62 @@ impl AST { /// # Ok(()) /// # } /// ``` - pub fn merge(self, mut other: Self) -> Self { - let Self(mut ast, mut functions) = self; + pub fn merge(&self, other: &Self) -> Self { + let Self(ast, 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); + let ast = match (ast.is_empty(), other.0.is_empty()) { + (false, false) => { + let mut ast = ast.clone(); + ast.extend(other.0.iter().cloned()); + ast } - } + (false, true) => ast.clone(), + (true, false) => other.0.clone(), + (true, true) => vec![], + }; - Self(ast, functions) + #[cfg(feature = "sync")] + { + Self(ast, Arc::new(functions.merge(other.1.as_ref()))) + } + #[cfg(not(feature = "sync"))] + { + Self(ast, Rc::new(functions.merge(other.1.as_ref()))) + } + } + + /// Clear all function definitions in the `AST`. + pub fn clear_functions(&mut self) { + #[cfg(feature = "sync")] + { + self.1 = Arc::new(FunctionsLib::new()); + } + #[cfg(not(feature = "sync"))] + { + self.1 = Rc::new(FunctionsLib::new()); + } + } + + /// Clear all statements in the `AST`, leaving only function definitions. + pub fn retain_functions(&mut self) { + self.0 = vec![]; } } -impl Add for AST { - type Output = Self; +impl Default for AST { + fn default() -> Self { + #[cfg(feature = "sync")] + { + Self(vec![], Arc::new(FunctionsLib::new())) + } + #[cfg(not(feature = "sync"))] + { + Self(vec![], Rc::new(FunctionsLib::new())) + } + } +} +impl Add for &AST { + type Output = AST; fn add(self, rhs: Self) -> Self::Output { self.merge(rhs) @@ -2592,7 +2635,17 @@ pub fn parse_global_expr<'a, 'e>( // // Do not optimize AST if `no_optimize` #[cfg(feature = "no_optimize")] - AST(vec![Stmt::Expr(Box::new(expr))], vec![]), + AST( + vec![Stmt::Expr(Box::new(expr))], + #[cfg(feature = "sync")] + { + Arc::new(FunctionsLib::new()) + }, + #[cfg(not(feature = "sync"))] + { + Rc::new(FunctionsLib::new()) + }, + ), ) } @@ -2667,7 +2720,7 @@ pub fn parse<'a, 'e>( // // Do not optimize AST if `no_optimize` #[cfg(feature = "no_optimize")] - AST(statements, functions.into_iter().map(Arc::new).collect()), + AST(statements, Arc::new(FunctionsLib::from_vec(functions))), ) } diff --git a/tests/call_fn.rs b/tests/call_fn.rs index c5b8b307..555219f2 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -22,8 +22,7 @@ fn test_fn() -> Result<(), EvalAltResult> { fn test_call_fn() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - engine.consume( - true, + let ast = engine.compile( r" fn hello(x, y) { x + y @@ -31,14 +30,20 @@ fn test_call_fn() -> Result<(), EvalAltResult> { fn hello(x) { x * 2 } - ", + fn hello() { + 42 + } + ", )?; - let r: i64 = engine.call_fn("hello", (42 as INT, 123 as INT))?; + let r: i64 = engine.call_fn(&ast, "hello", (42 as INT, 123 as INT))?; assert_eq!(r, 165); - let r: i64 = engine.call_fn("hello", 123 as INT)?; + let r: i64 = engine.call_fn1(&ast, "hello", 123 as INT)?; assert_eq!(r, 246); + let r: i64 = engine.call_fn0(&ast, "hello")?; + assert_eq!(r, 42); + Ok(()) } From ae9a975576620a32c719339fa57d4f7e5c344c93 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 5 Apr 2020 09:56:52 +0800 Subject: [PATCH 14/20] Make downcast_ref, downcast_mut and is public. --- src/any.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/any.rs b/src/any.rs index 814e369a..7e7494e8 100644 --- a/src/any.rs +++ b/src/any.rs @@ -92,15 +92,13 @@ impl Any for T { impl Variant { /// Is this `Variant` a specific type? - pub(crate) fn is(&self) -> bool { - let t = TypeId::of::(); - let boxed = ::type_id(self); - - t == boxed + pub fn is(&self) -> bool { + TypeId::of::() == ::type_id(self) } /// Get a reference of a specific type to the `Variant`. - pub(crate) fn downcast_ref(&self) -> Option<&T> { + /// Returns `None` if the cast fails. + pub fn downcast_ref(&self) -> Option<&T> { if self.is::() { unsafe { Some(&*(self as *const Variant as *const T)) } } else { @@ -109,7 +107,8 @@ impl Variant { } /// Get a mutable reference of a specific type to the `Variant`. - pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { + /// Returns `None` if the cast fails. + pub fn downcast_mut(&mut self) -> Option<&mut T> { if self.is::() { unsafe { Some(&mut *(self as *mut Variant as *mut T)) } } else { From 3f247fd695c1656cf61e1451fdbe4d817ced2eb2 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 5 Apr 2020 12:17:31 +0800 Subject: [PATCH 15/20] Allow passing in custom Scope to call_fn. --- README.md | 12 +++-- src/api.rs | 55 +++++++++++++-------- src/engine.rs | 123 ++++++++++++++++++++++++++++++----------------- src/scope.rs | 1 + tests/call_fn.rs | 19 +++++--- 5 files changed, 134 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index dbf37052..97e27192 100644 --- a/README.md +++ b/README.md @@ -232,18 +232,22 @@ let ast = engine.compile(true, } ")?; +// A custom scope can also contain any variables/constants available to the functions +let mut scope = Scope::new(); + // Evaluate a function defined in the script, passing arguments into the script as a tuple // if there are more than one. Beware, arguments must be of the correct types because // Rhai does not have built-in type conversions. If arguments of the wrong types are passed, // the Engine will not find the function. -let result: i64 = engine.call_fn(&ast, "hello", ( String::from("abc"), 123_i64 ) )?; -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ put arguments in a tuple +let result: i64 = engine.call_fn(&mut scope, &ast, "hello", ( String::from("abc"), 123_i64 ) )?; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// put arguments in a tuple -let result: i64 = engine.call_fn1(&ast, "hello", 123_i64)? +let result: i64 = engine.call_fn1(&mut scope, &ast, "hello", 123_i64)? // ^^^^^^^^ use 'call_fn1' for one argument -let result: i64 = engine.call_fn0(&ast, "hello")? +let result: i64 = engine.call_fn0(&mut scope, &ast, "hello")? // ^^^^^^^^ use 'call_fn0' for no arguments ``` diff --git a/src/api.rs b/src/api.rs index ed14ada3..018ac400 100644 --- a/src/api.rs +++ b/src/api.rs @@ -857,23 +857,31 @@ impl<'e> Engine<'e> { /// # #[cfg(not(feature = "no_stdlib"))] /// # #[cfg(not(feature = "no_function"))] /// # { - /// use rhai::Engine; + /// use rhai::{Engine, Scope}; /// /// let mut engine = Engine::new(); /// - /// let ast = engine.compile("fn num() { 42 }")?; + /// let ast = engine.compile("fn num() { 42 + foo }")?; + /// + /// let mut scope = Scope::new(); + /// scope.push("foo", 42_i64); /// /// // Call the script-defined function - /// let result: i64 = engine.call_fn0(&ast, "num")?; + /// let result: i64 = engine.call_fn0(&mut scope, &ast, "num")?; /// - /// assert_eq!(result, 42); + /// assert_eq!(result, 84); /// # } /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "no_function"))] - pub fn call_fn0(&mut self, ast: &AST, name: &str) -> Result { - self.call_fn_internal(ast, name, vec![]) + pub fn call_fn0( + &mut self, + scope: &mut Scope, + ast: &AST, + name: &str, + ) -> Result { + self.call_fn_internal(scope, ast, name, vec![]) } /// Call a script function defined in an `AST` with one argument. @@ -885,16 +893,19 @@ impl<'e> Engine<'e> { /// # #[cfg(not(feature = "no_stdlib"))] /// # #[cfg(not(feature = "no_function"))] /// # { - /// use rhai::Engine; + /// use rhai::{Engine, Scope}; /// /// let mut engine = Engine::new(); /// - /// let ast = engine.compile("fn inc(x) { x + 1 }")?; + /// let ast = engine.compile("fn inc(x) { x + foo }")?; + /// + /// let mut scope = Scope::new(); + /// scope.push("foo", 42_i64); /// /// // Call the script-defined function - /// let result: i64 = engine.call_fn1(&ast, "inc", 123_i64)?; + /// let result: i64 = engine.call_fn1(&mut scope, &ast, "inc", 123_i64)?; /// - /// assert_eq!(result, 124); + /// assert_eq!(result, 165); /// # } /// # Ok(()) /// # } @@ -902,11 +913,12 @@ impl<'e> Engine<'e> { #[cfg(not(feature = "no_function"))] pub fn call_fn1( &mut self, + scope: &mut Scope, ast: &AST, name: &str, arg: A, ) -> Result { - self.call_fn_internal(ast, name, vec![arg.into_dynamic()]) + self.call_fn_internal(scope, ast, name, vec![arg.into_dynamic()]) } /// Call a script function defined in an `AST` with multiple arguments. @@ -918,16 +930,19 @@ impl<'e> Engine<'e> { /// # #[cfg(not(feature = "no_stdlib"))] /// # #[cfg(not(feature = "no_function"))] /// # { - /// use rhai::Engine; + /// use rhai::{Engine, Scope}; /// /// let mut engine = Engine::new(); /// - /// let ast = engine.compile("fn add(x, y) { len(x) + y }")?; + /// let ast = engine.compile("fn add(x, y) { len(x) + y + foo }")?; + /// + /// let mut scope = Scope::new(); + /// scope.push("foo", 42_i64); /// /// // Call the script-defined function - /// let result: i64 = engine.call_fn(&ast, "add", (String::from("abc"), 123_i64))?; + /// let result: i64 = engine.call_fn(&mut scope, &ast, "add", (String::from("abc"), 123_i64))?; /// - /// assert_eq!(result, 126); + /// assert_eq!(result, 168); /// # } /// # Ok(()) /// # } @@ -935,26 +950,28 @@ impl<'e> Engine<'e> { #[cfg(not(feature = "no_function"))] pub fn call_fn( &mut self, + scope: &mut Scope, ast: &AST, name: &str, args: A, ) -> Result { - self.call_fn_internal(ast, name, args.into_vec()) + self.call_fn_internal(scope, ast, name, args.into_vec()) } #[cfg(not(feature = "no_function"))] fn call_fn_internal( &mut self, + scope: &mut Scope, ast: &AST, name: &str, - mut values: Vec, + mut arg_values: Vec, ) -> Result { - let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); + let mut args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); self.fn_lib = Some(ast.1.clone()); let result = self - .call_fn_raw(name, &mut arg_values, None, Position::none(), 0)? + .call_fn_raw(Some(scope), name, &mut args, None, Position::none(), 0)? .try_cast() .map_err(|a| { EvalAltResult::ErrorMismatchOutputType( diff --git a/src/engine.rs b/src/engine.rs index e1ba5401..8bd55259 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -435,6 +435,7 @@ impl Engine<'_> { /// Universal method for calling functions either registered with the `Engine` or written in Rhai pub(crate) fn call_fn_raw( &mut self, + scope: Option<&mut Scope>, fn_name: &str, args: &mut FnCallArgs, def_val: Option<&Dynamic>, @@ -444,25 +445,56 @@ impl Engine<'_> { // First search in script-defined functions (can override built-in) if let Some(ref fn_lib_arc) = self.fn_lib { if let Some(fn_def) = fn_lib_arc.clone().get_function(fn_name, args.len()) { - let mut scope = Scope::new(); + match scope { + // Extern scope passed in which is not empty + Some(scope) if scope.len() > 0 => { + let scope_len = scope.len(); - scope.extend( - // Put arguments into scope as variables - fn_def - .params - .iter() - .zip(args.iter().map(|x| (*x).into_dynamic())) - .map(|(name, value)| (name, ScopeEntryType::Normal, value)), - ); + scope.extend( + // Put arguments into scope as variables - variable name is copied + fn_def + .params + .iter() + .zip(args.iter().map(|x| (*x).into_dynamic())) + .map(|(name, value)| (name.clone(), ScopeEntryType::Normal, value)), + ); - // Evaluate the function at one higher level of call depth - return self.eval_stmt(&mut scope, &fn_def.body, level + 1).or_else( - |err| match err { - // Convert return statement to return value - EvalAltResult::Return(x, _) => Ok(x), - err => Err(err.set_position(pos)), - }, - ); + // Evaluate the function at one higher level of call depth + let result = self.eval_stmt(scope, &fn_def.body, level + 1).or_else( + |err| match err { + // Convert return statement to return value + EvalAltResult::Return(x, _) => Ok(x), + err => Err(err.set_position(pos)), + }, + ); + + scope.rewind(scope_len); + + return result; + } + // No new scope - create internal scope + _ => { + let mut scope = Scope::new(); + + scope.extend( + // Put arguments into scope as variables + fn_def + .params + .iter() + .zip(args.iter().map(|x| (*x).into_dynamic())) + .map(|(name, value)| (name, ScopeEntryType::Normal, value)), + ); + + // Evaluate the function at one higher level of call depth + return self.eval_stmt(&mut scope, &fn_def.body, level + 1).or_else( + |err| match err { + // Convert return statement to return value + EvalAltResult::Return(x, _) => Ok(x), + err => Err(err.set_position(pos)), + }, + ); + } + } } } @@ -563,7 +595,7 @@ impl Engine<'_> { level: usize, ) -> Result { match dot_rhs { - // xxx.fn_name(args) + // xxx.fn_name(arg_expr_list) Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => { let mut values = arg_expr_list .iter() @@ -572,17 +604,19 @@ impl Engine<'_> { let this_ptr = target.get_mut(scope); - let mut arg_values: Vec<_> = once(this_ptr) + let mut args: Vec<_> = once(this_ptr) .chain(values.iter_mut().map(Dynamic::as_mut)) .collect(); - self.call_fn_raw(fn_name, &mut arg_values, def_val.as_ref(), *pos, 0) + let def_val = def_val.as_ref(); + + self.call_fn_raw(None, fn_name, &mut args, def_val, *pos, 0) } // xxx.id Expr::Property(id, pos) => { - let this_ptr = target.get_mut(scope); - self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) + let mut args = [target.get_mut(scope)]; + self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0) } // xxx.idx_lhs[idx_expr] @@ -591,8 +625,8 @@ impl Engine<'_> { let value = match idx_lhs.as_ref() { // xxx.id[idx_expr] Expr::Property(id, pos) => { - let this_ptr = target.get_mut(scope); - self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)? + let mut args = [target.get_mut(scope)]; + self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0)? } // xxx.???[???][idx_expr] Expr::Index(_, _, _) => { @@ -615,8 +649,8 @@ impl Engine<'_> { Expr::Dot(dot_lhs, rhs, _) => match dot_lhs.as_ref() { // xxx.id.rhs Expr::Property(id, pos) => { - let this_ptr = target.get_mut(scope); - self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) + let mut args = [target.get_mut(scope)]; + self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0) .and_then(|mut val| { self.get_dot_val_helper(scope, Target::from(val.as_mut()), rhs, level) }) @@ -627,8 +661,8 @@ impl Engine<'_> { let val = match idx_lhs.as_ref() { // xxx.id[idx_expr].rhs Expr::Property(id, pos) => { - let this_ptr = target.get_mut(scope); - self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0)? + let mut args = [target.get_mut(scope)]; + self.call_fn_raw(None, &make_getter(id), &mut args, None, *pos, 0)? } // xxx.???[???][idx_expr].rhs Expr::Index(_, _, _) => { @@ -977,7 +1011,7 @@ impl Engine<'_> { // xxx.id Expr::Property(id, pos) => { let mut args = [this_ptr, new_val.0.as_mut()]; - self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0) + self.call_fn_raw(None, &make_setter(id), &mut args, None, *pos, 0) } // xxx.lhs[idx_expr] @@ -986,7 +1020,7 @@ impl Engine<'_> { Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() { // xxx.id[idx_expr] Expr::Property(id, pos) => self - .call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) + .call_fn_raw(None, &make_getter(id), &mut [this_ptr], None, *pos, 0) .and_then(|val| { let (_, _, idx) = self.get_indexed_value(scope, &val, idx_expr, *op_pos, level)?; @@ -995,7 +1029,7 @@ impl Engine<'_> { }) .and_then(|mut val| { let mut args = [this_ptr, val.as_mut()]; - self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0) + self.call_fn_raw(None, &make_setter(id), &mut args, None, *pos, 0) }), // All others - syntax error for setters chain @@ -1009,14 +1043,14 @@ impl Engine<'_> { Expr::Dot(lhs, rhs, _) => match lhs.as_ref() { // xxx.id.rhs Expr::Property(id, pos) => { - self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) + self.call_fn_raw(None, &make_getter(id), &mut [this_ptr], None, *pos, 0) .and_then(|mut val| { self.set_dot_val_helper(scope, val.as_mut(), rhs, new_val, level) .map(|_| val) // Discard Ok return value }) .and_then(|mut val| { let mut args = [this_ptr, val.as_mut()]; - self.call_fn_raw(&make_setter(id), &mut args, None, *pos, 0) + self.call_fn_raw(None, &make_setter(id), &mut args, None, *pos, 0) }) } @@ -1026,7 +1060,7 @@ impl Engine<'_> { Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() { // xxx.id[idx_expr].rhs Expr::Property(id, pos) => { - self.call_fn_raw(&make_getter(id), &mut [this_ptr], None, *pos, 0) + self.call_fn_raw(None, &make_getter(id), &mut [this_ptr], None, *pos, 0) .and_then(|v| { let (mut value, _, idx) = self.get_indexed_value(scope, &v, idx_expr, *op_pos, level)?; @@ -1039,13 +1073,8 @@ impl Engine<'_> { Self::update_indexed_value(v, idx, value, val_pos) }) .and_then(|mut v| { - self.call_fn_raw( - &make_setter(id), - &mut [this_ptr, v.as_mut()], - None, - *pos, - 0, - ) + let mut args = [this_ptr, v.as_mut()]; + self.call_fn_raw(None, &make_setter(id), &mut args, None, *pos, 0) }) } @@ -1315,7 +1344,8 @@ impl Engine<'_> { .into_dynamic(); // Redirect call to `print` - self.call_fn_raw(KEYWORD_PRINT, &mut [result.as_mut()], None, pos, level) + let mut args = [result.as_mut()]; + self.call_fn_raw(None, KEYWORD_PRINT, &mut args, None, pos, level) } // type_of @@ -1349,7 +1379,7 @@ impl Engine<'_> { // Compile the script text #[cfg(not(feature = "no_optimize"))] - let mut ast = { + let ast = { let orig_optimization_level = self.optimization_level; self.set_optimization_level(OptimizationLevel::None); @@ -1360,7 +1390,7 @@ impl Engine<'_> { }; #[cfg(feature = "no_optimize")] - let mut ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?; + let ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?; // If new functions are defined, merge it into the current functions library let merged = AST( @@ -1404,7 +1434,9 @@ impl Engine<'_> { 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) + let def_val = def_val.as_ref(); + + self.call_fn_raw(None, fn_name, &mut arg_values, def_val, *pos, level) } } } @@ -1527,6 +1559,7 @@ impl Engine<'_> { if let Some(ref type_iterators) = self.type_iterators { if let Some(iter_fn) = type_iterators.get(&tid) { + // Add the loop variable - variable name is copied scope.push(name.clone(), ()); let entry = ScopeSource { diff --git a/src/scope.rs b/src/scope.rs index b76e3904..d3740f4c 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -63,6 +63,7 @@ pub(crate) struct EntryRef<'a> { /// allowing for automatic _shadowing_. /// /// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. +#[derive(Debug, Clone)] pub struct Scope<'a>(Vec>); impl<'a> Scope<'a> { diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 555219f2..1535f504 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, ParseErrorType, INT}; +use rhai::{Engine, EvalAltResult, ParseErrorType, Scope, INT}; #[test] fn test_fn() -> Result<(), EvalAltResult> { @@ -21,6 +21,9 @@ fn test_fn() -> Result<(), EvalAltResult> { #[test] fn test_call_fn() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); + let mut scope = Scope::new(); + + scope.push("foo", 42 as INT); let ast = engine.compile( r" @@ -28,22 +31,22 @@ fn test_call_fn() -> Result<(), EvalAltResult> { x + y } fn hello(x) { - x * 2 + x * foo } fn hello() { - 42 + 42 + foo } ", )?; - let r: i64 = engine.call_fn(&ast, "hello", (42 as INT, 123 as INT))?; + let r: i64 = engine.call_fn(&mut scope, &ast, "hello", (42 as INT, 123 as INT))?; assert_eq!(r, 165); - let r: i64 = engine.call_fn1(&ast, "hello", 123 as INT)?; - assert_eq!(r, 246); + let r: i64 = engine.call_fn1(&mut scope, &ast, "hello", 123 as INT)?; + assert_eq!(r, 5166); - let r: i64 = engine.call_fn0(&ast, "hello")?; - assert_eq!(r, 42); + let r: i64 = engine.call_fn0(&mut scope, &ast, "hello")?; + assert_eq!(r, 84); Ok(()) } From e0514a4ec02b462249c098e731b43c98a142b10d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 5 Apr 2020 12:37:07 +0800 Subject: [PATCH 16/20] Remove ref modifiers. --- src/api.rs | 2 +- src/engine.rs | 10 +++++----- src/error.rs | 32 +++++++++++++++----------------- src/optimize.rs | 6 +++--- src/parser.rs | 2 +- src/result.rs | 46 +++++++++++++++++++++++----------------------- 6 files changed, 48 insertions(+), 50 deletions(-) diff --git a/src/api.rs b/src/api.rs index 018ac400..a8d32933 100644 --- a/src/api.rs +++ b/src/api.rs @@ -831,7 +831,7 @@ impl<'e> Engine<'e> { ast: &AST, ) -> Result<(), EvalAltResult> { let statements = { - let AST(ref statements, ref functions) = ast; + let AST(statements, functions) = ast; self.fn_lib = Some(functions.clone()); statements }; diff --git a/src/engine.rs b/src/engine.rs index 8bd55259..f37388f7 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -420,7 +420,7 @@ impl Engine<'_> { }; // Search built-in's and external functions - if let Some(ref functions) = self.functions { + if let Some(functions) = &self.functions { if let Some(func) = functions.get(&spec) { // Run external function Ok(Some(func(args, pos)?)) @@ -443,7 +443,7 @@ impl Engine<'_> { level: usize, ) -> Result { // First search in script-defined functions (can override built-in) - if let Some(ref fn_lib_arc) = self.fn_lib { + if let Some(fn_lib_arc) = &self.fn_lib { if let Some(fn_def) = fn_lib_arc.clone().get_function(fn_name, args.len()) { match scope { // Extern scope passed in which is not empty @@ -511,7 +511,7 @@ impl Engine<'_> { } // Search built-in's and external functions - if let Some(ref functions) = self.functions { + if let Some(functions) = &self.functions { if let Some(func) = functions.get(&spec) { // Run external function let result = func(args, pos)?; @@ -1395,7 +1395,7 @@ impl Engine<'_> { // If new functions are defined, merge it into the current functions library let merged = AST( ast.0, - if let Some(ref fn_lib) = self.fn_lib { + if let Some(fn_lib) = &self.fn_lib { #[cfg(feature = "sync")] { Arc::new(fn_lib.as_ref().merge(&ast.1)) @@ -1557,7 +1557,7 @@ impl Engine<'_> { let arr = self.eval_expr(scope, expr, level)?; let tid = Any::type_id(&*arr); - if let Some(ref type_iterators) = self.type_iterators { + if let Some(type_iterators) = &self.type_iterators { if let Some(iter_fn) = type_iterators.get(&tid) { // Add the loop variable - variable name is copied scope.push(name.clone(), ()); diff --git a/src/error.rs b/src/error.rs index 5fa654a5..0991a19b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -130,8 +130,8 @@ impl ParseError { } pub(crate) fn desc(&self) -> &str { - match self.0 { - ParseErrorType::BadInput(ref p) => p, + match &self.0 { + ParseErrorType::BadInput(p) => p, ParseErrorType::UnexpectedEOF => "Script is incomplete", ParseErrorType::UnknownOperator(_) => "Unknown operator", ParseErrorType::MissingToken(_, _) => "Expecting a certain token that is missing", @@ -166,50 +166,48 @@ impl Error for ParseError {} impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 { - ParseErrorType::BadInput(ref s) | ParseErrorType::MalformedCallExpr(ref s) => { + match &self.0 { + ParseErrorType::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => { write!(f, "{}", if s.is_empty() { self.desc() } else { s })? } - ParseErrorType::ForbiddenConstantExpr(ref s) => { + ParseErrorType::ForbiddenConstantExpr(s) => { write!(f, "Expecting a constant to assign to '{}'", s)? } - ParseErrorType::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.desc(), s)?, + ParseErrorType::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s)?, #[cfg(not(feature = "no_index"))] - ParseErrorType::MalformedIndexExpr(ref s) => { + ParseErrorType::MalformedIndexExpr(s) => { write!(f, "{}", if s.is_empty() { self.desc() } else { s })? } #[cfg(not(feature = "no_object"))] - ParseErrorType::DuplicatedProperty(ref s) => { + ParseErrorType::DuplicatedProperty(s) => { write!(f, "Duplicated property '{}' for object map literal", s)? } - ParseErrorType::ExprExpected(ref s) => write!(f, "Expecting {} expression", s)?, + ParseErrorType::ExprExpected(s) => write!(f, "Expecting {} expression", s)?, #[cfg(not(feature = "no_function"))] - ParseErrorType::FnMissingParams(ref s) => { + ParseErrorType::FnMissingParams(s) => { write!(f, "Expecting parameters for function '{}'", s)? } #[cfg(not(feature = "no_function"))] - ParseErrorType::FnMissingBody(ref s) => { + ParseErrorType::FnMissingBody(s) => { write!(f, "Expecting body statement block for function '{}'", s)? } #[cfg(not(feature = "no_function"))] - ParseErrorType::FnDuplicatedParam(ref s, ref arg) => { + ParseErrorType::FnDuplicatedParam(s, arg) => { write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)? } - ParseErrorType::MissingToken(ref token, ref s) => { - write!(f, "Expecting '{}' {}", token, s)? - } + ParseErrorType::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s)?, - ParseErrorType::AssignmentToConstant(ref s) if s.is_empty() => { + ParseErrorType::AssignmentToConstant(s) if s.is_empty() => { write!(f, "{}", self.desc())? } - ParseErrorType::AssignmentToConstant(ref s) => { + ParseErrorType::AssignmentToConstant(s) => { write!(f, "Cannot assign to constant '{}'", s)? } _ => write!(f, "{}", self.desc())?, diff --git a/src/optimize.rs b/src/optimize.rs index 5fa2769b..4dcbf7b3 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -453,7 +453,7 @@ 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 let Some(ref fn_lib_arc) = state.engine.fn_lib { + if let Some(fn_lib_arc) = &state.engine.fn_lib { if fn_lib_arc.has_function(&id, args.len()) { // A script-defined function overrides the built-in function - do not make the call return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos); @@ -493,11 +493,11 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos), // constant-name - Expr::Variable(ref name, _) if state.contains_constant(name) => { + Expr::Variable(name, _) if state.contains_constant(&name) => { state.set_dirty(); // Replace constant with value - state.find_constant(name).expect("should find constant in scope!").clone() + state.find_constant(&name).expect("should find constant in scope!").clone() } // All other expressions - skip diff --git a/src/parser.rs b/src/parser.rs index d125878b..0ce3e30d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2061,7 +2061,7 @@ fn parse_binary_op<'a>( let mut current_lhs = lhs; loop { - let (current_precedence, bind_right) = if let Some((ref current_op, _)) = input.peek() { + let (current_precedence, bind_right) = if let Some((current_op, _)) = input.peek() { (current_op.precedence(), current_op.is_bind_right()) } else { (0, false) diff --git a/src/result.rs b/src/result.rs index dcbade53..57594ccc 100644 --- a/src/result.rs +++ b/src/result.rs @@ -268,32 +268,32 @@ impl EvalAltResult { /// 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 { + match &mut self { #[cfg(not(feature = "no_std"))] Self::ErrorReadingScriptFile(_, _) => (), - Self::ErrorParsing(ParseError(_, ref mut pos)) - | Self::ErrorFunctionNotFound(_, ref mut pos) - | Self::ErrorFunctionArgsMismatch(_, _, _, ref mut pos) - | Self::ErrorBooleanArgMismatch(_, ref mut pos) - | Self::ErrorCharMismatch(ref mut pos) - | Self::ErrorArrayBounds(_, _, ref mut pos) - | Self::ErrorStringBounds(_, _, ref mut pos) - | Self::ErrorIndexingType(_, ref mut pos) - | Self::ErrorNumericIndexExpr(ref mut pos) - | Self::ErrorStringIndexExpr(ref mut pos) - | Self::ErrorLogicGuard(ref mut pos) - | Self::ErrorFor(ref mut pos) - | Self::ErrorVariableNotFound(_, ref mut pos) - | Self::ErrorAssignmentToUnknownLHS(ref mut pos) - | Self::ErrorAssignmentToConstant(_, ref mut pos) - | 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::ErrorParsing(ParseError(_, pos)) + | Self::ErrorFunctionNotFound(_, pos) + | Self::ErrorFunctionArgsMismatch(_, _, _, pos) + | Self::ErrorBooleanArgMismatch(_, pos) + | Self::ErrorCharMismatch(pos) + | Self::ErrorArrayBounds(_, _, pos) + | Self::ErrorStringBounds(_, _, pos) + | Self::ErrorIndexingType(_, pos) + | Self::ErrorNumericIndexExpr(pos) + | Self::ErrorStringIndexExpr(pos) + | Self::ErrorLogicGuard(pos) + | Self::ErrorFor(pos) + | Self::ErrorVariableNotFound(_, pos) + | Self::ErrorAssignmentToUnknownLHS(pos) + | Self::ErrorAssignmentToConstant(_, pos) + | Self::ErrorMismatchOutputType(_, pos) + | Self::ErrorDotExpr(_, pos) + | Self::ErrorArithmetic(_, pos) + | Self::ErrorStackOverflow(pos) + | Self::ErrorRuntime(_, pos) + | Self::ErrorLoopBreak(_, pos) + | Self::Return(_, pos) => *pos = new_position, } self From 94313ca095295889431551f273c58db9e34de498 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 5 Apr 2020 12:57:20 +0800 Subject: [PATCH 17/20] Test variable mutation in outer scope. --- tests/call_fn.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 1535f504..909e8a90 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -31,10 +31,12 @@ fn test_call_fn() -> Result<(), EvalAltResult> { x + y } fn hello(x) { - x * foo + x = x * foo; + foo = 1; + x } fn hello() { - 42 + foo + 41 + foo } ", )?; @@ -46,7 +48,14 @@ fn test_call_fn() -> Result<(), EvalAltResult> { assert_eq!(r, 5166); let r: i64 = engine.call_fn0(&mut scope, &ast, "hello")?; - assert_eq!(r, 84); + assert_eq!(r, 42); + + assert_eq!( + scope + .get_value::("foo") + .expect("variable foo should exist"), + 1 + ); Ok(()) } From 44d6a5e4660f0445d01db86b2ba783c87a7711bc Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 5 Apr 2020 17:44:48 +0800 Subject: [PATCH 18/20] Reduce cloning. --- examples/repl.rs | 6 +++--- src/any.rs | 2 +- src/api.rs | 6 +++--- src/engine.rs | 10 ++++++++-- src/optimize.rs | 2 +- src/parser.rs | 46 +++++++++++++++++++--------------------------- src/scope.rs | 20 +++++++++----------- 7 files changed, 44 insertions(+), 48 deletions(-) diff --git a/examples/repl.rs b/examples/repl.rs index e166b54c..d4679842 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -141,18 +141,18 @@ fn main() { .compile_with_scope(&scope, &script) .map_err(EvalAltResult::ErrorParsing) .and_then(|r| { - ast_u = r; + ast_u = r.clone(); #[cfg(not(feature = "no_optimize"))] { engine.set_optimization_level(OptimizationLevel::Full); - ast = engine.optimize_ast(&scope, &ast_u); + ast = engine.optimize_ast(&scope, r); engine.set_optimization_level(OptimizationLevel::None); } #[cfg(feature = "no_optimize")] { - ast = ast_u.clone(); + ast = r; } // Merge the AST into the main diff --git a/src/any.rs b/src/any.rs index 7e7494e8..1a7bda22 100644 --- a/src/any.rs +++ b/src/any.rs @@ -125,7 +125,7 @@ impl fmt::Debug for Variant { impl Clone for Dynamic { fn clone(&self) -> Self { - Any::into_dynamic(self.as_ref()) + self.as_ref().into_dynamic() } } diff --git a/src/api.rs b/src/api.rs index a8d32933..047cf525 100644 --- a/src/api.rs +++ b/src/api.rs @@ -986,7 +986,7 @@ impl<'e> Engine<'e> { } /// Optimize the `AST` with constants defined in an external Scope. - /// An optimized copy of the `AST` is returned while the original `AST` is untouched. + /// An optimized copy of the `AST` is returned while the original `AST` is consumed. /// /// Although optimization is performed by default during compilation, sometimes it is necessary to /// _re_-optimize an AST. For example, when working with constants that are passed in via an @@ -997,11 +997,11 @@ impl<'e> Engine<'e> { /// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope /// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running. #[cfg(not(feature = "no_optimize"))] - pub fn optimize_ast(&self, scope: &Scope, ast: &AST) -> AST { + pub fn optimize_ast(&self, scope: &Scope, ast: AST) -> AST { optimize_into_ast( self, scope, - ast.0.clone(), + ast.0, ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect(), ) } diff --git a/src/engine.rs b/src/engine.rs index f37388f7..8f4f418f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -452,10 +452,11 @@ impl Engine<'_> { scope.extend( // Put arguments into scope as variables - variable name is copied + // TODO - avoid copying variable name fn_def .params .iter() - .zip(args.iter().map(|x| (*x).into_dynamic())) + .zip(args.into_iter().map(|x| (*x).into_dynamic())) .map(|(name, value)| (name.clone(), ScopeEntryType::Normal, value)), ); @@ -481,7 +482,7 @@ impl Engine<'_> { fn_def .params .iter() - .zip(args.iter().map(|x| (*x).into_dynamic())) + .zip(args.into_iter().map(|x| (*x).into_dynamic())) .map(|(name, value)| (name, ScopeEntryType::Normal, value)), ); @@ -1227,6 +1228,7 @@ impl Engine<'_> { *scope.get_mut(entry) = rhs_val.clone(); Ok(rhs_val) } + ScopeSource { typ: ScopeEntryType::Constant, .. @@ -1560,6 +1562,7 @@ impl Engine<'_> { if let Some(type_iterators) = &self.type_iterators { if let Some(iter_fn) = type_iterators.get(&tid) { // Add the loop variable - variable name is copied + // TODO - avoid copying variable name scope.push(name.clone(), ()); let entry = ScopeSource { @@ -1622,11 +1625,13 @@ impl Engine<'_> { // Let statement Stmt::Let(name, Some(expr), _) => { let val = self.eval_expr(scope, expr, level)?; + // TODO - avoid copying variable name in inner block? scope.push_dynamic_value(name.clone(), ScopeEntryType::Normal, val, false); Ok(().into_dynamic()) } Stmt::Let(name, None, _) => { + // TODO - avoid copying variable name in inner block? scope.push(name.clone(), ()); Ok(().into_dynamic()) } @@ -1634,6 +1639,7 @@ impl Engine<'_> { // Const statement Stmt::Const(name, expr, _) if expr.is_constant() => { let val = self.eval_expr(scope, expr, level)?; + // TODO - avoid copying variable name in inner block? scope.push_dynamic_value(name.clone(), ScopeEntryType::Constant, val, true); Ok(().into_dynamic()) } diff --git a/src/optimize.rs b/src/optimize.rs index 4dcbf7b3..ec5d3688 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -480,7 +480,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { // Otherwise use the default value, if any def_value.clone() } - }).and_then(|result| map_dynamic_to_expr(result, pos).0) + }).and_then(|result| map_dynamic_to_expr(result, pos)) .map(|expr| { state.set_dirty(); expr diff --git a/src/parser.rs b/src/parser.rs index 0ce3e30d..8dcbbc98 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -221,15 +221,15 @@ impl AST { /// # } /// ``` pub fn merge(&self, other: &Self) -> Self { - let Self(ast, functions) = self; + let Self(statements, functions) = self; - let ast = match (ast.is_empty(), other.0.is_empty()) { + let ast = match (statements.is_empty(), other.0.is_empty()) { (false, false) => { - let mut ast = ast.clone(); - ast.extend(other.0.iter().cloned()); - ast + let mut statements = statements.clone(); + statements.extend(other.0.iter().cloned()); + statements } - (false, true) => ast.clone(), + (false, true) => statements.clone(), (true, false) => other.0.clone(), (true, true) => vec![], }; @@ -1733,8 +1733,8 @@ fn parse_map_literal<'a>( PERR::MissingToken("}".into(), "to end this object map literal".into()) .into_err_eof() })? { - (Token::Identifier(s), pos) => (s.clone(), pos), - (Token::StringConst(s), pos) => (s.clone(), pos), + (Token::Identifier(s), pos) => (s, pos), + (Token::StringConst(s), pos) => (s, pos), (_, pos) if map.is_empty() => { return Err(PERR::MissingToken( "}".into(), @@ -2727,35 +2727,27 @@ pub fn parse<'a, 'e>( /// Map a `Dynamic` value to an expression. /// /// Returns Some(expression) if conversion is successful. Otherwise None. -pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> (Option, Dynamic) { +pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { if value.is::() { - let value2 = value.clone(); - (Some(Expr::IntegerConstant(value.cast(), pos)), value2) + Some(Expr::IntegerConstant(value.cast(), pos)) } else if value.is::() { - let value2 = value.clone(); - (Some(Expr::CharConstant(value.cast(), pos)), value2) + Some(Expr::CharConstant(value.cast(), pos)) } else if value.is::() { - let value2 = value.clone(); - (Some(Expr::StringConstant(value.cast(), pos)), value2) + Some(Expr::StringConstant(value.cast(), pos)) } else if value.is::() { - let value2 = value.clone(); - ( - Some(if value.cast::() { - Expr::True(pos) - } else { - Expr::False(pos) - }), - value2, - ) + Some(if value.cast::() { + Expr::True(pos) + } else { + Expr::False(pos) + }) } else { #[cfg(not(feature = "no_float"))] { if value.is::() { - let value2 = value.clone(); - return (Some(Expr::FloatConstant(value.cast(), pos)), value2); + return Some(Expr::FloatConstant(value.cast(), pos)); } } - (None, value) + None } } diff --git a/src/scope.rs b/src/scope.rs index d3740f4c..14f53cb5 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -126,17 +126,15 @@ impl<'a> Scope<'a> { value: Dynamic, map_expr: bool, ) { - let (expr, value) = if map_expr { - map_dynamic_to_expr(value, Position::none()) - } else { - (None, value) - }; - self.0.push(Entry { name: name.into(), typ: entry_type, - value, - expr, + value: value.clone(), + expr: if map_expr { + map_dynamic_to_expr(value, Position::none()) + } else { + None + }, }); } @@ -163,15 +161,15 @@ impl<'a> Scope<'a> { .find(|(_, Entry { name, .. })| name == key) .map( |( - i, + index, Entry { name, typ, value, .. }, )| { ( EntryRef { - name: name.as_ref(), - index: i, + name, + index, typ: *typ, }, value.clone(), From c4498d147d305eccf80d7a115c7b60bd9653eda3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 5 Apr 2020 19:17:48 +0800 Subject: [PATCH 19/20] Add set_value to Scope. --- README.md | 20 +++++++++++++------- src/scope.rs | 32 ++++++++++++++++++++++++++++++-- tests/var_scope.rs | 6 +++++- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 97e27192..fbb08cce 100644 --- a/README.md +++ b/README.md @@ -733,12 +733,14 @@ fn main() -> Result<(), EvalAltResult> // First create the state let mut scope = Scope::new(); - // Then push some initialized variables into the state - // NOTE: Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64. - // Better stick to them or it gets hard working with the script. + // Then push (i.e. add) some initialized variables into the state. + // Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64. + // Better stick to them or it gets hard working with the script. scope.push("y", 42_i64); scope.push("z", 999_i64); - scope.push("s", "hello, world!".to_string()); // remember to use 'String', not '&str' + + // 'set_value' adds a variable when one doesn't exist + scope.set_value("s", "hello, world!".to_string()); // remember to use 'String', not '&str' // First invocation engine.eval_with_scope::<()>(&mut scope, r" @@ -749,10 +751,14 @@ fn main() -> Result<(), EvalAltResult> // Second invocation using the same state let result = engine.eval_with_scope::(&mut scope, "x")?; - println!("result: {}", result); // prints 979 + println!("result: {}", result); // prints 979 - // Variable y is changed in the script - assert_eq!(scope.get_value::("y").expect("variable x should exist"), 1); + // Variable y is changed in the script - read it with 'get_value' + assert_eq!(scope.get_value::("y").expect("variable y should exist"), 1); + + // We can modify scope variables directly with 'set_value' + scope.set_value("y", 42_i64); + assert_eq!(scope.get_value::("y").expect("variable y should exist"), 42); Ok(()) } diff --git a/src/scope.rs b/src/scope.rs index 14f53cb5..6b44ec6e 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -179,16 +179,44 @@ impl<'a> Scope<'a> { } /// Get the value of an entry in the Scope, starting from the last. - pub fn get_value(&self, key: &str) -> Option { + pub fn get_value(&self, name: &str) -> Option { self.0 .iter() .enumerate() .rev() // Always search a Scope in reverse order - .find(|(_, Entry { name, .. })| name == key) + .find(|(_, Entry { name: key, .. })| name == key) .and_then(|(_, Entry { value, .. })| value.downcast_ref::()) .map(T::clone) } + /// Update the value of the named variable. + /// Search starts from the last, and only the last variable matching the specified name is updated. + /// If no variable matching the specified name is found, a new variable is added. + /// + /// # Panics + /// + /// Panics when trying to update the value of a constant. + pub fn set_value(&mut self, name: &'a str, value: T) { + match self.get(name) { + Some(( + EntryRef { + typ: EntryType::Constant, + .. + }, + _, + )) => panic!("variable {} is constant", name), + Some(( + EntryRef { + index, + typ: EntryType::Normal, + .. + }, + _, + )) => self.0.get_mut(index).unwrap().value = value.into_dynamic(), + None => self.push(name, value.into_dynamic()), + } + } + /// 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"); diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 6b660d34..39cf5b76 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -9,8 +9,12 @@ 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); + + scope.set_value("x", 42 as INT); + assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 42); + engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?; - assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 12); + assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 42); Ok(()) } From 2bb195cd65113c0fc5cce3b52c4318442745764f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 5 Apr 2020 23:43:40 +0800 Subject: [PATCH 20/20] Add doc tests to Scope. --- README.md | 170 ++++++++++++++++++++++----------------- src/scope.rs | 218 +++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 293 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index fbb08cce..51b6d637 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,21 @@ Rhai - Embedded Scripting for Rust ![crates.io](https://img.shields.io/crates/d/rhai) [![API Docs](https://docs.rs/rhai/badge.svg)](https://docs.rs/rhai/) -Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application. +Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way +to add scripting to any application. Rhai's current features set: * `no-std` support -* Easy integration with Rust functions and data types, supporting getter/setter methods +* Easy integration with Rust native functions and data types, including getter/setter methods * Easily call a script-defined function from Rust +* Freely pass variables/constants into a script via an external [`Scope`] * Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop) * Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app) * Easy-to-use language similar to JS+Rust * Support for overloaded functions * Compiled script is optimized for repeat evaluations +* Support for minimal builds by excluding unneeded language features * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) to do checked arithmetic operations); for [`no_std`] builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. @@ -70,7 +73,8 @@ Optional features | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, [`Engine`], [`Scope`] and `AST` are all `Send + Sync`. | -By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here to opt-**out** of certain functionalities that are not needed. +By default, Rhai includes all the standard functionalities in a small, tight package. +Most features are here to opt-**out** of certain functionalities that are not needed. Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. [`unchecked`]: #optional-features @@ -209,8 +213,8 @@ Compiling a script file is also supported: let ast = engine.compile_file("hello_world.rhai".into())?; ``` -Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - via `call_fn` -or its cousins `call_fn1` (one argument) and `call_fn0` (no argument). +Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - +via `call_fn` or its cousins `call_fn1` (one argument) and `call_fn0` (no argument). ```rust // Define functions in a script. @@ -295,12 +299,14 @@ The following primitive types are supported natively: [`()`]: #values-and-types -All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust. +All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - +they even cannot be added together. This is very similar to Rust. -The default integer type is `i64`. If other integer types are not needed, it is possible to exclude them and make a smaller build with the [`only_i64`] feature. +The default integer type is `i64`. If other integer types are not needed, it is possible to exclude them and make a +smaller build with the [`only_i64`] feature. -If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`. -This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty. +If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, +including `i64`. This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty. If no floating-point is needed or supported, use the [`no_float`] feature to remove it. @@ -358,12 +364,13 @@ if type_of(mystery) == "i64" { } ``` -In Rust, sometimes a `Dynamic` forms part of the return value - a good example is elements within an `Array` which are `Dynamic`, -or property values in an object map. In order to get the _real_ value, the actual value type _must_ be known in advance. -There is no easy way for Rust to detect, at run-time, what type the `Dynamic` value is (short of using the `type_name` -function to get the textual name of the type and then matching on that). +In Rust, sometimes a `Dynamic` forms part of a returned value - a good example is an array with `Dynamic` elements, +or an object map with `Dynamic` property values. To get the _real_ values, the actual value types _must_ be known in advance. +There is no easy way for Rust to decide, at run-time, what type the `Dynamic` value is (short of using the `type_name` +function and match against the name). -To use a `Dynamic` value in Rust, use the `cast` method to convert the value into a specific, known type. +A `Dynamic` value's actual type can be checked via the `is` method. +The `cast` method (from the `rhai::AnyExt` trait) then converts the value into a specific, known type. Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails. ```rust @@ -372,13 +379,15 @@ use rhai::AnyExt; // Pull in the trait. let list: Array = engine.eval("...")?; // return type is 'Array' let item = list[0]; // an element in an 'Array' is 'Dynamic' +item.is::() == true; // 'is' returns whether a 'Dynamic' value is of a particular type + let value = item.cast::(); // if the element is 'i64', this succeeds; otherwise it panics let value: i64 = item.cast(); // type can also be inferred let value = item.try_cast::()?; // 'try_cast' does not panic when the cast fails, but returns an error ``` -The `type_name` method gets the name of the actual type as a string, which you may match against. +The `type_name` method gets the name of the actual type as a static string slice, which you may match against. ```rust use rhai::Any; // Pull in the trait. @@ -423,7 +432,7 @@ To call these functions, they need to be registered with the [`Engine`]. ```rust use rhai::{Engine, EvalAltResult}; use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn` -use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn` +use rhai::{Any, Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn` // Normal function fn add(x: i64, y: i64) -> i64 { @@ -432,7 +441,7 @@ fn add(x: i64, y: i64) -> i64 { // Function that returns a Dynamic value fn get_an_any() -> Dynamic { - Box::new(42_i64) + (42_i64).into_dynamic() // 'into_dynamic' is defined by the 'rhai::Any' trait } fn main() -> Result<(), EvalAltResult> @@ -456,14 +465,17 @@ fn main() -> Result<(), EvalAltResult> } ``` -To return a [`Dynamic`] value from a Rust function, simply `Box` it and return it. +To return a [`Dynamic`] value from a Rust function, use the `into_dynamic()` method +(under the `rhai::Any` trait) to convert it. ```rust +use rhai::Any; // Pull in the trait + fn decide(yes_no: bool) -> Dynamic { if yes_no { - Box::new(42_i64) + (42_i64).into_dynamic() } else { - Box::new("hello world!".to_string()) // remember &str is not supported + String::from("hello!").into_dynamic() // remember &str is not supported by Rhai } } ``` @@ -471,7 +483,8 @@ fn decide(yes_no: bool) -> Dynamic { Generic functions ----------------- -Generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately: +Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately. +Essentially this is a form of function overloading as Rhai does not support generics. ```rust use std::fmt::Display; @@ -492,15 +505,17 @@ fn main() } ``` -This example shows how to register multiple functions (or, in this case, multiple instances of the same function) to the same name in script. -This enables function overloading based on the number and types of parameters. +This example shows how to register multiple functions (or, in this case, multiple overloaded versions of the same function) +under the same name. This enables function overloading based on the number and types of parameters. Fallible functions ------------------ -If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be registered with `register_result_fn` (using the `RegisterResultFn` trait). +If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be registered with `register_result_fn` +(using the `RegisterResultFn` trait). -The function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements `From<&str>` and `From` etc. and the error text gets converted into `EvalAltResult::ErrorRuntime`. +The function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements `From<&str>` and `From` etc. +and the error text gets converted into `EvalAltResult::ErrorRuntime`. ```rust use rhai::{Engine, EvalAltResult, Position}; @@ -612,14 +627,15 @@ let mut engine = Engine::new(); engine.register_type::(); ``` -To use methods and functions with the [`Engine`], we need to register them. There are some convenience functions to help with this. -Below I register update and new with the [`Engine`]. +To use native types, methods and functions with the [`Engine`], we need to register them. +There are some convenience functions to help with these. Below, the `update` and `new` methods are registered with the [`Engine`]. -*Note: [`Engine`] follows the convention that methods use a `&mut` first parameter so that invoking methods can update the value in memory.* +*Note: [`Engine`] follows the convention that methods use a `&mut` first parameter so that invoking methods +can update the value in memory.* ```rust -engine.register_fn("update", TestStruct::update); // registers 'update(&mut ts)' -engine.register_fn("new_ts", TestStruct::new); // registers 'new' +engine.register_fn("update", TestStruct::update); // registers 'update(&mut TestStruct)' +engine.register_fn("new_ts", TestStruct::new); // registers 'new()' ``` Finally, we call our script. The script can see the function and method we registered earlier. @@ -631,8 +647,9 @@ let result = engine.eval::("let x = new_ts(); x.update(); x")?; println!("result: {}", result.field); // prints 42 ``` -In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method-call on that type because internally they are the same thing: -methods on a type is implemented as a functions taking an first argument. +In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method call +on that type because internally they are the same thing: +methods on a type is implemented as a functions taking a `&mut` first argument. ```rust fn foo(ts: &mut TestStruct) -> i64 { @@ -646,7 +663,8 @@ let result = engine.eval::("let x = new_ts(); x.foo()")?; println!("result: {}", result); // prints 1 ``` -If the [`no_object`] feature is turned on, however, the _method_ style of function calls (i.e. calling a function as an object-method) is no longer supported. +If the [`no_object`] feature is turned on, however, the _method_ style of function calls +(i.e. calling a function as an object-method) is no longer supported. ```rust // Below is a syntax error under 'no_object' because 'len' cannot be called in method style. @@ -709,19 +727,21 @@ println!("Answer: {}", result); // prints 42 Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set` and `register_get_set` are not available when the [`no_object`] feature is turned on. -Initializing and maintaining state ---------------------------------- +`Scope` - Initializing and maintaining state +------------------------------------------- -[`Scope`]: #initializing-and-maintaining-state +[`Scope`]: #scope---initializing-and-maintaining-state -By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined but no global state. -This gives each evaluation a clean starting slate. In order to continue using the same global state from one invocation to the next, -such a state must be manually created and passed in. +By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined +but no global state. This gives each evaluation a clean starting slate. In order to continue using the same global state +from one invocation to the next, such a state must be manually created and passed in. -All `Scope` variables are [`Dynamic`], meaning they can store values of any type. If the [`sync`] feature is used, however, then only types -that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`. This is extremely useful in multi-threaded applications. +All `Scope` variables are [`Dynamic`], meaning they can store values of any type. If the [`sync`] feature is used, however, +then only types that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`. +This is extremely useful in multi-threaded applications. -In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is threaded through multiple invocations: +In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is +threaded through multiple invocations: ```rust use rhai::{Engine, Scope, EvalAltResult}; @@ -828,7 +848,8 @@ Variables Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`'). -Variable names must start with an ASCII letter or an underscore '`_`', must contain at least one ASCII letter, and must start with an ASCII letter before a digit. +Variable names must start with an ASCII letter or an underscore '`_`', must contain at least one ASCII letter, +and must start with an ASCII letter before a digit. Therefore, names like '`_`', '`_42`', '`3a`' etc. are not legal variable names, but '`_c3po`' and '`r2d2`' are. Variable names are also case _sensitive_. @@ -879,7 +900,8 @@ Integer numbers follow C-style format with support for decimal, binary ('`0b`'), The default system integer type (also aliased to `INT`) is `i64`. It can be turned into `i32` via the [`only_i32`] feature. -Floating-point numbers are also supported if not disabled with [`no_float`]. The default system floating-point type is `i64` (also aliased to `FLOAT`). +Floating-point numbers are also supported if not disabled with [`no_float`]. The default system floating-point type is `i64` +(also aliased to `FLOAT`). '`_`' separators can be added freely and are ignored within a number. @@ -935,7 +957,8 @@ number = -5 - +5; Numeric functions ----------------- -The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: +The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on +`i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: | Function | Description | | ------------ | --------------------------------- | @@ -961,18 +984,21 @@ The following standard functions (defined in the standard library but excluded i Strings and Chars ----------------- -String and char literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') and hex ('`\x`_xx_') escape sequences. +String and char literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') and +hex ('`\x`_xx_') escape sequences. -Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full, 32-bit extended Unicode code points. +Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full, +32-bit extended Unicode code points. Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`s!), but there are major differences. 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. +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. +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. [`type_of()`] a string returns `"string"`. @@ -1236,8 +1262,8 @@ Comparison operators Comparing most values of the same data type work out-of-the-box for standard types supported by the system. -However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system -types - `INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`. +However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system types - +`INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`. ```rust 42 == 42; // true @@ -1421,8 +1447,8 @@ return 123 + 456; // returns 579 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. +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. ```rust if some_bad_condition_has_happened { @@ -1493,7 +1519,8 @@ 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). -It is important to remember that all arguments are passed by _value_, so all functions are _pure_ (i.e. they never modify their arguments). +It is important to remember that all arguments are passed by _value_, so all functions are _pure_ +(i.e. they never modifytheir arguments). Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if not careful. ```rust @@ -1621,8 +1648,8 @@ For example, in the following: } ``` -Rhai attempts to eliminate _dead code_ (i.e. code that does nothing, for example an expression by itself as a statement, which is allowed in Rhai). -The above script optimizes to: +Rhai attempts to eliminate _dead code_ (i.e. code that does nothing, for example an expression by itself as a statement, +which is allowed in Rhai). The above script optimizes to: ```rust { @@ -1750,21 +1777,22 @@ Function side effect considerations ---------------------------------- All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_ (i.e. they do not mutate state -nor cause side any effects, with the exception of `print` and `debug` which are handled specially) so using [`OptimizationLevel::Full`] -is usually quite safe _unless_ you register your own types and functions. +nor cause side any effects, with the exception of `print` and `debug` which are handled specially) so using +[`OptimizationLevel::Full`] is usually quite safe _unless_ you register your own types and functions. If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie within a pruned code block). -If custom functions are registered to replace built-in operators, they will also be called when the operators are used (in an `if` -statement, for example) and cause side-effects. +If custom functions are registered to replace built-in operators, they will also be called when the operators are used +(in an `if` statement, for example) and cause side-effects. Function volatility considerations --------------------------------- -Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external -environment and is not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value! -The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments -it will eagerly execute the function call. This causes the script to behave differently from the intended semantics because -essentially the result of the function call will always be the same value. +Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ +on the external environment and is not _pure_. A perfect example is a function that gets the current time - +obviously each run will return a different value! The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that +all functions are _pure_, so when it finds constant arguments it will eagerly execute the function call. +This causes the script to behave differently from the intended semantics because essentially the result of the function call +will always be the same value. Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions. @@ -1799,15 +1827,15 @@ print("start!"); print("end!"); ``` -In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to a type error. -However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces no side effects), -thus the script silently runs to completion without errors. +In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to +a type error. However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces +no side effects), thus the script silently runs to completion without errors. Turning off optimizations ------------------------- -It is usually a bad idea to depend on a script failing or such kind of subtleties, but if it turns out to be necessary (why? I would never guess), -turn it off by setting the optimization level to [`OptimizationLevel::None`]. +It is usually a bad idea to depend on a script failing or such kind of subtleties, but if it turns out to be necessary +(why? I would never guess), turn it off by setting the optimization level to [`OptimizationLevel::None`]. ```rust let engine = rhai::Engine::new(); diff --git a/src/scope.rs b/src/scope.rs index 6b44ec6e..15eda16c 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -52,9 +52,14 @@ pub(crate) struct EntryRef<'a> { /// let mut engine = Engine::new(); /// let mut my_scope = Scope::new(); /// -/// engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;")?; +/// my_scope.push("z", 40_i64); /// -/// assert_eq!(engine.eval_with_scope::(&mut my_scope, "x + 1")?, 6); +/// engine.eval_with_scope::<()>(&mut my_scope, "let x = z + 1; z = 0;")?; +/// +/// assert_eq!(engine.eval_with_scope::(&mut my_scope, "x + 1")?, 42); +/// +/// assert_eq!(my_scope.get_value::("x").unwrap(), 41); +/// assert_eq!(my_scope.get_value::("z").unwrap(), 0); /// # Ok(()) /// # } /// ``` @@ -68,31 +73,106 @@ pub struct Scope<'a>(Vec>); impl<'a> Scope<'a> { /// Create a new Scope. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// ``` pub fn new() -> Self { Self(Vec::new()) } /// Empty the Scope. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// assert!(my_scope.contains("x")); + /// assert_eq!(my_scope.len(), 1); + /// assert!(!my_scope.is_empty()); + /// + /// my_scope.clear(); + /// assert!(!my_scope.contains("x")); + /// assert_eq!(my_scope.len(), 0); + /// assert!(my_scope.is_empty()); + /// ``` pub fn clear(&mut self) { self.0.clear(); } /// Get the number of entries inside the Scope. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// assert_eq!(my_scope.len(), 0); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.len(), 1); + /// ``` pub fn len(&self) -> usize { self.0.len() } /// Is the Scope empty? + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// assert!(my_scope.is_empty()); + /// + /// my_scope.push("x", 42_i64); + /// assert!(!my_scope.is_empty()); + /// ``` pub fn is_empty(&self) -> bool { self.0.len() == 0 } /// Add (push) a new entry to the Scope. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// ``` pub fn push>, T: Any + Clone>(&mut self, name: K, value: T) { self.push_dynamic_value(name, EntryType::Normal, value.into_dynamic(), false); } /// Add (push) a new `Dynamic` entry to the Scope. + /// + /// # Examples + /// + /// ``` + /// use rhai::{Any, Scope}; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push_dynamic("x", (42_i64).into_dynamic()); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// ``` pub fn push_dynamic>>(&mut self, name: K, value: Dynamic) { self.push_dynamic_value(name, EntryType::Normal, value, false); } @@ -101,8 +181,20 @@ impl<'a> Scope<'a> { /// /// 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, constants must be in one of the recognized types: /// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push_constant("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// ``` pub fn push_constant>, T: Any + Clone>(&mut self, name: K, value: T) { self.push_dynamic_value(name, EntryType::Constant, value.into_dynamic(), true); } @@ -111,9 +203,21 @@ impl<'a> Scope<'a> { /// /// 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`. + /// + /// # Examples + /// + /// ``` + /// use rhai::{Any, Scope}; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push_constant_dynamic("x", (42_i64).into_dynamic()); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// ``` pub fn push_constant_dynamic>>(&mut self, name: K, value: Dynamic) { self.push_dynamic_value(name, EntryType::Constant, value, true); } @@ -139,63 +243,129 @@ impl<'a> Scope<'a> { } /// Truncate (rewind) the Scope to a previous size. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// my_scope.push("y", 123_i64); + /// assert!(my_scope.contains("x")); + /// assert!(my_scope.contains("y")); + /// assert_eq!(my_scope.len(), 2); + /// + /// my_scope.rewind(1); + /// assert!(my_scope.contains("x")); + /// assert!(!my_scope.contains("y")); + /// assert_eq!(my_scope.len(), 1); + /// + /// my_scope.rewind(0); + /// assert!(!my_scope.contains("x")); + /// assert!(!my_scope.contains("y")); + /// assert_eq!(my_scope.len(), 0); + /// assert!(my_scope.is_empty()); + /// ``` pub fn rewind(&mut self, size: usize) { self.0.truncate(size); } /// Does the scope contain the entry? - pub fn contains(&self, key: &str) -> bool { + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// assert!(my_scope.contains("x")); + /// assert!(!my_scope.contains("y")); + /// ``` + pub fn contains(&self, name: &str) -> bool { self.0 .iter() - .enumerate() .rev() // Always search a Scope in reverse order - .any(|(_, Entry { name, .. })| name == key) + .any(|Entry { name: key, .. }| name == key) } /// Find an entry in the Scope, starting from the last. - pub(crate) fn get(&self, key: &str) -> Option<(EntryRef, Dynamic)> { + pub(crate) fn get(&self, name: &str) -> Option<(EntryRef, Dynamic)> { self.0 .iter() .enumerate() .rev() // Always search a Scope in reverse order - .find(|(_, Entry { name, .. })| name == key) - .map( + .find_map( |( index, Entry { - name, typ, value, .. + name: key, + typ, + value, + .. }, )| { - ( - EntryRef { - name, - index, - typ: *typ, - }, - value.clone(), - ) + if name == key { + Some(( + EntryRef { + name: key, + index, + typ: *typ, + }, + value.clone(), + )) + } else { + None + } }, ) } /// Get the value of an entry in the Scope, starting from the last. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// ``` pub fn get_value(&self, name: &str) -> Option { self.0 .iter() - .enumerate() - .rev() // Always search a Scope in reverse order - .find(|(_, Entry { name: key, .. })| name == key) - .and_then(|(_, Entry { value, .. })| value.downcast_ref::()) + .rev() + .find(|Entry { name: key, .. }| name == key) + .and_then(|Entry { value, .. }| value.downcast_ref::()) .map(T::clone) } - /// Update the value of the named variable. - /// Search starts from the last, and only the last variable matching the specified name is updated. - /// If no variable matching the specified name is found, a new variable is added. + /// Update the value of the named entry. + /// Search starts backwards from the last, and only the first entry matching the specified name is updated. + /// If no entry matching the specified name is found, a new one is added. /// /// # Panics /// /// Panics when trying to update the value of a constant. + /// + /// # Examples + /// + /// ``` + /// use rhai::Scope; + /// + /// let mut my_scope = Scope::new(); + /// + /// my_scope.push("x", 42_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); + /// + /// my_scope.set_value("x", 0_i64); + /// assert_eq!(my_scope.get_value::("x").unwrap(), 0); + /// ``` pub fn set_value(&mut self, name: &'a str, value: T) { match self.get(name) { Some((