From df1dd5190eb5c825e01f44895b25a3561faeda02 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 7 Oct 2020 10:43:53 +0800 Subject: [PATCH] Add usage pattern on multi-layer functions. --- doc/src/SUMMARY.md | 5 +- doc/src/engine/custom-syntax.md | 2 +- doc/src/patterns/multi-layer.md | 117 ++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 doc/src/patterns/multi-layer.md diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 9b6f1fc5..ce9c1554 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -118,8 +118,9 @@ The Rhai Scripting Language 2. [Loadable Configuration](patterns/config.md) 3. [Control Layer](patterns/control.md) 4. [Singleton Command](patterns/singleton.md) - 5. [One Engine Instance Per Call](patterns/parallel.md) - 6. [Scriptable Event Handler with State](patterns/events.md) + 5. [Multi-Layer Functions](patterns/multi-layer.md) + 6. [One Engine Instance Per Call](patterns/parallel.md) + 7. [Scriptable Event Handler with State](patterns/events.md) 9. [Advanced Topics](advanced.md) 1. [Capture Scope for Function Call](language/fn-capture.md) 2. [Low-Level API](rust/register-raw.md) diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md index 3f88112c..f1d8e481 100644 --- a/doc/src/engine/custom-syntax.md +++ b/doc/src/engine/custom-syntax.md @@ -131,7 +131,7 @@ It should simply be passed straight-through the the [`Engine`]. ### Access Arguments The most important argument is `inputs` where the matched identifiers (`$ident$`), expressions/statements (`$expr$`) -and statement blocks (`$block$) are provided. +and statement blocks (`$block$`) are provided. To access a particular argument, use the following patterns: diff --git a/doc/src/patterns/multi-layer.md b/doc/src/patterns/multi-layer.md new file mode 100644 index 00000000..8a296c72 --- /dev/null +++ b/doc/src/patterns/multi-layer.md @@ -0,0 +1,117 @@ +Multi-Layer Functions +===================== + +{{#include ../links.md}} + + +Usage Scenario +-------------- + +* A system is divided into separate _layers_, each providing logic in terms of scripted [functions]. + +* A lower layer provides _default implementations_ of certain functions. + +* Higher layers each provide progressively more specific implementations of the same functions. + +* A more specific function, if defined in a higher layer, always overrides the implementation in a lower layer. + +* This is akin to object-oriented programming but with functions. + +* This type of system is extremely convenient for dynamic business rules configuration, setting corporate-wide + policies, granting permissions for specific roles etc. where specific, local rules need to override + corporate-wide defaults. + + +Key Concepts +------------ + +* Each layer is a separate script. + +* 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. + + +Examples +-------- + +Assume the following four scripts: + +```rust +---------------- +| default.rhai | +---------------- + +// Default implementation of 'foo'. +fn foo(x) { x + 1 } + +// Default implementation of 'bar'. +fn bar(x, y) { x + y } + +// Default implementation of 'no_touch'. +fn no_touch() { throw "do not touch me!"; } + + +--------------- +| lowest.rhai | +--------------- + +// Specific implementation of 'foo'. +fn foo(x) { x * 2 } + +// New implementation for this layer. +fn baz() { print("hello!"); } + + +--------------- +| middle.rhai | +--------------- + +// Specific implementation of 'bar'. +fn bar(x, y) { x - y } + +// Specific implementation of 'baz'. +fn baz() { print("hey!"); } + + +---------------- +| highest.rhai | +---------------- + +// Specific implementation of 'foo'. +fn foo(x) { x + 42 } +``` + +Load and merge them sequentially: + +```rust +let engine = Engine::new(); + +// Compile the baseline default implementations. +let mut ast = engine.compile_file("default.rhai".into())?; + +// Merge in the first layer. +let lowest = engine.compile_file("lowest.rhai".into())?; +ast = ast.merge(&lowest); + +// Merge in the second layer. +let middle = engine.compile_file("middle.rhai".into())?; +ast = ast.merge(&middle); + +// Merge in the third layer. +let highest = engine.compile_file("highest.rhai".into())?; +ast = ast.merge(&highest); + +// Now, 'ast' contains the following functions: +// +// fn no_touch() { // from 'default.rhai' +// throw "do not touch me!"; +// } +// fn foo(x) { x + 42 } // from 'highest.rhai' +// fn bar(x, y) { x - y } // from 'middle.rhai' +// fn baz() { print("hey!"); } // from 'middle.rhai' +``` + +Unfortunately, there is no `super` call that calls the base implementation +(i.e. no way for a higher-layer function to call an equivalent lower-layer function).