From 64c421b3d736628da3557866a1865a3b9cddbb9c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 28 Sep 2020 22:14:19 +0800 Subject: [PATCH] Revise docs. --- README.md | 21 ++- RELEASES.md | 13 +- doc/src/SUMMARY.md | 6 +- doc/src/about/features.md | 2 +- doc/src/appendix/keywords.md | 60 ++++---- doc/src/appendix/literals.md | 22 +-- doc/src/appendix/operators.md | 20 +-- doc/src/engine/custom-syntax.md | 8 +- doc/src/engine/func.md | 14 +- doc/src/engine/optimize/semantics.md | 10 +- doc/src/engine/raw.md | 1 - doc/src/engine/scope.md | 7 +- doc/src/language/arrays.md | 32 ++--- doc/src/language/constants.md | 39 +++++- doc/src/language/convert.md | 32 +++++ doc/src/language/dynamic.md | 27 ++-- doc/src/language/fn-anon.md | 4 +- doc/src/language/fn-closure.md | 50 +++---- doc/src/language/fn-namespaces.md | 6 +- doc/src/language/fn-ptr.md | 4 +- doc/src/language/json.md | 16 ++- doc/src/language/method.md | 8 +- doc/src/language/modules/ast.md | 59 -------- doc/src/language/modules/export.md | 29 +--- doc/src/language/object-maps-oop.md | 14 +- doc/src/language/object-maps.md | 22 +-- doc/src/language/string-fn.md | 28 ++-- doc/src/language/strings-chars.md | 22 +-- doc/src/language/type-of.md | 21 +++ doc/src/links.md | 2 + doc/src/patterns/config.md | 16 ++- doc/src/patterns/control.md | 6 + doc/src/patterns/singleton.md | 78 +++++++---- doc/src/plugins/function.md | 10 +- doc/src/plugins/module.md | 22 +-- doc/src/rust/getters-setters.md | 8 +- doc/src/rust/modules/ast.md | 88 ++++++++++++ doc/src/rust/modules/create.md | 36 +++-- doc/src/rust/modules/imp-resolver.md | 19 ++- doc/src/rust/modules/resolvers.md | 128 ++++++++++++++++-- doc/src/rust/packages/create.md | 8 +- doc/src/rust/packages/index.md | 8 +- doc/src/rust/register-raw.md | 12 +- doc/src/rust/serde.md | 11 ++ doc/src/safety/sandbox.md | 14 +- ...{macro_register.rs => plugins_register.rs} | 0 tests/{macro_unroll.rs => plugins_unroll.rs} | 0 47 files changed, 686 insertions(+), 377 deletions(-) delete mode 100644 doc/src/language/modules/ast.md create mode 100644 doc/src/rust/modules/ast.md rename tests/{macro_register.rs => plugins_register.rs} (100%) rename tests/{macro_unroll.rs => plugins_unroll.rs} (100%) diff --git a/README.md b/README.md index fbd2f3d2..5b06f34c 100644 --- a/README.md +++ b/README.md @@ -23,22 +23,23 @@ Standard features ----------------- * Easy-to-use language similar to JavaScript+Rust with dynamic typing. +* Fairly low compile-time overhead. +* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM). * Tight integration with native Rust [functions](https://schungx.github.io/rhai/rust/functions.html) and [types]([#custom-types-and-methods](https://schungx.github.io/rhai/rust/custom.html)), including [getters/setters](https://schungx.github.io/rhai/rust/getters-setters.html), [methods](https://schungx.github.io/rhai/rust/custom.html) and [indexers](https://schungx.github.io/rhai/rust/indexers.html). * Freely pass Rust variables/constants into a script via an external [`Scope`](https://schungx.github.io/rhai/rust/scope.html). * Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust. -* Fairly low compile-time overhead. -* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM). * Relatively little `unsafe` code (yes there are some for performance reasons). +* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec)). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). -* [Function overloading](https://schungx.github.io/rhai/language/overload.html). -* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html). -* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html) with additional support for [currying](https://schungx.github.io/rhai/language/fn-curry.html) and [closures](https://schungx.github.io/rhai/language/fn-closure.html). -* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html). +* Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations. +* Easy custom API development via [plugins](https://schungx.github.io/rhai/plugins/index.html) system powered by procedural macros. +* [Function overloading](https://schungx.github.io/rhai/language/overload.html) and [operator overloading](https://schungx.github.io/rhai/rust/operators.html). +* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html) with additional support for [currying](https://schungx.github.io/rhai/language/fn-curry.html). +* [Closures](https://schungx.github.io/rhai/language/fn-closure.html) (anonymous functions) that can capture shared values. +* Some syntactic support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html). * Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html). * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). -* Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations. * Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html). -* Easy custom API development via [plugins](https://schungx.github.io/rhai/plugins/index.html) system powered by procedural macros. Protection against attacks -------------------------- @@ -69,13 +70,11 @@ Scripts can be evaluated directly from the editor. License ------- -Licensed under either: +Licensed under either of the following, at your choice: * [Apache License, Version 2.0](https://github.com/jonathandturner/rhai/blob/master/LICENSE-APACHE.txt), or * [MIT license](https://github.com/jonathandturner/rhai/blob/master/LICENSE-MIT.txt) -at your choice. - Unless explicitly stated otherwise, any contribution intentionally submitted for inclusion in this crate, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions. diff --git a/RELEASES.md b/RELEASES.md index a4c31fee..3503baf7 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,6 +4,9 @@ Rhai Release Notes Version 0.19.0 ============== +The major new feature for this version is _Plugins_ support, powered by procedural macros. +Plugins make it extremely easy to develop and register Rust functions with an `Engine`. + Bug fixes --------- @@ -17,10 +20,10 @@ Breaking changes ---------------- * `Engine::register_set_result` and `Engine::register_indexer_set_result` now take a function that returns `Result<(), Box>`. -* `Engine::register_indexer_XXX` and `Module::set_indexer_XXX` panic when the type is `Arrary`, `Map` or `String`. +* `Engine::register_indexer_XXX` and `Module::set_indexer_XXX` panic when the type is `Array`, `Map` or `String`. * `EvalAltResult` has a new variant `ErrorInModule` which holds errors when loading an external module. * `Module::eval_ast_as_new` now takes an extra boolean parameter, indicating whether to encapsulate the entire module into a separate namespace. -* Functions in `FileModuleResolver` loaded modules now can cross-call each other, but cannot access the global namespace. For the old behavior, use `MergingFileModuleResolver` instead. +* Functions in `FileModuleResolver` loaded modules now can cross-call each other in addition to functions in the global namespace. For the old behavior, use `MergingFileModuleResolver` instead. * New `EvalAltResult::ErrorInModule` variant capturing errors when loading a module from a script file. New features @@ -28,11 +31,11 @@ New features * Plugins support via procedural macros. * Scripted functions are allowed in packages. -* `parse_int` and `parse_float` functions. +* `parse_int` and `parse_float` functions for parsing numbers; `split` function for splitting strings. * `AST::iter_functions` and `Module::iter_script_fn_info` to iterate functions. -* Functions iteration functions now take `FnMut` instead of `Fn`. +* Functions iteration functions for `AST` and `Module` now take `FnMut` instead of `Fn`. * New `FileModuleResolver` that encapsulates the entire `AST` of the module script, allowing function cross-calling. The old version is renamed `MergingFileModuleResolver`. -* `split` function for splitting strings. +* `+` and `-` operators for timestamps to increment/decrement by seconds. Version 0.18.3 diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 23f39757..5193b7bb 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -81,15 +81,15 @@ The Rhai Scripting Language 2. [Overloading](language/overload.md) 3. [Namespaces](language/fn-namespaces.md) 4. [Function Pointers](language/fn-ptr.md) - 5. [Anonymous Functions](language/fn-anon.md) - 6. [Currying](language/fn-curry.md) + 5. [Currying](language/fn-curry.md) + 6. [Anonymous Functions](language/fn-anon.md) 7. [Closures](language/fn-closure.md) 16. [Print and Debug](language/print-debug.md) 17. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 2. [Import Modules](language/modules/import.md) 3. [Create from Rust](rust/modules/create.md) - 4. [Create from AST](language/modules/ast.md) + 4. [Create from AST](rust/modules/ast.md) 5. [Module Resolvers](rust/modules/resolvers.md) 1. [Custom Implementation](rust/modules/imp-resolver.md) 18. [Eval Statement](language/eval.md) diff --git a/doc/src/about/features.md b/doc/src/about/features.md index cacc1885..bcfe5a6e 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -37,7 +37,7 @@ Dynamic * Dynamic dispatch via [function pointers] with additional support for [currying]. -* Closures via [automatic currying] with capturing shared variables from the external scope. +* [Closures] that can capture shared variables. * Some support for [object-oriented programming (OOP)][OOP]. diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index 1b52d049..47a7b446 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -3,36 +3,36 @@ Keywords List {{#include ../links.md}} -| Keyword | Description | Inactive under | Overloadable | -| :-------------------: | ---------------------------------------- | :-------------: | :----------: | -| `true` | boolean true literal | | no | -| `false` | boolean false literal | | no | -| `let` | variable declaration | | no | -| `const` | constant declaration | | no | -| `is_shared` | is a value shared? | | no | -| `if` | if statement | | no | -| `else` | else block of if statement | | no | -| `while` | while loop | | no | -| `loop` | infinite loop | | no | -| `for` | for loop | | no | -| `in` | containment test, part of for loop | | no | -| `continue` | continue a loop at the next iteration | | no | -| `break` | loop breaking | | no | -| `return` | return value | | no | -| `throw` | throw exception | | no | -| `import` | import module | [`no_module`] | no | -| `export` | export variable | [`no_module`] | no | -| `as` | alias for variable export | [`no_module`] | no | -| `private` | mark function private | [`no_function`] | no | -| `fn` (lower-case `f`) | function definition | [`no_function`] | no | -| `Fn` (capital `F`) | function to create a [function pointer] | | yes | -| `call` | call a [function pointer] | | no | -| `curry` | curry a [function pointer] | | no | -| `this` | reference to base object for method call | [`no_function`] | no | -| `type_of` | get type name of value | | yes | -| `print` | print value | | yes | -| `debug` | print value in debug format | | yes | -| `eval` | evaluate script | | yes | +| Keyword | Description | Inactive under | Overloadable | +| :-------------------: | ------------------------------------------- | :-------------: | :----------: | +| `true` | boolean true literal | | no | +| `false` | boolean false literal | | no | +| `let` | variable declaration | | no | +| `const` | constant declaration | | no | +| `is_shared` | is a value shared? | | no | +| `if` | if statement | | no | +| `else` | else block of if statement | | no | +| `while` | while loop | | no | +| `loop` | infinite loop | | no | +| `for` | for loop | | no | +| `in` | 1) containment test
2) part of for loop | | no | +| `continue` | continue a loop at the next iteration | | no | +| `break` | break out of loop iteration | | no | +| `return` | return value | | no | +| `throw` | throw exception | | no | +| `import` | import module | [`no_module`] | no | +| `export` | export variable | [`no_module`] | no | +| `as` | alias for variable export | [`no_module`] | no | +| `private` | mark function private | [`no_function`] | no | +| `fn` (lower-case `f`) | function definition | [`no_function`] | no | +| `Fn` (capital `F`) | create a [function pointer] | | yes | +| `call` | call a [function pointer] | | no | +| `curry` | curry a [function pointer] | | no | +| `this` | reference to base object for method call | [`no_function`] | no | +| `type_of` | get type name of value | | yes | +| `print` | print value | | yes | +| `debug` | print value in debug format | | yes | +| `eval` | evaluate script | | yes | Reserved Keywords diff --git a/doc/src/appendix/literals.md b/doc/src/appendix/literals.md index a308e951..2d51ba36 100644 --- a/doc/src/appendix/literals.md +++ b/doc/src/appendix/literals.md @@ -3,14 +3,14 @@ Literals Syntax {{#include ../links.md}} -| Type | Literal syntax | -| :--------------------------------: | :------------------------------------------------------------------------------: | -| `INT` | `42`, `-123`, `0`,
`0x????..` (hex), `0b????..` (binary), `0o????..` (octal) | -| `FLOAT` | `42.0`, `-123.456`, `0.0` | -| [String] | `"... \x?? \u???? \U???????? ..."` | -| Character | `"... \x?? \u???? \U???????? ..."` | -| [`Array`] | `[ ???, ???, ??? ]` | -| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` | -| Boolean true | `true` | -| Boolean false | `false` | -| `Nothing`/`null`/`nil`/`void`/Unit | `()` | +| Type | Literal syntax | +| :--------------------------------: | :-----------------------------------------------------------------------------------------: | +| `INT` | decimal: `42`, `-123`, `0`
hex: `0x????..`
binary: `0b????..`
octal: `0o????..` | +| `FLOAT` | `42.0`, `-123.456`, `0.0` | +| [String] | `"... \x?? \u???? \U???????? ..."` | +| Character | single: `'?'`
ASCII hex: `'\x??'`
Unicode: `'\u????'`, `'\U????????'` | +| [`Array`] | `[ ???, ???, ??? ]` | +| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` | +| Boolean true | `true` | +| Boolean false | `false` | +| `Nothing`/`null`/`nil`/`void`/Unit | `()` | diff --git a/doc/src/appendix/operators.md b/doc/src/appendix/operators.md index 812966a8..e53ba01b 100644 --- a/doc/src/appendix/operators.md +++ b/doc/src/appendix/operators.md @@ -40,13 +40,13 @@ Symbols | ------------ | ------------------------ | | `:` | property value separator | | `::` | module path separator | -| `#` | _Reserved_ | -| `=>` | _Reserved_ | -| `->` | _Reserved_ | -| `<-` | _Reserved_ | -| `===` | _Reserved_ | -| `!==` | _Reserved_ | -| `:=` | _Reserved_ | -| `::<` .. `>` | _Reserved_ | -| `@` | _Reserved_ | -| `(*` .. `*)` | _Reserved_ | +| `#` | _reserved_ | +| `=>` | _reserved_ | +| `->` | _reserved_ | +| `<-` | _reserved_ | +| `===` | _reserved_ | +| `!==` | _reserved_ | +| `:=` | _reserved_ | +| `::<` .. `>` | _reserved_ | +| `@` | _reserved_ | +| `(*` .. `*)` | _reserved_ | diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md index 8d0820c8..3f88112c 100644 --- a/doc/src/engine/custom-syntax.md +++ b/doc/src/engine/custom-syntax.md @@ -4,7 +4,7 @@ Extend Rhai with Custom Syntax {{#include ../links.md}} -For the ultimate advantageous, there is a built-in facility to _extend_ the Rhai language +For the ultimate adventurous, there is a built-in facility to _extend_ the Rhai language with custom-defined _syntax_. But before going off to define the next weird statement type, heed this warning: @@ -28,7 +28,7 @@ Where This Might Be Useful * Where a custom syntax _significantly_ simplifies the code and _significantly_ enhances understanding of the code's intent. -* Where certain logic cannot be easily encapsulated inside a function. This is usually the case where _closures_ are required, because Rhai does not have closures. +* Where certain logic cannot be easily encapsulated inside a function. * Where you just want to confuse your user and make their lives miserable, because you can. @@ -154,10 +154,10 @@ let result = engine.eval_expression_tree(context, scope, expr)?; New variables maybe declared (usually with a variable name that is passed in via `$ident$). -It can simply be pushed into the [`scope`]. +It can simply be pushed into the [`Scope`]. However, beware that all new variables must be declared _prior_ to evaluating any expression tree. -In other words, any `scope.push(...)` calls must come _before_ any `engine::eval_expression_tree(...)` calls. +In other words, any `Scope::push` calls must come _before_ any `Engine::eval_expression_tree` calls. ```rust let var_name = inputs[0].get_variable_name().unwrap().to_string(); diff --git a/doc/src/engine/func.md b/doc/src/engine/func.md index f545b926..d0ce6784 100644 --- a/doc/src/engine/func.md +++ b/doc/src/engine/func.md @@ -11,9 +11,9 @@ Creating them is accomplished via the `Func` trait which contains `create_from_s (as well as its companion method `create_from_ast`): ```rust -use rhai::{Engine, Func}; // use 'Func' for 'create_from_script' +use rhai::{Engine, Func}; // use 'Func' for 'create_from_script' -let engine = Engine::new(); // create a new 'Engine' just for this +let engine = Engine::new(); // create a new 'Engine' just for this let script = "fn calc(x, y) { x + y.len < 42 }"; @@ -25,14 +25,14 @@ let script = "fn calc(x, y) { x + y.len < 42 }"; let func = Func::<(i64, String), bool>::create_from_script( // ^^^^^^^^^^^^^ function parameter types in tuple - engine, // the 'Engine' is consumed into the closure - script, // the script, notice number of parameters must match - "calc" // the entry-point function name + engine, // the 'Engine' is consumed into the closure + script, // the script, notice number of parameters must match + "calc" // the entry-point function name )?; -func(123, "hello".to_string())? == false; // call the closure +func(123, "hello".to_string())? == false; // call the closure -schedule_callback(func); // pass it as a callback to another function +schedule_callback(func); // pass it as a callback to another function // Although there is nothing you can't do by manually writing out the closure yourself... let engine = Engine::new(); diff --git a/doc/src/engine/optimize/semantics.md b/doc/src/engine/optimize/semantics.md index 021173ef..db014688 100644 --- a/doc/src/engine/optimize/semantics.md +++ b/doc/src/engine/optimize/semantics.md @@ -8,13 +8,13 @@ Some optimizations can alter subtle semantics of the script. For example: ```rust -if true { // condition always true - 123.456; // eliminated - hello; // eliminated, EVEN THOUGH the variable doesn't exist! - foo(42) // promoted up-level +if true { // condition always true + 123.456; // eliminated + hello; // eliminated, EVEN THOUGH the variable doesn't exist! + foo(42) // promoted up-level } -foo(42) // <- the above optimizes to this +foo(42) // <- the above optimizes to this ``` If the original script were evaluated instead, it would have been an error - the variable `hello` does not exist, diff --git a/doc/src/engine/raw.md b/doc/src/engine/raw.md index 06e4ff33..cf867246 100644 --- a/doc/src/engine/raw.md +++ b/doc/src/engine/raw.md @@ -1,4 +1,3 @@ - Raw `Engine` =========== diff --git a/doc/src/engine/scope.md b/doc/src/engine/scope.md index daa08a15..3deecc7d 100644 --- a/doc/src/engine/scope.md +++ b/doc/src/engine/scope.md @@ -31,19 +31,20 @@ let mut scope = Scope::new(); scope .push("y", 42_i64) .push("z", 999_i64) + .push_constant("MY_NUMBER", 123_i64) // constants can also be added .set_value("s", "hello, world!".to_string()); //'set_value' adds a variable when one doesn't exist - // remember to use 'String', not '&str' + // remember to use 'String', not '&str' // First invocation engine.eval_with_scope::<()>(&mut scope, r" - let x = 4 + 5 - y + z + s.len; + let x = 4 + 5 - y + z + MY_NUMBER + s.len; y = 1; ")?; // Second invocation using the same state let result = engine.eval_with_scope::(&mut scope, "x")?; -println!("result: {}", result); // prints 979 +println!("result: {}", result); // prints 1102 // Variable y is changed in the script - read it with 'get_value' assert_eq!(scope.get_value::("y").expect("variable y should exist"), 1); diff --git a/doc/src/language/arrays.md b/doc/src/language/arrays.md index a4a8e458..29e1aafb 100644 --- a/doc/src/language/arrays.md +++ b/doc/src/language/arrays.md @@ -30,22 +30,22 @@ Built-in Functions The following methods (mostly defined in the [`BasicArrayPackage`][packages] but excluded if using a [raw `Engine`]) operate on arrays: -| Function | Parameter(s) | Description | -| ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -| `push` | element to insert | inserts an element at the end | -| `+=` operator | array, element to insert (not another array) | inserts an element at the end | -| `append` | array to append | concatenates the second array to the end of the first | -| `+=` operator | array, array to append | concatenates the second array to the end of the first | -| `+` operator | first array, second array | concatenates the first array with the second | -| `insert` | element to insert, position
(beginning if <= 0, end if >= length) | inserts an element at a certain index | -| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | -| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | -| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid | -| `reverse` | _none_ | reverses the array | -| `len` method and property | _none_ | returns the number of elements | -| `pad` | element to pad, target length | pads the array with an element to at least a specified length | -| `clear` | _none_ | empties the array | -| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | +| Function | Parameter(s) | Description | +| ------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `push` | element to insert | inserts an element at the end | +| `append` | array to append | concatenates the second array to the end of the first | +| `+=` operator | 1) array
2) element to insert (not another array) | inserts an element at the end | +| `+=` operator | 1) array
2) array to append | concatenates the second array to the end of the first | +| `+` operator | 1) first array
2) second array | concatenates the first array with the second | +| `insert` | 1) element to insert
2) position (beginning if <= 0, end if >= length) | inserts an element at a certain index | +| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | +| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | +| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid | +| `reverse` | _none_ | reverses the array | +| `len` method and property | _none_ | returns the number of elements | +| `pad` | 1) target length
2) element to pad | pads the array with an element to at least a specified length | +| `clear` | _none_ | empties the array | +| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | Use Custom Types With Arrays diff --git a/doc/src/language/constants.md b/doc/src/language/constants.md index d88ecd4f..81ad1ce6 100644 --- a/doc/src/language/constants.md +++ b/doc/src/language/constants.md @@ -16,8 +16,45 @@ x = 123; // <- syntax error: cannot assign to constant ``` Unlike variables which need not have initial values (default to [`()`]), -constants must be assigned one, and it must be a constant _value_, not an expression. +constants must be assigned one, and it must be a [_literal value_](../appendix/literals.md), +not an expression. ```rust const x = 40 + 2; // <- syntax error: cannot assign expression to constant ``` + + +Manually Add Constant into Custom Scope +-------------------------------------- + +It is possible to add a constant into a custom [`Scope`] so it'll be available to scripts +running with that [`Scope`]. + +When added to a custom [`Scope`], a constant can hold any value, not just a literal value. + +It is very useful to have a constant value hold a [custom type], which essentially acts +as a [_singleton_](../patterns/singleton.md). The singleton object can be modified via its +registered API - being a constant only prevents it from being re-assigned or operated upon by Rhai; +mutating it via a Rust function is still allowed. + +```rust +use rhai::{Engine, Scope}; + +struct TestStruct(i64); // custom type + +let engine = Engine::new() + .register_type_with_name::("TestStruct") // register custom type + .register_get_set("value", + |obj: &mut TestStruct| obj.0, // property getter + |obj: &mut TestStruct, value: i64| obj.0 = value // property setter + ); + +let mut scope = Scope::new(); // create custom scope + +scope.push_constant("MY_NUMBER", TestStruct(123_i64)); // add constant variable + +engine.consume_with_scope(&mut scope, r" + MY_NUMBER.value = 42; // constant objects can be modified + print(MY_NUMBER.value); // prints 42 +")?; +``` diff --git a/doc/src/language/convert.md b/doc/src/language/convert.md index 0c2226eb..a14feed3 100644 --- a/doc/src/language/convert.md +++ b/doc/src/language/convert.md @@ -3,6 +3,10 @@ Value Conversions {{#include ../links.md}} + +Convert Between Integer and Floating-Point +----------------------------------------- + The `to_float` function converts a supported number to `FLOAT` (defaults to `f64`). The `to_int` function converts a supported number to `INT` (`i32` or `i64` depending on [`only_i32`]). @@ -22,3 +26,31 @@ let c = 'X'; // character print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88" ``` + + +Parse String into Number +------------------------ + +The `parse_float` function converts a [string] into a `FLOAT` (defaults to `f64`). + +The `parse_int` function converts a [string] into an `INT` (`i32` or `i64` depending on [`only_i32`]). +An optional radix (2-36) can be provided to parse the [string] into a number of the specified radix. + +```rust +let x = parse_float("123.4"); // parse as floating-point +x == 123.4; +type_of(x) == "f64"; + +let dec = parse_int("42"); // parse as decimal +let dec = parse_int("42", 10); // radix = 10 is the default +dec == 42; +type_of(dec) == "i64"; + +let bin = parse_int("110", 2); // parse as binary (radix = 2) +bin == 0b110; +type_of(bin) == "i64"; + +let hex = parse_int("ab", 16); // parse as hex (radix = 16) +hex == 0xab; +type_of(hex) == "i64"; +``` diff --git a/doc/src/language/dynamic.md b/doc/src/language/dynamic.md index 7909d162..9de60a1c 100644 --- a/doc/src/language/dynamic.md +++ b/doc/src/language/dynamic.md @@ -58,15 +58,15 @@ The `cast` method then converts the value into a specific, known type. Alternatively, use the `try_cast` method which does not panic but returns `None` 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 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 +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.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 'None' +let value = item.try_cast::()?; // 'try_cast' does not panic when the cast fails, but returns 'None' ``` Type Name @@ -76,17 +76,21 @@ The `type_name` method gets the name of the actual type as a static string slice which can be `match`-ed against. ```rust -let list: Array = engine.eval("...")?; // return type is 'Array' -let item = list[0]; // an element in an 'Array' is 'Dynamic' +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 +match item.type_name() { // 'type_name' returns the name of the actual Rust type "i64" => ... "alloc::string::String" => ... "bool" => ... - "path::to::module::TestStruct" => ... + "crate::path::to::module::TestStruct" => ... } ``` +**Note:** `type_name` always returns the _full_ Rust path name of the type, even when the type +has been registered with a friendly name via `Engine::register_type_with_name`. This behavior +is different from that of the [`type_of`][`type_of()`] function in Rhai. + Conversion Traits ---------------- @@ -100,4 +104,5 @@ The following conversion traits are implemented for `Dynamic`: * `From` * `From` * `From>` (into an [array]) -* `From>` (into an [object map]). +* `From>` (into an [object map]) +* `From` (into a [timestamp] if not [`no_std`]) diff --git a/doc/src/language/fn-anon.md b/doc/src/language/fn-anon.md index 8b514509..49ee13c2 100644 --- a/doc/src/language/fn-anon.md +++ b/doc/src/language/fn-anon.md @@ -10,8 +10,8 @@ This scenario is especially common when simulating object-oriented programming ( // Define object let obj = #{ data: 42, - increment: Fn("inc_obj"), // use function pointers to - decrement: Fn("dec_obj"), // refer to method functions + increment: Fn("inc_obj"), // use function pointers to + decrement: Fn("dec_obj"), // refer to method functions print: Fn("print_obj") }; diff --git a/doc/src/language/fn-closure.md b/doc/src/language/fn-closure.md index 7f04dbac..b620e503 100644 --- a/doc/src/language/fn-closure.md +++ b/doc/src/language/fn-closure.md @@ -23,43 +23,25 @@ Therefore, similar to closures in many languages, these captured shared values p reference counting, and may be read or modified even after the variables that hold them go out of scope and no longer exist. -Use the `is_shared` function to check whether a particular value is a shared value. +Use the `Dynamic::is_shared` function to check whether a particular value is a shared value. Automatic currying can be turned off via the [`no_closure`] feature. -Actual Implementation ---------------------- - -The actual implementation de-sugars to: - -1. Keeping track of what variables are accessed inside the anonymous function, - -2. If a variable is not defined within the anonymous function's scope, it is looked up _outside_ the function and - in the current execution scope - where the anonymous function is created. - -3. The variable is added to the parameters list of the anonymous function, at the front. - -4. The variable is then converted into a **reference-counted shared value**. - - An [anonymous function] which captures an external variable is the only way to create a reference-counted shared value in Rhai. - -5. The shared value is then [curried][currying] into the [function pointer] itself, essentially carrying a reference to that shared value - and inserting it into future calls of the function. - - This process is called _Automatic Currying_, and is the mechanism through which Rhai simulates normal closures. - - Examples -------- ```rust let x = 1; // a normal variable +x.is_shared() == false; + let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f' x.is_shared() == true; // 'x' is now a shared value! +f.call(2) == 3; // 1 + 2 == 3 + x = 40; // changing 'x'... f.call(2) == 42; // the value of 'x' is 40 because 'x' is shared @@ -117,6 +99,8 @@ will occur and the script will terminate with an error. ```rust let x = 20; +x.is_shared() == false; // 'x' is not shared, so no data race is possible + let f = |a| this += x + a; // 'x' is captured in this closure x.is_shared() == true; // now 'x' is shared @@ -152,6 +136,26 @@ x.call(f, 2); TL;DR ----- +### Q: How is it actually implemented? + +The actual implementation of closures de-sugars to: + +1. Keeping track of what variables are accessed inside the anonymous function, + +2. If a variable is not defined within the anonymous function's scope, it is looked up _outside_ the function and + in the current execution scope - where the anonymous function is created. + +3. The variable is added to the parameters list of the anonymous function, at the front. + +4. The variable is then converted into a **reference-counted shared value**. + + An [anonymous function] which captures an external variable is the only way to create a reference-counted shared value in Rhai. + +5. The shared value is then [curried][currying] into the [function pointer] itself, essentially carrying a reference to that shared value + and inserting it into future calls of the function. + + This process is called _Automatic Currying_, and is the mechanism through which Rhai simulates normal closures. + ### Q: Why are closures implemented as automatic currying? In concept, a closure _closes_ over captured variables from the outer scope - that's why diff --git a/doc/src/language/fn-namespaces.md b/doc/src/language/fn-namespaces.md index 0621472f..e6844f9a 100644 --- a/doc/src/language/fn-namespaces.md +++ b/doc/src/language/fn-namespaces.md @@ -43,10 +43,12 @@ This aspect is very similar to JavaScript before ES6 modules. // Compile a script into AST let ast1 = engine.compile( r#" - fn get_message() { "Hello!" } // greeting message + fn get_message() { + "Hello!" // greeting message + } fn say_hello() { - print(get_message()); // prints message + print(get_message()); // prints message } say_hello(); diff --git a/doc/src/language/fn-ptr.md b/doc/src/language/fn-ptr.md index 833cfaa2..8c294a83 100644 --- a/doc/src/language/fn-ptr.md +++ b/doc/src/language/fn-ptr.md @@ -61,8 +61,8 @@ hello.call(0); // error: function not found - 'hello_world (i64)' Global Namespace Only -------------------- -Because of their dynamic nature, function pointers cannot refer to functions in a _module_ [namespace][function namespace] -(i.e. functions in [`import`]-ed modules). They can only refer to functions within the global [namespace][function namespace]. +Because of their dynamic nature, function pointers cannot refer to functions in [`import`]-ed [modules]. +They can only refer to functions within the global [namespace][function namespace]. See [function namespaces] for more details. ```rust diff --git a/doc/src/language/json.md b/doc/src/language/json.md index 951f0cbe..0d82b64a 100644 --- a/doc/src/language/json.md +++ b/doc/src/language/json.md @@ -44,10 +44,13 @@ Representation of Numbers ------------------------ JSON numbers are all floating-point while Rhai supports integers (`INT`) and floating-point (`FLOAT`) if -the [`no_float`] feature is not used. Most common generators of JSON data distinguish between -integer and floating-point values by always serializing a floating-point number with a decimal point -(i.e. `123.0` instead of `123` which is assumed to be an integer). This style can be used successfully -with Rhai [object maps]. +the [`no_float`] feature is not used. + +Most common generators of JSON data distinguish between integer and floating-point values by always +serializing a floating-point number with a decimal point (i.e. `123.0` instead of `123` which is +assumed to be an integer). + +This style can be used successfully with Rhai [object maps]. Parse JSON with Sub-Objects @@ -68,9 +71,10 @@ A JSON object hash starting with `#{` is handled transparently by `Engine::parse // JSON with sub-object 'b'. let json = r#"{"a":1, "b":{"x":true, "y":false}}"#; -let new_json = json.replace("{" "#{"); +// Our JSON text does not contain the '{' character, so off we go! +let new_json = json.replace("{", "#{"); -// The leading '{' will also be replaced to '#{', but parse_json can handle this. +// The leading '{' will also be replaced to '#{', but 'parse_json' handles this just fine. let map = engine.parse_json(&new_json, false)?; map.len() == 2; // 'map' contains two properties: 'a' and 'b' diff --git a/doc/src/language/method.md b/doc/src/language/method.md index 4b88fe68..01633350 100644 --- a/doc/src/language/method.md +++ b/doc/src/language/method.md @@ -47,10 +47,10 @@ an equivalent method coded in script, where the object is accessed via the `this The following table illustrates the differences: -| Function type | Parameters | Object reference | Function signature | -| :-----------: | :--------: | :--------------------: | :-----------------------------------------------------: | -| Native Rust | _n_ + 1 | first `&mut` parameter | `fn method`
`(obj: &mut T, x: U, y: V) {}` | -| Rhai script | _n_ | `this` | `fn method(x, y) {}` | +| Function type | Parameters | Object reference | Function signature | +| :-----------: | :--------: | :-----------------------: | :---------------------------: | +| Native Rust | _N_ + 1 | first `&mut T` parameter | `Fn(obj: &mut T, x: U, y: V)` | +| Rhai script | _N_ | `this` (of type `&mut T`) | `Fn(x: U, y: V)` | `&mut` is Efficient, Except for `ImmutableString` diff --git a/doc/src/language/modules/ast.md b/doc/src/language/modules/ast.md deleted file mode 100644 index 56cde70d..00000000 --- a/doc/src/language/modules/ast.md +++ /dev/null @@ -1,59 +0,0 @@ -Create a Module from an AST -========================== - -{{#include ../../links.md}} - -It is easy to convert a pre-compiled [`AST`] into a module: just use `Module::eval_ast_as_new`. - -Don't forget the [`export`] statement, otherwise there will be no variables exposed by the module -other than non-[`private`] functions (unless that's intentional). - -```rust -use rhai::{Engine, Module}; - -let engine = Engine::new(); - -// Compile a script into an 'AST' -let ast = engine.compile(r#" - // Functions become module functions - fn calc(x) { - x + 1 - } - fn add_len(x, y) { - x + y.len - } - - // Imported modules can become sub-modules - import "another module" as extra; - - // Variables defined at global level can become module variables - const x = 123; - let foo = 41; - let hello; - - // Variable values become constant module variable values - foo = calc(foo); - hello = "hello, " + foo + " worlds!"; - - // Finally, export the variables and modules - export - x as abc, // aliased variable name - foo, - hello, - extra as foobar; // export sub-module -"#)?; - -// Convert the 'AST' into a module, using the 'Engine' to evaluate it first -// -// The second parameter ('private_namespace'), when set to true, will encapsulate -// a copy of the entire 'AST' into each function, allowing functions in the module script -// to cross-call each other. Otherwise module script functions access the global namespace. -// -// This incurs additional overhead, avoidable by setting 'private_namespace' to false. -let module = Module::eval_ast_as_new(Scope::new(), &ast, true, &engine)?; - -// 'module' now can be loaded into a custom 'Scope' for future use. It contains: -// - sub-module: 'foobar' (renamed from 'extra') -// - functions: 'calc', 'add_len' -// - variables: 'abc' (renamed from 'x'), 'foo', 'hello' -``` diff --git a/doc/src/language/modules/export.md b/doc/src/language/modules/export.md index 46416bcc..aaa8eb2d 100644 --- a/doc/src/language/modules/export.md +++ b/doc/src/language/modules/export.md @@ -3,35 +3,16 @@ Export Variables, Functions and Sub-Modules in Module {{#include ../../links.md}} -A _module_ can be created from a single script (or pre-compiled [`AST`]) containing global variables, -functions and sub-modules via the `Module::eval_ast_as_new` method. -When given an [`AST`], it is first evaluated, then the following items are exposed as members of the new module: - -* Global variables - essentially all variables that remain in the [`Scope`] at the end of a script run - that are exported. Variables not exported (via the `export` statement) remain hidden. - -* Functions not specifically marked `private`. - -* Global modules that remain in the [`Scope`] at the end of a script run. - -The parameter `private_namespace` in `Module::eval_ast_as_new` determines the exact behavior of -functions exposed by the module and the namespace that they can access: - -| `private_namespace` value | Behavior of module functions | Namespace | Call global functions | Call functions in same module | -| :-----------------------: | ---------------------------------------------------- | :-------: | :-------------------: | :---------------------------: | -| `true` | encapsulate the entire `AST` into each function call | module | no | yes | -| `false` | register each function independently | global | yes | no | - - -Global Variables ----------------- +Export Global Variables +---------------------- The `export` statement, which can only be at global level, exposes selected variables as members of a module. Variables not exported are _private_ and hidden. They are merely used to initialize the module, but cannot be accessed from outside. -Everything exported from a module is **constant** (**read-only**). +Everything exported from a module is **constant** (i.e. read-only). ```rust // This is a module script. @@ -53,8 +34,8 @@ export x as answer; // the variable 'x' is exported under the alias 'answer' ``` -Functions ---------- +Export Functions +---------------- All functions are automatically exported, _unless_ it is explicitly opt-out with the [`private`] prefix. diff --git a/doc/src/language/object-maps-oop.md b/doc/src/language/object-maps-oop.md index 0c2b840d..f96fda04 100644 --- a/doc/src/language/object-maps-oop.md +++ b/doc/src/language/object-maps-oop.md @@ -10,25 +10,23 @@ If an [object map]'s property holds a [function pointer], the property can simpl a normal method in method-call syntax. This is a _short-hand_ to avoid the more verbose syntax of using the `call` function keyword. -When a property holding a [function pointer] (which incudes [closures]) is called like a method, +When a property holding a [function pointer] or a [closure] is called like a method, what happens next depends on whether the target function is a native Rust function or a script-defined function. -If it is a registered native Rust method function, it is called directly. +* If it is a registered native Rust function, it is called directly in _method-call_ style with the [object map] inserted as the first argument. -If it is a script-defined function, the `this` variable within the function body is bound -to the [object map] before the function is called. There is no way to simulate this behavior -via a normal function-call syntax because all scripted function arguments are passed by value. +* If it is a script-defined function, the `this` variable within the function body is bound to the [object map] before the function is called. ```rust let obj = #{ data: 40, - action: || this.data += x // 'action' holds a function pointer which is a closure + action: || this.data += x // 'action' holds a closure }; -obj.action(2); // Calls the function pointer with `this` bound to 'obj' +obj.action(2); // calls the function pointer with `this` bound to 'obj' -obj.call(obj.action, 2); // The above de-sugars to this +obj.call(obj.action, 2); // <- the above de-sugars to this obj.data == 42; diff --git a/doc/src/language/object-maps.md b/doc/src/language/object-maps.md index c2c0d20e..937b1818 100644 --- a/doc/src/language/object-maps.md +++ b/doc/src/language/object-maps.md @@ -57,17 +57,17 @@ Built-in Functions The following methods (defined in the [`BasicMapPackage`][packages] but excluded if using a [raw `Engine`]) operate on object maps: -| Function | Parameter(s) | Description | -| ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `has` | property name | does the object map contain a property of a particular name? | -| `len` | _none_ | returns the number of properties | -| `clear` | _none_ | empties the object map | -| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) | -| `+=` operator, `mixin` | second object map | 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 | first object map, second object map | merges the first object map with the second | -| `fill_with` | second object map | adds in all properties of the second object map that do not exist in the object map | -| `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] | -| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] | +| Function | Parameter(s) | Description | +| ---------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `has` | property name | does the object map contain a property of a particular name? | +| `len` | _none_ | returns the number of properties | +| `clear` | _none_ | empties the object map | +| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) | +| `+=` operator, `mixin` | second object map | 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 | 1) first object map
2) second object map | merges the first object map with the second | +| `fill_with` | second object map | adds in all properties of the second object map that do not exist in the object map | +| `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] | +| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] | Examples diff --git a/doc/src/language/string-fn.md b/doc/src/language/string-fn.md index a9caaa94..d6e373a5 100644 --- a/doc/src/language/string-fn.md +++ b/doc/src/language/string-fn.md @@ -6,20 +6,20 @@ Built-in String Functions The following standard methods (mostly defined in the [`MoreStringPackage`][packages] but excluded if using a [raw `Engine`]) operate on [strings]: -| Function | Parameter(s) | Description | -| ------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | -| `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string | -| `pad` | character to pad, target length | pads the string with an character to at least a specified length | -| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string | -| `clear` | _none_ | empties the string | -| `truncate` | target length | cuts off the string at exactly a specified number of characters | -| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string | -| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found | -| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) | -| `split` | delimiter character/string | splits the string by the specified delimiter, returning an [array] of string segments; not available under [`no_index`] | -| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) | -| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another | -| `trim` | _none_ | trims the string of whitespace at the beginning and end | +| Function | Parameter(s) | Description | +| ------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string | +| `pad` | 1) character to pad
2) target length | pads the string with an character to at least a specified length | +| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string | +| `clear` | _none_ | empties the string | +| `truncate` | target length | cuts off the string at exactly a specified number of characters | +| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string | +| `index_of` | 1) character/sub-string to search for
2) start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found | +| `sub_string` | 1) start index
2) length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) | +| `split` | delimiter character/string | splits the string by the specified delimiter, returning an [array] of string segments; not available under [`no_index`] | +| `crop` | 1) start index
2) length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) | +| `replace` | 1) target character/sub-string
2) replacement character/string | replaces a sub-string with another | +| `trim` | _none_ | trims the string of whitespace at the beginning and end | Examples -------- diff --git a/doc/src/language/strings-chars.md b/doc/src/language/strings-chars.md index 81ca6258..a402d485 100644 --- a/doc/src/language/strings-chars.md +++ b/doc/src/language/strings-chars.md @@ -43,17 +43,17 @@ Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicod Standard escape sequences: -| Escape sequence | Meaning | -| --------------- | ------------------------------ | -| `\\` | back-slash `\` | -| `\t` | tab | -| `\r` | carriage-return `CR` | -| `\n` | line-feed `LF` | -| `\"` | double-quote `"` in strings | -| `\'` | single-quote `'` in characters | -| `\x`_xx_ | Unicode in 2-digit hex | -| `\u`_xxxx_ | Unicode in 4-digit hex | -| `\U`_xxxxxxxx_ | Unicode in 8-digit hex | +| Escape sequence | Meaning | +| --------------- | -------------------------------- | +| `\\` | back-slash `\` | +| `\t` | tab | +| `\r` | carriage-return `CR` | +| `\n` | line-feed `LF` | +| `\"` | double-quote `"` | +| `\'` | single-quote `'` | +| `\x`_xx_ | ASCII character in 2-digit hex | +| `\u`_xxxx_ | Unicode character in 4-digit hex | +| `\U`_xxxxxxxx_ | Unicode character in 8-digit hex | Differences from Rust Strings diff --git a/doc/src/language/type-of.md b/doc/src/language/type-of.md index 02ddbc28..87171d06 100644 --- a/doc/src/language/type-of.md +++ b/doc/src/language/type-of.md @@ -24,3 +24,24 @@ if type_of(x) == "string" { do_something_with_string(x); } ``` + + +Custom Types +------------ + +`type_of()` a [custom type] returns: + +* if registered via `Engine::register_type_with_name` - the registered name + +* if registered via `Engine::register_type` - the full Rust path name + +```rust +struct TestStruct1; +struct TestStruct2; + +engine + // type_of(struct1) == "crate::path::to::module::TestStruct1" + .register_type::() + // type_of(struct2) == "MyStruct" + .register_type_with_name::("MyStruct"); +``` diff --git a/doc/src/links.md b/doc/src/links.md index 0231689b..2fd9d152 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -49,6 +49,8 @@ [`Dynamic`]: {{rootUrl}}/language/dynamic.md [`to_int`]: {{rootUrl}}/language/convert.md [`to_float`]: {{rootUrl}}/language/convert.md +[`parse_int`]: {{rootUrl}}/language/convert.md +[`parse_float`]: {{rootUrl}}/language/convert.md [custom type]: {{rootUrl}}/rust/custom.md [custom types]: {{rootUrl}}/rust/custom.md diff --git a/doc/src/patterns/config.md b/doc/src/patterns/config.md index c87a0fb3..3017bb1d 100644 --- a/doc/src/patterns/config.md +++ b/doc/src/patterns/config.md @@ -51,6 +51,12 @@ let config: Rc> = Rc::new(RefCell::new(Default::default())); ### Register Config API +The trick to building a Config API is to clone the shared configuration object and +move it into each function registration as a closure. + +It is not possible to use a [plugin module] to achieve this, so each function must +be registered one after another. + ```rust // Notice 'move' is used to move the shared configuration object into the closure. let cfg = config.clone(); @@ -66,27 +72,27 @@ engine.register_fn("config_set", move |value: i64| *cfg.borrow_mut().some_field let cfg = config.clone(); engine.register_fn("config_add", move |value: String| - cfg.borrow_mut().some_list.push(value) + cfg.borrow_mut().some_list.push(value) ); let cfg = config.clone(); engine.register_fn("config_add", move |values: &mut Array| - cfg.borrow_mut().some_list.extend(values.into_iter().map(|v| v.to_string())) + cfg.borrow_mut().some_list.extend(values.into_iter().map(|v| v.to_string())) ); let cfg = config.clone(); engine.register_fn("config_add", move |key: String, value: bool| - cfg.borrow_mut().some_map.insert(key, value) + cfg.borrow_mut().some_map.insert(key, value) ); let cfg = config.clone(); engine.register_fn("config_contains", move |value: String| - cfg.borrow().some_list.contains(&value) + cfg.borrow().some_list.contains(&value) ); let cfg = config.clone(); engine.register_fn("config_is_set", move |value: String| - cfg.borrow().some_map.get(&value).cloned().unwrap_or(false) + cfg.borrow().some_map.get(&value).cloned().unwrap_or(false) ); ``` diff --git a/doc/src/patterns/control.md b/doc/src/patterns/control.md index 52b6b23c..c3624442 100644 --- a/doc/src/patterns/control.md +++ b/doc/src/patterns/control.md @@ -64,6 +64,12 @@ let bunny: Rc> = Rc::new(RefCell::(EnergizerBunny::new() ### Register Control API +The trick to building a Control API is to clone the shared API object and +move it into each function registration as a closure. + +It is not possible to use a [plugin module] to achieve this, so each function must +be registered one after another. + ```rust // Notice 'move' is used to move the shared API object into the closure. let b = bunny.clone(); diff --git a/doc/src/patterns/singleton.md b/doc/src/patterns/singleton.md index c38df1d5..fa7c9d75 100644 --- a/doc/src/patterns/singleton.md +++ b/doc/src/patterns/singleton.md @@ -58,7 +58,7 @@ impl EnergizerBunny { pub fn new () -> Self { ... } pub fn go (&mut self) { ... } pub fn stop (&mut self) { ... } - pub fn is_going (&self) { ... } + pub fn is_going (&self) -> bol { ... } pub fn get_speed (&self) -> i64 { ... } pub fn set_speed (&mut self, speed: i64) { ... } pub fn turn (&mut self, left_turn: bool) { ... } @@ -77,37 +77,52 @@ let SharedBunnyType = Rc>; engine.register_type_with_name::("EnergizerBunny"); ``` -### Register Methods and Getters/Setters +### Develop a Plugin with Methods and Getters/Setters + +The easiest way to develop a complete set of API for a [custom type] is via a [plugin module]. ```rust -engine - .register_get_set("power", - |bunny: &mut SharedBunnyType| bunny.borrow().is_going(), - |bunny: &mut SharedBunnyType, on: bool| { - if on { - if bunny.borrow().is_going() { - println!("Still going..."); - } else { - bunny.borrow_mut().go(); - } +use rhai::plugins::*; + +#[export_module] +pub mod bunny_api { + pub const MAX_SPEED: i64 = 100; + + #[rhai_fn(get = "power")] + pub fn get_power(bunny: &mut SharedBunnyType) -> bool { + bunny.borrow().is_going() + } + #[rhai_fn(set = "power")] + pub fn set_power(bunny: &mut SharedBunnyType, on: bool) { + if on { + if bunny.borrow().is_going() { + println!("Still going..."); } else { - if bunny.borrow().is_going() { - bunny.borrow_mut().stop(); - } else { - println!("Already out of battery!"); - } + bunny.borrow_mut().go(); + } + } else { + if bunny.borrow().is_going() { + bunny.borrow_mut().stop(); + } else { + println!("Already out of battery!"); } } - ).register_get("speed", |bunny: &mut SharedBunnyType| { + } + #[rhai_fn(get = "speed")] + pub fn get_speed(bunny: &mut SharedBunnyType) -> i64 { if bunny.borrow().is_going() { bunny.borrow().get_speed() } else { 0 } - }).register_set_result("speed", |bunny: &mut SharedBunnyType, speed: i64| { + } + #[rhai_fn(set = "speed", return_raw)] + pub fn set_speed(bunny: &mut SharedBunnyType, speed: i64) + -> Result> + { if speed <= 0 { Err("Speed must be positive!".into()) - } else if speed > 100 { + } else if speed > MAX_SPEED { Err("Bunny will be going too fast!".into()) } else if !bunny.borrow().is_going() { Err("Bunny is not yet going!".into()) @@ -115,15 +130,20 @@ engine b.borrow_mut().set_speed(speed); Ok(().into()) } - }).register_fn("turn_left", |bunny: &mut SharedBunnyType| { + } + pub fn turn_left(bunny: &mut SharedBunnyType) { if bunny.borrow().is_going() { bunny.borrow_mut().turn(true); } - }).register_fn("turn_right", |bunny: &mut SharedBunnyType| { + } + pub fn turn_right(bunny: &mut SharedBunnyType) { if bunny.borrow().is_going() { bunny.borrow_mut().turn(false); } - }); + } +} + +engine.load_package(exported_module!(bunny_api)); ``` ### Push Constant Command Object into Custom Scope @@ -132,7 +152,9 @@ engine let bunny: SharedBunnyType = Rc::new(RefCell::(EnergizerBunny::new())); let mut scope = Scope::new(); -scope.push_constant("BUNNY", bunny.clone()); + +// Add the command object into a custom Scope. +scope.push_constant("Bunny", bunny.clone()); engine.consume_with_scope(&mut scope, script)?; ``` @@ -140,11 +162,11 @@ engine.consume_with_scope(&mut scope, script)?; ### Use the Command API in Script ```rust -// Access the command object via constant variable 'BUNNY'. +// Access the command object via constant variable 'Bunny'. -if !BUNNY.power { BUNNY.power = true; } +if !Bunny.power { Bunny.power = true; } -if BUNNY.speed > 50 { BUNNY.speed = 50; } +if Bunny.speed > 50 { Bunny.speed = 50; } -BUNNY.turn_left(); +Bunny.turn_left(); ``` diff --git a/doc/src/plugins/function.md b/doc/src/plugins/function.md index d0d9b5fd..388c9b84 100644 --- a/doc/src/plugins/function.md +++ b/doc/src/plugins/function.md @@ -11,11 +11,11 @@ individual functions instead of a full-blown [plugin module]. Macros ------ -| Macro | Apply to | Description | -| ----------------------- | --------------------------------------------------------------- | ------------------------------------------------------------- | -| `#[export_fn]` | rust function defined in a Rust module | exports the function | -| `register_exported_fn!` | [`Engine`] instance, register name string, use path to function | registers the function into an [`Engine`] under specific name | -| `set_exported_fn!` | [`Module`] instance, register name string, use path to function | registers the function into an [`Module`] under specific name | +| Macro | Signature | Description | +| ----------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------- | +| `#[export_fn]` | apply to rust function defined in a Rust module | exports the function | +| `register_exported_fn!` | `register_exported_fn!(&mut `_engine_`, "`_name_`", `_function_`)` | registers the function into an [`Engine`] under a specific name | +| `set_exported_fn!` | `set_exported_fn!(&mut `_module_`, "`_name_`", `_function_`)` | registers the function into a [`Module`] under a specific name | `#[export_fn]` and `register_exported_fn!` diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md index 2d001f68..97e2ffa6 100644 --- a/doc/src/plugins/module.md +++ b/doc/src/plugins/module.md @@ -29,9 +29,9 @@ use rhai::plugins::*; // a "prelude" import for macros #[export_module] mod my_module { - // This constant will be registered as the constant variable 'SOME_NUMBER'. + // This constant will be registered as the constant variable 'MY_NUMBER'. // Ignored when loaded as a package. - pub const SOME_NUMBER: i64 = 42; + pub const MY_NUMBER: i64 = 42; // This function will be registered as 'greet'. pub fn greet(name: &str) -> String { @@ -260,12 +260,12 @@ Inner attributes can be applied to the inner items of a module to tweak the expo Parameters should be set on inner attributes to specify the desired behavior. -| Attribute Parameter | Use with | Apply to | Description | -| ------------------- | --------------------------- | -------------------------------------------------------- | ------------------------------------------------------ | -| `skip` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | do not export this function/sub-module | -| `name = "..."` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | registers function/sub-module under the specified name | -| `get = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a getter for the named property | -| `set = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a setter for the named property | -| `index_get` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index getter | -| `index_set` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index setter | -| `return_raw` | `#[rhai_fn]` | function returning `Result>` | marks this as a [fallible function] | +| Attribute Parameter | Use with | Apply to | Description | +| ------------------- | --------------------------- | ----------------------------------------------------- | ------------------------------------------------------ | +| `skip` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | do not export this function/sub-module | +| `name = "..."` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | registers function/sub-module under the specified name | +| `get = "..."` | `#[rhai_fn]` | `pub fn (&mut Type) -> Value` | registers a getter for the named property | +| `set = "..."` | `#[rhai_fn]` | `pub fn (&mut Type, Value)` | registers a setter for the named property | +| `index_get` | `#[rhai_fn]` | `pub fn (&mut Type, INT) -> Value` | registers an index getter | +| `index_set` | `#[rhai_fn]` | `pub fn (&mut Type, INT, Value)` | registers an index setter | +| `return_raw` | `#[rhai_fn]` | `pub fn (...) -> Result>` | marks this as a [fallible function] | diff --git a/doc/src/rust/getters-setters.md b/doc/src/rust/getters-setters.md index efc19514..bb07989d 100644 --- a/doc/src/rust/getters-setters.md +++ b/doc/src/rust/getters-setters.md @@ -53,10 +53,10 @@ impl TestStruct { let mut engine = Engine::new(); - engine - .register_type::() - .register_get_set("xyz", TestStruct::get_field, TestStruct::set_field) - .register_fn("new_ts", TestStruct::new); +engine + .register_type::() + .register_get_set("xyz", TestStruct::get_field, TestStruct::set_field) + .register_fn("new_ts", TestStruct::new); let result = engine.eval::(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?; diff --git a/doc/src/rust/modules/ast.md b/doc/src/rust/modules/ast.md new file mode 100644 index 00000000..0348579a --- /dev/null +++ b/doc/src/rust/modules/ast.md @@ -0,0 +1,88 @@ +Create a Module from an AST +========================== + +{{#include ../../links.md}} + + +`Module::eval_ast_as_new` +------------------------ + +A _module_ can be created from a single script (or pre-compiled [`AST`]) containing global variables, +functions and sub-modules via the `Module::eval_ast_as_new` method. + +When given an [`AST`], it is first evaluated, then the following items are exposed as members of the new module: + +* Global variables - essentially all variables that remain in the [`Scope`] at the end of a script run - that are exported. Variables not exported (via the `export` statement) remain hidden. + +* Functions not specifically marked `private`. + +* Global modules that remain in the [`Scope`] at the end of a script run. + + +`merge_namespaces` Parameter +--------------------------- + +The parameter `merge_namespaces` in `Module::eval_ast_as_new` determines the exact behavior of +functions exposed by the module and the namespace that they can access: + +| `merge_namespaces` value | Description | Namespace | Performance | Call global functions | Call functions in same module | +| :----------------------: | ------------------------------------------------ | :-----------------: | :---------: | :-------------------: | :---------------------------: | +| `true` | encapsulate entire `AST` into each function call | module, then global | slower | yes | yes | +| `false` | register each function independently | global only | fast | yes | no | + + +Examples +-------- + +Don't forget the [`export`] statement, otherwise there will be no variables exposed by the module +other than non-[`private`] functions (unless that's intentional). + +```rust +use rhai::{Engine, Module}; + +let engine = Engine::new(); + +// Compile a script into an 'AST' +let ast = engine.compile(r#" + // Functions become module functions + fn calc(x) { + x + 1 + } + fn add_len(x, y) { + x + y.len + } + + // Imported modules can become sub-modules + import "another module" as extra; + + // Variables defined at global level can become module variables + const x = 123; + let foo = 41; + let hello; + + // Variable values become constant module variable values + foo = calc(foo); + hello = "hello, " + foo + " worlds!"; + + // Finally, export the variables and modules + export + x as abc, // aliased variable name + foo, + hello, + extra as foobar; // export sub-module +"#)?; + +// Convert the 'AST' into a module, using the 'Engine' to evaluate it first +// +// The second parameter ('merge_namespaces'), when set to true, will encapsulate +// a copy of the entire 'AST' into each function, allowing functions in the module script +// to cross-call each other. +// +// This incurs additional overhead, avoidable by setting 'merge_namespaces' to false. +let module = Module::eval_ast_as_new(Scope::new(), &ast, true, &engine)?; + +// 'module' now contains: +// - sub-module: 'foobar' (renamed from 'extra') +// - functions: 'calc', 'add_len' +// - constants: 'abc' (renamed from 'x'), 'foo', 'hello' +``` diff --git a/doc/src/rust/modules/create.md b/doc/src/rust/modules/create.md index d2f68e54..51462b22 100644 --- a/doc/src/rust/modules/create.md +++ b/doc/src/rust/modules/create.md @@ -22,19 +22,38 @@ For the complete `Module` API, refer to the [documentation](https://docs.rs/rhai Make the `Module` Available to the `Engine` ------------------------------------------ -In order to _use_ a custom module, there must be a [module resolver], which serves the module when -loaded via `import` statements. +`Engine::load_package` supports loading a [module] as a [package]. + +Since it acts as a [package], all functions will be registered into the _global_ namespace +and can be accessed without _module qualifiers_. + +```rust +use rhai::{Engine, Module}; + +let mut module = Module::new(); // new module +module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions + +// Load the module into the Engine as a new package. +let mut engine = Engine::new(); +engine.load_package(module); + +engine.eval::("inc(41)")? == 42; // no need to import module +``` + + +Make the `Module` Dynamically Loadable +------------------------------------- + +In order to dynamically load a custom module, there must be a [module resolver] which serves +the module when loaded via `import` statements. The easiest way is to use, for example, the [`StaticModuleResolver`][module resolver] to hold such a custom module. ```rust -use rhai::{Engine, Scope, Module, i64}; +use rhai::{Engine, Scope, Module}; use rhai::module_resolvers::StaticModuleResolver; -let mut engine = Engine::new(); -let mut scope = Scope::new(); - let mut module = Module::new(); // new module module.set_var("answer", 41_i64); // variable 'answer' under module module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions @@ -47,11 +66,12 @@ let mut resolver = StaticModuleResolver::new(); resolver.insert("question", module); // Set the module resolver into the 'Engine' +let mut engine = Engine::new(); engine.set_module_resolver(Some(resolver)); // Use module-qualified variables -engine.eval::(&scope, r#"import "question" as q; q::answer + 1"#)? == 42; +engine.eval::(r#"import "question" as q; q::answer + 1"#)? == 42; // Call module-qualified functions -engine.eval::(&scope, r#"import "question" as q; q::inc(q::answer)"#)? == 42; +engine.eval::(r#"import "question" as q; q::inc(q::answer)"#)? == 42; ``` diff --git a/doc/src/rust/modules/imp-resolver.md b/doc/src/rust/modules/imp-resolver.md index d1d9ed38..58aada13 100644 --- a/doc/src/rust/modules/imp-resolver.md +++ b/doc/src/rust/modules/imp-resolver.md @@ -11,8 +11,14 @@ A module resolver must implement the trait [`rhai::ModuleResolver`][traits], which contains only one function: `resolve`. When Rhai prepares to load a module, `ModuleResolver::resolve` is called with the name -of the _module path_ (i.e. the path specified in the [`import`] statement). Upon success, it should -return a [`Module`]; if the module cannot be load, return `EvalAltResult::ErrorModuleNotFound`. +of the _module path_ (i.e. the path specified in the [`import`] statement). + +* Upon success, it should return a [`Module`]. + +* If the path does not resolve to a valid module, return `EvalAltResult::ErrorModuleNotFound`. + +* If the module failed to load, return `EvalAltResult::ErrorInModule`. + Example ------- @@ -35,9 +41,12 @@ impl ModuleResolver for MyModuleResolver { // Check module path. if is_valid_module_path(path) { // Load the custom module. - let module: Module = load_secret_module(path); - Ok(module) + load_secret_module(path).map_err(|err| + // Return EvalAltResult::ErrorInModule upon loading error + EvalAltResult::ErrorInModule(err.to_string(), pos).into() + ) } else { + // Return EvalAltResult::ErrorModuleNotFound if the path is invalid Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()) } } @@ -50,7 +59,7 @@ engine.set_module_resolver(Some(MyModuleResolver {})); engine.consume(r#" import "hello" as foo; // this 'import' statement will call - // 'MyModuleResolver::resolve' with "hello" as path + // 'MyModuleResolver::resolve' with "hello" as `path` foo:bar(); "#)?; ``` diff --git a/doc/src/rust/modules/resolvers.md b/doc/src/rust/modules/resolvers.md index 451388af..ebb73698 100644 --- a/doc/src/rust/modules/resolvers.md +++ b/doc/src/rust/modules/resolvers.md @@ -12,16 +12,120 @@ Built-In Module Resolvers ------------------------ There are a number of standard resolvers built into Rhai, the default being the `FileModuleResolver` -which simply loads a script file based on the path (with `.rhai` extension attached) and execute it to form a module. +which simply loads a script file based on the path (with `.rhai` extension attached) +and execute it to form a module. Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace. -| Module Resolver | Description | Namespace | -| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------: | -| `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.
`FileModuleResolver::create_module()` loads a script file and returns a module. | Module (cannot access global namespace) | -| `GlobalFileModuleResolver` | A simpler but more efficient version of `FileModuleResolver`, intended for short utility modules. Not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.
**Note:** All functions are assumed independent and cannot cross-call each other.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.
`FileModuleResolver::create_module()` loads a script file and returns a module. | Global | -| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. | Global | -| `ModuleResolversCollection` | A collection of module resolvers. Modules will be resolved from each resolver in sequential order.
This is useful when multiple types of modules are needed simultaneously. | Global | + +`FileModuleResolver` (default) +----------------------------- + +The _default_ module resolution service, not available for [`no_std`] or [WASM] builds. +Loads a script file (based off the current directory) with `.rhai` extension. + +All functions in the _global_ namespace, plus all those defined in the same module, +are _merged_ into a _unified_ namespace. + +```rust +------------------ +| my_module.rhai | +------------------ + +private fn inner_message() { "hello! from module!" } + +fn greet(callback) { print(callback.call()); } + +------------- +| main.rhai | +------------- + +fn main_message() { "hi! from main!" } + +import "my_module" as m; + +m::greet(|| "hello, " + "world!"); // works - anonymous function in global + +m::greet(|| inner_message()); // works - function in module + +m::greet(|| main_message()); // works - function in global +``` + +The base directory can be changed via the `FileModuleResolver::new_with_path` constructor function. + +`FileModuleResolver::create_module` loads a script file and returns a module. + + +`GlobalFileModuleResolver` +------------------------- + +A simpler but more efficient version of `FileModuleResolver`, intended for short utility modules. +Not available for [`no_std`] or [WASM] builds. +Loads a script file (based off the current directory) with `.rhai` extension. + +All functions are assumed **independent** and _cannot_ cross-call each other. +Functions are searched _only_ in the _global_ namespace. + +```rust +------------------ +| my_module.rhai | +------------------ + +private fn inner_message() { "hello! from module!" } + +fn greet_inner() { + print(inner_message()); // cross-calling a module function! + // there will be trouble because each function + // in the module is supposed to be independent + // of each other +} + +fn greet() { + print(main_message()); // function is searched in global namespace +} + +------------- +| main.rhai | +------------- + +fn main_message() { "hi! from main!" } + +import "my_module" as m; + +m::greet_inner(); // <- function not found: 'inner_message' + +m::greet(); // works because 'main_message' exists in + // the global namespace +``` + +The base directory can be changed via the `FileModuleResolver::new_with_path` constructor function. + +`GlobalFileModuleResolver::create_module` loads a script file and returns a module. + + +`StaticModuleResolver` +--------------------- + +Loads modules that are statically added. This can be used under [`no_std`]. + +Functions are searched in the _global_ namespace by default. + +```rust +use rhai::{Module, module_resolvers::StaticModuleResolver}; + +let module: Module = create_a_module(); + +let mut resolver = StaticModuleResolver::new(); +resolver.insert("my_module", module); +``` + + +`ModuleResolversCollection` +-------------------------- + +A collection of module resolvers. Modules will be resolved from each resolver in sequential order. + +This is useful when multiple types of modules are needed simultaneously. Set into `Engine` @@ -30,8 +134,14 @@ Set into `Engine` An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`: ```rust -// Use the 'StaticModuleResolver' -let resolver = rhai::module_resolvers::StaticModuleResolver::new(); +use rhai::module_resolvers::StaticModuleResolver; + +// Create a module resolver +let resolver = StaticModuleResolver::new(); + +// Register functions into 'resolver'... + +// Use the module resolver engine.set_module_resolver(Some(resolver)); // Effectively disable 'import' statements by setting module resolver to 'None' diff --git a/doc/src/rust/packages/create.md b/doc/src/rust/packages/create.md index 01d11b12..698be745 100644 --- a/doc/src/rust/packages/create.md +++ b/doc/src/rust/packages/create.md @@ -34,9 +34,11 @@ Macro Parameters ```rust // Import necessary types and traits. use rhai::{ - def_package, - packages::Package, - packages::{ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage} + def_package, // 'def_package!' macro + packages::Package, // 'Package' trait + packages::{ // pre-defined packages + ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage + } }; // Define the package 'MyPackage'. diff --git a/doc/src/rust/packages/index.md b/doc/src/rust/packages/index.md index f2e6b598..020097cc 100644 --- a/doc/src/rust/packages/index.md +++ b/doc/src/rust/packages/index.md @@ -9,10 +9,12 @@ Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package packages to be used. Packages typically contain Rust functions that are callable within a Rhai script. -All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers). +All functions registered in a package is loaded under the _global namespace_ +(i.e. they're available without module qualifiers). -Once a package is created (e.g. via `Package::new`), it can be _shared_ (via `Package::get`) among multiple instances of [`Engine`], -even across threads (under [`sync`]). Therefore, a package only has to be created _once_. +Once a package is created (e.g. via `Package::new`), it can be _shared_ (via `Package::get`) +among multiple instances of [`Engine`], even across threads (under [`sync`]). +Therefore, a package only has to be created _once_. ```rust use rhai::Engine; diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index 965a103b..b77ebab5 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -84,12 +84,12 @@ Extract Arguments To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following: -| Argument type | Access (`n` = argument position) | Result | -| ------------------------------ | ------------------------------------- | --------------------------------------------------------- | -| [Primary type][standard types] | `args[n].clone().cast::()` | copy of value | -| Custom type | `args[n].read_lock::().unwrap()` | immutable reference to value | -| Custom type (consumed) | `std::mem::take(args[n]).cast::()` | the _consumed_ value.
The original value becomes `()` | -| `this` object | `args[0].write_lock::().unwrap()` | mutable reference to value | +| Argument type | Access (`n` = argument position) | Result | +| ------------------------------ | ------------------------------------- | ----------------------------------------------------- | +| [Primary type][standard types] | `args[n].clone().cast::()` | copy of value | +| [Custom type] | `args[n].read_lock::().unwrap()` | immutable reference to value | +| [Custom type] (consumed) | `std::mem::take(args[n]).cast::()` | the _consumed_ value; the original value becomes `()` | +| `this` object | `args[0].write_lock::().unwrap()` | mutable reference to value | When there is a mutable reference to the `this` object (i.e. the first argument), there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain. diff --git a/doc/src/rust/serde.md b/doc/src/rust/serde.md index 19c3a22e..7b9a12f1 100644 --- a/doc/src/rust/serde.md +++ b/doc/src/rust/serde.md @@ -105,6 +105,17 @@ let x: MyStruct = from_dynamic(&result)?; ``` +Cannot Deserialize Shared Values +------------------------------- + +A [`Dynamic`] containing a _shared_ value cannot be deserialized - i.e. it will give a type error. + +Use `Dynamic::flatten` to obtain a cloned copy before deserialization +(if the value is not shared, it is simply returned and not cloned). + +Shared values are turned off via the [`no_closure`] feature. + + Lighter Alternative ------------------- diff --git a/doc/src/safety/sandbox.md b/doc/src/safety/sandbox.md index a85a5f9e..60e35534 100644 --- a/doc/src/safety/sandbox.md +++ b/doc/src/safety/sandbox.md @@ -11,11 +11,15 @@ Furthermore, an [`Engine`] created non-`mut` cannot mutate any state, including It is highly recommended that [`Engine`]'s be created immutable as much as possible. ```rust -// 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); +let mut engine = Engine::new(); + +// Use the fluent API to configure an 'Engine' +engine.register_get("field", get_field) + .register_set("field", set_field) + .register_fn("do_work", action); + +// Then turn it into an immutable instance +let engine = engine; // 'engine' is immutable... ``` diff --git a/tests/macro_register.rs b/tests/plugins_register.rs similarity index 100% rename from tests/macro_register.rs rename to tests/plugins_register.rs diff --git a/tests/macro_unroll.rs b/tests/plugins_unroll.rs similarity index 100% rename from tests/macro_unroll.rs rename to tests/plugins_unroll.rs