From c86a97960118651ca74fc2b5888bb006c6b34f70 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 7 Aug 2020 18:40:31 +0800 Subject: [PATCH] Refine docs. --- README.md | 2 +- doc/src/SUMMARY.md | 8 ++++---- doc/src/about/features.md | 2 +- doc/src/language/arrays.md | 10 ++++++++-- doc/src/language/object-maps.md | 31 +++++++++++++++++++++++-------- doc/src/links.md | 3 +++ doc/src/patterns/config.md | 8 +++++--- doc/src/patterns/control.md | 16 +++++++++++++++- doc/src/patterns/index.md | 4 +--- doc/src/patterns/oop.md | 29 ++++++++++++++--------------- doc/src/patterns/singleton.md | 22 ++++++++++++++++++---- doc/src/rust/functions.md | 8 ++++++-- doc/src/rust/indexers.md | 4 +++- doc/src/rust/serde.md | 2 +- doc/src/safety/sandbox.md | 30 ++++++++++++++++++++++++------ examples/serde.rs | 2 +- 16 files changed, 128 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 338a1309..bfd3fc0c 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Standard features Protection against attacks -------------------------- -* Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`). +* Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless [explicitly permitted](https://schungx.github.io/rhai/patterns/control.html). * Rugged - protected against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts. * Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run. diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index a705b015..2ef05a0f 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -101,10 +101,10 @@ The Rhai Scripting Language 9. [Maximum Statement Depth](safety/max-stmt-depth.md) 7. [Advanced Topics](advanced.md) 1. [Advanced Patterns](patterns/index.md) - 1. [Loadable Configuration](patterns/config.md) - 2. [Control Layer](patterns/control.md) - 3. [Singleton Command](patterns/singleton.md) - 4. [Object-Oriented Programming (OOP)](patterns/oop.md) + 1. [Object-Oriented Programming (OOP)](patterns/oop.md) + 2. [Loadable Configuration](patterns/config.md) + 3. [Control Layer](patterns/control.md) + 4. [Singleton Command](patterns/singleton.md) 2. [Capture Scope for Function Call](language/fn-capture.md) 3. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md) 4. [Script Optimization](engine/optimize/index.md) diff --git a/doc/src/about/features.md b/doc/src/about/features.md index 6c2e3965..d0624c2b 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -46,7 +46,7 @@ Safe * Relatively little `unsafe` code (yes there are some for performance reasons). -* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`). +* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless [explicitly permitted]({{rootUrl}}/patterns/control.md). Rugged ------ diff --git a/doc/src/language/arrays.md b/doc/src/language/arrays.md index 3afc09b6..69f24103 100644 --- a/doc/src/language/arrays.md +++ b/doc/src/language/arrays.md @@ -3,9 +3,15 @@ Arrays {{#include ../links.md}} -Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices. +Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices: -Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`'. +> _array_ `[` _index_ `]` + +Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`': + +> `[` _value_ `,` _value_ `,` `...` `,` _value_ `]` +> +> `[` _value_ `,` _value_ `,` `...` `,` _value_ `,` `]` `// trailing comma is OK` All elements stored in an array are [`Dynamic`], and the array can freely grow or shrink with elements added or removed. diff --git a/doc/src/language/object-maps.md b/doc/src/language/object-maps.md index 27220134..c2c0d20e 100644 --- a/doc/src/language/object-maps.md +++ b/doc/src/language/object-maps.md @@ -19,21 +19,36 @@ Object Map Literals ------------------ 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 +and separated by commas '`,`': + +> `#{` _property_ `:` _value_ `,` `...` `,` _property_ `:` _value_ `}` +> +> `#{` _property_ `:` _value_ `,` `...` `,` _property_ `:` _value_ `,` `}` `// trailing comma is OK` + +The property _name_ can be a simple variable name following the same naming rules as [variables], or an arbitrary [string] literal. Access Properties ----------------- +----------------- -Property values can be accessed via the _dot_ notation (_object_ `.` _property_) -or _index_ notation (_object_ `[` _property_ `]`). +### Dot Notation -The dot notation allows only property names that follow the same naming rules as [variables]. +The _dot notation_ allows only property names that follow the same naming rules as [variables]. -The index notation allows setting/getting properties of arbitrary names (even the empty [string]). +> _object_ `.` _property_ -**Important:** Trying to read a non-existent property returns [`()`] instead of causing an error. +### Index Notation + +The _index notation_ allows setting/getting properties of arbitrary names (even the empty [string]). + +> _object_ `[` _property_ `]` + +### Non-Existence + +Trying to read a non-existing property returns [`()`] instead of causing an error. + +This is similar to JavaScript where accessing a non-existing property returns `undefined`. Built-in Functions @@ -89,7 +104,7 @@ let foo = #{ a:1, b:2, c:3 }["a"]; foo == 1; fn abc() { - #{ a:1, b:2, c:3 } // a function returning an object map + ##{ a:1, b:2, c:3 } // a function returning an object map } let foo = abc().b; diff --git a/doc/src/links.md b/doc/src/links.md index d2df1d5b..ee7f05e4 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -76,6 +76,8 @@ [function]: {{rootUrl}}/language/functions.md [functions]: {{rootUrl}}/language/functions.md +[function overloading]: {{rootUrl}}/rust/functions.md#function-overloading +[fallible functions]: {{rootUrl}}/rust/fallible.md [function pointer]: {{rootUrl}}/language/fn-ptr.md [function pointers]: {{rootUrl}}/language/fn-ptr.md [currying]: {{rootUrl}}/language/fn-curry.md @@ -100,6 +102,7 @@ [OOP]: {{rootUrl}}/patterns/oop.md [DSL]: {{rootUrl}}/engine/dsl.md +[sand-boxed]: {{rootUrl}}/safety/sandbox.md [maximum statement depth]: {{rootUrl}}/safety/max-stmt-depth.md [maximum call stack depth]: {{rootUrl}}/safety/max-call-stack.md [maximum number of operations]: {{rootUrl}}/safety/max-operations.md diff --git a/doc/src/patterns/config.md b/doc/src/patterns/config.md index 640191fa..a1fcdf44 100644 --- a/doc/src/patterns/config.md +++ b/doc/src/patterns/config.md @@ -9,11 +9,11 @@ Usage Scenario * A system where settings and configurations are complex and logic-driven. -* Where it is not possible to configure said system via standard configuration file formats such as `TOML` or `YAML`. +* Where said system is too complex to configure via standard configuration file formats such as `JSON`, `TOML` or `YAML`. -* The system configuration is complex enough that it requires a full programming language. Essentially _configuration by code_. +* The system is complex enough to require a full programming language to configure. Essentially _configuration by code_. -* Yet the configurations must be flexible, late-bound and dynamically loadable, just like a configuration file. +* Yet the configuration must be flexible, late-bound and dynamically loadable, just like a configuration file. Key Concepts @@ -23,6 +23,8 @@ Key Concepts * Expose the configuration API. Use separate scripts to configure that API. Dynamically load scripts via the `import` statement. +* Leverage [function overloading] to simplify the API design. + * Since Rhai is _sand-boxed_, it cannot mutate the environment. To modify the external configuration object via an API, it must be wrapped in a `RefCell` (or `RwLock`/`Mutex` for [`sync`]) and shared to the [`Engine`]. diff --git a/doc/src/patterns/control.md b/doc/src/patterns/control.md index 2f5aa961..af6021e6 100644 --- a/doc/src/patterns/control.md +++ b/doc/src/patterns/control.md @@ -19,12 +19,26 @@ Key Concepts * Expose a Control API. -* Since Rhai is _sand-boxed_, it cannot mutate the environment. To perform external actions via an API, the actual system must be wrapped in a `RefCell` (or `RwLock`/`Mutex` for [`sync`]) and shared to the [`Engine`]. +* Leverage [function overloading] to simplify the API design. + +* Since Rhai is _[sand-boxed]_, it cannot mutate the environment. To perform external actions via an API, the actual system must be wrapped in a `RefCell` (or `RwLock`/`Mutex` for [`sync`]) and shared to the [`Engine`]. Implementation -------------- +There are two broad ways for Rhai to control an external system, both of which involve +wrapping the system in a shared, interior-mutated object. + +This is one way which does not involve exposing the data structures of the external system, +but only through exposing an abstract API primarily made up of functions. + +Use this when the API is relatively simple and clean, and the number of functions is small enough. + +For a complex API involving lots of functions, or an API that is object-based, +use the [Singleton Command Object]({{rootUrl}}/patterns/singleton.md) pattern instead. + + ### Functional API Assume that a system provides the following functional API: diff --git a/doc/src/patterns/index.md b/doc/src/patterns/index.md index 522d3403..b0e5d633 100644 --- a/doc/src/patterns/index.md +++ b/doc/src/patterns/index.md @@ -4,6 +4,4 @@ Advanced Patterns {{#include ../links.md}} -Use Rhai in different scenarios other than simply evaluating a user script. - -These patterns are useful when Rhai needs to affect/control the external environment. +Leverage the full power and flexibility of Rhai in advanced scenarios. diff --git a/doc/src/patterns/oop.md b/doc/src/patterns/oop.md index 017c4932..823df31e 100644 --- a/doc/src/patterns/oop.md +++ b/doc/src/patterns/oop.md @@ -18,17 +18,17 @@ Rhai's [object maps] has [special support for OOP]({{rootUrl}}/language/object-m | [Object map] properties that hold [function pointers] | methods | When a property of an [object map] is called like a method function, and if it happens to hold -a valid [function pointer] (perhaps defined via an [anonymous function]), then the call will be -dispatched to the actual function with `this` binding to the [object map] itself. +a valid [function pointer] (perhaps defined via an [anonymous function] or more commonly as a [closure]), +then the call will be dispatched to the actual function with `this` binding to the [object map] itself. Use Anonymous Functions to Define Methods ---------------------------------------- -[Anonymous functions] defined as values for [object map] properties take on a syntactic shape -that resembles very closely that of class methods in an OOP language. +[Anonymous functions] or [closures] defined as values for [object map] properties take on +a syntactic shape that resembles very closely that of class methods in an OOP language. -Anonymous functions can also _capture_ variables from the defining environment, which is a very +Closures also _[capture][automatic currying]_ variables from the defining environment, which is a very common OOP pattern. Capturing is accomplished via a feature called _[automatic currying]_ and can be turned off via the [`no_closure`] feature. @@ -40,23 +40,22 @@ Examples let factor = 1; // Define the object -let obj = - #{ - data: 0, - increment: |x| this.data += x, // 'this' binds to 'obj' - update: |x| this.data = x * factor, // 'this' binds to 'obj', 'factor' is captured - action: || print(this.data) // 'this' binds to 'obj' - }; +let obj = #{ + data: 0, // object field + increment: |x| this.data += x, // 'this' binds to 'obj' + update: |x| this.data = x * factor, // 'this' binds to 'obj', 'factor' is captured + action: || print(this.data) // 'this' binds to 'obj' + }; // Use the object obj.increment(1); -obj.action(); // prints 1 +obj.action(); // prints 1 obj.update(42); -obj.action(); // prints 42 +obj.action(); // prints 42 factor = 2; obj.update(42); -obj.action(); // prints 84 +obj.action(); // prints 84 ``` diff --git a/doc/src/patterns/singleton.md b/doc/src/patterns/singleton.md index 7172e745..c38df1d5 100644 --- a/doc/src/patterns/singleton.md +++ b/doc/src/patterns/singleton.md @@ -1,5 +1,5 @@ -Singleton Command Objects -======================== +Singleton Command Object +======================= {{#include ../links.md}} @@ -21,9 +21,11 @@ Usage Scenario Key Concepts ------------ -* Expose a Command type with an API. +* Expose a Command type with an API. The [`no_object`] feature must not be on. -* Since Rhai is _sand-boxed_, it cannot mutate the environment. To perform external actions via an API, the command object type must be wrapped in a `RefCell` (or `RwLock`/`Mutex` for [`sync`]) and shared to the [`Engine`]. +* Leverage [function overloading] to simplify the API design. + +* Since Rhai is _[sand-boxed]_, it cannot mutate the environment. To perform external actions via an API, the command object type must be wrapped in a `RefCell` (or `RwLock`/`Mutex` for [`sync`]) and shared to the [`Engine`]. * Load each command object into a custom [`Scope`] as constant variables. @@ -33,6 +35,18 @@ Key Concepts Implementation -------------- +There are two broad ways for Rhai to control an external system, both of which involve +wrapping the system in a shared, interior-mutated object. + +This is the other way which involves directly exposing the data structures of the external system +as a name singleton object in the scripting space. + +Use this when the API is complex and clearly object-based. + +For a relatively simple API that is action-based and not object-based, +use the [Control Layer]({{rootUrl}}/patterns/control.md) pattern instead. + + ### Functional API Assume the following command object type: diff --git a/doc/src/rust/functions.md b/doc/src/rust/functions.md index 8d8666cf..99e9a485 100644 --- a/doc/src/rust/functions.md +++ b/doc/src/rust/functions.md @@ -7,7 +7,7 @@ Rhai's scripting engine is very lightweight. It gets most of its abilities from To call these functions, they need to be _registered_ with the [`Engine`] using `Engine::register_fn` (in the `RegisterFn` trait) and `Engine::register_result_fn` (in the `RegisterResultFn` trait, -see [fallible functions]({{rootUrl}}/rust/fallible.md)). +see [fallible functions]). ```rust use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString}; @@ -62,8 +62,12 @@ let x = (42_i64).into(); // 'into()' works for standard t let y = Dynamic::from("hello!".to_string()); // remember &str is not supported by Rhai ``` + +Function Overloading +-------------------- + Functions registered with the [`Engine`] can be _overloaded_ as long as the _signature_ is unique, i.e. different functions can have the same name as long as their parameters are of different types -and/or different number. +or different number. New definitions _overwrite_ previous definitions of the same name and same number/types of parameters. diff --git a/doc/src/rust/indexers.md b/doc/src/rust/indexers.md index d5905b44..94a6b5f1 100644 --- a/doc/src/rust/indexers.md +++ b/doc/src/rust/indexers.md @@ -5,7 +5,9 @@ Custom Type Indexers A custom type can also expose an _indexer_ by registering an indexer function. -A custom type with an indexer function defined can use the bracket '`[]`' notation to get a property value. +A custom type with an indexer function defined can use the bracket notation to get a property value: + +> _object_ `[` _index_ `]` Like getters and setters, indexers take a `&mut` reference to the first parameter. diff --git a/doc/src/rust/serde.md b/doc/src/rust/serde.md index de59dc34..c28eccfd 100644 --- a/doc/src/rust/serde.md +++ b/doc/src/rust/serde.md @@ -91,7 +91,7 @@ struct MyStruct { let engine = Engine::new(); let result: Dynamic = engine.eval(r#" - #{ + ##{ a: 42, b: [ "hello", "world" ], c: true, diff --git a/doc/src/safety/sandbox.md b/doc/src/safety/sandbox.md index 61d8469d..a85a5f9e 100644 --- a/doc/src/safety/sandbox.md +++ b/doc/src/safety/sandbox.md @@ -5,13 +5,31 @@ Sand-Boxing - Block Access to External Data Rhai is _sand-boxed_ so a script can never read from outside its own environment. -Furthermore, an [`Engine`] created non-`mut` cannot mutate any state outside of itself; -so it is highly recommended that [`Engine`]'s are created immutable as much as possible. +Furthermore, an [`Engine`] created non-`mut` cannot mutate any state, including itself +(and therefore it is also _re-entrant_). + +It is highly recommended that [`Engine`]'s be created immutable as much as possible. ```rust -let mut engine = Engine::new(); // create mutable 'Engine' +// Use the fluent API to configure an 'Engine' and then keep an immutable instance. +let engine = Engine::new() + .register_get("field", get_field) + .register_set("field", set_field) + .register_fn("do_work", action); -engine.register_get("add", add); // configure 'engine' - -let engine = engine; // shadow the variable so that 'engine' is now immutable +// 'engine' is immutable... ``` + + +Using Rhai to Control External Environment +----------------------------------------- + +How does a _sand-boxed_, immutable [`Engine`] control the external environment? +This is necessary in order to use Rhai as a _dynamic control layer_ over a Rust core system. + +There are two general patterns, both involving wrapping the external system +in a shared, interior-mutated object (e.g. `Rc>`): + +* [Control Layer]({{rootUrl}}/patterns/control.md) pattern. + +* [Singleton Command Object]({{rootUrl}}/patterns/singleton.md) pattern. diff --git a/examples/serde.rs b/examples/serde.rs index 3cb68459..52c39b51 100644 --- a/examples/serde.rs +++ b/examples/serde.rs @@ -56,7 +56,7 @@ mod example { let result: Dynamic = engine .eval( r#" - #{ + ##{ a: 42, b: [ "hello", "world" ], c: true,