diff --git a/.gitignore b/.gitignore index bdd52cb9..6eed6fee 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,5 @@ target/ Cargo.lock .vscode/ .cargo/ -doc/book/ before* after* diff --git a/Cargo.toml b/Cargo.toml index 6ee58d61..8d3234c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,8 @@ version = "0.19.10" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] description = "Embedded scripting for Rust" -homepage = "https://schungx.github.io/rhai" -repository = "https://github.com/jonathandturner/rhai" +homepage = "https://rhaiscript.github.io/book" +repository = "https://github.com/rhaiscript/rhai" readme = "README.md" license = "MIT OR Apache-2.0" include = [ diff --git a/README.md b/README.md index b812b5c4..7a24a1ba 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ Rhai - Embedded Scripting for Rust ================================= -![GitHub last commit](https://img.shields.io/github/last-commit/jonathandturner/rhai?logo=github) -[![Build Status](https://github.com/jonathandturner/rhai/workflows/Build/badge.svg)](https://github.com/jonathandturner/rhai/actions) -[![license](https://img.shields.io/crates/l/rhai)](https://github.com/license/jonathandturner/rhai) +![GitHub last commit](https://img.shields.io/github/last-commit/rhaiscript/rhai?logo=github) +[![Build Status](https://github.com/rhaiscript/rhai/workflows/Build/badge.svg)](https://github.com/rhaiscript/rhai/actions) +[![license](https://img.shields.io/crates/l/rhai)](https://github.com/license/rhaiscript/rhai) [![crates.io](https://img.shields.io/crates/v/rhai?logo=rust)](https://crates.io/crates/rhai/) [![crates.io](https://img.shields.io/crates/d/rhai?logo=rust)](https://crates.io/crates/rhai/) [![API Docs](https://docs.rs/rhai/badge.svg?logo=docs.rs)](https://docs.rs/rhai/) [![chat](https://img.shields.io/discord/767611025456889857.svg?logo=discord)](https://discord.gg/HquqbYFcZ9) [![Reddit](https://img.shields.io/reddit/subreddit-subscribers/Rhai?logo=reddit)](https://www.reddit.com/r/Rhai) -![Rhai logo](https://schungx.github.io/rhai/images/logo/rhai-banner-transparent-colour.svg) +![Rhai logo](https://rhaiscript.github.io/book/images/logo/rhai-banner-transparent-colour.svg) Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application. @@ -31,44 +31,44 @@ 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) - all clonable Rust types are supported; no need to implement any special trait. -* Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust. +* Tight integration with native Rust [functions](https://rhaiscript.github.io/book/rust/functions.html) and [types]([#custom-types-and-methods](https://rhaiscript.github.io/book/rust/custom.html)), including [getters/setters](https://rhaiscript.github.io/book/rust/getters-setters.html), [methods](https://rhaiscript.github.io/book/rust/custom.html) and [indexers](https://rhaiscript.github.io/book/rust/indexers.html). +* Freely pass Rust variables/constants into a script via an external [`Scope`](https://rhaiscript.github.io/book/rust/scope.html) - all clonable Rust types are supported; no need to implement any special trait. +* Easily [call a script-defined function](https://rhaiscript.github.io/book/engine/call-fn.html) from Rust. * 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). -* 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). +* Scripts are [optimized](https://rhaiscript.github.io/book/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations. +* Easy custom API development via [plugins](https://rhaiscript.github.io/book/plugins/index.html) system powered by procedural macros. +* [Function overloading](https://rhaiscript.github.io/book/language/overload.html) and [operator overloading](https://rhaiscript.github.io/book/rust/operators.html). +* Dynamic dispatch via [function pointers](https://rhaiscript.github.io/book/language/fn-ptr.html) with additional support for [currying](https://rhaiscript.github.io/book/language/fn-curry.html). +* [Closures](https://rhaiscript.github.io/book/language/fn-closure.html) (anonymous functions) that can capture shared values. +* Some syntactic support for [object-oriented programming (OOP)](https://rhaiscript.github.io/book/language/oop.html). +* Organize code base with dynamically-loadable [modules](https://rhaiscript.github.io/book/language/modules.html). * Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature). -* 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). +* Support for [minimal builds](https://rhaiscript.github.io/book/start/builds/minimal.html) by excluding unneeded language [features](https://rhaiscript.github.io/book/start/features.html). Protected against attacks ------------------------- -* 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. +* Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless [explicitly permitted](https://rhaiscript.github.io/book/patterns/control.html). +* Rugged - protected against malicious attacks (such as [stack-overflow](https://rhaiscript.github.io/book/safety/max-call-stack.html), [over-sized data](https://rhaiscript.github.io/book/safety/max-string-size.html), and [runaway scripts](https://rhaiscript.github.io/book/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts. +* Track script evaluation [progress](https://rhaiscript.github.io/book/safety/progress.html) and manually terminate a script run. For those who actually want their own language --------------------------------------------- -* Use as a [DSL](https://schungx.github.io/rhai/engine/dsl.html). -* Restrict the language by surgically [disabling keywords and operators](https://schungx.github.io/rhai/engine/disable.html). -* Define [custom operators](https://schungx.github.io/rhai/engine/custom-op.html). -* Extend the language with [custom syntax](https://schungx.github.io/rhai/engine/custom-syntax.html). +* Use as a [DSL](https://rhaiscript.github.io/book/engine/dsl.html). +* Restrict the language by surgically [disabling keywords and operators](https://rhaiscript.github.io/book/engine/disable.html). +* Define [custom operators](https://rhaiscript.github.io/book/engine/custom-op.html). +* Extend the language with [custom syntax](https://rhaiscript.github.io/book/engine/custom-syntax.html). Documentation ------------- -See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai scripting engine and language. +See [The Rhai Book](https://rhaiscript.github.io/book) for details on the Rhai scripting engine and language. To build _The Book_, first install [`mdbook`](https://github.com/rust-lang/mdBook) and [`mdbook-tera`](https://github.com/avitex/mdbook-tera) (for templating). @@ -87,8 +87,8 @@ License 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) +* [Apache License, Version 2.0](https://github.com/rhaiscript/rhai/blob/master/LICENSE-APACHE.txt), or +* [MIT license](https://github.com/rhaiscript/rhai/blob/master/LICENSE-MIT.txt) Unless explicitly stated otherwise, any contribution intentionally submitted for inclusion in this crate, as defined in the Apache-2.0 license, shall diff --git a/RELEASES.md b/RELEASES.md index d377aa7c..c014d5e6 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -8,12 +8,15 @@ Breaking changes ---------------- * The error variant `EvalAltResult::ErrorInFunctionCall` has a new parameter holding the _source_ of the function. +* `ParseErrorType::WrongFnDefinition` is renamed `FnWrongDefinition`. +* Redefining an existing function within the same script now throws a new `ParseErrorType::FnDuplicatedDefinition`. This is to prevent accidental overwriting an earlier function definition. Enhancements ------------ * Source information is provided when there is an error within a call to a function defined in another module. * Source information is provided to the `NativeCallContext` for native Rust functions. +* `EvalAltResult::clear_position` to clear the position information of an error - useful when only the message is needed and the position doesn't need to be printed out. Version 0.19.9 @@ -478,7 +481,7 @@ Version 0.16.0 The major new feature in this version is OOP - well, poor man's OOP, that is. -The `README` is officially transferred to [The Rhai Book](https://schungx.github.io/rhai). +The `README` is officially transferred to [The Rhai Book](https://rhaiscript.github.io/book). An online [Playground](https://alvinhochun.github.io/rhai-demo/) is available. @@ -503,7 +506,7 @@ New features Enhancements ------------ -* [The Rhai Book](https://schungx.github.io/rhai) is online. Most content in the original `README` was transferred to the Book. +* [The Rhai Book](https://rhaiscript.github.io/book) is online. Most content in the original `README` was transferred to the Book. * New feature `internals` to expose internal data structures (e.g. the AST nodes). diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 97d80523..073843b1 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -4,8 +4,8 @@ version = "0.3.1" edition = "2018" authors = ["jhwgh1968"] description = "Procedural macro support package for Rhai, a scripting language for Rust" -homepage = "https://schungx.github.io/rhai/plugins/index.html" -repository = "https://github.com/jonathandturner/rhai" +homepage = "https://rhaiscript.github.io/book/plugins/index.html" +repository = "https://github.com/rhaiscript/rhai" license = "MIT OR Apache-2.0" [lib] diff --git a/codegen/README.md b/codegen/README.md index 275c26eb..5099d3a4 100644 --- a/codegen/README.md +++ b/codegen/README.md @@ -2,4 +2,4 @@ Procedural Macros for Plugins ============================= This crate holds procedural macros for code generation, supporting the plugins system -for [Rhai](https://github.com/jonathandturner/rhai). +for [Rhai](https://github.com/rhaiscript/rhai). diff --git a/codegen/ui_tests/rhai_mod_unknown_type.rs b/codegen/ui_tests/rhai_mod_unknown_type.rs index 7c19ab18..4c067fd3 100644 --- a/codegen/ui_tests/rhai_mod_unknown_type.rs +++ b/codegen/ui_tests/rhai_mod_unknown_type.rs @@ -17,7 +17,7 @@ pub mod test_module { fn main() { let n = Point { x: 0.0, - y: 10.0, + y: 10.0 }; if test_module::test_fn(n) { println!("yes"); diff --git a/codegen/ui_tests/rhai_mod_unknown_type.stderr b/codegen/ui_tests/rhai_mod_unknown_type.stderr index 87360967..1b400234 100644 --- a/codegen/ui_tests/rhai_mod_unknown_type.stderr +++ b/codegen/ui_tests/rhai_mod_unknown_type.stderr @@ -19,5 +19,5 @@ help: consider importing one of these items | 11 | use std::fmt::Pointer; | -11 | use syn::export::fmt::Pointer; +11 | use syn::__private::fmt::Pointer; | diff --git a/doc/README.md b/doc/README.md deleted file mode 100644 index 63d79bc4..00000000 --- a/doc/README.md +++ /dev/null @@ -1,41 +0,0 @@ -The Rhai Book -============= - -[_The Rhai Book_](https://schungx.github.io/rhai) serves as Rhai's primary -documentation and tutorial resource. - - -How to Build from Source ------------------------- - -* Install [`mdbook`](https://github.com/rust-lang/mdBook): - -```bash -cargo install mdbook -``` - -* Install [`mdbook-tera`](https://github.com/avitex/mdbook-tera) (for templating): - -```bash -cargo install mdbook-tera -``` - -* Run build in source directory: - -```bash -cd doc -mdbook build -``` - - -Configuration Settings ----------------------- - -Settings stored in `context.json`: - -| Setting | Description | -| ---------- | ------------------------------------------------------------------------------------------------- | -| `version` | version of Rhai | -| `repoHome` | points to the [root of the GitHub repo](https://github.com/jonathandturner/rhai/blob/master) | -| `repoTree` | points to the [root of the GitHub repo tree](https://github.com/jonathandturner/rhai/tree/master) | -| `rootUrl` | sub-directory for the root domain, e.g. `/rhai` | diff --git a/doc/book.toml b/doc/book.toml deleted file mode 100644 index a48eadaf..00000000 --- a/doc/book.toml +++ /dev/null @@ -1,22 +0,0 @@ -[book] -title = "Rhai - Embedded Scripting for Rust" -authors = ["Jonathan Turner", "Stephen Chung"] -description = "Tutorial and reference on the Rhai scripting engine and language." -language = "en" - -[output.html] -no-section-label = true -git-repository-url = "https://github.com/jonathandturner/rhai" -curly-quotes = true - -[output.html.fold] -enable = true -level = 4 - -[outputX.linkcheck] -follow-web-links = false -traverse-parent-directories = false -warning-policy = "ignore" - -[preprocessor.tera] -command = "mdbook-tera --json ./src/context.json" diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md deleted file mode 100644 index 0f16501f..00000000 --- a/doc/src/SUMMARY.md +++ /dev/null @@ -1,149 +0,0 @@ -The Rhai Scripting Language -========================== - -1. [What is Rhai](about/index.md) - 1. [Features](about/features.md) - 2. [Supported Targets and Builds](about/targets.md) - 3. [What Rhai Isn't](about/non-design.md) - 4. [Licensing](about/license.md) - 5. [Related Resources](about/related.md) -2. [Getting Started](start/index.md) - 1. [Online Playground](start/playground.md) - 2. [Install the Rhai Crate](start/install.md) - 3. [Optional Features](start/features.md) - 4. [Special Builds](start/builds/index.md) - 1. [Performance](start/builds/performance.md) - 2. [Minimal](start/builds/minimal.md) - 3. [no-std](start/builds/no-std.md) - 4. [WebAssembly (WASM)](start/builds/wasm.md) - 5. [Packaged Utilities](start/bin.md) - 6. [Examples](start/examples/index.md) - 1. [Rust](start/examples/rust.md) - 2. [Scripts](start/examples/scripts.md) -3. [Using the `Engine`](engine/index.md) - 1. [Hello World in Rhai – Evaluate a Script](engine/hello-world.md) - 2. [Compile to AST for Repeated Evaluations](engine/compile.md) - 3. [Call a Rhai Function from Rust](engine/call-fn.md) - 4. [Create a Rust Closure from a Rhai Function](engine/func.md) - 5. [Evaluate Expressions Only](engine/expressions.md) - 6. [Raw Engine](engine/raw.md) - 7. [Scope – Initializing and Maintaining State](engine/scope.md) - 8. [Engine Configuration Options](engine/options.md) -4. [Extend Rhai with Rust](rust/index.md) - 1. [Traits](rust/traits.md) - 2. [Register a Rust Function](rust/functions.md) - 1. [String Parameters in Rust Functions](rust/strings.md) - 3. [Register a Generic Rust Function](rust/generic.md) - 4. [Register a Fallible Rust Function](rust/fallible.md) - 5. [Override a Built-in Function](rust/override.md) - 6. [Operator Overloading](rust/operators.md) - 7. [Register any Rust Type and its Methods](rust/custom.md) - 1. [Property Getters and Setters](rust/getters-setters.md) - 2. [Indexers](rust/indexers.md) - 3. [Disable Custom Types](rust/disable-custom.md) - 4. [Printing Custom Types](rust/print-custom.md) - 8. [Modules](rust/modules/index.md) - 1. [Create from Rust](rust/modules/create.md) - 2. [Create from AST](rust/modules/ast.md) - 3. [Module Resolvers](rust/modules/resolvers.md) - 1. [Custom Module Resolvers](rust/modules/imp-resolver.md) - 9. [Plugins](plugins/index.md) - 1. [Export a Rust Module](plugins/module.md) - 2. [Export a Rust Function](plugins/function.md) - 10. [Packages](rust/packages/index.md) - 1. [Built-in Packages](rust/packages/builtin.md) - 2. [Custom Packages](rust/packages/create.md) -5. [Rhai Language Reference](language/index.md) - 1. [Comments](language/comments.md) - 1. [Doc-Comments](language/doc-comments.md) - 2. [Values and Types](language/values-and-types.md) - 1. [Dynamic Values](language/dynamic.md) - 2. [Serialization/Deserialization with `serde`](rust/serde.md) - 3. [type_of()](language/type-of.md) - 4. [Numbers](language/numbers.md) - 1. [Operators](language/num-op.md) - 2. [Functions](language/num-fn.md) - 3. [Value Conversions](language/convert.md) - 5. [Strings and Characters](language/strings-chars.md) - 1. [Built-in Functions](language/string-fn.md) - 6. [Arrays](language/arrays.md) - 7. [Object Maps](language/object-maps.md) - 1. [Parse from JSON](language/json.md) - 2. [Special Support for OOP](language/object-maps-oop.md) - 8. [Time-Stamps](language/timestamps.md) - 3. [Keywords](language/keywords.md) - 4. [Statements](language/statements.md) - 5. [Variables](language/variables.md) - 6. [Constants](language/constants.md) - 7. [Logic Operators](language/logic.md) - 8. [Assignment Operators](language/assignment-op.md) - 9. [If Statement](language/if.md) - 10. [Switch Expression](language/switch.md) - 11. [While Loop](language/while.md) - 12. [Do Loop](language/do.md) - 13. [Loop Statement](language/loop.md) - 14. [For Loop](language/for.md) - 1. [Iterators for Custom Types](language/iterator.md) - 15. [Return Values](language/return.md) - 16. [Throw Exception on Error](language/throw.md) - 17. [Catch Exceptions](language/try-catch.md) - 18. [Functions](language/functions.md) - 1. [Call Method as Function](language/method.md) - 2. [Overloading](language/overload.md) - 3. [Namespaces](language/fn-namespaces.md) - 4. [Function Pointers](language/fn-ptr.md) - 5. [Currying](language/fn-curry.md) - 6. [Anonymous Functions](language/fn-anon.md) - 7. [Closures](language/fn-closure.md) - 19. [Print and Debug](language/print-debug.md) - 20. [Modules](language/modules/index.md) - 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) - 2. [Import Modules](language/modules/import.md) - 21. [Eval Function](language/eval.md) -6. [Safety and Protection](safety/index.md) - 1. [Checked Arithmetic](safety/checked.md) - 2. [Sand-Boxing](safety/sandbox.md) - 3. [Maximum Length of Strings](safety/max-string-size.md) - 4. [Maximum Size of Arrays](safety/max-array-size.md) - 5. [Maximum Size of Object Maps](safety/max-map-size.md) - 6. [Maximum Number of Operations](safety/max-operations.md) - 1. [Tracking Progress and Force-Termination](safety/progress.md) - 7. [Maximum Number of Modules](safety/max-modules.md) - 8. [Maximum Call Stack Depth](safety/max-call-stack.md) - 9. [Maximum Statement Depth](safety/max-stmt-depth.md) -7. [Script Optimization](engine/optimize/index.md) - 1. [Optimization Levels](engine/optimize/optimize-levels.md) - 2. [Re-Optimize an AST](engine/optimize/reoptimize.md) - 3. [Eager Function Evaluation](engine/optimize/eager.md) - 4. [Side-Effect Considerations](engine/optimize/side-effects.md) - 5. [Volatility Considerations](engine/optimize/volatility.md) - 6. [Subtle Semantic Changes](engine/optimize/semantics.md) -8. [Usage Patterns](patterns/index.md) - 1. [Object-Oriented Programming (OOP)](patterns/oop.md) - 2. [Working With Rust Enums](patterns/enums.md) - 3. [Loadable Configuration](patterns/config.md) - 4. [Control Layer](patterns/control.md) - 5. [Singleton Command](patterns/singleton.md) - 6. [Multi-Layer Functions](patterns/multi-layer.md) - 7. [One Engine Instance Per Call](patterns/parallel.md) - 8. [Scriptable Event Handler with State](patterns/events.md) - 9. [Dynamic Constants Provider](patterns/dynamic-const.md) -9. [Advanced Topics](advanced.md) - 1. [Capture Scope for Function Call](language/fn-capture.md) - 2. [Low-Level API](rust/register-raw.md) - 3. [Variable Resolver](engine/var.md) - 4. [Use as DSL](engine/dsl.md) - 1. [Disable Keywords and/or Operators](engine/disable.md) - 2. [Custom Operators](engine/custom-op.md) - 3. [Extending with Custom Syntax](engine/custom-syntax.md) - 5. [Multiple Instantiation](patterns/multiple.md) - 6. [Functions Metadata](engine/metadata/index.md) - 1. [Generate Function Signatures](engine/metadata/gen_fn_sig.md) - 2. [Export Metadata to JSON](engine/metadata/export_to_json.md) -10. [External Tools](tools/index.md) - 1. [Online Playground](tools/playground.md) - 2. [`rhai-doc`](tools/rhai-doc.md) -11. [Appendix](appendix/index.md) - 1. [Keywords](appendix/keywords.md) - 2. [Operators and Symbols](appendix/operators.md) - 3. [Literals](appendix/literals.md) diff --git a/doc/src/about/features.md b/doc/src/about/features.md deleted file mode 100644 index 597f6677..00000000 --- a/doc/src/about/features.md +++ /dev/null @@ -1,79 +0,0 @@ -Features -======== - -{{#include ../links.md}} - -Easy ----- - -* Easy-to-use language similar to JavaScript+Rust with dynamic typing. - -* Tight integration with native Rust [functions] and [types][custom types] including [getters/setters], - [methods][custom type] and [indexers]. - -* Freely pass Rust variables/constants into a script via an external [`Scope`] – all clonable Rust types are supported seamlessly - without the need to implement any special trait. - -* Easily [call a script-defined function]({{rootUrl}}/engine/call-fn.md) from Rust. - -* Very few additional dependencies – right now only [`smallvec`](https://crates.io/crates/smallvec/) plus crates for procedural macros; - for [`no-std`] and `WASM` builds, a number of additional dependencies are pulled in to provide for missing functionalities. - -* [Plugins] system powered by procedural macros simplifies custom API development. - -Fast ----- - -* Fairly low compile-time overhead. - -* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM). - -* Scripts are [optimized][script optimization] (useful for template-based machine-generated scripts) for repeated evaluations. - -Dynamic -------- - -* [Function overloading]({{rootUrl}}/language/overload.md). - -* [Operator overloading]({{rootUrl}}/rust/operators.md). - -* Organize code base with dynamically-loadable [modules]. - -* Dynamic dispatch via [function pointers] with additional support for [currying]. - -* [Closures] that can capture shared variables. - -* Some support for [object-oriented programming (OOP)][OOP]. - -* Hook into variables access via [variable resolver]. - -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]({{rootUrl}}/patterns/control.md). - -Rugged ------- - -* Protected against malicious attacks (such as [stack-overflow][maximum call stack depth], [over-sized data][maximum length of strings], - and [runaway scripts][maximum number of operations] etc.) that may come from untrusted third-party user-land scripts. - -* Track script evaluation [progress] and manually terminate a script run. - -Flexible --------- - -* Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature). - -* Serialization/deserialization support via [`serde`](https://crates.io/crates/serde). - -* Support for [minimal builds] by excluding unneeded language [features]. - -* Supports [most build targets](targets.md) including `no-std` and [WASM]. - -* Surgically [disable keywords and operators] to restrict the language. - -* Use as a [DSL] by defining [custom operators] and/or extending the language with [custom syntax]. diff --git a/doc/src/about/index.md b/doc/src/about/index.md deleted file mode 100644 index f316698d..00000000 --- a/doc/src/about/index.md +++ /dev/null @@ -1,46 +0,0 @@ -What is Rhai -============ - -{{#include ../links.md}} - -![Rhai Logo]({{rootUrl}}/images/logo/rhai-banner-transparent-colour.svg) - -Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way -to add scripting to any application. - - -Versions --------- - -This Book is for version **{{version}}** of Rhai. - -{% if rootUrl != "" and not rootUrl is ending_with("vnext") %} -For the latest development version, see [here]({{rootUrl}}/vnext/). -{% endif %} - - -Etymology of the name "Rhai" ---------------------------- - -### As per Rhai's author Johnathan Turner - -In the beginning there was [ChaiScript](http://chaiscript.com), -which is an embedded scripting language for C++. -Originally it was intended to be a scripting language similar to **JavaScript**. - -With java being a kind of hot beverage, the new language was named after -another hot beverage – **Chai**, which is the word for "tea" in many world languages -and, in particular, a popular kind of milk tea consumed in India. - -Later, when the novel implementation technique behind ChaiScript was ported from C++ to Rust, -logically the `C` was changed to an `R` to make it "RhaiScript", or just "Rhai". - -### On the origin of the semi-official Rhai logo - -One of Rhai's maintainers, [Stephen Chung](https://github.com/schungx), was thinking about a logo when he accidentally -came across a copy of _Catcher in the Rye_ in a restaurant, and drew the first version -of the logo. - -Then [`@semirix`](https://github.com/semirix) refined it to the current version. - -The plan is to make the logo official together with a `1.0` release. diff --git a/doc/src/about/license.md b/doc/src/about/license.md deleted file mode 100644 index 8465ec4c..00000000 --- a/doc/src/about/license.md +++ /dev/null @@ -1,14 +0,0 @@ -Licensing -========= - -{{#include ../links.md}} - -Rhai is licensed under either of the following, at your choice: - -* [Apache License, Version 2.0]({{repoHome}}/LICENSE-APACHE.txt), or - -* [MIT license]({{repoHome}}/LICENSE-MIT.txt). - -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/doc/src/about/non-design.md b/doc/src/about/non-design.md deleted file mode 100644 index aa39f465..00000000 --- a/doc/src/about/non-design.md +++ /dev/null @@ -1,77 +0,0 @@ -What Rhai Isn't -=============== - -{{#include ../links.md}} - -Rhai's purpose is to provide a dynamic layer over Rust code, in the same spirit of _zero cost abstractions_. -It doesn't attempt to be a new language. For example: - -* **No classes**. Well, Rust doesn't either. On the other hand... - -* **No traits**... so it is also not Rust. Do your Rusty stuff in Rust. - -* **No structures/records/tuples** – define your types in Rust instead; Rhai can seamlessly work with _any Rust type_. - - There is, however, a built-in [object map] type which is adequate for most uses. - It is possible to simulate [object-oriented programming (OOP)][OOP] by storing [function pointers] - or [closures] in [object map] properties, turning them into _methods_. - -* **No first-class functions** – Code your functions in Rust instead, and register them with Rhai. - - There is, however, support for simple [function pointers] to allow runtime dispatch by function name. - -* **No garbage collection** – this should be expected, so... - -* **No first-class closures** – do your closure magic in Rust instead: [turn a Rhai scripted function into a Rust closure]({{rootUrl}}/engine/call-fn.md). - - There is, however, support for simulated [closures] via [currying] a [function pointer] with - captured shared variables. - -* **No byte-codes/JIT** – Rhai has an optimized AST-walking interpreter which is fast enough for most casual - usage scenarios. Essential AST data structures are packed and kept together to maximize cache friendliness. - - Functions are dispatched based on pre-calculated hashes and accessing variables are mostly through pre-calculated - offsets to the variables file (a [`Scope`]), so it is seldom necessary to look something up by text name. - - In addition, Rhai's design deliberately avoids maintaining a _scope chain_ so function scopes do not - pay any speed penalty. This particular design also allows variables data to be kept together in a contiguous - block, avoiding allocations and fragmentation while being cache-friendly. In a typical script evaluation run, - no data is shared and nothing is locked. - - Still, the purpose of Rhai is not to be super _fast_, but to make it as easy and versatile as possible to - integrate with native Rust applications. - -* **No formal language grammar** – Rhai uses a hand-coded lexer, a hand-coded top-down recursive-descent parser - for statements, and a hand-coded Pratt parser for expressions. - - This lack of formalism allows the _tokenizer_ and _parser_ themselves to be exposed as services in order - to support [disabling keywords/operators][disable keywords and operators], adding [custom operators], - and defining [custom syntax]. - - -Do Not Write The Next 4D VR Game in Rhai ---------------------------------------- - -Due to this intended usage, Rhai deliberately keeps the language simple and small by omitting -advanced language features such as classes, inheritance, interfaces, generics, -first-class functions/closures, pattern matching, concurrency, byte-codes VM, JIT etc. -Focus is on _flexibility_ and _ease of use_ instead of raw speed. - -Avoid the temptation to write full-fledge application logic entirely in Rhai - -that use case is best fulfilled by more complete languages such as JavaScript or Lua. - - -Thin Dynamic Wrapper Layer Over Rust Code ----------------------------------------- - -In actual practice, it is usually best to expose a Rust API into Rhai for scripts to call. - -All the core functionalities should be written in Rust, with Rhai being the dynamic _control_ layer. - -This is similar to some dynamic languages where most of the core functionalities reside in a C/C++ -standard library. - -Another similar scenario is a web front-end driving back-end services written in a systems language. -In this case, JavaScript takes the role of Rhai while the back-end language, well... it can actually also be Rust. -Except that Rhai integrates with Rust _much_ more tightly, removing the need for interfaces such -as XHR calls and payload encoding such as JSON. diff --git a/doc/src/about/related.md b/doc/src/about/related.md deleted file mode 100644 index 84af9824..00000000 --- a/doc/src/about/related.md +++ /dev/null @@ -1,36 +0,0 @@ -Related Resources -================= - -{{#include ../links.md}} - - -Online Resources for Rhai -------------------------- - -* [GitHub](https://github.com/jonathandturner/rhai) – Home repository - -* [`crates.io`](https://crates.io/crates/rhai) – Rhai crate - -* [`DOCS.RS`](https://docs.rs/rhai) – Rhai API documentation - -* [`LIB.RS`](https://lib.rs/crates/rhai) – Rhai library info - -* [Discord Chat](https://discord.gg/HquqbYFcZ9) – Rhai channel - -* [Reddit](https://www.reddit.com/r/Rhai) – Rhai community - - -External Tools --------------- - -* [Online Playground][playground] – Run Rhai scripts directly from an editor in the browser - -* [`rhai-doc`] – Rhai script documentation tool - - -Other Cool Projects -------------------- - -* [ChaiScript](http://chaiscript.com) – A strong inspiration for Rhai. An embedded scripting language for C++. - -* Check out the list of [scripting languages for Rust](https://github.com/rust-unofficial/awesome-rust#scripting) on [awesome-rust](https://github.com/rust-unofficial/awesome-rust) diff --git a/doc/src/about/targets.md b/doc/src/about/targets.md deleted file mode 100644 index a68430a0..00000000 --- a/doc/src/about/targets.md +++ /dev/null @@ -1,18 +0,0 @@ -Supported Targets and Builds -=========================== - -{{#include ../links.md}} - -The following targets and builds are support by Rhai: - -* All common CPU targets for Windows, Linux and MacOS. - -* WebAssembly ([WASM]) - -* [`no-std`] - - -Minimum Rust Version --------------------- - -The minimum version of Rust required to compile Rhai is `1.45.0`. diff --git a/doc/src/advanced.md b/doc/src/advanced.md deleted file mode 100644 index 87136de0..00000000 --- a/doc/src/advanced.md +++ /dev/null @@ -1,6 +0,0 @@ -Advanced Topics -=============== - -{{#include links.md}} - -This section covers advanced features of the Rhai [`Engine`]. diff --git a/doc/src/appendix/index.md b/doc/src/appendix/index.md deleted file mode 100644 index bdb6cae9..00000000 --- a/doc/src/appendix/index.md +++ /dev/null @@ -1,6 +0,0 @@ -Appendix -======== - -{{#include ../links.md}} - -This section contains miscellaneous reference materials. diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md deleted file mode 100644 index 2866cb5d..00000000 --- a/doc/src/appendix/keywords.md +++ /dev/null @@ -1,75 +0,0 @@ -Keywords List -============= - -{{#include ../links.md}} - -| Keyword | Description | Inactive under | Is function? | Overloadable | -| :-------------------: | ------------------------------------------- | :-------------: | :----------: | :----------: | -| `true` | boolean true literal | | no | | -| `false` | boolean false literal | | no | | -| `let` | variable declaration | | no | | -| `const` | constant declaration | | no | | -| `if` | if statement | | no | | -| `else` | else block of if statement | | no | | -| `switch` | matching | | no | | -| `do` | looping | | no | | -| `while` | 1) while loop
2) condition for do loop | | no | | -| `until` | do 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 | | -| `try` | trap exception | | no | | -| `catch` | catch 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 | yes | -| `call` | call a [function pointer] | | yes | no | -| `curry` | curry a [function pointer] | | yes | no | -| `this` | reference to base object for method call | [`no_function`] | no | | -| `type_of` | get type name of value | | yes | yes | -| `print` | print value | | yes | yes | -| `debug` | print value in debug format | | yes | yes | -| `eval` | evaluate script | | yes | yes | - - -Reserved Keywords ------------------ - -| Keyword | Potential usage | -| --------- | --------------------- | -| `var` | variable declaration | -| `static` | variable declaration | -| `begin` | block scope | -| `end` | block scope | -| `shared` | share value | -| `each` | looping | -| `then` | control flow | -| `goto` | control flow | -| `exit` | control flow | -| `unless` | control flow | -| `match` | matching | -| `case` | matching | -| `public` | function/field access | -| `new` | constructor | -| `use` | import namespace | -| `with` | scope | -| `module` | module | -| `package` | package | -| `thread` | threading | -| `spawn` | threading | -| `go` | threading | -| `await` | async | -| `async` | async | -| `sync` | async | -| `yield` | async | -| `default` | special value | -| `void` | special value | -| `null` | special value | -| `nil` | special value | diff --git a/doc/src/appendix/literals.md b/doc/src/appendix/literals.md deleted file mode 100644 index 3e5a5fdf..00000000 --- a/doc/src/appendix/literals.md +++ /dev/null @@ -1,16 +0,0 @@ -Literals Syntax -=============== - -{{#include ../links.md}} - -| 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][string] | 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 deleted file mode 100644 index 71e94965..00000000 --- a/doc/src/appendix/operators.md +++ /dev/null @@ -1,74 +0,0 @@ -Operators and Symbols -==================== - -{{#include ../links.md}} - - -Operators ---------- - -| Operator | Description | Binary? | Binding direction | -| :-----------------------------------------------------------------------------------------: | -------------------------------------- | :--------: | :---------------: | -| `+` | add | yes | left | -| `-` | 1) subtract
2) negative (prefix) | yes
no | left
right | -| `*` | multiply | yes | left | -| `/` | divide | yes | left | -| `%` | modulo | yes | left | -| `~` | power | yes | left | -| `>>` | right bit-shift | yes | left | -| `<<` | left bit-shift | yes | left | -| `&` | 1) bit-wise _AND_
2) boolean _AND_ | yes | left | -| \| | 1) bit-wise _OR_
2) boolean _OR_ | yes | left | -| `^` | 1) bit-wise _XOR_
2) boolean _XOR_ | yes | left | -| `=`, `+=`, `-=`, `*=`, `/=`,
`~=`, `%=`, `<<=`, `>>=`, `&=`,
\|=, `^=` | assignments | yes | right | -| `==` | equals to | yes | left | -| `~=` | not equals to | yes | left | -| `>` | greater than | yes | left | -| `>=` | greater than or equals to | yes | left | -| `<` | less than | yes | left | -| `<=` | less than or equals to | yes | left | -| `&&` | boolean _AND_ (short-circuits) | yes | left | -| \|\| | boolean _OR_ (short-circuits) | yes | left | -| `!` | boolean _NOT_ | no | left | -| `[` .. `]` | indexing | yes | right | -| `.` | 1) property access
2) method call | yes | right | - - -Symbols and Patterns --------------------- - -| Symbol | Name | Description | -| ---------------------------------- | :------------------: | ------------------------------------- | -| `_` | underscore | default `switch` case | -| `;` | semicolon | statement separator | -| `,` | comma | list separator | -| `:` | colon | [object map] property value separator | -| `::` | path | module path separator | -| `#{` .. `}` | hash map | [object map] literal | -| `"` .. `"` | double quote | [string] | -| `'` .. `'` | single quote | [character][string] | -| `\` | escape | escape character literal | -| `(` .. `)` | parentheses | expression grouping | -| `{` .. `}` | braces | block statement | -| \| .. \| | pipes | closure | -| `[` .. `]` | brackets | [array] literal | -| `!` | bang | function call in calling scope | -| `=>` | double arrow | `switch` expression case separator | -| `//` | comment | line comment | -| `/*` .. `*/` | comment | block comment | -| `(*` .. `*)` | comment | _reserved_ | -| `<` .. `>` | angular brackets | _reserved_ | -| `++` | increment | _reserved_ | -| `--` | decrement | _reserved_ | -| `..` | range | _reserved_ | -| `...` | range | _reserved_ | -| `**` | exponentiation | _reserved_ | -| `#` | hash | _reserved_ | -| `@` | at | _reserved_ | -| `$` | dollar | _reserved_ | -| `->` | arrow | _reserved_ | -| `<-` | left arrow | _reserved_ | -| `===` | strict equals to | _reserved_ | -| `!==` | strict not equals to | _reserved_ | -| `:=` | assignment | _reserved_ | -| `::<` .. `>` | turbofish | _reserved_ | diff --git a/doc/src/context.json b/doc/src/context.json deleted file mode 100644 index d19c1ac0..00000000 --- a/doc/src/context.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "version": "0.19.10", - "repoHome": "https://github.com/jonathandturner/rhai/blob/master", - "repoTree": "https://github.com/jonathandturner/rhai/tree/master", - "rootUrl": "", - "rootUrlX": "/rhai", - "rootUrlXX": "/rhai/vnext" -} \ No newline at end of file diff --git a/doc/src/engine/call-fn.md b/doc/src/engine/call-fn.md deleted file mode 100644 index 548b058a..00000000 --- a/doc/src/engine/call-fn.md +++ /dev/null @@ -1,95 +0,0 @@ -Calling Rhai Functions from Rust -=============================== - -{{#include ../links.md}} - -Rhai also allows working _backwards_ from the other direction – i.e. calling a Rhai-scripted function -from Rust via `Engine::call_fn`. - -Functions declared with `private` are hidden and cannot be called from Rust (see also [modules]). - -```rust -// Define functions in a script. -let ast = engine.compile(true, - r#" - // a function with two parameters: string and i64 - fn hello(x, y) { - x.len + y - } - - // functions can be overloaded: this one takes only one parameter - fn hello(x) { - x * 2 - } - - // this one takes no parameters - fn hello() { - 42 - } - - // this one is private and cannot be called by 'call_fn' - private hidden() { - throw "you shouldn't see me!"; - } - "#)?; - -// A custom scope can also contain any variables/constants available to the functions -let mut scope = Scope::new(); - -// Evaluate a function defined in the script, passing arguments into the script as a tuple. -// Beware, arguments must be of the correct types because Rhai does not have built-in type conversions. -// If arguments of the wrong types are passed, the Engine will not find the function. - -let result: i64 = engine.call_fn(&mut scope, &ast, "hello", ( String::from("abc"), 123_i64 ) )?; -// ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -// return type must be specified put arguments in a tuple - -let result: i64 = engine.call_fn(&mut scope, &ast, "hello", (123_i64,) )?; -// ^^^^^^^^^^ tuple of one - -let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?; -// ^^ unit = tuple of zero - -// The following call will return a function-not-found error because -// 'hidden' is declared with 'private'. -let result: () = engine.call_fn(&mut scope, &ast, "hidden", ())?; -``` - - -Low-Level API – `Engine::call_fn_dynamic` ----------------------------------------------- - -For more control, construct all arguments as `Dynamic` values and use `Engine::call_fn_dynamic`, passing it -anything that implements `AsMut` (such as a simple array or a `Vec`): - -```rust -let result = engine.call_fn_dynamic( - &mut scope, // scope to use - &ast, // AST containing the functions - "hello", // function entry-point - None, // 'this' pointer, if any - [ String::from("abc").into(), 123_i64.into() ] // arguments - )?; -``` - - -Binding the `this` Pointer -------------------------- - -`Engine::call_fn_dynamic` can also bind a value to the `this` pointer of a script-defined function. - -```rust -let ast = engine.compile("fn action(x) { this += x; }")?; - -let mut value: Dynamic = 1_i64.into(); - -let result = engine.call_fn_dynamic( - &mut scope, - &ast, - "action", - Some(&mut value), // binding the 'this' pointer - [ 41_i64.into() ] - )?; - -assert_eq!(value.as_int()?, 42); -``` diff --git a/doc/src/engine/compile.md b/doc/src/engine/compile.md deleted file mode 100644 index 07c172c1..00000000 --- a/doc/src/engine/compile.md +++ /dev/null @@ -1,27 +0,0 @@ -Compile a Script (to AST) -======================== - -{{#include ../links.md}} - -To repeatedly evaluate a script, _compile_ it first with `Engine::compile` into an `AST` -(abstract syntax tree) form. - -`Engine::eval_ast` evaluates a pre-compiled `AST`. - -```rust -// Compile to an AST and store it for later evaluations -let ast = engine.compile("40 + 2")?; - -for _ in 0..42 { - let result: i64 = engine.eval_ast(&ast)?; - - println!("Answer #{}: {}", i, result); // prints 42 -} -``` - -Compiling a script file is also supported with `Engine::compile_file` -(not available under [`no_std`] or in [WASM] builds): - -```rust -let ast = engine.compile_file("hello_world.rhai".into())?; -``` diff --git a/doc/src/engine/custom-op.md b/doc/src/engine/custom-op.md deleted file mode 100644 index 3dacfa9d..00000000 --- a/doc/src/engine/custom-op.md +++ /dev/null @@ -1,115 +0,0 @@ -Custom Operators -================ - -{{#include ../links.md}} - -For use as a DSL (Domain-Specific Languages), it is sometimes more convenient to augment Rhai with -customized operators performing specific logic. - -`Engine::register_custom_operator` registers a keyword as a custom operator, giving it a particular -_precedence_ (which cannot be zero). - - -Example -------- - -```rust -use rhai::{Engine, RegisterFn}; - -let mut engine = Engine::new(); - -// Register a custom operator named 'foo' and give it a precedence of 160 -// (i.e. between +|- and *|/) -// Also register the implementation of the customer operator as a function -engine - .register_custom_operator("foo", 160)? - .register_fn("foo", |x: i64, y: i64| (x * y) - (x + y)); - -// The custom operator can be used in expressions -let result = engine.eval_expression::("1 + 2 * 3 foo 4 - 5 / 6")?; -// ^ custom operator - -// The above is equivalent to: 1 + ((2 * 3) foo 4) - (5 / 6) -result == 15; -``` - - -Alternatives to a Custom Operator --------------------------------- - -Custom operators are merely _syntactic sugar_. They map directly to registered functions. - -Therefore, the following are equivalent (assuming `foo` has been registered as a custom operator): - -```rust -1 + 2 * 3 foo 4 - 5 / 6 // use custom operator - -1 + foo(2 * 3, 4) - 5 / 6 // use function call -``` - -A script using custom operators can always be pre-processed, via a pre-processor application, -into a syntax that uses the corresponding function calls. - -Using `Engine::register_custom_operator` merely enables a convenient shortcut. - - -Must be a Valid Identifier or Reserved Symbol --------------------------------------------- - -All custom operators must be _identifiers_ that follow the same naming rules as [variables]. - -Alternatively, they can also be [reserved symbols]({{rootUrl}}/appendix/operators.md#symbols), -[disabled operators or keywords][disable keywords and operators]. - -```rust -engine.register_custom_operator("foo", 20); // 'foo' is a valid custom operator - -engine.register_custom_operator("#", 20); // the reserved symbol '#' is also - // a valid custom operator - -engine.register_custom_operator("+", 30); // <- error: '+' is an active operator - -engine.register_custom_operator("=>", 30); // <- error: '=>' is an active symbol -``` - - -Binary Operators Only ---------------------- - -All custom operators must be _binary_ (i.e. they take two operands). -_Unary_ custom operators are not supported. - -```rust -engine - .register_custom_operator("foo", 160)? - .register_fn("foo", |x: i64| x * x); - -engine.eval::("1 + 2 * 3 foo 4 - 5 / 6")?; // error: function 'foo (i64, i64)' not found -``` - - -Operator Precedence -------------------- - -All operators in Rhai has a _precedence_ indicating how tightly they bind. - -A higher precedence binds more tightly than a lower precedence, so `*` and `/` binds before `+` and `-` etc. - -When registering a custom operator, the operator's precedence must also be provided. - -The following _precedence table_ shows the built-in precedence of standard Rhai operators: - -| Category | Operators | Precedence (0-255) | -| ------------------- | :-------------------------------------------------------------------------------------: | :----------------: | -| Assignments | `=`, `+=`, `-=`, `*=`, `/=`, `~=`, `%=`,
`<<=`, `>>=`, `&=`, \|=, `^=` | 0 | -| Logic and bit masks | \|\|, \|, `^` | 30 | -| Logic and bit masks | `&&`, `&` | 60 | -| Comparisons | `==`, `!=` | 90 | -| | `in` | 110 | -| Comparisons | `>`, `>=`, `<`, `<=` | 130 | -| Arithmetic | `+`, `-` | 150 | -| Arithmetic | `*`, `/`, `%` | 180 | -| Arithmetic | `~` | 190 | -| Bit-shifts | `<<`, `>>` | 210 | -| Object | `.` _(binds to right)_ | 240 | -| Unary operators | unary `+`, `-`, `!` _(binds to right)_ | 255 | diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md deleted file mode 100644 index 5c27142a..00000000 --- a/doc/src/engine/custom-syntax.md +++ /dev/null @@ -1,404 +0,0 @@ -Extend Rhai with Custom Syntax -============================= - -{{#include ../links.md}} - - -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: - - -Don't Do It™ ------------- - -Stick with standard language syntax as much as possible. - -Having to learn Rhai is bad enough, no sane user would ever want to learn _yet_ another -obscure language syntax just to do something. - -Try to use [custom operators] first. Defining a custom syntax should be considered a _last resort_. - - -Where This Might Be Useful -------------------------- - -* Where an operation is used a _LOT_ and a custom syntax saves a lot of typing. - -* 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. - -* Where you just want to confuse your user and make their lives miserable, because you can. - - -Step One – Design The Syntax ---------------------------------- - -A custom syntax is simply a list of symbols. - -These symbol types can be used: - -* Standard [keywords]({{rootUrl}}/appendix/keywords.md) - -* Standard [operators]({{rootUrl}}/appendix/operators.md#operators). - -* Reserved [symbols]({{rootUrl}}/appendix/operators.md#symbols). - -* Identifiers following the [variable] naming rules. - -* `$expr$` – any valid expression, statement or statement block. - -* `$block$` – any valid statement block (i.e. must be enclosed by `'{'` .. `'}'`). - -* `$ident$` – any [variable] name. - -### The First Symbol Must be an Identifier - -There is no specific limit on the combination and sequencing of each symbol type, -except the _first_ symbol which must be a custom keyword that follows the naming rules -of [variables]. - -The first symbol also cannot be a normal or reserved [keyword]. -In other words, any valid identifier that is not a [keyword] will work fine. - -### The First Symbol Must be Unique - -Rhai uses the _first_ symbol as a clue to parse custom syntax. - -Therefore, at any one time, there can only be _one_ custom syntax starting with each unique symbol. - -Any new custom syntax definition using the same first symbol simply _overwrites_ the previous one. - -### Example - -```rust -exec $ident$ <- $expr$ : $block$ -``` - -The above syntax is made up of a stream of symbols: - -| Position | Input | Symbol | Description | -| :------: | :---: | :-------: | -------------------------------------------------------------------------------------------------------- | -| 1 | | `exec` | custom keyword | -| 2 | 1 | `$ident$` | a variable name | -| 3 | | `<-` | the left-arrow symbol (which is a [reserved symbol]({{rootUrl}}/appendix/operators.md#symbols) in Rhai). | -| 4 | 2 | `$expr$` | an expression, which may be enclosed with `{` .. `}`, or not. | -| 5 | | `:` | the colon symbol | -| 6 | 3 | `$block$` | a statement block, which must be enclosed with `{` .. `}`. | - -This syntax matches the following sample code and generates three inputs (one for each non-keyword): - -```rust -// Assuming the 'exec' custom syntax implementation declares the variable 'hello': -let x = exec hello <- foo(1, 2) : { - hello += bar(hello); - baz(hello); - }; - -print(x); // variable 'x' has a value returned by the custom syntax - -print(hello); // variable declared by a custom syntax persists! -``` - - -Step Two – Implementation ------------------------------- - -Any custom syntax must include an _implementation_ of it. - -### Function Signature - -The function signature of an implementation is: - -> `Fn(context: &mut EvalContext, inputs: &[Expression]) -> Result>` - -where: - -| Parameter | Type | Description | -| -------------------------- | :-------------------------------------: | ---------------------------------------------------------------------------------------------------------------------- | -| `context` | `&mut EvalContext` | mutable reference to the current evaluation _context_ | -| • `scope()` | `&Scope` | reference to the current [`Scope`] | -| • `scope_mut()` | `&mut &mut Scope` | mutable reference to the current [`Scope`]; variables can be added to/removed from it | -| • `engine()` | `&Engine` | reference to the current [`Engine`] | -| • `source()` | `Option<&str>` | reference to the current source, if any | -| • `iter_imports()` | `impl Iterator` | iterator of the current stack of [modules] imported via `import` statements | -| • `imports()` | `&Imports` | reference to the current stack of [modules] imported via `import` statements; requires the [`internals`] feature | -| • `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | -| • `namespaces()` | `&[&Module]` | reference to the namespaces (as [modules]) containing all script-defined functions; requires the [`internals`] feature | -| • `this_ptr()` | `Option<&Dynamic>` | reference to the current bound [`this`] pointer, if any | -| • `call_level()` | `usize` | the current nesting level of function calls | -| `inputs` | `&[Expression]` | a list of input expression trees | - -### Return Value - -Return value is the result of evaluating the custom syntax expression. - -### Access Arguments - -The most important argument is `inputs` where the matched identifiers (`$ident$`), expressions/statements (`$expr$`) -and statement blocks (`$block$`) are provided. - -To access a particular argument, use the following patterns: - -| Argument type | Pattern (`n` = slot in `inputs`) | Result type | Description | -| :-----------: | ---------------------------------------- | :----------: | ------------------ | -| `$ident$` | `inputs[n].get_variable_name().unwrap()` | `&str` | name of a variable | -| `$expr$` | `inputs.get(n).unwrap()` | `Expression` | an expression tree | -| `$block$` | `inputs.get(n).unwrap()` | `Expression` | an expression tree | - -### Evaluate an Expression Tree - -Use the `EvalContext::eval_expression_tree` method to evaluate an arbitrary expression tree -within the current evaluation context. - -```rust -let expression = inputs.get(0).unwrap(); -let result = context.eval_expression_tree(expression)?; -``` - -### Declare Variables - -New variables maybe declared (usually with a variable name that is passed in via `$ident$). - -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`] calls that change the list of must come _before_ any -`EvalContext::eval_expression_tree` calls. - -```rust -let var_name = inputs[0].get_variable_name().unwrap(); -let expression = inputs.get(1).unwrap(); - -context.scope_mut().push(var_name, 0 as INT); // do this BEFORE 'context.eval_expression_tree'! - -let result = context.eval_expression_tree(expression)?; -``` - - -Step Three – Register the Custom Syntax --------------------------------------------- - -Use `Engine::register_custom_syntax` to register a custom syntax. - -Again, beware that the _first_ symbol must be unique. If there already exists a custom syntax starting -with that symbol, the previous syntax will be overwritten. - -The syntax is passed simply as a slice of `&str`. - -```rust -// Custom syntax implementation -fn implementation_func( - context: &mut EvalContext, - inputs: &[Expression] -) -> Result> { - let var_name = inputs[0].get_variable_name().unwrap().to_string(); - let stmt = inputs.get(1).unwrap(); - let condition = inputs.get(2).unwrap(); - - // Push one new variable into the scope BEFORE 'context.eval_expression_tree' - context.scope_mut().push(var_name, 0 as INT); - - loop { - // Evaluate the statement block - context.eval_expression_tree(stmt)?; - - // Evaluate the condition expression - let stop = !context.eval_expression_tree(condition)? - .as_bool().map_err(|err| Box::new( - EvalAltResult::ErrorMismatchDataType( - "bool".to_string(), - err.to_string(), - condition.position(), - ) - ))?; - - if stop { - break; - } - } - - Ok(Dynamic::UNIT) -} - -// Register the custom syntax (sample): exec |x| -> { x += 1 } while x < 0 -engine.register_custom_syntax( - &[ "exec", "|", "$ident$", "|", "->", "$block$", "while", "$expr$" ], // the custom syntax - 1, // the number of new variables declared within this custom syntax - implementation_func -)?; -``` - -Remember that a custom syntax acts as an _expression_, so it can show up practically anywhere: - -```rust -// Use as an expression: -let foo = (exec |x| -> { x += 1 } while x < 0) * 100; - -// Use as a function call argument: -do_something(exec |x| -> { x += 1 } while x < 0, 24, true); - -// Use as a statement: -exec |x| -> { x += 1 } while x < 0; -// ^ terminate statement with ';' -``` - - -Step Four – Disable Unneeded Statement Types -------------------------------------------------- - -When a DSL needs a custom syntax, most likely than not it is extremely specialized. -Therefore, many statement types actually may not make sense under the same usage scenario. - -So, while at it, better [disable][disable keywords and operators] those built-in keywords -and operators that should not be used by the user. The would leave only the bare minimum -language surface exposed, together with the custom syntax that is tailor-designed for -the scenario. - -A keyword or operator that is disabled can still be used in a custom syntax. - -In an extreme case, it is possible to disable _every_ keyword in the language, leaving only -custom syntax (plus possibly expressions). But again, Don't Do It™ – unless you are certain -of what you're doing. - - -Step Five – Document -------------------------- - -For custom syntax, documentation is crucial. - -Make sure there are _lots_ of examples for users to follow. - - -Step Six – Profit! ------------------------- - - -Really Advanced – Custom Parsers -------------------------------------- - -Sometimes it is desirable to have multiple custom syntax starting with the -same symbol. This is especially common for _command-style_ syntax where the -second symbol calls a particular command: - -```rust -// The following simulates a command-style syntax, all starting with 'perform'. -perform hello world; // A fixed sequence of symbols -perform action 42; // Perform a system action with a parameter -perform update system; // Update the system -perform check all; // Check all system settings -perform cleanup; // Clean up the system -perform add something; // Add something to the system -perform remove something; // Delete something from the system -``` - -Alternatively, a custom syntax may have variable length, with a termination symbol: - -```rust -// The following is a variable-length list terminated by '>' -tags < "foo", "bar", 123, ... , x+y, true > -``` - -For even more flexibility in order to handle these advanced use cases, there is a -_low level_ API for custom syntax that allows the registration of an entire mini-parser. - -Use `Engine::register_custom_syntax_raw` to register a custom syntax _parser_ -together with the implementation function. - -### How Custom Parsers Work - -A custom parser takes as input parameters two pieces of information: - -* The symbols parsed so far; `$ident$` is replaced with the actual identifier parsed, - while `$expr$` and `$block$` stay as they were. - - The custom parser can inspect this symbols stream to determine the next symbol to parse. - -* The _look-ahead_ symbol, which is the symbol that will be parsed _next_. - - If the look-ahead is an expected symbol, the customer parser just returns it to continue parsing, - or it can return `$ident$` to parse it as an identifier, or even `$expr$` to start parsing - an expression. - - If the look-ahead is '`{`', then the custom parser may also return `$block$` to start parsing a - statements block. - - If the look-ahead is unexpected, the custom parser should then return the symbol expected - and Rhai will fail with a parse error containing information about the expected symbol. - -A custom parser always returns the _next_ symbol expected, which can also be `$ident$`, -`$expr$` or `$block$`, or `None` if parsing should terminate (_without_ reading the -look-ahead symbol). - - -### Example - -```rust -engine.register_custom_syntax_raw( - "perform", - // The custom parser implementation - always returns the next symbol expected - // 'look_ahead' is the next symbol about to be read - |symbols, look_ahead| match symbols.len() { - // perform ... - 1 => Ok(Some("$ident$".to_string())), - // perform command ... - 2 => match symbols[1].as_str() { - "action" => Ok(Some("$expr$".into())), - "hello" => Ok(Some("world".into())), - "update" | "check" | "add" | "remove" => Ok(Some("$ident$".into())), - "cleanup" => Ok(None), - cmd => Err(ParseError(Box::new(ParseErrorType::BadInput( - LexError::ImproperSymbol(format!("Improper command: {}", cmd)) - )), Position::NONE)), - }, - // perform command arg ... - 3 => match (symbols[1].as_str(), symbols[2].as_str()) { - ("action", _) => Ok(None), - ("hello", "world") => Ok(None), - ("update", arg) if arg == "system" => Ok(None), - ("update", arg) if arg == "client" => Ok(None), - ("check", arg) => Ok(None), - ("add", arg) => Ok(None), - ("remove", arg) => Ok(None), - (cmd, arg) => Err(ParseError(Box::new(ParseErrorType::BadInput( - LexError::ImproperSymbol( - format!("Invalid argument for command {}: {}", cmd, arg) - ) - )), Position::NONE)), - }, - _ => unreachable!(), - }, - // Number of new variables declared by this custom syntax - 0, - // Implementation function - implementation_func -); -``` - -### Function Signature - -The custom syntax parser has the following signature: - -> `Fn(symbols: &[ImmutableString], look_ahead: &str) -> Result, ParseError>` - -where: - -| Parameter | Type | Description | -| ------------ | :------------------: | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| `symbols` | `&[ImmutableString]` | a slice of symbols that have been parsed so far, possibly containing `$expr$` and/or `$block$`; `$ident$` is replaced by the actual identifier | -| `look_ahead` | `&str` | a string slice containing the next symbol that is about to be read | - -Most strings are [`ImmutableString`][string]'s so it is usually more efficient to just `clone` the appropriate one -(if any matches, or keep an internal cache for commonly-used symbols) as the return value. - -### Return Value - -The return value is `Result, ParseError>` where: - -| Value | Description | -| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Ok(None)` | parsing complete and there are no more symbols to match | -| `Ok(Some(symbol))` | the next symbol to match, which can also be `$expr$`, `$ident$` or `$block$` | -| `Err(ParseError)` | error that is reflected back to the [`Engine`] – normally `ParseError(ParseErrorType::BadInput(LexError::ImproperSymbol(message)), Position::NONE)` to indicate that there is a syntax error, but it can be any `ParseError`. | diff --git a/doc/src/engine/disable.md b/doc/src/engine/disable.md deleted file mode 100644 index 28e35f70..00000000 --- a/doc/src/engine/disable.md +++ /dev/null @@ -1,28 +0,0 @@ -Disable Certain Keywords and/or Operators -======================================== - -{{#include ../links.md}} - -For certain embedded usage, it is sometimes necessary to restrict the language to a strict subset of Rhai -to prevent usage of certain language features. - -Rhai supports surgically disabling a keyword or operator via the `Engine::disable_symbol` method. - -```rust -use rhai::Engine; - -let mut engine = Engine::new(); - -engine - .disable_symbol("if") // disable the 'if' keyword - .disable_symbol("+="); // disable the '+=' operator - -// The following all return parse errors. - -engine.compile("let x = if true { 42 } else { 0 };")?; -// ^ 'if' is rejected as a reserved keyword - -engine.compile("let x = 40 + 2; x += 1;")?; -// ^ '+=' is not recognized as an operator -// ^ other operators are not affected -``` diff --git a/doc/src/engine/dsl.md b/doc/src/engine/dsl.md deleted file mode 100644 index efb9ac81..00000000 --- a/doc/src/engine/dsl.md +++ /dev/null @@ -1,92 +0,0 @@ -Use Rhai as a Domain-Specific Language (DSL) -=========================================== - -{{#include ../links.md}} - -Rhai can be successfully used as a domain-specific language (DSL). - - -Expressions Only ----------------- - -In many DSL scenarios, only evaluation of expressions is needed. - -The [`Engine::eval_expression_XXX`][`eval_expression`] API can be used to restrict -a script to expressions only. - - -Unicode Standard Annex #31 Identifiers -------------------------------------- - -Variable names and other identifiers do not necessarily need to be ASCII-only. - -The [`unicode-xid-ident`] feature, when turned on, causes Rhai to allow variable names and identifiers -that follow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/). - -This is sometimes useful in a non-English DSL. - - -Disable Keywords and/or Operators --------------------------------- - -In some DSL scenarios, it is necessary to further restrict the language to exclude certain -language features that are not necessary or dangerous to the application. - -For example, a DSL may disable the `while` loop while keeping all other statement types intact. - -It is possible, in Rhai, to surgically [disable keywords and operators]. - - -Custom Operators ----------------- - -On the other hand, some DSL scenarios require special operators that make sense only for -that specific environment. In such cases, it is possible to define [custom operators] in Rhai. - -For example: - -```rust -let animal = "rabbit"; -let food = "carrot"; - -animal eats food // custom operator 'eats' - -eats(animal, food) // <- the above really de-sugars to this -``` - -Although a [custom operator] always de-sugars to a simple function call, -nevertheless it makes the DSL syntax much simpler and expressive. - - -Custom Syntax -------------- - -For advanced DSL scenarios, it is possible to define entire expression [_syntax_][custom syntax] &ndash -essentially custom statement types. - -For example, the following is a SQL-like syntax for some obscure DSL operation: - -```rust -let table = [..., ..., ..., ...]; - -// Syntax = calculate $ident$ ( $expr$ -> $ident$ ) => $ident$ : $expr$ -let total = calculate sum(table->price) => row : row.weight > 50; - -// Note: There is nothing special about those symbols; to make it look exactly like SQL: -// Syntax = SELECT $ident$ ( $ident$ ) AS $ident$ FROM $expr$ WHERE $expr$ -let total = SELECT sum(price) AS row FROM table WHERE row.weight > 50; -``` - -After registering this custom syntax with Rhai, it can be used anywhere inside a script as -a normal expression. - -For its evaluation, the callback function will receive the following list of inputs: - -* `inputs[0] = "sum"` - math operator -* `inputs[1] = "price"` - field name -* `inputs[2] = "row"` - loop variable name -* `inputs[3] = Expression(table)` - data source -* `inputs[4] = Expression(row.wright > 50)` - filter predicate - -Other identifiers, such as `"calculate"`, `"FROM"`, as well as symbols such as `->` and `:` etc., -are parsed in the order defined within the custom syntax. diff --git a/doc/src/engine/expressions.md b/doc/src/engine/expressions.md deleted file mode 100644 index cb3ea9b2..00000000 --- a/doc/src/engine/expressions.md +++ /dev/null @@ -1,27 +0,0 @@ -Evaluate Expressions Only -======================== - -{{#include ../links.md}} - -Sometimes a use case does not require a full-blown scripting _language_, but only needs to evaluate _expressions_. - -In these cases, use the `Engine::compile_expression` and `Engine::eval_expression` methods or their `_with_scope` variants. - -```rust -let result = engine.eval_expression::("2 + (10 + 10) * 2")?; -``` - -When evaluating _expressions_, no full-blown statement (e.g. `if`, `while`, `for`, `fn`) – not even variable assignment – -is supported and will be considered parse errors when encountered. - -[Closures] and [anonymous functions] are also not supported because in the background they compile to functions. - -```rust -// The following are all syntax errors because the script is not an expression. - -engine.eval_expression::<()>("x = 42")?; - -let ast = engine.compile_expression("let x = 42")?; - -let result = engine.eval_expression_with_scope::(&mut scope, "if x { 42 } else { 123 }")?; -``` diff --git a/doc/src/engine/func.md b/doc/src/engine/func.md deleted file mode 100644 index d0ce6784..00000000 --- a/doc/src/engine/func.md +++ /dev/null @@ -1,43 +0,0 @@ -Create a Rust Closure from a Rhai Function -========================================= - -{{#include ../links.md}} - -It is possible to further encapsulate a script in Rust such that it becomes a normal Rust function. - -Such a _closure_ is very useful as call-back functions. - -Creating them is accomplished via the `Func` trait which contains `create_from_script` -(as well as its companion method `create_from_ast`): - -```rust -use rhai::{Engine, Func}; // use 'Func' for 'create_from_script' - -let engine = Engine::new(); // create a new 'Engine' just for this - -let script = "fn calc(x, y) { x + y.len < 42 }"; - -// Func takes two type parameters: -// 1) a tuple made up of the types of the script function's parameters -// 2) the return type of the script function -// -// 'func' will have type Box Result>> and is callable! -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 -)?; - -func(123, "hello".to_string())? == false; // call the closure - -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(); -let ast = engine.compile(script)?; -schedule_callback(Box::new(move |x: i64, y: String| -> Result> { - engine.call_fn(&mut Scope::new(), &ast, "calc", (x, y)) -})); -``` diff --git a/doc/src/engine/hello-world.md b/doc/src/engine/hello-world.md deleted file mode 100644 index 241d282b..00000000 --- a/doc/src/engine/hello-world.md +++ /dev/null @@ -1,60 +0,0 @@ -Hello World in Rhai -=================== - -{{#include ../links.md}} - -To get going with Rhai is as simple as creating an instance of the scripting engine `rhai::Engine` via -`Engine::new`, then calling the `eval` method: - -```rust -use rhai::{Engine, EvalAltResult}; - -fn main() -> Result<(), Box> -{ - let engine = Engine::new(); - - let result = engine.eval::("40 + 2")?; - // ^^^^^^^ cast the result to an 'i64', this is required - - println!("Answer: {}", result); // prints 42 - - Ok(()) -} -``` - -Evaluate a script file directly: - -```rust -// 'eval_file' takes a 'PathBuf' -let result = engine.eval_file::("hello_world.rhai".into())?; -``` - - -Error Type ----------- - -`rhai::EvalAltResult` is the standard Rhai error type, which is a Rust `enum` containing all errors encountered -during the parsing or evaluation process. - - -Return Type ------------ - -The type parameter for `Engine::eval` is used to specify the type of the return value, -which _must_ match the actual type or an error is returned. Rhai is very strict here. - -There are two ways to specify the return type – _turbofish_ notation, or type inference. - -Use [`Dynamic`] for uncertain return types. - -```rust -let result = engine.eval::("40 + 2")?; // return type is i64, specified using 'turbofish' notation - -let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64 - -result.is::() == true; - -let result: Dynamic = engine.eval("boo()")?; // use 'Dynamic' if you're not sure what type it'll be! - -let result = engine.eval::("40 + 2")?; // returns an error because the actual return type is i64, not String -``` diff --git a/doc/src/engine/index.md b/doc/src/engine/index.md deleted file mode 100644 index 8bf58504..00000000 --- a/doc/src/engine/index.md +++ /dev/null @@ -1,8 +0,0 @@ -Using the Engine -================ - -{{#include ../links.md}} - -Rhai's interpreter resides in the [`Engine`] type under the master `rhai` namespace. - -This section shows how to set up, configure and use this scripting engine. diff --git a/doc/src/engine/metadata/export_to_json.md b/doc/src/engine/metadata/export_to_json.md deleted file mode 100644 index 13d2da72..00000000 --- a/doc/src/engine/metadata/export_to_json.md +++ /dev/null @@ -1,107 +0,0 @@ -Export Functions Metadata to JSON -================================ - -{{#include ../../links.md}} - - -`Engine::gen_fn_metadata_to_json`
`Engine::gen_fn_metadata_with_ast_to_json` ------------------------------------------------------------------------------- - -As part of a _reflections_ API, `Engine::gen_fn_metadata_to_json` and the corresponding -`Engine::gen_fn_metadata_with_ast_to_json` export the full list of [functions metadata] -in JSON format. - -The [`metadata`] feature must be used to turn on this API, which requires -the [`serde_json`](https://crates.io/crates/serde_json) crate. - -### Sources - -Functions from the following sources are included: - -1) Script-defined functions in an [`AST`] (for `Engine::gen_fn_metadata_with_ast_to_json`) -2) Native Rust functions registered into the global namespace via the `Engine::register_XXX` API -3) _Public_ (i.e. non-[`private`]) functions (native Rust or Rhai scripted) in static modules - registered via `Engine::register_static_module` -4) Native Rust functions in global modules registered via `Engine::register_global_module` (optional) - -Notice that if a function has been [overloaded][function overloading], only the overriding function's -metadata is included. - - -JSON Schema ------------ - -The JSON schema used to hold functions metadata is very simple, containing a nested structure of -`modules` and a list of `functions`. - -### Modules Schema - -```json -{ - "modules": - { - "sub_module_1": - { - "modules": - { - "sub_sub_module_A": - { - "functions": - [ - { ... function metadata ... }, - { ... function metadata ... }, - { ... function metadata ... }, - { ... function metadata ... }, - ... - ] - }, - "sub_sub_module_B": - { - ... - } - } - }, - "sub_module_2": - { - ... - }, - ... - }, - "functions": - [ - { ... function metadata ... }, - { ... function metadata ... }, - { ... function metadata ... }, - { ... function metadata ... }, - ... - ] -} -``` - -### Function Metadata Schema - -```json -{ - "namespace": "internal" | "global", - "access": "public" | "private", - "name": "fn_name", - "type": "native" | "script", - "numParams": 42, /* number of parameters */ - "params": /* omitted if no parameters */ - [ - { "name": "param_1", "type": "type_1" }, - { "name": "param_2" }, /* no type info */ - { "name": "_", "type": "type_3" }, - ... - ], - "returnType": "ret_type", /* omitted if unknown */ - "signature": "[private] fn_name(param_1: type_1, param_2, _: type_3) -> ret_type", - "docComments": /* omitted if none */ - [ - "/// doc-comment line 1", - "/// doc-comment line 2", - "/** doc-comment block */", - ... - ] -} -``` diff --git a/doc/src/engine/metadata/gen_fn_sig.md b/doc/src/engine/metadata/gen_fn_sig.md deleted file mode 100644 index fe1df178..00000000 --- a/doc/src/engine/metadata/gen_fn_sig.md +++ /dev/null @@ -1,91 +0,0 @@ -Generate Function Signatures -=========================== - -{{#include ../../links.md}} - - -`Engine::gen_fn_signatures` --------------------------- - -As part of a _reflections_ API, `Engine::gen_fn_signatures` returns a list of function _signatures_ -(as `Vec`), each corresponding to a particular function available to that [`Engine`] instance. - -> `fn_name ( param_1: type_1, param_2: type_2, ... , param_n : type_n ) -> return_type` - -### Sources - -Functions from the following sources are included, in order: - -1) Native Rust functions registered into the global namespace via the `Engine::register_XXX` API -2) _Public_ (i.e. non-[`private`]) functions (native Rust or Rhai scripted) in global sub-modules - registered via `Engine::register_static_module`. -3) Native Rust functions in global modules registered via `Engine::register_global_module` (optional) - - -Functions Metadata ------------------- - -Beware, however, that not all function signatures contain parameters and return value information. - -### `Engine::register_XXX` - -For instance, functions registered via `Engine::register_XXX` contain no information on -the names of parameter and their actual types because Rust simply does not make such metadata -available natively. The return type is also undetermined. - -A function registered under the name `foo` with three parameters and unknown return type: - -> `foo(_, _, _)` - -An operator function – again, unknown parameters and return type. -Notice that function names do not need to be valid identifiers. - -> `+(_, _)` - -A [property setter][getters/setters] – again, unknown parameters and return type. -Notice that function names do not need to be valid identifiers. -In this case, the first parameter should be `&mut T` of the custom type and the return value is `()`: - -> `set$prop(_, _, _)` - -### Script-Defined Functions - -Script-defined [function] signatures contain parameter names. Since all parameters, as well as -the return value, are [`Dynamic`] the types are simply not shown. - -A script-defined function always takes dynamic arguments, and the return type is also dynamic, -so no type information is needed: - -> `foo(x, y, z)` - -probably defined as: - -```rust -fn foo(x, y, z) { - ... -} -``` - -is the same as: - -> `foo(x: Dynamic, y: Dynamic, z: Dynamic) -> Result>` - -### Plugin Functions - -Functions defined in [plugin modules] are the best. They contain all the metadata -describing the functions. - -For example, a plugin function `merge`: - -> `merge(list: &mut MyStruct, num: usize, name: &str) -> Option` - -Notice that function names do not need to be valid identifiers. - -For example, an operator defined as a [fallible function] in a [plugin module] via -`#[rhai_fn(name="+=", return_raw)]` returns `Result>`: - -> `+=(list: &mut MyStruct, num: usize, name: &str) -> Result>` - -For example, a [property getter][getters/setters] defined in a [plugin module]: - -> `get$prop(obj: &mut MyStruct) -> String` diff --git a/doc/src/engine/metadata/index.md b/doc/src/engine/metadata/index.md deleted file mode 100644 index 227b1fcf..00000000 --- a/doc/src/engine/metadata/index.md +++ /dev/null @@ -1,28 +0,0 @@ -Functions Metadata -================== - -{{#include ../../links.md}} - -The _metadata_ of a [function] means all relevant information related to a function's -definition including: - -1. Its callable name - -2. Its access mode (public or [private][`private`]) - -3. Its parameters and types (if any) - -4. Its return value and type (if any) - -5. Its nature (i.e. native Rust-based or Rhai script-based) - -6. Its [namespace][function namespace] (module or global) - -7. Its purpose, in the form of [doc-comments] - -8. Usage notes, warnings, etc., in the form of [doc-comments] - -A function's _signature_ encapsulates the first four pieces of information in a single -concise line of definition: - -> `[private] fn_name ( param_1: type_1, param_2: type_2, ... , param_n : type_n ) -> return_type` diff --git a/doc/src/engine/optimize/disable.md b/doc/src/engine/optimize/disable.md deleted file mode 100644 index 8c02726c..00000000 --- a/doc/src/engine/optimize/disable.md +++ /dev/null @@ -1,24 +0,0 @@ -Turn Off Script Optimizations -============================ - -{{#include ../../links.md}} - -When scripts: - -* are known to be run only _once_, - -* are known to contain no dead code, - -* do not use constants in calculations - -the optimization pass may be a waste of time and resources. In that case, turn optimization off -by setting the optimization level to [`OptimizationLevel::None`]. - -Alternatively, turn off optimizations via the [`no_optimize`] feature. - -```rust -let engine = rhai::Engine::new(); - -// Turn off the optimizer -engine.set_optimization_level(rhai::OptimizationLevel::None); -``` diff --git a/doc/src/engine/optimize/eager.md b/doc/src/engine/optimize/eager.md deleted file mode 100644 index 2071154b..00000000 --- a/doc/src/engine/optimize/eager.md +++ /dev/null @@ -1,26 +0,0 @@ -Eager Function Evaluation When Using Full Optimization Level -========================================================== - -{{#include ../../links.md}} - -When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_ -and will _eagerly_ evaluated all function calls with constant arguments, using the result to replace the call. - -This also applies to all operators (which are implemented as functions). - -For instance, the same example above: - -```rust -// When compiling the following with OptimizationLevel::Full... - -const DECISION = 1; - // this condition is now eliminated because 'sign(DECISION) > 0' -if DECISION.sign() > 0 { // is a call to the 'sign' and '>' functions, and they return 'true' - print("hello!"); // this block is promoted to the parent level -} else { - print("boo!"); // this block is eliminated because it is never reached -} - -print("hello!"); // <- the above is equivalent to this - // ('print' and 'debug' are handled specially) -``` diff --git a/doc/src/engine/optimize/index.md b/doc/src/engine/optimize/index.md deleted file mode 100644 index 6852604b..00000000 --- a/doc/src/engine/optimize/index.md +++ /dev/null @@ -1,146 +0,0 @@ -Script Optimization -=================== - -{{#include ../../links.md}} - -Rhai includes an _optimizer_ that tries to optimize a script after parsing. -This can reduce resource utilization and increase execution speed. - -Script optimization can be turned off via the [`no_optimize`] feature. - - -Dead Code Removal ----------------- - -For example, in the following: - -```rust -{ - let x = 999; // NOT eliminated: variable may be used later on (perhaps even an 'eval') - 123; // eliminated: no effect - "hello"; // eliminated: no effect - [1, 2, x, x*2, 5]; // eliminated: no effect - foo(42); // NOT eliminated: the function 'foo' may have side-effects - 666 // NOT eliminated: this is the return value of the block, - // and the block is the last one so this is the return value of the whole script -} -``` - -Rhai attempts to eliminate _dead code_ (i.e. code that does nothing, for example an expression by itself as a statement, -which is allowed in Rhai). - -The above script optimizes to: - -```rust -{ - let x = 999; - foo(42); - 666 -} -``` - - -Constants Propagation --------------------- - -Constants propagation is used to remove dead code: - -```rust -const ABC = true; - -if ABC || some_work() { print("done!"); } // 'ABC' is constant so it is replaced by 'true'... - -if true || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called - -if true { print("done!"); } // <- the line above is equivalent to this - -print("done!"); // <- the line above is further simplified to this - // because the condition is always true -``` - -These are quite effective for template-based machine-generated scripts where certain constant values -are spliced into the script text in order to turn on/off certain sections. - -For fixed script texts, the constant values can be provided in a user-defined [`Scope`] object -to the [`Engine`] for use in compilation and evaluation. - -### Caveat - -If the [constants] are modified later on (yes, it is possible, via Rust functions), -the modified values will not show up in the optimized script. -Only the initialization values of [constants] are ever retained. - -This is almost never a problem because real-world scripts seldom modify a constant, -but the possibility is always there. - - -Eager Operator Evaluations -------------------------- - -Beware, however, that most operators are actually function calls, and those functions can be overridden, -so whether they are optimized away depends on the situation: - -* If the operands are not _constant_ values, it is not optimized. - -* If the operator is [overloaded][operator overloading], it is not optimized because the overloading function may not be _pure_ - (i.e. may cause side-effects when called). - -* If the operator is not _binary_, it is not optimized. Only binary operators are built-in to Rhai. - -* If the operands are not of the same type, it is not optimized. - -* If the operator is not _built-in_ (see list of [built-in operators]), it is not optimized. - -* If the operator is a binary built-in operator for a [standard type][standard types], it is called and replaced by a constant result. - -Rhai guarantees that no external function will be run (in order not to trigger side-effects) during the -optimization process (unless the optimization level is set to [`OptimizationLevel::Full`]). - -```rust -// The following is most likely generated by machine. - -const DECISION = 1; // this is an integer, one of the standard types - -if DECISION == 1 { // this is optimized into 'true' - : -} else if DECISION == 2 { // this is optimized into 'false' - : -} else if DECISION == 3 { // this is optimized into 'false' - : -} else { - : -} -``` - -Because of the eager evaluation of operators for [standard types], many constant expressions will be evaluated -and replaced by the result. - -```rust -let x = (1+2) * 3-4 / 5%6; // will be replaced by 'let x = 9' - -let y = (1 > 2) || (3 < =4); // will be replaced by 'let y = true' -``` - -For operators that are not optimized away due to one of the above reasons, the function calls -are simply left behind: - -```rust -// Assume 'new_state' returns some custom type that is NOT one of the standard types. -// Also assume that the '==; operator is defined for that custom type. -const DECISION_1 = new_state(1); -const DECISION_2 = new_state(2); -const DECISION_3 = new_state(3); - -if DECISION == 1 { // NOT optimized away because the operator is not built-in - : // and may cause side-effects if called! - : -} else if DECISION == 2 { // same here, NOT optimized away - : -} else if DECISION == 3 { // same here, NOT optimized away - : -} else { - : -} -``` - -Alternatively, turn the optimizer to [`OptimizationLevel::Full`]. diff --git a/doc/src/engine/optimize/optimize-levels.md b/doc/src/engine/optimize/optimize-levels.md deleted file mode 100644 index c9e3015f..00000000 --- a/doc/src/engine/optimize/optimize-levels.md +++ /dev/null @@ -1,34 +0,0 @@ -Optimization Levels -================== - -{{#include ../../links.md}} - -There are three levels of optimization: `None`, `Simple` and `Full`. - -* `None` is obvious – no optimization on the AST is performed. - -* `Simple` (default) performs only relatively _safe_ optimizations without causing side-effects - (i.e. it only relies on static analysis and [built-in operators] for constant [standard types], - and will not perform any external function calls). - - However, it is important to bear in mind that _constants propagation_ is performed with the - caveat that, if [constants] are modified later on (yes, it is possible, via Rust functions), - the modified values will not show up in the optimized script. Only the initialization values - of [constants] are ever retained. - - Furthermore, overriding a [built-in operator][built-in operators] in the [`Engine`] afterwards - has no effect after the optimizer replaces an expression with its calculated value. - -* `Full` is _much_ more aggressive, _including_ calling external functions on constant arguments to determine their result. - One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators. - - -Set Optimization Level ---------------------- - -An [`Engine`]'s optimization level is set via a call to `Engine::set_optimization_level`: - -```rust -// Turn on aggressive optimizations -engine.set_optimization_level(rhai::OptimizationLevel::Full); -``` diff --git a/doc/src/engine/optimize/reoptimize.md b/doc/src/engine/optimize/reoptimize.md deleted file mode 100644 index 38726649..00000000 --- a/doc/src/engine/optimize/reoptimize.md +++ /dev/null @@ -1,38 +0,0 @@ -Re-Optimize an AST -================== - -{{#include ../../links.md}} - -Sometimes it is more efficient to store one single, large script with delimited code blocks guarded by -constant variables. This script is compiled once to an [`AST`]. - -Then, depending on the execution environment, constants are passed into the [`Engine`] and the [`AST`] -is _re_-optimized based on those constants via the `Engine::optimize_ast` method, -effectively pruning out unused code sections. - -The final, optimized [`AST`] is then used for evaluations. - -```rust -// Compile master script to AST -let master_ast = engine.compile( -r" - if SCENARIO == 1 { - do_work(); - } else if SCENARIO == 2 { - do_something(); - } else if SCENARIO == 3 { - do_something_else(); - } else { - do_nothing(); - } -")?; - -// Create a new 'Scope' - put constants in it to aid optimization -let mut scope = Scope::new(); -scope.push_constant("SCENARIO", 1_i64); - -// Re-optimize the AST -let new_ast = engine.optimize_ast(&scope, master_ast.clone(), OptimizationLevel::Simple); - -// 'new_ast' is essentially: 'do_work()' -``` diff --git a/doc/src/engine/optimize/semantics.md b/doc/src/engine/optimize/semantics.md deleted file mode 100644 index 86aa51b9..00000000 --- a/doc/src/engine/optimize/semantics.md +++ /dev/null @@ -1,43 +0,0 @@ -Subtle Semantic Changes After Optimization -========================================= - -{{#include ../../links.md}} - -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 -} - -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, -so the script would have been terminated at that point with an error return. - -In fact, any errors inside a statement that has been eliminated will silently _disappear_: - -```rust -print("start!"); -if my_decision { /* do nothing... */ } // eliminated due to no effect -print("end!"); - -// The above optimizes to: - -print("start!"); -print("end!"); -``` - -In the script above, if `my_decision` holds anything other than a boolean value, -the script should have been terminated due to a type error. - -However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces -no side-effects), thus the script silently runs to completion without errors. - -It is usually a _Very Bad Idea™_ to depend on a script failing or such kind of subtleties, but if it turns out to be necessary -(why? I would never guess), turn script optimization off by setting the optimization level to [`OptimizationLevel::None`]. diff --git a/doc/src/engine/optimize/side-effects.md b/doc/src/engine/optimize/side-effects.md deleted file mode 100644 index fbacea0b..00000000 --- a/doc/src/engine/optimize/side-effects.md +++ /dev/null @@ -1,22 +0,0 @@ -Side-Effect Considerations for Full Optimization Level -==================================================== - -{{#include ../../links.md}} - -All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_ -(i.e. they do not mutate state nor cause any side-effects, with the exception of `print` and `debug` -which are handled specially) so using [`OptimizationLevel::Full`] is usually quite safe _unless_ -custom types and functions are registered. - -If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie -within a pruned code block). - -If custom functions are registered to overload built-in operators, they will also be called when -the operators are used (in an `if` statement, for example) causing side-effects. - -Therefore, the rule-of-thumb is: - -* _Always_ register custom types and functions _after_ compiling scripts if [`OptimizationLevel::Full`] is used. - -* _DO NOT_ depend on knowledge that the functions have no side-effects, because those functions can change later on and, - when that happens, existing scripts may break in subtle ways. diff --git a/doc/src/engine/optimize/volatility.md b/doc/src/engine/optimize/volatility.md deleted file mode 100644 index dcd1f487..00000000 --- a/doc/src/engine/optimize/volatility.md +++ /dev/null @@ -1,16 +0,0 @@ -Volatility Considerations for Full Optimization Level -=================================================== - -{{#include ../../links.md}} - -Even if a custom function does not mutate state nor cause side-effects, it may still be _volatile_, -i.e. it _depends_ on the external environment and is not _pure_. - -A perfect example is a function that gets the current time – obviously each run will return a different value! - -The optimizer, when using [`OptimizationLevel::Full`], will _merrily assume_ that all functions are _pure_, -so when it finds constant arguments (or none) it eagerly executes the function call and replaces it with the result. - -This causes the script to behave differently from the intended semantics. - -Therefore, **avoid using [`OptimizationLevel::Full`]** if non-_pure_ custom types and/or functions are involved. diff --git a/doc/src/engine/options.md b/doc/src/engine/options.md deleted file mode 100644 index 345a4b4c..00000000 --- a/doc/src/engine/options.md +++ /dev/null @@ -1,19 +0,0 @@ -Engine Configuration Options -=========================== - -{{#include ../links.md}} - -A number of other configuration options are available from the `Engine` to fine-tune behavior and safeguards. - -| Method | Not available under | Description | -| ------------------------ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -| `set_doc_comments` | | enables/disables [doc-comments] | -| `set_optimization_level` | [`no_optimize`] | sets the amount of script _optimizations_ performedSee [script optimization] | -| `set_max_expr_depths` | [`unchecked`] | sets the maximum nesting levels of an expression/statementSee [maximum statement depth] | -| `set_max_call_levels` | [`unchecked`] | sets the maximum number of function call levels (default 50) to avoid infinite recursionSee [maximum call stack depth] | -| `set_max_operations` | [`unchecked`] | sets the maximum number of _operations_ that a script is allowed to consumeSee [maximum number of operations] | -| `set_max_modules` | [`unchecked`] | sets the maximum number of [modules] that a script is allowed to loadSee [maximum number of modules] | -| `set_max_string_size` | [`unchecked`] | sets the maximum length (in UTF-8 bytes) for [strings]See [maximum length of strings] | -| `set_max_array_size` | [`unchecked`], [`no_index`] | sets the maximum size for [arrays]See [maximum size of arrays] | -| `set_max_map_size` | [`unchecked`], [`no_object`] | sets the maximum number of properties for [object maps]See [maximum size of object maps] | -| `disable_symbol` | | disables a certain keyword or operatorSee [disable keywords and operators] | diff --git a/doc/src/engine/raw.md b/doc/src/engine/raw.md deleted file mode 100644 index 017814a5..00000000 --- a/doc/src/engine/raw.md +++ /dev/null @@ -1,28 +0,0 @@ -Raw `Engine` -=========== - -{{#include ../links.md}} - -`Engine::new` creates a scripting [`Engine`] with common functionalities (e.g. printing to the console via `print`). - -In many controlled embedded environments, however, these may not be needed and unnecessarily occupy -application code storage space. - -Use `Engine::new_raw` to create a _raw_ `Engine`, in which only a minimal set of -basic arithmetic and logical operators are supported (see below). - -To add more functionalities to a _raw_ `Engine`, load [packages] into it. - - -Built-in Operators ------------------- - -| Operators | Assignment operators | Supported for types
(see [standard types]) | -| ------------------------- | ---------------------------- | ----------------------------------------------------------------------------- | -| `+`, | `+=` | `INT`, `FLOAT` (if not [`no_float`]), `char`, `ImmutableString` | -| `-`, `*`, `/`, `%`, `~`, | `-=`, `*=`, `/=`, `%=`, `~=` | `INT`, `FLOAT` (if not [`no_float`]) | -| `<<`, `>>` | `<<=`, `>>=` | `INT` | -| `&`, \|, `^` | `&=`, \|=, `^=` | `INT`, `bool` | -| `&&`, \|\| | | `bool` | -| `==`, `!=` | | `INT`, `FLOAT` (if not [`no_float`]), `bool`, `char`, `()`, `ImmutableString` | -| `>`, `>=`, `<`, `<=` | | `INT`, `FLOAT` (if not [`no_float`]), `char`, `()`, `ImmutableString` | diff --git a/doc/src/engine/scope.md b/doc/src/engine/scope.md deleted file mode 100644 index 067fe077..00000000 --- a/doc/src/engine/scope.md +++ /dev/null @@ -1,55 +0,0 @@ -`Scope` – Initializing and Maintaining State -================================================= - -{{#include ../links.md}} - -By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined -but no global state. This gives each evaluation a clean starting slate. - -In order to continue using the same global state from one invocation to the next, -such a state must be manually created and passed in. - -All `Scope` variables are [`Dynamic`], meaning they can store values of any type. - -Under [`sync`], however, only types that are `Send + Sync` are supported, and the entire `Scope` itself -will also be `Send + Sync`. This is extremely useful in multi-threaded applications. - -In this example, a global state object (a `Scope`) is created with a few initialized variables, -then the same state is threaded through multiple invocations: - -```rust -use rhai::{Engine, Scope, EvalAltResult}; - -let engine = Engine::new(); - -// First create the state -let mut scope = Scope::new(); - -// Then push (i.e. add) some initialized variables into the state. -// Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64. -// Better stick to them or it gets hard working with the script. -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' - -// First invocation -engine.eval_with_scope::<()>(&mut scope, r" - 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 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); - -// We can modify scope variables directly with 'set_value' -scope.set_value("y", 42_i64); -assert_eq!(scope.get_value::("y").expect("variable y should exist"), 42); -``` diff --git a/doc/src/engine/var.md b/doc/src/engine/var.md deleted file mode 100644 index c0fb5579..00000000 --- a/doc/src/engine/var.md +++ /dev/null @@ -1,93 +0,0 @@ -Variable Resolver -================= - -{{#include ../links.md}} - -By default, Rhai looks up access to variables from the enclosing block scope, -working its way outwards until it reaches the top (global) level, then it -searches the [`Scope`] that is passed into the `Engine::eval` call. - -There is a built-in facility for advanced users to _hook_ into the variable -resolution service and to override its default behavior. - -To do so, provide a closure to the [`Engine`] via the `Engine::on_var` method: - -```rust -let mut engine = Engine::new(); - -// Register a variable resolver. -engine.on_var(|name, index, context| { - match name { - "MYSTIC_NUMBER" => Ok(Some((42 as INT).into())), - // Override a variable - make it not found even if it exists! - "DO_NOT_USE" => Err(Box::new( - EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::NONE) - )), - // Silently maps 'chameleon' into 'innocent'. - "chameleon" => context.scope().get_value("innocent").map(Some).ok_or_else(|| Box::new( - EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::NONE) - )), - // Return Ok(None) to continue with the normal variable resolution process. - _ => Ok(None) - } -}); -``` - - -Returned Values are Constants ----------------------------- - -Variable values, if any returned, are treated as _constants_ by the script and cannot be assigned to. -This is to avoid needing a mutable reference to the underlying data provider which may not be possible to obtain. - -In order to change these variables, it is best to push them into a custom [`Scope`] instead of using -a variable resolver. Then these variables can be assigned to and their updated values read back after -the script is evaluated. - - -Benefits of Using a Variable Resolver ------------------------------------- - -1. Avoid having to maintain a custom [`Scope`] with all variables regardless of need (because a script may not use them all). - -2. _Short-circuit_ variable access, essentially overriding standard behavior. - -3. _Lazy-load_ variables when they are accessed, not up-front. This benefits when the number of variables is very large, when they are timing-dependent, or when they are expensive to load. - -4. Rename system variables on a script-by-script basis without having to construct different [`Scope`]'s. - - -Function Signature ------------------- - -The function signature passed to `Engine::on_var` takes the following form: - -> `Fn(name: &str, index: usize, context: &EvalContext)` -> `-> Result, Box> + 'static` - -where: - -| Parameter | Type | Description | -| -------------------------- | :-------------------------------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | `&str` | variable name | -| `index` | `usize` | an offset from the bottom of the current [`Scope`] that the variable is supposed to reside.
Offsets start from 1, with 1 meaning the last variable in the current [`Scope`]. Essentially the correct variable is at position `scope.len() - index`.
If `index` is zero, then there is no pre-calculated offset position and a search through the current [`Scope`] must be performed. | -| `context` | `&EvalContext` | reference to the current evaluation _context_ | -| • `scope()` | `&Scope` | reference to the current [`Scope`] | -| • `engine()` | `&Engine` | reference to the current [`Engine`] | -| • `source()` | `Option<&str>` | reference to the current source, if any | -| • `iter_imports()` | `impl Iterator` | iterator of the current stack of [modules] imported via `import` statements | -| • `imports()` | `&Imports` | reference to the current stack of [modules] imported via `import` statements; requires the [`internals`] feature | -| • `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | -| • `namespaces()` | `&[&Module]` | reference to the namespaces (as [modules]) containing all script-defined functions; requires the [`internals`] feature | -| • `this_ptr()` | `Option<&Dynamic>` | reference to the current bound [`this`] pointer, if any | -| • `call_level()` | `usize` | the current nesting level of function calls | - -### Return Value - -The return value is `Result, Box>` where: - -| Value | Description | -| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Ok(None)` | normal variable resolution process should continue, i.e. continue searching through the [`Scope`] | -| `Ok(Some(Dynamic))` | value of the variable, treated as a constant | -| `Err(Box)` | error that is reflected back to the [`Engine`].
Normally this is `EvalAltResult::ErrorVariableNotFound(var_name, Position::NONE)` to indicate that the variable does not exist, but it can be any `EvalAltResult`. | diff --git a/doc/src/images/logo/favicon.png b/doc/src/images/logo/favicon.png deleted file mode 100644 index f5cf9cac..00000000 Binary files a/doc/src/images/logo/favicon.png and /dev/null differ diff --git a/doc/src/images/logo/favicon.svg b/doc/src/images/logo/favicon.svg deleted file mode 100644 index 16aeb468..00000000 --- a/doc/src/images/logo/favicon.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/doc/src/images/logo/rhai-banner-transparent-colour.png b/doc/src/images/logo/rhai-banner-transparent-colour.png deleted file mode 100644 index 8d478bdc..00000000 Binary files a/doc/src/images/logo/rhai-banner-transparent-colour.png and /dev/null differ diff --git a/doc/src/images/logo/rhai-banner-transparent-colour.svg b/doc/src/images/logo/rhai-banner-transparent-colour.svg deleted file mode 100644 index 57ee1693..00000000 --- a/doc/src/images/logo/rhai-banner-transparent-colour.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/src/images/logo/rhai-colour-black.png b/doc/src/images/logo/rhai-colour-black.png deleted file mode 100644 index b874a27e..00000000 Binary files a/doc/src/images/logo/rhai-colour-black.png and /dev/null differ diff --git a/doc/src/images/logo/rhai-colour-black.svg b/doc/src/images/logo/rhai-colour-black.svg deleted file mode 100644 index 2a2469f1..00000000 --- a/doc/src/images/logo/rhai-colour-black.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/src/images/logo/rhai-colour-white.png b/doc/src/images/logo/rhai-colour-white.png deleted file mode 100644 index db83cee0..00000000 Binary files a/doc/src/images/logo/rhai-colour-white.png and /dev/null differ diff --git a/doc/src/images/logo/rhai-colour-white.svg b/doc/src/images/logo/rhai-colour-white.svg deleted file mode 100644 index 9008b4dd..00000000 --- a/doc/src/images/logo/rhai-colour-white.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/src/images/logo/rhai-icon-colour-black.png b/doc/src/images/logo/rhai-icon-colour-black.png deleted file mode 100644 index 55c7d714..00000000 Binary files a/doc/src/images/logo/rhai-icon-colour-black.png and /dev/null differ diff --git a/doc/src/images/logo/rhai-icon-colour-black.svg b/doc/src/images/logo/rhai-icon-colour-black.svg deleted file mode 100644 index 05db75f0..00000000 --- a/doc/src/images/logo/rhai-icon-colour-black.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/src/images/logo/rhai-icon-colour-white.png b/doc/src/images/logo/rhai-icon-colour-white.png deleted file mode 100644 index 4af8351f..00000000 Binary files a/doc/src/images/logo/rhai-icon-colour-white.png and /dev/null differ diff --git a/doc/src/images/logo/rhai-icon-colour-white.svg b/doc/src/images/logo/rhai-icon-colour-white.svg deleted file mode 100644 index fde472a9..00000000 --- a/doc/src/images/logo/rhai-icon-colour-white.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/src/images/logo/rhai-icon-transparent-black.png b/doc/src/images/logo/rhai-icon-transparent-black.png deleted file mode 100644 index c1d0c712..00000000 Binary files a/doc/src/images/logo/rhai-icon-transparent-black.png and /dev/null differ diff --git a/doc/src/images/logo/rhai-icon-transparent-black.svg b/doc/src/images/logo/rhai-icon-transparent-black.svg deleted file mode 100644 index f02f7a47..00000000 --- a/doc/src/images/logo/rhai-icon-transparent-black.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/doc/src/images/logo/rhai-icon-transparent-colour.png b/doc/src/images/logo/rhai-icon-transparent-colour.png deleted file mode 100644 index c7a54fc4..00000000 Binary files a/doc/src/images/logo/rhai-icon-transparent-colour.png and /dev/null differ diff --git a/doc/src/images/logo/rhai-icon-transparent-colour.svg b/doc/src/images/logo/rhai-icon-transparent-colour.svg deleted file mode 100644 index a8408964..00000000 --- a/doc/src/images/logo/rhai-icon-transparent-colour.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/doc/src/images/logo/rhai-icon-transparent-white.png b/doc/src/images/logo/rhai-icon-transparent-white.png deleted file mode 100644 index cd63ff1e..00000000 Binary files a/doc/src/images/logo/rhai-icon-transparent-white.png and /dev/null differ diff --git a/doc/src/images/logo/rhai-icon-transparent-white.svg b/doc/src/images/logo/rhai-icon-transparent-white.svg deleted file mode 100644 index 6648a7ae..00000000 --- a/doc/src/images/logo/rhai-icon-transparent-white.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/doc/src/images/logo/rhai-logo-transparent-colour-black.png b/doc/src/images/logo/rhai-logo-transparent-colour-black.png deleted file mode 100644 index 9d2f9efb..00000000 Binary files a/doc/src/images/logo/rhai-logo-transparent-colour-black.png and /dev/null differ diff --git a/doc/src/images/logo/rhai-logo-transparent-colour-black.svg b/doc/src/images/logo/rhai-logo-transparent-colour-black.svg deleted file mode 100644 index f7e36f24..00000000 --- a/doc/src/images/logo/rhai-logo-transparent-colour-black.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/src/images/logo/rhai-logo-transparent-colour-white.png b/doc/src/images/logo/rhai-logo-transparent-colour-white.png deleted file mode 100644 index 028f6ad6..00000000 Binary files a/doc/src/images/logo/rhai-logo-transparent-colour-white.png and /dev/null differ diff --git a/doc/src/images/logo/rhai-logo-transparent-colour-white.svg b/doc/src/images/logo/rhai-logo-transparent-colour-white.svg deleted file mode 100644 index 3055938d..00000000 --- a/doc/src/images/logo/rhai-logo-transparent-colour-white.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/src/images/logo/rhai-logo-transparent-sil-black.png b/doc/src/images/logo/rhai-logo-transparent-sil-black.png deleted file mode 100644 index 97b7d122..00000000 Binary files a/doc/src/images/logo/rhai-logo-transparent-sil-black.png and /dev/null differ diff --git a/doc/src/images/logo/rhai-logo-transparent-sil-black.svg b/doc/src/images/logo/rhai-logo-transparent-sil-black.svg deleted file mode 100644 index 565c24ba..00000000 --- a/doc/src/images/logo/rhai-logo-transparent-sil-black.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/src/images/logo/rhai-logo-transparent-sil-white.png b/doc/src/images/logo/rhai-logo-transparent-sil-white.png deleted file mode 100644 index 99049d18..00000000 Binary files a/doc/src/images/logo/rhai-logo-transparent-sil-white.png and /dev/null differ diff --git a/doc/src/images/logo/rhai-logo-transparent-sil-white.svg b/doc/src/images/logo/rhai-logo-transparent-sil-white.svg deleted file mode 100644 index 9eabc0d7..00000000 --- a/doc/src/images/logo/rhai-logo-transparent-sil-white.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/src/images/logo/rhai-sil-black.png b/doc/src/images/logo/rhai-sil-black.png deleted file mode 100644 index 92b0b7e6..00000000 Binary files a/doc/src/images/logo/rhai-sil-black.png and /dev/null differ diff --git a/doc/src/images/logo/rhai-sil-black.svg b/doc/src/images/logo/rhai-sil-black.svg deleted file mode 100644 index 8d4c9b32..00000000 --- a/doc/src/images/logo/rhai-sil-black.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/src/images/logo/rhai-sil-white.png b/doc/src/images/logo/rhai-sil-white.png deleted file mode 100644 index c1466588..00000000 Binary files a/doc/src/images/logo/rhai-sil-white.png and /dev/null differ diff --git a/doc/src/images/logo/rhai-sil-white.svg b/doc/src/images/logo/rhai-sil-white.svg deleted file mode 100644 index bdb09519..00000000 --- a/doc/src/images/logo/rhai-sil-white.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/src/language/arrays.md b/doc/src/language/arrays.md deleted file mode 100644 index 492800b9..00000000 --- a/doc/src/language/arrays.md +++ /dev/null @@ -1,218 +0,0 @@ -Arrays -====== - -{{#include ../links.md}} - -Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices: - -> _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. - -The Rust type of a Rhai array is `rhai::Array`. - -[`type_of()`] an array returns `"array"`. - -Arrays are disabled via the [`no_index`] feature. - -The maximum allowed size of an array can be controlled via `Engine::set_max_array_size` -(see [maximum size of arrays]. - - -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 | -| `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 | -| `==` operator | 1) first array
2) second array | are the two arrays the same (elements compared with the `==` operator, if defined)? | -| `!=` operator | 1) first array
2) second array | are the two arrays different (elements compared with the `==` operator, if defined)? | -| `in` operator | item to find | does the array contain the item (compared with the `==` operator, if defined)? | -| `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) | -| `extract` | 1) start position, beginning if < 0, end if > length
2) _(optional)_ number of items to extract, none if < 0 | extracts a portion of the array into a new array | -| `remove` | index | removes an element at a particular index and returns it ([`()`] 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) | -| `chop` | target length | cuts off the head of the array, leaving the tail at exactly a specified length | -| `drain` | 1) [function pointer] to predicate (usually a [closure])
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | removes all items (returning them) that return `true` when called with the predicate function:
1st parameter: array item
2nd parameter: _(optional)_ offset index | -| `drain` | 1) start position, beginning if < 0, end if > length
2) number of items to remove, none if < 0 | removes a portion of the array, returning the removed items (not in original order) | -| `retain` | 1) [function pointer] to predicate (usually a [closure])
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | removes all items (returning them) that do not return `true` when called with the predicate function:
1st parameter: array item
2nd parameter: _(optional)_ offset index | -| `retain` | 1) start position, beginning if < 0, end if > length
2) number of items to retain, none if < 0 | retains a portion of the array, removes all other items and returning them (not in original order) | -| `splice` | 1) start position, beginning if < 0, end if > length
2) number of items to remove, none if < 0
3) array to insert | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) | -| `filter` | [function pointer] to predicate (usually a [closure]) | constructs a new array with all items that return `true` when called with the predicate function:
1st parameter: array item
2nd parameter: _(optional)_ offset index | -| `index_of` | [function pointer] to predicate (usually a [closure]) | returns the index of the first item in the array that returns `true` when called with the predicate function, or -1 if not found:
1st parameter: array item
2nd parameter: _(optional)_ offset index | -| `map` | [function pointer] to conversion function (usually a [closure]) | constructs a new array with all items mapped to the result of applying the conversion function:
1st parameter: array item
2nd parameter: _(optional)_ offset index | -| `reduce` | 1) [function pointer] to accumulator function (usually a [closure])
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | reduces the array into a single value via the accumulator function:
1st parameter: accumulated value ([`()`] initially)
2nd parameter: array item
3rd parameter: _(optional)_ offset index | -| `reduce_rev` | 1) [function pointer] to accumulator function (usually a [closure])
2) _(optional)_ [function pointer] to function (usually a [closure]) that provides the initial value | reduces the array (in reverse order) into a single value via the accumulator function:
1st parameter: accumulated value ([`()`] initially)
2nd parameter: array item
3rd parameter: _(optional)_ offset index | -| `some` | [function pointer] to predicate (usually a [closure]) | returns `true` if any item returns `true` when called with the predicate function:
1st parameter: array item
2nd parameter: _(optional)_ offset index | -| `all` | [function pointer] to predicate (usually a [closure]) | returns `true` if all items return `true` when called with the predicate function:
1st parameter: array item
2nd parameter: _(optional)_ offset index | -| `sort` | [function pointer] to a comparison function (usually a [closure]) | sorts the array with a comparison function:
1st parameter: first item
2nd parameter: second item
return value: `INT` < 0 if first < second, > 0 if first > second, 0 if first == second | - - -Use Custom Types With Arrays ---------------------------- - -To use a [custom type] with arrays, a number of array functions need to be manually implemented, -in particular `push`, `insert`, `pad` and the `+=` operator. In addition, the `==` operator must be -implemented for the [custom type] in order to support the `in` operator which uses `==` to -compare elements. - -See the section on [custom types] for more details. - - -Examples --------- - -```rust -let y = [2, 3]; // y == [2, 3] - -let y = [2, 3,]; // y == [2, 3] - -y.insert(0, 1); // y == [1, 2, 3] - -y.insert(999, 4); // y == [1, 2, 3, 4] - -y.len == 4; - -y[0] == 1; -y[1] == 2; -y[2] == 3; -y[3] == 4; - -(1 in y) == true; // use 'in' to test if an item exists in the array -(42 in y) == false; // 'in' uses the '==' operator (which users can override) - // to check if the target item exists in the array - -y[1] = 42; // y == [1, 42, 3, 4] - -(42 in y) == true; - -y.remove(2) == 3; // y == [1, 42, 4] - -y.len == 3; - -y[2] == 4; // elements after the removed element are shifted - -ts.list = y; // arrays can be assigned completely (by value copy) - -ts.list[1] == 42; - -[1, 2, 3][0] == 1; // indexing on array literal - -fn abc() { - [42, 43, 44] // a function returning an array -} - -abc()[0] == 42; - -y.push(4); // y == [1, 42, 4, 4] - -y += 5; // y == [1, 42, 4, 4, 5] - -y.len == 5; - -y.shift() == 1; // y == [42, 4, 4, 5] - -y.chop(3); // y == [4, 4, 5] - -y.len == 3; - -y.pop() == 5; // y == [4, 4] - -y.len == 2; - -for item in y { // arrays can be iterated with a 'for' statement - print(item); -} - -y.pad(6, "hello"); // y == [4, 4, "hello", "hello", "hello", "hello"] - -y.len == 6; - -y.truncate(4); // y == [4, 4, "hello", "hello"] - -y.len == 4; - -y.clear(); // y == [] - -y.len == 0; - -let a = [42, 123, 99]; - -a.map(|v| v + 1); // returns [43, 124, 100] - -a.map(|v, i| v + i); // returns [42, 124, 101] - -a.filter(|v| v > 50); // returns [123, 99] - -a.filter(|v, i| i == 1); // returns [123] - -// Use a closure to provide the initial value -a.reduce(|sum, v| sum + v, || 0) == 264; - -// Detect the initial value of '()' -a.reduce( - |sum, v| if sum.type_of() == "()" { v } else { sum + v } -) == 264; - -// Detect the initial value via index -a.reduce(|sum, v, i| - if i == 0 { v } else { sum + v } -) == 264; - -// Use a closure to provide the initial value -a.reduce_rev(|sum, v| sum + v, || 0) == 264; - -// Detect the initial value of '()' -a.reduce_rev( - |sum, v| if sum.type_of() == "()" { v } else { sum + v } -) == 264; - -// Detect the initial value via index -a.reduce_rev(|sum, v, i| - if i == 2 { v } else { sum + v } -) == 264; - -a.some(|v| v > 50); // returns true - -a.some(|v, i| v < i); // returns false - -a.none(|v| v != 0); // returns false - -a.none(|v, i| v == i); // returns true - -a.all(|v| v > 50); // returns false - -a.all(|v, i| v > i); // returns true - -a.splice(1, 1, [1, 3, 2]); // a == [42, 1, 3, 2, 99] - -a.extract(1, 3); // returns [1, 3, 2] - -a.sort(|x, y| x - y); // a == [1, 2, 3, 42, 99] - -a.drain(|v| v <= 1); // a == [2, 3, 42, 99] - -a.drain(|v, i| i >= 3); // a == [2, 3, 42] - -a.retain(|v| v > 10); // a == [42] - -a.retain(|v, i| i > 0); // a == [] -``` diff --git a/doc/src/language/assignment-op.md b/doc/src/language/assignment-op.md deleted file mode 100644 index 5cfd73c5..00000000 --- a/doc/src/language/assignment-op.md +++ /dev/null @@ -1,66 +0,0 @@ -Compound Assignment Operators -============================= - -{{#include ../links.md}} - - -```rust -let number = 9; - -number += 8; // number = number + 8 - -number -= 7; // number = number - 7 - -number *= 6; // number = number * 6 - -number /= 5; // number = number / 5 - -number %= 4; // number = number % 4 - -number ~= 3; // number = number ~ 3 - -number <<= 2; // number = number << 2 - -number >>= 1; // number = number >> 1 - -number &= 0x00ff; // number = number & 0x00ff; - -number |= 0x00ff; // number = number | 0x00ff; - -number ^= 0x00ff; // number = number ^ 0x00ff; -``` - - -The Flexible `+=` ----------------- - -The the `+` and `+=` operators are often [overloaded][function overloading] to perform -build-up operations for different data types. - -For example, it is used to build [strings]: - -```rust -let my_str = "abc"; -my_str += "ABC"; -my_str += 12345; - -my_str == "abcABC12345" -``` - -to concatenate [arrays]: - -```rust -let my_array = [1, 2, 3]; -my_array += [4, 5]; - -my_array == [1, 2, 3, 4, 5]; -``` - -and mix two [object maps] together: - -```rust -let my_obj = #{a:1, b:2}; -my_obj += #{c:3, d:4, e:5}; - -my_obj.len() == 5; -``` diff --git a/doc/src/language/comments.md b/doc/src/language/comments.md deleted file mode 100644 index 111e370c..00000000 --- a/doc/src/language/comments.md +++ /dev/null @@ -1,24 +0,0 @@ -Comments -======== - -{{#include ../links.md}} - -Comments are C-style, including '`/*` ... `*/`' pairs for block comments -and '`//`' for comments to the end of the line. - -Comments can be nested. - -```rust -let /* intruder comment */ name = "Bob"; - -// This is a very important one-line comment - -/* This comment spans - multiple lines, so it - only makes sense that - it is even more important */ - -/* Fear not, Rhai satisfies all nesting needs with nested comments: - /*/*/*/*/**/*/*/*/*/ -*/ -``` diff --git a/doc/src/language/constants.md b/doc/src/language/constants.md deleted file mode 100644 index 31398f42..00000000 --- a/doc/src/language/constants.md +++ /dev/null @@ -1,97 +0,0 @@ -Constants -========= - -{{#include ../links.md}} - -Constants can be defined using the `const` keyword and are immutable. - -Constants follow the same naming rules as [variables]. - -```rust -const x = 42; - -print(x * 2); // prints 84 - -x = 123; // <- syntax error: cannot assign to constant -``` - -```rust -const x; // 'x' is a constant '()' - -const x = 40 + 2; // 'x' is a constant 42 -``` - - -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). - -```rust -use rhai::{Engine, Scope, RegisterFn}; - -#[derive(Debug, Clone)] -struct TestStruct(i64); // custom type - -let mut engine = Engine::new(); - -engine - .register_type_with_name::("TestStruct") // register custom type - .register_get("value", |obj: &mut TestStruct| obj.0), // property getter - .register_fn("update_value", - |obj: &mut TestStruct, value: i64| obj.0 = value // mutating method - ); - -let mut scope = Scope::new(); // create custom scope - -scope.push_constant("MY_NUMBER", TestStruct(123_i64)); // add constant variable - -// Beware: constant objects can still be modified via a method call! -engine.consume_with_scope(&mut scope, -r" - MY_NUMBER.update_value(42); - print(MY_NUMBER.value); // prints 42 -")?; -``` - - -Caveat – Constants Can be Modified via Rust ------------------------------------------------- - -A custom type stored as a constant cannot be modified via script, but _can_ be modified via -a registered Rust function that takes a first `&mut` parameter – because there is no way for -Rhai to know whether the Rust function modifies its argument! - -```rust -const x = 42; // a constant - -x.increment(); // call 'increment' defined in Rust with '&mut' first parameter - -x == 43; // value of 'x' is changed! - -fn double() { - this *= 2; // function doubles 'this' -} - -let y = 1; // 'y' is not constant and mutable - -y.double(); // double it... - -y == 2; // value of 'y' is changed as expected - -x.double(); // <- error: cannot modify constant 'this' - -x == 43; // value of 'x' is unchanged by script -``` - -This is important to keep in mind because the script [optimizer][script optimization] -by default does _constant propagation_ as a operation. - -If a constant is eventually modified by a Rust function, the optimizer will not see -the updated value and will propagate the original initialization value instead. diff --git a/doc/src/language/convert.md b/doc/src/language/convert.md deleted file mode 100644 index a14feed3..00000000 --- a/doc/src/language/convert.md +++ /dev/null @@ -1,56 +0,0 @@ -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`]). - -That's it; for other conversions, register custom conversion functions. - -```rust -let x = 42; - -let y = x * 100.0; // <- error: cannot multiply i64 with f64 - -let y = x.to_float() * 100.0; // works - -let z = y.to_int() + x; // works - -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/do.md b/doc/src/language/do.md deleted file mode 100644 index c1129944..00000000 --- a/doc/src/language/do.md +++ /dev/null @@ -1,28 +0,0 @@ -`do` Loop -========= - -{{#include ../links.md}} - -`do` loops have two opposite variants: `do` ... `while` and `do` ... `until`. - -Like the `while` loop, `continue` can be used to skip to the next iteration, by-passing all following statements; -`break` can be used to break out of the loop unconditionally. - -```rust -let x = 10; - -do { - x -= 1; - if x < 6 { continue; } // skip to the next iteration - print(x); - if x == 5 { break; } // break out of do loop -} while x > 0; - - -do { - x -= 1; - if x < 6 { continue; } // skip to the next iteration - print(x); - if x == 5 { break; } // break out of do loop -} until x == 0; -``` diff --git a/doc/src/language/doc-comments.md b/doc/src/language/doc-comments.md deleted file mode 100644 index a3fdcf91..00000000 --- a/doc/src/language/doc-comments.md +++ /dev/null @@ -1,77 +0,0 @@ -Doc-Comments -============ - -{{#include ../links.md}} - -Similar to Rust, comments starting with `///` (three slashes) or `/**` (two asterisks) are -_doc-comments_. - -Doc-comments can only appear in front of [function] definitions, not any other elements: - -```rust -/// This is a valid one-line doc-comment -fn foo() {} - -/** This is a - ** valid block - ** doc-comment - **/ -fn bar(x) { - /// Syntax error - this doc-comment is invalid - x + 1 -} - -/** Syntax error - this doc-comment is invalid */ -let x = 42; - -/// Syntax error - this doc-comment is also invalid -{ - let x = 42; -} -``` - - -Special Cases -------------- - -Long streams of `//////...` and `/*****...` do _NOT_ form doc-comments. -This is consistent with popular comment block styles for C-like languages. - -```rust -/////////////////////////////// <- this is not a doc-comment -// This is not a doc-comment // <- this is a normal comment -/////////////////////////////// <- this is not a doc-comment - -// However, watch out for comment lines starting with '///' - -////////////////////////////////////////// <- this is not a doc-comment -/// This, however, IS a doc-comment!!! /// <- this starts with '///' -////////////////////////////////////////// <- this is not a doc-comment - -/**************************************** - * * - * This is also not a doc-comment block * - * so we don't have to put this in * - * front of a function. * - * * - ****************************************/ -``` - - -Using Doc-Comments ------------------- - -Doc-comments are stored within the script's [`AST`] after compilation. - -The `AST::iter_functions` method provides a `ScriptFnMetadata` instance -for each function defined within the script, which includes doc-comments. - -Doc-comments never affect the evaluation of a script nor do they incur -significant performance overhead. However, third party tools can take advantage -of this information to auto-generate documentation for Rhai script functions. - - -Disabling Doc-Comments ----------------------- - -Doc-comments can be disabled via the `Engine::set_doc_comments` method. diff --git a/doc/src/language/dynamic.md b/doc/src/language/dynamic.md deleted file mode 100644 index 8c10d9f8..00000000 --- a/doc/src/language/dynamic.md +++ /dev/null @@ -1,100 +0,0 @@ -Dynamic Values -============== - -{{#include ../links.md}} - -A `Dynamic` value can be _any_ type. However, under [`sync`], all types must be `Send + Sync`. - - -Use `type_of()` to Get Value Type --------------------------------- - -Because [`type_of()`] a `Dynamic` value returns the type of the actual value, -it is usually used to perform type-specific actions based on the actual value's type. - -```c -let mystery = get_some_dynamic_value(); - -switch type_of(mystery) { - "i64" => print("Hey, I got an integer here!"), - "f64" => print("Hey, I got a float here!"), - "string" => print("Hey, I got a string here!"), - "bool" => print("Hey, I got a boolean here!"), - "array" => print("Hey, I got an array here!"), - "map" => print("Hey, I got an object map here!"), - "Fn" => print("Hey, I got a function pointer here!"), - "TestStruct" => print("Hey, I got the TestStruct custom type here!"), - _ => print("I don't know what this is: " + type_of(mystery)) -} -``` - - -Functions Returning `Dynamic` ----------------------------- - -In Rust, sometimes a `Dynamic` forms part of a returned value – a good example is an [array] -which contains `Dynamic` elements, or an [object map] which contains `Dynamic` property values. - -To get the _real_ values, the actual value types _must_ be known in advance. -There is no easy way for Rust to decide, at run-time, what type the `Dynamic` value is -(short of using the `type_name` function and match against the name). - - -Type Checking and Casting ------------------------- - -A `Dynamic` value's actual type can be checked via the `is` method. - -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' - -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.try_cast::()?; // 'try_cast' does not panic when the cast fails, but returns 'None' -``` - -Type Name ---------- - -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' - -match item.type_name() { // 'type_name' returns the name of the actual Rust type - "i64" => ... - "alloc::string::String" => ... - "bool" => ... - "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 ----------------- - -The following conversion traits are implemented for `Dynamic`: - -* `From` (`i32` if [`only_i32`]) -* `From` (if not [`no_float`]) -* `From` -* `From` -* `From` -* `From` -* `From>` (into an [array]) -* `From>` (into an [object map]) -* `From` (into a [timestamp] if not [`no_std`]) diff --git a/doc/src/language/eval.md b/doc/src/language/eval.md deleted file mode 100644 index 7e9bdd4d..00000000 --- a/doc/src/language/eval.md +++ /dev/null @@ -1,83 +0,0 @@ -`eval` Function -=============== - -{{#include ../links.md}} - -Or "How to Shoot Yourself in the Foot even Easier" ------------------------------------------------- - -Saving the best for last, there is the ever-dreaded... `eval` function! - -```rust -let x = 10; - -fn foo(x) { x += 12; x } - -let script = "let y = x;"; // build a script -script += "y += foo(y);"; -script += "x + y"; - -let result = eval(script); // <- look, JavaScript, we can also do this! - -result == 42; - -x == 10; // prints 10: functions call arguments are passed by value -y == 32; // prints 32: variables defined in 'eval' persist! - -eval("{ let z = y }"); // to keep a variable local, use a statement block - -print(z); // <- error: variable 'z' not found - -"print(42)".eval(); // <- nope... method-call style doesn't work with 'eval' -``` - -Script segments passed to `eval` execute inside the current [`Scope`], so they can access and modify _everything_, -including all variables that are visible at that position in code! It is almost as if the script segments were -physically pasted in at the position of the `eval` call. - - -Cannot Define New Functions --------------------------- - -New functions cannot be defined within an `eval` call, since functions can only be defined at the _global_ level, -not inside another function call! - -```rust -let script = "x += 32"; -let x = 10; -eval(script); // variable 'x' in the current scope is visible! -print(x); // prints 42 - -// The above is equivalent to: -let script = "x += 32"; -let x = 10; -x += 32; -print(x); -``` - - -`eval` is Evil --------------- - -For those who subscribe to the (very sensible) motto of ["`eval` is evil"](http://linterrors.com/js/eval-is-evil), -disable `eval` using [`Engine::disable_symbol`][disable keywords and operators]: - -```rust -engine.disable_symbol("eval"); // disable usage of 'eval' -``` - -`eval` can also be disabled by overloading it, probably with something that throws: - -```rust -fn eval(script) { throw "eval is evil! I refuse to run " + script } - -let x = eval("40 + 2"); // throws "eval is evil! I refuse to run 40 + 2" -``` - -Or overload it from Rust: - -```rust -engine.register_result_fn("eval", |script: String| -> Result<(), Box> { - Err(format!("eval is evil! I refuse to run {}", script).into()) -}); -``` diff --git a/doc/src/language/fn-anon.md b/doc/src/language/fn-anon.md deleted file mode 100644 index a2e45706..00000000 --- a/doc/src/language/fn-anon.md +++ /dev/null @@ -1,60 +0,0 @@ -Anonymous Functions -=================== - -{{#include ../links.md}} - -Sometimes it gets tedious to define separate functions only to dispatch them via single [function pointers]. -This scenario is especially common when simulating object-oriented programming ([OOP]). - -```rust -// Define object -let obj = #{ - data: 42, - increment: Fn("inc_obj"), // use function pointers to - decrement: Fn("dec_obj"), // refer to method functions - print: Fn("print_obj") -}; - -// Define method functions one-by-one -fn inc_obj(x) { this.data += x; } -fn dec_obj(x) { this.data -= x; } -fn print_obj() { print(this.data); } -``` - -The above can be replaced by using _anonymous functions_ which have the same syntax as Rust's closures -(but they are **NOT** real closures, merely syntactic sugar): - -```rust -let obj = #{ - data: 42, - increment: |x| this.data += x, // one-liner - decrement: |x| this.data -= x, - print_obj: || { print(this.data); } // full function body -}; -``` - -The anonymous functions will be hoisted into separate functions in the global namespace. -The above is equivalent to: - -```rust -let obj = #{ - data: 42, - increment: Fn("anon_fn_1000"), - decrement: Fn("anon_fn_1001"), - print: Fn("anon_fn_1002") -}; - -fn anon_fn_1000(x) { this.data += x; } -fn anon_fn_1001(x) { this.data -= x; } -fn anon_fn_1002() { print this.data; } -``` - - -WARNING – NOT Real Closures --------------------------------- - -Remember: anonymous functions, though having the same syntax as Rust _closures_, are themselves -**not** real closures. - -In particular, they capture their execution environment via [automatic currying] -(disabled via [`no_closure`]). diff --git a/doc/src/language/fn-capture.md b/doc/src/language/fn-capture.md deleted file mode 100644 index 599b211d..00000000 --- a/doc/src/language/fn-capture.md +++ /dev/null @@ -1,71 +0,0 @@ -Capture The Calling Scope for Function Call -========================================== - -{{#include ../links.md}} - - -Peeking Out of The Pure Box ---------------------------- - -Rhai functions are _pure_, meaning that they depend on on their arguments and have no -access to the calling environment. - -When a function accesses a variable that is not defined within that function's scope, -it raises an evaluation error. - -It is possible, through a special syntax, to capture the calling scope – i.e. the scope -that makes the function call – and access variables defined there. - -```rust -fn foo(y) { // function accesses 'x' and 'y', but 'x' is not defined - x += y; // 'x' is modified in this function - x -} - -let x = 1; - -foo(41); // error: variable 'x' not found - -// Calling a function with a '!' causes it to capture the calling scope - -foo!(41) == 42; // the function can access the value of 'x', but cannot change it - -x == 1; // 'x' is still the original value - -x.method!(); // <- syntax error: capturing is not allowed in method-call style - -// Capturing also works for function pointers - -let f = Fn("foo"); - -call!(f, 41) == 42; // must use function-call style - -f.call!(41); // <- syntax error: capturing is not allowed in method-call style - -// Capturing is not available for module functions - -import "hello" as h; - -h::greet!(); // <- syntax error: capturing is not allowed in namespace-qualified calls -``` - - -No Mutations ------------- - -Variables in the calling scope are captured as cloned copies. -Changes to them do **not** reflect back to the calling scope. - -Rhai functions remain _pure_ in the sense that they can never mutate their environment. - - -Caveat Emptor -------------- - -Functions relying on the calling scope is often a _Very Bad Idea™_ because it makes code -almost impossible to reason and maintain, as their behaviors are volatile and unpredictable. - -They behave more like macros that are expanded inline than actual function calls, thus the -syntax is also similar to Rust's macro invocations. - -This usage should be at the last resort. YOU HAVE BEEN WARNED. diff --git a/doc/src/language/fn-closure.md b/doc/src/language/fn-closure.md deleted file mode 100644 index 985a67f3..00000000 --- a/doc/src/language/fn-closure.md +++ /dev/null @@ -1,192 +0,0 @@ -Simulating Closures -=================== - -{{#include ../links.md}} - -Capture External Variables via Automatic Currying ------------------------------------------------- - -Since [anonymous functions] de-sugar to standard function definitions, they retain all the behaviors of -Rhai functions, including being _pure_, having no access to external variables. - -The anonymous function syntax, however, automatically _captures_ variables that are not defined within -the current scope, but are defined in the external scope – i.e. the scope where the anonymous function -is created. - -Variables that are accessible during the time the [anonymous function] is created can be captured, -as long as they are not shadowed by local variables defined within the function's scope. - -The captured variables are automatically converted into **reference-counted shared values** -(`Rc>` in normal builds, `Arc>` in [`sync`] builds). - -Therefore, similar to closures in many languages, these captured shared values persist through -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 `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. - - -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 - -// The above de-sugars into this: -fn anon$1001(x, y) { x + y } // parameter 'x' is inserted - -$make_shared(x); // convert variable 'x' into a shared value - -let f = Fn("anon$1001").curry(x); // shared 'x' is curried - -f.call(2) == 42; -``` - - -Beware: Captured Variables are Truly Shared ------------------------------------------- - -The example below is a typical tutorial sample for many languages to illustrate the traps -that may accompany capturing external scope variables in closures. - -It prints `9`, `9`, `9`, ... `9`, `9`, not `0`, `1`, `2`, ... `8`, `9`, because there is -ever only _one_ captured variable, and all ten closures capture the _same_ variable. - -```rust -let funcs = []; - -for i in range(0, 10) { - funcs.push(|| print(i)); // the for loop variable 'i' is captured -} - -funcs.len() == 10; // 10 closures stored in the array - -funcs[0].type_of() == "Fn"; // make sure these are closures - -for f in funcs { - f.call(); // all references to 'i' are the same variable! -} -``` - - -Therefore – Be Careful to Prevent Data Races -------------------------------------------------- - -Rust does not have data races, but that doesn't mean Rhai doesn't. - -Avoid performing a method call on a captured shared variable (which essentially takes a -mutable reference to the shared object) while using that same variable as a parameter -in the method call – this is a sure-fire way to generate a data race error. - -If a shared value is used as the `this` pointer in a method call to a closure function, -then the same shared value _must not_ be captured inside that function, or a data race -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 - -x.call(f, 2); // <- error: data race detected on 'x' -``` - - -Data Races in `sync` Builds Can Become Deadlocks ------------------------------------------------ - -Under the [`sync`] feature, shared values are guarded with a `RwLock`, meaning that data race -conditions no longer raise an error. - -Instead, they wait endlessly for the `RwLock` to be freed, and thus can become deadlocks. - -On the other hand, since the same thread (i.e. the [`Engine`] thread) that is holding the lock -is attempting to read it again, this may also [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1) -depending on the O/S. - -```rust -let x = 20; - -let f = |a| this += x + a; // 'x' is captured in this closure - -// Under `sync`, the following may wait forever, or may panic, -// because 'x' is locked as the `this` pointer but also accessed -// via a captured shared value. -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 -they are called _closures_. When this happen, a typical language implementation hoists -those variables that are captured away from the stack frame and into heap-allocated storage. -This is because those variables may be needed after the stack frame goes away. - -These heap-allocated captured variables only go away when all the closures that need them -are finished with them. A garbage collector makes this trivial to implement – they are -automatically collected as soon as all closures needing them are destroyed. - -In Rust, this can be done by reference counting instead, with the potential pitfall of creating -reference loops that will prevent those variables from being deallocated forever. -Rhai avoids this by clone-copying most data values, so reference loops are hard to create. - -Rhai does the hoisting of captured variables into the heap by converting those values -into reference-counted locked values, also allocated on the heap. The process is identical. - -Closures are usually implemented as a data structure containing two items: - -1) A function pointer to the function body of the closure, -2) A data structure containing references to the captured shared variables on the heap. - -Usually a language implementation passes the structure containing references to captured -shared variables into the function pointer, the function body taking this data structure -as an additional parameter. - -This is essentially what Rhai does, except that Rhai passes each variable individually -as separate parameters to the function, instead of creating a structure and passing that -structure as a single parameter. This is the only difference. - -Therefore, in most languages, essentially all closures are implemented as automatic currying of -shared variables hoisted into the heap, automatically passing those variables as parameters into -the function. Rhai just brings this directly up to the front. diff --git a/doc/src/language/fn-curry.md b/doc/src/language/fn-curry.md deleted file mode 100644 index c223d8cd..00000000 --- a/doc/src/language/fn-curry.md +++ /dev/null @@ -1,39 +0,0 @@ -Function Pointer Currying -======================== - -{{#include ../links.md}} - -It is possible to _curry_ a [function pointer] by providing partial (or all) arguments. - -Currying is done via the `curry` keyword and produces a new [function pointer] which carries -the curried arguments. - -When the curried [function pointer] is called, the curried arguments are inserted starting from the left. -The actual call arguments should be reduced by the number of curried arguments. - -```rust -fn mul(x, y) { // function with two parameters - x * y -} - -let func = Fn("mul"); - -func.call(21, 2) == 42; // two arguments are required for 'mul' - -let curried = func.curry(21); // currying produces a new function pointer which - // carries 21 as the first argument - -let curried = curry(func, 21); // function-call style also works - -curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)' - // only one argument is now required -``` - - -Automatic Currying ------------------- - -[Anonymous functions] defined via a closure syntax _capture_ external variables -that are not shadowed inside the function's scope. - -This is accomplished via [automatic currying]. diff --git a/doc/src/language/fn-namespaces.md b/doc/src/language/fn-namespaces.md deleted file mode 100644 index 47489acf..00000000 --- a/doc/src/language/fn-namespaces.md +++ /dev/null @@ -1,119 +0,0 @@ -Function Namespaces -================== - -{{#include ../links.md}} - -Each Function is a Separate Compilation Unit -------------------------------------------- - -[Functions] in Rhai are _pure_ and they form individual _compilation units_. -This means that individual functions can be separated, exported, re-grouped, imported, -and generally mix-'n-match-ed with other completely unrelated scripts. - -For example, the `AST::merge` and `AST::combine` methods (or the equivalent `+` and `+=` operators) -allow combining all functions in one [`AST`] into another, forming a new, unified, group of functions. - -In general, there are two types of _namespaces_ where functions are looked up: - -| Namespace | How Many | Source | Lookup | Sub-modules? | Variables? | -| --------- | :------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | :----------: | :--------: | -| Global | One | 1) [`AST`] being evaluated
2) `Engine::register_XXX` API
3) global [modules] registered via `Engine::register_global_module`
4) functions in static [modules] registered via `Engine::register_static_module` and marked _global_ | simple name | ignored | ignored | -| Module | Many | 1) [Module] registered via `Engine::register_static_module`
2) [Module] loaded via [`import`] statement | namespace-qualified name | yes | yes | - - -Module Namespaces ------------------ - -There can be multiple module namespaces at any time during a script evaluation, usually loaded via the -[`import`] statement. - -_Static_ module namespaces can also be registered into an [`Engine`] via `Engine::register_static_module`. - -Functions and variables in module namespaces are isolated and encapsulated within their own environments. - -They must be called or accessed in a _namespace-qualified_ manner. - -```rust -import "my_module" as m; // new module namespace 'm' created via 'import' - -let x = m::calc_result(); // namespace-qualified function call - -let y = m::MY_NUMBER; // namespace-qualified variable (constant) access - -let x = calc_result(); // <- error: function 'calc_result' not found - // in global namespace! -``` - - -Global Namespace ----------------- - -There is one _global_ namespace for every [`Engine`], which includes (in the following search order): - -* All functions defined in the [`AST`] currently being evaluated. - -* All native Rust functions and iterators registered via the `Engine::register_XXX` API. - -* All functions and iterators defined in global [modules] that are registered into the [`Engine`] via - `Engine::register_global_module`. - -* Functions defined in [modules] registered via `Engine::register_static_module` that are specifically - marked for exposure to the global namespace (e.g. via the `#[rhai(global)]` attribute in a [plugin module]). - -Anywhere in a Rhai script, when a function call is made, the function is searched within the -global namespace, in the above search order. - -Therefore, function calls in Rhai are _late_ bound – meaning that the function called cannot be -determined or guaranteed and there is no way to _lock down_ the function being called. -This aspect is very similar to JavaScript before ES6 modules. - -```rust -// Compile a script into AST -let ast1 = engine.compile( - r#" - fn get_message() { - "Hello!" // greeting message - } - - fn say_hello() { - print(get_message()); // prints message - } - - say_hello(); - "# -)?; - -// Compile another script with an overriding function -let ast2 = engine.compile(r#"fn get_message() { "Boo!" }"#)?; - -// Combine the two AST's -ast1 += ast2; // 'message' will be overwritten - -engine.consume_ast(&ast1)?; // prints 'Boo!' -``` - -Therefore, care must be taken when _cross-calling_ functions to make sure that the correct -functions are called. - -The only practical way to ensure that a function is a correct one is to use [modules] - -i.e. define the function in a separate module and then [`import`] it: - -```rust ----------------- -| message.rhai | ----------------- - -fn get_message() { "Hello!" } - - ---------------- -| script.rhai | ---------------- - -import "message" as msg; - -fn say_hello() { - print(msg::get_message()); -} -say_hello(); -``` diff --git a/doc/src/language/fn-ptr.md b/doc/src/language/fn-ptr.md deleted file mode 100644 index 4717504a..00000000 --- a/doc/src/language/fn-ptr.md +++ /dev/null @@ -1,271 +0,0 @@ -Function Pointers -================= - -{{#include ../links.md}} - -It is possible to store a _function pointer_ in a variable just like a normal value. -In fact, internally a function pointer simply stores the _name_ of the function as a string. - -A function pointer is created via the `Fn` function, which takes a [string] parameter. - -Call a function pointer using the `call` method. - - -Built-in methods ----------------- - -The following standard methods (mostly defined in the [`BasicFnPackage`][packages] but excluded if -using a [raw `Engine`]) operate on function pointers: - -| Function | Parameter(s) | Description | -| ---------------------------------- | ------------ | ------------------------------------------------------------------------------------------------ | -| `name` method and property | _none_ | returns the name of the function encapsulated by the function pointer | -| `is_anonymous` method and property | _none_ | does the function pointer refer to an [anonymous function]? Not available under [`no_function`]. | -| `call` | _arguments_ | calls the function matching the function pointer's name with the _arguments_ | - - -Examples --------- - -```rust -fn foo(x) { 41 + x } - -let func = Fn("foo"); // use the 'Fn' function to create a function pointer - -print(func); // prints 'Fn(foo)' - -let func = fn_name.Fn(); // <- error: 'Fn' cannot be called in method-call style - -func.type_of() == "Fn"; // type_of() as function pointer is 'Fn' - -func.name == "foo"; - -func.call(1) == 42; // call a function pointer with the 'call' method - -foo(1) == 42; // <- the above de-sugars to this - -call(func, 1); // normal function call style also works for 'call' - -let len = Fn("len"); // 'Fn' also works with registered native Rust functions - -len.call("hello") == 5; - -let add = Fn("+"); // 'Fn' works with built-in operators also - -add.call(40, 2) == 42; - -let fn_name = "hello"; // the function name does not have to exist yet - -let hello = Fn(fn_name + "_world"); - -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 [`import`]-ed [modules]. -They can only refer to functions within the global [namespace][function namespace]. -See _[Function Namespaces]_ for more details. - -```rust -import "foo" as f; // assume there is 'f::do_work()' - -f::do_work(); // works! - -let p = Fn("f::do_work"); // error: invalid function name - -fn do_work_now() { // call it from a local function - f::do_work(); -} - -let p = Fn("do_work_now"); - -p.call(); // works! -``` - - -Dynamic Dispatch ----------------- - -The purpose of function pointers is to enable rudimentary _dynamic dispatch_, meaning to determine, -at runtime, which function to call among a group. - -Although it is possible to simulate dynamic dispatch via a number and a large `if-then-else-if` statement, -using function pointers significantly simplifies the code. - -```rust -let x = some_calculation(); - -// These are the functions to call depending on the value of 'x' -fn method1(x) { ... } -fn method2(x) { ... } -fn method3(x) { ... } - -// Traditional - using decision variable -let func = sign(x); - -// Dispatch with if-statement -if func == -1 { - method1(42); -} else if func == 0 { - method2(42); -} else if func == 1 { - method3(42); -} - -// Using pure function pointer -let func = if x < 0 { - Fn("method1") -} else if x == 0 { - Fn("method2") -} else if x > 0 { - Fn("method3") -} - -// Dynamic dispatch -func.call(42); - -// Using functions map -let map = [ Fn("method1"), Fn("method2"), Fn("method3") ]; - -let func = sign(x) + 1; - -// Dynamic dispatch -map[func].call(42); -``` - - -Bind the `this` Pointer ----------------------- - -When `call` is called as a _method_ but not on a function pointer, it is possible to dynamically dispatch -to a function call while binding the object in the method call to the `this` pointer of the function. - -To achieve this, pass the function pointer as the _first_ argument to `call`: - -```rust -fn add(x) { // define function which uses 'this' - this += x; -} - -let func = Fn("add"); // function pointer to 'add' - -func.call(1); // error: 'this' pointer is not bound - -let x = 41; - -func.call(x, 1); // error: function 'add (i64, i64)' not found - -call(func, x, 1); // error: function 'add (i64, i64)' not found - -x.call(func, 1); // 'this' is bound to 'x', dispatched to 'func' - -x == 42; -``` - -Beware that this only works for _method-call_ style. Normal function-call style cannot bind -the `this` pointer (for syntactic reasons). - -Therefore, obviously, binding the `this` pointer is unsupported under [`no_object`]. - - -Call a Function Pointer in Rust ------------------------------- - -It is completely normal to register a Rust function with an [`Engine`] that takes parameters -whose types are function pointers. The Rust type in question is `rhai::FnPtr`. - -A function pointer in Rhai is essentially syntactic sugar wrapping the _name_ of a function -to call in script. Therefore, the script's [`AST`] is required to call a function pointer, -as well as the entire _execution context_ that the script is running in. - -For a rust function taking a function pointer as parameter, the [Low-Level API](../rust/register-raw.md) -must be used to register the function. - -Essentially, use the low-level `Engine::register_raw_fn` method to register the function. -`FnPtr::call_dynamic` is used to actually call the function pointer, passing to it the -current _native call context_, the `this` pointer, and other necessary arguments. - -```rust -use rhai::{Engine, Module, Dynamic, FnPtr, NativeCallContext}; - -let mut engine = Engine::new(); - -// Define Rust function in required low-level API signature -fn call_fn_ptr_with_value(context: NativeCallContext, args: &mut [&mut Dynamic]) - -> Result> -{ - // 'args' is guaranteed to contain enough arguments of the correct types - let fp = std::mem::take(args[1]).cast::(); // 2nd argument - function pointer - let value = args[2].clone(); // 3rd argument - function argument - let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer - - // Use 'FnPtr::call_dynamic' to call the function pointer. - // Beware, private script-defined functions will not be found. - fp.call_dynamic(context, Some(this_ptr), [value]) -} - -// Register a Rust function using the low-level API -engine.register_raw_fn("super_call", - &[ // parameter types - std::any::TypeId::of::(), - std::any::TypeId::of::(), - std::any::TypeId::of::() - ], - call_fn_ptr_with_value -); -``` - - -`NativeCallContext` ------------------- - -`FnPtr::call_dynamic` takes a parameter of type `NativeCallContext` which holds the _native call context_ -of the particular call to a registered Rust function. It is a type that exposes the following: - -| Field | Type | Description | -| ------------------- | :-------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `engine()` | `&Engine` | the current [`Engine`], with all configurations and settings.
This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. | -| `source()` | `Option<&str>` | reference to the current source, if any | -| `iter_imports()` | `impl Iterator` | iterator of the current stack of [modules] imported via `import` statements | -| `imports()` | `&Imports` | reference to the current stack of [modules] imported via `import` statements; requires the [`internals`] feature | -| `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | -| `namespaces()` | `&[&Module]` | reference to the namespaces (as [modules]) containing all script-defined functions; requires the [`internals`] feature | - - -This type is normally provided by the [`Engine`] (e.g. when using [`Engine::register_fn_raw`](../rust/register-raw.md)). -However, it may also be manually constructed from a tuple: - -```rust -use rhai::{Engine, FnPtr, NativeCallContext}; - -let engine = Engine::new(); - -// Compile script to AST -let mut ast = engine.compile( - r#" - let test = "hello"; - |x| test + x // this creates an closure - "#, -)?; - -// Save the closure together with captured variables -let fn_ptr = engine.eval_ast::(&ast)?; - -// Get rid of the script, retaining only functions -ast.retain_functions(|_, _, _| true); - -// Create function namespace from the 'AST' -let lib = [ast.as_ref()]; - -// Create native call context -let context = NativeCallContext::new(&engine, &lib); - -// 'f' captures: the engine, the AST, and the closure -let f = move |x: i64| fn_ptr.call_dynamic(context, None, [x.into()]); - -// 'f' can be called like a normal function -let result = f(42)?; -``` diff --git a/doc/src/language/for.md b/doc/src/language/for.md deleted file mode 100644 index 6af5a422..00000000 --- a/doc/src/language/for.md +++ /dev/null @@ -1,103 +0,0 @@ -`for` Loop -========== - -{{#include ../links.md}} - -Iterating through a range or an [array], or any type with a registered [type iterator], -is provided by the `for` ... `in` loop. - -Like C, `continue` can be used to skip to the next iteration, by-passing all following statements; -`break` can be used to break out of the loop unconditionally. - -To loop through a number sequence (with or without steps), use the `range` function to -return a numeric iterator. - - -Iterate Through Strings ------------------------ - -Iterating through a [string] yields characters. - -```rust -let s = "hello, world!"; - -for ch in s { - if ch > 'z' { continue; } // skip to the next iteration - - print(ch); - - if x == '@' { break; } // break out of for loop -} -``` - - -Iterate Through Arrays ----------------------- - -Iterating through an [array] yields cloned _copies_ of each element. - -```rust -let array = [1, 3, 5, 7, 9, 42]; - -for x in array { - if x > 10 { continue; } // skip to the next iteration - - print(x); - - if x == 42 { break; } // break out of for loop -} -``` - - -Iterate Through Numeric Ranges ------------------------------ - -The `range` function allows iterating through a range of numbers -(not including the last number). - -```rust -// Iterate starting from 0 and stopping at 49. -for x in range(0, 50) { - if x > 10 { continue; } // skip to the next iteration - - print(x); - - if x == 42 { break; } // break out of for loop -} - -// The 'range' function also takes a step. -for x in range(0, 50, 3) { // step by 3 - if x > 10 { continue; } // skip to the next iteration - - print(x); - - if x == 42 { break; } // break out of for loop -} -``` - - -Iterate Through Object Maps --------------------------- - -Two methods, `keys` and `values`, return [arrays] containing cloned _copies_ -of all property names and values of an [object map], respectively. - -These [arrays] can be iterated. - -```rust -let map = #{a:1, b:3, c:5, d:7, e:9}; - -// Property names are returned in unsorted, random order -for x in map.keys() { - if x > 10 { continue; } // skip to the next iteration - - print(x); - - if x == 42 { break; } // break out of for loop -} - -// Property values are returned in unsorted, random order -for val in map.values() { - print(val); -} -``` diff --git a/doc/src/language/functions.md b/doc/src/language/functions.md deleted file mode 100644 index d7f9917d..00000000 --- a/doc/src/language/functions.md +++ /dev/null @@ -1,190 +0,0 @@ -Functions -========= - -{{#include ../links.md}} - -Rhai supports defining functions in script (unless disabled with [`no_function`]): - -```rust -fn add(x, y) { - return x + y; -} - -fn sub(x, y,) { // trailing comma in parameters list is OK - return x - y; -} - -add(2, 3) == 5; - -sub(2, 3,) == -1; // trailing comma in arguments list is OK -``` - - -Implicit Return ---------------- - -Just like in Rust, an implicit return can be used. In fact, the last statement of a block is _always_ the block's return value -regardless of whether it is terminated with a semicolon `';'`. This is different from Rust. - -```rust -fn add(x, y) { // implicit return: - x + y; // value of the last statement (no need for ending semicolon) - // is used as the return value -} - -fn add2(x) { - return x + 2; // explicit return -} - -add(2, 3) == 5; - -add2(42) == 44; -``` - - -Global Definitions Only ----------------------- - -Functions can only be defined at the global level, never inside a block or another function. - -```rust -// Global level is OK -fn add(x, y) { - x + y -} - -// The following will not compile -fn do_addition(x) { - fn add_y(n) { // <- syntax error: functions cannot be defined inside another function - n + y - } - - add_y(x) -} -``` - - -No Access to External Scope --------------------------- - -Functions are not _closures_. They do not capture the calling environment -and can only access their own parameters. -They cannot access variables external to the function itself. - -```rust -let x = 42; - -fn foo() { x } // <- syntax error: variable 'x' doesn't exist -``` - - -But Can Call Other Functions ---------------------------- - -All functions in the same [`AST`] can call each other. - -```rust -fn foo(x) { x + 1 } // function defined in the global namespace - -fn bar(x) { foo(x) } // OK! function 'foo' can be called -``` - - -Use Before Definition Allowed ----------------------------- - -Unlike C/C++, functions in Rhai can be defined _anywhere_ at global level. - -A function does not need to be defined prior to being used in a script; -a statement in the script can freely call a function defined afterwards. - -This is similar to Rust and many other modern languages, such as JavaScript's `function` keyword. - - -Arguments are Passed by Value ----------------------------- - -Functions defined in script always take [`Dynamic`] parameters (i.e. they can be of any types). -Therefore, functions with the same name and same _number_ of parameters are equivalent. - -All arguments are passed by _value_, so all Rhai script-defined functions are _pure_ -(i.e. they never modify their arguments). - -Any update to an argument will **not** be reflected back to the caller. - -```rust -fn change(s) { // 's' is passed by value - s = 42; // only a COPY of 's' is changed -} - -let x = 500; - -change(x); - -x == 500; // 'x' is NOT changed! -``` - - -`this` – Simulating an Object Method ------------------------------------------ - -Script-defined functions can also be called in method-call style. -When this happens, the keyword '`this`' binds to the object in the method call and can be changed. - -```rust -fn change() { // not that the object does not need a parameter - this = 42; // 'this' binds to the object in method-call -} - -let x = 500; - -x.change(); // call 'change' in method-call style, 'this' binds to 'x' - -x == 42; // 'x' is changed! - -change(); // <- error: `this` is unbound -``` - - -`is_def_fn` ------------ - -Use `is_def_fn` to detect if a Rhai function is defined (and therefore callable), -based on its name and the number of parameters. - -```rust -fn foo(x) { x + 1 } - -is_def_fn("foo", 1) == true; - -is_def_fn("foo", 0) == false; - -is_def_fn("foo", 2) == false; - -is_def_fn("bar", 1) == false; -``` - - -Metadata --------- - -The function `get_fn_metadata_list` is a _reflection_ API that returns an array of the metadata -of all script-defined functions in scope. - -Functions from the following sources are returned, in order: - -1) Encapsulated script environment (e.g. when loading a [module] from a script file), -2) Current script, -3) [Modules] imported via the [`import`] statement (latest imports first), -4) [Modules] added via [`Engine::register_static_module`]({{rootUrl}}/rust/modules/create.md) (latest registrations first) - -The return value is an [array] of [object maps] (so `get_fn_metadata_list` is not available under -[`no_index`] or [`no_object`]), containing the following fields: - -| Field | Type | Optional? | Description | -| -------------- | :------------------: | :-------: | ---------------------------------------------------------------------- | -| `namespace` | [string] | yes | the module _namespace_ if the function is defined within a module | -| `access` | [string] | no | `"public"` if the function is public,
`"private"` if it is private | -| `name` | [string] | no | function name | -| `params` | [array] of [strings] | no | parameter names | -| `is_anonymous` | `bool` | no | is this function an anonymous function? | diff --git a/doc/src/language/if.md b/doc/src/language/if.md deleted file mode 100644 index b9c63dab..00000000 --- a/doc/src/language/if.md +++ /dev/null @@ -1,51 +0,0 @@ -`if` Statement -============== - -{{#include ../links.md}} - -`if` statements follow C syntax: - -```rust -if foo(x) { - print("It's true!"); -} else if bar == baz { - print("It's true again!"); -} else if baz.is_foo() { - print("Yet again true."); -} else if foo(bar - baz) { - print("True again... this is getting boring."); -} else { - print("It's finally false!"); -} -``` - -Braces Are Mandatory --------------------- - -Unlike C, the condition expression does _not_ need to be enclosed in parentheses '`(`' .. '`)`', but -all branches of the `if` statement must be enclosed within braces '`{`' .. '`}`', -even when there is only one statement inside the branch. - -Like Rust, there is no ambiguity regarding which `if` clause a branch belongs to. - -```rust -// Rhai is not C! -if (decision) print("I've decided!"); -// ^ syntax error, expecting '{' in statement block -``` - - -`if`-Expressions ---------------- - -Like Rust, `if` statements can also be used as _expressions_, replacing the `? :` conditional operators -in other C-like languages. - -```rust -// The following is equivalent to C: int x = 1 + (decision ? 42 : 123) / 2; -let x = 1 + if decision { 42 } else { 123 } / 2; -x == 22; - -let x = if decision { 42 }; // no else branch defaults to '()' -x == (); -``` diff --git a/doc/src/language/index.md b/doc/src/language/index.md deleted file mode 100644 index ac4e4fb6..00000000 --- a/doc/src/language/index.md +++ /dev/null @@ -1,7 +0,0 @@ -Rhai Language Reference -====================== - -{{#include ../links.md}} - -This section outlines the Rhai language. - diff --git a/doc/src/language/iterator.md b/doc/src/language/iterator.md deleted file mode 100644 index e885efba..00000000 --- a/doc/src/language/iterator.md +++ /dev/null @@ -1,45 +0,0 @@ -Iterators for Custom Types -========================== - -{{#include ../links.md}} - -If a [custom type] is iterable, the [`for`](for.md) loop can be used to iterate through -its items in sequence. - -In order to use a [`for`](for.md) statement, a _type iterator_ must be registered for -the [custom type] in question. - -`Engine::register_iterator` allows registration of a _type iterator_ for any type -that implements `IntoIterator`: - -```rust -// Custom type -#[derive(Debug, Clone)] -struct TestStruct { ... } - -// Implement 'IntoIterator' trait -impl IntoIterator for TestStruct { - type Item = ...; - type IntoIter = SomeIterType; - - fn into_iter(self) -> Self::IntoIter { - ... - } -} - -engine - .register_type_with_name::("TestStruct") - .register_fn("new_ts", || TestStruct { ... }) - .register_iterator::(); // register type iterator -``` - -With a type iterator registered, the [custom type] can be iterated through: - -```rust -let ts = new_ts(); - -// Use 'for' statement to loop through items in 'ts' -for item in ts { - ... -} -``` diff --git a/doc/src/language/json.md b/doc/src/language/json.md deleted file mode 100644 index 26e9f81f..00000000 --- a/doc/src/language/json.md +++ /dev/null @@ -1,95 +0,0 @@ -Parse an Object Map from JSON -============================ - -{{#include ../links.md}} - -The syntax for an [object map] is extremely similar to the JSON representation of a object hash, -with the exception of `null` values which can technically be mapped to [`()`]. - -A valid JSON string does not start with a hash character `#` while a Rhai [object map] does – that's the major difference! - -Use the `Engine::parse_json` method to parse a piece of JSON into an object map. -The JSON text must represent a single object hash (i.e. must be wrapped within "`{ .. }`") -otherwise it returns a syntax error. - -```rust -// JSON string - notice that JSON property names are always quoted -// notice also that comments are acceptable within the JSON string -let json = r#"{ - "a": 1, // <- this is an integer number - "b": true, - "c": 123.0, // <- this is a floating-point number - "$d e f!": "hello", // <- any text can be a property name - "^^^!!!": [1,42,"999"], // <- value can be array or another hash - "z": null // <- JSON 'null' value - } -"#; - -// Parse the JSON expression as an object map -// Set the second boolean parameter to true in order to map 'null' to '()' -let map = engine.parse_json(json, true)?; - -map.len() == 6; // 'map' contains all properties in the JSON string - -// Put the object map into a 'Scope' -let mut scope = Scope::new(); -scope.push("map", map); - -let result = engine.eval_with_scope::(r#"map["^^^!!!"].len()"#)?; - -result == 3; // the object map is successfully used in the script -``` - -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]. - - -Parse JSON with Sub-Objects --------------------------- - -`Engine::parse_json` depends on the fact that the [object map] literal syntax in Rhai is _almost_ -the same as a JSON object. However, it is _almost_ because the syntax for a sub-object in JSON -(i.e. "`{ ... }`") is different from a Rhai [object map] literal (i.e. "`#{ ... }`"). - -When `Engine::parse_json` encounters JSON with sub-objects, it fails with a syntax error. - -If it is certain that no text string in the JSON will ever contain the character '`{`', -then it is possible to parse it by first replacing all occupance of '`{`' with "`#{`". - -A JSON object hash starting with `#{` is handled transparently by `Engine::parse_json`. - -```rust -// JSON with sub-object 'b'. -let json = r#"{"a":1, "b":{"x":true, "y":false}}"#; - -// 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' handles this just fine. -let map = engine.parse_json(&new_json, false)?; - -map.len() == 2; // 'map' contains two properties: 'a' and 'b' -``` - - -Use `serde` to Serialize/Deserialize to/from JSON ------------------------------------------------- - -Remember, `Engine::parse_json` is nothing more than a _cheap_ alternative to true JSON parsing. - -If correctness is needed, or for more configuration possibilities, turn on the [`serde`][features] -feature to pull in the [`serde`](https://crates.io/crates/serde) crate which enables -serialization and deserialization to/from multiple formats, including JSON. - -Beware, though... the [`serde`](https://crates.io/crates/serde) crate is quite heavy. - -See _[Serialization/Deserialization of `Dynamic` with `serde`][`serde`]_ for more details. diff --git a/doc/src/language/keywords.md b/doc/src/language/keywords.md deleted file mode 100644 index 53f3ca65..00000000 --- a/doc/src/language/keywords.md +++ /dev/null @@ -1,26 +0,0 @@ -Keywords -======== - -{{#include ../links.md}} - -The following are reserved keywords in Rhai: - -| Active keywords | Reserved keywords | Usage | Inactive under feature | -| ---------------------------------------------------------------- | ---------------------------------------------------------- | ---------------------- | :--------------------: | -| `true`, `false` | | constants | | -| `let`, `const` | `var`, `static` | variables | | -| | `begin`, `end` | block scopes | | -| `is_shared` | | shared values | [`no_closure`] | -| `if`, `else` | `then`, `unless`, `goto`, `exit` | control flow | | -| `switch` | `match`, `case` | switching and matching | | -| `do`, `while`, `loop`, `until`, `for`, `in`, `continue`, `break` | `each` | looping | | -| `fn`, `private` | `public`, `new` | functions | [`no_function`] | -| `return` | | return values | | -| `throw`, `try`, `catch` | | throw/catch exceptions | | -| `import`, `export`, `as` | `use`, `with`, `module`, `package` | modules/packages | [`no_module`] | -| `Fn`, `call`, `curry` | | function pointers | | -| | `spawn`, `thread`, `go`, `sync`, `async`, `await`, `yield` | threading/async | | -| `type_of`, `print`, `debug`, `eval` | | special functions | | -| | `default`, `void`, `null`, `nil` | special values | | - -Keywords cannot become the name of a [function] or [variable], even when they are disabled. diff --git a/doc/src/language/logic.md b/doc/src/language/logic.md deleted file mode 100644 index 9f6ebbcf..00000000 --- a/doc/src/language/logic.md +++ /dev/null @@ -1,78 +0,0 @@ -Logic Operators -============== - -{{#include ../links.md}} - -Comparison Operators -------------------- - -| Operator | Description | -| :------: | ------------------------- | -| `==` | equals to | -| `!=` | not equals to | -| `>` | greater than | -| `>=` | greater than or equals to | -| `<` | less than | -| `<=` | less than or equals to | - -Comparing most values of the same data type work out-of-the-box for all [standard types] supported by the system. - -However, if using a [raw `Engine`] without loading any [packages], comparisons can only be made between a limited -set of types (see [built-in operators]). - -```rust -42 == 42; // true - -42 > 42; // false - -"hello" > "foo"; // true - -"42" == 42; // false -``` - -Comparing two values of _different_ data types, or of unknown data types, always results in `false`, -except for '`!=`' (not equals) which results in `true`. This is in line with intuition. - -```rust -42 == 42.0; // false - i64 cannot be compared with f64 - -42 != 42.0; // true - i64 cannot be compared with f64 - -42 > "42"; // false - i64 cannot be compared with string - -42 <= "42"; // false - i64 cannot be compared with string - -let ts = new_ts(); // custom type - -ts == 42; // false - types cannot be compared - -ts != 42; // true - types cannot be compared -``` - -Boolean operators ------------------ - -| Operator | Description | Short-Circuits? | -| :---------------: | ------------- | :-------------: | -| `!` (prefix) | boolean _NOT_ | no | -| `&&` | boolean _AND_ | yes | -| `&` | boolean _AND_ | no | -| \|\| | boolean _OR_ | yes | -| \| | boolean _OR_ | no | - -Double boolean operators `&&` and `||` _short-circuit_ – meaning that the second operand will not be evaluated -if the first one already proves the condition wrong. - -Single boolean operators `&` and `|` always evaluate both operands. - -```rust -a() || b(); // b() is not evaluated if a() is true - -a() && b(); // b() is not evaluated if a() is false - -a() | b(); // both a() and b() are evaluated - -a() & b(); // both a() and b() are evaluated -``` - -All boolean operators are [built in][built-in operators] for the `bool` data type. diff --git a/doc/src/language/loop.md b/doc/src/language/loop.md deleted file mode 100644 index 3fd9b5fb..00000000 --- a/doc/src/language/loop.md +++ /dev/null @@ -1,26 +0,0 @@ -Infinite `loop` -=============== - -{{#include ../links.md}} - -Infinite loops follow Rust syntax. - -Like Rust, `continue` can be used to skip to the next iteration, by-passing all following statements; -`break` can be used to break out of the loop unconditionally. - -```rust -let x = 10; - -loop { - x -= 1; - - if x > 5 { continue; } // skip to the next iteration - - print(x); - - if x == 0 { break; } // break out of loop -} -``` - -Beware: a `loop` statement without a `break` statement inside its loop block is infinite - -there is no way for the loop to stop iterating. diff --git a/doc/src/language/method.md b/doc/src/language/method.md deleted file mode 100644 index 5843faef..00000000 --- a/doc/src/language/method.md +++ /dev/null @@ -1,95 +0,0 @@ -Call Method as Function -====================== - -{{#include ../links.md}} - - -First `&mut` Parameter ----------------------- - -Property [getters/setters] and [methods][custom types] in a Rust custom type registered with the [`Engine`] can be called -just like a regular function. In fact, like Rust, property getters/setters and object methods -are registered as regular [functions] in Rhai that take a first `&mut` parameter. - -Unlike functions defined in script (for which all arguments are passed by _value_), -native Rust functions may mutate the object (or the first argument if called in normal function call style). - -However, sometimes it is not as straight-forward, and methods called in function-call style may end up -not muting the object – see the example below. Therefore, it is best to always use method-call style. - -Custom types, properties and methods can be disabled via the [`no_object`] feature. - -```rust -let a = new_ts(); // constructor function -a.field = 500; // property setter -a.update(); // method call, 'a' can be modified - -update(a); // <- this de-sugars to 'a.update()' thus if 'a' is a simple variable - // unlike scripted functions, 'a' can be modified and is not a copy - -let array = [ a ]; - -update(array[0]); // <- 'array[0]' is an expression returning a calculated value, - // a transient (i.e. a copy), so this statement has no effect - // except waste a lot of time cloning - -array[0].update(); // <- call in method-call style will update 'a' -``` - -**IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.** - - -Number of Parameters in Methods ------------------------------- - -Native Rust methods registered with an [`Engine`] take _one additional parameter_ more than -an equivalent method coded in script, where the object is accessed via the `this` pointer instead. - -The following table illustrates the differences: - -| 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 `&mut ImmutableString` ----------------------------------------------------- - -Using a `&mut` first parameter is highly encouraged when using types that are expensive to clone, -even when the intention is not to mutate that argument, because it avoids cloning that argument value. - -Even when a function is never intended to be a method – for example an operator, -it is still sometimes beneficial to make it method-like (i.e. with a first `&mut` parameter) -if the first parameter is not modified. - -For types that are expensive to clone (remember, all function calls are passed cloned -copies of argument values), this may result in a significant performance boost. - -For primary types that are cheap to clone (e.g. those that implement `Copy`), including `ImmutableString`, -this is not necessary. - -```rust -// This is a type that is very expensive to clone. -#[derive(Debug, Clone)] -struct VeryComplexType { ... } - -// Calculate some value by adding 'VeryComplexType' with an integer number. -fn do_add(obj: &VeryComplexType, offset: i64) -> i64 { - ... -} - -engine - .register_type::() - .register_fn("+", add_pure /* or add_method*/); - -// Very expensive to call, as the 'VeryComplexType' is cloned before each call. -fn add_pure(obj: VeryComplexType, offset: i64) -> i64 { - do_add(obj, offset) -} - -// Efficient to call, as only a reference to the 'VeryComplexType' is passed. -fn add_method(obj: &mut VeryComplexType, offset: i64) -> i64 { - do_add(obj, offset) -} -``` diff --git a/doc/src/language/modules/export.md b/doc/src/language/modules/export.md deleted file mode 100644 index 362ca111..00000000 --- a/doc/src/language/modules/export.md +++ /dev/null @@ -1,98 +0,0 @@ -Export Variables, Functions and Sub-Modules in Module -=================================================== - -{{#include ../../links.md}} - - -The easiest way to expose a collection of functions as a self-contained [module] is to do it via a Rhai script itself. - -See the section on [_Creating a Module from AST_]({{rootUrl}}/rust/modules/ast.md) for more details. - -The script text is evaluated, variables are then selectively exposed via the [`export`] statement. -Functions defined by the script are automatically exported. - -Modules loaded within this module at the global level become _sub-modules_ and are also automatically exported. - - -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** (i.e. read-only). - -```rust -// This is a module script. - -let hidden = 123; // variable not exported - default hidden -let x = 42; // this will be exported below - -export x; // the variable 'x' is exported under its own name - -export let x = 42; // convenient short-hand to declare a variable and export it - // under its own name - -export x as answer; // the variable 'x' is exported under the alias 'answer' - // another script can load this module and access 'x' as 'module::answer' - -{ - let inner = 0; // local variable - it disappears when the statement block ends, - // therefore it is not 'global' and cannot be exported - - export inner; // <- syntax error: cannot export a local variable -} -``` - -### Multiple Exports - -One `export` statement can export multiple variables, even under multiple names. - -```rust -// The following exports three variables: -// - 'x' (as 'x' and 'hello') -// - 'y' (as 'foo' and 'bar') -// - 'z' (as 'z') -export x, x as hello, x as world, y as foo, y as bar, z; -``` - - -Export Functions ----------------- - -All functions are automatically exported, _unless_ it is explicitly opt-out with the [`private`] prefix. - -Functions declared [`private`] are hidden to the outside. - -```rust -// This is a module script. - -fn inc(x) { x + 1 } // script-defined function - default public - -private fn foo() {} // private function - hidden -``` - -[`private`] functions are commonly called to initialize the module. -They cannot be called apart from this. - - -Sub-Modules ------------ - -All loaded modules are automatically exported as sub-modules. - -To prevent a module from being exported, load it inside a block statement so that it goes away at the -end of the block. - -```rust -// This is a module script. - -import "hello" as foo; // exported as sub-module 'foo' - -{ - import "world" as bar; // not exported - the module disappears at the end - // of the statement block and is not 'global' -} -``` diff --git a/doc/src/language/modules/import.md b/doc/src/language/modules/import.md deleted file mode 100644 index d335beeb..00000000 --- a/doc/src/language/modules/import.md +++ /dev/null @@ -1,113 +0,0 @@ -Import a Module -=============== - -{{#include ../../links.md}} - - -Before a module can be used (via an `import` statement) in a script, there must be a [module resolver] -registered into the [`Engine`], the default being the `FileModuleResolver`. - -See the section on [_Module Resolvers_][module resolver] for more details. - - -`import` Statement ------------------ - -A module can be _imported_ via the `import` statement, and be given a name. -Its members can be accessed via '`::`' similar to C++. - -A module that is only `import`-ed but not under any module name is commonly used for initialization purposes, -where the module script contains initialization statements that puts the functions registered with the -[`Engine`] into a particular state. - -```rust -import "crypto_init"; // run the script file 'crypto_init.rhai' without creating an imported module - -import "crypto" as lock; // run the script file 'crypto.rhai' and import it as a module named 'lock' - -const SECRET_NUMBER = 42; - -let mod_file = "crypto_" + SECRET_NUMBER; - -import mod_file as my_mod; // load the script file "crypto_42.rhai" and import it as a module named 'my_mod' - // notice that module path names can be dynamically constructed! - // any expression that evaluates to a string is acceptable after the 'import' keyword - -lock::encrypt(secret); // use functions defined under the module via '::' - -lock::hash::sha256(key); // sub-modules are also supported - -print(lock::status); // module variables are constants - -lock::status = "off"; // <- runtime error - cannot modify a constant -``` - - -Scoped Imports --------------- - -`import` statements are _scoped_, meaning that they are only accessible inside the scope that they're imported. - -They can appear anywhere a normal statement can be, but in the vast majority of cases `import` statements are -group at the beginning of a script. It is not advised to deviate from this common practice unless -there is a _Very Good Reason™_. - -Especially, do not place an `import` statement within a loop; doing so will repeatedly re-load the same module -during every iteration of the loop! - -```rust -let mod = "crypto"; - -if secured { // new block scope - import mod as c; // import module (the path needs not be a constant string) - - c::encrypt(key); // use a function in the module -} // the module disappears at the end of the block scope - -c::encrypt(others); // <- this causes a run-time error because the 'crypto' module - // is no longer available! - -for x in range(0, 1000) { - import "crypto" as c; // <- importing a module inside a loop is a Very Bad Idea™ - - c.encrypt(something); -} -``` - - -Recursive Imports ----------------- - -Beware of _import cycles_ – i.e. recursively loading the same module. This is a sure-fire way to -cause a stack overflow in the [`Engine`], unless stopped by setting a limit for [maximum number of modules]. - -For instance, importing itself always causes an infinite recursion: - -```rust --------------- -| hello.rhai | --------------- - -import "hello" as foo; // import itself - infinite recursion! - -foo::do_something(); -``` - -Modules cross-referencing also cause infinite recursion: - -```rust --------------- -| hello.rhai | --------------- - -import "world" as foo; -foo::do_something(); - - --------------- -| world.rhai | --------------- - -import "hello" as bar; -bar::do_something_else(); -``` diff --git a/doc/src/language/modules/index.md b/doc/src/language/modules/index.md deleted file mode 100644 index dd8c928a..00000000 --- a/doc/src/language/modules/index.md +++ /dev/null @@ -1,14 +0,0 @@ -Modules -======= - -{{#include ../../links.md}} - -Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_. -Modules can be disabled via the [`no_module`] feature. - -A module is of the type `Module` and holds a collection of functions, variables, [type iterators] and sub-modules. -It may be created entirely from Rust functions, or it may encapsulate a Rhai script together with the functions -and variables defined by that script. - -Other scripts can then load this module and use the functions and variables exported -as if they were defined inside the same script. diff --git a/doc/src/language/num-fn.md b/doc/src/language/num-fn.md deleted file mode 100644 index 599b03c1..00000000 --- a/doc/src/language/num-fn.md +++ /dev/null @@ -1,46 +0,0 @@ -Numeric Functions -================ - -{{#include ../links.md}} - -Integer Functions ----------------- - -The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`]) -operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: - -| Function | No available under | Description | -| -------- | :----------------: | ----------------------------------------------------------------------- | -| `abs` | | absolute value | -| `sign` | | returns -1 (`INT`) if the number is negative, +1 if positive, 0 if zero | - - -Floating-Point Functions ------------------------ - -The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`]) -operate on `f64` only: - -| Category | Functions | -| ---------------- | --------------------------------------------------------------------- | -| Trigonometry | `sin`, `cos`, `tan`, `sinh`, `cosh`, `tanh` in degrees | -| Arc-trigonometry | `asin`, `acos`, `atan`, `asinh`, `acosh`, `atanh` in degrees | -| Square root | `sqrt` | -| Exponential | `exp` (base _e_) | -| Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) | -| Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` methods and properties | -| Conversion | [`to_int`] | -| Testing | `is_nan`, `is_finite`, `is_infinite` methods and properties | - - -Conversion Functions -------------------- - -The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`]) -parse numbers: - -| Function | No available under | Description | -| --------------- | :----------------: | --------------------------------------------------- | -| [`to_float`] | [`no_float`] | converts an integer type to `FLOAT` | -| [`parse_int`] | | converts a [string] to `INT` with an optional radix | -| [`parse_float`] | [`no_float`] | converts a [string] to `FLOAT` | diff --git a/doc/src/language/num-op.md b/doc/src/language/num-op.md deleted file mode 100644 index a897c7ae..00000000 --- a/doc/src/language/num-op.md +++ /dev/null @@ -1,51 +0,0 @@ -Numeric Operators -================= - -{{#include ../links.md}} - -Numeric operators generally follow C styles. - -Unary Operators ---------------- - -| Operator | Description | -| -------- | ----------- | -| `+` | positive | -| `-` | negative | - -```rust -let number = -5; - -number = -5 - +5; -``` - -Binary Operators ----------------- - -| Operator | Description | Integers only | -| --------------- | ---------------------------------------------------- | :-----------: | -| `+` | plus | | -| `-` | minus | | -| `*` | multiply | | -| `/` | divide (integer division if acting on integer types) | | -| `%` | modulo (remainder) | | -| `~` | power | | -| `&` | bit-wise _And_ | Yes | -| \| | bit-wise _Or_ | Yes | -| `^` | bit-wise _Xor_ | Yes | -| `<<` | left bit-shift | Yes | -| `>>` | right bit-shift | Yes | - -```rust -let x = (1 + 2) * (6 - 4) / 2; // arithmetic, with parentheses - -let reminder = 42 % 10; // modulo - -let power = 42 ~ 2; // power (i64 and f64 only) - -let left_shifted = 42 << 3; // left shift - -let right_shifted = 42 >> 3; // right shift - -let bit_op = 42 | 99; // bit masking -``` diff --git a/doc/src/language/numbers.md b/doc/src/language/numbers.md deleted file mode 100644 index 78a4f236..00000000 --- a/doc/src/language/numbers.md +++ /dev/null @@ -1,22 +0,0 @@ -Numbers -======= - -{{#include ../links.md}} - -Integer numbers follow C-style format with support for decimal, binary ('`0b`'), octal ('`0o`') and hex ('`0x`') notations. - -The default system integer type (also aliased to `INT`) is `i64`. It can be turned into `i32` via the [`only_i32`] feature. - -Floating-point numbers are also supported if not disabled with [`no_float`]. The default system floating-point type is `i64` -(also aliased to `FLOAT`). It can be turned into `f32` via the [`f32_float`] feature. - -'`_`' separators can be added freely and are ignored within a number – except at the very beginning or right after -a decimal point ('`.`'). - -| Format | Type | -| --------------------- | ---------------- | -| `123_345`, `-42` | `INT` in decimal | -| `0o07_76` | `INT` in octal | -| `0xabcd_ef` | `INT` in hex | -| `0b0101_1001` | `INT` in binary | -| `123_456.789`, `-42.` | `FLOAT` | diff --git a/doc/src/language/object-maps-oop.md b/doc/src/language/object-maps-oop.md deleted file mode 100644 index f96fda04..00000000 --- a/doc/src/language/object-maps-oop.md +++ /dev/null @@ -1,39 +0,0 @@ -Special Support for OOP via Object Maps -====================================== - -{{#include ../links.md}} - -[Object maps] can be used to simulate [object-oriented programming (OOP)][OOP] by storing data -as properties and methods as properties holding [function pointers]. - -If an [object map]'s property holds a [function pointer], the property can simply be called like -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] 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 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. - -```rust -let obj = #{ - data: 40, - action: || this.data += x // 'action' holds a closure - }; - -obj.action(2); // calls the function pointer with `this` bound to 'obj' - -obj.call(obj.action, 2); // <- the above de-sugars to this - -obj.data == 42; - -// To achieve the above with normal function pointer call will fail. -fn do_action(map, x) { map.data += x; } // 'map' is a copy - -obj.action = Fn("do_action"); - -obj.action.call(obj, 2); // 'obj' is passed as a copy by value -``` diff --git a/doc/src/language/object-maps.md b/doc/src/language/object-maps.md deleted file mode 100644 index 8dcdbef5..00000000 --- a/doc/src/language/object-maps.md +++ /dev/null @@ -1,169 +0,0 @@ -Object Maps -=========== - -{{#include ../links.md}} - -Object maps are hash dictionaries. Properties are all [`Dynamic`] and can be freely added and retrieved. - -The Rust type of a Rhai object map is `rhai::Map`. - -[`type_of()`] an object map returns `"map"`. - -Object maps are disabled via the [`no_object`] feature. - -The maximum allowed size of an object map can be controlled via `Engine::set_max_map_size` -(see [maximum size of object maps]). - - -Object Map Literals ------------------- - -Object map literals are built within braces '`#{`' ... '`}`' (_name_ `:` _value_ syntax similar to Rust) -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 ------------------ - -### Dot Notation - -The _dot notation_ allows only property names that follow the same naming rules as [variables]. - -> _object_ `.` _property_ - -### 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 ------------------ - -The following methods (defined in the [`BasicMapPackage`][packages] but excluded if using a [raw `Engine`]) -operate on object maps: - -| Function | Parameter(s) | Description | -| ---------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `has`, `in` operator | 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 | -| `==` operator | 1) first object map
2) second object map | are the two object map the same (elements compared with the `==` operator, if defined)? | -| `!=` operator | 1) first object map
2) second object map | are the two object map different (elements compared with the `==` operator, if defined)? | -| `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 --------- - -```rust -let y = #{ // object map literal with 3 properties - a: 1, - bar: "hello", - "baz!$@": 123.456, // like JavaScript, you can use any string as property names... - "": false, // even the empty string! - - a: 42 // <- syntax error: duplicated property name -}; - -y.a = 42; // access via dot notation -y.baz!$@ = 42; // <- syntax error: only proper variable names allowed in dot notation -y."baz!$@" = 42; // <- syntax error: strings not allowed in dot notation - -y.a == 42; - -y["baz!$@"] == 123.456; // access via index notation - -"baz!$@" in y == true; // use 'in' to test if a property exists in the object map -("z" in y) == false; - -ts.obj = y; // object maps can be assigned completely (by value copy) -let foo = ts.list.a; -foo == 42; - -let foo = #{ a:1,}; // trailing comma is OK - -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 -} - -let foo = abc().b; -foo == 2; - -let foo = y["a"]; -foo == 42; - -y.has("a") == true; -y.has("xyz") == false; - -y.xyz == (); // a non-existing property returns '()' -y["xyz"] == (); - -y.len() == 3; - -y.remove("a") == 1; // remove property - -y.len() == 2; -y.has("a") == false; - -for name in y.keys() { // get an array of all the property names via 'keys' - print(name); -} - -for val in y.values() { // get an array of all the property values via 'values' - print(val); -} - -y.clear(); // empty the object map - -y.len() == 0; -``` - - -No Support for Property Getters ------------------------------- - -In order not to affect the speed of accessing properties in an object map, new property -[getters][getters/setters] cannot be registered because they conflict with the syntax of -property access. - -A property [getter][getters/setters] function registered via `Engine::register_get`, for example, -for a `Map` will never be found – instead, the property will be looked up in the object map. - -Therefore, _method-call_ notation must be used for built-in properties: - -```rust -map.len // access property 'len', returns '()' if not found - -map.len() // returns the number of properties - -map.keys // access property 'keys', returns '()' if not found - -map.keys() // returns array of all property names - -map.values // access property 'values', returns '()' if not found - -map.values() // returns array of all property values -``` diff --git a/doc/src/language/overload.md b/doc/src/language/overload.md deleted file mode 100644 index 2ec2fd4e..00000000 --- a/doc/src/language/overload.md +++ /dev/null @@ -1,29 +0,0 @@ -Function Overloading -=================== - -{{#include ../links.md}} - -[Functions] defined in script can be _overloaded_ by _arity_ (i.e. they are resolved purely upon the function's _name_ -and _number_ of parameters, but not parameter _types_ since all parameters are the same type – [`Dynamic`]). - -New definitions _overwrite_ previous definitions of the same name and number of parameters. - -```rust -fn foo(x,y,z) { print("Three!!! " + x + "," + y + "," + z); } - -fn foo(x) { print("One! " + x); } - -fn foo(x,y) { print("Two! " + x + "," + y); } - -fn foo() { print("None."); } - -fn foo(x) { print("HA! NEW ONE! " + x); } // overwrites previous definition - -foo(1,2,3); // prints "Three!!! 1,2,3" - -foo(42); // prints "HA! NEW ONE! 42" - -foo(1,2); // prints "Two!! 1,2" - -foo(); // prints "None." -``` diff --git a/doc/src/language/print-debug.md b/doc/src/language/print-debug.md deleted file mode 100644 index 996e6aac..00000000 --- a/doc/src/language/print-debug.md +++ /dev/null @@ -1,72 +0,0 @@ -`print` and `debug` -=================== - -{{#include ../links.md}} - -The `print` and `debug` functions default to printing to `stdout`, with `debug` using standard debug formatting. - -```rust -print("hello"); // prints hello to stdout - -print(1 + 2 + 3); // prints 6 to stdout - -print("hello" + 42); // prints hello42 to stdout - -debug("world!"); // prints "world!" to stdout using debug formatting -``` - -Override `print` and `debug` with Callback Functions --------------------------------------------------- - -When embedding Rhai into an application, it is usually necessary to trap `print` and `debug` output -(for logging into a tracking log, for example) with the `Engine::on_print` and `Engine::on_debug` methods: - -```rust -// Any function or closure that takes an '&str' argument can be used to override 'print'. -engine.on_print(|x| println!("hello: {}", x)); - -// Any function or closure that takes a '&str' and a 'Position' argument can be used to -// override 'debug'. -engine.on_debug(|x, src, pos| println!("DEBUG of {} at {:?}: {}", src.unwrap_or("unknown"), pos, x)); - -// Example: quick-'n-dirty logging -let logbook = Arc::new(RwLock::new(Vec::::new())); - -// Redirect print/debug output to 'log' -let log = logbook.clone(); -engine.on_print(move |s| log.write().unwrap().push(format!("entry: {}", s))); - -let log = logbook.clone(); -engine.on_debug(move |s, src, pos| log.write().unwrap().push( - format!("DEBUG of {} at {:?}: {}", src.unwrap_or("unknown"), pos, s) - )); - -// Evaluate script -engine.eval::<()>(script)?; - -// 'logbook' captures all the 'print' and 'debug' output -for entry in logbook.read().unwrap().iter() { - println!("{}", entry); -} -``` - - -`on_debug` Callback Signature ------------------------------ - -The function signature passed to `Engine::on_debug` takes the following form: - -> `Fn(text: &str, source: Option<&str>, pos: Position) + 'static` - -where: - -| Parameter | Type | Description | -| --------- | :------------: | --------------------------------------------------------------- | -| `text` | `&str` | text to display | -| `source` | `Option<&str>` | source of the current evaluation, if any | -| `pos` | `Position` | position (line number and character offset) of the `debug` call | - -The _source_ of a script evaluation is any text string provided to an [`AST`] via the `AST::set_source` method. - -If a [module] is loaded via an [`import`] statement, then the _source_ of functions defined -within the module will be the module's _path_. diff --git a/doc/src/language/return.md b/doc/src/language/return.md deleted file mode 100644 index 41c063f9..00000000 --- a/doc/src/language/return.md +++ /dev/null @@ -1,16 +0,0 @@ -Return Values -============= - -{{#include ../links.md}} - -The `return` statement is used to immediately stop evaluation and exist the current context -(typically a function call) yielding a _return value_. - -```rust -return; // equivalent to return (); - -return 123 + 456; // returns 579 -``` - -A `return` statement at _global_ level stop the entire script evaluation, -the return value is taken as the result of the script evaluation. diff --git a/doc/src/language/statements.md b/doc/src/language/statements.md deleted file mode 100644 index a4fad876..00000000 --- a/doc/src/language/statements.md +++ /dev/null @@ -1,42 +0,0 @@ -Statements -========== - -{{#include ../links.md}} - -Terminated by '`;`' ------------------- - -Statements are terminated by semicolons '`;`' and they are mandatory, -except for the _last_ statement in a _block_ (enclosed by '`{`' .. '`}`' pairs) where it can be omitted. - -Semicolons can also be omitted if the statement ends with a block itself -(e.g. the `if`, `while`, `for` and `loop` statements). - -```rust -let a = 42; // normal assignment statement -let a = foo(42); // normal function call statement -foo < 42; // normal expression as statement - -let a = { 40 + 2 }; // 'a' is set to the value of the statement block, which is the value of the last statement -// ^ the last statement does not require a terminating semicolon (although it also works with it) -// ^ semicolon required here to terminate the assignment statement; it is a syntax error without it - -if foo { a = 42 } -// ^ there is no need to terminate an if-statement with a semicolon - -4 * 10 + 2 // a statement which is just one expression - no ending semicolon is OK - // because it is the last statement of the whole block -``` - - -Statement Expression --------------------- - -A statement can be used anywhere where an expression is expected. These are called, for lack of a more -creative name, "statement expressions." - -The _last_ statement of a statement block is _always_ the block's return value when used as a statement, -_regardless_ of whether it is terminated by a semicolon or not. This is different from Rust where, -if the last statement is terminated by a semicolon, the block's return value is taken to be `()`. - -If the last statement has no return value (e.g. variable definitions, assignments) then it is assumed to be [`()`]. diff --git a/doc/src/language/string-fn.md b/doc/src/language/string-fn.md deleted file mode 100644 index db3d8b88..00000000 --- a/doc/src/language/string-fn.md +++ /dev/null @@ -1,65 +0,0 @@ -Built-in String Functions -======================== - -{{#include ../links.md}} - -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` | 1) target length
2) character/string to pad | pads the string with a character or a string 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) _(optional)_ start index | 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) _(optional)_ number of characters to extract, none if < 0 | 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) _(optional)_ number of characters to retain, none if < 0 | retains only a portion of the string | -| `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 --------- - -```rust -let full_name == " Bob C. Davis "; -full_name.len == 14; - -full_name.trim(); -full_name.len == 12; -full_name == "Bob C. Davis"; - -full_name.pad(15, '$'); -full_name.len == 15; -full_name == "Bob C. Davis$$$"; - -let n = full_name.index_of('$'); -n == 12; - -full_name.index_of("$$", n + 1) == 13; - -full_name.sub_string(n, 3) == "$$$"; - -full_name.truncate(6); -full_name.len == 6; -full_name == "Bob C."; - -full_name.replace("Bob", "John"); -full_name.len == 7; -full_name == "John C."; - -full_name.contains('C') == true; -full_name.contains("John") == true; - -full_name.crop(5); -full_name == "C."; - -full_name.crop(0, 1); -full_name == "C"; - -full_name.clear(); -full_name.len == 0; -``` diff --git a/doc/src/language/strings-chars.md b/doc/src/language/strings-chars.md deleted file mode 100644 index f814288e..00000000 --- a/doc/src/language/strings-chars.md +++ /dev/null @@ -1,133 +0,0 @@ -Strings and Characters -===================== - -{{#include ../links.md}} - -String in Rhai contain any text sequence of valid Unicode characters. -Internally strings are stored in UTF-8 encoding. - -Strings can be built up from other strings and types via the `+` operator -(provided by the [`MoreStringPackage`][packages] but excluded if using a [raw `Engine`]). -This is particularly useful when printing output. - -[`type_of()`] a string returns `"string"`. - -The maximum allowed length of a string can be controlled via `Engine::set_max_string_size` -(see [maximum length of strings]). - - -The `ImmutableString` Type -------------------------- - -All strings in Rhai are implemented as `ImmutableString` (see [standard types]). -An `ImmutableString` does not change and can be shared. - -Modifying an `ImmutableString` causes it first to be cloned, and then the modification made to the copy. - -### **IMPORTANT** – Avoid `String` Parameters - -`ImmutableString` should be used in place of `String` for function parameters because using -`String` is very inefficient (the `String` argument is cloned during every call). - -A alternative is to use `&str` which de-sugars to `ImmutableString`. - -```rust -fn slow(s: String) -> i64 { ... } // string is cloned each call - -fn fast1(s: ImmutableString) -> i64 { ... } // cloning 'ImmutableString' is cheap - -fn fast2(s: &str) -> i64 { ... } // de-sugars to above -``` - - -String and Character Literals ----------------------------- - -String and character literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') -and hex ('`\x`_xx_') escape sequences. - -Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full, -32-bit extended Unicode code points. - -Standard escape sequences: - -| 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 ----------------------------- - -Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`'s!), -but nevertheless there are major differences. - -In Rhai a string is the same as an array of Unicode characters and can be directly indexed (unlike Rust). - -This is similar to most other languages where strings are internally represented not as UTF-8 but as arrays of multi-byte -Unicode characters. - -Individual characters within a Rhai string can also be replaced just as if the string is an array of Unicode characters. - -In Rhai, there are also no separate concepts of `String` and `&str` as in Rust. - - -Examples --------- - -```rust -let name = "Bob"; -let middle_initial = 'C'; -let last = "Davis"; - -let full_name = name + " " + middle_initial + ". " + last; -full_name == "Bob C. Davis"; - -// String building with different types -let age = 42; -let record = full_name + ": age " + age; -record == "Bob C. Davis: age 42"; - -// Unlike Rust, Rhai strings can be indexed to get a character -// (disabled with 'no_index') -let c = record[4]; -c == 'C'; - -ts.s = record; // custom type properties can take strings - -let c = ts.s[4]; -c == 'C'; - -let c = "foo"[0]; // indexing also works on string literals... -c == 'f'; - -let c = ("foo" + "bar")[5]; // ... and expressions returning strings -c == 'r'; - -// Escape sequences in strings -record += " \u2764\n"; // escape sequence of '❤' in Unicode -record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line - -// Unlike Rust, Rhai strings can be directly modified character-by-character -// (disabled with 'no_index') -record[4] = '\x58'; // 0x58 = 'X' -record == "Bob X. Davis: age 42 ❤\n"; - -// Use 'in' to test if a substring (or character) exists in a string -"Davis" in record == true; -'X' in record == true; -'C' in record == false; - -// Strings can be iterated with a 'for' statement, yielding characters -for ch in record { - print(ch); -} -``` diff --git a/doc/src/language/switch.md b/doc/src/language/switch.md deleted file mode 100644 index 08d380ee..00000000 --- a/doc/src/language/switch.md +++ /dev/null @@ -1,111 +0,0 @@ -`switch` Expression -=================== - -{{#include ../links.md}} - -The `switch` _expression_ allows matching on literal values, and it mostly follows Rust's -`match` syntax: - -```c -switch calc_secret_value(x) { - 1 => print("It's one!"), - 2 => { - print("It's two!"); - print("Again!"); - } - 3 => print("Go!"), - // _ is the default when no cases match - _ => print("Oops! Something's wrong: " + x) -} -``` - - -Expression, Not Statement ------------------------- - -`switch` is not a statement, but an expression. This means that a `switch` expression can -appear anywhere a regular expression can, e.g. as function call arguments. - -```c -let x = switch foo { 1 => true, _ => false }; - -func(switch foo { - "hello" => 42, - "world" => 123, - _ => 0 -}); - -// The above is somewhat equivalent to: - -let x = if foo == 1 { true } else { false }; - -if foo == "hello" { - func(42); -} else if foo == "world" { - func(123); -} else { - func(0); -} -``` - - -Array and Object Map Literals Also Work --------------------------------------- - -The `switch` expression can match against any _literal_, including [array] and [object map] literals. - -```c -// Match on arrays -switch [foo, bar, baz] { - ["hello", 42, true] => { ... } - ["hello", 123, false] => { ... } - ["world", 1, true] => { ... } - _ => { ... } -} - -// Match on object maps -switch map { - #{ a: 1, b: 2, c: true } => { ... } - #{ a: 42, d: "hello" } => { ... } - _ => { ... } -} -``` - -Switching on [arrays] is very useful when working with Rust enums (see [this chapter]({{rootUrl}}/patterns/enums.md) -for more details). - - -Difference From `if`-`else if` Chain ------------------------------------ - -Although a `switch` expression looks _almost_ the same as an `if`-`else if` chain, -there are subtle differences between the two. - -### Look-up Table vs `x == y` - -A `switch` expression matches through _hashing_ via a look-up table. -Therefore, matching is very fast. Walking down an `if`-`else if` chain -is _much_ slower. - -On the other hand, operators can be [overloaded][operator overloading] in Rhai, -meaning that it is possible to override the `==` operator for integers such -that `x == y` returns a different result from the built-in default. - -`switch` expressions do _not_ use the `==` operator for comparison; -instead, they _hash_ the data values and jump directly to the correct -statements via a pre-compiled look-up table. This makes matching extremely -efficient, but it also means that [overloading][operator overloading] -the `==` operator will have no effect. - -Therefore, in environments where it is desirable to [overload][operator overloading] -the `==` operator – though it is difficult to think of valid scenarios where you'd want -`1 == 1` to return something other than `true` – avoid using the `switch` expression. - -### Efficiency - -Because the `switch` expression works through a look-up table, it is very efficient -even for _large_ number of cases; in fact, switching is an O(1) operation regardless -of the size of the data and number of cases to match. - -A long `if`-`else if` chain becomes increasingly slower with each additional case -because essentially an O(n) _linear scan_ is performed. diff --git a/doc/src/language/throw.md b/doc/src/language/throw.md deleted file mode 100644 index a11a8074..00000000 --- a/doc/src/language/throw.md +++ /dev/null @@ -1,52 +0,0 @@ -Throw Exception on Error -======================= - -{{#include ../links.md}} - -All of [`Engine`]'s evaluation/consuming methods return `Result>` -with `EvalAltResult` holding error information. - -To deliberately return an error during an evaluation, use the `throw` keyword. - -```rust -if some_bad_condition_has_happened { - throw error; // 'throw' any value as the exception -} - -throw; // defaults to '()' -``` - -Exceptions thrown via `throw` in the script can be captured in Rust by matching -`Err(Box)` with the exception value -captured by `value`. - -```rust -let result = engine.eval::(r#" - let x = 42; - - if x > 0 { - throw x; - } -"#); - -println!("{}", result); // prints "Runtime error: 42 (line 5, position 15)" -``` - - -Catch a Thrown Exception ------------------------- - -It is possible to _catch_ an exception instead of having it abort the evaluation -of the entire script via the [`try` ... `catch`]({{rootUrl}}/language/try-catch.md) -statement common to many C-like languages. - -```rust -try -{ - throw 42; -} -catch (err) // 'err' captures the thrown exception value -{ - print(err); // prints 42 -} -``` diff --git a/doc/src/language/timestamps.md b/doc/src/language/timestamps.md deleted file mode 100644 index 0a8a0adc..00000000 --- a/doc/src/language/timestamps.md +++ /dev/null @@ -1,40 +0,0 @@ -`timestamp` -=========== - -{{#include ../links.md}} - -Timestamps are provided by the [`BasicTimePackage`][packages] (excluded if using a [raw `Engine`]) -via the `timestamp` function. - -Timestamps are not available under [`no_std`]. - -The Rust type of a timestamp is `std::time::Instant` ([`instant::Instant`] in [WASM] builds). - -[`type_of()`] a timestamp returns `"timestamp"`. - - -Built-in Functions ------------------ - -The following methods (defined in the [`BasicTimePackage`][packages] but excluded if using a [raw `Engine`]) operate on timestamps: - -| Function | Parameter(s) | Description | -| ----------------------------- | ------------------------------------------- | -------------------------------------------------------- | -| `elapsed` method and property | _none_ | returns the number of seconds since the timestamp | -| `-` operator | 1) later timestamp
2) earlier timestamp | returns the number of seconds between the two timestamps | -| `+` operator | number of seconds to add | returns a new timestamp | -| `-` operator | number of seconds to subtract | returns a new timestamp | - - -Examples --------- - -```rust -let now = timestamp(); - -// Do some lengthy operation... - -if now.elapsed > 30.0 { - print("takes too long (over 30 seconds)!") -} -``` diff --git a/doc/src/language/try-catch.md b/doc/src/language/try-catch.md deleted file mode 100644 index a132fc61..00000000 --- a/doc/src/language/try-catch.md +++ /dev/null @@ -1,108 +0,0 @@ -Catch Exceptions -================ - -{{#include ../links.md}} - - -When an [exception] is thrown via a [`throw`] statement, evaluation of the script halts -and the [`Engine`] returns with `Err(Box)` containing the -exception value that has been thrown. - -It is possible, via the `try` ... `catch` statement, to _catch_ exceptions, optionally -with an _error variable_. - -```rust -// Catch an exception and capturing its value -try -{ - throw 42; -} -catch (err) // 'err' captures the thrown exception value -{ - print(err); // prints 42 -} - -// Catch an exception without capturing its value -try -{ - print(42/0); // deliberate divide-by-zero exception -} -catch // no error variable - exception value is discarded -{ - print("Ouch!"); -} - -// Exception in the 'catch' block -try -{ - print(42/0); // throw divide-by-zero exception -} -catch -{ - print("You seem to be dividing by zero here..."); - - throw "die"; // a 'throw' statement inside a 'catch' block - // throws a new exception -} -``` - - -Re-Throw Exception ------------------- - -Like the `try` ... `catch` syntax in most languages, it is possible to _re-throw_ -an exception within the `catch` block simply by another [`throw`] statement without -a value. - - -```rust -try -{ - // Call something that will throw an exception... - do_something_bad_that_throws(); -} -catch -{ - print("Oooh! You've done something real bad!"); - - throw; // 'throw' without a value within a 'catch' block - // re-throws the original exception -} - -``` - - -Catchable Exceptions --------------------- - -Many script-oriented exceptions can be caught via `try` ... `catch`: - -| Error type | Error value | -| --------------------------------------------- | :------------------------: | -| Runtime error thrown by a [`throw`] statement | value in `throw` statement | -| Other runtime error | error message [string] | -| Arithmetic error | error message [string] | -| Variable not found | error message [string] | -| [Function] not found | error message [string] | -| [Module] not found | error message [string] | -| Unbound [`this`] | error message [string] | -| Data type mismatch | error message [string] | -| Assignment to a calculated/constant value | error message [string] | -| [Array]/[string] indexing out-of-bounds | error message [string] | -| Indexing with an inappropriate data type | error message [string] | -| Error in a dot expression | error message [string] | -| `for` statement without a [type iterator] | error message [string] | -| Error in an `in` expression | error message [string] | -| Data race detected | error message [string] | - - -Non-Catchable Exceptions ------------------------- - -Some exceptions _cannot_ be caught: - -* Syntax error during parsing -* System error – e.g. script file not found -* Script evaluation metrics over [safety limits]({{rootUrl}}/safety/index.md) -* Function calls nesting exceeding [maximum call stack depth] -* Script evaluation manually terminated diff --git a/doc/src/language/type-of.md b/doc/src/language/type-of.md deleted file mode 100644 index 45e3d0b0..00000000 --- a/doc/src/language/type-of.md +++ /dev/null @@ -1,47 +0,0 @@ -`type_of()` -=========== - -{{#include ../links.md}} - -The `type_of` function detects the actual type of a value. - -This is useful because all variables are [`Dynamic`] in nature. - -```rust -// Use 'type_of()' to get the actual types of values -type_of('c') == "char"; -type_of(42) == "i64"; - -let x = 123; -x.type_of() == "i64"; // method-call style is also OK -type_of(x) == "i64"; - -x = 99.999; -type_of(x) == "f64"; - -x = "hello"; -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/language/values-and-types.md b/doc/src/language/values-and-types.md deleted file mode 100644 index fbae2013..00000000 --- a/doc/src/language/values-and-types.md +++ /dev/null @@ -1,42 +0,0 @@ -Values and Types -=============== - -{{#include ../links.md}} - -The following primitive types are supported natively: - -| Category | Equivalent Rust types | [`type_of()`] | `to_string()` | -| -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | ----------------------- | -| **Integer number** | `u8`, `i8`, `u16`, `i16`,
`u32`, `i32` (default for [`only_i32`]),
`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. | -| **Floating-point number** (disabled with [`no_float`]) | `f32` (default for [`f32_float`]), `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. | -| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` | -| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. | -| **Immutable Unicode [string]** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`) | `"string"` | `"hello"` etc. | -| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | -| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | -| **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | `""` | -| **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` | -| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | -| **Shared value** (a reference-counted, shared [`Dynamic`] value, created via [automatic currying], disabled with [`no_closure`]) | | _the actual type_ | _actual value_ | -| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | -| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | -| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ | - -All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - -they even cannot be added together. This is very similar to Rust. - -The default integer type is `i64`. If other integer types are not needed, it is possible to exclude them and make a -smaller build with the [`only_i64`] feature. - -If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`. -This is useful on some 32-bit targets where using 64-bit integers incur a performance penalty. - -If no floating-point is needed or supported, use the [`no_float`] feature to remove it. - -[Strings] in Rhai are _immutable_, meaning that they can be shared but not modified. In actual, the `ImmutableString` type -is an alias to `Rc` or `Arc` (depending on the [`sync`] feature). -Any modification done to a Rhai string will cause the string to be cloned and the modifications made to the copy. - -The `to_string` function converts a standard type into a [string] for display purposes. - -The `to_debug` function converts a standard type into a [string] in debug format. diff --git a/doc/src/language/variables.md b/doc/src/language/variables.md deleted file mode 100644 index d2582906..00000000 --- a/doc/src/language/variables.md +++ /dev/null @@ -1,68 +0,0 @@ -Variables -========= - -{{#include ../links.md}} - -Valid Names ------------ - -Variables in Rhai follow normal C naming rules – must contain only ASCII letters, digits and underscores '`_`', -and cannot start with a digit. - -For example: '`_c3po`' and '`r2d2`' are valid variable names, but '`3abc`' is not. - -However, unlike Rust, a variable name must also contain at least one ASCII letter, and an ASCII letter must come before any digit. -In other words, the first character that is not an underscore '`_`' must be an ASCII letter and not a digit. - -Therefore, some names acceptable to Rust, like '`_`', '`_42foo`', '`_1`' etc., are not valid in Rhai. -This restriction is to reduce confusion because, for instance, '`_1`' can easily be misread (or mis-typed) as `-1`. - -Variable names are case _sensitive_. - -Variable names also cannot be the same as a [keyword]. - -### Unicode Standard Annex #31 Identifiers - -The [`unicode-xid-ident`] feature expands the allowed characters for variable names to the set defined by -[Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/). - - -Declare a Variable ------------------- - -Variables are declared using the `let` keyword. - -Variables do not have to be given an initial value. -If none is provided, it defaults to [`()`]. - -A variable defined within a statement block is _local_ to that block. - -Use `is_def_var` to detect if a variable is defined. - -```rust -let x; // ok - value is '()' -let x = 3; // ok -let _x = 42; // ok -let x_ = 42; // also ok -let _x_ = 42; // still ok - -let _ = 123; // <- syntax error: illegal variable name -let _9 = 9; // <- syntax error: illegal variable name - -let x = 42; // variable is 'x', lower case -let X = 123; // variable is 'X', upper case -x == 42; -X == 123; - -{ - let x = 999; // local variable 'x' shadows the 'x' in parent block - x == 999; // access to local 'x' -} -x == 42; // the parent block's 'x' is not changed - -is_def_var("x") == true; - -is_def_var("_x") == true; - -is_def_var("y") == false; -``` diff --git a/doc/src/language/while.md b/doc/src/language/while.md deleted file mode 100644 index 5b7a5ac8..00000000 --- a/doc/src/language/while.md +++ /dev/null @@ -1,20 +0,0 @@ -`while` Loop -============ - -{{#include ../links.md}} - -`while` loops follow C syntax. - -Like C, `continue` can be used to skip to the next iteration, by-passing all following statements; -`break` can be used to break out of the loop unconditionally. - -```rust -let x = 10; - -while x > 0 { - x -= 1; - if x < 6 { continue; } // skip to the next iteration - print(x); - if x == 5 { break; } // break out of while loop -} -``` diff --git a/doc/src/links.md b/doc/src/links.md deleted file mode 100644 index c2681d79..00000000 --- a/doc/src/links.md +++ /dev/null @@ -1,147 +0,0 @@ -[features]: {{rootUrl}}/start/features.md -[`unchecked`]: {{rootUrl}}/start/features.md -[`sync`]: {{rootUrl}}/start/features.md -[`no_optimize`]: {{rootUrl}}/start/features.md -[`no_float`]: {{rootUrl}}/start/features.md -[`f32_float`]: {{rootUrl}}/start/features.md -[`only_i32`]: {{rootUrl}}/start/features.md -[`only_i64`]: {{rootUrl}}/start/features.md -[`no_index`]: {{rootUrl}}/start/features.md -[`no_object`]: {{rootUrl}}/start/features.md -[`no_function`]: {{rootUrl}}/start/features.md -[`no_module`]: {{rootUrl}}/start/features.md -[`no_closure`]: {{rootUrl}}/start/features.md -[`no_std`]: {{rootUrl}}/start/features.md -[`no-std`]: {{rootUrl}}/start/features.md -[`metadata`]: {{rootUrl}}/start/features.md -[`internals`]: {{rootUrl}}/start/features.md -[`unicode-xid-ident`]: {{rootUrl}}/start/features.md - -[minimal builds]: {{rootUrl}}/start/builds/minimal.md -[WASM]: {{rootUrl}}/start/builds/wasm.md -[playground]: https://alvinhochun.github.io/rhai-demo - -[`Engine`]: {{rootUrl}}/engine/hello-world.md -[traits]: {{rootUrl}}/rust/traits.md -[`private`]: {{rootUrl}}/engine/call-fn.md -[`call_fn`]: {{rootUrl}}/engine/call-fn.md -[`Func`]: {{rootUrl}}/engine/func.md -[`AST`]: {{rootUrl}}/engine/compile.md -[`eval_expression`]: {{rootUrl}}/engine/expressions.md -[`eval_expression_with_scope`]: {{rootUrl}}/engine/expressions.md -[raw `Engine`]: {{rootUrl}}/engine/raw.md -[built-in operators]: {{rootUrl}}/engine/raw.md#built-in-operators -[package]: {{rootUrl}}/rust/packages/index.md -[packages]: {{rootUrl}}/rust/packages/index.md -[custom package]: {{rootUrl}}/rust/packages/create.md -[custom packages]: {{rootUrl}}/rust/packages/create.md -[plugin]: {{rootUrl}}/plugins/index.md -[plugins]: {{rootUrl}}/plugins/index.md -[plugin module]: {{rootUrl}}/plugins/module.md -[plugin modules]: {{rootUrl}}/plugins/module.md -[plugin function]: {{rootUrl}}/plugins/function.md -[plugin functions]: {{rootUrl}}/plugins/function.md -[functions metadata]: {{rootUrl}}/engine/metadata/index.md -[`Scope`]: {{rootUrl}}/engine/scope.md -[`serde`]: {{rootUrl}}/rust/serde.md - -[`type_of()`]: {{rootUrl}}/language/type-of.md -[`to_string()`]: {{rootUrl}}/language/values-and-types.md -[`()`]: {{rootUrl}}/language/values-and-types.md -[standard types]: {{rootUrl}}/language/values-and-types.md -[`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 -[getters/setters]: {{rootUrl}}/rust/getters-setters.md -[indexers]: {{rootUrl}}/rust/indexers.md -[type iterator]: {{rootUrl}}/language/iterator.md -[type iterators]: {{rootUrl}}/language/iterator.md - -[`instant::Instant`]: https://crates.io/crates/instant - -[`print`]: {{rootUrl}}/language/print-debug.md -[`debug`]: {{rootUrl}}/language/print-debug.md - -[keywords]: {{rootUrl}}/appendix/keywords.md -[keyword]: {{rootUrl}}/appendix/keywords.md - -[variable]: {{rootUrl}}/language/variables.md -[variables]: {{rootUrl}}/language/variables.md -[constant]: {{rootUrl}}/language/constants.md -[constants]: {{rootUrl}}/language/constants.md - -[string]: {{rootUrl}}/language/strings-chars.md -[strings]: {{rootUrl}}/language/strings-chars.md -[char]: {{rootUrl}}/language/strings-chars.md - -[array]: {{rootUrl}}/language/arrays.md -[arrays]: {{rootUrl}}/language/arrays.md -[`Array`]: {{rootUrl}}/language/arrays.md - -[`Map`]: {{rootUrl}}/language/object-maps.md -[object map]: {{rootUrl}}/language/object-maps.md -[object maps]: {{rootUrl}}/language/object-maps.md - -[`timestamp`]: {{rootUrl}}/language/timestamps.md -[timestamp]: {{rootUrl}}/language/timestamps.md -[timestamps]: {{rootUrl}}/language/timestamps.md - -[doc-comments]: {{rootUrl}}/language/doc-comments.md -[function]: {{rootUrl}}/language/functions.md -[functions]: {{rootUrl}}/language/functions.md -[function overloading]: {{rootUrl}}/rust/functions.md#function-overloading -[fallible function]: {{rootUrl}}/rust/fallible.md -[fallible functions]: {{rootUrl}}/rust/fallible.md -[`throw`]: {{rootUrl}}/language/throw.md -[exception]: {{rootUrl}}/language/throw.md -[exceptions]: {{rootUrl}}/language/throw.md -[function pointer]: {{rootUrl}}/language/fn-ptr.md -[function pointers]: {{rootUrl}}/language/fn-ptr.md -[currying]: {{rootUrl}}/language/fn-curry.md -[capture]: {{rootUrl}}/language/fn-capture.md -[automatic currying]: {{rootUrl}}/language/fn-closure.md -[closure]: {{rootUrl}}/language/fn-closure.md -[closures]: {{rootUrl}}/language/fn-closure.md -[function namespace]: {{rootUrl}}/language/fn-namespaces.md -[function namespaces]: {{rootUrl}}/language/fn-namespaces.md -[anonymous function]: {{rootUrl}}/language/fn-anon.md -[anonymous functions]: {{rootUrl}}/language/fn-anon.md -[operator overloading]: {{rootUrl}}/rust/operators.md - -[`Module`]: {{rootUrl}}/rust/modules/index.md -[module]: {{rootUrl}}/rust/modules/index.md -[modules]: {{rootUrl}}/rust/modules/index.md -[module resolver]: {{rootUrl}}/rust/modules/resolvers.md -[variable resolver]: {{rootUrl}}/engine/var.md -[`export`]: {{rootUrl}}/language/modules/export.md -[`import`]: {{rootUrl}}/language/modules/import.md - -[`eval`]: {{rootUrl}}/language/eval.md - -[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 -[maximum number of modules]: {{rootUrl}}/safety/max-modules.md -[maximum length of strings]: {{rootUrl}}/safety/max-string-size.md -[maximum size of arrays]: {{rootUrl}}/safety/max-array-size.md -[maximum size of object maps]: {{rootUrl}}/safety/max-map-size.md -[progress]: {{rootUrl}}/safety/progress.md - -[script optimization]: {{rootUrl}}/engine/optimize/index.md -[`OptimizationLevel::Full`]: {{rootUrl}}/engine/optimize/optimize-levels.md -[`OptimizationLevel::Simple`]: {{rootUrl}}/engine/optimize/optimize-levels.md -[`OptimizationLevel::None`]: {{rootUrl}}/engine/optimize/optimize-levels.md - -[disable keywords and operators]: {{rootUrl}}/engine/disable.md -[custom operator]: {{rootUrl}}/engine/custom-op.md -[custom operators]: {{rootUrl}}/engine/custom-op.md -[custom syntax]: {{rootUrl}}/engine/custom-syntax.md diff --git a/doc/src/patterns/config.md b/doc/src/patterns/config.md deleted file mode 100644 index 6af3ae10..00000000 --- a/doc/src/patterns/config.md +++ /dev/null @@ -1,163 +0,0 @@ -Loadable Configuration -====================== - -{{#include ../links.md}} - - -Usage Scenario --------------- - -* A system where settings and configurations are complex and logic-driven. - -* Where said system is too complex to configure via standard configuration file formats such as `JSON`, `TOML` or `YAML`. - -* The system is complex enough to require a full programming language to configure. Essentially _configuration by code_. - -* Yet the configuration must be flexible, late-bound and dynamically loadable, just like a configuration file. - - -Key Concepts ------------- - -* Leverage the loadable [modules] of Rhai. The [`no_module`] feature must not be on. - -* 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`]. - - -Implementation --------------- - -### Configuration Type - -```rust -#[derive(Debug, Clone, Default)] -struct Config { - pub id: String; - pub some_field: i64; - pub some_list: Vec; - pub some_map: HashMap; -} -``` - -### Make Shared Object - -```rust -pub type SharedConfig = Rc>; -``` - -Note: Use `Arc>` or `Arc>` when using the [`sync`] feature because the function -must then be `Send + Sync`. - -```rust -let config: SharedConfig = 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 via a closure. - -Therefore, it is not possible to use a [plugin module] to achieve this, and 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(); -engine.register_fn("config_set_id", move |id: String| *cfg.borrow_mut().id = id); - -let cfg = config.clone(); -engine.register_fn("config_get_id", move || cfg.borrow().id.clone()); - -let cfg = config.clone(); -engine.register_fn("config_set", move |value: i64| *cfg.borrow_mut().some_field = value); - -// Remember Rhai functions can be overloaded when designing the API. - -let cfg = config.clone(); -engine.register_fn("config_add", move |value: String| - 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())) -); - -let cfg = config.clone(); -engine.register_fn("config_add", move |key: String, value: bool| - 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) -); - -let cfg = config.clone(); -engine.register_fn("config_is_set", move |value: String| - cfg.borrow().some_map.get(&value).cloned().unwrap_or(false) -); -``` - -### Configuration Script - -```rust ------------------- -| my_config.rhai | ------------------- - -config_set_id("hello"); - -config_add("foo"); // add to list -config_add("bar", true); // add to map - -if config_contains("hey") || config_is_set("hey") { - config_add("baz", false); // add to map -} -``` - -### Load the Configuration - -```rust -import "my_config"; // run configuration script without creating a module - -let id = config_get_id(); - -id == "hello"; -``` - - -Consider a Custom Syntax ------------------------- - -This is probably one of the few scenarios where a [custom syntax] can be recommended. - -A properly-designed [custom syntax] can make the configuration file clean, simple to write, -easy to understand and quick to modify. - -For example, the above configuration example may be expressed by this custom syntax: - -```rust ------------------- -| my_config.rhai | ------------------- - -// Configure ID -id "hello"; - -// Add to list -list + "foo"; - -// Add to map -map "bar" => true; - -if config contains "hey" || config is_set "hey" { - map "baz" => false; -} -``` - -Notice that `contains` and `is_set` may be implemented as a [custom operator]. diff --git a/doc/src/patterns/control.md b/doc/src/patterns/control.md deleted file mode 100644 index 25d73231..00000000 --- a/doc/src/patterns/control.md +++ /dev/null @@ -1,144 +0,0 @@ -Scriptable Control Layer -======================== - -{{#include ../links.md}} - - -Usage Scenario --------------- - -* A system provides core functionalities, but no driving logic. - -* The driving logic must be dynamic and hot-loadable. - -* A script is used to drive the system and provide control intelligence. - - -Key Concepts ------------- - -* Expose a Control API. - -* 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 has a clear object structure, -use the [Singleton Command Object]({{rootUrl}}/patterns/singleton.md) pattern instead. - - -### Functional API - -Assume that a system provides the following functional API: - -```rust -struct EnergizerBunny; - -impl EnergizerBunny { - pub fn new () -> Self { ... } - pub fn go (&mut self) { ... } - pub fn stop (&mut self) { ... } - pub fn is_going (&self) { ... } - pub fn get_speed (&self) -> i64 { ... } - pub fn set_speed (&mut self, speed: i64) { ... } -} -``` - -### Wrap API in Shared Object - -```rust -pub type SharedBunny = Rc>; -``` - -Note: Use `Arc>` or `Arc>` when using the [`sync`] feature because the function -must then be `Send + Sync`. - -```rust -let bunny: SharedBunny = 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 via a closure. - -Therefore, it is not possible to use a [plugin module] to achieve this, and 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(); -engine.register_fn("bunny_power", move |on: bool| { - if on { - if b.borrow().is_going() { - println!("Still going..."); - } else { - b.borrow_mut().go(); - } - } else { - if b.borrow().is_going() { - b.borrow_mut().stop(); - } else { - println!("Already out of battery!"); - } - } -}); - -let b = bunny.clone(); -engine.register_fn("bunny_is_going", move || b.borrow().is_going()); - -let b = bunny.clone(); -engine.register_fn("bunny_get_speed", move || - if b.borrow().is_going() { b.borrow().get_speed() } else { 0 } -); - -let b = bunny.clone(); -engine.register_result_fn("bunny_set_speed", move |speed: i64| - if speed <= 0 { - return Err("Speed must be positive!".into()); - } else if speed > 100 { - return Err("Bunny will be going too fast!".into()); - } - - if b.borrow().is_going() { - b.borrow_mut().set_speed(speed) - } else { - return Err("Bunny is not yet going!".into()); - } - - Ok(Dynamic::UNIT) -); -``` - -### Use the API - -```rust -if !bunny_is_going() { bunny_power(true); } - -if bunny_get_speed() > 50 { bunny_set_speed(50); } -``` - - -Caveat ------- - -Although this usage pattern appears a perfect fit for _game_ logic, avoid writing the -_entire game_ in Rhai. Performance will not be acceptable. - -Implement as much functionalities of the game engine in Rust as possible. -Rhai integrates well with Rust so this is usually not a hinderance. - -Lift as much out of Rhai as possible. -Use Rhai only for the logic that _must_ be dynamic or hot-loadable. diff --git a/doc/src/patterns/dynamic-const.md b/doc/src/patterns/dynamic-const.md deleted file mode 100644 index e3fe7c06..00000000 --- a/doc/src/patterns/dynamic-const.md +++ /dev/null @@ -1,56 +0,0 @@ -Dynamic Constants Provider -========================= - -{{#include ../links.md}} - - -Usage Scenario --------------- - -* A system has a _large_ number of constants, but only a minor set will be used by any script. - -* The system constants are expensive to load. - -* The system constants set is too massive to push into a custom [`Scope`]. - -* The values of system constants are volatile and call-dependent. - - -Key Concepts ------------- - -* Use a [variable resolver] to intercept variable access. - -* Only load a variable when it is being used. - -* Perform a lookup based on variable name, and provide the correct data value. - -* May even perform back-end network access or look up the latest value from a database. - - -Implementation --------------- - -```rust -let mut engine = Engine::new(); - -// Create shared data provider. -// Assume that SystemValuesProvider::get(&str) -> Option gets a value. -let provider = Arc::new(SystemValuesProvider::new()); - -// Clone the shared provider -let db = provider.clone(); - -// Register a variable resolver. -// Move the shared provider into the closure. -engine.on_var(move |name, _, _, _| Ok(db.get(name).map(Dynamic::from))); -``` - - -Values are Constants --------------------- - -All values provided by a [variable resolver] are _constants_ due to their dynamic nature. -They cannot be assigned to. - -In order to change values in an external system, register a dedicated API for that purpose. diff --git a/doc/src/patterns/enums.md b/doc/src/patterns/enums.md deleted file mode 100644 index d76e62f9..00000000 --- a/doc/src/patterns/enums.md +++ /dev/null @@ -1,237 +0,0 @@ -Working With Rust Enums -======================= - -{{#include ../links.md}} - -Enums in Rust are typically used with _pattern matching_. Rhai is dynamic, so although -it integrates with Rust enum variants just fine (treated transparently as [custom types]), -it is impossible (short of registering a complete API) to distinguish between individual -enum variants or to extract internal data from them. - - -Simulate an Enum API --------------------- - -A [plugin module] is extremely handy in creating an entire API for a custom enum type. - -```rust -use rhai::{Engine, Dynamic, EvalAltResult}; -use rhai::plugin::*; - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -enum MyEnum { - Foo, - Bar(i64), - Baz(String, bool) -} - -// Create a plugin module with functions constructing the 'MyEnum' variants -#[export_module] -mod MyEnumModule { - // Constructors for 'MyEnum' variants - pub const Foo: &MyEnum = MyEnum::Foo; - pub fn Bar(value: i64) -> MyEnum { - MyEnum::Bar(value) - } - pub fn Baz(val1: String, val2: bool) -> MyEnum { - MyEnum::Baz(val1, val2) - } - // Access to fields - #[rhai_fn(get = "enum_type")] - pub fn get_type(a: &mut MyEnum) -> String { - match a { - MyEnum::Foo => "Foo".to_string(), - MyEnum::Bar(_) => "Bar".to_string(), - MyEnum::Baz(_, _) => "Baz".to_string() - } - } - #[rhai_fn(get = "field_0")] - pub fn get_field_0(a: &mut MyEnum) -> Dynamic { - match a { - MyEnum::Foo => Dynamic::UNIT, - MyEnum::Bar(x) => Dynamic::from(x), - MyEnum::Baz(x, _) => Dynamic::from(x) - } - } - #[rhai_fn(get = "field_1")] - pub fn get_field_1(a: &mut MyEnum) -> Dynamic { - match a { - MyEnum::Foo | MyEnum::Bar(_) => Dynamic::UNIT, - MyEnum::Baz(_, x) => Dynamic::from(x) - } - } - // Printing - #[rhai(global, name = "to_string", name = "print", name = "to_debug", name = "debug")] - pub fn to_string(a: &mut MyEnum) -> String { - format!("{:?}", a)) - } - #[rhai_fn(global, name = "+")] - pub fn add_to_str(s: &str, a: MyEnum) -> String { - format!("{}{:?}", s, a)) - } - #[rhai_fn(global, name = "+")] - pub fn add_str(a: &mut MyEnum, s: &str) -> String { - format!("{:?}", a).push_str(s)) - } - #[rhai_fn(global, name = "+=")] - pub fn append_to_str(s: &mut ImmutableString, a: MyEnum) -> String { - s += a.to_string()) - } - // '==' and '!=' operators - #[rhai_fn(global, name = "==")] - pub fn eq(a: &mut MyEnum, b: MyEnum) -> bool { - a == &b - } - #[rhai_fn(global, name = "!=")] - pub fn neq(a: &mut MyEnum, b: MyEnum) -> bool { - a != &b - } - // Array functions - #[rhai_fn(global, name = "push")] - pub fn append_to_array(list: &mut Array, item: MyEnum) { - list.push(Dynamic::from(item))); - } - #[rhai_fn(global, name = "+=")] - pub fn append_to_array_op(list: &mut Array, item: MyEnum) { - list.push(Dynamic::from(item))); - } - #[rhai_fn(global, name = "insert")] - pub fn insert_to_array(list: &mut Array, position: i64, item: MyEnum) { - if position <= 0 { - list.insert(0, Dynamic::from(item)); - } else if (position as usize) >= list.len() - 1 { - list.push(item); - } else { - list.insert(position as usize, Dynamic::from(item)); - } - } - #[rhai_fn(global, name = "pad")] - pub fn pad_array(list: &mut Array, len: i64, item: MyEnum) { - if len as usize > list.len() { list.resize(len as usize, item); } - } -} - -let mut engine = Engine::new(); - -// Load the module as the module namespace "MyEnum" -engine - .register_type_with_name::("MyEnum") - .register_static_module("MyEnum", exported_module!(MyEnumModule).into()); -``` - -With this API in place, working with enums feels almost the same as in Rust: - -```rust -let x = MyEnum::Foo; - -let y = MyEnum::Bar(42); - -let z = MyEnum::Baz("hello", true); - -x == MyEnum::Foo; - -y != MyEnum::Bar(0); - -// Detect enum types - -x.enum_type == "Foo"; - -y.enum_type == "Bar"; - -z.enum_type == "Baz"; - -// Extract enum fields - -y.field_0 == 42; - -y.field_1 == (); - -z.field_0 == "hello"; - -z.field_1 == true; -``` - -Since enums are internally treated as [custom types], they are not _literals_ and cannot be -used as a match case in `switch` expressions. This is quite a limitation because the equivalent -`match` statement is commonly used in Rust to work with enums and bind variables to -variant-internal data. - -It is possible, however, to `switch` through enum variants based on their types: - -```c -switch x.enum_type { - "Foo" => ..., - "Bar" => { - let value = foo.field_0; - ... - } - "Baz" => { - let val1 = foo.field_0; - let val2 = foo.field_1; - ... - } -} -``` - - -Use `switch` Through Arrays ---------------------------- - -Another way to work with Rust enums in a `switch` expression is through exposing the internal data -(or at least those that act as effective _discriminants_) of each enum variant as a variable-length -[array], usually with the name of the variant as the first item for convenience: - -```rust -use rhai::Array; - -engine.register_get("enum_data", |x: &mut Enum| { - match x { - Enum::Foo => vec![ "Foo".into() ] as Array, - - // Say, skip the data field because it is not - // used as a discriminant - Enum::Bar(value) => vec![ "Bar".into() ] as Array, - - // Say, all fields act as discriminants - Enum::Baz(val1, val2) => vec![ - "Baz".into(), val1.clone().into(), (*val2).into() - ] as Array - } -}); -``` - -Then it is a simple matter to match an enum via the `switch` expression: - -```c -// Assume 'value' = 'MyEnum::Baz("hello", true)' -// 'enum_data' creates a variable-length array with 'MyEnum' data -let x = switch value.enum_data { - ["Foo"] => 1, - ["Bar"] => value.field_1, - ["Baz", "hello", false] => 4, - ["Baz", "hello", true] => 5, - _ => 9 -}; - -x == 5; - -// Which is essentially the same as: -let x = switch [value.type, value.field_0, value.field_1] { - ["Foo", (), ()] => 1, - ["Bar", 42, ()] => 42, - ["Bar", 123, ()] => 123, - : - ["Baz", "hello", false] => 4, - ["Baz", "hello", true] => 5, - _ => 9 -} -``` - -Usually, a helper method returns an array of values that can uniquely determine -the switch case based on actual usage requirements – which means that it probably -skips fields that contain data instead of discriminants. - -Then `switch` is used to very quickly match through a large number of array shapes -and jump to the appropriate case implementation. - -Data fields can then be extracted from the enum independently. diff --git a/doc/src/patterns/events.md b/doc/src/patterns/events.md deleted file mode 100644 index 24574612..00000000 --- a/doc/src/patterns/events.md +++ /dev/null @@ -1,202 +0,0 @@ -Scriptable Event Handler with State -================================== - -{{#include ../links.md}} - - -Usage Scenario --------------- - -* A system sends _events_ that must be handled. - -* Flexibility in event handling must be provided, through user-side scripting. - -* State must be kept between invocations of event handlers. - -* Default implementations of event handlers can be provided. - - -Key Concepts ------------- - -* An _event handler_ object is declared that holds the following items: - * [`Engine`] with registered functions serving as an API, - * [`AST`] of the user script, - * a [`Scope`] containing state. - -* Upon an event, the appropriate event handler function in the script is called via [`Engine::call_fn`][`call_fn`]. - -* Optionally, trap the `EvalAltResult::ErrorFunctionNotFound` error to provide a default implementation. - - -Implementation --------------- - -### Declare Handler Object - -In most cases, it would be simpler to store an [`Engine`] instance together with the handler object -because it only requires registering all API functions only once. - -In rare cases where handlers are created and destroyed in a tight loop, a new [`Engine`] instance -can be created for each event. See [_One Engine Instance Per Call_](parallel.md) for more details. - -```rust -use rhai::{Engine, Scope, AST, EvalAltResult}; - -// Event handler -struct Handler { - // Scripting engine - pub engine: Engine, - // Use a custom 'Scope' to keep stored state - pub scope: Scope<'static>, - // Program script - pub ast: AST -} -``` - -### Register API for Any Custom Type - -[Custom types] are often used to hold state. The easiest way to register an entire API is via a [plugin module]. - -```rust -use rhai::plugin::*; - -// A custom type to a hold state value. -#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] -pub struct SomeType { - data: i64; -} - -#[export_module] -mod SomeTypeAPI { - #[rhai_fn(global)] - pub func1(obj: &mut SomeType) -> bool { ... } - #[rhai_fn(global)] - pub func2(obj: &mut SomeType) -> bool { ... } - pub process(data: i64) -> i64 { ... } - #[rhai_fn(get = "value")] - pub get_value(obj: &mut SomeType) -> i64 { obj.data } - #[rhai_fn(set = "value")] - pub set_value(obj: &mut SomeType, value: i64) { obj.data = value; } -} -``` - -### Initialize Handler Object - -Steps to initialize the event handler: - -1. Register an API with the [`Engine`], -2. Create a custom [`Scope`] to serve as the stored state, -3. Add default state variables into the custom [`Scope`], -4. Get the handler script and [compile][`AST`] it, -5. Store the compiled [`AST`] for future evaluations, -6. Run the [`AST`] to initialize event handler state variables. - -```rust -impl Handler { - pub new(path: impl Into) -> Self { - let mut engine = Engine::new(); - - // Register custom types and API's - engine - .register_type_with_name::("SomeType") - .register_global_module(exported_module!(SomeTypeAPI)); - - // Create a custom 'Scope' to hold state - let mut scope = Scope::new(); - - // Add initialized state into the custom 'Scope' - scope.push("state1", false); - scope.push("state2", SomeType::new(42)); - - // Compile the handler script. - // In a real application you'd be handling errors... - let ast = engine.compile_file(path).unwrap(); - - // Evaluate the script to initialize it and other state variables. - // In a real application you'd again be handling errors... - engine.consume_ast_with_scope(&mut scope, &ast).unwrap(); - - // The event handler is essentially these three items: - Handler { engine, scope, ast } - } -} -``` - -### Hook up events - -There is usually an interface or trait that gets called when an event comes from the system. - -Mapping an event from the system into a scripted handler is straight-forward: - -```rust -impl Handler { - // Say there are three events: 'start', 'end', 'update'. - // In a real application you'd be handling errors... - pub fn on_event(&mut self, event_name: &str, event_data: i64) -> Result<(), Error> { - let engine = &self.engine; - let scope = &mut self.scope; - let ast = &self.ast; - - match event_name { - // The 'start' event maps to function 'start'. - // In a real application you'd be handling errors... - "start" => engine.call_fn(scope, ast, "start", (event_data,))?, - - // The 'end' event maps to function 'end'. - // In a real application you'd be handling errors... - "end" => engine.call_fn(scope, ast, "end", (event_data,))?, - - // The 'update' event maps to function 'update'. - // This event provides a default implementation when the scripted function - // is not found. - "update" => - engine.call_fn(scope, ast, "update", (event_data,)) - .or_else(|err| match *err { - EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "update" => { - // Default implementation of 'update' event handler - self.scope.set_value("state2", SomeType::new(42)); - // Turn function-not-found into a success - Ok(Dynamic::UNIT) - } - _ => Err(err.into()) - })? - } - } -} -``` - -### Sample Handler Script - -Because the stored state is kept in a custom [`Scope`], it is possible for all functions defined -in the handler script to access and modify these state variables. - -The API registered with the [`Engine`] can be also used throughout the script. - -```rust -fn start(data) { - if state1 { - throw "Already started!"; - } - if state2.func1() || state2.func2() { - throw "Conditions not yet ready to start!"; - } - state1 = true; - state2.value = data; -} - -fn end(data) { - if !state1 { - throw "Not yet started!"; - } - if state2.func1() || state2.func2() { - throw "Conditions not yet ready to start!"; - } - state1 = false; - state2.value = data; -} - -fn update(data) { - state2.value += process(data); -} -``` diff --git a/doc/src/patterns/index.md b/doc/src/patterns/index.md deleted file mode 100644 index fb651ae8..00000000 --- a/doc/src/patterns/index.md +++ /dev/null @@ -1,7 +0,0 @@ -Usage Patterns -============== - -{{#include ../links.md}} - - -Leverage the full power and flexibility of Rhai in different scenarios. diff --git a/doc/src/patterns/multi-layer.md b/doc/src/patterns/multi-layer.md deleted file mode 100644 index 9092ca82..00000000 --- a/doc/src/patterns/multi-layer.md +++ /dev/null @@ -1,117 +0,0 @@ -Multi-Layer Functions -===================== - -{{#include ../links.md}} - - -Usage Scenario --------------- - -* A system is divided into separate _layers_, each providing logic in terms of scripted [functions]. - -* A lower layer provides _default implementations_ of certain functions. - -* Higher layers each provide progressively more specific implementations of the same functions. - -* A more specific function, if defined in a higher layer, always overrides the implementation in a lower layer. - -* This is akin to object-oriented programming but with functions. - -* This type of system is extremely convenient for dynamic business rules configuration, setting corporate-wide - policies, granting permissions for specific roles etc. where specific, local rules need to override - corporate-wide defaults. - - -Key Concepts ------------- - -* Each layer is a separate script. - -* The lowest layer script is compiled into a base [`AST`]. - -* Higher layer scripts are also compiled into [`AST`] and _combined_ into the base using `AST::combine` - (or the `+=` operator), overriding any existing functions. - - -Examples --------- - -Assume the following four scripts: - -```rust ----------------- -| default.rhai | ----------------- - -// Default implementation of 'foo'. -fn foo(x) { x + 1 } - -// Default implementation of 'bar'. -fn bar(x, y) { x + y } - -// Default implementation of 'no_touch'. -fn no_touch() { throw "do not touch me!"; } - - ---------------- -| lowest.rhai | ---------------- - -// Specific implementation of 'foo'. -fn foo(x) { x * 2 } - -// New implementation for this layer. -fn baz() { print("hello!"); } - - ---------------- -| middle.rhai | ---------------- - -// Specific implementation of 'bar'. -fn bar(x, y) { x - y } - -// Specific implementation of 'baz'. -fn baz() { print("hey!"); } - - ----------------- -| highest.rhai | ----------------- - -// Specific implementation of 'foo'. -fn foo(x) { x + 42 } -``` - -Load and combine them sequentially: - -```rust -let engine = Engine::new(); - -// Compile the baseline default implementations. -let mut ast = engine.compile_file("default.rhai".into())?; - -// Combine the first layer. -let lowest = engine.compile_file("lowest.rhai".into())?; -ast += lowest; - -// Combine the second layer. -let middle = engine.compile_file("middle.rhai".into())?; -ast += lowest; - -// Combine the third layer. -let highest = engine.compile_file("highest.rhai".into())?; -ast += lowest; - -// Now, 'ast' contains the following functions: -// -// fn no_touch() { // from 'default.rhai' -// throw "do not touch me!"; -// } -// fn foo(x) { x + 42 } // from 'highest.rhai' -// fn bar(x, y) { x - y } // from 'middle.rhai' -// fn baz() { print("hey!"); } // from 'middle.rhai' -``` - -Unfortunately, there is no `super` call that calls the base implementation -(i.e. no way for a higher-layer function to call an equivalent lower-layer function). diff --git a/doc/src/patterns/multiple.md b/doc/src/patterns/multiple.md deleted file mode 100644 index e7806265..00000000 --- a/doc/src/patterns/multiple.md +++ /dev/null @@ -1,89 +0,0 @@ -Multiple Instantiation -====================== - -{{#include ../links.md}} - - -Background ----------- - -Rhai's [features] are not strictly additive. This is easily deduced from the [`no_std`] feature -which prepares the crate for `no-std` builds. Obviously, turning on this feature has a material -impact on how Rhai behaves. - -Many crates resolve this by going the opposite direction: build for `no-std` in default, -but add a `std` feature, included by default, which builds for the `stdlib`. - - -Rhai Language Features Are Not Additive --------------------------------------- - -Rhai, however, is more complex. Language features cannot be easily made _additive_. - -That is because the _lack_ of a language feature is a feature by itself. - -For example, by including [`no_float`], a project sets the Rhai language to ignore floating-point math. -Floating-point numbers do not even parse under this case and will generate syntax errors. -Assume that the project expects this behavior (why? perhaps integers are all that make sense -within the project domain). - -Now, assume that a dependent crate also depends on Rhai. Under such circumstances, -unless _exact_ versioning is used and the dependent crate depends on a _different_ version -of Rhai, Cargo automatically _merges_ both dependencies, with the [`no_float`] feature turned on -because Cargo features are _additive_. - -This will break the dependent crate, which does not by itself specify [`no_float`] -and expects floating-point numbers and math to work normally. - -There is no way out of this dilemma. Reversing the [features] set with a `float` feature -causes the project to break because floating-point numbers are not rejected as expected. - - -Multiple Instantiations of Rhai Within The Same Project ------------------------------------------------------- - -The trick is to differentiate between multiple identical copies of Rhai, each having -a different [features] set, by their _sources_: - -* Different versions from [`crates.io`](https://crates.io/crates/rhai/) – The official crate. - -* Different releases from [`GitHub`](https://github.com/jonathandturner/rhai) – Crate source on GitHub. - -* Forked copy of [https://github.com/jonathandturner/rhai](https://github.com/jonathandturner/rhai) on GitHub. - -* Local copy of [https://github.com/jonathandturner/rhai](https://github.com/jonathandturner/rhai) downloaded form GitHub. - -Use the following configuration in `Cargo.toml` to pull in multiple copies of Rhai within the same project: - -```toml -[dependencies] -rhai = { version = "{{version}}", features = [ "no_float" ] } -rhai_github = { git = "https://github.com/jonathandturner/rhai", features = [ "unchecked" ] } -rhai_my_github = { git = "https://github.com/my_github/rhai", branch = "variation1", features = [ "serde", "no_closure" ] } -rhai_local = { path = "../rhai_copy" } -``` - -The example above creates four different modules: `rhai`, `rhai_github`, `rhai_my_github` and -`rhai_local`, each referring to a different Rhai copy with the appropriate [features] set. - -Only one crate of any particular version can be used from each source, because Cargo merges -all candidate cases within the same source, adding all [features] together. - -If more than four different instantiations of Rhai is necessary (why?), create more local repositories -or GitHub forks or branches. - - -Caveat – No Way To Avoid Dependency Conflicts --------------------------------------------------- - -Unfortunately, pulling in Rhai from different sources do not resolve the problem of -[features] conflict between dependencies. Even overriding `crates.io` via the `[patch]` manifest -section doesn't work – all dependencies will eventually find the only one copy. - -What is necessary – multiple copies of Rhai, one for each dependent crate that requires it, -together with their _unique_ [features] set intact. In other words, turning off Cargo's -crate merging feature _just for Rhai_. - -Unfortunately, as of this writing, there is no known method to achieve it. - -Therefore, moral of the story: avoid pulling in multiple crates that depend on Rhai. diff --git a/doc/src/patterns/oop.md b/doc/src/patterns/oop.md deleted file mode 100644 index 7b225c8c..00000000 --- a/doc/src/patterns/oop.md +++ /dev/null @@ -1,109 +0,0 @@ -Object-Oriented Programming (OOP) -================================ - -{{#include ../links.md}} - -Rhai does not have _objects_ per se, but it is possible to _simulate_ object-oriented programming. - - -Use Object Maps to Simulate OOP ------------------------------- - -Rhai's [object maps] has [special support for OOP]({{rootUrl}}/language/object-maps-oop.md). - -| Rhai concept | Maps to OOP | -| ----------------------------------------------------- | :---------: | -| [Object maps] | objects | -| [Object map] properties holding values | properties | -| [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] 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 Closures to Define Methods ------------------------------ - -[Anonymous functions] or [closures] defined as values for [object map] properties take on -a syntactic shape which resembles very closely that of class methods in an OOP language. - -Closures also _[capture][automatic currying]_ variables from the defining environment, which is a very -common language feature. Capturing is accomplished via a feature called _[automatic currying]_ and -can be turned off via the [`no_closure`] feature. - - -Examples --------- - -```rust -let factor = 1; - -// Define the object -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.update(42); -obj.action(); // prints 42 - -factor = 2; - -obj.update(42); -obj.action(); // prints 84 -``` - - -Simulating Inheritance With Mixin --------------------------------- - -The `fill_with` method of [object maps] can be conveniently used to _polyfill_ default -method implementations from a _base class_, as per OOP lingo. - -Do not use the `mixin` method because it _overwrites_ existing fields. - -```rust -// Define base class -let BaseClass = #{ - factor: 1, - data: 42, - - get_data: || this.data * 2, - update: |x| this.data += x * this.factor -}; - -let obj = #{ - // Override base class field - factor: 100, - - // Override base class method - // Notice that the base class can also be accessed, if in scope - get_data: || this.call(BaseClass.get_data) * 999, -} - -// Polyfill missing fields/methods -obj.fill_with(BaseClass); - -// By this point, 'obj' has the following: -// -// #{ -// factor: 100 -// data: 42, -// get_data: || this.call(BaseClass.get_data) * 999, -// update: |x| this.data += x * this.factor -// } - -// obj.get_data() => (this.data (42) * 2) * 999 -obj.get_data() == 83916; - -obj.update(1); - -obj.data == 142 -``` diff --git a/doc/src/patterns/parallel.md b/doc/src/patterns/parallel.md deleted file mode 100644 index 3db89b3f..00000000 --- a/doc/src/patterns/parallel.md +++ /dev/null @@ -1,75 +0,0 @@ -One Engine Instance Per Call -=========================== - -{{#include ../links.md}} - - -Usage Scenario --------------- - -* A system where scripts are called a _lot_, in tight loops or in parallel. - -* Keeping a global [`Engine`] instance is sub-optimal due to contention and locking. - -* Scripts need to be executed independently from each other, perhaps concurrently. - -* Scripts are used to [create Rust closure][`Func`] that are stored and may be called at any time, perhaps concurrently. - In this case, the [`Engine`] instance is usually moved into the closure itself. - - -Key Concepts ------------- - -* Create a single instance of each standard [package] required. - To duplicate `Engine::new`, create a [`StandardPackage`]({{rootUrl}}/rust/packages/builtin.md). - -* Gather up all common custom functions into a [custom package]. - -* Store a global `AST` for use with all engines. - -* Always use `Engine::new_raw` to create a [raw `Engine`], instead of `Engine::new` which is _much_ more expensive. - A [raw `Engine`] is _extremely_ cheap to create. - - Registering the [`StandardPackage`]({{rootUrl}}/rust/packages/builtin.md) into a [raw `Engine`] via - `Engine::register_global_module` is essentially the same as `Engine::new`. - - However, because packages are shared, using existing package is _much cheaper_ than - registering all the functions one by one. - -* Register the required packages with the [raw `Engine`] via `Engine::register_global_module`, - using `Package::as_shared_module` to obtain a shared [module]. - - -Examples --------- - -```rust -use rhai::packages::{Package, StandardPackage}; - -let ast = /* ... some AST ... */; -let std_pkg = StandardPackage::new(); -let custom_pkg = MyCustomPackage::new(); - -let make_call = |x: i64| -> Result<(), Box> { - // Create a raw Engine - extremely cheap - let mut engine = Engine::new_raw(); - - // Register packages as global modules - cheap - engine.register_global_module(std_pkg.as_shared_module()); - engine.register_global_module(custom_pkg.as_shared_module()); - - // Create custom scope - cheap - let mut scope = Scope::new(); - - // Push variable into scope - relatively cheap - scope.push("x", x); - - // Evaluate script. - engine.consume_ast_with_scope(&mut scope, &ast) -}; - -// The following loop creates 10,000 Engine instances! -for x in 0..10_000 { - make_call(x)?; -} -``` diff --git a/doc/src/patterns/singleton.md b/doc/src/patterns/singleton.md deleted file mode 100644 index 97b7d221..00000000 --- a/doc/src/patterns/singleton.md +++ /dev/null @@ -1,175 +0,0 @@ -Singleton Command Object -======================= - -{{#include ../links.md}} - - -Usage Scenario --------------- - -* A system provides core functionalities, but no driving logic. - -* The driving logic must be dynamic and hot-loadable. - -* A script is used to drive the system and provide control intelligence. - -* The API is multiplexed, meaning that it can act on multiple system-provided entities, or - -* The API lends itself readily to an object-oriented (OO) representation. - - -Key Concepts ------------- - -* Expose a Command type with an API. The [`no_object`] feature must not be on. - -* 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. - -* Control each command object in script via the constants. - - -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 but has a clear object structure. - -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: - -```rust -struct EnergizerBunny { ... } - -impl EnergizerBunny { - pub fn new () -> Self { ... } - pub fn go (&mut self) { ... } - pub fn stop (&mut 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) { ... } -} -``` - -### Wrap Command Object Type as Shared - -```rust -pub type SharedBunny = Rc>; -``` - -Note: Use `Arc>` or `Arc>` when using the [`sync`] feature because the function -must then be `Send + Sync`. - -### Register the Custom Type - -```rust -engine.register_type_with_name::("EnergizerBunny"); -``` - -### 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 -use rhai::plugin::*; - -#[export_module] -pub mod bunny_api { - pub const MAX_SPEED: i64 = 100; - - #[rhai_fn(get = "power")] - pub fn get_power(bunny: &mut SharedBunny) -> bool { - bunny.borrow().is_going() - } - #[rhai_fn(set = "power")] - pub fn set_power(bunny: &mut SharedBunny, on: bool) { - if on { - if bunny.borrow().is_going() { - println!("Still going..."); - } else { - bunny.borrow_mut().go(); - } - } else { - if bunny.borrow().is_going() { - bunny.borrow_mut().stop(); - } else { - println!("Already out of battery!"); - } - } - } - #[rhai_fn(get = "speed")] - pub fn get_speed(bunny: &mut SharedBunny) -> i64 { - if bunny.borrow().is_going() { - bunny.borrow().get_speed() - } else { - 0 - } - } - #[rhai_fn(set = "speed", return_raw)] - pub fn set_speed(bunny: &mut SharedBunny, speed: i64) - -> Result> - { - if speed <= 0 { - Err("Speed must be positive!".into()) - } 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()) - } else { - b.borrow_mut().set_speed(speed); - Ok(Dynamic::UNIT) - } - } - pub fn turn_left(bunny: &mut SharedBunny) { - if bunny.borrow().is_going() { - bunny.borrow_mut().turn(true); - } - } - pub fn turn_right(bunny: &mut SharedBunny) { - if bunny.borrow().is_going() { - bunny.borrow_mut().turn(false); - } - } -} - -engine.register_global_module(exported_module!(bunny_api).into()); -``` - -### Push Constant Command Object into Custom Scope - -```rust -let bunny: SharedBunny = Rc::new(RefCell::(EnergizerBunny::new())); - -let mut scope = Scope::new(); - -// Add the command object into a custom Scope. -scope.push_constant("Bunny", bunny.clone()); - -engine.consume_with_scope(&mut scope, script)?; -``` - -### Use the Command API in Script - -```rust -// Access the command object via constant variable 'Bunny'. - -if !Bunny.power { Bunny.power = true; } - -if Bunny.speed > 50 { Bunny.speed = 50; } - -Bunny.turn_left(); -``` diff --git a/doc/src/plugins/function.md b/doc/src/plugins/function.md deleted file mode 100644 index 8f7b3f46..00000000 --- a/doc/src/plugins/function.md +++ /dev/null @@ -1,156 +0,0 @@ -Export a Rust Function to Rhai -============================= - -{{#include ../links.md}} - - -Sometimes only a few ad hoc functions are required and it is simpler to register -individual functions instead of a full-blown [plugin module]. - - -Macros ------- - -| 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 | -| `set_exported_global_fn!` | `set_exported_global_fn!(&mut `_module_`, "`_name_`", `_function_`)` | registers the function into a [`Module`] under a specific name, exposing it to the global namespace | - - -`#[export_fn]` and `register_exported_fn!` ------------------------------------------ - -Apply `#[export_fn]` onto a function defined at _module level_ to convert it into a Rhai plugin function. - -The function cannot be nested inside another function – it can only be defined directly under a module. - -To register the plugin function, simply call `register_exported_fn!`. The name of the function can be -any text string, so it is possible to register _overloaded_ functions as well as operators. - -```rust -use rhai::plugin::*; // import macros - -#[export_fn] -fn increment(num: &mut i64) { - *num += 1; -} - -fn main() { - let mut engine = Engine::new(); - - // 'register_exported_fn!' registers the function as 'inc' with the Engine. - register_exported_fn!(engine, "inc", increment); -} -``` - - -Fallible Functions ------------------- - -To register [fallible functions] (i.e. functions that may return errors), apply the -`#[rhai_fn(return_raw)]` attribute on plugin functions that return `Result>`. - -A syntax error is generated if the function with `#[rhai_fn(return_raw)]` does not -have the appropriate return type. - -```rust -use rhai::plugin::*; // a "prelude" import for macros - -#[export_fn] -#[rhai_fn(return_raw)] -pub fn double_and_divide(x: i64, y: i64) -> Result> { - if y == 0 { - Err("Division by zero!".into()) - } else { - let result = (x * 2) / y; - Ok(result.into()) - } -} - -fn main() { - let mut engine = Engine::new(); - - // Overloads the operator '+' with the Engine. - register_exported_fn!(engine, "+", double_and_divide); -} -``` - - -`NativeCallContext` Parameter ----------------------------- - -If the _first_ parameter of a function is of type `rhai::NativeCallContext`, then it is treated -specially by the plugins system. - -`NativeCallContext` is a type that encapsulates the current _native call context_ and exposes the following: - -| Field | Type | Description | -| ------------------- | :-------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `engine()` | `&Engine` | the current [`Engine`], with all configurations and settings.
This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. | -| `source()` | `Option<&str>` | reference to the current source, if any | -| `iter_imports()` | `impl Iterator` | iterator of the current stack of [modules] imported via `import` statements | -| `imports()` | `&Imports` | reference to the current stack of [modules] imported via `import` statements; requires the [`internals`] feature | -| `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | -| `namespaces()` | `&[&Module]` | reference to the namespaces (as [modules]) containing all script-defined functions; requires the [`internals`] feature | - -This first parameter, if exists, will be stripped before all other processing. It is _virtual_. -Most importantly, it does _not_ count as a parameter to the function and there is no need to provide -this argument when calling the function in Rhai. - -The native call context can be used to call a [function pointer] or [closure] that has been passed -as a parameter to the function, thereby implementing a _callback_: - -```rust -use rhai::{Dynamic, FnPtr, NativeCallContext, EvalAltResult}; -use rhai::plugin::*; // a "prelude" import for macros - -#[export_fn] -#[rhai_fn(return_raw)] -pub fn greet(context: NativeCallContext, callback: FnPtr) - -> Result> -{ - // Call the callback closure with the current context - // to obtain the name to greet! - let name = callback.call_dynamic(context, None, [])?; - Ok(format!("hello, {}!", name).into()) -} -``` - -The native call context is also useful in another scenario: protecting a function from malicious scripts. - -```rust -use rhai::{Dynamic, Array, NativeCallContext, EvalAltResult, Position}; -use rhai::plugin::*; // a "prelude" import for macros - -// This function builds an array of arbitrary size, but is protected -// against attacks by first checking with the allowed limit set -// into the 'Engine'. -#[export_fn] -#[rhai_fn(return_raw)] -pub fn grow(context: NativeCallContext, size: i64) - -> Result> -{ - // Make sure the function does not generate a - // data structure larger than the allowed limit - // for the Engine! - if size as usize > context.engine().max_array_size() - { - return EvalAltResult::ErrorDataTooLarge( - "Size to grow".to_string(), - context.engine().max_array_size(), - size as usize, - Position::NONE, - ).into(); - } - - let array = Array::new(); - - for x in 0..size { - array.push(x.into()); - } - - OK(array.into()) -} -``` diff --git a/doc/src/plugins/index.md b/doc/src/plugins/index.md deleted file mode 100644 index 1e3f3cc9..00000000 --- a/doc/src/plugins/index.md +++ /dev/null @@ -1,13 +0,0 @@ -Plugins -======= - -{{#include ../links.md}} - -Rhai contains a robust _plugin_ system that greatly simplifies registration of custom -functionality. - -Instead of using the large `Engine::register_XXX` API or the parallel `Module::set_fn_XXX` API, -a _plugin_ simplifies the work of creating and registering new functionality in an [`Engine`]. - -Plugins are processed via a set of procedural macros under the `rhai::plugin` module. These -allow registering Rust functions directly in the Engine, or adding Rust modules as packages. diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md deleted file mode 100644 index c147809f..00000000 --- a/doc/src/plugins/module.md +++ /dev/null @@ -1,507 +0,0 @@ -Export a Rust Module to Rhai -============================ - -{{#include ../links.md}} - - -Prelude -------- - -When using the plugins system, the entire `rhai::plugin` module must be imported as a prelude -because code generated will need these imports. - -```rust -use rhai::plugin::*; -``` - - -`#[export_module]` ------------------- - -When applied to a Rust module, the `#[export_module]` attribute generates the necessary -code and metadata to allow Rhai access to its public (i.e. marked `pub`) functions, constants -and sub-modules. - -This code is exactly what would need to be written by hand to achieve the same goal, -and is custom fit to each exported item. - -All `pub` functions become registered functions, all `pub` constants become [module] constant variables, -and all sub-modules become Rhai sub-modules. - -This Rust module can then be registered into an [`Engine`] as a normal [module]. -This is done via the `exported_module!` macro. - -The macro `combine_with_exported_module!` can be used to _combine_ all the functions -and variables into an existing [module], _flattening_ the namespace – i.e. all sub-modules -are eliminated and their contents promoted to the top level. This is typical for -developing [custom packages]. - -```rust -use rhai::plugin::*; // a "prelude" import for macros - -#[export_module] -mod my_module { - // This constant will be registered as the constant variable 'MY_NUMBER'. - // Ignored when registered as a global module. - pub const MY_NUMBER: i64 = 42; - - // This function will be registered as 'greet'. - pub fn greet(name: &str) -> String { - format!("hello, {}!", name) - } - // This function will be registered as 'get_num'. - pub fn get_num() -> i64 { - mystic_number() - } - // This function will be registered as 'increment'. - // It will also be exposed to the global namespace since 'global' is set. - #[rhai_fn(global)] - pub fn increment(num: &mut i64) { - *num += 1; - } - // This function is not 'pub', so NOT registered. - fn mystic_number() -> i64 { - 42 - } - - // Sub-modules are ignored when the module is registered globally. - pub mod my_sub_module { - // This function is ignored when registered globally. - // Otherwise it is a valid registered function under a sub-module. - pub fn get_info() -> String { - "hello".to_string() - } - } - - // Sub-modules are commonly used to put feature gates on a group of - // functions because feature gates cannot be put on function definitions. - // This is currently a limitation of the plugin procedural macros. - #[cfg(feature = "advanced_functions")] - pub mod advanced { - // This function is ignored when registered globally. - // Otherwise it is a valid registered function under a sub-module - // which only exists when the 'advanced_functions' feature is used. - pub fn advanced_calc(input: i64) -> i64 { - input * 2 - } - } -} -``` - -### Use `Engine::register_global_module` - -The simplest way to register this into an [`Engine`] is to first use the `exported_module!` macro -to turn it into a normal Rhai [module], then use the `Engine::register_global_module` method on it: - -```rust -fn main() { - let mut engine = Engine::new(); - - // The macro call creates a Rhai module from the plugin module. - let module = exported_module!(my_module); - - // A module can simply be registered into the global namespace. - engine.register_global_module(module.into()); -} -``` - -The functions contained within the module definition (i.e. `greet`, `get_num` and `increment`) -are automatically registered into the [`Engine`] when `Engine::register_global_module` is called. - -```rust -let x = greet("world"); -x == "hello, world!"; - -let x = greet(get_num().to_string()); -x == "hello, 42!"; - -let x = get_num(); -x == 42; - -increment(x); -x == 43; -``` - -Notice that, when using a [module] as a [package], only functions registered at the _top level_ -can be accessed. - -Variables as well as sub-modules are **ignored**. - -### Use `Engine::register_static_module` - -Another simple way to register this into an [`Engine`] is, again, to use the `exported_module!` macro -to turn it into a normal Rhai [module], then use the `Engine::register_static_module` method on it: - -```rust -fn main() { - let mut engine = Engine::new(); - - // The macro call creates a Rhai module from the plugin module. - let module = exported_module!(my_module); - - // A module can simply be registered as a static module namespace. - engine.register_static_module("service", module.into()); -} -``` - -The functions contained within the module definition (i.e. `greet`, `get_num` and `increment`), -plus the constant `MY_NUMBER`, are automatically registered under the module namespace `service`: - -```rust -let x = service::greet("world"); -x == "hello, world!"; - -service::MY_NUMBER == 42; - -let x = service::greet(service::get_num().to_string()); -x == "hello, 42!"; - -let x = service::get_num(); -x == 42; - -service::increment(x); -x == 43; -``` - -All functions (usually _methods_) defined in the module and marked with `#[rhai_fn(global)]`, -as well as all [type iterators], are automatically exposed to the _global_ namespace, so -[iteration]({{rootUrl}}/language/for.md), [getters/setters] and [indexers] for [custom types] -can work as expected. - -In fact, the default for all [getters/setters] and [indexers] defined in a plugin module -is `#[rhai_fn(global)]` unless specifically overridden by `#[rhai_fn(internal)]`. - -Therefore, in the example above, the `increment` method (defined with `#[rhai_fn(global)]`) -works fine when called in method-call style: - -```rust -let x = 42; -x.increment(); -x == 43; -``` - -### Use Dynamically - -Using this directly as a dynamically-loadable Rhai [module] is almost the same, except that a -[module resolver] must be used to serve the module, and the module is loaded via `import` statements. - -See the [module] section for more information. - -### Combine into Custom Package - -Finally the plugin module can also be used to develop a [custom package], -using `combine_with_exported_module!`: - -```rust -def_package!(rhai:MyPackage:"My own personal super package", module, { - combine_with_exported_module!(module, "my_module_ID", my_module)); -}); -``` - -`combine_with_exported_module!` automatically _flattens_ the module namespace so that all -functions in sub-modules are promoted to the top level. This is convenient for [custom packages]. - - -Sub-Modules and Feature Gates ----------------------------- - -Sub-modules in a plugin module definition are turned into valid sub-modules in the resultant -Rhai `Module`. - -They are also commonly used to put _feature gates_ or _compile-time gates_ on a group of functions, -because currently attributes do not work on individual function definitions due to a limitation of -the procedural macros system. - -This is especially convenient when using the `combine_with_exported_module!` macro to develop -[custom packages] because selected groups of functions can easily be included or excluded based on -different combinations of feature flags instead of having to manually include/exclude every -single function. - -```rust -#[export_module] -mod my_module { - // Always available - pub fn func0() {} - - // The following sub-module is only available under 'feature1' - #[cfg(feature = "feature1")] - pub mod feature1 { - fn func1() {} - fn func2() {} - fn func3() {} - } - - // The following sub-module is only available under 'feature2' - #[cfg(feature = "feature2")] - pub mod feature2 { - fn func4() {} - fn func5() {} - fn func6() {} - } -} - -// Registered functions: -// func0 - always available -// func1 - available under 'feature1' -// func2 - available under 'feature1' -// func3 - available under 'feature1' -// func4 - available under 'feature2' -// func5 - available under 'feature2' -// func6 - available under 'feature2' -combine_with_exported_module!(module, "my_module_ID", my_module); -``` - - -Function Overloading and Operators ---------------------------------- - -Operators and overloaded functions can be specified via applying the `#[rhai_fn(name = "...")]` -attribute to individual functions. - -The text string given as the `name` parameter to `#[rhai_fn]` is used to register the function with -the [`Engine`], disregarding the actual name of the function. - -With `#[rhai_fn(name = "...")]`, multiple functions may be registered under the same name in Rhai, -so long as they have different parameters. - -Operators (which require function names that are not valid for Rust) can also be registered this way. - -Registering the same function name with the same parameter types will cause a parsing error. - -```rust -use rhai::plugin::*; // a "prelude" import for macros - -#[export_module] -mod my_module { - // This is the '+' operator for 'TestStruct'. - #[rhai_fn(name = "+")] - pub fn add(obj: &mut TestStruct, value: i64) { - obj.prop += value; - } - // This function is 'calc (i64)'. - #[rhai_fn(name = "calc")] - pub fn calc_with_default(num: i64) -> i64 { - ... - } - // This function is 'calc (i64, bool)'. - #[rhai_fn(name = "calc")] - pub fn calc_with_option(num: i64, option: bool) -> i64 { - ... - } -} -``` - - -Getters, Setters and Indexers ------------------------------ - -Functions can be marked as [getters/setters] and [indexers] for [custom types] via the `#[rhai_fn]` -attribute, which is applied on a function level. - -```rust -use rhai::plugin::*; // a "prelude" import for macros - -#[export_module] -mod my_module { - // This is a normal function 'greet'. - pub fn greet(name: &str) -> String { - format!("hello, {}!", name) - } - // This is a getter for 'TestStruct::prop'. - #[rhai_fn(get = "prop")] - pub fn get_prop(obj: &mut TestStruct) -> i64 { - obj.prop - } - // This is a setter for 'TestStruct::prop'. - #[rhai_fn(set = "prop")] - pub fn set_prop(obj: &mut TestStruct, value: i64) { - obj.prop = value; - } - // This is an index getter for 'TestStruct'. - #[rhai_fn(index_get)] - pub fn get_index(obj: &mut TestStruct, index: i64) -> bool { - obj.list[index] - } - // This is an index setter for 'TestStruct'. - #[rhai_fn(index_set)] - pub fn get_index(obj: &mut TestStruct, index: i64, state: bool) { - obj.list[index] = state; - } -} -``` - - -Multiple Registrations ----------------------- - -Parameters to the `#[rhai_fn(...)]` attribute can be applied multiple times. - -This is especially useful for the `name = "..."`, `get = "..."` and `set = "..."` parameters -to give multiple alternative names to the same function. - -```rust -use rhai::plugin::*; // a "prelude" import for macros - -#[export_module] -mod my_module { - // This function can be called in five ways - #[rhai_fn(name = "get_prop_value", name = "prop", name = "+", set = "prop", index_get)] - pub fn prop_function(obj: &mut TestStruct, index: i64) -> i64 { - obj.prop[index] - } -} -``` - -The above function can be called in five ways: - -| Parameter for `#[rhai_fn(...)]` | Type | Call style | -| ------------------------------- | :-------------: | --------------------------------------------- | -| `name = "get_prop_value"` | method function | `get_prop_value(x, 0)`, `x.get_prop_value(0)` | -| `name = "prop"` | method function | `prop(x, 0)`, `x.prop(0)` | -| `name = "+"` | operator | `x + 42` | -| `set = "prop"` | setter | `x.prop = 42` | -| `index_get` | index getter | `x[0]` | - - -Fallible Functions ------------------- - -To register [fallible functions] (i.e. functions that may return errors), apply the -`#[rhai_fn(return_raw)]` attribute on functions that return `Result>`. - -A syntax error is generated if the function with `#[rhai_fn(return_raw)]` does not -have the appropriate return type. - -```rust -use rhai::plugin::*; // a "prelude" import for macros - -#[export_module] -mod my_module { - // This overloads the '/' operator for i64. - #[rhai_fn(name = "/", return_raw)] - pub fn double_and_divide(x: i64, y: i64) -> Result> { - if y == 0 { - Err("Division by zero!".into()) - } else { - let result = (x * 2) / y; - Ok(result.into()) - } - } -} -``` - - -`NativeCallContext` Parameter ----------------------------- - -If the _first_ parameter of a function is of type `rhai::NativeCallContext`, then it is treated -specially by the plugins system. - -`NativeCallContext` is a type that encapsulates the current _native call context_ and exposes the following: - -| Field | Type | Description | -| ------------------- | :-------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `engine()` | `&Engine` | the current [`Engine`], with all configurations and settings.
This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. | -| `source()` | `Option<&str>` | reference to the current source, if any | -| `iter_imports()` | `impl Iterator` | iterator of the current stack of [modules] imported via `import` statements | -| `imports()` | `&Imports` | reference to the current stack of [modules] imported via `import` statements; requires the [`internals`] feature | -| `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | -| `namespaces()` | `&[&Module]` | reference to the namespaces (as [modules]) containing all script-defined functions; requires the [`internals`] feature | - -This first parameter, if exists, will be stripped before all other processing. It is _virtual_. -Most importantly, it does _not_ count as a parameter to the function and there is no need to provide -this argument when calling the function in Rhai. - -The native call context can be used to call a [function pointer] or [closure] that has been passed -as a parameter to the function, thereby implementing a _callback_: - -```rust -use rhai::{Dynamic, FnPtr, NativeCallContext, EvalAltResult}; -use rhai::plugin::*; // a "prelude" import for macros - -#[export_module] -mod my_module { - #[rhai_fn(return_raw)] - pub fn greet(context: NativeCallContext, callback: FnPtr) - -> Result> - { - // Call the callback closure with the current context - // to obtain the name to greet! - let name = callback.call_dynamic(context, None, [])?; - Ok(format!("hello, {}!", name).into()) - } -} -``` - -The native call context is also useful in another scenario: protecting a function from malicious scripts. - -```rust -use rhai::{Dynamic, Array, NativeCallContext, EvalAltResult, Position}; -use rhai::plugin::*; // a "prelude" import for macros - -#[export_module] -mod my_module { - // This function builds an array of arbitrary size, but is protected - // against attacks by first checking with the allowed limit set - // into the 'Engine'. - #[rhai_fn(return_raw)] - pub fn grow(context: NativeCallContext, size: i64) - -> Result> - { - // Make sure the function does not generate a - // data structure larger than the allowed limit - // for the Engine! - if size as usize > context.engine().max_array_size() - { - return EvalAltResult::ErrorDataTooLarge( - "Size to grow".to_string(), - context.engine().max_array_size(), - size as usize, - Position::NONE, - ).into(); - } - - let array = Array::new(); - - for x in 0..size { - array.push(x.into()); - } - - OK(array.into()) - } -} -``` - - -`#[export_module]` Parameters ----------------------------- - -Parameters can be applied to the `#[export_module]` attribute to override its default behavior. - -| Parameter | Description | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| _none_ | exports only public (i.e. `pub`) functions | -| `export_all` | exports all functions (including private, non-`pub` functions); use `#[rhai_fn(skip)]` on individual functions to avoid export | -| `export_prefix = "..."` | exports functions (including private, non-`pub` functions) with names starting with a specific prefix | - - -Inner Attributes ----------------- - -Inner attributes can be applied to the inner items of a module to tweak the export process. - -`#[rhai_fn]` is applied to functions, while `#[rhai_mod]` is applied to sub-modules. - -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 | -| `global` | `#[rhai_fn]` | function | expose this function to the global namespace | -| `internal` | `#[rhai_fn]` | function | keep this function within the internal module namespace | -| `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/custom.md b/doc/src/rust/custom.md deleted file mode 100644 index 1b0bec44..00000000 --- a/doc/src/rust/custom.md +++ /dev/null @@ -1,200 +0,0 @@ -Register any Rust Type and its Methods -===================================== - -{{#include ../links.md}} - - -Free Typing ------------ - -Rhai works seamlessly with _any_ Rust type. The type can be _anything_; it does not -have any prerequisites other than being `Clone`. It does not need to implement -any other trait or use any custom `#[derive]`. - -This allows Rhai to be integrated into an existing Rust code base with as little plumbing -as possible, usually silently and seamlessly. External types that are not defined -within the same crate (and thus cannot implement special Rhai traits or -use special `#[derive]`) can also be used easily with Rhai. - -The reason why it is termed a _custom_ type throughout this documentation is that -Rhai natively supports a number of data types with fast, internal treatment (see -the list of [standard types]). Any type outside of this list is considered _custom_. - -Any type not supported natively by Rhai is stored as a Rust _trait object_, with no -restrictions other than being `Clone` (plus `Send + Sync` under the [`sync`] feature). -It runs slightly slower than natively-supported types as it does not have built-in, -optimized implementations for commonly-used functions, but for all other purposes has -no difference. - -Support for custom types can be turned off via the [`no_object`] feature. - - -Register a Custom Type and its Methods -------------------------------------- - -Any custom type must implement the `Clone` trait as this allows the [`Engine`] to pass by value. - -If the [`sync`] feature is used, it must also be `Send + Sync`. - -Notice that the custom type needs to be _registered_ using `Engine::register_type` -or `Engine::register_type_with_name`. - -To use native methods on custom types in Rhai scripts, it is common to register an API -for the type using one of the `Engine::register_XXX` functions. - -```rust -use rhai::{Engine, EvalAltResult}; -use rhai::RegisterFn; // remember 'RegisterFn' is needed - -#[derive(Clone)] -struct TestStruct { - field: i64 -} - -impl TestStruct { - fn new() -> Self { - Self { field: 1 } - } - - fn update(&mut self, x: i64) { // methods take &mut as first parameter - self.field += x; - } -} - -let mut engine = Engine::new(); - -// Most Engine API's can be chained up. -engine - .register_type::() // register custom type - .register_fn("new_ts", TestStruct::new) - .register_fn("update", TestStruct::update); - -// Cast result back to custom type. -let result = engine.eval::( - r" - let x = new_ts(); // calls 'TestStruct::new' - x.update(41); // calls 'TestStruct::update' - x // 'x' holds a 'TestStruct' - " -)?; - -println!("result: {}", result.field); // prints 42 -``` - -Rhai follows the convention that methods of custom types take a `&mut` first parameter -to that type, so that invoking methods can always update it. - -All other parameters in Rhai are passed by value (i.e. clones). - -**IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.** - - -Method-Call Style vs. Function-Call Style ----------------------------------------- - -Any function with a first argument that is a `&mut` reference can be used -as method calls because internally they are the same thing: methods on a type is -implemented as a functions taking a `&mut` first argument. - -This design is similar to Rust. - -```rust -impl TestStruct { - fn foo(&mut self) -> i64 { - self.field - } -} - -engine.register_fn("foo", TestStruct::foo); - -let result = engine.eval::( - r" - let x = new_ts(); - foo(x); // normal call to 'foo' - x.foo() // 'foo' can also be called like a method on 'x' - " -)?; - -println!("result: {}", result); // prints 1 -``` - -Under [`no_object`], however, the _method_ style of function calls -(i.e. calling a function as an object-method) is no longer supported. - -```rust -// Below is a syntax error under 'no_object'. -let result = engine.eval("let x = [1, 2, 3]; x.clear();")?; - // ^ cannot call in method style under 'no_object' -``` - - -`type_of()` a Custom Type -------------------------- - -[`type_of()`] works fine with custom types and returns the name of the type. - -If `Engine::register_type_with_name` is used to register the custom type -with a special "pretty-print" name, [`type_of()`] will return that name instead. - -```rust -engine - .register_type::() - .register_fn("new_ts1", TestStruct1::new) - .register_type_with_name::("TestStruct") - .register_fn("new_ts2", TestStruct2::new); - -let ts1_type = engine.eval::(r#"let x = new_ts1(); x.type_of()"#)?; -let ts2_type = engine.eval::(r#"let x = new_ts2(); x.type_of()"#)?; - -println!("{}", ts1_type); // prints 'path::to::TestStruct' -println!("{}", ts1_type); // prints 'TestStruct' -``` - - -Use the Custom Type With Arrays ------------------------------- - -The `push`, `insert`, `pad` functions, as well as the `+=` operator, for [arrays] are only -defined for standard built-in types. For custom types, type-specific versions must be registered: - -```rust -engine - .register_fn("push", |list: &mut Array, item: TestStruct| { - list.push(Dynamic::from(item)); - }).register_fn("+=", |list: &mut Array, item: TestStruct| { - list.push(Dynamic::from(item)); - }).register_fn("insert", |list: &mut Array, position: i64, item: TestStruct| { - if position <= 0 { - list.insert(0, Dynamic::from(item)); - } else if (position as usize) >= list.len() - 1 { - list.push(item); - } else { - list.insert(position as usize, Dynamic::from(item)); - } - }).register_fn("pad", |list: &mut Array, len: i64, item: TestStruct| { - if len as usize > list.len() { - list.resize(len as usize, item); - } - }); -``` - -In particular, in order to use the `in` operator with a custom type for an [array], -the `==` operator must be registered for the custom type: - -```rust -// Assume 'TestStruct' implements `PartialEq` -engine.register_fn("==", - |item1: &mut TestStruct, item2: TestStruct| item1 == &item2 -); - -// Then this works in Rhai: -let item = new_ts(); // construct a new 'TestStruct' -item in array; // 'in' operator uses '==' -``` - - -Working With Enums ------------------- - -It is quite easy to use Rust enums with Rhai. -See [this chapter]({{rootUrl}}/patterns/enums.md) for more details. diff --git a/doc/src/rust/disable-custom.md b/doc/src/rust/disable-custom.md deleted file mode 100644 index 912482e5..00000000 --- a/doc/src/rust/disable-custom.md +++ /dev/null @@ -1,18 +0,0 @@ -Disable Custom Types -==================== - -{{#include ../links.md}} - - -`no_object` Feature -------------------- - -The custom types API `register_type`, `register_type_with_name`, `register_get`, `register_get_result`, -`register_set`, `register_set_result` and `register_get_set` are not available under [`no_object`]. - - -`no_index` Feature ------------------- - -The indexers API `register_indexer_get`, `register_indexer_get_result`, `register_indexer_set`, -`register_indexer_set_result`, and `register_indexer_get_set` are also not available under [`no_index`]. diff --git a/doc/src/rust/fallible.md b/doc/src/rust/fallible.md deleted file mode 100644 index 86d15576..00000000 --- a/doc/src/rust/fallible.md +++ /dev/null @@ -1,41 +0,0 @@ -Register a Fallible Rust Function -================================ - -{{#include ../links.md}} - -If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be registered with `register_result_fn` -(using the `RegisterResultFn` trait). - -The function must return `Result>`. - -```rust -use rhai::{Engine, EvalAltResult, Position}; -use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn' - -// Function that may fail - the result type must be 'Dynamic' -fn safe_divide(x: i64, y: i64) -> Result> { - if y == 0 { - // Return an error if y is zero - Err("Division by zero!".into()) // shortcut to create Box - } else { - Ok((x / y).into()) // convert result into 'Dynamic' - } -} - -let mut engine = Engine::new(); - -// Fallible functions that return Result values must use register_result_fn() -engine.register_result_fn("divide", safe_divide); - -if let Err(error) = engine.eval::("divide(40, 0)") { - println!("Error: {:?}", *error); // prints ErrorRuntime("Division by zero detected!", (1, 1)") -} -``` - -Create a `Box` ----------------------------- - -`Box` implements `From<&str>` and `From` etc. -and the error text gets converted into `Box`. - -The error values are `Box`-ed in order to reduce memory footprint of the error path, which should be hit rarely. diff --git a/doc/src/rust/functions.md b/doc/src/rust/functions.md deleted file mode 100644 index 99e9a485..00000000 --- a/doc/src/rust/functions.md +++ /dev/null @@ -1,73 +0,0 @@ -Register a Rust Function -======================== - -{{#include ../links.md}} - -Rhai's scripting engine is very lightweight. It gets most of its abilities from functions. - -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]). - -```rust -use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString}; -use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn' -use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn' - -// Normal function that returns a standard type -// Remember to use 'ImmutableString' and not 'String' -fn add_len(x: i64, s: ImmutableString) -> i64 { - x + s.len() -} -// Alternatively, '&str' maps directly to 'ImmutableString' -fn add_len_str(x: i64, s: &str) -> i64 { - x + s.len() -} - -// Function that returns a 'Dynamic' value - must return a 'Result' -fn get_any_value() -> Result> { - Ok((42_i64).into()) // standard types can use 'into()' -} - -let mut engine = Engine::new(); - -engine - .register_fn("add", add_len) - .register_fn("add_str", add_len_str); - -let result = engine.eval::(r#"add(40, "xx")"#)?; - -println!("Answer: {}", result); // prints 42 - -let result = engine.eval::(r#"add_str(40, "xx")"#)?; - -println!("Answer: {}", result); // prints 42 - -// Functions that return Dynamic values must use register_result_fn() -engine.register_result_fn("get_any_value", get_any_value); - -let result = engine.eval::("get_any_value()")?; - -println!("Answer: {}", result); // prints 42 -``` - -To create a [`Dynamic`] value, use the `Dynamic::from` method. -[Standard types] in Rhai can also use `into()`. - -```rust -use rhai::Dynamic; - -let x = (42_i64).into(); // 'into()' works for standard types - -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 -or different number. - -New definitions _overwrite_ previous definitions of the same name and same number/types of parameters. diff --git a/doc/src/rust/generic.md b/doc/src/rust/generic.md deleted file mode 100644 index d2bea789..00000000 --- a/doc/src/rust/generic.md +++ /dev/null @@ -1,30 +0,0 @@ -Register a Generic Rust Function -=============================== - -{{#include ../links.md}} - -Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately. - -This essentially _overloads_ the function with different parameter types as Rhai does not natively support generics -but Rhai does support _function overloading_. - -```rust -use std::fmt::Display; - -use rhai::{Engine, RegisterFn}; - -fn show_it(x: &mut T) { - println!("put up a good show: {}!", x) -} - -let mut engine = Engine::new(); - -engine - .register_fn("print", show_it::) - .register_fn("print", show_it::) - .register_fn("print", show_it::); -``` - -The above example shows how to register multiple functions -(or, in this case, multiple overloaded versions of the same function) -under the same name. diff --git a/doc/src/rust/getters-setters.md b/doc/src/rust/getters-setters.md deleted file mode 100644 index 334c7438..00000000 --- a/doc/src/rust/getters-setters.md +++ /dev/null @@ -1,69 +0,0 @@ -Custom Type Property Getters and Setters -======================================= - -{{#include ../links.md}} - -A [custom type] can also expose properties by registering `get` and/or `set` functions. - -Getters and setters each take a `&mut` reference to the first parameter. - -Getters and setters are disabled when the [`no_object`] feature is used. - -| `Engine` API | Function signature(s)
(`T: Clone` = custom type,
`V: Clone` = data type) | Can mutate `T`? | -| --------------------- | -------------------------------------------------------------------------------- | :----------------------------: | -| `register_get` | `Fn(&mut T) -> V` | yes, but not advised | -| `register_set` | `Fn(&mut T, V)` | yes | -| `register_get_set` | getter: `Fn(&mut T) -> V`
setter: `Fn(&mut T, V)` | yes, but not advised in getter | -| `register_get_result` | `Fn(&mut T) -> Result>` | yes, but not advised | -| `register_set_result` | `Fn(&mut T, V) -> Result<(), Box>` | yes | - -By convention, property getters are not supposed to mutate the [custom type], although there is nothing -that prevents this mutation. - - -Cannot Override Object Maps --------------------------- - -Property getters and setters are mainly intended for [custom types]. - -Any getter or setter function registered for [object maps] is simply _ignored_ because -the get/set calls will be interpreted as properties on the [object maps]. - - -Examples --------- - -```rust -#[derive(Clone)] -struct TestStruct { - field: String -} - -impl TestStruct { - // Remember &mut must be used even for getters - fn get_field(&mut self) -> String { - self.field.clone() - } - - fn set_field(&mut self, new_val: &str) { - self.field = new_val.to_string(); - } - - fn new() -> Self { - Self { field: "hello" } - } -} - -let mut engine = Engine::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"#)?; - -println!("Answer: {}", result); // prints 42 -``` - -**IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.** diff --git a/doc/src/rust/index.md b/doc/src/rust/index.md deleted file mode 100644 index 6a0ca08d..00000000 --- a/doc/src/rust/index.md +++ /dev/null @@ -1,9 +0,0 @@ -Extend Rhai with Rust -==================== - -{{#include ../links.md}} - -Most features and functionalities required by a Rhai script should actually be coded in Rust, -which leverages the superior native run-time speed. - -This section discusses how to extend Rhai with functionalities written in Rust. diff --git a/doc/src/rust/indexers.md b/doc/src/rust/indexers.md deleted file mode 100644 index f7cff1af..00000000 --- a/doc/src/rust/indexers.md +++ /dev/null @@ -1,82 +0,0 @@ -Custom Type Indexers -=================== - -{{#include ../links.md}} - -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: - -> _object_ `[` _index_ `]` - -Like property [getters/setters], indexers take a `&mut` reference to the first parameter. - -They also take an additional parameter of any type that serves as the _index_ within brackets. - -Indexers are disabled when the [`no_index`] feature is used. - -| `Engine` API | Function signature(s)
(`T: Clone` = custom type,
`X: Clone` = index type,
`V: Clone` = data type) | Can mutate `T`? | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------- | :----------------------------: | -| `register_indexer_get` | `Fn(&mut T, X) -> V` | yes, but not advised | -| `register_indexer_set` | `Fn(&mut T, X, V)` | yes | -| `register_indexer_get_set` | getter: `Fn(&mut T, X) -> V`
setter: `Fn(&mut T, X, V)` | yes, but not advised in getter | -| `register_indexer_get_result` | `Fn(&mut T, X) -> Result>` | yes, but not advised | -| `register_indexer_set_result` | `Fn(&mut T, X, V) -> Result<(), Box>` | yes | - -By convention, index getters are not supposed to mutate the [custom type], although there is nothing -that prevents this mutation. - -**IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.** - - -Cannot Override Arrays, Object Maps and Strings ----------------------------------------------- - -For efficiency reasons, indexers **cannot** be used to overload (i.e. override) -built-in indexing operations for [arrays], [object maps] and [strings]. - -Attempting to register indexers for an [array], [object map] or [string] panics. - - -Examples --------- - -```rust -#[derive(Clone)] -struct TestStruct { - fields: Vec -} - -impl TestStruct { - // Remember &mut must be used even for getters - fn get_field(&mut self, index: String) -> i64 { - self.fields[index.len()] - } - fn set_field(&mut self, index: String, value: i64) { - self.fields[index.len()] = value - } - - fn new() -> Self { - Self { fields: vec![1, 2, 3, 4, 5] } - } -} - -let mut engine = Engine::new(); - -engine - .register_type::() - .register_fn("new_ts", TestStruct::new) - // Short-hand: .register_indexer_get_set(TestStruct::get_field, TestStruct::set_field); - .register_indexer_get(TestStruct::get_field) - .register_indexer_set(TestStruct::set_field); - -let result = engine.eval::( - r#" - let a = new_ts(); - a["xyz"] = 42; // these indexers use strings - a["xyz"] // as the index type - "# -)?; - -println!("Answer: {}", result); // prints 42 -``` diff --git a/doc/src/rust/modules/ast.md b/doc/src/rust/modules/ast.md deleted file mode 100644 index 0b2bb8b8..00000000 --- a/doc/src/rust/modules/ast.md +++ /dev/null @@ -1,80 +0,0 @@ -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. - -See the section on [_Exporting Variables, Functions and Sub-Modules_][`export`] for details on how to -prepare a Rhai script for this purpose as well as to control which functions/variables to export. - -When given an [`AST`], it is first evaluated, then the following items are exposed as members of the -new [module]: - -* Global variables – all variables exported via the `export` statement (those not exported remain hidden). - -* Functions not specifically marked `private`. - -* Global modules that remain in the [`Scope`] at the end of a script run (become sub-modules). - -`Module::eval_ast_as_new` encapsulates the entire `AST` into each function call, merging the -module namespace with the global namespace. Therefore, functions defined within the same module -script can cross-call each other. - - -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 -// A copy of the entire 'AST' is encapsulated into each function, -// allowing functions in the module script to cross-call each other. -let module = Module::eval_ast_as_new(Scope::new(), &ast, &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 deleted file mode 100644 index f5e10e97..00000000 --- a/doc/src/rust/modules/create.md +++ /dev/null @@ -1,158 +0,0 @@ -Create a Module from Rust -======================== - -{{#include ../../links.md}} - - -Create via Plugin ------------------ - -By far the simplest way to create a [module] is via a [plugin module] -which converts a normal Rust module into a Rhai [module] via procedural macros. - - -Create via `Module` API ------------------------ - -Manually creating a [module] is possible via the `Module` API. - -For the complete `Module` API, refer to the [documentation](https://docs.rs/rhai/{{version}}/rhai/struct.Module.html) online. - - -Use Case 1 – Make the `Module` Globally Available ------------------------------------------------------- - -`Engine::register_global_module` registers a shared [module] into the _global_ namespace. - -All [functions] and [type iterators] can be accessed without _namespace qualifiers_. -Variables and sub-modules are **ignored**. - -This is by far the easiest way to expose a module's functionalities to Rhai. - -```rust -use rhai::{Engine, Module}; - -let mut module = Module::new(); // new module - -// Use the 'Module::set_fn_XXX' API to add functions. -let hash = module.set_fn_1("inc", |x: i64| Ok(x + 1)); - -// Remember to update the parameter names/types and return type metadata. -// 'Module::set_fn_XXX' by default does not set function metadata. -module.update_fn_metadata(hash, ["x: i64", "i64"]); - -// Register the module into the global namespace of the Engine. -let mut engine = Engine::new(); -engine.register_global_module(module.into()); - -engine.eval::("inc(41)")? == 42; // no need to import module -``` - -Registering a [module] via `Engine::register_global_module` is essentially the _same_ -as calling `Engine::register_fn` (or any of the `Engine::register_XXX` API) individually -on each top-level function within that [module]. In fact, the actual implementation of -`Engine::register_fn` etc. simply adds the function to an internal [module]! - -```rust -// The above is essentially the same as: -let mut engine = Engine::new(); - -engine.register_fn("inc", |x: i64| x + 1); - -engine.eval::("inc(41)")? == 42; // no need to import module -``` - -Use Case 2 – Make the `Module` a Static Module ---------------------------------------------------- - -`Engine::register_static_module` registers a [module] and under a specific module namespace. - -```rust -use rhai::{Engine, Module}; - -let mut module = Module::new(); // new module - -// Use the 'Module::set_fn_XXX' API to add functions. -let hash = module.set_fn_1("inc", |x: i64| Ok(x + 1)); - -// Remember to update the parameter names/types and return type metadata. -// 'Module::set_fn_XXX' by default does not set function metadata. -module.update_fn_metadata(hash, ["x: i64", "i64"]); - -// Register the module into the Engine as the static module namespace path -// 'services::calc' -let mut engine = Engine::new(); -engine.register_static_module("services::calc", module.into()); - -// refer to the 'services::calc' module -engine.eval::("services::calc::inc(41)")? == 42; -``` - -### Expose Functions to the Global Namespace - -The `Module::set_fn_XXX_mut` API methods can optionally expose functions in the [module] -to the _global_ namespace by setting the `namespace` parameter to `FnNamespace::Global`, -so [getters/setters] and [indexers] for [custom types] can work as expected. - -[Type iterators], because of their special nature, are _always_ exposed to the _global_ namespace. - -```rust -use rhai::{Engine, Module, FnNamespace}; - -let mut module = Module::new(); // new module - -// Expose method 'inc' to the global namespace (default is 'FnNamespace::Internal') -let hash = module.set_fn_1_mut("inc", FnNamespace::Global, |x: &mut i64| Ok(x + 1)); - -// Remember to update the parameter names/types and return type metadata. -// 'Module::set_fn_XXX_mut' by default does not set function metadata. -module.update_fn_metadata(hash, ["x: &mut i64", "i64"]); - -// Register the module into the Engine as a static module namespace 'calc' -let mut engine = Engine::new(); -engine.register_static_module("calc", module.into()); - -// 'inc' works when qualified by the namespace -engine.eval::("calc::inc(41)")? == 42; - -// 'inc' also works without a namespace qualifier -// because it is exposed to the global namespace -engine.eval::("let x = 41; x.inc()")? == 42; -engine.eval::("let x = 41; inc(x)")? == 42; -``` - - -Use Case 3 – 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}; -use rhai::module_resolvers::StaticModuleResolver; - -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 - -// Create the module resolver -let mut resolver = StaticModuleResolver::new(); - -// Add the module into the module resolver under the name 'question' -// They module can then be accessed via: 'import "question" as q;' -resolver.insert("question", module); - -// Set the module resolver into the 'Engine' -let mut engine = Engine::new(); -engine.set_module_resolver(resolver); - -// Use namespace-qualified variables -engine.eval::(r#"import "question" as q; q::answer + 1"#)? == 42; - -// Call namespace-qualified functions -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 deleted file mode 100644 index 1094e208..00000000 --- a/doc/src/rust/modules/imp-resolver.md +++ /dev/null @@ -1,72 +0,0 @@ -Implement a Custom Module Resolver -================================= - -{{#include ../../links.md}} - -For many applications in which Rhai is embedded, it is necessary to customize the way that modules -are resolved. For instance, modules may need to be loaded from script texts stored in a database, -not in the file system. - -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 an [`Rc`][module] (or [`Arc`][module] under [`sync`]). - - The module should call `Module::build_index` on the target module before returning. - This method flattens the entire module tree and _indexes_ it for fast function name resolution. - If the module is already indexed, calling this method has no effect. - -* If the path does not resolve to a valid module, return `EvalAltResult::ErrorModuleNotFound`. - -* If the module failed to load, return `EvalAltResult::ErrorInModule`. - - -Example -------- - -```rust -use rhai::{ModuleResolver, Module, Engine, EvalAltResult}; - -// Define a custom module resolver. -struct MyModuleResolver {} - -// Implement the 'ModuleResolver' trait. -impl ModuleResolver for MyModuleResolver { - // Only required function. - fn resolve( - &self, - engine: &Engine, // reference to the current 'Engine' - path: &str, // the module path - pos: Position, // position of the 'import' statement - ) -> Result, Box> { - // Check module path. - if is_valid_module_path(path) { - let mut my_module = - load_secret_module(path) // load the custom module - .map_err(|err| - // Return EvalAltResult::ErrorInModule upon loading error - EvalAltResult::ErrorInModule(path.into(), Box::new(err), pos).into() - )?; - my_module.build_index(); // index it - Rc::new(my_module) // make it shared - } else { - // Return EvalAltResult::ErrorModuleNotFound if the path is invalid - Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()) - } - } -} - -let mut engine = Engine::new(); - -// Set the custom module resolver into the 'Engine'. -engine.set_module_resolver(MyModuleResolver {}); - -engine.consume(r#" - import "hello" as foo; // this 'import' statement will call - // 'MyModuleResolver::resolve' with "hello" as 'path' - foo:bar(); -"#)?; -``` diff --git a/doc/src/rust/modules/index.md b/doc/src/rust/modules/index.md deleted file mode 100644 index b42aa030..00000000 --- a/doc/src/rust/modules/index.md +++ /dev/null @@ -1,29 +0,0 @@ -Modules -======= - -{{#include ../../links.md}} - -Rhai allows organizing functionalities (functions, both Rust-based or script-based, and variables) -into independent _modules_. Modules can be disabled via the [`no_module`] feature. - -A module is of the type `Module` and holds a collection of functions, variables, -[type iterators] and sub-modules. - -It may be created entirely from Rust functions, or it may encapsulate a Rhai script together -with the functions and variables defined by that script. - -Other scripts can then load this module and use the functions and variables exported -as if they were defined inside the same script. - -Alternatively, modules can be registered directly into an [`Engine`] and made available -to scripts either globally or under individual static module [_namespaces_][function namespaces]. - - -Usage Patterns --------------- - -| Usage | API | Lookup | Sub-modules? | Variables? | -| -------------- | :-------------------------------: | :----------------------: | :----------: | :--------: | -| Global module | `Engine:: register_global_module` | simple name | ignored | ignored | -| Static module | `Engine:: register_static_module` | namespace-qualified name | yes | yes | -| Dynamic module | [`import`] statement | namespace-qualified name | yes | yes | diff --git a/doc/src/rust/modules/resolvers.md b/doc/src/rust/modules/resolvers.md deleted file mode 100644 index c7ba3196..00000000 --- a/doc/src/rust/modules/resolvers.md +++ /dev/null @@ -1,181 +0,0 @@ -Module Resolvers -================ - -{{#include ../../links.md}} - -When encountering an [`import`] statement, Rhai attempts to _resolve_ the module based on the path string. - -See the section on [_Importing Modules_][`import`] for more details. - -_Module Resolvers_ are service types that implement the [`ModuleResolver`][traits] trait. - - -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. - -Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace. - - -`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. - -All modules imported at _global_ level via [`import`] statements become sub-modules, -which are also available to functions defined within the same script file. - -Modules are also _cached_ so a script file is only evaluated _once_, even when repeatedly imported. - -```rust ------------------- -| my_module.rhai | ------------------- - -// This function overrides any in the main script. -private fn inner_message() { "hello! from module!" } - -fn greet() { - print(inner_message()); // call function in module script -} - -fn greet_main() { - print(main_message()); // call function not in module script -} - -------------- -| main.rhai | -------------- - -// This function is overridden by the module script. -fn inner_message() { "hi! from main!" } - -// This function is found by the module script. -fn main_message() { "main here!" } - -import "my_module" as m; - -m::greet(); // prints "hello! from module!" - -m::greet_main(); // prints "main here!" -``` - -### Simulating virtual functions - -When calling a namespace-qualified function defined within a module, other functions defined within -the same module script override any similar-named functions (with the same number of parameters) -defined in the global namespace. This is to ensure that a module acts as a self-contained unit and -functions defined in the calling script do not override module code. - -In some situations, however, it is actually beneficial to do it in reverse: have module code call functions -defined in the calling script (i.e. in the global namespace) if they exist, and only call those defined -in the module script if none are found. - -One such situation is the need to provide a _default implementation_ to a simulated _virtual_ function: - -```rust ------------------- -| my_module.rhai | ------------------- - -// Do not do this (it will override the main script): -// fn message() { "hello! from module!" } - -// This function acts as the default implementation. -private fn default_message() { "hello! from module!" } - -// This function depends on a 'virtual' function 'message' -// which is not defined in the module script. -fn greet() { - if is_def_fn("message", 0) { // 'is_def_fn' detects if 'message' is defined. - print(message()); - } else { - print(default_message()); - } -} - -------------- -| main.rhai | -------------- - -// The main script defines 'message' which is needed by the module script. -fn message() { "hi! from main!" } - -import "my_module" as m; - -m::greet(); // prints "hi! from main!" - --------------- -| main2.rhai | --------------- - -// The main script does not define 'message' which is needed by the module script. - -import "my_module" as m; - -m::greet(); // prints "hello! from module!" -``` - -### Changing the base directory - -The base directory can be changed via the `FileModuleResolver::new_with_path` constructor function. - - -`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. - - -`DummyResolversCollection` -------------------------- - -This module resolver acts as a _dummy_ and always fails all module resolution calls. - - -Set into `Engine` ------------------ - -An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`: - -```rust -use rhai::module_resolvers::{DummyModuleResolver, StaticModuleResolver}; - -// Create a module resolver -let resolver = StaticModuleResolver::new(); - -// Register functions into 'resolver'... - -// Use the module resolver -engine.set_module_resolver(resolver); - -// Effectively disable 'import' statements by setting module resolver to -// the 'DummyModuleResolver' which acts as... well... a dummy. -engine.set_module_resolver(DummyModuleResolver::new()); -``` diff --git a/doc/src/rust/operators.md b/doc/src/rust/operators.md deleted file mode 100644 index 83175bf8..00000000 --- a/doc/src/rust/operators.md +++ /dev/null @@ -1,74 +0,0 @@ -Operator Overloading -=================== - -{{#include ../links.md}} - -In Rhai, a lot of functionalities are actually implemented as functions, including basic operations -such as arithmetic calculations. - -For example, in the expression "`a + b`", the `+` operator calls a function named "`+`"! - -```rust -let x = a + b; - -let x = +(a, b); // <- the above is equivalent to this function call -``` - -Similarly, comparison operators including `==`, `!=` etc. are all implemented as functions, -with the stark exception of `&&` and `||`. - - -`&&` and `||` Cannot Be Overloaded ---------------------------------- - -Because they [_short-circuit_]({{rootUrl}}/language/logic.md#boolean-operators), `&&` and `||` are -handled specially and _not_ via a function; as a result, overriding them has no effect at all. - - -Overload Operator via Rust Function ----------------------------------- - -Operator functions cannot be defined as a script function (because operators syntax are not valid function names). - -However, operator functions _can_ be registered to the [`Engine`] via the methods -`Engine::register_fn`, `Engine::register_result_fn` etc. - -When a custom operator function is registered with the same name as an operator, it _overrides_ the built-in version. - -```rust -use rhai::{Engine, EvalAltResult, RegisterFn}; - -let mut engine = Engine::new(); - -fn strange_add(a: i64, b: i64) -> i64 { (a + b) * 42 } - -engine.register_fn("+", strange_add); // overload '+' operator for two integers! - -let result: i64 = engine.eval("1 + 0"); // the overloading version is used - -result == 42; - -let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded - -result == 1.0; - -fn mixed_add(a: i64, b: f64) -> f64 { (a as f64) + b } - -engine.register_fn("+", mixed_add); // register '+' operator for an integer and a float - -let result: i64 = engine.eval("1 + 1.0"); // <- normally an error... - -result == 2.0; // ... but not now -``` - - -Considerations --------------- - -Normally, use operator overloading for [custom types] only. - -Be very careful when overriding built-in operators because script authors expect standard operators to behave in a -consistent and predictable manner, and will be annoyed if a calculation for '`+`' turns into a subtraction, for example. - -Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`]. -See the [script-optimization] for more details. diff --git a/doc/src/rust/override.md b/doc/src/rust/override.md deleted file mode 100644 index 7620bfd5..00000000 --- a/doc/src/rust/override.md +++ /dev/null @@ -1,22 +0,0 @@ -Override a Built-in Function -=========================== - -{{#include ../links.md}} - -Any similarly-named function defined in a script _overrides_ any built-in or registered -native Rust function of the same name and number of parameters. - -```rust -// Override the built-in function 'to_float' when called as a method -fn to_float() { - print("Ha! Gotcha! " + this); - 42.0 -} - -let x = 123.to_float(); - -print(x); // what happens? -``` - -A registered native Rust function, in turn, overrides any built-in function of the -same name, number and types of parameters. diff --git a/doc/src/rust/packages/builtin.md b/doc/src/rust/packages/builtin.md deleted file mode 100644 index a9b07b6c..00000000 --- a/doc/src/rust/packages/builtin.md +++ /dev/null @@ -1,40 +0,0 @@ -Built-In Packages -================ - -{{#include ../../links.md}} - -`Engine::new` creates an [`Engine`] with the `StandardPackage` loaded. - -`Engine::new_raw` creates an [`Engine`] with _no_ package loaded. - -| Package | Description | In `Core` | In `Standard` | -| ---------------------- | ------------------------------------------------------------------------------------------------------ | :-------: | :-----------: | -| `ArithmeticPackage` | arithmetic operators (e.g. `+`, `-`, `*`, `/`) for numeric types that are not built in (e.g. `u16`) | yes | yes | -| `BasicIteratorPackage` | numeric ranges (e.g. `range(1, 10)`) | yes | yes | -| `LogicPackage` | logical and comparison operators (e.g. `==`, `>`) for numeric types that are not built in (e.g. `u16`) | yes | yes | -| `BasicStringPackage` | basic string functions (e.g. `print`, `debug`, `len`) that are not built in | yes | yes | -| `BasicTimePackage` | basic time functions (e.g. [timestamps]) | yes | yes | -| `MoreStringPackage` | additional string functions, including converting common types to string | no | yes | -| `BasicMathPackage` | basic math functions (e.g. `sin`, `sqrt`) | no | yes | -| `BasicArrayPackage` | basic [array] functions (not available under `no_index`) | no | yes | -| `BasicMapPackage` | basic [object map] functions (not available under `no_object`) | no | yes | -| `BasicFnPackage` | basic methods for [function pointers]. | yes | yes | -| `CorePackage` | basic essentials | yes | yes | -| `StandardPackage` | standard library (default for `Engine::new`) | no | yes | - - -`CorePackage` -------------- - -If only minimal functionalities are required, register the `CorePackage` instead: - -```rust -use rhai::Engine; -use rhai::packages::{Package, CorePackage}; - -let mut engine = Engine::new_raw(); -let package = CorePackage::new(); - -// Register the package into the 'Engine' by converting it into a shared module. -engine.register_global_module(package.as_shared_module()); -``` diff --git a/doc/src/rust/packages/create.md b/doc/src/rust/packages/create.md deleted file mode 100644 index b02fe61d..00000000 --- a/doc/src/rust/packages/create.md +++ /dev/null @@ -1,130 +0,0 @@ -Create a Custom Package -======================= - -{{#include ../../links.md}} - - -The macro `def_package!` can be used to create a custom [package]. - -A custom package can aggregate many other packages into a single self-contained unit. -More functions can be added on top of others. - - -`def_package!` --------------- - -> `def_package!(root:package_name:description, variable, block)` - -where: - -| Parameter | Description | -| :------------: | ----------------------------------------------------------------------------------------------- | -| `root` | root namespace, usually `rhai` | -| `package_name` | name of the package, usually ending in `...Package` | -| `description` | doc-comment for the package | -| `variable` | a variable name holding a reference to the [module] (`&mut Module`) that is to form the package | -| `block` | a code block that initializes the package | - - -Examples --------- - -```rust -// Import necessary types and traits. -use rhai::{ - def_package, // 'def_package!' macro - packages::Package, // 'Package' trait - packages::{ // pre-defined packages - ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage - } -}; - -// Define the package 'MyPackage'. -def_package!(rhai:MyPackage:"My own personal super package", module, { - // Aggregate other packages simply by calling 'init' on each. - ArithmeticPackage::init(module); - LogicPackage::init(module); - BasicArrayPackage::init(module); - BasicMapPackage::init(module); - - // Register additional Rust functions using the standard 'set_fn_XXX' module API. - let hash = module.set_fn_1("foo", |s: ImmutableString| { - Ok(foo(s.into_owned())) - }); - - // Remember to update the parameter names/types and return type metadata. - // 'set_fn_XXX' by default does not set function metadata. - module.update_fn_metadata(hash, ["s: ImmutableString", "i64"]); -}); -``` - - -Create a Custom Package from a Plugin Module -------------------------------------------- - -By far the easiest way to create a custom module is to call `plugin::combine_with_exported_module!` -from within `def_package!` which simply merges in all the functions defined within a [plugin module]. - -In fact, this exactly is how Rhai's built-in packages, such as `BasicMathPackage`, are implemented. - -Due to specific requirements of a [package], `plugin::combine_with_exported_module!` -_flattens_ all sub-modules (i.e. all functions and [type iterators] defined within sub-modules -are pulled up to the top level instead) and so there will not be any sub-modules added to the package. - -Variables in the [plugin module] are ignored. - -```rust -// Import necessary types and traits. -use rhai::{ - def_package, - packages::Package, - packages::{ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage} -}; -use rhai::plugin::*; - -// Define plugin module. -#[export_module] -mod my_module { - pub const MY_NUMBER: i64 = 42; - - pub fn greet(name: &str) -> String { - format!("hello, {}!", name) - } - pub fn get_num() -> i64 { - 42 - } - - // This is a sub-module, but if using combine_with_exported_module!, it will - // be flattened and all functions registered at the top level. - pub mod my_sub_module { - pub fn get_sub_num() -> i64 { - 0 - } - } -} - -// Define the package 'MyPackage'. -def_package!(rhai:MyPackage:"My own personal super package", module, { - // Aggregate other packages simply by calling 'init' on each. - ArithmeticPackage::init(module); - LogicPackage::init(module); - BasicArrayPackage::init(module); - BasicMapPackage::init(module); - - // Merge all registered functions and constants from the plugin module into the custom package. - // - // The sub-module 'my_sub_module' is flattened and its functions registered at the top level. - // - // The text string name in the second parameter can be anything and is reserved for future use; - // it is recommended to be an ID string that uniquely identifies the plugin module. - // - // The constant variable, 'MY_NUMBER', is ignored. - // - // This call ends up registering three functions at the top level of the package: - // 1) greet - // 2) get_num - // 3) get_sub_num (pulled up from 'my_sub_module') - // - combine_with_exported_module!(module, "my-functions", my_module)); -}); -``` diff --git a/doc/src/rust/packages/index.md b/doc/src/rust/packages/index.md deleted file mode 100644 index afbba8cf..00000000 --- a/doc/src/rust/packages/index.md +++ /dev/null @@ -1,58 +0,0 @@ -Packages -======== - -{{#include ../../links.md}} - -The built-in library of Rhai is provided as various _packages_ that can be -turned into _shared_ [modules], which in turn can be registered into the -_global namespace_ of an [`Engine`] via `Engine::register_global_module`. - -Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package` -must be loaded in order for packages to be used. - -### Packages _are_ Modules - -Internally, a _package_ is a _newtype_ wrapping a pre-defined [module], -with some conveniences to make it easier to define and use as a standard -_library_ for an [`Engine`]. - -Packages typically contain Rust functions that are callable within a Rhai script. -All _top-level_ functions in a package are available under the _global namespace_ -(i.e. they're available without namespace qualifiers). - -Sub-modules and variables are ignored in packages. - - -Share a Package Among Multiple `Engine`'s ----------------------------------------- - -`Engine::register_global_module` and `Engine::register_static_module` both require _shared_ [modules]. - -Once a package is created (e.g. via `Package::new`), it can create _shared_ [modules] -(via `Package::as_shared_module`) and register them into multiple instances of [`Engine`], -even across threads (under the [`sync`] feature). - -Therefore, a package only has to be created _once_ and essentially shared among multiple -[`Engine`] instances. This is particular useful when spawning large number of [raw `Engine`'s][raw `Engine`]. - -```rust -use rhai::Engine; -use rhai::packages::Package // load the 'Package' trait to use packages -use rhai::packages::CorePackage; // the 'core' package contains basic functionalities (e.g. arithmetic) - -// Create a package - can be shared among multiple 'Engine' instances -let package = CorePackage::new(); - -let mut engines_collection: Vec = Vec::new(); - -// Create 100 'raw' Engines -for _ in 0..100 { - let mut engine = Engine::new_raw(); - - // Register the package into the global namespace. - // 'Package::as_shared_module' converts the package into a shared module. - engine.register_global_module(package.as_shared_module()); - - engines_collection.push(engine); -} -``` diff --git a/doc/src/rust/print-custom.md b/doc/src/rust/print-custom.md deleted file mode 100644 index c013559b..00000000 --- a/doc/src/rust/print-custom.md +++ /dev/null @@ -1,18 +0,0 @@ -Printing for Custom Types -======================== - -{{#include ../links.md}} - -To use custom types for [`print`] and [`debug`], or convert its value into a [string], -it is necessary that the following functions be registered (assuming the custom type -is `T: Display + Debug`): - -| Function | Signature | Typical implementation | Usage | -| ----------- | ---------------------------------------------- | ---------------------------- | -------------------------------------------------------------------- | -| `to_string` | \|x: &mut T\| -> String | `x.to_string()` | converts the custom type into a [string] | -| `print` | \|x: &mut T\| -> String | `x.to_string()` | converts the custom type into a [string] for the [`print`] statement | -| `to_debug` | \|x: &mut T\| -> String | `format!("{:?}", x)` | converts the custom type into a [string] in debug format | -| `debug` | \|x: &mut T\| -> String | `format!("{:?}", x)` | converts the custom type into a [string] for the [`debug`] statement | -| `+` | \|s: &str, x: T\| -> String | `format!("{}{}", s, x)` | concatenates the custom type with another [string] | -| `+` | \|x: &mut T, s: &str\| -> String | `x.to_string().push_str(s);` | concatenates another [string] with the custom type | -| `+=` | \|s: &mut ImmutableString, x: T\| | `s += x.to_string()` | appends the custom type to an existing [string] | diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md deleted file mode 100644 index 7b7566f7..00000000 --- a/doc/src/rust/register-raw.md +++ /dev/null @@ -1,189 +0,0 @@ -Use the Low-Level API to Register a Rust Function -================================================ - -{{#include ../links.md}} - -When a native Rust function is registered with an `Engine` using the `Engine::register_XXX` API, -Rhai transparently converts all function arguments from [`Dynamic`] into the correct types before -calling the function. - -For more power and flexibility, there is a _low-level_ API to work directly with [`Dynamic`] values -without the conversions. - - -Raw Function Registration -------------------------- - -The `Engine::register_raw_fn` method is marked _volatile_, meaning that it may be changed without warning. - -If this is acceptable, then using this method to register a Rust function opens up more opportunities. - -In particular, a the current _native call context_ (in form of the `NativeCallContext` type) is passed as an argument. -`NativeCallContext` exposes the current [`Engine`], among others, so the Rust function can also use [`Engine`] facilities -(such as evaluating a script). - -```rust -engine.register_raw_fn( - "increment_by", // function name - &[ // a slice containing parameter types - std::any::TypeId::of::(), // type of first parameter - std::any::TypeId::of::() // type of second parameter - ], - |context, args| { // fixed function signature - // Arguments are guaranteed to be correct in number and of the correct types. - - // But remember this is Rust, so you can keep only one mutable reference at any one time! - // Therefore, get a '&mut' reference to the first argument _last_. - // Alternatively, use `args.split_first_mut()` etc. to split the slice first. - - let y = *args[1].read_lock::().unwrap(); // get a reference to the second argument - // then copy it because it is a primary type - - let y = std::mem::take(args[1]).cast::(); // alternatively, directly 'consume' it - - let x = args[0].write_lock::().unwrap(); // get a '&mut' reference to the first argument - - *x += y; // perform the action - - Ok(Dynamic::UNIT) // must be 'Result>' - } -); - -// The above is the same as (in fact, internally they are equivalent): - -engine.register_fn("increment_by", |x: &mut i64, y: i64| *x += y); -``` - - -Function Signature ------------------- - -The function signature passed to `Engine::register_raw_fn` takes the following form: - -> `Fn(context: NativeCallContext, args: &mut [&mut Dynamic])` -> `-> Result> + 'static` - -where: - -| Parameter | Type | Description | -| -------------------------- | :-------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `T` | `impl Clone` | return type of the function | -| `context` | `NativeCallContext` | the current _native call context_ | -| • `engine()` | `&Engine` | the current [`Engine`], with all configurations and settings.
This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. | -| • `source()` | `Option<&str>` | reference to the current source, if any | -| • `iter_imports()` | `impl Iterator` | iterator of the current stack of [modules] imported via `import` statements | -| • `imports()` | `&Imports` | reference to the current stack of [modules] imported via `import` statements; requires the [`internals`] feature | -| • `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | -| • `namespaces()` | `&[&Module]` | reference to the namespaces (as [modules]) containing all script-defined functions; requires the [`internals`] feature | -| `args` | `&mut [&mut Dynamic]` | a slice containing `&mut` references to [`Dynamic`] values.
The slice is guaranteed to contain enough arguments _of the correct types_. | - -### Return value - -The return value is the result of the function call. - -Remember, in Rhai, all arguments _except_ the _first_ one are always passed by _value_ (i.e. cloned). -Therefore, it is unnecessary to ever mutate any argument except the first one, as all mutations -will be on the cloned copy. - - -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 | - -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. - - -Example – Passing a Callback to a Rust Function ----------------------------------------------------- - -The low-level API is useful when there is a need to interact with the scripting [`Engine`] -within a function. - -The following example registers a function that takes a [function pointer] as an argument, -then calls it within the same [`Engine`]. This way, a _callback_ function can be provided -to a native Rust function. - -```rust -use rhai::{Engine, FnPtr}; - -let mut engine = Engine::new(); - -// Register a Rust function -engine.register_raw_fn( - "bar", - &[ - std::any::TypeId::of::(), // parameter types - std::any::TypeId::of::(), - std::any::TypeId::of::(), - ], - |context, args| { - // 'args' is guaranteed to contain enough arguments of the correct types - - let fp = std::mem::take(args[1]).cast::(); // 2nd argument - function pointer - let value = args[2].clone(); // 3rd argument - function argument - let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer - - // Use 'FnPtr::call_dynamic' to call the function pointer. - // Beware, private script-defined functions will not be found. - fp.call_dynamic(context, Some(this_ptr), [value]) - }, -); - -let result = engine.eval::( - r#" - fn foo(x) { this += x; } // script-defined function 'foo' - - let x = 41; // object - x.bar(Fn("foo"), 1); // pass 'foo' as function pointer - x - "#)?; -``` - - -TL;DR – Why `read_lock` and `write_lock` ---------------------------------------------- - -The `Dynamic` API that casts it to a reference to a particular data type is `read_lock` -(for an immutable reference) and `write_lock` (for a mutable reference). - -As the naming shows, something is _locked_ in order to allow this access, and that something -is a _shared value_ created by [capturing][automatic currying] variables from [closures]. - -Shared values are implemented as `Rc>` (`Arc>` under [`sync`]). - -If the value is _not_ a shared value, or if running under [`no_closure`] where there is -no [capturing][automatic currying], this API de-sugars to a simple `Dynamic::downcast_ref` and -`Dynamic::downcast_mut`. In other words, there is no locking and reference counting overhead -for the vast majority of non-shared values. - -If the value is a shared value, then it is first locked and the returned lock guard -then allows access to the underlying value in the specified type. - - -Hold Multiple References ------------------------- - -In order to access a value argument that is expensive to clone _while_ holding a mutable reference -to the first argument, either _consume_ that argument via `mem::take` as above, or use `args.split_first` -to partition the slice: - -```rust -// Partition the slice -let (first, rest) = args.split_first_mut().unwrap(); - -// Mutable reference to the first parameter -let this_ptr = &mut *first.write_lock::().unwrap(); - -// Immutable reference to the second value parameter -// This can be mutable but there is no point because the parameter is passed by value -let value_ref = &*rest[0].read_lock::().unwrap(); -``` diff --git a/doc/src/rust/serde.md b/doc/src/rust/serde.md deleted file mode 100644 index 38ef690a..00000000 --- a/doc/src/rust/serde.md +++ /dev/null @@ -1,126 +0,0 @@ -Serialization and Deserialization of `Dynamic` with `serde` -========================================================= - -{{#include ../links.md}} - -Rhai's [`Dynamic`] type supports serialization and deserialization by [`serde`](https://crates.io/crates/serde) -via the [`serde`][features] feature. - -A [`Dynamic`] can be seamlessly converted to and from a type that implements -[`serde::Serialize`](https://docs.serde.rs/serde/trait.Serialize.html) and/or -[`serde::Deserialize`](https://docs.serde.rs/serde/trait.Deserialize.html). - - -Serialization -------------- - -The function `rhai::serde::to_dynamic` automatically converts any Rust type that implements -[`serde::Serialize`](https://docs.serde.rs/serde/trait.Serialize.html) into a [`Dynamic`]. - -This is usually not necessary because using [`Dynamic::from`][`Dynamic`] is much easier and is essentially -the same thing. The only difference is treatment for integer values. `Dynamic::from` will keep the different -integer types intact, while `rhai::serde::to_dynamic` will convert them all into [`INT`][standard types] -(i.e. the system integer type which is `i64` or `i32` depending on the [`only_i32`] feature). - -In particular, Rust `struct`'s (or any type that is marked as a `serde` map) are converted into [object maps] -while Rust `Vec`'s (or any type that is marked as a `serde` sequence) are converted into [arrays]. - -While it is also simple to serialize a Rust type to `JSON` via `serde`, -then use [`Engine::parse_json`]({{rootUrl}}/language/json.md) to convert it into an [object map], -`rhai::serde::to_dynamic` serializes it to [`Dynamic`] directly via `serde` without going through the `JSON` step. - -```rust -use rhai::{Dynamic, Map}; -use rhai::serde::to_dynamic; - -#[derive(Debug, serde::Serialize)] -struct Point { - x: f64, - y: f64 -} - -#[derive(Debug, serde::Serialize)] -struct MyStruct { - a: i64, - b: Vec, - c: bool, - d: Point -} - -let x = MyStruct { - a: 42, - b: vec![ "hello".into(), "world".into() ], - c: true, - d: Point { x: 123.456, y: 999.0 } -}; - -// Convert the 'MyStruct' into a 'Dynamic' -let map: Dynamic = to_dynamic(x); - -map.is::() == true; -``` - - -Deserialization ---------------- - -The function `rhai::serde::from_dynamic` automatically converts a [`Dynamic`] value into any Rust type -that implements [`serde::Deserialize`](https://docs.serde.rs/serde/trait.Deserialize.html). - -In particular, [object maps] are converted into Rust `struct`'s (or any type that is marked as -a `serde` map) while [arrays] are converted into Rust `Vec`'s (or any type that is marked -as a `serde` sequence). - -```rust -use rhai::{Engine, Dynamic}; -use rhai::serde::from_dynamic; - -#[derive(Debug, serde::Deserialize)] -struct Point { - x: f64, - y: f64 -} - -#[derive(Debug, serde::Deserialize)] -struct MyStruct { - a: i64, - b: Vec, - c: bool, - d: Point -} - -let engine = Engine::new(); - -let result: Dynamic = engine.eval(r#" - ##{ - a: 42, - b: [ "hello", "world" ], - c: true, - d: #{ x: 123.456, y: 999.0 } - } - "#)?; - -// Convert the 'Dynamic' object map into 'MyStruct' -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 -------------------- - -The [`serde`](https://crates.io/crates/serde) crate is quite heavy. - -If only _simple_ JSON parsing (i.e. only deserialization) of a hash object into a Rhai [object map] is required, -the [`Engine::parse_json`]({{rootUrl}}/language/json.md}}) method is available as a _cheap_ alternative, -but it does not provide the same level of correctness, nor are there any configurable options. diff --git a/doc/src/rust/strings.md b/doc/src/rust/strings.md deleted file mode 100644 index 737bb207..00000000 --- a/doc/src/rust/strings.md +++ /dev/null @@ -1,43 +0,0 @@ -`String` Parameters in Rust Functions -==================================== - -{{#include ../links.md}} - - -Avoid `String` --------------- - -As must as possible, avoid using `String` parameters in functions. - -Each `String` argument is cloned during every single call to that function – and the copy -immediately thrown away right after the call. - -Needless to say, it is _extremely_ inefficient to use `String` parameters. - - -`&str` Maps to `ImmutableString` -------------------------------- - -Rust functions accepting parameters of `String` should use `&str` instead because it maps directly to -[`ImmutableString`][string] which is the type that Rhai uses to represent [strings] internally. - -The parameter type `String` involves always converting an [`ImmutableString`][string] into a `String` -which mandates cloning it. - -Using `ImmutableString` or `&str` is much more efficient. -A common mistake made by novice Rhai users is to register functions with `String` parameters. - -```rust -fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Very inefficient!!! -fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- This is better -fn get_len3(s: ImmutableString) -> i64 { s.len() as i64 } // <- the above is equivalent to this - -engine - .register_fn("len1", get_len1) - .register_fn("len2", get_len2) - .register_fn("len3", get_len3); - -let len = engine.eval::("x.len1()")?; // 'x' is cloned, very inefficient! -let len = engine.eval::("x.len2()")?; // 'x' is shared -let len = engine.eval::("x.len3()")?; // 'x' is shared -``` diff --git a/doc/src/rust/traits.md b/doc/src/rust/traits.md deleted file mode 100644 index a9b1a297..00000000 --- a/doc/src/rust/traits.md +++ /dev/null @@ -1,14 +0,0 @@ -Traits -====== - -{{#include ../links.md}} - -A number of traits, under the `rhai::` module namespace, provide additional functionalities. - -| Trait | Description | Methods | -| ------------------------ | ------------------------------------------------------------------ | --------------------------------------------------------------------- | -| `RegisterFn` | trait for registering functions | `register_fn` | -| `RegisterResultFn` | trait for registering [fallible functions] | `register_result_fn` | -| `Func` | trait for creating Rust closures from script | `create_from_ast`, `create_from_script` | -| `ModuleResolver` | trait implemented by [module resolution][module resolver] services | `resolve` | -| `plugin::PluginFunction` | trait implemented by [plugin] functions | `call`, `is_method_call`, `is_variadic`, `clone_boxed`, `input_types` | diff --git a/doc/src/safety/checked.md b/doc/src/safety/checked.md deleted file mode 100644 index e16164b0..00000000 --- a/doc/src/safety/checked.md +++ /dev/null @@ -1,11 +0,0 @@ -Checked Arithmetic -================= - -{{#include ../links.md}} - -By default, all arithmetic calculations in Rhai are _checked_, meaning that the script terminates -with an error whenever it detects a numeric over-flow/under-flow condition or an invalid -floating-point operation, instead of crashing the entire system. - -This checking can be turned off via the [`unchecked`] feature for higher performance -(but higher risks as well). diff --git a/doc/src/safety/index.md b/doc/src/safety/index.md deleted file mode 100644 index 6e4e8c1c..00000000 --- a/doc/src/safety/index.md +++ /dev/null @@ -1,45 +0,0 @@ -Safety and Protection Against DoS Attacks -======================================== - -{{#include ../links.md}} - -For scripting systems open to untrusted user-land scripts, it is always best to limit the amount of -resources used by a script so that it does not consume more resources that it is allowed to. - -The most important resources to watch out for are: - -* **Memory**: A malicious script may continuously grow a [string], an [array] or [object map] until all memory is consumed. - - It may also create a large [array] or [object map] literal that exhausts all memory during parsing. - -* **CPU**: A malicious script may run an infinite tight loop that consumes all CPU cycles. - -* **Time**: A malicious script may run indefinitely, thereby blocking the calling system which is waiting for a result. - -* **Stack**: A malicious script may attempt an infinite recursive call that exhausts the call stack. - - Alternatively, it may create a degenerated deep expression with so many levels that the parser exhausts the call stack - when parsing the expression; or even deeply-nested statement blocks, if nested deep enough. - - Another way to cause a stack overflow is to load a [self-referencing module][`import`]. - -* **Overflows**: A malicious script may deliberately cause numeric over-flows and/or under-flows, divide by zero, and/or - create bad floating-point representations, in order to crash the system. - -* **Files**: A malicious script may continuously [`import`] an external module within an infinite loop, - thereby putting heavy load on the file-system (or even the network if the file is not local). - - Even when modules are not created from files, they still typically consume a lot of resources to load. - -* **Data**: A malicious script may attempt to read from and/or write to data that it does not own. If this happens, - it is a severe security breach and may put the entire system at risk. - - -`unchecked` ------------ - -All these safe-guards can be turned off via the [`unchecked`] feature, which disables all -safety checks (even fatal errors such as arithmetic overflows and division-by-zero). - -This will increase script evaluation performance, at the expense of having an erroneous -script able to panic the entire system. diff --git a/doc/src/safety/max-array-size.md b/doc/src/safety/max-array-size.md deleted file mode 100644 index a50a3745..00000000 --- a/doc/src/safety/max-array-size.md +++ /dev/null @@ -1,40 +0,0 @@ -Maximum Size of Arrays -===================== - -{{#include ../links.md}} - -Limit How Large Arrays Can Grow ------------------------------- - -Rhai by default does not limit how large an [array] can be. - -This can be changed via the `Engine::set_max_array_size` method, with zero being unlimited (the default). - -A script attempting to create an array literal larger than the maximum will terminate with a parse error. - -Any script operation that produces an array larger than the maximum also terminates the script with an error result. - -This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). - -```rust -let mut engine = Engine::new(); - -engine.set_max_array_size(500); // allow arrays only up to 500 items - -engine.set_max_array_size(0); // allow unlimited arrays -``` - - -Setting Maximum Size -------------------- - -Be conservative when setting a maximum limit and always consider the fact that a registered function may grow -an array's size without Rhai noticing until the very end. - -For instance, the built-in '`+`' operator for arrays concatenates two arrays together to form one larger array; -if both arrays are _slightly_ below the maximum size limit, the resultant array may be almost _twice_ the maximum size. - -As a malicious script may create a deeply-nested array which consumes huge amounts of memory while each individual -array still stays under the maximum size limit, Rhai also recursively adds up the sizes of all [strings], [arrays] -and [object maps] contained within each array to make sure that the _aggregate_ sizes of none of these data structures -exceed their respective maximum size limits (if any). diff --git a/doc/src/safety/max-call-stack.md b/doc/src/safety/max-call-stack.md deleted file mode 100644 index 7efb3c9d..00000000 --- a/doc/src/safety/max-call-stack.md +++ /dev/null @@ -1,31 +0,0 @@ -Maximum Call Stack Depth -======================= - -{{#include ../links.md}} - -Limit How Stack Usage by Scripts -------------------------------- - -Rhai by default limits function calls to a maximum depth of 128 levels (8 levels in debug build). - -This limit may be changed via the `Engine::set_max_call_levels` method. - -A script exceeding the maximum call stack depth will terminate with an error result. - -This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). - -```rust -let mut engine = Engine::new(); - -engine.set_max_call_levels(10); // allow only up to 10 levels of function calls - -engine.set_max_call_levels(0); // allow no function calls at all (max depth = zero) -``` - - -Setting Maximum Stack Depth --------------------------- - -When setting this limit, care must be also taken to the evaluation depth of each _statement_ -within a function. It is entirely possible for a malicious script to embed a recursive call deep -inside a nested expression or statement block (see [maximum statement depth]). diff --git a/doc/src/safety/max-map-size.md b/doc/src/safety/max-map-size.md deleted file mode 100644 index a980562b..00000000 --- a/doc/src/safety/max-map-size.md +++ /dev/null @@ -1,40 +0,0 @@ -Maximum Size of Object Maps -========================== - -{{#include ../links.md}} - -Limit How Large Object Maps Can Grow ------------------------------------ - -Rhai by default does not limit how large (i.e. the number of properties) an [object map] can be. - -This can be changed via the `Engine::set_max_map_size` method, with zero being unlimited (the default). - -A script attempting to create an object map literal with more properties than the maximum will terminate with a parse error. - -Any script operation that produces an object map with more properties than the maximum also terminates the script with an error result. - -This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). - -```rust -let mut engine = Engine::new(); - -engine.set_max_map_size(500); // allow object maps with only up to 500 properties - -engine.set_max_map_size(0); // allow unlimited object maps -``` - - -Setting Maximum Size -------------------- - -Be conservative when setting a maximum limit and always consider the fact that a registered function may grow -an object map's size without Rhai noticing until the very end. - -For instance, the built-in '`+`' operator for object maps concatenates two object maps together to form one larger object map; -if both object maps are _slightly_ below the maximum size limit, the resultant object map may be almost _twice_ the maximum size. - -As a malicious script may create a deeply-nested object map which consumes huge amounts of memory while each individual -object map still stays under the maximum size limit, Rhai also recursively adds up the sizes of all [strings], [arrays] -and [object maps] contained within each object map to make sure that the _aggregate_ sizes of none of these data structures -exceed their respective maximum size limits (if any). diff --git a/doc/src/safety/max-modules.md b/doc/src/safety/max-modules.md deleted file mode 100644 index adb1c133..00000000 --- a/doc/src/safety/max-modules.md +++ /dev/null @@ -1,26 +0,0 @@ -Maximum Number of Modules -======================== - -{{#include ../links.md}} - -Rhai by default does not limit how many [modules] can be loaded via [`import`] statements. - -This can be changed via the `Engine::set_max_modules` method. Notice that setting the maximum number -of modules to zero does _not_ indicate unlimited modules, but disallows loading any module altogether. - -A script attempting to load more than the maximum number of modules will terminate with an error result. - -This limit can also be used to stop [`import`-loops][`import`] (i.e. cycles of modules referring to each other). - -This check can be disabled via the [`unchecked`] feature for higher performance -(but higher risks as well). - -```rust -let mut engine = Engine::new(); - -engine.set_max_modules(5); // allow loading only up to 5 modules - -engine.set_max_modules(0); // disallow loading any module (maximum = zero) - -engine.set_max_modules(1000); // set to a large number for effectively unlimited modules -``` diff --git a/doc/src/safety/max-operations.md b/doc/src/safety/max-operations.md deleted file mode 100644 index 86d85fc1..00000000 --- a/doc/src/safety/max-operations.md +++ /dev/null @@ -1,44 +0,0 @@ -Maximum Number of Operations -=========================== - -{{#include ../links.md}} - - -Limit How Long a Script Can Run ------------------------------- - -Rhai by default does not limit how much time or CPU a script consumes. - -This can be changed via the `Engine::set_max_operations` method, with zero being unlimited (the default). - -The _operations count_ is intended to be a very course-grained measurement of the amount of CPU that a script -has consumed, allowing the system to impose a hard upper limit on computing resources. - -A script exceeding the maximum operations count terminates with an error result. -This can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). - -```rust -let mut engine = Engine::new(); - -engine.set_max_operations(500); // allow only up to 500 operations for this script - -engine.set_max_operations(0); // allow unlimited operations -``` - - -What Does One _Operation_ Mean ------------------------------ - -The concept of one single _operation_ in Rhai is volatile – it roughly equals one expression node, -loading one variable/constant, one operator call, one iteration of a loop, or one function call etc. -with sub-expressions, statements and function calls executed inside these contexts accumulated on top. - -A good rule-of-thumb is that one simple non-trivial expression consumes on average 5-10 operations. - -One _operation_ can take an unspecified amount of time and real CPU cycles, depending on the particulars. -For example, loading a constant consumes very few CPU cycles, while calling an external Rust function, -though also counted as only one operation, may consume much more computing resources. - -To help visualize, think of an _operation_ as roughly equals to one _instruction_ of a hypothetical CPU -which includes _specialized_ instructions, such as _function call_, _load module_ etc., each taking up -one CPU cycle to execute. diff --git a/doc/src/safety/max-stmt-depth.md b/doc/src/safety/max-stmt-depth.md deleted file mode 100644 index e9749689..00000000 --- a/doc/src/safety/max-stmt-depth.md +++ /dev/null @@ -1,56 +0,0 @@ -Maximum Statement Depth -====================== - -{{#include ../links.md}} - -Limit How Deeply-Nested a Statement Can Be ------------------------------------------ - -Rhai by default limits statements and expressions nesting to a maximum depth of 128 -(which should be plenty) when they are at _global_ level, but only a depth of 32 -when they are within function bodies. - -For debug builds, these limits are set further downwards to 32 and 16 respectively. - -That is because it is possible to overflow the [`Engine`]'s stack when it tries to -recursively parse an extremely deeply-nested code stream. - -```rust -// The following, if long enough, can easily cause stack overflow during parsing. -let a = (1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(...)+1))))))))))); -``` - -This limit may be changed via the `Engine::set_max_expr_depths` method. - -There are two limits to set, one for the maximum depth at global level, and the other for function bodies. - -A script exceeding the maximum nesting depths will terminate with a parsing error. -The malicious [`AST`] will not be able to get past parsing in the first place. - -This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). - -```rust -let mut engine = Engine::new(); - -engine.set_max_expr_depths(50, 5); // allow nesting up to 50 layers of expressions/statements - // at global level, but only 5 inside functions -``` - -Beware that there may be multiple layers for a simple language construct, even though it may correspond -to only one AST node. That is because the Rhai _parser_ internally runs a recursive chain of function calls -and it is important that a malicious script does not panic the parser in the first place. - - -Beware of Recursion -------------------- - -_Functions_ are placed under stricter limits because of the multiplicative effect of _recursion_. - -A script can effectively call itself while deep inside an expression chain within the function body, -thereby overflowing the stack even when the level of recursion is within limit. - -In general, make sure that `C x ( 5 + F ) + S` layered calls do not cause a stack overflow, where: - -* `C` = maximum call stack depth, -* `F` = maximum statement depth for functions, -* `S` = maximum statement depth at global level. diff --git a/doc/src/safety/max-string-size.md b/doc/src/safety/max-string-size.md deleted file mode 100644 index c693af9c..00000000 --- a/doc/src/safety/max-string-size.md +++ /dev/null @@ -1,36 +0,0 @@ -Maximum Length of Strings -======================== - -{{#include ../links.md}} - -Limit How Long Strings Can Grow ------------------------------- - -Rhai by default does not limit how long a [string] can be. - -This can be changed via the `Engine::set_max_string_size` method, with zero being unlimited (the default). - -A script attempting to create a string literal longer than the maximum length will terminate with a parse error. - -Any script operation that produces a string longer than the maximum also terminates the script with an error result. - -This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). - -```rust -let mut engine = Engine::new(); - -engine.set_max_string_size(500); // allow strings only up to 500 bytes long (in UTF-8 format) - -engine.set_max_string_size(0); // allow unlimited string length -``` - - -Setting Maximum Length ---------------------- - -Be conservative when setting a maximum limit and always consider the fact that a registered function may grow -a string's length without Rhai noticing until the very end. - -For instance, the built-in '`+`' operator for strings concatenates two strings together to form one longer string; -if both strings are _slightly_ below the maximum length limit, the resultant string may be almost _twice_ the maximum length. - diff --git a/doc/src/safety/progress.md b/doc/src/safety/progress.md deleted file mode 100644 index 749ac95b..00000000 --- a/doc/src/safety/progress.md +++ /dev/null @@ -1,52 +0,0 @@ -Track Progress and Force-Termination -=================================== - -{{#include ../links.md}} - -It is impossible to know when, or even whether, a script run will end -(a.k.a. the [Halting Problem](http://en.wikipedia.org/wiki/Halting_problem)). - -When dealing with third-party untrusted scripts that may be malicious, to track evaluation progress and -to force-terminate a script prematurely (for any reason), provide a closure to the [`Engine`] via -the `Engine::on_progress` method: - -```rust -let mut engine = Engine::new(); - -engine.on_progress(|count| { // parameter is number of operations already performed - if count % 1000 == 0 { - println!("{}", count); // print out a progress log every 1,000 operations - } - None // return 'None' to continue running the script - // return 'Some(token)' to immediately terminate the script -}); -``` - -The closure passed to `Engine::on_progress` will be called once for every operation. -Return `Some(token)` to terminate the script immediately, with the provided value -(any [`Dynamic`]) acting as a termination token. - - -Termination Token ------------------ - -The [`Dynamic`] value returned by the closure for `Engine::on_progress` is a _termination token_. -A script that is manually terminated returns with `Err(EvalAltResult::ErrorTerminated)` -wrapping this value. - -The termination token is commonly used to provide information on the _reason_ or _source_ -behind the termination decision. - -If the termination token is not needed, simply return `Some(Dynamic::UNIT)` to terminate the script -run with [`()`] as the token. - - -Operations Count vs. Progress Percentage ---------------------------------------- - -Notice that the _operations count_ value passed into the closure does not indicate the _percentage_ of work -already done by the script (and thus it is not real _progress_ tracking), because it is impossible to determine -how long a script may run. - -It is possible, however, to calculate this percentage based on an estimated total number of operations -for a typical run. diff --git a/doc/src/safety/sandbox.md b/doc/src/safety/sandbox.md deleted file mode 100644 index 798a73ac..00000000 --- a/doc/src/safety/sandbox.md +++ /dev/null @@ -1,39 +0,0 @@ -Sand-Boxing – Block Access to External Data -================================================ - -{{#include ../links.md}} - -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, 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(); - -// 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... -``` - - -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/doc/src/start/bin.md b/doc/src/start/bin.md deleted file mode 100644 index a053315d..00000000 --- a/doc/src/start/bin.md +++ /dev/null @@ -1,59 +0,0 @@ -Packaged Utilities -================== - -{{#include ../links.md}} - -A number of Rhai-driven utility programs can be found in the `src/bin` directory: - -| Utility program | Description | -| :-----------------------------------------------: | ----------------------------------------------------------- | -| [`rhai-repl`]({{repoTree}}/examples/rhai-repl.rs) | a simple REPL, interactively evaluate statements from stdin | -| [`rhai-run`]({{repoTree}}/examples/rhai-run.rs) | runs each filename passed to it as a Rhai script | - - -`rhai-repl` – The Rhai REPL Tool -------------------------------------- - -`rhai-repl` is a particularly useful utility program – it allows one to interactively -try out Rhai's language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop). - -Filenames passed to it as command line arguments are run and loaded before the REPL starts. - -### Example - -The following command first runs three scripts – `init1.rhai`, `init2.rhai` and -`init3.rhai` – loading the functions defined in each script into the _global_ -namespace. - -Then it enters an REPL, which can call the above functions freely. - -```bash -rhai-repl init1.rhai init2.rhai init3.rhai -``` - - -`rhai-run` – The Rhai Runner ---------------------------------- - -Use `rhai-run` to run Rhai scripts. - -Filenames passed to it as command line arguments are run in sequence. - -### Example - -The following command runs the scripts `script1.rhai`, `script2.rhai` and `script3.rhai` -in order. - -```bash -rhai-run script1.rhai script2.rhai script3.rhai -``` - - -Running a Utility Program -------------------------- - -Utilities can be run with the following command: - -```bash -cargo run --bin {program_name} -``` diff --git a/doc/src/start/builds/index.md b/doc/src/start/builds/index.md deleted file mode 100644 index a3cdfbf3..00000000 --- a/doc/src/start/builds/index.md +++ /dev/null @@ -1,7 +0,0 @@ -Special Builds -============== - -{{#include ../../links.md}} - -It is possible to mix-and-match various [features] of the Rhai crate to make -specialized builds with specific characteristics and behaviors. diff --git a/doc/src/start/builds/minimal.md b/doc/src/start/builds/minimal.md deleted file mode 100644 index c916baea..00000000 --- a/doc/src/start/builds/minimal.md +++ /dev/null @@ -1,54 +0,0 @@ -Minimal Build -============= - -{{#include ../../links.md}} - -Configuration -------------- - -In order to compile a _minimal_ build – i.e. a build optimized for size – perhaps for `no-std` embedded targets or for -compiling to [WASM], it is essential that the correct linker flags are used in `cargo.toml`: - -```toml -[profile.release] -lto = "fat" # turn on Link-Time Optimizations -codegen-units = 1 # trade compile time with maximum optimization -opt-level = "z" # optimize for size -``` - - -Use `i32` Only --------------- - -For embedded systems that must optimize for code size, the architecture is commonly 32-bit. -Use [`only_i32`] to prune away large sections of code implementing functions for other numeric types -(including `i64`). - -If, for some reason, 64-bit long integers must be supported, use [`only_i64`] instead of [`only_i32`]. - - -Opt-Out of Features ------------------- - -Opt out of as many features as possible, if they are not needed, to reduce code size because, remember, by default -all code is compiled into the final binary since what a script requires cannot be predicted. -If a language feature will never be needed, omitting it is a prudent strategy to optimize the build for size. - -Omitting arrays ([`no_index`]) yields the most code-size savings, followed by floating-point support -([`no_float`]), checked arithmetic/script resource limits ([`unchecked`]) and finally object maps and custom types ([`no_object`]). - -Where the usage scenario does not call for loading externally-defined modules, use [`no_module`] to save some bytes. -Disable script-defined functions ([`no_function`]) and possibly closures ([`no_closure`]) when the features are not needed. -Both of these have some code size savings but not much. - - -Use a Raw [`Engine`] -------------------- - -[`Engine::new_raw`][raw `Engine`] creates a _raw_ engine. -A _raw_ engine supports, out of the box, only a very [restricted set]({{rootUrl}}/engine/raw.md#built-in-operators) -of basic arithmetic and logical operators. - -Selectively include other necessary functionalities by picking specific [packages] to minimize the footprint. - -Packages are shared (even across threads via the [`sync`] feature), so they only have to be created once. diff --git a/doc/src/start/builds/no-std.md b/doc/src/start/builds/no-std.md deleted file mode 100644 index ce30ddb0..00000000 --- a/doc/src/start/builds/no-std.md +++ /dev/null @@ -1,79 +0,0 @@ -`no-std` Build -============= - -{{#include ../../links.md}} - -The feature [`no_std`] automatically converts the scripting engine into a `no-std` build. - -Usually, a `no-std` build goes hand-in-hand with [minimal builds] because typical embedded -hardware (the primary target for `no-std`) has limited storage. - - -Nightly Required ----------------- - -Currently, [`no_std`] requires the nightly compiler due to the crates that it uses. - - -Implementation --------------- - -Rhai allocates, so the first thing that must be included in any `no-std` project is -an allocator crate, such as [`wee_alloc`](https://crates.io/crates/wee_alloc). - -Then there is the need to set up proper error/panic handlers. -The following example uses `panic = "abort"` and `wee_alloc` as the allocator. - -```rust -// Set up for no-std. -#![no_std] - -// The following no-std features are usually needed. -#![feature(alloc_error_handler, start, core_intrinsics, lang_items, link_cfg)] - -// Set up the global allocator. -extern crate alloc; -extern crate wee_alloc; - -#[global_allocator] -static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - -// Rust needs a CRT runtime on Windows when compiled with MSVC. -#[cfg(all(windows, target_env = "msvc"))] -#[link(name = "msvcrt")] -#[link(name = "libcmt")] -extern {} - -// Set up panic and error handlers -#[alloc_error_handler] -fn err_handler(_: core::alloc::Layout) -> ! { - core::intrinsics::abort(); -} - -#[panic_handler] -#[lang = "panic_impl"] -extern "C" fn rust_begin_panic(_: &core::panic::PanicInfo) -> ! { - core::intrinsics::abort(); -} - -#[lang = "eh_personality"] -extern "C" fn eh_personality() {} - -#[no_mangle] -extern "C" fn rust_eh_register_frames() {} - -#[no_mangle] -extern "C" fn rust_eh_unregister_frames() {} - -#[start] -fn main(_argc: isize, _argv: *const *const u8) -> isize { - // ... main program ... -} -``` - - -Samples -------- - -Check out the [`no-std` sample applications](../examples/rust.md#no-std-samples) -for different operating environments. diff --git a/doc/src/start/builds/performance.md b/doc/src/start/builds/performance.md deleted file mode 100644 index 859e1782..00000000 --- a/doc/src/start/builds/performance.md +++ /dev/null @@ -1,61 +0,0 @@ -Performance Build -================= - -{{#include ../../links.md}} - -Some features are for performance. For example, using [`only_i32`] or [`only_i64`] disables all other integer types (such as `u16`). - - -Use Only One Integer Type ------------------------- - -If only a single integer type is needed in scripts – most of the time this is the case – it is best to avoid registering -lots of functions related to other integer types that will never be used. As a result, [`Engine`] creation will be faster -because fewer functions need to be loaded. - -The [`only_i32`] and [`only_i64`] features disable all integer types except `i32` or `i64` respectively. - - -Use Only 32-Bit Numbers ----------------------- - -If only 32-bit integers are needed – again, most of the time this is the case – turn on [`only_i32`]. -Under this feature, only `i32` is supported as a built-in integer type and no others. - -On 64-bit targets this may not gain much, but on certain 32-bit targets this improves performance -due to 64-bit arithmetic requiring more CPU cycles to complete. - - -Minimize Size of `Dynamic` -------------------------- - -Turning on [`no_float`] or [`f32_float`] and [`only_i32`] on 32-bit targets makes the critical [`Dynamic`] -data type only 8 bytes long. -Normally [`Dynamic`] can be up to 12-16 bytes in order to hold an `i64` or `f64`. - -A small [`Dynamic`] helps performance due to better cache efficiency. - - -Use `ImmutableString` --------------------- - -Internally, Rhai uses _immutable_ [strings] instead of the Rust `String` type. This is mainly to avoid excessive -cloning when passing function arguments. - -Rhai's internal string type is `ImmutableString` (basically `Rc` or `Arc` depending on the [`sync`] feature). -It is cheap to clone, but expensive to modify (a new copy of the string must be made in order to change it). - -Therefore, functions taking `String` parameters should use `ImmutableString` or `&str` (both map to `ImmutableString`) -for the best performance with Rhai. - - -Disable Closures ----------------- - -Support for [closures] that capture shared variables adds material overhead to script evaluation. - -This is because every data access must be checked whether it is a shared value and, if so, take a read -lock before reading it. - -Use [`no_closure`] to disable closure and capturing support to optimize the hot path -because there is no need to take locks for shared data. diff --git a/doc/src/start/builds/wasm.md b/doc/src/start/builds/wasm.md deleted file mode 100644 index 27b85e77..00000000 --- a/doc/src/start/builds/wasm.md +++ /dev/null @@ -1,55 +0,0 @@ -Building to WebAssembly (WASM) -============================= - -{{#include ../../links.md}} - -It is possible to use Rhai when compiling to WebAssembly (WASM). -This yields a scripting engine (and language) that can be run in a standard web browser. - -Why you would _want_ to is another matter... as there is already a nice, fast, complete scripting language -for the the common WASM environment (i.e. a browser) – and it is called JavaScript. - -But anyhow, do it because you _can_! - -When building for WASM, certain features will not be available, -such as the script file API's and loading modules from external script files. - - -Size ----- - -Also look into [minimal builds] to reduce generated WASM size. - -As of this version, a typical, full-featured Rhai scripting engine compiles to a single WASM file -less than 200KB gzipped. - -When excluding features that are marginal in WASM environment, the gzipped payload can be -further shrunk to 160KB. - - -Speed ------ - -In benchmark tests, a WASM build runs scripts roughly 1.7-2.2x slower than a native optimized release build. - - -Common Features ---------------- - -Some Rhai functionalities are not necessary in a WASM environment, so the following features -are typically used for a WASM build: - -| Feature | Description | -| :-----------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [`unchecked`] | When a WASM module panics, it doesn't crash the entire web app; however this also disables [maximum number of operations] and [progress] tracking so a script can still run indefinitely – the web app must terminate it itself. | -| [`only_i32`] | WASM supports 32-bit and 64-bit integers, but most scripts will only need 32-bit. | -| [`f32_float`] | WASM supports 32-bit single-precision and 64-bit double-precision floating-point numbers, but single-precision is usually fine for most uses. | -| [`no_module`] | A WASM module cannot load modules from the file system, so usually this is not needed, but the savings are minimal; alternatively, a custom [module resolver] can be provided that loads other Rhai scripts. | - -The following features are typically _not_ used because they don't make sense in a WASM build: - -| Feature | Why unnecessary | -| :-----------: | ------------------------------------------------------------------------------------------------------ | -| [`sync`] | WASM is single-threaded. | -| [`no_std`] | `std` lib works fine with WASM. | -| [`internals`] | WASM usually doesn't need to access Rhai internal data structures, unless you are walking the [`AST`]. | diff --git a/doc/src/start/examples/index.md b/doc/src/start/examples/index.md deleted file mode 100644 index 6af1495b..00000000 --- a/doc/src/start/examples/index.md +++ /dev/null @@ -1,7 +0,0 @@ -Examples -======== - -{{#include ../../links.md}} - -Rhai comes with a number of examples showing how to integrate the scripting [`Engine`] within -a Rust application, as well as a number of sample scripts that showcase different Rhai language features. diff --git a/doc/src/start/examples/rust.md b/doc/src/start/examples/rust.md deleted file mode 100644 index b46d7249..00000000 --- a/doc/src/start/examples/rust.md +++ /dev/null @@ -1,45 +0,0 @@ -Rust Examples -============ - -{{#include ../../links.md}} - -A number of examples can be found in the `examples` directory: - -| Example | Description | -| ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -| [`arrays_and_structs`]({{repoTree}}/examples/arrays_and_structs.rs) | shows how to register a custom Rust type and using [arrays] on it | -| [`custom_types_and_methods`]({{repoTree}}/examples/custom_types_and_methods.rs) | shows how to register a custom Rust type and methods for it | -| [`hello`]({{repoTree}}/examples/hello.rs) | simple example that evaluates an expression and prints the result | -| [`reuse_scope`]({{repoTree}}/examples/reuse_scope.rs) | evaluates two pieces of code in separate runs, but using a common [`Scope`] | -| [`serde`]({{repoTree}}/examples/serde.rs) | example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).
The [`serde`] feature is required to run | -| [`simple_fn`]({{repoTree}}/examples/simple_fn.rs) | shows how to register a simple function | -| [`strings`]({{repoTree}}/examples/strings.rs) | shows different ways to register functions taking string arguments | - - -Running Examples ----------------- - -Examples can be run with the following command: - -```bash -cargo run --example {example_name} -``` - -`no-std` Samples ----------------- - -To illustrate `no-std` builds, a number of sample applications are available under the `no_std` directory: - -| Sample | Description | Optimization | Allocator | Panics | -| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | :----------: | :-----------------------------------------------: | :----: | -| [`no_std_test`]({{repoTree}}/no_std/no_std_test) | bare-bones test application that evaluates a Rhai expression and sets the result as the return value | size | [`wee_alloc`](https://crates.io/crates/wee_alloc) | abort | - -`cargo run` cannot be used to run a `no-std` sample. It must first be built: - -```bash -cd no_std/no_std_test - -cargo +nightly build --release - -./target/release/no_std_test -``` diff --git a/doc/src/start/examples/scripts.md b/doc/src/start/examples/scripts.md deleted file mode 100644 index 80fe4908..00000000 --- a/doc/src/start/examples/scripts.md +++ /dev/null @@ -1,53 +0,0 @@ -Example Scripts -============== - -{{#include ../../links.md}} - -Language Feature Scripts ------------------------ - -There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` directory: - -| Script | Description | -| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | -| [`array.rhai`]({{repoTree}}/scripts/array.rhai) | [arrays] | -| [`assignment.rhai`]({{repoTree}}/scripts/assignment.rhai) | variable declarations | -| [`comments.rhai`]({{repoTree}}/scripts/comments.rhai) | just comments | -| [`for1.rhai`]({{repoTree}}/scripts/for1.rhai) | [`for`]({{rootUrl}}/language/for.md) loops | -| [`for2.rhai`]({{repoTree}}/scripts/for2.rhai) | [`for`]({{rootUrl}}/language/for.md) loops on [arrays] | -| [`function_decl1.rhai`]({{repoTree}}/scripts/function_decl1.rhai) | a [function] without parameters | -| [`function_decl2.rhai`]({{repoTree}}/scripts/function_decl2.rhai) | a [function] with two parameters | -| [`function_decl3.rhai`]({{repoTree}}/scripts/function_decl3.rhai) | a [function] with many parameters | -| [`if1.rhai`]({{repoTree}}/scripts/if1.rhai) | [`if`]({{rootUrl}}/language/if.md) example | -| [`loop.rhai`]({{repoTree}}/scripts/loop.rhai) | count-down [`loop`]({{rootUrl}}/language/loop.md) in Rhai, emulating a `do` .. `while` loop | -| [`module.rhai`]({{repoTree}}/scripts/module.rhai) | import a script file as a module | -| [`oop.rhai`]({{repoTree}}/scripts/oop.rhai) | simulate [object-oriented programming (OOP)][OOP] with [closures] | -| [`op1.rhai`]({{repoTree}}/scripts/op1.rhai) | just simple addition | -| [`op2.rhai`]({{repoTree}}/scripts/op2.rhai) | simple addition and multiplication | -| [`op3.rhai`]({{repoTree}}/scripts/op3.rhai) | change evaluation order with parenthesis | -| [`string.rhai`]({{repoTree}}/scripts/string.rhai) | [string] operations | -| [`strings_map.rhai`]({{repoTree}}/scripts/strings_map.rhai) | [string] and [object map] operations | -| [`while.rhai`]({{repoTree}}/scripts/while.rhai) | [`while`]({{rootUrl}}/language/while.md) loop | - - -Benchmark Scripts ----------------- - -The following scripts are for benchmarking the speed of Rhai: - -| Scripts | Description | -| --------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| [`speed_test.rhai`]({{repoTree}}/scripts/speed_test.rhai) | a simple application to measure the speed of Rhai's interpreter (1 million iterations) | -| [`primes.rhai`]({{repoTree}}/scripts/primes.rhai) | use Sieve of Eratosthenes to find all primes smaller than a limit | -| [`fibonacci.rhai`]({{repoTree}}/scripts/fibonacci.rhai) | calculate the n-th Fibonacci number using a really dumb algorithm | -| [`mat_mul.rhai`]({{repoTree}}/scripts/mat_mul.rhai) | matrix multiplication test to measure the speed of multi-dimensional array access | - - -Running Example Scripts ----------------------- - -The [`rhai-run`](../bin.md) utility can be used to run Rhai scripts: - -```bash -cargo run --bin rhai-run scripts/any_script.rhai -``` diff --git a/doc/src/start/features.md b/doc/src/start/features.md deleted file mode 100644 index d1db695b..00000000 --- a/doc/src/start/features.md +++ /dev/null @@ -1,68 +0,0 @@ -Optional Features -================ - -{{#include ../links.md}} - -By default, Rhai includes all the standard functionalities in a small, tight package. - -Most features are here to opt-**out** of certain functionalities that are not needed. -Notice that this deviates from Rust norm where features are _additive_. - -Excluding unneeded functionalities can result in smaller, faster builds as well as -more control over what a script can (or cannot) do. - -| Feature | Additive? | Description | -| ------------------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `unchecked` | no | disables arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.
Beware that a bad script may panic the entire system! | -| `sync` | no | restricts all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync` | -| `no_optimize` | no | disables [script optimization] | -| `no_float` | no | disables floating-point numbers and math | -| `f32_float` | no | sets the system floating-point type to `f32` instead of `f64`. `FLOAT` is set to `f32` | -| `only_i32` | no | sets the system integer type to `i32` and disable all other integer types. `INT` is set to `i32` | -| `only_i64` | no | sets the system integer type to `i64` and disable all other integer types. `INT` is set to `i64` | -| `no_index` | no | disables [arrays] and indexing features | -| `no_object` | no | disables support for [custom types] and [object maps] | -| `no_function` | no | disables script-defined [functions] (implies `no_closure`) | -| `no_module` | no | disables loading external [modules] | -| `no_closure` | no | disables [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls | -| `no_std` | no | builds for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features | -| `serde` | yes | enables serialization/deserialization via `serde` (requires the [`serde`](https://crates.io/crates/serde) crate) | -| `unicode-xid-ident` | no | allows [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers | -| `metadata` | yes | enables exporting [functions metadata] to [JSON format]({{rootUrl}}/engine/metadata/export_to_json.md) (implies `serde` and additionally requires the [`serde_json`](https://crates.io/crates/serde_json) crate) | -| `internals` | yes | exposes internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version | - - -Example -------- - -The `Cargo.toml` configuration below turns on these six features: - -* `sync` (everything `Send + Sync`) -* `unchecked` (disable all checking – should not be used with untrusted user scripts) -* `only_i32` (only 32-bit signed integers) -* `no_float` (no floating point numbers) -* `no_module` (no loading external [modules]) -* `no_function` (no defining [functions]) - -```toml -[dependencies] -rhai = { version = "{{version}}", features = [ "sync", "unchecked", "only_i32", "no_float", "no_module", "no_function" ] } -``` - -The resulting scripting engine supports only the `i32` integer numeral type (and no others like `u32`, `i16` or `i64`), -no floating-point, is `Send + Sync` (so it can be safely used across threads), and does not support defining [functions] -nor loading external [modules]. - -This configuration is perfect for an expression parser in a 32-bit embedded system without floating-point hardware. - - -Caveat – Features Are Not Additive ---------------------------------------- - -Most Rhai features are not strictly _additive_ – i.e. they do not only add optional functionalities. - -In fact, most features are _subtractive_ – i.e. they _remove_ functionalities. - -There is a reason for this design, because the _lack_ of a language feature by itself is a feature. - -See [here]({{rootUrl}}/patterns/multiple.md) for more details. diff --git a/doc/src/start/index.md b/doc/src/start/index.md deleted file mode 100644 index f89dfbd0..00000000 --- a/doc/src/start/index.md +++ /dev/null @@ -1,6 +0,0 @@ -Getting Started -=============== - -{{#include ../links.md}} - -This section shows how to install the Rhai crate into a Rust application. diff --git a/doc/src/start/install.md b/doc/src/start/install.md deleted file mode 100644 index 007f6d63..00000000 --- a/doc/src/start/install.md +++ /dev/null @@ -1,30 +0,0 @@ -Install the Rhai Crate -===================== - -{{#include ../links.md}} - -In order to use Rhai in a project, the Rhai crate must first be made a dependency. - -The easiest way is to install the Rhai crate from [`crates.io`](https:/crates.io/crates/rhai/), -starting by looking up the latest version and adding this line under `dependencies` in the project's `Cargo.toml`: - -```toml -[dependencies] -rhai = "{{version}}" # assuming {{version}} is the latest version -``` - -Or to automatically use the latest released crate version on [`crates.io`](https:/crates.io/crates/rhai/): - -```toml -[dependencies] -rhai = "*" -``` - -Crate versions are released on [`crates.io`](https:/crates.io/crates/rhai/) infrequently, -so to track the latest features, enhancements and bug fixes, pull directly from -[GitHub](https://github.com/jonathandturner/rhai): - -```toml -[dependencies] -rhai = { git = "https://github.com/jonathandturner/rhai" } -``` diff --git a/doc/src/start/playground.md b/doc/src/start/playground.md deleted file mode 100644 index 08809805..00000000 --- a/doc/src/start/playground.md +++ /dev/null @@ -1,10 +0,0 @@ -Online Playground -================= - -{{#include ../links.md}} - -Rhai provides an [online playground][playground] to try out its language and engine features -without having to install anything. - -The playground provides a syntax-highlighting script editor with example snippets. -Scripts can be evaluated directly from the editor. diff --git a/doc/src/tools/index.md b/doc/src/tools/index.md deleted file mode 100644 index 9b9eff01..00000000 --- a/doc/src/tools/index.md +++ /dev/null @@ -1,6 +0,0 @@ -External Tools -============== - -{{#include ../links.md}} - -External tools available to work with Rhai. diff --git a/doc/src/tools/playground.md b/doc/src/tools/playground.md deleted file mode 100644 index 91507a3d..00000000 --- a/doc/src/tools/playground.md +++ /dev/null @@ -1,15 +0,0 @@ -Online Playground -================= - -{{#include ../links.md}} - - -The Online Playground runs off a [WASM] build of Rhai and allows evaluating -Rhai scripts directly within a browser editor window. - - -Author : [`@alvinhochun`](https://github.com/alvinhochun) - -Repo : [On GitHub](https://github.com/alvinhochun/rhai-playground) - -URL : [Link to Online Playground][playground] diff --git a/doc/src/tools/rhai-doc.md b/doc/src/tools/rhai-doc.md deleted file mode 100644 index fd32be6a..00000000 --- a/doc/src/tools/rhai-doc.md +++ /dev/null @@ -1,6 +0,0 @@ -Rhai Script Documentation Tool -============================= - -{{#include ../links.md}} - -<< TODO >> \ No newline at end of file diff --git a/doc/theme/favicon.png b/doc/theme/favicon.png deleted file mode 100644 index f5cf9cac..00000000 Binary files a/doc/theme/favicon.png and /dev/null differ diff --git a/doc/theme/favicon.svg b/doc/theme/favicon.svg deleted file mode 100644 index 4d5eaf02..00000000 --- a/doc/theme/favicon.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/no_std/no_std_test/Cargo.toml b/no_std/no_std_test/Cargo.toml index 1db56f57..6a85b79d 100644 --- a/no_std/no_std_test/Cargo.toml +++ b/no_std/no_std_test/Cargo.toml @@ -8,8 +8,8 @@ version = "0.1.0" edition = "2018" authors = ["Stephen Chung"] description = "no-std test application" -homepage = "https://github.com/jonathandturner/rhai/tree/no_std/no_std_test" -repository = "https://github.com/jonathandturner/rhai" +homepage = "https://github.com/rhaiscript/rhai/tree/no_std/no_std_test" +repository = "https://github.com/rhaiscript/rhai" [dependencies] rhai = { path = "../../", features = [ "no_std" ], default_features = false } diff --git a/src/ast.rs b/src/ast.rs index fafc4394..4891878b 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -6,18 +6,16 @@ use crate::module::NamespaceRef; use crate::stdlib::{ borrow::Cow, boxed::Box, - collections::HashMap, fmt, - hash::Hash, + hash::{Hash, Hasher}, num::{NonZeroU64, NonZeroUsize}, - ops::{Add, AddAssign}, + ops::{Add, AddAssign, Deref, DerefMut}, string::String, vec, vec::Vec, }; -use crate::syntax::FnCustomSyntaxEval; use crate::token::Token; -use crate::utils::StraightHasherBuilder; +use crate::utils::{HashableHashMap, StraightHasherBuilder}; use crate::{ Dynamic, FnNamespace, FnPtr, ImmutableString, Module, Position, Shared, StaticVec, INT, }; @@ -492,11 +490,7 @@ impl AST { (true, true) => vec![], }; - let source = if other.source.is_some() { - other.source.clone() - } else { - self.source.clone() - }; + let source = other.source.clone().or_else(|| self.source.clone()); let mut functions = functions.as_ref().clone(); functions.merge_filtered(&other.functions, &mut filter); @@ -702,7 +696,7 @@ pub enum ReturnType { /// # WARNING /// /// This type is volatile and may change. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash)] pub enum Stmt { /// No-op. Noop(Position), @@ -711,7 +705,10 @@ pub enum Stmt { /// `switch` expr `{` literal or _ `=>` stmt `,` ... `}` Switch( Expr, - Box<(HashMap, Option)>, + Box<( + HashableHashMap, + Option, + )>, Position, ), /// `while` expr `{` stmt `}` @@ -899,10 +896,8 @@ impl Stmt { /// # WARNING /// /// This type is volatile and may change. -#[derive(Clone)] +#[derive(Clone, Hash)] pub struct CustomExpr { - /// Implementation function. - pub func: Shared, /// List of keywords. pub keywords: StaticVec, /// List of tokens actually parsed. @@ -926,7 +921,7 @@ impl fmt::Debug for CustomExpr { /// # WARNING /// /// This type is volatile and may change. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash)] pub struct BinaryExpr { /// LHS expression. pub lhs: Expr, @@ -940,7 +935,7 @@ pub struct BinaryExpr { /// # WARNING /// /// This type is volatile and may change. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, Hash)] pub struct FnCallExpr { /// Pre-calculated hash for a script-defined function of the same name and number of parameters. /// None if native Rust only. @@ -959,13 +954,83 @@ pub struct FnCallExpr { pub args: StaticVec, } +/// A type that wraps a [`FLOAT`] and implements [`Hash`]. +#[cfg(not(feature = "no_float"))] +#[derive(Clone, Copy)] +pub struct FloatWrapper(FLOAT); + +#[cfg(not(feature = "no_float"))] +impl Hash for FloatWrapper { + fn hash(&self, state: &mut H) { + self.0.to_le_bytes().hash(state); + } +} + +#[cfg(not(feature = "no_float"))] +impl AsRef for FloatWrapper { + fn as_ref(&self) -> &FLOAT { + &self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl AsMut for FloatWrapper { + fn as_mut(&mut self) -> &mut FLOAT { + &mut self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl Deref for FloatWrapper { + type Target = FLOAT; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl DerefMut for FloatWrapper { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(not(feature = "no_float"))] +impl fmt::Debug for FloatWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +#[cfg(not(feature = "no_float"))] +impl fmt::Display for FloatWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +#[cfg(not(feature = "no_float"))] +impl From for FloatWrapper { + fn from(value: FLOAT) -> Self { + Self::new(value) + } +} + +#[cfg(not(feature = "no_float"))] +impl FloatWrapper { + pub const fn new(value: FLOAT) -> Self { + Self(value) + } +} + /// _(INTERNALS)_ An expression sub-tree. /// Exported under the `internals` feature only. /// /// # WARNING /// /// This type is volatile and may change. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash)] pub enum Expr { /// Dynamic constant. /// Used to hold either an [`Array`] or [`Map`] literal for quick cloning. @@ -977,7 +1042,7 @@ pub enum Expr { IntegerConstant(INT, Position), /// Floating-point constant. #[cfg(not(feature = "no_float"))] - FloatConstant(FLOAT, Position), + FloatConstant(FloatWrapper, Position), /// Character constant. CharConstant(char, Position), /// [String][ImmutableString] constant. @@ -1250,19 +1315,20 @@ mod tests { /// This test is to make sure no code changes increase the sizes of critical data structures. #[test] fn check_struct_sizes() { - use std::mem::size_of; + use crate::stdlib::mem::size_of; + use crate::*; - assert_eq!(size_of::(), 16); - assert_eq!(size_of::>(), 16); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 16); - assert_eq!(size_of::>(), 16); - assert_eq!(size_of::(), 32); - assert_eq!(size_of::>(), 32); - assert_eq!(size_of::(), 32); - assert_eq!(size_of::(), 48); - assert_eq!(size_of::(), 56); - assert_eq!(size_of::(), 16); - assert_eq!(size_of::(), 72); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::>(), 16); + assert_eq!(size_of::(), 4); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::>(), 16); + assert_eq!(size_of::(), 32); + assert_eq!(size_of::>(), 32); + assert_eq!(size_of::(), 32); + assert_eq!(size_of::(), 48); + assert_eq!(size_of::(), 56); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 72); } } diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index d5d5c406..67f39957 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -11,9 +11,10 @@ use std::{ }; /// Pretty-print error. -fn print_error(input: &str, err: EvalAltResult) { +fn print_error(input: &str, mut err: EvalAltResult) { let lines: Vec<_> = input.trim().split('\n').collect(); let pos = err.position(); + err.clear_position(); let line_no = if lines.len() > 1 { if pos.is_none() { @@ -26,8 +27,6 @@ fn print_error(input: &str, err: EvalAltResult) { }; // Print error position - let pos_text = format!(" ({})", pos); - if pos.is_none() { // No position println!("{}", err); @@ -40,7 +39,7 @@ fn print_error(input: &str, err: EvalAltResult) { "{0:>1$} {2}", "^", line_no.len() + pos.position().unwrap(), - err.to_string().replace(&pos_text, "") + err ); } } diff --git a/src/bin/rhai-run.rs b/src/bin/rhai-run.rs index 4453f84b..24e60452 100644 --- a/src/bin/rhai-run.rs +++ b/src/bin/rhai-run.rs @@ -5,19 +5,17 @@ use rhai::OptimizationLevel; use std::{env, fs::File, io::Read, process::exit}; -fn eprint_error(input: &str, err: EvalAltResult) { - fn eprint_line(lines: &[&str], pos: Position, err: &str) { +fn eprint_error(input: &str, mut err: EvalAltResult) { + fn eprint_line(lines: &[&str], pos: Position, err_msg: &str) { let line = pos.line().unwrap(); - let line_no = format!("{}: ", line); - let pos_text = format!(" ({})", pos); eprintln!("{}{}", line_no, lines[line - 1]); eprintln!( "{:>1$} {2}", "^", line_no.len() + pos.position().unwrap(), - err.replace(&pos_text, "") + err_msg ); eprintln!(""); } @@ -26,6 +24,7 @@ fn eprint_error(input: &str, err: EvalAltResult) { // Print error let pos = err.position(); + err.clear_position(); if pos.is_none() { // No position diff --git a/src/dynamic.rs b/src/dynamic.rs index 47507ff6..fc5ab807 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -7,14 +7,13 @@ use crate::stdlib::{ boxed::Box, fmt, hash::{Hash, Hasher}, - mem, ops::{Deref, DerefMut}, string::String, }; use crate::{FnPtr, ImmutableString, INT}; #[cfg(not(feature = "no_float"))] -use crate::FLOAT; +use crate::{ast::FloatWrapper, FLOAT}; #[cfg(not(feature = "no_index"))] use crate::Array; @@ -155,7 +154,7 @@ pub enum Union { Char(char, AccessMode), Int(INT, AccessMode), #[cfg(not(feature = "no_float"))] - Float(FLOAT, AccessMode), + Float(FloatWrapper, AccessMode), #[cfg(not(feature = "no_index"))] Array(Box, AccessMode), #[cfg(not(feature = "no_object"))] @@ -362,8 +361,6 @@ impl Dynamic { impl Hash for Dynamic { fn hash(&self, state: &mut H) { - mem::discriminant(self).hash(state); - match &self.0 { Union::Unit(_, _) => ().hash(state), Union::Bool(value, _) => value.hash(state), @@ -371,7 +368,7 @@ impl Hash for Dynamic { Union::Char(ch, _) => ch.hash(state), Union::Int(i, _) => i.hash(state), #[cfg(not(feature = "no_float"))] - Union::Float(f, _) => f.to_le_bytes().hash(state), + Union::Float(f, _) => f.hash(state), #[cfg(not(feature = "no_index"))] Union::Array(a, _) => (**a).hash(state), #[cfg(not(feature = "no_object"))] @@ -559,13 +556,16 @@ impl Dynamic { pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, AccessMode::ReadWrite)); /// A [`Dynamic`] containing the floating-point zero. #[cfg(not(feature = "no_float"))] - pub const FLOAT_ZERO: Dynamic = Self(Union::Float(0.0, AccessMode::ReadWrite)); + pub const FLOAT_ZERO: Dynamic = + Self(Union::Float(FloatWrapper::new(0.0), AccessMode::ReadWrite)); /// A [`Dynamic`] containing the floating-point one. #[cfg(not(feature = "no_float"))] - pub const FLOAT_ONE: Dynamic = Self(Union::Float(1.0, AccessMode::ReadWrite)); + pub const FLOAT_ONE: Dynamic = + Self(Union::Float(FloatWrapper::new(1.0), AccessMode::ReadWrite)); /// A [`Dynamic`] containing the floating-point negative one. #[cfg(not(feature = "no_float"))] - pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float(-1.0, AccessMode::ReadWrite)); + pub const FLOAT_NEGATIVE_ONE: Dynamic = + Self(Union::Float(FloatWrapper::new(-1.0), AccessMode::ReadWrite)); /// Get the [`AccessMode`] for this [`Dynamic`]. pub(crate) fn access_mode(&self) -> AccessMode { @@ -836,7 +836,7 @@ impl Dynamic { #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Float(value, _) => unsafe_try_cast(value), + Union::Float(value, _) => unsafe_try_cast(*value), _ => None, }; } @@ -1105,7 +1105,7 @@ impl Dynamic { #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match &self.0 { - Union::Float(value, _) => ::downcast_ref::(value), + Union::Float(value, _) => ::downcast_ref::(value.as_ref()), _ => None, }; } @@ -1194,7 +1194,7 @@ impl Dynamic { #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match &mut self.0 { - Union::Float(value, _) => ::downcast_mut::(value), + Union::Float(value, _) => ::downcast_mut::(value.as_mut()), _ => None, }; } @@ -1277,7 +1277,7 @@ impl Dynamic { #[inline(always)] pub fn as_float(&self) -> Result { match self.0 { - Union::Float(n, _) => Ok(n), + Union::Float(n, _) => Ok(*n), #[cfg(not(feature = "no_closure"))] Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), @@ -1380,6 +1380,13 @@ impl From for Dynamic { impl From for Dynamic { #[inline(always)] fn from(value: FLOAT) -> Self { + Self(Union::Float(value.into(), AccessMode::ReadWrite)) + } +} +#[cfg(not(feature = "no_float"))] +impl From for Dynamic { + #[inline(always)] + fn from(value: FloatWrapper) -> Self { Self(Union::Float(value, AccessMode::ReadWrite)) } } diff --git a/src/engine.rs b/src/engine.rs index 05d67062..37f279d2 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -135,7 +135,8 @@ impl Imports { .rev() .find_map(|(_, m)| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) } - /// Does the specified [`TypeId`][std::any::TypeId] iterator exist in this stack of imported [modules][Module]? + /// Does the specified [`TypeId`][std::any::TypeId] iterator exist in this stack of + /// imported [modules][Module]? #[allow(dead_code)] #[inline(always)] pub fn contains_iter(&self, id: TypeId) -> bool { @@ -508,8 +509,8 @@ pub struct State { /// In some situation, e.g. after running an `eval` statement, subsequent offsets become mis-aligned. /// When that happens, this flag is turned on to force a scope lookup by name. pub always_search: bool, - /// Level of the current scope. The global (root) level is zero, a new block (or function call) - /// is one level higher, and so on. + /// Level of the current scope. The global (root) level is zero, a new block + /// (or function call) is one level higher, and so on. pub scope_level: usize, /// Number of operations performed. pub operations: u64, @@ -542,30 +543,34 @@ impl State { pub struct Limits { /// Maximum levels of call-stack to prevent infinite recursion. /// Not available under `no_function`. + /// + /// Set to zero to effectively disable function calls. #[cfg(not(feature = "no_function"))] pub max_call_stack_depth: usize, - /// Maximum depth of statements/expressions at global level (0 = unlimited). - pub max_expr_depth: usize, - /// Maximum depth of statements/expressions in functions (0 = unlimited). + /// Maximum depth of statements/expressions at global level. + pub max_expr_depth: Option, + /// Maximum depth of statements/expressions in functions. /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] - pub max_function_expr_depth: usize, - /// Maximum number of operations allowed to run (0 = unlimited). - pub max_operations: u64, + pub max_function_expr_depth: Option, + /// Maximum number of operations allowed to run. + pub max_operations: Option, /// Maximum number of [modules][Module] allowed to load. /// Not available under `no_module`. + /// + /// Set to zero to effectively disable loading any [module][Module]. #[cfg(not(feature = "no_module"))] pub max_modules: usize, - /// Maximum length of a [string][ImmutableString] (0 = unlimited). - pub max_string_size: usize, - /// Maximum length of an [array][Array] (0 = unlimited). + /// Maximum length of a [string][ImmutableString]. + pub max_string_size: Option, + /// Maximum length of an [array][Array]. /// Not available under `no_index`. #[cfg(not(feature = "no_index"))] - pub max_array_size: usize, - /// Maximum number of properties in an [object map][Map] (0 = unlimited). + pub max_array_size: Option, + /// Maximum number of properties in an [object map][Map]. /// Not available under `no_object`. #[cfg(not(feature = "no_object"))] - pub max_map_size: usize, + pub max_map_size: Option, } /// Context of a script evaluation process. @@ -777,13 +782,13 @@ pub fn search_imports( // Qualified - check if the root module is directly indexed let index = if state.always_search { - 0 + None } else { - namespace.index().map_or(0, NonZeroUsize::get) + namespace.index() }; - Ok(if index > 0 { - let offset = mods.len() - index; + Ok(if let Some(index) = index { + let offset = mods.len() - index.get(); mods.get(offset).expect("invalid index in Imports") } else { mods.find(root) @@ -838,17 +843,17 @@ impl Engine { limits: Limits { #[cfg(not(feature = "no_function"))] max_call_stack_depth: MAX_CALL_STACK_DEPTH, - max_expr_depth: MAX_EXPR_DEPTH, + max_expr_depth: NonZeroUsize::new(MAX_EXPR_DEPTH), #[cfg(not(feature = "no_function"))] - max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH, - max_operations: 0, + max_function_expr_depth: NonZeroUsize::new(MAX_FUNCTION_EXPR_DEPTH), + max_operations: None, #[cfg(not(feature = "no_module"))] max_modules: usize::MAX, - max_string_size: 0, + max_string_size: None, #[cfg(not(feature = "no_index"))] - max_array_size: 0, + max_array_size: None, #[cfg(not(feature = "no_object"))] - max_map_size: 0, + max_map_size: None, }, disable_doc_comments: false, @@ -895,17 +900,17 @@ impl Engine { limits: Limits { #[cfg(not(feature = "no_function"))] max_call_stack_depth: MAX_CALL_STACK_DEPTH, - max_expr_depth: MAX_EXPR_DEPTH, + max_expr_depth: NonZeroUsize::new(MAX_EXPR_DEPTH), #[cfg(not(feature = "no_function"))] - max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH, - max_operations: 0, + max_function_expr_depth: NonZeroUsize::new(MAX_FUNCTION_EXPR_DEPTH), + max_operations: None, #[cfg(not(feature = "no_module"))] max_modules: usize::MAX, - max_string_size: 0, + max_string_size: None, #[cfg(not(feature = "no_index"))] - max_array_size: 0, + max_array_size: None, #[cfg(not(feature = "no_object"))] - max_map_size: 0, + max_map_size: None, }, disable_doc_comments: false, @@ -975,14 +980,11 @@ impl Engine { } // Check if it is directly indexed - let index = if state.always_search { - 0 - } else { - index.map_or(0, NonZeroUsize::get) - }; + let index = if state.always_search { &None } else { index }; // Check the variable resolver, if any if let Some(ref resolve_var) = self.resolve_var { + let index = index.map(NonZeroUsize::get).unwrap_or(0); let context = EvalContext { engine: self, scope, @@ -1000,8 +1002,8 @@ impl Engine { } } - let index = if index > 0 { - scope.len() - index + let index = if let Some(index) = index { + scope.len() - index.get() } else { // Find the variable in the scope scope @@ -1012,8 +1014,8 @@ impl Engine { let val = scope.get_mut_by_index(index); - // Check for data race - probably not necessary because the only place it should conflict is in a method call - // when the object variable is also used as a parameter. + // Check for data race - probably not necessary because the only place it should conflict is + // in a method call when the object variable is also used as a parameter. // if cfg!(not(feature = "no_closure")) && val.is_locked() { // return EvalAltResult::ErrorDataRace(name.into(), *pos).into(); // } @@ -1285,7 +1287,8 @@ impl Engine { ) .or_else( |err| match *err { - // If there is no setter, no need to feed it back because the property is read-only + // If there is no setter, no need to feed it back because + // the property is read-only EvalAltResult::ErrorDotExpr(_, _) => { Ok((Dynamic::UNIT, false)) } @@ -1405,7 +1408,8 @@ impl Engine { } /// Evaluate a chain of indexes and store the results in a [`StaticVec`]. - /// [`StaticVec`] is used to avoid an allocation in the overwhelming cases of just a few levels of indexing. + /// [`StaticVec`] is used to avoid an allocation in the overwhelming cases of + /// just a few levels of indexing. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] fn eval_indexed_chain( &self, @@ -1666,118 +1670,6 @@ impl Engine { } } - /// Get a [`Target`] from an expression. - pub(crate) fn eval_expr_as_target<'s>( - &self, - scope: &'s mut Scope, - mods: &mut Imports, - state: &mut State, - lib: &[&Module], - this_ptr: &'s mut Option<&mut Dynamic>, - expr: &Expr, - _no_const: bool, - level: usize, - ) -> Result<(Target<'s>, Position), Box> { - match expr { - // var - point directly to the value - Expr::Variable(_) => { - let (mut target, pos) = - self.search_namespace(scope, mods, state, lib, this_ptr, expr)?; - - // If necessary, constants are cloned - if target.as_ref().is_read_only() { - target = target.into_owned(); - } - - Ok((target, pos)) - } - // var[...] - #[cfg(not(feature = "no_index"))] - Expr::Index(x, _) if x.lhs.get_variable_access(false).is_some() => match x.rhs { - Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"), - // var[...]... - Expr::FnCall(_, _) | Expr::Index(_, _) | Expr::Dot(_, _) => self - .eval_expr(scope, mods, state, lib, this_ptr, expr, level) - .map(|v| (v.into(), expr.position())), - // var[expr] - point directly to the item - _ => { - let idx = self.eval_expr(scope, mods, state, lib, this_ptr, &x.rhs, level)?; - let idx_pos = x.rhs.position(); - let (mut target, pos) = self.eval_expr_as_target( - scope, mods, state, lib, this_ptr, &x.lhs, _no_const, level, - )?; - - let is_ref = target.is_ref(); - - if target.is_shared() || target.is_value() { - let target_ref = target.as_mut(); - self.get_indexed_mut( - mods, state, lib, target_ref, idx, idx_pos, false, is_ref, true, level, - ) - .map(Target::into_owned) - } else { - let target_ref = target.take_ref().unwrap(); - self.get_indexed_mut( - mods, state, lib, target_ref, idx, idx_pos, false, is_ref, true, level, - ) - } - .map(|v| (v, pos)) - } - }, - // var.prop - #[cfg(not(feature = "no_object"))] - Expr::Dot(x, _) if x.lhs.get_variable_access(false).is_some() => match x.rhs { - Expr::Variable(_) => unreachable!( - "unexpected Expr::Variable in dot access (should be Expr::Property)" - ), - // var.prop - Expr::Property(ref p) => { - let (mut target, _) = self.eval_expr_as_target( - scope, mods, state, lib, this_ptr, &x.lhs, _no_const, level, - )?; - let is_ref = target.is_ref(); - - if target.is::() { - // map.prop - point directly to the item - let (_, _, Ident { name, pos }) = p.as_ref(); - let idx = name.clone().into(); - - if target.is_shared() || target.is_value() { - let target_ref = target.as_mut(); - self.get_indexed_mut( - mods, state, lib, target_ref, idx, *pos, false, is_ref, true, level, - ) - .map(Target::into_owned) - } else { - let target_ref = target.take_ref().unwrap(); - self.get_indexed_mut( - mods, state, lib, target_ref, idx, *pos, false, is_ref, true, level, - ) - } - .map(|v| (v, *pos)) - } else { - // var.prop - call property getter - let (getter, _, Ident { pos, .. }) = p.as_ref(); - let mut args = [target.as_mut()]; - self.exec_fn_call( - mods, state, lib, getter, None, &mut args, is_ref, true, false, *pos, - None, None, level, - ) - .map(|(v, _)| (v.into(), *pos)) - } - } - // var.??? - _ => self - .eval_expr(scope, mods, state, lib, this_ptr, expr, level) - .map(|v| (v.into(), expr.position())), - }, - // expr - _ => self - .eval_expr(scope, mods, state, lib, this_ptr, expr, level) - .map(|v| (v.into(), expr.position())), - } - } - /// Evaluate an expression. pub(crate) fn eval_expr( &self, @@ -1923,6 +1815,10 @@ impl Engine { .iter() .map(Into::into) .collect::>(); + let custom_def = self + .custom_syntax + .get(custom.tokens.first().unwrap()) + .unwrap(); let mut context = EvalContext { engine: self, scope, @@ -1932,7 +1828,7 @@ impl Engine { this_ptr, level, }; - (custom.func)(&mut context, &expressions) + (custom_def.func)(&mut context, &expressions) } _ => unreachable!("expression cannot be evaluated: {:?}", expr), @@ -2072,11 +1968,7 @@ impl Engine { let args = &mut [lhs_ptr_inner, &mut rhs_val]; // Overriding exact implementation - let source = if source.is_none() { - state.source.as_ref() - } else { - source - }; + let source = source.or_else(|| state.source.as_ref()); if func.is_plugin_fn() { func.get_plugin_fn() .call((self, source, &*mods, lib).into(), args)?; @@ -2130,14 +2022,13 @@ impl Engine { &mut rhs_val, ]; - let result = self - .exec_fn_call( + Some( + self.exec_fn_call( mods, state, lib, op, None, args, false, false, false, *op_pos, None, None, level, ) - .map(|(v, _)| v)?; - - Some((result, rhs_expr.position())) + .map(|(v, _)| (v, rhs_expr.position()))?, + ) }; // Must be either `var[index] op= val` or `var.prop op= val` @@ -2193,12 +2084,8 @@ impl Engine { let (table, def_stmt) = x.as_ref(); let hasher = &mut get_hasher(); - self.eval_expr_as_target( - scope, mods, state, lib, this_ptr, match_expr, false, level, - )? - .0 - .as_ref() - .hash(hasher); + self.eval_expr(scope, mods, state, lib, this_ptr, match_expr, level)? + .hash(hasher); let hash = hasher.finish(); if let Some(stmt) = table.get(&hash) { @@ -2517,20 +2404,23 @@ impl Engine { result: Result>, pos: Position, ) -> Result> { - // If no data size limits, just return - let mut total = 0; + // Simply return all errors + if result.is_err() { + return result; + } - total += self.max_string_size(); + // If no data size limits, just return + let mut has_limit = self.limits.max_string_size.is_some(); #[cfg(not(feature = "no_index"))] { - total += self.max_array_size(); + has_limit = has_limit || self.limits.max_array_size.is_some(); } #[cfg(not(feature = "no_object"))] { - total += self.max_map_size(); + has_limit = has_limit || self.limits.max_map_size.is_some(); } - if total == 0 { + if !has_limit { return result; } @@ -2586,34 +2476,33 @@ impl Engine { } } - match result { - // Simply return all errors - Err(_) => return result, - // String with limit - Ok(Dynamic(Union::Str(_, _))) if self.max_string_size() > 0 => (), - // Array with limit - #[cfg(not(feature = "no_index"))] - Ok(Dynamic(Union::Array(_, _))) if self.max_array_size() > 0 => (), - // Map with limit - #[cfg(not(feature = "no_object"))] - Ok(Dynamic(Union::Map(_, _))) if self.max_map_size() > 0 => (), - // Everything else is simply returned - Ok(_) => return result, - }; - let (_arr, _map, s) = calc_size(result.as_ref().unwrap()); - if s > self.max_string_size() { + if s > self + .limits + .max_string_size + .map_or(usize::MAX, NonZeroUsize::get) + { return EvalAltResult::ErrorDataTooLarge("Length of string".to_string(), pos).into(); } #[cfg(not(feature = "no_index"))] - if _arr > self.max_array_size() { + if _arr + > self + .limits + .max_array_size + .map_or(usize::MAX, NonZeroUsize::get) + { return EvalAltResult::ErrorDataTooLarge("Size of array".to_string(), pos).into(); } #[cfg(not(feature = "no_object"))] - if _map > self.max_map_size() { + if _map + > self + .limits + .max_map_size + .map_or(usize::MAX, NonZeroUsize::get) + { return EvalAltResult::ErrorDataTooLarge("Size of object map".to_string(), pos).into(); } diff --git a/src/engine_api.rs b/src/engine_api.rs index 5f5b4b21..fed074bf 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -8,11 +8,9 @@ use crate::stdlib::{ any::{type_name, TypeId}, boxed::Box, format, - hash::{Hash, Hasher}, string::String, vec::Vec, }; -use crate::utils::get_hasher; use crate::{ scope::Scope, Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, Module, NativeCallContext, ParseError, Position, Shared, AST, @@ -24,13 +22,6 @@ use crate::Array; #[cfg(not(feature = "no_object"))] use crate::Map; -/// Calculate a unique hash for a script. -fn calc_hash_for_scripts<'a>(scripts: impl IntoIterator) -> u64 { - let s = &mut get_hasher(); - scripts.into_iter().for_each(|&script| script.hash(s)); - s.finish() -} - /// Engine public API impl Engine { /// Register a function of the [`Engine`]. @@ -960,9 +951,8 @@ impl Engine { scripts: &[&str], optimization_level: OptimizationLevel, ) -> Result { - let hash = calc_hash_for_scripts(scripts); let stream = self.lex(scripts); - self.parse(hash, &mut stream.peekable(), scope, optimization_level) + self.parse(&mut stream.peekable(), scope, optimization_level) } /// Read the contents of a file into a string. #[cfg(not(feature = "no_std"))] @@ -1123,8 +1113,6 @@ impl Engine { .into()); }; - let hash = calc_hash_for_scripts(&scripts); - let stream = self.lex_with_map( &scripts, if has_null { @@ -1138,12 +1126,8 @@ impl Engine { }, ); - let ast = self.parse_global_expr( - hash, - &mut stream.peekable(), - &scope, - OptimizationLevel::None, - )?; + let ast = + self.parse_global_expr(&mut stream.peekable(), &scope, OptimizationLevel::None)?; // Handle null - map to () if has_null { @@ -1222,11 +1206,10 @@ impl Engine { script: &str, ) -> Result { let scripts = [script]; - let hash = calc_hash_for_scripts(&scripts); let stream = self.lex(&scripts); let mut peekable = stream.peekable(); - self.parse_global_expr(hash, &mut peekable, scope, self.optimization_level) + self.parse_global_expr(&mut peekable, scope, self.optimization_level) } /// Evaluate a script file. /// @@ -1384,12 +1367,10 @@ impl Engine { script: &str, ) -> Result> { let scripts = [script]; - let hash = calc_hash_for_scripts(&scripts); let stream = self.lex(&scripts); // No need to optimize a lone expression - let ast = - self.parse_global_expr(hash, &mut stream.peekable(), scope, OptimizationLevel::None)?; + let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?; self.eval_ast_with_scope(scope, &ast) } @@ -1522,9 +1503,8 @@ impl Engine { script: &str, ) -> Result<(), Box> { let scripts = [script]; - let hash = calc_hash_for_scripts(&scripts); let stream = self.lex(&scripts); - let ast = self.parse(hash, &mut stream.peekable(), scope, self.optimization_level)?; + let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?; self.consume_ast_with_scope(scope, &ast) } /// Evaluate an AST, but throw away the result and only return error (if any). diff --git a/src/engine_settings.rs b/src/engine_settings.rs index 7d99c1fa..396c155e 100644 --- a/src/engine_settings.rs +++ b/src/engine_settings.rs @@ -4,6 +4,9 @@ use crate::stdlib::{format, num::NonZeroU8, string::String}; use crate::token::Token; use crate::Engine; +#[cfg(not(feature = "unchecked"))] +use crate::stdlib::num::{NonZeroU64, NonZeroUsize}; + #[cfg(not(feature = "no_module"))] use crate::stdlib::boxed::Box; @@ -62,11 +65,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn set_max_operations(&mut self, operations: u64) -> &mut Self { - self.limits.max_operations = if operations == u64::MAX { - 0 - } else { - operations - }; + self.limits.max_operations = NonZeroU64::new(operations); self } /// The maximum number of operations allowed for a script to run (0 for unlimited). @@ -75,7 +74,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn max_operations(&self) -> u64 { - self.limits.max_operations + self.limits.max_operations.map_or(0, NonZeroU64::get) } /// Set the maximum number of imported [modules][crate::Module] allowed for a script. /// @@ -106,18 +105,10 @@ impl Engine { max_expr_depth: usize, #[cfg(not(feature = "no_function"))] max_function_expr_depth: usize, ) -> &mut Self { - self.limits.max_expr_depth = if max_expr_depth == usize::MAX { - 0 - } else { - max_expr_depth - }; + self.limits.max_expr_depth = NonZeroUsize::new(max_expr_depth); #[cfg(not(feature = "no_function"))] { - self.limits.max_function_expr_depth = if max_function_expr_depth == usize::MAX { - 0 - } else { - max_function_expr_depth - }; + self.limits.max_function_expr_depth = NonZeroUsize::new(max_function_expr_depth); } self } @@ -127,7 +118,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn max_expr_depth(&self) -> usize { - self.limits.max_expr_depth + self.limits.max_expr_depth.map_or(0, NonZeroUsize::get) } /// The depth limit for expressions in functions (0 for unlimited). /// @@ -136,7 +127,9 @@ impl Engine { #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn max_function_expr_depth(&self) -> usize { - self.limits.max_function_expr_depth + self.limits + .max_function_expr_depth + .map_or(0, NonZeroUsize::get) } /// Set the maximum length of [strings][crate::ImmutableString] (0 for unlimited). /// @@ -144,7 +137,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn set_max_string_size(&mut self, max_size: usize) -> &mut Self { - self.limits.max_string_size = if max_size == usize::MAX { 0 } else { max_size }; + self.limits.max_string_size = NonZeroUsize::new(max_size); self } /// The maximum length of [strings][crate::ImmutableString] (0 for unlimited). @@ -153,7 +146,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn max_string_size(&self) -> usize { - self.limits.max_string_size + self.limits.max_string_size.map_or(0, NonZeroUsize::get) } /// Set the maximum length of [arrays][crate::Array] (0 for unlimited). /// @@ -162,7 +155,7 @@ impl Engine { #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn set_max_array_size(&mut self, max_size: usize) -> &mut Self { - self.limits.max_array_size = if max_size == usize::MAX { 0 } else { max_size }; + self.limits.max_array_size = NonZeroUsize::new(max_size); self } /// The maximum length of [arrays][crate::Array] (0 for unlimited). @@ -172,7 +165,7 @@ impl Engine { #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn max_array_size(&self) -> usize { - self.limits.max_array_size + self.limits.max_array_size.map_or(0, NonZeroUsize::get) } /// Set the maximum size of [object maps][crate::Map] (0 for unlimited). /// @@ -181,7 +174,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn set_max_map_size(&mut self, max_size: usize) -> &mut Self { - self.limits.max_map_size = if max_size == usize::MAX { 0 } else { max_size }; + self.limits.max_map_size = NonZeroUsize::new(max_size); self } /// The maximum size of [object maps][crate::Map] (0 for unlimited). @@ -191,7 +184,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn max_map_size(&self) -> usize { - self.limits.max_map_size + self.limits.max_map_size.map_or(0, NonZeroUsize::get) } /// Set the module resolution service used by the [`Engine`]. /// diff --git a/src/fn_call.rs b/src/fn_call.rs index 78879f50..21bc570c 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -734,7 +734,7 @@ impl Engine { // If new functions are defined within the eval string, it is an error if ast.lib().count().0 != 0 { - return Err(ParseErrorType::WrongFnDefinition.into()); + return Err(ParseErrorType::FnWrongDefinition.into()); } // Evaluate the AST diff --git a/src/lib.rs b/src/lib.rs index a618879d..fb28dcf1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ //! # Rhai - embedded scripting for Rust //! -//! ![Rhai logo](https://schungx.github.io/rhai/images/logo/rhai-banner-transparent-colour.svg) +//! ![Rhai logo](https://rhaiscript.github.io/book/images/logo/rhai-banner-transparent-colour.svg) //! //! Rhai is a tiny, simple and fast embedded scripting language for Rust //! that gives you a safe and easy way to add scripting to your applications. @@ -54,7 +54,7 @@ //! //! # Documentation //! -//! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai scripting engine and language. +//! See [The Rhai Book](https://rhaiscript.github.io/book) for details on the Rhai scripting engine and language. #![cfg_attr(feature = "no_std", no_std)] @@ -136,7 +136,7 @@ pub use utils::ImmutableString; use fn_native::Locked; #[cfg(feature = "internals")] -pub use utils::{calc_native_fn_hash, calc_script_fn_hash}; +pub use utils::{calc_native_fn_hash, calc_script_fn_hash, HashableHashMap}; #[cfg(not(feature = "internals"))] pub(crate) use utils::{calc_native_fn_hash, calc_script_fn_hash}; @@ -183,7 +183,9 @@ pub use token::{get_next_token, parse_string_literal, InputStream, Token, Tokeni #[cfg(feature = "internals")] #[deprecated = "this type is volatile and may change"] -pub use ast::{BinaryExpr, CustomExpr, Expr, FnCallExpr, Ident, ReturnType, ScriptFnDef, Stmt}; +pub use ast::{ + BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, Ident, ReturnType, ScriptFnDef, Stmt, +}; #[cfg(feature = "internals")] #[deprecated = "this type is volatile and may change"] diff --git a/src/parse_error.rs b/src/parse_error.rs index 0a03726c..e42c851a 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -131,7 +131,12 @@ pub enum ParseErrorType { /// Defining a function `fn` in an appropriate place (e.g. inside another function). /// /// Never appears under the `no_function` feature. - WrongFnDefinition, + FnWrongDefinition, + /// Defining a function with a name that conflicts with an existing function. + /// Wrapped values are the function name and number of parameters. + /// + /// Never appears under the `no_object` feature. + FnDuplicatedDefinition(String, usize), /// Missing a function name after the `fn` keyword. /// /// Never appears under the `no_function` feature. @@ -180,7 +185,7 @@ impl ParseErrorType { pub(crate) fn desc(&self) -> &str { match self { Self::UnexpectedEOF => "Script is incomplete", - Self::BadInput(p) => p.desc(), + Self::BadInput(err) => err.desc(), Self::UnknownOperator(_) => "Unknown operator", Self::MissingToken(_, _) => "Expecting a certain token that is missing", Self::MalformedCallExpr(_) => "Invalid expression in function call arguments", @@ -193,12 +198,13 @@ impl ParseErrorType { Self::VariableExpected => "Expecting name of a variable", Self::Reserved(_) => "Invalid use of reserved keyword", Self::ExprExpected(_) => "Expecting an expression", + Self::FnWrongDefinition => "Function definitions must be at global level and cannot be inside a block or another function", + Self::FnDuplicatedDefinition(_, _) => "Function already exists", Self::FnMissingName => "Expecting function name in function declaration", Self::FnMissingParams(_) => "Expecting parameters in function declaration", Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration", Self::FnMissingBody(_) => "Expecting body statement block for function declaration", Self::WrongDocComment => "Doc-comment must be followed immediately by a function definition", - Self::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function", Self::WrongExport => "Export statement can only appear at global level", Self::AssignmentToConstant(_) => "Cannot assign to a constant value", Self::AssignmentToInvalidLHS(_) => "Expression cannot be assigned to", @@ -221,6 +227,14 @@ impl fmt::Display for ParseErrorType { f.write_str(if s.is_empty() { self.desc() } else { s }) } + Self::FnDuplicatedDefinition(s, n) => { + write!(f, "Function '{}' with ", s)?; + match n { + 0 => f.write_str("no parameters already exists"), + 1 => f.write_str("1 parameter already exists"), + _ => write!(f, "{} parameters already exists", n), + } + } Self::DuplicatedProperty(s) => { write!(f, "Duplicated property '{}' for object map literal", s) } diff --git a/src/parser.rs b/src/parser.rs index 7f43e9c9..4c462f66 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -41,8 +41,6 @@ type FunctionsLib = HashMap; struct ParseState<'e> { /// Reference to the scripting [`Engine`]. engine: &'e Engine, - /// Hash that uniquely identifies a script. - script_hash: u64, /// Interned strings. strings: HashMap, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. @@ -63,11 +61,11 @@ struct ParseState<'e> { modules: StaticVec, /// Maximum levels of expression nesting. #[cfg(not(feature = "unchecked"))] - max_expr_depth: usize, + max_expr_depth: Option, /// Maximum levels of expression nesting in functions. #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_function"))] - max_function_expr_depth: usize, + max_function_expr_depth: Option, } impl<'e> ParseState<'e> { @@ -75,15 +73,13 @@ impl<'e> ParseState<'e> { #[inline(always)] pub fn new( engine: &'e Engine, - script_hash: u64, - #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, + #[cfg(not(feature = "unchecked"))] max_expr_depth: Option, #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_function"))] - max_function_expr_depth: usize, + max_function_expr_depth: Option, ) -> Self { Self { engine, - script_hash, #[cfg(not(feature = "unchecked"))] max_expr_depth, #[cfg(not(feature = "unchecked"))] @@ -216,15 +212,17 @@ impl ParseSettings { } /// Make sure that the current level of expression nesting is within the maximum limit. #[cfg(not(feature = "unchecked"))] - #[inline] - pub fn ensure_level_within_max_limit(&self, limit: usize) -> Result<(), ParseError> { - if limit == 0 { - Ok(()) - } else if self.level > limit { - Err(PERR::ExprTooDeep.into_err(self.pos)) - } else { - Ok(()) + #[inline(always)] + pub fn ensure_level_within_max_limit( + &self, + limit: Option, + ) -> Result<(), ParseError> { + if let Some(limit) = limit { + if self.level > limit.get() { + return Err(PERR::ExprTooDeep.into_err(self.pos)); + } } + Ok(()) } } @@ -919,7 +917,7 @@ fn parse_switch( Ok(Stmt::Switch( item, - Box::new((final_table, def_stmt)), + Box::new((final_table.into(), def_stmt)), settings.pos, )) } @@ -956,7 +954,7 @@ fn parse_primary( }, #[cfg(not(feature = "no_float"))] Token::FloatConstant(x) => { - let x = *x; + let x = (*x).into(); input.next().unwrap(); Expr::FloatConstant(x, settings.pos) } @@ -986,7 +984,6 @@ fn parse_primary( Token::Pipe | Token::Or if settings.allow_anonymous_fn => { let mut new_state = ParseState::new( state.engine, - state.script_hash, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, #[cfg(not(feature = "unchecked"))] @@ -1284,7 +1281,7 @@ fn parse_unary( .map(|i| Expr::IntegerConstant(i, pos)) .or_else(|| { #[cfg(not(feature = "no_float"))] - return Some(Expr::FloatConstant(-(num as FLOAT), pos)); + return Some(Expr::FloatConstant((-(num as FLOAT)).into(), pos)); #[cfg(feature = "no_float")] return None; }) @@ -1292,7 +1289,7 @@ fn parse_unary( // Negative float #[cfg(not(feature = "no_float"))] - Expr::FloatConstant(x, pos) => Ok(Expr::FloatConstant(-x, pos)), + Expr::FloatConstant(x, pos) => Ok(Expr::FloatConstant((-(*x)).into(), pos)), // Call negative function expr => { @@ -1998,14 +1995,7 @@ fn parse_custom_syntax( } } - Ok(Expr::Custom( - Box::new(CustomExpr { - keywords, - func: syntax.func.clone(), - tokens, - }), - pos, - )) + Ok(Expr::Custom(Box::new(CustomExpr { keywords, tokens }), pos)) } /// Parse an expression. @@ -2585,7 +2575,7 @@ fn parse_stmt( // fn ... #[cfg(not(feature = "no_function"))] - Token::Fn if !settings.is_global => Err(PERR::WrongFnDefinition.into_err(settings.pos)), + Token::Fn if !settings.is_global => Err(PERR::FnWrongDefinition.into_err(settings.pos)), #[cfg(not(feature = "no_function"))] Token::Fn | Token::Private => { @@ -2600,7 +2590,6 @@ fn parse_stmt( (Token::Fn, pos) => { let mut new_state = ParseState::new( state.engine, - state.script_hash, #[cfg(not(feature = "unchecked"))] state.max_function_expr_depth, #[cfg(not(feature = "unchecked"))] @@ -2621,13 +2610,20 @@ fn parse_stmt( let func = parse_fn(input, &mut new_state, lib, access, settings, _comments)?; - lib.insert( - // Qualifiers (none) + function name + number of arguments. - calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap(), - func, - ); + // Qualifiers (none) + function name + number of arguments. + let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap(); - Ok(Stmt::Noop(settings.pos)) + if lib.contains_key(&hash) { + return Err(PERR::FnDuplicatedDefinition( + func.name.into_owned(), + func.params.len(), + ) + .into_err(pos)); + } + + lib.insert(hash, func); + + Ok(Stmt::Noop(pos)) } (_, pos) => Err(PERR::MissingToken( @@ -3002,10 +2998,10 @@ fn parse_anon_fn( params.into_iter().map(|(v, _)| v).collect() }; - // Create unique function name by hashing the script hash plus the position + // Create unique function name by hashing the script body plus the parameters. let hasher = &mut get_hasher(); - state.script_hash.hash(hasher); - settings.pos.hash(hasher); + params.iter().for_each(|p| p.as_str().hash(hasher)); + body.hash(hasher); let hash = hasher.finish(); let fn_name: ImmutableString = format!("{}{:016x}", crate::engine::FN_ANONYMOUS, hash).into(); @@ -3038,7 +3034,6 @@ fn parse_anon_fn( impl Engine { pub(crate) fn parse_global_expr( &self, - script_hash: u64, input: &mut TokenStream, scope: &Scope, optimization_level: OptimizationLevel, @@ -3046,12 +3041,11 @@ impl Engine { let mut functions = Default::default(); let mut state = ParseState::new( self, - script_hash, #[cfg(not(feature = "unchecked"))] - self.max_expr_depth(), + NonZeroUsize::new(self.max_expr_depth()), #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_function"))] - self.max_function_expr_depth(), + NonZeroUsize::new(self.max_function_expr_depth()), ); let settings = ParseSettings { @@ -3088,19 +3082,17 @@ impl Engine { /// Parse the global level statements. fn parse_global_level( &self, - script_hash: u64, input: &mut TokenStream, ) -> Result<(Vec, Vec), ParseError> { let mut statements = Vec::with_capacity(16); let mut functions = HashMap::with_capacity_and_hasher(16, StraightHasherBuilder); let mut state = ParseState::new( self, - script_hash, #[cfg(not(feature = "unchecked"))] - self.max_expr_depth(), + NonZeroUsize::new(self.max_expr_depth()), #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_function"))] - self.max_function_expr_depth(), + NonZeroUsize::new(self.max_function_expr_depth()), ); while !input.peek().unwrap().0.is_eof() { @@ -3158,12 +3150,11 @@ impl Engine { #[inline(always)] pub(crate) fn parse( &self, - script_hash: u64, input: &mut TokenStream, scope: &Scope, optimization_level: OptimizationLevel, ) -> Result { - let (statements, lib) = self.parse_global_level(script_hash, input)?; + let (statements, lib) = self.parse_global_level(input)?; Ok( // Optimize AST diff --git a/src/result.rs b/src/result.rs index 0face85e..d0ca7e52 100644 --- a/src/result.rs +++ b/src/result.rs @@ -366,6 +366,11 @@ impl EvalAltResult { | Self::Return(_, pos) => *pos, } } + /// Clear the [position][Position] information of this error. + pub fn clear_position(&mut self) -> &mut Self { + self.set_position(Position::NONE); + self + } /// Override the [position][Position] of this error. pub fn set_position(&mut self, new_position: Position) { match self { diff --git a/src/stdlib.rs b/src/stdlib.rs index 8171750b..0c0e0438 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -13,6 +13,7 @@ mod inner { #[cfg(feature = "sync")] pub use alloc::sync; + pub use alloc::{borrow, boxed, format, rc, string, vec}; pub use core_error as error; diff --git a/src/token.rs b/src/token.rs index 7ae8ba8a..de291fbe 100644 --- a/src/token.rs +++ b/src/token.rs @@ -8,6 +8,7 @@ use crate::stdlib::{ borrow::Cow, char, fmt, format, iter::Peekable, + num::NonZeroUsize, str::{Chars, FromStr}, string::{String, ToString}, }; @@ -747,7 +748,7 @@ impl From for String { #[derive(Debug, Clone, Eq, PartialEq, Default)] pub struct TokenizeState { /// Maximum length of a string (0 = unlimited). - pub max_string_size: usize, + pub max_string_size: Option, /// Can the next token be a unary operator? pub non_unary: bool, /// Is the tokenizer currently inside a block comment? @@ -796,8 +797,10 @@ pub fn parse_string_literal( pos.advance(); - if state.max_string_size > 0 && result.len() > state.max_string_size { - return Err((LexError::StringTooLong(state.max_string_size), *pos)); + if let Some(max) = state.max_string_size { + if result.len() > max.get() { + return Err((LexError::StringTooLong(max.get()), *pos)); + } } match next_char { @@ -902,8 +905,10 @@ pub fn parse_string_literal( let s = result.iter().collect::(); - if state.max_string_size > 0 && s.len() > state.max_string_size { - return Err((LexError::StringTooLong(state.max_string_size), *pos)); + if let Some(max) = state.max_string_size { + if s.len() > max.get() { + return Err((LexError::StringTooLong(max.get()), *pos)); + } } Ok(s) @@ -1801,7 +1806,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] max_string_size: self.limits.max_string_size, #[cfg(feature = "unchecked")] - max_string_size: 0, + max_string_size: None, non_unary: false, comment_level: 0, end_with_none: false, diff --git a/src/utils.rs b/src/utils.rs index c43a5aaf..6931476a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,13 +6,16 @@ use crate::stdlib::{ borrow::Borrow, boxed::Box, cmp::Ordering, + collections::HashMap, fmt, + fmt::{Debug, Display}, hash::{BuildHasher, Hash, Hasher}, iter::{empty, FromIterator}, num::NonZeroU64, - ops::{Add, AddAssign, Deref}, + ops::{Add, AddAssign, Deref, DerefMut}, str::FromStr, string::{String, ToString}, + vec::Vec, }; use crate::Shared; @@ -140,6 +143,55 @@ pub(crate) fn combine_hashes(a: NonZeroU64, b: NonZeroU64) -> NonZeroU64 { NonZeroU64::new(a.get() ^ b.get()).unwrap_or_else(|| NonZeroU64::new(42).unwrap()) } +/// _(INTERNALS)_ A type that wraps a [`HashMap`] and implements [`Hash`]. +/// Exported under the `internals` feature only. +#[derive(Clone, Default)] +pub struct HashableHashMap(HashMap); + +impl From> for HashableHashMap { + fn from(value: HashMap) -> Self { + Self(value) + } +} +impl AsRef> for HashableHashMap { + fn as_ref(&self) -> &HashMap { + &self.0 + } +} +impl AsMut> for HashableHashMap { + fn as_mut(&mut self) -> &mut HashMap { + &mut self.0 + } +} +impl Deref for HashableHashMap { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for HashableHashMap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl Debug for HashableHashMap { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} +impl Hash for HashableHashMap { + fn hash(&self, state: &mut B) { + let mut keys: Vec<_> = self.0.keys().collect(); + keys.sort(); + + keys.into_iter().for_each(|key| { + key.hash(state); + self.0.get(&key).unwrap().hash(state); + }); + } +} + /// The system immutable string type. /// /// An [`ImmutableString`] wraps an [`Rc`][std::rc::Rc]`<`[`String`]`>` @@ -276,17 +328,17 @@ impl<'a> FromIterator for ImmutableString { } } -impl fmt::Display for ImmutableString { +impl Display for ImmutableString { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self.0.as_str(), f) + Display::fmt(self.0.as_str(), f) } } -impl fmt::Debug for ImmutableString { +impl Debug for ImmutableString { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self.0.as_str(), f) + Debug::fmt(self.0.as_str(), f) } } diff --git a/tests/expressions.rs b/tests/expressions.rs index 771d1d02..e6c09b5c 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -26,7 +26,7 @@ fn test_expressions() -> Result<(), Box> { Ok(()) } -/// This example taken from https://github.com/jonathandturner/rhai/issues/115 +/// This example taken from https://github.com/rhaiscript/rhai/issues/115 #[test] #[cfg(not(feature = "no_object"))] fn test_expressions_eval() -> Result<(), Box> { diff --git a/tests/internal_fn.rs b/tests/internal_fn.rs index 67417344..80d904e7 100644 --- a/tests/internal_fn.rs +++ b/tests/internal_fn.rs @@ -1,6 +1,6 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult, ParseErrorType, INT}; #[test] fn test_internal_fn() -> Result<(), Box> { @@ -46,18 +46,30 @@ fn test_internal_fn_overloading() -> Result<(), Box> { assert_eq!( engine.eval::( - r#" + r" fn abc(x,y,z) { 2*x + 3*y + 4*z + 888 } - fn abc(x) { x + 42 } fn abc(x,y) { x + 2*y + 88 } fn abc() { 42 } - fn abc(x) { x - 42 } // should override previous definition + fn abc(x) { x - 42 } abc() + abc(1) + abc(1,2) + abc(1,2,3) - "# + " )?, 1002 ); + assert_eq!( + *engine + .compile( + r" + fn abc(x) { x + 42 } + fn abc(x) { x - 42 } + " + ) + .expect_err("should error") + .0, + ParseErrorType::FnDuplicatedDefinition("abc".to_string(), 1) + ); + Ok(()) }