Add AST::combine and AST::combine_filtered.

This commit is contained in:
Stephen Chung 2020-10-07 12:11:25 +08:00
parent 3340760b35
commit 1523981e4e
5 changed files with 151 additions and 18 deletions

View File

@ -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

View File

@ -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

View File

@ -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:
//

View File

@ -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::<Dynamic>(&mut scope, &main_ast)

View File

@ -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<rhai::EvalAltResult>> {
/// # #[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::<String>(&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<rhai::EvalAltResult>> {
/// # #[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::<String>(&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<Self> for &AST {
}
}
impl Add<&Self> for &AST {
type Output = AST;
fn add(self, rhs: &Self) -> Self::Output {
self.merge(rhs)
}
}
impl AddAssign<AST> 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()