diff --git a/RELEASES.md b/RELEASES.md index ba52e9cc..02c654f3 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -20,6 +20,7 @@ New features * `OptimizationLevel::Simple` now eagerly evaluates built-in binary operators of primary types (if not overloaded). * Added `is_def_var()` to detect if variable is defined, and `is_def_fn()` to detect if script function is defined. * `Dynamic::from(&str)` now constructs a `Dynamic` with a copy of the string as value. +* `AST::combine` and `AST::combine_filtered` allows combining two `AST`'s without creating a new one. Version 0.19.0 diff --git a/doc/src/language/fn-namespaces.md b/doc/src/language/fn-namespaces.md index e1a4a55e..0e3819b2 100644 --- a/doc/src/language/fn-namespaces.md +++ b/doc/src/language/fn-namespaces.md @@ -10,8 +10,8 @@ Each Function is a Separate Compilation Unit This means that individual functions can be separated, exported, re-grouped, imported, and generally mix-'n-match-ed with other completely unrelated scripts. -For example, the `AST::merge` method allows merging all functions in one [`AST`] into another, -forming a new, combined, group of functions. +For example, the `AST::merge` and `AST::combine` methods (or the equivalent `+` and `+=` operators) +allow combining all functions in one [`AST`] into another, forming a new, unified, group of functions. In general, there are two types of _namespaces_ where functions are looked up: @@ -58,10 +58,10 @@ let ast1 = engine.compile( // Compile another script with an overriding function let ast2 = engine.compile(r#"fn get_message() { "Boo!" }"#)?; -// Merge the two AST's -let ast = ast1.merge(ast2); // 'message' will be overwritten +// Combine the two AST's +ast1 += ast2; // 'message' will be overwritten -engine.consume_ast(&ast)?; // prints 'Boo!' +engine.consume_ast(&ast1)?; // prints 'Boo!' ``` Therefore, care must be taken when _cross-calling_ functions to make sure that the correct diff --git a/doc/src/patterns/multi-layer.md b/doc/src/patterns/multi-layer.md index 8a296c72..9092ca82 100644 --- a/doc/src/patterns/multi-layer.md +++ b/doc/src/patterns/multi-layer.md @@ -29,8 +29,8 @@ Key Concepts * The lowest layer script is compiled into a base [`AST`]. -* Higher layer scripts are also compiled into [`AST`] and _merged_ into the base using `AST::merge`, - overriding any existing functions. +* Higher layer scripts are also compiled into [`AST`] and _combined_ into the base using `AST::combine` + (or the `+=` operator), overriding any existing functions. Examples @@ -83,7 +83,7 @@ fn baz() { print("hey!"); } fn foo(x) { x + 42 } ``` -Load and merge them sequentially: +Load and combine them sequentially: ```rust let engine = Engine::new(); @@ -91,17 +91,17 @@ let engine = Engine::new(); // Compile the baseline default implementations. let mut ast = engine.compile_file("default.rhai".into())?; -// Merge in the first layer. +// Combine the first layer. let lowest = engine.compile_file("lowest.rhai".into())?; -ast = ast.merge(&lowest); +ast += lowest; -// Merge in the second layer. +// Combine the second layer. let middle = engine.compile_file("middle.rhai".into())?; -ast = ast.merge(&middle); +ast += lowest; -// Merge in the third layer. +// Combine the third layer. let highest = engine.compile_file("highest.rhai".into())?; -ast = ast.merge(&highest); +ast += lowest; // Now, 'ast' contains the following functions: // diff --git a/examples/repl.rs b/examples/repl.rs index 5a76b93c..ee20c4c8 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -155,7 +155,7 @@ fn main() { } // Merge the AST into the main - main_ast = main_ast.merge(&ast); + main_ast += ast.clone(); // Evaluate engine.eval_ast_with_scope::(&mut scope, &main_ast) diff --git a/src/parser.rs b/src/parser.rs index a4dd3b1c..b2b30bb3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -31,7 +31,7 @@ use crate::stdlib::{ hash::{Hash, Hasher}, iter::empty, num::NonZeroUsize, - ops::Add, + ops::{Add, AddAssign}, string::{String, ToString}, vec, vec::Vec, @@ -152,7 +152,7 @@ impl AST { /// 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_. + /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. /// Of course, if the first `AST` uses a `return` statement at the end, then /// the second `AST` will essentially be dead code. @@ -202,10 +202,62 @@ impl AST { self.merge_filtered(other, |_, _, _| true) } + /// Combine one `AST` with another. The second `AST` is consumed. + /// + /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. + /// Of course, if the first `AST` uses a `return` statement at the end, then + /// the second `AST` will essentially be dead code. + /// + /// All script-defined functions in the second `AST` overwrite similarly-named functions + /// in the first `AST` with the same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let mut ast1 = engine.compile(r#" + /// fn foo(x) { 42 + x } + /// foo(1) + /// "#)?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { "hello" + n } + /// foo("!") + /// "#)?; + /// + /// ast1.combine(ast2); // Combine 'ast2' into 'ast1' + /// + /// // Notice that using the '+=' operator also works: + /// // ast1 += ast2; + /// + /// // 'ast1' is essentially: + /// // + /// // fn foo(n) { "hello" + n } // <- definition of first 'foo' is overwritten + /// // foo(1) // <- notice this will be "hello1" instead of 43, + /// // // but it is no longer the return value + /// // foo("!") // returns "hello!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast1)?, "hello!"); + /// # } + /// # Ok(()) + /// # } + /// ``` + pub fn combine(&mut self, other: Self) -> &mut Self { + self.combine_filtered(other, |_, _, _| true) + } + /// 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_. + /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. /// Of course, if the first `AST` uses a `return` statement at the end, then /// the second `AST` will essentially be dead code. @@ -277,6 +329,72 @@ impl AST { Self::new(ast, functions) } + /// Combine one `AST` with another. The second `AST` is consumed. + /// + /// Statements in the second `AST` are simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. + /// Of course, if the first `AST` uses a `return` statement at the end, then + /// the second `AST` will essentially be dead code. + /// + /// All script-defined functions in the second `AST` are first selected based on a filter + /// predicate, then overwrite similarly-named functions in the first `AST` with the + /// same number of parameters. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// # #[cfg(not(feature = "no_function"))] + /// # { + /// use rhai::Engine; + /// + /// let engine = Engine::new(); + /// + /// let mut ast1 = engine.compile(r#" + /// fn foo(x) { 42 + x } + /// foo(1) + /// "#)?; + /// + /// let ast2 = engine.compile(r#" + /// fn foo(n) { "hello" + n } + /// fn error() { 0 } + /// foo("!") + /// "#)?; + /// + /// // Combine 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1' + /// ast1.combine_filtered(ast2, |_, name, params| name == "error" && params == 0); + /// + /// // 'ast1' is essentially: + /// // + /// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten + /// // // because 'ast2::foo' is filtered away + /// // foo(1) // <- notice this will be 43 instead of "hello1", + /// // // but it is no longer the return value + /// // fn error() { 0 } // <- this function passes the filter and is merged + /// // foo("!") // <- returns "42!" + /// + /// // Evaluate it + /// assert_eq!(engine.eval_ast::(&ast1)?, "42!"); + /// # } + /// # Ok(()) + /// # } + /// ``` + pub fn combine_filtered( + &mut self, + other: Self, + mut filter: impl FnMut(FnAccess, &str, usize) -> bool, + ) -> &mut Self { + let Self(ref mut statements, ref mut functions) = self; + + if !other.0.is_empty() { + statements.extend(other.0.into_iter()); + } + + functions.merge_filtered(&other.1, &mut filter); + + self + } + /// Filter out the functions, retaining only some based on a filter predicate. /// /// # Example @@ -333,6 +451,20 @@ impl Add for &AST { } } +impl Add<&Self> for &AST { + type Output = AST; + + fn add(self, rhs: &Self) -> Self::Output { + self.merge(rhs) + } +} + +impl AddAssign for &mut AST { + fn add_assign(&mut self, rhs: AST) { + self.combine(rhs); + } +} + impl AsRef<[Stmt]> for AST { fn as_ref(&self) -> &[Stmt] { self.statements()