commit
3b16fae455
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rhai"
|
||||
version = "0.15.2"
|
||||
version = "0.16.0"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
|
||||
description = "Embedded scripting for Rust"
|
||||
@ -33,6 +33,7 @@ no_index = [] # no arrays and indexing
|
||||
no_object = [] # no custom objects
|
||||
no_function = [] # no script-defined functions
|
||||
no_module = [] # no modules
|
||||
internals = [] # expose internal data structures
|
||||
|
||||
# compiling for no-std
|
||||
no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]
|
||||
|
14
README.md
14
README.md
@ -21,7 +21,7 @@ Supported targets and builds
|
||||
Features
|
||||
--------
|
||||
|
||||
* Easy-to-use language similar to JS+Rust with dynamic typing.
|
||||
* Easy-to-use language similar to JavaScript+Rust with dynamic typing.
|
||||
* Tight integration with native Rust [functions](https://schungx.github.io/rhai/rust/functions.html) and [types]([#custom-types-and-methods](https://schungx.github.io/rhai/rust/custom.html)), including [getters/setters](https://schungx.github.io/rhai/rust/getters-setters.html), [methods](https://schungx.github.io/rhai/rust/custom.html) and [indexers](https://schungx.github.io/rhai/rust/indexers.html).
|
||||
* Freely pass Rust variables/constants into a script via an external [`Scope`](https://schungx.github.io/rhai/rust/scope.html).
|
||||
* Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust.
|
||||
@ -31,10 +31,12 @@ Features
|
||||
one single source file, all with names starting with `"unsafe_"`).
|
||||
* Re-entrant scripting engine can be made `Send + Sync` (via the [`sync`] feature).
|
||||
* Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
|
||||
* Rugged - protection 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.
|
||||
* 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.
|
||||
* [Function overloading](https://schungx.github.io/rhai/language/overload.html).
|
||||
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html).
|
||||
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html).
|
||||
* Some 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://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
|
||||
* Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html).
|
||||
@ -42,4 +44,10 @@ Features
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language.
|
||||
See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai scripting engine and language.
|
||||
|
||||
Playground
|
||||
----------
|
||||
|
||||
An [Online Playground](https://alvinhochun.github.io/rhai-demo/) is available with syntax-highlighting editor.
|
||||
Scripts can be evaluated directly from the editor.
|
||||
|
18
RELEASES.md
18
RELEASES.md
@ -1,18 +1,34 @@
|
||||
Rhai Release Notes
|
||||
==================
|
||||
|
||||
Version 0.15.2
|
||||
Version 0.16.0
|
||||
==============
|
||||
|
||||
The major new feature in this version is OOP - well, poor man's OOP, that is.
|
||||
|
||||
Breaking changes
|
||||
----------------
|
||||
|
||||
* The trait function `ModuleResolver::resolve` no longer takes a `Scope` as argument.
|
||||
* Functions defined in script now differentiates between using method-call style and normal function-call style.
|
||||
The method-call style will bind the object to the `this` parameter instead of consuming the first parameter.
|
||||
* Imported modules are no longer stored in the `Scope`. `Scope::push_module` is removed.
|
||||
Therefore, cannot rely on module imports to persist across invocations using a `Scope`.
|
||||
* `AST::retain_functions` is used for another purpose. The old `AST::retain_functions` is renamed to `AST::clear_statements`.
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
* Support for _function pointers_ via `Fn(name)` and `Fn.call(...)` syntax - a poor man's first-class function.
|
||||
* Support for calling script-defined functions in method-call style with `this` binding to the object.
|
||||
* Special support in object maps for OOP.
|
||||
* Expanded the `AST` API for fine-tuned manipulation of functions.
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
* [The Rhai Book](https://schungx.github.io/rhai) 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).
|
||||
|
||||
|
||||
Version 0.15.1
|
||||
|
@ -1,36 +1,37 @@
|
||||
The Rhai Scripting Language
|
||||
==========================
|
||||
|
||||
1. [What is Rhai](about.md)
|
||||
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. [Related Resources](about/related.md)
|
||||
2. [Getting Started](start.md)
|
||||
1. [Install the Rhai Crate](start/install.md)
|
||||
2. [Optional Features](start/features.md)
|
||||
3. [Special Builds](start/builds.md)
|
||||
1. [Performance Build](start/builds/performance.md)
|
||||
2. [Minimal Build](start/builds/minimal.md)
|
||||
3. [no-std Build](start/builds/no-std.md)
|
||||
3. [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)
|
||||
4. [Examples](start/examples.md)
|
||||
5. [Examples](start/examples/index.md)
|
||||
1. [Rust](start/examples/rust.md)
|
||||
2. [Scripts](start/examples/scripts.md)
|
||||
3. [Using the `Engine`](engine.md)
|
||||
4. [Using the `Engine`](engine/index.md)
|
||||
1. [Hello World in Rhai - Evaluate a Script](engine/hello-world.md)
|
||||
2. [Compile a Script to AST for Repeated Evaluations](engine/compile.md)
|
||||
3. [Call a Rhai Function from Rust](engine/call-fn.md)
|
||||
4. [Create a Rust Anonymous Function from a Rhai Function](engine/func.md)
|
||||
5. [Evaluate Expressions Only](engine/expressions.md)
|
||||
6. [Raw Engine](engine/raw.md)
|
||||
4. [Extend Rhai with Rust](rust.md)
|
||||
5. [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. [Packages](rust/packages.md)
|
||||
5. [Packages](rust/packages/index.md)
|
||||
1. [Built-in Packages](rust/packages/builtin.md)
|
||||
2. [Create a Custom Package](rust/packages/create.md)
|
||||
6. [Override a Built-in Function](rust/override.md)
|
||||
@ -40,9 +41,9 @@ The Rhai Scripting Language
|
||||
2. [Indexers](rust/indexers.md)
|
||||
3. [Disable Custom Types](rust/disable-custom.md)
|
||||
4. [Printing Custom Types](rust/print-custom.md)
|
||||
9. [Scope - Initializing and Maintaining State](rust/scope.md)
|
||||
9. [Scope - Initializing and Maintaining State](rust/scope.md)
|
||||
10. [Engine Configuration Options](rust/options.md)
|
||||
5. [Rhai Language Reference](language.md)
|
||||
6. [Rhai Language Reference](language/index.md)
|
||||
1. [Comments](language/comments.md)
|
||||
2. [Values and Types](language/values-and-types.md)
|
||||
1. [Dynamic Values](language/dynamic.md)
|
||||
@ -56,6 +57,7 @@ The Rhai Scripting Language
|
||||
5. [Arrays](language/arrays.md)
|
||||
6. [Object Maps](language/object-maps.md)
|
||||
1. [Parse from JSON](language/json.md)
|
||||
2. [Special Support for OOP](language/object-maps-oop.md)
|
||||
7. [Time-Stamps](language/timestamps.md)
|
||||
3. [Keywords](language/keywords.md)
|
||||
4. [Statements](language/statements.md)
|
||||
@ -69,17 +71,19 @@ The Rhai Scripting Language
|
||||
12. [Return Values](language/return.md)
|
||||
13. [Throw Exception on Error](language/throw.md)
|
||||
14. [Functions](language/functions.md)
|
||||
1. [Function Overloading](language/overload.md)
|
||||
2. [Call Method as Function](language/method.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)
|
||||
15. [Print and Debug](language/print-debug.md)
|
||||
16. [Modules](language/modules.md)
|
||||
16. [Modules](language/modules/index.md)
|
||||
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
|
||||
2. [Import Modules](language/modules/import.md)
|
||||
3. [Create from Rust](language/modules/rust.md)
|
||||
4. [Create from AST](language/modules/ast.md)
|
||||
5. [Module Resolvers](language/modules/resolvers.md)
|
||||
1. [Implement a Custom Module Resolver](language/modules/imp-resolver.md)
|
||||
6. [Safety and Protection](safety.md)
|
||||
7. [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)
|
||||
@ -90,12 +94,17 @@ The Rhai Scripting Language
|
||||
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. [Advanced Topics](advanced.md)
|
||||
1. [Script Optimization](engine/optimize.md)
|
||||
8. [Advanced Topics](advanced.md)
|
||||
1. [Object-Oriented Programming (OOP)](language/oop.md)
|
||||
2. [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)
|
||||
2. [Eval Statement](language/eval.md)
|
||||
3. [Eval Statement](language/eval.md)
|
||||
9. [Appendix](appendix/index.md)
|
||||
1. [Keywords](appendix/keywords.md)
|
||||
2. [Operators](appendix/operators.md)
|
||||
3. [Literals](appendix/literals.md)
|
||||
|
@ -6,9 +6,9 @@ Features
|
||||
Easy
|
||||
----
|
||||
|
||||
* Easy-to-use language similar to JS+Rust with dynamic typing.
|
||||
* Easy-to-use language similar to JavaScript+Rust with dynamic typing.
|
||||
|
||||
* Tight integration with native Rust [functions]({{rootUrl}}/rust/functions.md) and [types]({{rootUrl}}/rust/custom.md), including [getters/setters]({{rootUrl}}/rust/getters-setters.md), [methods]({{rootUrl}}/rust/custom.md) and [indexers]({{rootUrl}}/rust/indexers.md).
|
||||
* 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`].
|
||||
|
||||
@ -24,7 +24,7 @@ Fast
|
||||
|
||||
* Fairly efficient evaluation (1 million iterations in 0.25 sec on a single core, 2.3 GHz Linux VM).
|
||||
|
||||
* Scripts are [optimized]({{rootUrl}}/engine/optimize.md) (useful for template-based machine-generated scripts) for repeated evaluations.
|
||||
* Scripts are [optimized][script optimization] (useful for template-based machine-generated scripts) for repeated evaluations.
|
||||
|
||||
Dynamic
|
||||
-------
|
||||
@ -35,6 +35,10 @@ Dynamic
|
||||
|
||||
* Organize code base with dynamically-loadable [modules].
|
||||
|
||||
* Dynamic dispatch via [function pointers].
|
||||
|
||||
* Some support for [object-oriented programming (OOP)][OOP].
|
||||
|
||||
Safe
|
||||
----
|
||||
|
||||
@ -46,7 +50,7 @@ Rugged
|
||||
|
||||
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
|
||||
|
||||
* Protected against malicious attacks (such as [stack-overflow]({{rootUrl}}/safety/max-call-stack.md), [over-sized data]({{rootUrl}}/safety/max-string-size.md), and [runaway scripts]({{rootUrl}}/safety/max-operations.md) etc.) that may come from untrusted third-party user-land scripts.
|
||||
* 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.
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
What is Rhai
|
||||
============
|
||||
|
||||
{{#include links.md}}
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way
|
||||
to add scripting to any application.
|
@ -11,10 +11,15 @@ It doesn't attempt to be a new language. For example:
|
||||
* No traits... so it is also not Rust. Do your Rusty stuff in Rust.
|
||||
|
||||
* No structures/records - 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]
|
||||
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] allowing runtime dispatch by function name.
|
||||
|
||||
* No garbage collection - this should be expected, so...
|
||||
|
||||
* No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure]({{rootUrl}}/engine/call-fn.md).
|
||||
@ -26,7 +31,7 @@ Due to this intended usage, Rhai deliberately keeps the language simple and smal
|
||||
such as classes, inheritance, first-class functions, closures, concurrency, byte-codes, JIT etc.
|
||||
|
||||
Avoid the temptation to write full-fledge program logic entirely in Rhai - that use case is best fulfilled by
|
||||
more complete languages such as JS or Lua.
|
||||
more complete languages such as JavaScript or Lua.
|
||||
|
||||
Therefore, in actual practice, it is usually best to expose a Rust API into Rhai for scripts to call.
|
||||
All your core functionalities should be in Rust.
|
||||
|
@ -5,8 +5,13 @@ Related Resources
|
||||
|
||||
Other online documentation resources for Rhai:
|
||||
|
||||
* [`DOCS.RS`](https://docs.rs/rhai)
|
||||
* [`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
|
||||
|
||||
* [Online Playground][playground] - Run scripts directly from editor
|
||||
|
||||
Other cool projects to check out:
|
||||
|
||||
|
@ -7,4 +7,4 @@ This section covers advanced features such as:
|
||||
|
||||
* [Script optimization]
|
||||
|
||||
* The dreaded (or beloved depending on your taste) [`eval`] statement
|
||||
* The dreaded (or beloved for those with twisted tastes) [`eval`] statement
|
||||
|
6
doc/src/appendix/index.md
Normal file
6
doc/src/appendix/index.md
Normal file
@ -0,0 +1,6 @@
|
||||
Appendix
|
||||
========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
This section contains miscellaneous reference materials.
|
33
doc/src/appendix/keywords.md
Normal file
33
doc/src/appendix/keywords.md
Normal file
@ -0,0 +1,33 @@
|
||||
Keywords List
|
||||
=============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
| Keyword | Description | Not available under |
|
||||
| :-------------------: | ---------------------------------------- | :-----------------: |
|
||||
| `true` | Boolean true literal | |
|
||||
| `false` | Boolean false literal | |
|
||||
| `let` | Variable declaration | |
|
||||
| `const` | Constant declaration | |
|
||||
| `if` | If statement | |
|
||||
| `else` | else block of if statement | |
|
||||
| `while` | While loop | |
|
||||
| `loop` | Infinite loop | |
|
||||
| `for` | For loop | |
|
||||
| `in` | Containment test, part of for loop | |
|
||||
| `continue` | Continue a loop at the next iteration | |
|
||||
| `break` | Loop breaking | |
|
||||
| `return` | Return value | |
|
||||
| `throw` | Throw exception | |
|
||||
| `import` | Import module | [`no_module`] |
|
||||
| `export` | Export variable | [`no_module`] |
|
||||
| `as` | Alias for variable export | [`no_module`] |
|
||||
| `private` | Mark function private | [`no_function`] |
|
||||
| `fn` (lower-case `f`) | Function definition | [`no_function`] |
|
||||
| `Fn` (capital `F`) | Function to create a [function pointer] | |
|
||||
| `call` | Call a [function pointer] | |
|
||||
| `this` | Reference to base object for method call | [`no_function`] |
|
||||
| `type_of` | Get type name of value | |
|
||||
| `print` | Print value | |
|
||||
| `debug` | Print value in debug format | |
|
||||
| `eval` | Evaluate script | |
|
16
doc/src/appendix/literals.md
Normal file
16
doc/src/appendix/literals.md
Normal file
@ -0,0 +1,16 @@
|
||||
Literals Syntax
|
||||
===============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
| Type | Literal syntax |
|
||||
| :--------------------------------: | :------------------------------------------------------------------------------: |
|
||||
| `INT` | `42`, `-123`, `0`,<br/>`0x????..` (hex), `0b????..` (binary), `0o????..` (octal) |
|
||||
| `FLOAT` | `42.0`, `-123.456`, `0.0` |
|
||||
| [String] | `"... \x?? \u???? \U???????? ..."` |
|
||||
| Character | `"... \x?? \u???? \U???????? ..."` |
|
||||
| [`Array`] | `[ ???, ???, ??? ]` |
|
||||
| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` |
|
||||
| Boolean true | `true` |
|
||||
| Boolean false | `false` |
|
||||
| `Nothing`/`null`/`nil`/`void`/Unit | `()` |
|
30
doc/src/appendix/operators.md
Normal file
30
doc/src/appendix/operators.md
Normal file
@ -0,0 +1,30 @@
|
||||
Operators
|
||||
=========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
| Operator | Description | Binary? |
|
||||
| :---------------: | ------------------------------ | :-----: |
|
||||
| `+` | Add | Yes |
|
||||
| `-` | Subtract, Minus | Yes/No |
|
||||
| `*` | Multiply | Yes |
|
||||
| `/` | Divide | Yes |
|
||||
| `%` | Modulo | Yes |
|
||||
| `~` | Power | Yes |
|
||||
| `>>` | Right bit-shift | Yes |
|
||||
| `<<` | Left bit-shift | Yes |
|
||||
| `&` | Bit-wise _And_, Boolean _And_ | Yes |
|
||||
| <code>\|</code> | Bit-wise _Or_, Boolean _Or_ | Yes |
|
||||
| `^` | Bit-wise _Xor_ | Yes |
|
||||
| `==` | Equals to | Yes |
|
||||
| `~=` | Not equals to | Yes |
|
||||
| `>` | Greater than | Yes |
|
||||
| `>=` | Greater than or equals to | Yes |
|
||||
| `<` | Less than | Yes |
|
||||
| `<=` | Less than or equals to | Yes |
|
||||
| `>=` | Greater than or equals to | Yes |
|
||||
| `&&` | Boolean _And_ (short-circuits) | Yes |
|
||||
| <code>\|\|</code> | Boolean _Or_ (short-circuits) | Yes |
|
||||
| `!` | Boolean _Not_ | No |
|
||||
| `[` .. `]` | Indexing | Yes |
|
||||
| `.` | Property access, Method call | Yes |
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"version": "0.16.0",
|
||||
"rootUrl": "",
|
||||
"rootUrlX": "/rhai"
|
||||
}
|
@ -3,7 +3,7 @@ Compile a Script (to AST)
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
To repeatedly evaluate a script, _compile_ it first into an AST (abstract syntax tree) form:
|
||||
To repeatedly evaluate a script, _compile_ it first into an `AST` (abstract syntax tree) form:
|
||||
|
||||
```rust
|
||||
// Compile to an AST and store it for later evaluations
|
||||
|
@ -1,7 +1,7 @@
|
||||
Using the Engine
|
||||
================
|
||||
|
||||
{{#include links.md}}
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai's interpreter resides in the [`Engine`] type under the master `rhai` namespace.
|
||||
|
@ -1,7 +1,7 @@
|
||||
Script Optimization
|
||||
===================
|
||||
|
||||
{{#include ../links.md}}
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Rhai includes an _optimizer_ that tries to optimize a script after parsing.
|
||||
This can reduce resource utilization and increase execution speed.
|
@ -3,10 +3,7 @@ Optimization Levels
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Set Optimization Level
|
||||
---------------------
|
||||
|
||||
There are actually three levels of optimizations: `None`, `Simple` and `Full`.
|
||||
There are three levels of optimization: `None`, `Simple` and `Full`.
|
||||
|
||||
* `None` is obvious - no optimization on the AST is performed.
|
||||
|
||||
@ -16,6 +13,10 @@ There are actually three levels of optimizations: `None`, `Simple` and `Full`.
|
||||
* `Full` is _much_ more aggressive, _including_ running 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
|
||||
|
@ -3,15 +3,38 @@ Re-Optimize an AST
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
If it is ever needed to _re_-optimize an `AST`, use the `optimize_ast` method:
|
||||
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 script to AST
|
||||
let ast = engine.compile("40 + 2")?;
|
||||
// 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 if using 'OptimizationLevel::Full'
|
||||
let scope = Scope::new();
|
||||
// Create a new 'Scope' - put constants in it to aid optimization
|
||||
let mut scope = Scope::new();
|
||||
scope.push_constant("SCENARIO_1", true);
|
||||
scope.push_constant("SCENARIO_2", false);
|
||||
scope.push_constant("SCENARIO_3", false);
|
||||
|
||||
// Re-optimize the AST
|
||||
let ast = engine.optimize_ast(&scope, &ast, OptimizationLevel::Full);
|
||||
let new_ast = engine.optimize_ast(&scope, master_ast.clone(), OptimizationLevel::Simple);
|
||||
|
||||
// 'new_ast' is essentially: 'do_work()'
|
||||
```
|
||||
|
@ -22,7 +22,7 @@ The maximum allowed size of an array can be controlled via `Engine::set_max_arra
|
||||
Built-in Functions
|
||||
-----------------
|
||||
|
||||
The following methods (mostly defined in the [`BasicArrayPackage`]({{rootUrl}}/rust/packages.md) but excluded if using a [raw `Engine`]) operate on arrays:
|
||||
The following methods (mostly defined in the [`BasicArrayPackage`][packages] but excluded if using a [raw `Engine`]) operate on arrays:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
@ -38,6 +38,7 @@ The following methods (mostly defined in the [`BasicArrayPackage`]({{rootUrl}}/r
|
||||
| `clear` | _none_ | empties the array |
|
||||
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
|
@ -9,11 +9,14 @@ 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
|
||||
```
|
||||
|
||||
Constants must be assigned a _value_, not an expression.
|
||||
Unlike variables which need not have initial values (default to [`()`]),
|
||||
constants must be assigned one, and it must be a constant _value_, not an expression.
|
||||
|
||||
```rust
|
||||
const x = 40 + 2; // <- syntax error: cannot assign expression to constant
|
||||
|
@ -11,10 +11,14 @@ 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"
|
||||
```
|
||||
|
@ -27,6 +27,8 @@ if type_of(mystery) == "i64" {
|
||||
print("Hey, I got an array here!");
|
||||
} else if type_of(mystery) == "map" {
|
||||
print("Hey, I got an object map here!");
|
||||
} else if type_of(mystery) == "Fn" {
|
||||
print("Hey, I got a function pointer here!");
|
||||
} else if type_of(mystery) == "TestStruct" {
|
||||
print("Hey, I got the TestStruct custom type here!");
|
||||
} else {
|
||||
|
@ -6,29 +6,29 @@
|
||||
Or "How to Shoot Yourself in the Foot even Easier"
|
||||
------------------------------------------------
|
||||
|
||||
Saving the best for last: in addition to script optimizations, there is the ever-dreaded... `eval` function!
|
||||
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
|
||||
let script = "let y = x;"; // build a script
|
||||
script += "y += foo(y);";
|
||||
script += "x + y";
|
||||
|
||||
let result = eval(script); // <- look, JS, we can also do this!
|
||||
let result = eval(script); // <- look, JavaScript, we can also do this!
|
||||
|
||||
print("Answer: " + result); // prints 42
|
||||
print("Answer: " + result); // prints 42
|
||||
|
||||
print("x = " + x); // prints 10: functions call arguments are passed by value
|
||||
print("y = " + y); // prints 32: variables defined in 'eval' persist!
|
||||
print("x = " + x); // prints 10: functions call arguments are passed by value
|
||||
print("y = " + y); // prints 32: variables defined in 'eval' persist!
|
||||
|
||||
eval("{ let z = y }"); // to keep a variable local, use a statement block
|
||||
eval("{ let z = y }"); // to keep a variable local, use a statement block
|
||||
|
||||
print("z = " + z); // <- error: variable 'z' not found
|
||||
print("z = " + z); // <- error: variable 'z' not found
|
||||
|
||||
"print(42)".eval(); // <- nope... method-call style doesn't work
|
||||
"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_,
|
||||
@ -45,8 +45,8 @@ 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
|
||||
eval(script); // variable 'x' in the current scope is visible!
|
||||
print(x); // prints 42
|
||||
|
||||
// The above is equivalent to:
|
||||
let script = "x += 32";
|
||||
@ -65,7 +65,7 @@ disable `eval` 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"); // 'eval' here throws "eval is evil! I refuse to run 40 + 2"
|
||||
let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run 40 + 2"
|
||||
```
|
||||
|
||||
Or overload it from Rust:
|
||||
@ -82,15 +82,15 @@ engine.register_result_fn("eval", alt_eval);
|
||||
`EvalPackage`
|
||||
-------------
|
||||
|
||||
There is even a package named [`EvalPackage`]({{rootUrl}}/rust/packages.md) which implements the disabling override:
|
||||
There is even a package named [`EvalPackage`][packages] which implements the disabling override:
|
||||
|
||||
```rust
|
||||
use rhai::Engine;
|
||||
use rhai::packages::Package // load the 'Package' trait to use packages
|
||||
use rhai::packages::EvalPackage; // the 'eval' package disables 'eval'
|
||||
use rhai::packages::Package // load the 'Package' trait to use packages
|
||||
use rhai::packages::EvalPackage; // the 'eval' package disables 'eval'
|
||||
|
||||
let mut engine = Engine::new();
|
||||
let package = EvalPackage::new(); // create the package
|
||||
let package = EvalPackage::new(); // create the package
|
||||
|
||||
engine.load_package(package.get()); // load the package
|
||||
engine.load_package(package.get()); // load the package
|
||||
```
|
||||
|
141
doc/src/language/fn-namespaces.md
Normal file
141
doc/src/language/fn-namespaces.md
Normal file
@ -0,0 +1,141 @@
|
||||
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` method allows merging all functions in one [`AST`] into another,
|
||||
forming a new, combined, group of functions.
|
||||
|
||||
In general, there are two types of _namespaces_ where functions are looked up:
|
||||
|
||||
| Namespace | Source | Lookup method | How Many |
|
||||
| --------- | ---------------------------------------------------------------------- | --------------------------------- | :----------------------: |
|
||||
| Global | `Engine::register_XXX` API, [`AST`] being evaluated, [packages] loaded | Simple function name | One |
|
||||
| Module | [`Module`] | Namespace-qualified function name | As many as [`import`]-ed |
|
||||
|
||||
|
||||
Global Namespace
|
||||
----------------
|
||||
|
||||
There is one _global_ namespace for every [`Engine`], which includes:
|
||||
|
||||
* All the native Rust functions registered via the `Engine::register_XXX` API.
|
||||
|
||||
* All the Rust functions defined in [packages] that are loaded into the [`Engine`].
|
||||
|
||||
In addition, during evaluation of an [`AST`], all script-defined functions bundled together within
|
||||
the [`AST`] are added to the global namespace and override any existing registered functions of
|
||||
the same names and number of parameters.
|
||||
|
||||
Anywhere in a Rhai script, when a function call is made, it is searched within the global namespace.
|
||||
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 message() { "Hello!" } // greeting message
|
||||
|
||||
fn say_hello() {
|
||||
print(message()); // prints message
|
||||
}
|
||||
|
||||
say_hello();
|
||||
"#
|
||||
)?;
|
||||
|
||||
// Compile another script with an overriding function
|
||||
let ast2 = engine.compile(r#"fn message() { "Boo!" }"#)?;
|
||||
|
||||
// Merge the two AST's
|
||||
let ast = ast1.merge(ast2); // 'message' will be overwritten
|
||||
|
||||
engine.consume_ast(&ast)?; // 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 message() { "Hello!" }
|
||||
|
||||
script.rhai:
|
||||
|
||||
fn say_hello() {
|
||||
import "message" as msg;
|
||||
print(msg::message());
|
||||
}
|
||||
say_hello();
|
||||
```
|
||||
|
||||
|
||||
Module Namespaces
|
||||
-----------------
|
||||
|
||||
[Modules] can be dynamically loaded into a Rhai script using the [`import`] keyword.
|
||||
When that happens, functions defined within the [module] can be called with a _qualified_ name.
|
||||
|
||||
There is a catch, though, if functions in a module script refer to global functions
|
||||
defined _within the script_. When called later, those functions will be searched in the
|
||||
current global namespace and may not be found.
|
||||
|
||||
```rust
|
||||
greeting.rhai:
|
||||
|
||||
fn message() { "Hello!" };
|
||||
|
||||
fn say_hello() { print(message()); }
|
||||
|
||||
say_hello(); // 'message' is looked up in the global namespace
|
||||
|
||||
script.rhai:
|
||||
|
||||
import "greeting" as g;
|
||||
g::say_hello(); // <- error: function not found - 'message'
|
||||
```
|
||||
|
||||
In the example above, although the module `greeting.rhai` loads fine (`"Hello!"` is printed),
|
||||
the subsequent call using the _namespace-qualified_ function name fails to find the same function
|
||||
'`message`' which now essentially becomes `g::message`. The call fails as there is no more
|
||||
function named '`message`' in the global namespace.
|
||||
|
||||
Therefore, when writing functions for a [module], make sure that those functions are as _pure_
|
||||
as possible and avoid cross-calling them from each other. A [function pointer] is a valid technique
|
||||
to call another function within a module-defined function:
|
||||
|
||||
```rust
|
||||
greeting.rhai:
|
||||
|
||||
fn message() { "Hello!" };
|
||||
|
||||
fn say_hello(msg_func) { // 'msg_func' is a function pointer
|
||||
print(msg_func.call()); // call via the function pointer
|
||||
}
|
||||
|
||||
say_hello(); // 'message' is looked up in the global namespace
|
||||
|
||||
script.rhai:
|
||||
|
||||
import "greeting" as g;
|
||||
|
||||
fn my_msg() {
|
||||
import "greeting" as g; // <- must import again here...
|
||||
g::message() // <- ... otherwise will not find module 'g'
|
||||
}
|
||||
|
||||
g::say_hello(Fn("my_msg")); // prints 'Hello!'
|
||||
```
|
136
doc/src/language/fn-ptr.md
Normal file
136
doc/src/language/fn-ptr.md
Normal file
@ -0,0 +1,136 @@
|
||||
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.
|
||||
|
||||
Call a function pointer using the `call` method, which needs to be called in method-call style.
|
||||
|
||||
|
||||
Built-in methods
|
||||
----------------
|
||||
|
||||
The following standard methods (mostly defined in the [`BasicFnPackage`][packages] but excluded if
|
||||
using a [raw `Engine`]) operate on [strings]:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| -------------------------- | ------------ | --------------------------------------------------------------------- |
|
||||
| `name` method and property | _none_ | returns the name of the function encapsulated by the function pointer |
|
||||
|
||||
|
||||
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); //<- error: 'call (Fn, i64)' is not a registered function
|
||||
|
||||
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 a _module_ [namespace][function namespace]
|
||||
(i.e. 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_something()'
|
||||
|
||||
f::do_something(); // works!
|
||||
|
||||
let p = Fn("f::do_something");
|
||||
|
||||
p.call(); // error: function not found - 'f::do_something'
|
||||
|
||||
fn do_something_now() { // call it from a local function
|
||||
import "foo" as f;
|
||||
f::do_something();
|
||||
}
|
||||
|
||||
let p = Fn("do_something_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);
|
||||
```
|
@ -5,50 +5,63 @@
|
||||
|
||||
Iterating through a range or an [array] 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.
|
||||
|
||||
```rust
|
||||
// Iterate through string, yielding characters
|
||||
let s = "hello, world!";
|
||||
|
||||
for ch in s {
|
||||
if ch > 'z' { continue; } // skip to the next iteration
|
||||
if ch > 'z' { continue; } // skip to the next iteration
|
||||
|
||||
print(ch);
|
||||
if x == '@' { break; } // break out of for loop
|
||||
|
||||
if x == '@' { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// Iterate through array
|
||||
let array = [1, 3, 5, 7, 9, 42];
|
||||
|
||||
for x in array {
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
if x == 42 { break; } // break out of for loop
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// The 'range' function allows iterating from first to last-1
|
||||
for x in range(0, 50) {
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
if x == 42 { break; } // break out of for loop
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// Iterate through object map
|
||||
let map = #{a:1, b:3, c:5, d:7, e:9};
|
||||
|
||||
// Property names are returned in random order
|
||||
// Property names are returned in unsorted, random order
|
||||
for x in keys(map) {
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
if x == 42 { break; } // break out of for loop
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// Property values are returned in random order
|
||||
// Property values are returned in unsorted, random order
|
||||
for val in values(map) {
|
||||
print(val);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ print(add(2, 3)); // prints 5
|
||||
print(sub(2, 3,)); // prints -1 - trailing comma in arguments list is OK
|
||||
```
|
||||
|
||||
|
||||
Implicit Return
|
||||
---------------
|
||||
|
||||
@ -38,6 +39,7 @@ print(add(2, 3)); // prints 5
|
||||
print(add2(42)); // prints 44
|
||||
```
|
||||
|
||||
|
||||
No Access to External Scope
|
||||
--------------------------
|
||||
|
||||
@ -50,26 +52,6 @@ let x = 42;
|
||||
fn foo() { x } // <- syntax error: variable 'x' doesn't exist
|
||||
```
|
||||
|
||||
Passing Arguments by Value
|
||||
-------------------------
|
||||
|
||||
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
|
||||
It is important to remember that all arguments are passed by _value_, so all functions are _pure_
|
||||
(i.e. they never modify their arguments).
|
||||
|
||||
Any update to an argument will **not** be reflected back to the caller.
|
||||
|
||||
This can introduce subtle bugs, if not careful, especially when using the _method-call_ style.
|
||||
|
||||
```rust
|
||||
fn change(s) { // 's' is passed by value
|
||||
s = 42; // only a COPY of 's' is changed
|
||||
}
|
||||
|
||||
let x = 500;
|
||||
x.change(); // de-sugars to 'change(x)'
|
||||
x == 500; // 'x' is NOT changed!
|
||||
```
|
||||
|
||||
Global Definitions Only
|
||||
----------------------
|
||||
@ -92,6 +74,56 @@ fn do_addition(x) {
|
||||
}
|
||||
```
|
||||
|
||||
Unlike C/C++, functions can be defined _anywhere_ within the 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.
|
||||
|
||||
Use Before Definition
|
||||
--------------------
|
||||
|
||||
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 Passed by Value
|
||||
------------------------
|
||||
|
||||
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
|
||||
Therefore, functions with the same name and same _number_ of parameters are equivalent.
|
||||
|
||||
It is important to remember that 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
|
||||
-----------------------------------
|
||||
|
||||
Functions can also be called in method-call style. When this is the case, 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 unbounded
|
||||
```
|
||||
|
@ -3,24 +3,30 @@
|
||||
|
||||
{{#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 ... {
|
||||
:
|
||||
} else if ... {
|
||||
:
|
||||
} 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!");
|
||||
}
|
||||
```
|
||||
|
||||
All branches of an `if` statement must be enclosed within braces '`{`' .. '`}`', even when there is only one statement.
|
||||
Like Rust, there is no ambiguity regarding which `if` clause a statement belongs to.
|
||||
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
|
||||
```
|
||||
|
@ -1,7 +1,7 @@
|
||||
Rhai Language Reference
|
||||
======================
|
||||
|
||||
{{#include links.md}}
|
||||
{{#include ../links.md}}
|
||||
|
||||
This section outlines the Rhai language.
|
||||
|
@ -13,8 +13,10 @@ The following are reserved keywords in Rhai:
|
||||
| `while`, `loop`, `for`, `in`, `continue`, `break` | Looping | |
|
||||
| `fn`, `private` | Functions | [`no_function`] |
|
||||
| `return` | Return values | |
|
||||
| `throw` | Return errors | |
|
||||
| `throw` | throw exceptions | |
|
||||
| `import`, `export`, `as` | Modules | [`no_module`] |
|
||||
| `Fn`, `call` | Function pointers | |
|
||||
| `type_of`, `print`, `debug`, `eval` | Special functions | |
|
||||
|
||||
Keywords cannot be the name of a [function] or [variable], unless the relevant exclusive feature is enabled.
|
||||
Keywords cannot be the name of a [function] or [variable], unless the relevant feature is enabled.
|
||||
For example, `fn` is a valid variable name under [`no_function`].
|
||||
|
@ -13,8 +13,11 @@ set of types (see [built-in operators]).
|
||||
|
||||
```rust
|
||||
42 == 42; // true
|
||||
|
||||
42 > 42; // false
|
||||
|
||||
"hello" > "foo"; // true
|
||||
|
||||
"42" == 42; // false
|
||||
```
|
||||
|
||||
@ -23,13 +26,17 @@ except for '`!=`' (not equals) which results in `true`. This is in line with int
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
@ -50,25 +57,42 @@ if the first one already proves the condition wrong.
|
||||
Single boolean operators `&` and `|` always evaluate both operands.
|
||||
|
||||
```rust
|
||||
this() || that(); // that() is not evaluated if this() is true
|
||||
this() && that(); // that() is not evaluated if this() is false
|
||||
a() || b(); // b() is not evaluated if a() is true
|
||||
|
||||
this() | that(); // both this() and that() are evaluated
|
||||
this() & that(); // both this() and that() are evaluated
|
||||
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
|
||||
```
|
||||
|
||||
Compound Assignment Operators
|
||||
----------------------------
|
||||
|
||||
```rust
|
||||
let number = 5;
|
||||
number += 4; // number = number + 4
|
||||
number -= 3; // number = number - 3
|
||||
number *= 2; // number = number * 2
|
||||
number /= 1; // number = number / 1
|
||||
number %= 3; // number = number % 3
|
||||
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 `+=` operator can also be used to build [strings]:
|
||||
|
@ -3,13 +3,24 @@ Infinite `loop`
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Infinite 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;
|
||||
|
||||
loop {
|
||||
x = 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.
|
||||
|
@ -3,11 +3,16 @@ Call Method as Function
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like a regular function in Rust.
|
||||
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
|
||||
@ -21,8 +26,8 @@ update(a); // <- this de-sugars to 'a.update()' thus if 'a' is a simple
|
||||
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
|
||||
// a transient (i.e. a copy), so this statement has no effect
|
||||
// except waste a lot of time cloning
|
||||
|
||||
array[0].update(); // <- call this method-call style will update 'a'
|
||||
array[0].update(); // <- call in method-call style will update 'a'
|
||||
```
|
||||
|
@ -3,7 +3,7 @@ Create a Module from an AST
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
It is easy to convert a pre-compiled `AST` into a module: just use `Module::eval_ast_as_new`.
|
||||
It is easy to convert a pre-compiled [`AST`] into a module: just use `Module::eval_ast_as_new`.
|
||||
|
||||
Don't forget the [`export`] statement, otherwise there will be no variables exposed by the module
|
||||
other than non-[`private`] functions (unless that's intentional).
|
||||
|
@ -3,9 +3,9 @@ Export Variables, Functions and Sub-Modules in Module
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
A _module_ is a single script (or pre-compiled `AST`) containing global variables, functions and sub-modules.
|
||||
A _module_ is a single script (or pre-compiled [`AST`]) containing global variables, functions and sub-modules.
|
||||
|
||||
A module can be created from a script via the `Module::eval_ast_as_new` method. When given an `AST`,
|
||||
A module can be created from a script via the `Module::eval_ast_as_new` method. When given an [`AST`],
|
||||
it is first evaluated, then the following items are exposed as members of the new module:
|
||||
|
||||
* Global variables - essentially all variables that remain in the [`Scope`] at the end of a script run - that are exported. Variables not exported (via the `export` statement) remain hidden.
|
||||
|
@ -7,8 +7,8 @@ For many applications in which Rhai is embedded, it is necessary to customize th
|
||||
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`, which contains only one function:
|
||||
`resolve`.
|
||||
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
|
||||
@ -43,18 +43,14 @@ impl ModuleResolver for MyModuleResolver {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Set the custom module resolver into the 'Engine'.
|
||||
engine.set_module_resolver(Some(MyModuleResolver {}));
|
||||
// Set the custom module resolver into the 'Engine'.
|
||||
engine.set_module_resolver(Some(MyModuleResolver {}));
|
||||
|
||||
engine.consume(r#"
|
||||
import "hello" as foo; // this 'import' statement will call
|
||||
// 'MyModuleResolver::resolve' with "hello" as path
|
||||
foo:bar();
|
||||
"#)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
engine.consume(r#"
|
||||
import "hello" as foo; // this 'import' statement will call
|
||||
// 'MyModuleResolver::resolve' with "hello" as path
|
||||
foo:bar();
|
||||
"#)?;
|
||||
```
|
||||
|
@ -3,6 +3,9 @@ Import a Module
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
`import` Statement
|
||||
-----------------
|
||||
|
||||
A module can be _imported_ via the `import` statement, and its members are accessed via '`::`' similar to C++.
|
||||
|
||||
```rust
|
||||
@ -17,10 +20,14 @@ 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, however, not advised to deviate from this common practice unless
|
||||
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
|
||||
@ -44,3 +51,32 @@ for x in range(0, 1000) {
|
||||
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
|
||||
// This file is 'hello.rhai'
|
||||
|
||||
import "hello" as foo; // import itself - infinite recursion!
|
||||
|
||||
foo::do_something();
|
||||
```
|
||||
|
||||
Modules cross-referencing also cause infinite recursion:
|
||||
|
||||
```rust
|
||||
// This file is 'hello.rhai' - references 'world.rhai'
|
||||
import "world" as foo;
|
||||
foo::do_something();
|
||||
|
||||
// This file is 'world.rhai' - references 'hello.rhai'
|
||||
import "hello" as bar;
|
||||
bar::do_something_else();
|
||||
```
|
||||
|
@ -1,7 +1,7 @@
|
||||
Modules
|
||||
=======
|
||||
|
||||
{{#include ../links.md}}
|
||||
{{#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.
|
@ -5,7 +5,7 @@ Module Resolvers
|
||||
|
||||
When encountering an [`import`] statement, Rhai attempts to _resolve_ the module based on the path string.
|
||||
|
||||
_Module Resolvers_ are service types that implement the [`ModuleResolver`]({{rootUrl}}/rust/traits.md) trait.
|
||||
_Module Resolvers_ are service types that implement the [`ModuleResolver`][traits] trait.
|
||||
|
||||
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.
|
||||
|
@ -6,18 +6,19 @@ Numeric Functions
|
||||
Integer Functions
|
||||
----------------
|
||||
|
||||
The following standard functions (defined in the [`BasicMathPackage`]({{rootUrl}}/rust/packages.md) but excluded if using a [raw `Engine`])
|
||||
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 | Description |
|
||||
| ------------ | --------------------------------- |
|
||||
| `abs` | absolute value |
|
||||
| [`to_float`] | converts an integer type to `f64` |
|
||||
| Function | Description |
|
||||
| ------------ | --------------------------------------------------------------- |
|
||||
| `abs` | absolute value |
|
||||
| `sign` | returns -1 if the number is negative, +1 if positive, 0 if zero |
|
||||
| [`to_float`] | converts an integer type to `f64` |
|
||||
|
||||
Floating-Point Functions
|
||||
-----------------------
|
||||
|
||||
The following standard functions (defined in the [`BasicMathPackage`]({{rootUrl}}/rust/packages.md) but excluded if using a [raw `Engine`])
|
||||
The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`])
|
||||
operate on `f64` only:
|
||||
|
||||
| Category | Functions |
|
||||
|
@ -22,19 +22,19 @@ number = -5 - +5;
|
||||
Binary Operators
|
||||
----------------
|
||||
|
||||
| Operator | Description | Integers only |
|
||||
| -------- | ---------------------------------------------------- | :-----------: |
|
||||
| `+` | Plus | |
|
||||
| `-` | Minus | |
|
||||
| `*` | Multiply | |
|
||||
| `/` | Divide (integer division if acting on integer types) | |
|
||||
| `%` | Modulo (remainder) | |
|
||||
| `~` | Power | |
|
||||
| `&` | Binary _And_ bit-mask | Yes |
|
||||
| `|` | Binary _Or_ bit-mask | Yes |
|
||||
| `^` | Binary _Xor_ bit-mask | Yes |
|
||||
| `<<` | Left bit-shift | Yes |
|
||||
| `>>` | Right bit-shift | Yes |
|
||||
| Operator | Description | Integers only |
|
||||
| --------------- | ---------------------------------------------------- | :-----------: |
|
||||
| `+` | Plus | |
|
||||
| `-` | Minus | |
|
||||
| `*` | Multiply | |
|
||||
| `/` | Divide (integer division if acting on integer types) | |
|
||||
| `%` | Modulo (remainder) | |
|
||||
| `~` | Power | |
|
||||
| `&` | Bit-wise _And_ | Yes |
|
||||
| <code>\|</code> | 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
|
||||
|
36
doc/src/language/object-maps-oop.md
Normal file
36
doc/src/language/object-maps-oop.md
Normal file
@ -0,0 +1,36 @@
|
||||
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] is called like a method, what happens next depends
|
||||
on whether the target function is a native Rust function or a script-defined function.
|
||||
|
||||
If it is a registered native Rust method function, then it is called directly.
|
||||
|
||||
If it is a script-defined function, the `this` variable within the function body is bound
|
||||
to the [object map] before the function is called. There is no way to simulate this behavior
|
||||
via a normal function-call syntax because all scripted function arguments are passed by value.
|
||||
|
||||
```rust
|
||||
fn do_action(x) { print(this.data + x); } // 'this' binds to the object when called
|
||||
|
||||
let obj = #{
|
||||
data: 40,
|
||||
action: Fn("do_action") // 'action' holds a function pointer to 'do_action'
|
||||
};
|
||||
|
||||
obj.action(2); // Short-hand syntax: prints 42
|
||||
|
||||
// To achieve the above with normal function pointer calls:
|
||||
fn do_action(map, x) { print(map.data + x); }
|
||||
|
||||
obj.action.call(obj, 2); // this call cannot mutate 'obj'
|
||||
```
|
@ -39,7 +39,7 @@ The index notation allows setting/getting properties of arbitrary names (even th
|
||||
Built-in Functions
|
||||
-----------------
|
||||
|
||||
The following methods (defined in the [`BasicMapPackage`]({{rootUrl}}/rust/packages.md) but excluded if using a [raw `Engine`])
|
||||
The following methods (defined in the [`BasicMapPackage`][packages] but excluded if using a [raw `Engine`])
|
||||
operate on object maps:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
@ -50,6 +50,7 @@ operate on object maps:
|
||||
| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) |
|
||||
| `+=` operator, `mixin` | second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) |
|
||||
| `+` operator | first object map, second object map | merges the first object map with the second |
|
||||
| `fill_with` | second object map | adds in all properties of the second object map that do not exist in the object map |
|
||||
| `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] |
|
||||
| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |
|
||||
|
||||
@ -61,7 +62,7 @@ Examples
|
||||
let y = #{ // object map literal with 3 properties
|
||||
a: 1,
|
||||
bar: "hello",
|
||||
"baz!$@": 123.456, // like JS, you can use any string as property names...
|
||||
"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
|
||||
|
44
doc/src/language/oop.md
Normal file
44
doc/src/language/oop.md
Normal file
@ -0,0 +1,44 @@
|
||||
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 |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
// Define the object
|
||||
let obj = #{
|
||||
data: 0,
|
||||
increment: Fn("add"), // when called, 'this' binds to 'obj'
|
||||
update: Fn("update"), // when called, 'this' binds to 'obj'
|
||||
action: Fn("action") // when called, 'this' binds to 'obj'
|
||||
};
|
||||
|
||||
// Define functions
|
||||
fn add(x) { this.data += x; } // update using 'this'
|
||||
fn update(x) { this.data = x; } // update using 'this'
|
||||
fn action() { print(this.data); } // access properties of 'this'
|
||||
|
||||
// Use the object
|
||||
obj.increment(1);
|
||||
obj.action(); // prints 1
|
||||
|
||||
obj.update(42);
|
||||
obj.action(); // prints 42
|
||||
```
|
@ -3,20 +3,27 @@ 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_
|
||||
[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."
|
||||
```
|
||||
|
@ -7,8 +7,11 @@ The `print` and `debug` functions default to printing to `stdout`, with `debug`
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
|
@ -3,8 +3,14 @@ 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.
|
||||
|
@ -3,13 +3,14 @@ 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.
|
||||
|
||||
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.
|
||||
If the last statement has no return value (e.g. variable definitions, assignments) then it is assumed to be [`()`].
|
||||
Semicolons can also be omitted if the statement contains a block itself
|
||||
(e.g. the `if`, `while`, `for` and `loop` statements).
|
||||
|
||||
```rust
|
||||
let a = 42; // normal assignment statement
|
||||
@ -20,6 +21,20 @@ let a = { 40 + 2 }; // 'a' is set to the value of the statement block, which
|
||||
// ^ 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
|
||||
|
||||
4 * 10 + 2 // a statement which is just one expression; no ending semicolon is OK
|
||||
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.
|
||||
|
||||
If the last statement has no return value (e.g. variable definitions, assignments) then it is assumed to be [`()`].
|
||||
|
@ -3,7 +3,7 @@ Built-in String Functions
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The following standard methods (mostly defined in the [`MoreStringPackage`]({{rootUrl}}/rust/packages.md) but excluded if
|
||||
The following standard methods (mostly defined in the [`MoreStringPackage`][packages] but excluded if
|
||||
using a [raw `Engine`]) operate on [strings]:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
|
@ -6,7 +6,7 @@ Strings and Characters
|
||||
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`]({{rootUrl}}/rust/packages.md)
|
||||
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"`.
|
||||
|
@ -3,12 +3,12 @@
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Timestamps are provided by the [`BasicTimePackage`]({{rootUrl}}/rust/packages.md) (excluded if using a [raw `Engine`])
|
||||
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`](https://crates.io/crates/instant) in [WASM] builds).
|
||||
The Rust type of a timestamp is `std::time::Instant` ([`instant::Instant`] in [WASM] builds).
|
||||
|
||||
[`type_of()`] a timestamp returns `"timestamp"`.
|
||||
|
||||
@ -16,7 +16,7 @@ The Rust type of a timestamp is `std::time::Instant` ([`instant::Instant`](https
|
||||
Built-in Functions
|
||||
-----------------
|
||||
|
||||
The following methods (defined in the [`BasicTimePackage`]({{rootUrl}}/rust/packages.md) but excluded if using a [raw `Engine`]) operate on timestamps:
|
||||
The following methods (defined in the [`BasicTimePackage`][packages] but excluded if using a [raw `Engine`]) operate on timestamps:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ----------------------------- | ---------------------------------- | -------------------------------------------------------- |
|
||||
|
@ -5,20 +5,21 @@ Values and Types
|
||||
|
||||
The following primitive types are supported natively:
|
||||
|
||||
| Category | Equivalent Rust types | [`type_of()`] | `to_string()` |
|
||||
| --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | --------------------- |
|
||||
| **Integer number** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. |
|
||||
| **Floating-point number** (disabled with [`no_float`]) | `f32`, `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<String>` or `Arc<String>`) | `"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`]({{rootUrl}}/rust/packages.md), disabled with [`no_std`]) | `std::time::Instant` ([instant::Instant](https://crates.io/crates/instant) if not [WASM] build) | `"timestamp"` | _not supported_ |
|
||||
| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _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)_ |
|
||||
| Category | Equivalent Rust types | [`type_of()`] | `to_string()` |
|
||||
| --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | ----------------------- |
|
||||
| **Integer number** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. |
|
||||
| **Floating-point number** (disabled with [`no_float`]) | `f32`, `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<String>` or `Arc<String>`) | `"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 not [WASM] build) | `"timestamp"` | _not supported_ |
|
||||
| **[Function pointer]** | _None_ | `Fn` | `"Fn(foo)"` |
|
||||
| **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _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.
|
||||
|
@ -3,17 +3,37 @@ Variables
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`').
|
||||
Valid Names
|
||||
-----------
|
||||
|
||||
Variable names must start with an ASCII letter or an underscore '`_`', must contain at least one ASCII letter,
|
||||
and must start with an ASCII letter before a digit.
|
||||
Variables in Rhai follow normal C naming rules - must contain only ASCII letters, digits and underscores '`_`',
|
||||
and cannot start with a digit.
|
||||
|
||||
Therefore, names like '`_`', '`_42`', '`3a`' etc. are not legal variable names, but '`_c3po`' and '`r2d2`' are.
|
||||
Variable names are also case _sensitive_.
|
||||
For example: '`_c3po`' and '`r2d2`' are valid variable names, but '`3abc`' is not.
|
||||
|
||||
Variables are defined using the `let` keyword. A variable defined within a statement block is _local_ to that block.
|
||||
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].
|
||||
|
||||
|
||||
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.
|
||||
|
||||
```rust
|
||||
let x; // ok - value is '()'
|
||||
let x = 3; // ok
|
||||
let _x = 42; // ok
|
||||
let x_ = 42; // also ok
|
||||
|
@ -3,6 +3,11 @@
|
||||
|
||||
{{#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;
|
||||
|
||||
|
@ -10,21 +10,24 @@
|
||||
[`no_function`]: {{rootUrl}}/start/features.md
|
||||
[`no_module`]: {{rootUrl}}/start/features.md
|
||||
[`no_std`]: {{rootUrl}}/start/features.md
|
||||
|
||||
[`no-std`]: {{rootUrl}}/start/features.md
|
||||
[`internals`]: {{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
|
||||
[`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.md
|
||||
[packages]: {{rootUrl}}/rust/packages.md
|
||||
[package]: {{rootUrl}}/rust/packages/index.md
|
||||
[packages]: {{rootUrl}}/rust/packages/index.md
|
||||
[`Scope`]: {{rootUrl}}/rust/scope.md
|
||||
|
||||
[`type_of()`]: {{rootUrl}}/language/type-of.md
|
||||
@ -37,10 +40,17 @@
|
||||
|
||||
[custom type]: {{rootUrl}}/rust/custom.md
|
||||
[custom types]: {{rootUrl}}/rust/custom.md
|
||||
[getters/setters]: {{rootUrl}}/rust/getters-setters.md
|
||||
[indexers]: {{rootUrl}}/rust/indexers.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
|
||||
|
||||
@ -62,15 +72,22 @@
|
||||
|
||||
[function]: {{rootUrl}}/language/functions.md
|
||||
[functions]: {{rootUrl}}/language/functions.md
|
||||
[function pointer]: {{rootUrl}}/language/fn-ptr.md
|
||||
[function pointers]: {{rootUrl}}/language/fn-ptr.md
|
||||
[function namespace]: {{rootUrl}}/language/fn-namespaces.md
|
||||
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md
|
||||
|
||||
[`Module`]: {{rootUrl}}/language/modules.md
|
||||
[module]: {{rootUrl}}/language/modules.md
|
||||
[modules]: {{rootUrl}}/language/modules.md
|
||||
[`Module`]: {{rootUrl}}/language/modules/index.md
|
||||
[module]: {{rootUrl}}/language/modules/index.md
|
||||
[modules]: {{rootUrl}}/language/modules/index.md
|
||||
[module resolver]: {{rootUrl}}/language/modules/imp-resolver.md
|
||||
[`export`]: {{rootUrl}}/language/modules/export.md
|
||||
[`import`]: {{rootUrl}}/language/modules/import.md
|
||||
|
||||
[`eval`]: {{rootUrl}}/language/eval.md
|
||||
|
||||
[OOP]: {{rootUrl}}/language/oop.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
|
||||
@ -78,9 +95,9 @@
|
||||
[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]:/safety/progress.md
|
||||
[progress]: {{rootUrl}}/safety/progress.md
|
||||
|
||||
[script optimization]: {{rootUrl}}/engine/optimize.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
|
||||
|
@ -1 +0,0 @@
|
||||
# Built-in Packages
|
@ -26,21 +26,16 @@ impl TestStruct {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<EvalAltResult>>
|
||||
{
|
||||
let engine = Engine::new();
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_type::<TestStruct>();
|
||||
engine.register_type::<TestStruct>();
|
||||
|
||||
engine.register_fn("update", TestStruct::update);
|
||||
engine.register_fn("new_ts", TestStruct::new);
|
||||
engine.register_fn("update", TestStruct::update);
|
||||
engine.register_fn("new_ts", TestStruct::new);
|
||||
|
||||
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
|
||||
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
|
||||
|
||||
println!("result: {}", result.field); // prints 42
|
||||
|
||||
Ok(())
|
||||
}
|
||||
println!("result: {}", result.field); // prints 42
|
||||
```
|
||||
|
||||
Register a Custom Type
|
||||
@ -66,7 +61,7 @@ impl TestStruct {
|
||||
}
|
||||
}
|
||||
|
||||
let engine = Engine::new();
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_type::<TestStruct>();
|
||||
```
|
||||
@ -102,15 +97,17 @@ println!("result: {}", result.field); // prints 42
|
||||
Method-Call Style vs. Function-Call Style
|
||||
----------------------------------------
|
||||
|
||||
In fact, 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.
|
||||
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
|
||||
fn foo(ts: &mut TestStruct) -> i64 {
|
||||
ts.field
|
||||
}
|
||||
|
||||
engine.register_fn("foo", foo); // register ad hoc function with correct signature
|
||||
engine.register_fn("foo", foo); // register a Rust native function
|
||||
|
||||
let result = engine.eval::<i64>(
|
||||
"let x = new_ts(); x.foo()" // 'foo' can be called like a method on 'x'
|
||||
@ -119,8 +116,8 @@ let result = engine.eval::<i64>(
|
||||
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.
|
||||
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' because 'clear' cannot be called in method style.
|
||||
|
@ -8,11 +8,6 @@ If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be re
|
||||
|
||||
The function must return `Result<Dynamic, Box<EvalAltResult>>`.
|
||||
|
||||
`Box<EvalAltResult>` implements `From<&str>` and `From<String>` etc.
|
||||
and the error text gets converted into `Box<EvalAltResult::ErrorRuntime>`.
|
||||
|
||||
The error values are `Box`-ed in order to reduce memory footprint of the error path, which should be hit rarely.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, EvalAltResult, Position};
|
||||
use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn'
|
||||
@ -27,15 +22,20 @@ fn safe_divide(x: i64, y: i64) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
}
|
||||
}
|
||||
|
||||
fn main()
|
||||
{
|
||||
let engine = Engine::new();
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Fallible functions that return Result values must use register_result_fn()
|
||||
engine.register_result_fn("divide", safe_divide);
|
||||
// Fallible functions that return Result values must use register_result_fn()
|
||||
engine.register_result_fn("divide", safe_divide);
|
||||
|
||||
if let Err(error) = engine.eval::<i64>("divide(40, 0)") {
|
||||
println!("Error: {:?}", *error); // prints ErrorRuntime("Division by zero detected!", (1, 1)")
|
||||
}
|
||||
if let Err(error) = engine.eval::<i64>("divide(40, 0)") {
|
||||
println!("Error: {:?}", *error); // prints ErrorRuntime("Division by zero detected!", (1, 1)")
|
||||
}
|
||||
```
|
||||
|
||||
Create a `Box<EvalAltResult>`
|
||||
----------------------------
|
||||
|
||||
`Box<EvalAltResult>` implements `From<&str>` and `From<String>` etc.
|
||||
and the error text gets converted into `Box<EvalAltResult::ErrorRuntime>`.
|
||||
|
||||
The error values are `Box`-ed in order to reduce memory footprint of the error path, which should be hit rarely.
|
||||
|
@ -29,30 +29,25 @@ fn get_any_value() -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
Ok((42_i64).into()) // standard types can use 'into()'
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<EvalAltResult>>
|
||||
{
|
||||
let engine = Engine::new();
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_fn("add", add_len);
|
||||
engine.register_fn("add_str", add_len_str);
|
||||
engine.register_fn("add", add_len);
|
||||
engine.register_fn("add_str", add_len_str);
|
||||
|
||||
let result = engine.eval::<i64>(r#"add(40, "xx")"#)?;
|
||||
let result = engine.eval::<i64>(r#"add(40, "xx")"#)?;
|
||||
|
||||
println!("Answer: {}", result); // prints 42
|
||||
println!("Answer: {}", result); // prints 42
|
||||
|
||||
let result = engine.eval::<i64>(r#"add_str(40, "xx")"#)?;
|
||||
let result = engine.eval::<i64>(r#"add_str(40, "xx")"#)?;
|
||||
|
||||
println!("Answer: {}", result); // prints 42
|
||||
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);
|
||||
// Functions that return Dynamic values must use register_result_fn()
|
||||
engine.register_result_fn("get_any_value", get_any_value);
|
||||
|
||||
let result = engine.eval::<i64>("get_any_value()")?;
|
||||
let result = engine.eval::<i64>("get_any_value()")?;
|
||||
|
||||
println!("Answer: {}", result); // prints 42
|
||||
|
||||
Ok(())
|
||||
}
|
||||
println!("Answer: {}", result); // prints 42
|
||||
```
|
||||
|
||||
To create a [`Dynamic`] value, use the `Dynamic::from` method.
|
||||
|
@ -17,14 +17,11 @@ fn show_it<T: Display>(x: &mut T) {
|
||||
println!("put up a good show: {}!", x)
|
||||
}
|
||||
|
||||
fn main()
|
||||
{
|
||||
let engine = Engine::new();
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_fn("print", show_it::<i64>);
|
||||
engine.register_fn("print", show_it::<bool>);
|
||||
engine.register_fn("print", show_it::<ImmutableString>);
|
||||
}
|
||||
engine.register_fn("print", show_it::<i64>);
|
||||
engine.register_fn("print", show_it::<bool>);
|
||||
engine.register_fn("print", show_it::<ImmutableString>);
|
||||
```
|
||||
|
||||
The above example shows how to register multiple functions
|
||||
|
@ -28,7 +28,7 @@ impl TestStruct {
|
||||
}
|
||||
}
|
||||
|
||||
let engine = Engine::new();
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_type::<TestStruct>();
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
Extend Rhai with Rust
|
||||
====================
|
||||
|
||||
{{#include links.md}}
|
||||
{{#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.
|
@ -9,6 +9,9 @@ A custom type with an indexer function defined can use the bracket '`[]`' notati
|
||||
|
||||
Indexers are disabled when the [`no_index`] feature is used.
|
||||
|
||||
For efficiency reasons, indexers **cannot** be used to overload (i.e. override) built-in indexing operations for
|
||||
[arrays] and [object maps].
|
||||
|
||||
```rust
|
||||
#[derive(Clone)]
|
||||
struct TestStruct {
|
||||
@ -28,7 +31,7 @@ impl TestStruct {
|
||||
}
|
||||
}
|
||||
|
||||
let engine = Engine::new();
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_type::<TestStruct>();
|
||||
|
||||
@ -42,6 +45,3 @@ let result = engine.eval::<i64>("let a = new_ts(); a[2] = 42; a[2]")?;
|
||||
|
||||
println!("Answer: {}", result); // prints 42
|
||||
```
|
||||
|
||||
For efficiency reasons, indexers **cannot** be used to overload (i.e. override) built-in indexing operations for
|
||||
[arrays] and [object maps].
|
||||
|
@ -17,6 +17,10 @@ Similarly, comparison operators including `==`, `!=` etc. are all implemented as
|
||||
with the stark exception of `&&` and `||`. 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
|
||||
@ -48,6 +52,10 @@ engine.register_fn("+", mixed_add); // register '+' operator for
|
||||
let result: i64 = engine.eval("1 + 1.0"); // prints 2.0 (normally an error)
|
||||
```
|
||||
|
||||
|
||||
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
|
||||
|
@ -18,6 +18,7 @@ Built-In Packages
|
||||
| `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 |
|
||||
| `EvalPackage` | Disable [`eval`] | No | No |
|
||||
| `CorePackage` | Basic essentials | Yes | Yes |
|
||||
| `StandardPackage` | Standard library (default for `Engine::new`) | No | Yes |
|
||||
|
@ -1,7 +1,7 @@
|
||||
Packages
|
||||
========
|
||||
|
||||
{{#include ../links.md}}
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Standard built-in Rhai features are provided in various _packages_ that can be loaded via a call to `Engine::load_package`.
|
||||
|
@ -3,14 +3,15 @@ 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`):
|
||||
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` | <code>\|s: &mut T\| -> ImmutableString</code> | `s.to_string().into()` | Converts the custom type into a [string] |
|
||||
| `print` | <code>\|s: &mut T\| -> ImmutableString</code> | `s.to_string().into()` | Converts the custom type into a [string] for the [`print`](#print-and-debug) statement |
|
||||
| `debug` | <code>\|s: &mut T\| -> ImmutableString</code> | `format!("{:?}", s).into()` | Converts the custom type into a [string] for the [`debug`](#print-and-debug) statement |
|
||||
| `print` | <code>\|s: &mut T\| -> ImmutableString</code> | `s.to_string().into()` | Converts the custom type into a [string] for the [`print`] statement |
|
||||
| `debug` | <code>\|s: &mut T\| -> ImmutableString</code> | `format!("{:?}", s).into()` | Converts the custom type into a [string] for the [`debug`] statement |
|
||||
| `+` | <code>\|s1: ImmutableString, s: T\| -> ImmutableString</code> | `s1 + s` | Append the custom type to another [string], for `print("Answer: " + type);` usage |
|
||||
| `+` | <code>\|s: T, s2: ImmutableString\| -> ImmutableString</code> | `s.to_string().push_str(&s2).into();` | Append another [string] to the custom type, for `print(type + " is the answer");` usage |
|
||||
| `+=` | <code>\|s1: &mut ImmutableString, s: T\|</code> | `s1 += s.to_string()` | Append the custom type to an existing [string], for `s += type;` usage |
|
||||
|
@ -20,40 +20,35 @@ then the same state is threaded through multiple invocations:
|
||||
```rust
|
||||
use rhai::{Engine, Scope, EvalAltResult};
|
||||
|
||||
fn main() -> Result<(), Box<EvalAltResult>>
|
||||
{
|
||||
let engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
// First create the state
|
||||
let mut scope = Scope::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);
|
||||
scope.push("z", 999_i64);
|
||||
// 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);
|
||||
scope.push("z", 999_i64);
|
||||
|
||||
// 'set_value' adds a variable when one doesn't exist
|
||||
scope.set_value("s", "hello, world!".to_string()); // remember to use 'String', not '&str'
|
||||
// 'set_value' adds a variable when one doesn't exist
|
||||
scope.set_value("s", "hello, world!".to_string()); // remember to use 'String', not '&str'
|
||||
|
||||
// First invocation
|
||||
engine.eval_with_scope::<()>(&mut scope, r"
|
||||
let x = 4 + 5 - y + z + s.len;
|
||||
y = 1;
|
||||
")?;
|
||||
// First invocation
|
||||
engine.eval_with_scope::<()>(&mut scope, r"
|
||||
let x = 4 + 5 - y + z + s.len;
|
||||
y = 1;
|
||||
")?;
|
||||
|
||||
// Second invocation using the same state
|
||||
let result = engine.eval_with_scope::<i64>(&mut scope, "x")?;
|
||||
// Second invocation using the same state
|
||||
let result = engine.eval_with_scope::<i64>(&mut scope, "x")?;
|
||||
|
||||
println!("result: {}", result); // prints 979
|
||||
println!("result: {}", result); // prints 979
|
||||
|
||||
// Variable y is changed in the script - read it with 'get_value'
|
||||
assert_eq!(scope.get_value::<i64>("y").expect("variable y should exist"), 1);
|
||||
// Variable y is changed in the script - read it with 'get_value'
|
||||
assert_eq!(scope.get_value::<i64>("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::<i64>("y").expect("variable y should exist"), 42);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// We can modify scope variables directly with 'set_value'
|
||||
scope.set_value("y", 42_i64);
|
||||
assert_eq!(scope.get_value::<i64>("y").expect("variable y should exist"), 42);
|
||||
```
|
||||
|
@ -1,31 +1,35 @@
|
||||
Safety and Protection Against DoS Attacks
|
||||
========================================
|
||||
|
||||
{{#include links.md}}
|
||||
{{#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 malicous script may continuously grow a [string], an [array] or [object map] until all memory is consumed.
|
||||
* **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 malicous script may run an infinite tight loop that consumes all CPU cycles.
|
||||
* **CPU**: A malicious script may run an infinite tight loop that consumes all CPU cycles.
|
||||
|
||||
* **Time**: A malicous script may run indefinitely, thereby blocking the calling system which is waiting for a result.
|
||||
* **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.
|
||||
|
||||
* **Stack**: A malicous 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.
|
||||
|
||||
* **Overflows**: A malicous script may deliberately cause numeric over-flows and/or under-flows, divide by zero, and/or
|
||||
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 malicous script may continuously [`import`] an external module within an infinite loop,
|
||||
* **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).
|
||||
Furthermore, the module script may simply [`import`] itself in an infinite recursion.
|
||||
|
||||
Even when modules are not created from files, they still typically consume a lot of resources to load.
|
||||
|
||||
* **Data**: A malicous script may attempt to read from and/or write to data that it does not own. If this happens,
|
||||
* **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.
|
@ -10,6 +10,8 @@ of modules to zero does _not_ indicate unlimited modules, but disallows loading
|
||||
|
||||
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).
|
||||
|
||||
|
@ -25,7 +25,7 @@ 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.
|
||||
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).
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
Special Builds
|
||||
==============
|
||||
|
||||
{{#include ../links.md}}
|
||||
{{#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.
|
@ -17,6 +17,16 @@ 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
|
||||
------------------
|
||||
|
||||
@ -28,13 +38,17 @@ Omitting arrays ([`no_index`]) yields the most code-size savings, followed by fl
|
||||
([`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`]) only when the feature is not needed because code size savings is minimal.
|
||||
Disable script-defined functions ([`no_function`]) when the feature is not needed.
|
||||
Both of these have little code size savings.
|
||||
|
||||
|
||||
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](#built-in-operators) of basic arithmetic and logical operators.
|
||||
[`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 loading specific [packages] to minimize the footprint.
|
||||
|
||||
Packages are sharable (even across threads via the [`sync`] feature), so they only have to be created once.
|
||||
|
@ -7,14 +7,17 @@ Use Only One Integer Type
|
||||
------------------------
|
||||
|
||||
Some features are for performance. For example, using [`only_i32`] or [`only_i64`] disables all other integer types (such as `u16`).
|
||||
|
||||
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, performance should improve.
|
||||
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.
|
||||
|
||||
|
||||
Use Only 32-Bit Numbers
|
||||
----------------------
|
||||
|
||||
If only 32-bit integers are needed - again, most of the time this is the case - using [`only_i32`] disables also `i64`.
|
||||
|
||||
On 64-bit targets this may not gain much, but on some 32-bit targets this improves performance due to 64-bit arithmetic
|
||||
requiring more CPU cycles to complete.
|
||||
|
||||
@ -24,4 +27,5 @@ Minimize Size of [`Dynamic`]
|
||||
|
||||
Turning on [`no_float`], and [`only_i32`] makes the key [`Dynamic`] data type only 8 bytes small on 32-bit targets
|
||||
while normally it can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`.
|
||||
|
||||
Making [`Dynamic`] small helps performance due to better cache efficiency.
|
||||
|
@ -14,8 +14,41 @@ 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.
|
||||
|
||||
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.
|
||||
|
||||
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`] | JavaScript has only one `number` type and we're only supporting `wasm32` here (so far). |
|
||||
| [`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. |
|
||||
|
@ -1,7 +1,7 @@
|
||||
Examples
|
||||
========
|
||||
|
||||
{{#include ../links.md}}
|
||||
{{#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.
|
@ -5,17 +5,17 @@ Rust Examples
|
||||
|
||||
A number of examples can be found in the `examples` folder:
|
||||
|
||||
| Example | Description |
|
||||
| ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | shows how to register a custom Rust type and using [arrays] on it |
|
||||
| [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | shows how to register a custom Rust type and methods for it |
|
||||
| [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | simple example that evaluates an expression and prints the result |
|
||||
| [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | example to test out `no-std` builds |
|
||||
| [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | evaluates two pieces of code in separate runs, but using a common [`Scope`] |
|
||||
| [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | runs each filename passed to it as a Rhai script |
|
||||
| [`simple_fn`](https://github.com/jonathandturner/rhai/tree/master/examples/simple_fn.rs) | shows how to register a simple function |
|
||||
| [`strings`](https://github.com/jonathandturner/rhai/tree/master/examples/strings.rs) | shows different ways to register functions taking string arguments |
|
||||
| [`repl`](https://github.com/jonathandturner/rhai/tree/master/examples/repl.rs) | a simple REPL, interactively evaluate statements from stdin |
|
||||
| Example | Description |
|
||||
| ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
| [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. |
|
||||
| [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. |
|
||||
| [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | Simple example that evaluates an expression and prints the result. |
|
||||
| [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | Example to test out `no-std` builds. |
|
||||
| [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. |
|
||||
| [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. |
|
||||
| [`simple_fn`](https://github.com/jonathandturner/rhai/tree/master/examples/simple_fn.rs) | Shows how to register a simple function. |
|
||||
| [`strings`](https://github.com/jonathandturner/rhai/tree/master/examples/strings.rs) | Shows different ways to register functions taking string arguments. |
|
||||
| [`repl`](https://github.com/jonathandturner/rhai/tree/master/examples/repl.rs) | A simple REPL, interactively evaluate statements from stdin. |
|
||||
|
||||
The `repl` example is a particularly good one as it allows one to interactively try out Rhai's
|
||||
language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop).
|
||||
|
@ -10,21 +10,22 @@ There are also a number of examples scripts that showcase Rhai's features, all i
|
||||
|
||||
| Script | Description |
|
||||
| -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
||||
| [`array.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/array.rhai) | [arrays] in Rhai |
|
||||
| [`assignment.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/assignment.rhai) | variable declarations |
|
||||
| [`comments.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/comments.rhai) | just comments |
|
||||
| [`array.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/array.rhai) | [Arrays] |
|
||||
| [`assignment.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/assignment.rhai) | Variable declarations |
|
||||
| [`comments.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/comments.rhai) | Just comments |
|
||||
| [`for1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for1.rhai) | [`for`](#for-loop) loops |
|
||||
| [`for2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for2.rhai) | [`for`](#for-loop) loops on [arrays] |
|
||||
| [`function_decl1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl1.rhai) | a [function] without parameters |
|
||||
| [`function_decl2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl2.rhai) | a [function] with two parameters |
|
||||
| [`function_decl3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl3.rhai) | a [function] with many parameters |
|
||||
| [`function_decl1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl1.rhai) | A [function] without parameters |
|
||||
| [`function_decl2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl2.rhai) | A [function] with two parameters |
|
||||
| [`function_decl3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl3.rhai) | A [function] with many parameters |
|
||||
| [`if1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/if1.rhai) | [`if`](#if-statement) example |
|
||||
| [`loop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/loop.rhai) | count-down [`loop`](#infinite-loop) in Rhai, emulating a `do` .. `while` loop |
|
||||
| [`op1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op1.rhai) | just simple addition |
|
||||
| [`op2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op2.rhai) | simple addition and multiplication |
|
||||
| [`op3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op3.rhai) | change evaluation order with parenthesis |
|
||||
| [`string.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/string.rhai) | [string] operations |
|
||||
| [`strings_map.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/strings_map.rhai) | [string] and [object map] operations |
|
||||
| [`loop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/loop.rhai) | Count-down [`loop`](#infinite-loop) in Rhai, emulating a `do` .. `while` loop |
|
||||
| [`oop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/oop.rhai) | Simulate [object-oriented programming (OOP)][OOP] |
|
||||
| [`op1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op1.rhai) | Just simple addition |
|
||||
| [`op2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op2.rhai) | Simple addition and multiplication |
|
||||
| [`op3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op3.rhai) | Change evaluation order with parenthesis |
|
||||
| [`string.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/string.rhai) | [String] operations |
|
||||
| [`strings_map.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/strings_map.rhai) | [String] and [object map] operations |
|
||||
| [`while.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/while.rhai) | [`while`](#while-loop) loop |
|
||||
|
||||
|
||||
@ -33,12 +34,12 @@ Benchmark Scripts
|
||||
|
||||
The following scripts are for benchmarking the speed of Rhai:
|
||||
|
||||
| Scripts | Description |
|
||||
| ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
|
||||
| [`speed_test.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/speed_test.rhai) | a simple program to measure the speed of Rhai's interpreter (1 million iterations) |
|
||||
| [`primes.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/primes.rhai) | use Sieve of Eratosthenes to find all primes smaller than a limit |
|
||||
| [`fibonacci.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/fibonacci.rhai) | calculate the n-th Fibonacci number using a really dumb algorithm |
|
||||
| [`mat_mul.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/mat_mul.rhai) | matrix multiplication test to measure the speed of multi-dimensional array access |
|
||||
| Scripts | Description |
|
||||
| ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
|
||||
| [`speed_test.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/speed_test.rhai) | A simple program to measure the speed of Rhai's interpreter (1 million iterations). |
|
||||
| [`primes.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/primes.rhai) | Use Sieve of Eratosthenes to find all primes smaller than a limit. |
|
||||
| [`fibonacci.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/fibonacci.rhai) | Calculate the n-th Fibonacci number using a really dumb algorithm. |
|
||||
| [`mat_mul.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/mat_mul.rhai) | Matrix multiplication test to measure the speed of multi-dimensional array access. |
|
||||
|
||||
|
||||
Running Example Scripts
|
||||
|
@ -4,24 +4,27 @@ 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.
|
||||
Excluding unneeded functionalities can result in smaller, faster builds as well as
|
||||
more control over what a script can (or cannot) do.
|
||||
|
||||
| Feature | Description |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `unchecked` | Disable 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` | Restrict 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` | Disable the script optimizer. |
|
||||
| `no_float` | Disable floating-point numbers and math. |
|
||||
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
|
||||
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
|
||||
| `no_index` | Disable [arrays] and indexing features. |
|
||||
| `no_object` | Disable support for custom types and [object maps]. |
|
||||
| `no_function` | Disable script-defined functions. |
|
||||
| `no_module` | Disable loading external modules. |
|
||||
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||
| Feature | Description |
|
||||
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `unchecked` | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.<br/>Beware that a bad script may panic the entire system! |
|
||||
| `sync` | Restrict 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` | Disable [script optimization]. |
|
||||
| `no_float` | Disable floating-point numbers and math. |
|
||||
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
|
||||
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
|
||||
| `no_index` | Disable [arrays] and indexing features. |
|
||||
| `no_object` | Disable support for [custom types] and [object maps]. |
|
||||
| `no_function` | Disable script-defined [functions]. |
|
||||
| `no_module` | Disable loading external [modules]. |
|
||||
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||
| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. |
|
||||
|
||||
|
||||
Example
|
||||
@ -30,19 +33,19 @@ Example
|
||||
The `Cargo.toml` configuration below turns on these six features:
|
||||
|
||||
* `sync` (everything `Send + Sync`)
|
||||
* `unchecked` (no checked arithmetic - should not be used with untrusted user scripts)
|
||||
* `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)
|
||||
* `no_module` (no loading external [modules])
|
||||
* `no_function` (no defining [functions])
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rhai = { version = "0.15.2", features = [ "sync", "unchecked", "only_i32", "no_float", "no_module", "no_function" ] }
|
||||
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` or `i16`),
|
||||
no floating-point, is `Send + Sync` (so it can be safely used across threads), does not support defining functions
|
||||
nor loading external modules.
|
||||
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), 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.
|
||||
|
@ -1,6 +1,6 @@
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
{{#include links.md}}
|
||||
{{#include ../links.md}}
|
||||
|
||||
This section shows how to install the Rhai crate into a Rust application.
|
@ -3,15 +3,17 @@ Install the Rhai Crate
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Install the Rhai crate from [`crates.io`](https:/crates.io/crates/rhai/) by adding this line
|
||||
under `dependencies` in `Cargo.toml`:
|
||||
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 = "0.15.2"
|
||||
rhai = "{{version}}" # assuming {{version}} is the latest version
|
||||
```
|
||||
|
||||
Use the latest released crate version on [`crates.io`](https:/crates.io/crates/rhai/):
|
||||
Or to automatically use the latest released crate version on [`crates.io`](https:/crates.io/crates/rhai/):
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
|
10
doc/src/start/playground.md
Normal file
10
doc/src/start/playground.md
Normal file
@ -0,0 +1,10 @@
|
||||
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.
|
@ -48,6 +48,7 @@ fn print_error(input: &str, err: EvalAltResult) {
|
||||
fn print_help() {
|
||||
println!("help => print this help");
|
||||
println!("quit, exit => quit");
|
||||
println!("scope => print all variables in the scope");
|
||||
println!("ast => print the last AST");
|
||||
println!("astu => print the last raw, un-optimized AST");
|
||||
println!(r"end a line with '\' to continue to the next line.");
|
||||
@ -110,6 +111,13 @@ fn main() {
|
||||
continue;
|
||||
}
|
||||
"exit" | "quit" => break, // quit
|
||||
"scope" => {
|
||||
scope
|
||||
.iter()
|
||||
.enumerate()
|
||||
.for_each(|(i, (name, value))| println!("[{}] {} = {:?}", i + 1, name, value));
|
||||
continue;
|
||||
}
|
||||
"astu" => {
|
||||
// print the last un-optimized AST
|
||||
println!("{:#?}", &ast_u);
|
||||
@ -158,7 +166,6 @@ fn main() {
|
||||
}
|
||||
|
||||
// Throw away all the statements, leaving only the functions
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
main_ast.retain_functions();
|
||||
main_ast.clear_statements();
|
||||
}
|
||||
}
|
||||
|
BIN
rhai_logo.png
Normal file
BIN
rhai_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@ -12,6 +12,6 @@ for a in arr {
|
||||
//print(a); // <- if you uncomment this line, the script will fail to run
|
||||
// because 'a' is not defined here
|
||||
|
||||
for i in range(0, 5) { // runs through a range from 1 to 5 exclusive
|
||||
for i in range(0, 5) { // runs through a range from 0 to 4
|
||||
print(i);
|
||||
}
|
||||
|
45
scripts/oop.rhai
Normal file
45
scripts/oop.rhai
Normal file
@ -0,0 +1,45 @@
|
||||
// This script simulates object-oriented programming (OOP) techniques
|
||||
// using function pointers (Fn) and object maps.
|
||||
|
||||
// Define object
|
||||
let obj1 = #{
|
||||
_data: 42, // data field
|
||||
get_data: Fn("getData"), // property getter
|
||||
action: Fn("action"), // method
|
||||
update: Fn("update1") // property setter
|
||||
};
|
||||
|
||||
fn getData() {
|
||||
this._data
|
||||
}
|
||||
fn action() {
|
||||
print("Data=" + this._data);
|
||||
}
|
||||
fn update1(x) {
|
||||
this._data = x;
|
||||
this.action();
|
||||
}
|
||||
|
||||
if obj1.get_data() > 0 { // property access
|
||||
obj1.update(123); // call method
|
||||
} else {
|
||||
print("we have a problem here");
|
||||
}
|
||||
|
||||
// Define another object based on the first object
|
||||
let obj2 = #{
|
||||
_data: 0, // data field - new value
|
||||
update: Fn("update2") // property setter - another function
|
||||
};
|
||||
obj2.fill_with(obj1); // add all other fields from obj1
|
||||
|
||||
fn update2(x) {
|
||||
this._data = x * 2;
|
||||
this.action();
|
||||
}
|
||||
|
||||
if obj2.get_data() > 0 { // property access
|
||||
obj2.update(0); // call method
|
||||
} else {
|
||||
obj2.update(42); // call method
|
||||
}
|
@ -10,8 +10,8 @@ print("foo" < "bar"); // string comparison
|
||||
print("foo" >= "bar"); // string comparison
|
||||
print("the answer is " + 42); // string building using non-string types
|
||||
|
||||
let s = "hello, world!"; // string variable
|
||||
print("length=" + s.len); // should be 13
|
||||
let s = "\u2764" hello, world! \U0001F603"; // string variable
|
||||
print("length=" + s.len); // should be 17
|
||||
|
||||
s[s.len-1] = '?'; // change the string
|
||||
s[s.len-3] = '?'; // change the string
|
||||
print("Question: " + s); // should print 'Question: hello, world?'
|
||||
|
155
src/any.rs
155
src/any.rs
@ -1,7 +1,6 @@
|
||||
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
|
||||
|
||||
use crate::fn_native::SendSync;
|
||||
use crate::module::Module;
|
||||
use crate::fn_native::{FnPtr, SendSync};
|
||||
use crate::parser::{ImmutableString, INT};
|
||||
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
|
||||
|
||||
@ -91,13 +90,13 @@ pub trait Variant: Any + Send + Sync {
|
||||
|
||||
impl<T: Any + Clone + SendSync> Variant for T {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self as &dyn Any
|
||||
self
|
||||
}
|
||||
fn as_mut_any(&mut self) -> &mut dyn Any {
|
||||
self as &mut dyn Any
|
||||
self
|
||||
}
|
||||
fn as_box_any(self: Box<Self>) -> Box<dyn Any> {
|
||||
self as Box<dyn Any>
|
||||
self
|
||||
}
|
||||
fn type_name(&self) -> &'static str {
|
||||
type_name::<T>()
|
||||
@ -138,7 +137,7 @@ pub enum Union {
|
||||
Array(Box<Array>),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Map(Box<Map>),
|
||||
Module(Box<Module>),
|
||||
FnPtr(FnPtr),
|
||||
Variant(Box<Box<dyn Variant>>),
|
||||
}
|
||||
|
||||
@ -175,7 +174,7 @@ impl Dynamic {
|
||||
Union::Array(_) => TypeId::of::<Array>(),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_) => TypeId::of::<Map>(),
|
||||
Union::Module(_) => TypeId::of::<Module>(),
|
||||
Union::FnPtr(_) => TypeId::of::<FnPtr>(),
|
||||
Union::Variant(value) => (***value).type_id(),
|
||||
}
|
||||
}
|
||||
@ -194,7 +193,7 @@ impl Dynamic {
|
||||
Union::Array(_) => "array",
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_) => "map",
|
||||
Union::Module(_) => "sub-scope",
|
||||
Union::FnPtr(_) => "Fn",
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -218,7 +217,7 @@ impl fmt::Display for Dynamic {
|
||||
Union::Array(value) => fmt::Debug::fmt(value, f),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => write!(f, "#{:?}", value),
|
||||
Union::Module(value) => fmt::Debug::fmt(value, f),
|
||||
Union::FnPtr(value) => fmt::Display::fmt(value, f),
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -242,7 +241,7 @@ impl fmt::Debug for Dynamic {
|
||||
Union::Array(value) => fmt::Debug::fmt(value, f),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => write!(f, "#{:?}", value),
|
||||
Union::Module(value) => fmt::Debug::fmt(value, f),
|
||||
Union::FnPtr(value) => fmt::Display::fmt(value, f),
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -266,7 +265,7 @@ impl Clone for Dynamic {
|
||||
Union::Array(ref value) => Self(Union::Array(value.clone())),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(ref value) => Self(Union::Map(value.clone())),
|
||||
Union::Module(ref value) => Self(Union::Module(value.clone())),
|
||||
Union::FnPtr(ref value) => Self(Union::FnPtr(value.clone())),
|
||||
Union::Variant(ref value) => (***value).clone_into_dynamic(),
|
||||
}
|
||||
}
|
||||
@ -311,58 +310,52 @@ impl Dynamic {
|
||||
/// assert_eq!(new_result.to_string(), "hello");
|
||||
/// ```
|
||||
pub fn from<T: Variant + Clone>(value: T) -> Self {
|
||||
let dyn_value = &value as &dyn Any;
|
||||
|
||||
if let Some(result) = dyn_value.downcast_ref::<()>().cloned().map(Union::Unit) {
|
||||
return Self(result);
|
||||
} else if let Some(result) = dyn_value.downcast_ref::<bool>().cloned().map(Union::Bool) {
|
||||
return Self(result);
|
||||
} else if let Some(result) = dyn_value.downcast_ref::<INT>().cloned().map(Union::Int) {
|
||||
return Self(result);
|
||||
} else if let Some(result) = dyn_value.downcast_ref::<char>().cloned().map(Union::Char) {
|
||||
return Self(result);
|
||||
} else if let Some(result) = dyn_value
|
||||
.downcast_ref::<ImmutableString>()
|
||||
.cloned()
|
||||
.map(Union::Str)
|
||||
{
|
||||
return Self(result);
|
||||
if let Some(result) = <dyn Any>::downcast_ref::<()>(&value) {
|
||||
return result.clone().into();
|
||||
} else if let Some(result) = <dyn Any>::downcast_ref::<bool>(&value) {
|
||||
return result.clone().into();
|
||||
} else if let Some(result) = <dyn Any>::downcast_ref::<INT>(&value) {
|
||||
return result.clone().into();
|
||||
} else if let Some(result) = <dyn Any>::downcast_ref::<char>(&value) {
|
||||
return result.clone().into();
|
||||
} else if let Some(result) = <dyn Any>::downcast_ref::<ImmutableString>(&value) {
|
||||
return result.clone().into();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
if let Some(result) = dyn_value.downcast_ref::<FLOAT>().cloned().map(Union::Float) {
|
||||
return Self(result);
|
||||
if let Some(result) = <dyn Any>::downcast_ref::<FLOAT>(&value) {
|
||||
return result.clone().into();
|
||||
}
|
||||
}
|
||||
|
||||
let mut var = Box::new(value);
|
||||
let mut boxed = Box::new(value);
|
||||
|
||||
var = match unsafe_cast_box::<_, Dynamic>(var) {
|
||||
boxed = match unsafe_cast_box::<_, Dynamic>(boxed) {
|
||||
Ok(d) => return *d,
|
||||
Err(var) => var,
|
||||
Err(val) => val,
|
||||
};
|
||||
var = match unsafe_cast_box::<_, String>(var) {
|
||||
Ok(s) => return Self(Union::Str(s.into())),
|
||||
Err(var) => var,
|
||||
boxed = match unsafe_cast_box::<_, String>(boxed) {
|
||||
Ok(s) => return (*s).into(),
|
||||
Err(val) => val,
|
||||
};
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
{
|
||||
var = match unsafe_cast_box::<_, Array>(var) {
|
||||
Ok(array) => return Self(Union::Array(array)),
|
||||
Err(var) => var,
|
||||
boxed = match unsafe_cast_box::<_, Array>(boxed) {
|
||||
Ok(array) => return (*array).into(),
|
||||
Err(val) => val,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
var = match unsafe_cast_box::<_, Map>(var) {
|
||||
Ok(map) => return Self(Union::Map(map)),
|
||||
Err(var) => var,
|
||||
boxed = match unsafe_cast_box::<_, Map>(boxed) {
|
||||
Ok(map) => return (*map).into(),
|
||||
Err(val) => val,
|
||||
}
|
||||
}
|
||||
|
||||
Self(Union::Variant(Box::new(var)))
|
||||
Self(Union::Variant(Box::new(boxed)))
|
||||
}
|
||||
|
||||
/// Get a copy of the `Dynamic` value as a specific type.
|
||||
@ -401,7 +394,7 @@ impl Dynamic {
|
||||
Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
Union::Module(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
|
||||
Union::FnPtr(value) => unsafe_try_cast(value),
|
||||
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
|
||||
}
|
||||
}
|
||||
@ -444,7 +437,7 @@ impl Dynamic {
|
||||
Union::Array(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
|
||||
Union::Module(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
|
||||
Union::FnPtr(value) => unsafe_try_cast(value).unwrap(),
|
||||
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).unwrap(),
|
||||
}
|
||||
}
|
||||
@ -454,24 +447,23 @@ impl Dynamic {
|
||||
/// Returns `None` if the cast fails.
|
||||
pub fn downcast_ref<T: Variant + Clone>(&self) -> Option<&T> {
|
||||
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
|
||||
return (self as &dyn Any).downcast_ref::<T>();
|
||||
return <dyn Any>::downcast_ref::<T>(self);
|
||||
}
|
||||
|
||||
match &self.0 {
|
||||
Union::Unit(value) => (value as &dyn Any).downcast_ref::<T>(),
|
||||
Union::Bool(value) => (value as &dyn Any).downcast_ref::<T>(),
|
||||
Union::Str(value) => (value as &dyn Any)
|
||||
.downcast_ref::<T>()
|
||||
.or_else(|| (value.as_ref() as &dyn Any).downcast_ref::<T>()),
|
||||
Union::Char(value) => (value as &dyn Any).downcast_ref::<T>(),
|
||||
Union::Int(value) => (value as &dyn Any).downcast_ref::<T>(),
|
||||
Union::Unit(value) => <dyn Any>::downcast_ref::<T>(value),
|
||||
Union::Bool(value) => <dyn Any>::downcast_ref::<T>(value),
|
||||
Union::Str(value) => <dyn Any>::downcast_ref::<T>(value)
|
||||
.or_else(|| <dyn Any>::downcast_ref::<T>(value.as_ref())),
|
||||
Union::Char(value) => <dyn Any>::downcast_ref::<T>(value),
|
||||
Union::Int(value) => <dyn Any>::downcast_ref::<T>(value),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Union::Float(value) => (value as &dyn Any).downcast_ref::<T>(),
|
||||
Union::Float(value) => <dyn Any>::downcast_ref::<T>(value),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
|
||||
Union::Array(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
|
||||
Union::Module(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
|
||||
Union::Map(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
|
||||
Union::FnPtr(value) => <dyn Any>::downcast_ref::<T>(value),
|
||||
Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(),
|
||||
}
|
||||
}
|
||||
@ -481,22 +473,22 @@ impl Dynamic {
|
||||
/// Returns `None` if the cast fails.
|
||||
pub fn downcast_mut<T: Variant + Clone>(&mut self) -> Option<&mut T> {
|
||||
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
|
||||
return (self as &mut dyn Any).downcast_mut::<T>();
|
||||
return <dyn Any>::downcast_mut::<T>(self);
|
||||
}
|
||||
|
||||
match &mut self.0 {
|
||||
Union::Unit(value) => (value as &mut dyn Any).downcast_mut::<T>(),
|
||||
Union::Bool(value) => (value as &mut dyn Any).downcast_mut::<T>(),
|
||||
Union::Str(value) => (value as &mut dyn Any).downcast_mut::<T>(),
|
||||
Union::Char(value) => (value as &mut dyn Any).downcast_mut::<T>(),
|
||||
Union::Int(value) => (value as &mut dyn Any).downcast_mut::<T>(),
|
||||
Union::Unit(value) => <dyn Any>::downcast_mut::<T>(value),
|
||||
Union::Bool(value) => <dyn Any>::downcast_mut::<T>(value),
|
||||
Union::Str(value) => <dyn Any>::downcast_mut::<T>(value),
|
||||
Union::Char(value) => <dyn Any>::downcast_mut::<T>(value),
|
||||
Union::Int(value) => <dyn Any>::downcast_mut::<T>(value),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Union::Float(value) => (value as &mut dyn Any).downcast_mut::<T>(),
|
||||
Union::Float(value) => <dyn Any>::downcast_mut::<T>(value),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
|
||||
Union::Array(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
|
||||
Union::Module(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
|
||||
Union::Map(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
|
||||
Union::FnPtr(value) => <dyn Any>::downcast_mut::<T>(value),
|
||||
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(),
|
||||
}
|
||||
}
|
||||
@ -543,6 +535,7 @@ impl Dynamic {
|
||||
pub fn as_str(&self) -> Result<&str, &'static str> {
|
||||
match &self.0 {
|
||||
Union::Str(s) => Ok(s),
|
||||
Union::FnPtr(f) => Ok(f.fn_name()),
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
@ -550,8 +543,16 @@ impl Dynamic {
|
||||
/// Convert the `Dynamic` into `String` and return it.
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
pub fn take_string(self) -> Result<String, &'static str> {
|
||||
self.take_immutable_string()
|
||||
.map(ImmutableString::into_owned)
|
||||
}
|
||||
|
||||
/// Convert the `Dynamic` into `ImmutableString` and return it.
|
||||
/// Returns the name of the actual type if the cast fails.
|
||||
pub(crate) fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
|
||||
match self.0 {
|
||||
Union::Str(s) => Ok(s.into_owned()),
|
||||
Union::Str(s) => Ok(s),
|
||||
Union::FnPtr(f) => Ok(f.take_fn_name()),
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
@ -583,16 +584,11 @@ impl From<char> for Dynamic {
|
||||
Self(Union::Char(value))
|
||||
}
|
||||
}
|
||||
impl From<String> for Dynamic {
|
||||
fn from(value: String) -> Self {
|
||||
impl<S: Into<ImmutableString>> From<S> for Dynamic {
|
||||
fn from(value: S) -> Self {
|
||||
Self(Union::Str(value.into()))
|
||||
}
|
||||
}
|
||||
impl From<ImmutableString> for Dynamic {
|
||||
fn from(value: ImmutableString) -> Self {
|
||||
Self(Union::Str(value))
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
impl<T: Variant + Clone> From<Vec<T>> for Dynamic {
|
||||
fn from(value: Vec<T>) -> Self {
|
||||
@ -610,16 +606,21 @@ impl<T: Variant + Clone> From<&[T]> for Dynamic {
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
impl<T: Variant + Clone> From<HashMap<String, T>> for Dynamic {
|
||||
fn from(value: HashMap<String, T>) -> Self {
|
||||
impl<K: Into<ImmutableString>, T: Variant + Clone> From<HashMap<K, T>> for Dynamic {
|
||||
fn from(value: HashMap<K, T>) -> Self {
|
||||
Self(Union::Map(Box::new(
|
||||
value
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, Dynamic::from(v)))
|
||||
.map(|(k, v)| (k.into(), Dynamic::from(v)))
|
||||
.collect(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
impl From<FnPtr> for Dynamic {
|
||||
fn from(value: FnPtr) -> Self {
|
||||
Self(Union::FnPtr(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// Private type which ensures that `rhai::Any` and `rhai::AnyExt` can only
|
||||
/// be implemented by this crate.
|
||||
|
72
src/api.rs
72
src/api.rs
@ -2,8 +2,8 @@
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::engine::{
|
||||
get_script_function_by_signature, make_getter, make_setter, Engine, State, FUNC_INDEXER_GET,
|
||||
FUNC_INDEXER_SET,
|
||||
get_script_function_by_signature, make_getter, make_setter, Engine, Imports, State, FN_IDX_GET,
|
||||
FN_IDX_SET,
|
||||
};
|
||||
use crate::error::ParseError;
|
||||
use crate::fn_call::FuncArgs;
|
||||
@ -323,7 +323,7 @@ impl Engine {
|
||||
U: Variant + Clone,
|
||||
X: Variant + Clone,
|
||||
{
|
||||
self.register_fn(FUNC_INDEXER_GET, callback);
|
||||
self.register_fn(FN_IDX_GET, callback);
|
||||
}
|
||||
|
||||
/// Register an index setter for a registered type with the `Engine`.
|
||||
@ -371,7 +371,7 @@ impl Engine {
|
||||
U: Variant + Clone,
|
||||
X: Variant + Clone,
|
||||
{
|
||||
self.register_fn(FUNC_INDEXER_SET, callback);
|
||||
self.register_fn(FN_IDX_SET, callback);
|
||||
}
|
||||
|
||||
/// Shorthand for register both index getter and setter functions for a registered type with the `Engine`.
|
||||
@ -422,7 +422,7 @@ impl Engine {
|
||||
self.register_indexer_set(setter);
|
||||
}
|
||||
|
||||
/// Compile a string into an `AST`, which can be used later for evaluation.
|
||||
/// Compile a string into an [`AST`], which can be used later for evaluation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -445,7 +445,7 @@ impl Engine {
|
||||
self.compile_with_scope(&Scope::new(), script)
|
||||
}
|
||||
|
||||
/// Compile a string into an `AST` using own scope, which can be used later for evaluation.
|
||||
/// Compile a string into an [`AST`] using own scope, which can be used later for evaluation.
|
||||
///
|
||||
/// The scope is useful for passing constants into the script for optimization
|
||||
/// when using `OptimizationLevel::Full`.
|
||||
@ -488,7 +488,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
/// When passed a list of strings, first join the strings into one large script,
|
||||
/// and then compile them into an `AST` using own scope, which can be used later for evaluation.
|
||||
/// and then compile them into an [`AST`] using own scope, which can be used later for evaluation.
|
||||
///
|
||||
/// The scope is useful for passing constants into the script for optimization
|
||||
/// when using `OptimizationLevel::Full`.
|
||||
@ -541,7 +541,7 @@ impl Engine {
|
||||
self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level)
|
||||
}
|
||||
|
||||
/// Join a list of strings and compile into an `AST` using own scope at a specific optimization level.
|
||||
/// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level.
|
||||
pub(crate) fn compile_with_scope_and_optimization_level(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
@ -577,7 +577,7 @@ impl Engine {
|
||||
Ok(contents)
|
||||
}
|
||||
|
||||
/// Compile a script file into an `AST`, which can be used later for evaluation.
|
||||
/// Compile a script file into an [`AST`], which can be used later for evaluation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -603,7 +603,7 @@ impl Engine {
|
||||
self.compile_file_with_scope(&Scope::new(), path)
|
||||
}
|
||||
|
||||
/// Compile a script file into an `AST` using own scope, which can be used later for evaluation.
|
||||
/// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation.
|
||||
///
|
||||
/// The scope is useful for passing constants into the script for optimization
|
||||
/// when using `OptimizationLevel::Full`.
|
||||
@ -685,7 +685,7 @@ impl Engine {
|
||||
self.eval_ast_with_scope(&mut scope, &ast)
|
||||
}
|
||||
|
||||
/// Compile a string containing an expression into an `AST`,
|
||||
/// Compile a string containing an expression into an [`AST`],
|
||||
/// which can be used later for evaluation.
|
||||
///
|
||||
/// # Example
|
||||
@ -709,7 +709,7 @@ impl Engine {
|
||||
self.compile_expression_with_scope(&Scope::new(), script)
|
||||
}
|
||||
|
||||
/// Compile a string containing an expression into an `AST` using own scope,
|
||||
/// Compile a string containing an expression into an [`AST`] using own scope,
|
||||
/// which can be used later for evaluation.
|
||||
///
|
||||
/// The scope is useful for passing constants into the script for optimization
|
||||
@ -917,7 +917,7 @@ impl Engine {
|
||||
self.eval_ast_with_scope(scope, &ast)
|
||||
}
|
||||
|
||||
/// Evaluate an `AST`.
|
||||
/// Evaluate an [`AST`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -939,7 +939,7 @@ impl Engine {
|
||||
self.eval_ast_with_scope(&mut Scope::new(), ast)
|
||||
}
|
||||
|
||||
/// Evaluate an `AST` with own scope.
|
||||
/// Evaluate an [`AST`] with own scope.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -973,7 +973,8 @@ impl Engine {
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
let (result, _) = self.eval_ast_with_scope_raw(scope, ast)?;
|
||||
let mut mods = Imports::new();
|
||||
let (result, _) = self.eval_ast_with_scope_raw(scope, &mut mods, ast)?;
|
||||
|
||||
let return_type = self.map_type_name(result.type_name());
|
||||
|
||||
@ -985,18 +986,19 @@ impl Engine {
|
||||
});
|
||||
}
|
||||
|
||||
/// Evaluate an `AST` with own scope.
|
||||
pub(crate) fn eval_ast_with_scope_raw(
|
||||
/// Evaluate an [`AST`] with own scope.
|
||||
pub(crate) fn eval_ast_with_scope_raw<'a>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
mods: &mut Imports,
|
||||
ast: &'a AST,
|
||||
) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
|
||||
let mut state = State::new();
|
||||
|
||||
ast.statements()
|
||||
.iter()
|
||||
.try_fold(().into(), |_, stmt| {
|
||||
self.eval_stmt(scope, &mut state, ast.lib(), stmt, 0)
|
||||
self.eval_stmt(scope, mods, &mut state, ast.lib(), &mut None, stmt, 0)
|
||||
})
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::Return(out, _) => Ok(out),
|
||||
@ -1050,7 +1052,7 @@ impl Engine {
|
||||
self.consume_ast_with_scope(&mut Scope::new(), ast)
|
||||
}
|
||||
|
||||
/// Evaluate an `AST` with own scope, but throw away the result and only return error (if any).
|
||||
/// Evaluate an [`AST`] with own scope, but throw away the result and only return error (if any).
|
||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||
pub fn consume_ast_with_scope(
|
||||
&self,
|
||||
@ -1058,11 +1060,12 @@ impl Engine {
|
||||
ast: &AST,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
let mut state = State::new();
|
||||
let mut mods = Default::default();
|
||||
|
||||
ast.statements()
|
||||
.iter()
|
||||
.try_fold(().into(), |_, stmt| {
|
||||
self.eval_stmt(scope, &mut state, ast.lib(), stmt, 0)
|
||||
self.eval_stmt(scope, &mut mods, &mut state, ast.lib(), &mut None, stmt, 0)
|
||||
})
|
||||
.map_or_else(
|
||||
|err| match *err {
|
||||
@ -1073,7 +1076,7 @@ impl Engine {
|
||||
)
|
||||
}
|
||||
|
||||
/// Call a script function defined in an `AST` with multiple arguments.
|
||||
/// Call a script function defined in an [`AST`] with multiple arguments.
|
||||
/// Arguments are passed as a tuple.
|
||||
///
|
||||
/// # Example
|
||||
@ -1130,7 +1133,7 @@ impl Engine {
|
||||
});
|
||||
}
|
||||
|
||||
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments.
|
||||
/// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -1176,7 +1179,7 @@ impl Engine {
|
||||
self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut())
|
||||
}
|
||||
|
||||
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments.
|
||||
/// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments.
|
||||
///
|
||||
/// ## WARNING
|
||||
///
|
||||
@ -1201,20 +1204,31 @@ impl Engine {
|
||||
})?;
|
||||
|
||||
let mut state = State::new();
|
||||
let mut mods = Imports::new();
|
||||
let args = args.as_mut();
|
||||
|
||||
self.call_script_fn(scope, &mut state, ast.lib(), name, fn_def, args, 0)
|
||||
self.call_script_fn(
|
||||
scope,
|
||||
&mut mods,
|
||||
&mut state,
|
||||
ast.lib(),
|
||||
&mut None,
|
||||
name,
|
||||
fn_def,
|
||||
args,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
/// Optimize the `AST` with constants defined in an external Scope.
|
||||
/// An optimized copy of the `AST` is returned while the original `AST` is consumed.
|
||||
/// Optimize the [`AST`] with constants defined in an external Scope.
|
||||
/// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed.
|
||||
///
|
||||
/// Although optimization is performed by default during compilation, sometimes it is necessary to
|
||||
/// _re_-optimize an AST. For example, when working with constants that are passed in via an
|
||||
/// external scope, it will be more efficient to optimize the `AST` once again to take advantage
|
||||
/// external scope, it will be more efficient to optimize the [`AST`] once again to take advantage
|
||||
/// of the new constants.
|
||||
///
|
||||
/// With this method, it is no longer necessary to recompile a large script. The script `AST` can be
|
||||
/// With this method, it is no longer necessary to recompile a large script. The script [`AST`] can be
|
||||
/// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope
|
||||
/// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
|
1021
src/engine.rs
1021
src/engine.rs
File diff suppressed because it is too large
Load Diff
10
src/error.rs
10
src/error.rs
@ -3,10 +3,17 @@
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::token::Position;
|
||||
|
||||
use crate::stdlib::{boxed::Box, char, error::Error, fmt, string::{String, ToString}};
|
||||
use crate::stdlib::{
|
||||
boxed::Box,
|
||||
char,
|
||||
error::Error,
|
||||
fmt,
|
||||
string::{String, ToString},
|
||||
};
|
||||
|
||||
/// Error when tokenizing the script text.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum LexError {
|
||||
/// An unexpected character is encountered when tokenizing the script text.
|
||||
UnexpectedChar(char),
|
||||
@ -60,6 +67,7 @@ impl LexError {
|
||||
/// They still exist so that the application can turn features on and off without going through
|
||||
/// massive code changes to remove/add back enum variants in match statements.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum ParseErrorType {
|
||||
/// Error in the script text. Wrapped value is the error message.
|
||||
BadInput(String),
|
||||
|
@ -15,8 +15,8 @@ use crate::stdlib::{boxed::Box, string::ToString};
|
||||
pub trait Func<ARGS, RET> {
|
||||
type Output;
|
||||
|
||||
/// Create a Rust anonymous function from an `AST`.
|
||||
/// The `Engine` and `AST` are consumed and basically embedded into the closure.
|
||||
/// Create a Rust anonymous function from an [`AST`].
|
||||
/// The `Engine` and [`AST`] are consumed and basically embedded into the closure.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -2,6 +2,7 @@ use crate::any::Dynamic;
|
||||
use crate::engine::Engine;
|
||||
use crate::parser::ScriptFnDef;
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::utils::ImmutableString;
|
||||
|
||||
use crate::stdlib::{boxed::Box, fmt, rc::Rc, sync::Arc};
|
||||
|
||||
@ -53,8 +54,41 @@ pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
|
||||
|
||||
pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
||||
|
||||
/// A general function pointer.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
|
||||
pub struct FnPtr(ImmutableString);
|
||||
|
||||
impl FnPtr {
|
||||
/// Get the name of the function.
|
||||
pub fn fn_name(&self) -> &str {
|
||||
self.get_fn_name().as_ref()
|
||||
}
|
||||
/// Get the name of the function.
|
||||
pub(crate) fn get_fn_name(&self) -> &ImmutableString {
|
||||
&self.0
|
||||
}
|
||||
/// Get the name of the function.
|
||||
pub(crate) fn take_fn_name(self) -> ImmutableString {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FnPtr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Fn({})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Into<ImmutableString>> From<S> for FnPtr {
|
||||
fn from(value: S) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// A general function trail object.
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub type FnAny = dyn Fn(&Engine, &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
|
||||
/// A general function trail object.
|
||||
#[cfg(feature = "sync")]
|
||||
pub type FnAny =
|
||||
dyn Fn(&Engine, &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
|
||||
@ -94,6 +128,17 @@ impl fmt::Debug for CallableFunction {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CallableFunction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Pure(_) => write!(f, "NativePureFunction"),
|
||||
Self::Method(_) => write!(f, "NativeMethod"),
|
||||
Self::Iterator(_) => write!(f, "NativeIterator"),
|
||||
CallableFunction::Script(s) => fmt::Display::fmt(s, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CallableFunction {
|
||||
/// Is this a pure native Rust function?
|
||||
pub fn is_pure(&self) -> bool {
|
||||
@ -102,7 +147,7 @@ impl CallableFunction {
|
||||
Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false,
|
||||
}
|
||||
}
|
||||
/// Is this a pure native Rust method-call?
|
||||
/// Is this a native Rust method function?
|
||||
pub fn is_method(&self) -> bool {
|
||||
match self {
|
||||
Self::Method(_) => true,
|
||||
|
28
src/lib.rs
28
src/lib.rs
@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! Rhai is a tiny, simple and very fast embedded scripting language for Rust
|
||||
//! that gives you a safe and easy way to add scripting to your applications.
|
||||
//! It provides a familiar syntax based on JS and Rust and a simple Rust interface.
|
||||
//! It provides a familiar syntax based on JavaScript and Rust and a simple Rust interface.
|
||||
//! Here is a quick example.
|
||||
//!
|
||||
//! First, the contents of `my_script.rhai`:
|
||||
@ -62,9 +62,10 @@
|
||||
//! | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
|
||||
//! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
|
||||
//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
|
||||
//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and `AST` are all `Send + Sync`. |
|
||||
//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and [`AST`] are all `Send + Sync`. |
|
||||
//! | `internals` | Expose internal data structures (beware they may be volatile from version to version). |
|
||||
//!
|
||||
//! See [The Rhai Book](https://schungx.github.io/rhai/) for details on the Rhai script engine and language.
|
||||
//! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language.
|
||||
|
||||
#![cfg_attr(feature = "no_std", no_std)]
|
||||
|
||||
@ -102,6 +103,9 @@ pub use scope::Scope;
|
||||
pub use token::Position;
|
||||
pub use utils::calc_fn_spec as calc_fn_hash;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use parser::FnAccess;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use fn_func::Func;
|
||||
|
||||
@ -125,3 +129,21 @@ pub mod module_resolvers {
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
pub use optimize::OptimizationLevel;
|
||||
|
||||
// Expose internal data structures.
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated(note = "this type is volatile and may change")]
|
||||
pub use token::{get_next_token, parse_string_literal, InputStream, Token, TokenizeState};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated(note = "this type is volatile and may change")]
|
||||
pub use parser::{Expr, ReturnType, ScriptFnDef, Stmt};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated(note = "this type is volatile and may change")]
|
||||
pub use module::ModuleRef;
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated(note = "this type is volatile and may change")]
|
||||
pub use utils::StaticVec;
|
||||
|
288
src/module.rs
288
src/module.rs
@ -2,28 +2,30 @@
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::calc_fn_hash;
|
||||
use crate::engine::{make_getter, make_setter, Engine, FUNC_INDEXER_GET, FUNC_INDEXER_SET};
|
||||
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync};
|
||||
use crate::engine::{make_getter, make_setter, Engine, Imports, FN_IDX_GET, FN_IDX_SET};
|
||||
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync, Shared};
|
||||
use crate::parser::{
|
||||
FnAccess,
|
||||
FnAccess::{Private, Public},
|
||||
ScriptFnDef, AST,
|
||||
};
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
||||
use crate::scope::{Entry as ScopeEntry, Scope};
|
||||
use crate::token::{Position, Token};
|
||||
use crate::utils::{StaticVec, StraightHasherBuilder};
|
||||
|
||||
use crate::stdlib::{
|
||||
any::TypeId,
|
||||
boxed::Box,
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
fmt, format,
|
||||
iter::empty,
|
||||
mem,
|
||||
num::NonZeroUsize,
|
||||
ops::{Deref, DerefMut},
|
||||
string::{String, ToString},
|
||||
sync::RwLock,
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
@ -35,7 +37,7 @@ pub type FuncReturn<T> = Result<T, Box<EvalAltResult>>;
|
||||
/// external Rust functions, and script-defined functions.
|
||||
///
|
||||
/// Not available under the `no_module` feature.
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Default)]
|
||||
pub struct Module {
|
||||
/// Sub-modules.
|
||||
modules: HashMap<String, Module>,
|
||||
@ -59,19 +61,47 @@ pub struct Module {
|
||||
/// Flattened collection of all external Rust functions, native or scripted,
|
||||
/// including those in sub-modules.
|
||||
all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>,
|
||||
|
||||
/// Is the module indexed?
|
||||
indexed: bool,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Module {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"<module vars={:?}, functions={}>",
|
||||
self.variables,
|
||||
self.functions.len(),
|
||||
"Module(\n modules: {}\n vars: {}\n functions: {}\n)",
|
||||
self.modules
|
||||
.keys()
|
||||
.map(|k| k.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
self.variables
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{}={:?}", k, v))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
self.functions
|
||||
.values()
|
||||
.map(|(_, _, _, f)| f.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Module {
|
||||
fn clone(&self) -> Self {
|
||||
// Only clone the index at the top level
|
||||
Self {
|
||||
all_variables: self.all_variables.clone(),
|
||||
all_functions: self.all_functions.clone(),
|
||||
indexed: self.indexed,
|
||||
..self.do_clone(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Module {
|
||||
/// Create a new module.
|
||||
///
|
||||
@ -106,6 +136,24 @@ impl Module {
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone the module, optionally skipping the index.
|
||||
fn do_clone(&self, clone_index: bool) -> Self {
|
||||
Self {
|
||||
modules: if clone_index {
|
||||
self.modules.clone()
|
||||
} else {
|
||||
self.modules
|
||||
.iter()
|
||||
.map(|(k, m)| (k.clone(), m.do_clone(clone_index)))
|
||||
.collect()
|
||||
},
|
||||
variables: self.variables.clone(),
|
||||
functions: self.functions.clone(),
|
||||
type_iterators: self.type_iterators.clone(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Does a variable exist in the module?
|
||||
///
|
||||
/// # Examples
|
||||
@ -166,20 +214,23 @@ impl Module {
|
||||
/// ```
|
||||
pub fn set_var(&mut self, name: impl Into<String>, value: impl Variant + Clone) {
|
||||
self.variables.insert(name.into(), Dynamic::from(value));
|
||||
self.indexed = false;
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a modules-qualified variable.
|
||||
/// Name and Position in `EvalAltResult` are None and must be set afterwards.
|
||||
///
|
||||
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
|
||||
pub(crate) fn get_qualified_var_mut(
|
||||
&mut self,
|
||||
name: &str,
|
||||
hash_var: u64,
|
||||
pos: Position,
|
||||
) -> Result<&mut Dynamic, Box<EvalAltResult>> {
|
||||
self.all_variables
|
||||
.get_mut(&hash_var)
|
||||
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.to_string(), pos)))
|
||||
self.all_variables.get_mut(&hash_var).ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorVariableNotFound(
|
||||
String::new(),
|
||||
Position::none(),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Set a script-defined function into the module.
|
||||
@ -197,6 +248,7 @@ impl Module {
|
||||
fn_def.into(),
|
||||
),
|
||||
);
|
||||
self.indexed = false;
|
||||
}
|
||||
|
||||
/// Does a sub-module exist in the module?
|
||||
@ -263,6 +315,7 @@ impl Module {
|
||||
/// ```
|
||||
pub fn set_sub_module(&mut self, name: impl Into<String>, sub_module: Module) {
|
||||
self.modules.insert(name.into(), sub_module.into());
|
||||
self.indexed = false;
|
||||
}
|
||||
|
||||
/// Does the particular Rust function exist in the module?
|
||||
@ -302,6 +355,8 @@ impl Module {
|
||||
self.functions
|
||||
.insert(hash_fn, (name, access, params, func.into()));
|
||||
|
||||
self.indexed = false;
|
||||
|
||||
hash_fn
|
||||
}
|
||||
|
||||
@ -557,7 +612,7 @@ impl Module {
|
||||
&mut self,
|
||||
func: impl Fn(&mut A, B) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
self.set_fn_2_mut(FUNC_INDEXER_GET, func)
|
||||
self.set_fn_2_mut(FN_IDX_GET, func)
|
||||
}
|
||||
|
||||
/// Set a Rust function taking three parameters into the module, returning a hash key.
|
||||
@ -673,7 +728,7 @@ impl Module {
|
||||
};
|
||||
let args = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<A>()];
|
||||
self.set_fn(
|
||||
FUNC_INDEXER_SET,
|
||||
FN_IDX_SET,
|
||||
Public,
|
||||
&args,
|
||||
CallableFunction::from_method(Box::new(f)),
|
||||
@ -786,17 +841,17 @@ impl Module {
|
||||
}
|
||||
|
||||
/// Get a modules-qualified function.
|
||||
/// Name and Position in `EvalAltResult` are None and must be set afterwards.
|
||||
///
|
||||
/// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match
|
||||
/// the hash calculated by `index_all_sub_modules`.
|
||||
pub(crate) fn get_qualified_fn(
|
||||
&mut self,
|
||||
name: &str,
|
||||
hash_qualified_fn: u64,
|
||||
) -> Result<&CallableFunction, Box<EvalAltResult>> {
|
||||
self.all_functions.get(&hash_qualified_fn).ok_or_else(|| {
|
||||
Box::new(EvalAltResult::ErrorFunctionNotFound(
|
||||
name.to_string(),
|
||||
String::new(),
|
||||
Position::none(),
|
||||
))
|
||||
})
|
||||
@ -804,12 +859,53 @@ impl Module {
|
||||
|
||||
/// Merge another module into this module.
|
||||
pub fn merge(&mut self, other: &Self) {
|
||||
self.merge_filtered(other, |_, _, _| true)
|
||||
}
|
||||
|
||||
/// Merge another module into this module, with only selected functions based on a filter predicate.
|
||||
pub(crate) fn merge_filtered(
|
||||
&mut self,
|
||||
other: &Self,
|
||||
filter: impl Fn(FnAccess, &str, usize) -> bool,
|
||||
) {
|
||||
self.variables
|
||||
.extend(other.variables.iter().map(|(k, v)| (k.clone(), v.clone())));
|
||||
self.functions
|
||||
.extend(other.functions.iter().map(|(&k, v)| (k, v.clone())));
|
||||
|
||||
self.functions.extend(
|
||||
other
|
||||
.functions
|
||||
.iter()
|
||||
.filter(|(_, (_, _, _, v))| match v {
|
||||
CallableFunction::Pure(_)
|
||||
| CallableFunction::Method(_)
|
||||
| CallableFunction::Iterator(_) => true,
|
||||
CallableFunction::Script(ref f) => {
|
||||
filter(f.access, f.name.as_str(), f.params.len())
|
||||
}
|
||||
})
|
||||
.map(|(&k, v)| (k, v.clone())),
|
||||
);
|
||||
|
||||
self.type_iterators
|
||||
.extend(other.type_iterators.iter().map(|(&k, v)| (k, v.clone())));
|
||||
|
||||
self.all_functions.clear();
|
||||
self.all_variables.clear();
|
||||
self.indexed = false;
|
||||
}
|
||||
|
||||
/// Filter out the functions, retaining only some based on a filter predicate.
|
||||
pub(crate) fn retain_functions(&mut self, filter: impl Fn(FnAccess, &str, usize) -> bool) {
|
||||
self.functions.retain(|_, (_, _, _, v)| match v {
|
||||
CallableFunction::Pure(_)
|
||||
| CallableFunction::Method(_)
|
||||
| CallableFunction::Iterator(_) => true,
|
||||
CallableFunction::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()),
|
||||
});
|
||||
|
||||
self.all_functions.clear();
|
||||
self.all_variables.clear();
|
||||
self.indexed = false;
|
||||
}
|
||||
|
||||
/// Get the number of variables in the module.
|
||||
@ -837,7 +933,16 @@ impl Module {
|
||||
self.functions.values()
|
||||
}
|
||||
|
||||
/// Create a new `Module` by evaluating an `AST`.
|
||||
/// Get an iterator over all script-defined functions in the module.
|
||||
pub fn iter_script_fn<'a>(&'a self) -> impl Iterator<Item = Shared<ScriptFnDef>> + 'a {
|
||||
self.functions
|
||||
.values()
|
||||
.map(|(_, _, _, f)| f)
|
||||
.filter(|f| f.is_script())
|
||||
.map(|f| f.get_shared_fn_def())
|
||||
}
|
||||
|
||||
/// Create a new `Module` by evaluating an [`AST`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -855,32 +960,27 @@ impl Module {
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub fn eval_ast_as_new(mut scope: Scope, ast: &AST, engine: &Engine) -> FuncReturn<Self> {
|
||||
let mut mods = Imports::new();
|
||||
|
||||
// Run the script
|
||||
engine.eval_ast_with_scope_raw(&mut scope, &ast)?;
|
||||
engine.eval_ast_with_scope_raw(&mut scope, &mut mods, &ast)?;
|
||||
|
||||
// Create new module
|
||||
let mut module = Module::new();
|
||||
|
||||
scope.into_iter().for_each(
|
||||
|ScopeEntry {
|
||||
typ, value, alias, ..
|
||||
}| {
|
||||
match typ {
|
||||
// Variables with an alias left in the scope become module variables
|
||||
ScopeEntryType::Normal | ScopeEntryType::Constant if alias.is_some() => {
|
||||
module.variables.insert(*alias.unwrap(), value);
|
||||
}
|
||||
// Modules left in the scope become sub-modules
|
||||
ScopeEntryType::Module if alias.is_some() => {
|
||||
module
|
||||
.modules
|
||||
.insert(*alias.unwrap(), value.cast::<Module>());
|
||||
}
|
||||
// Variables and modules with no alias are private and not exported
|
||||
_ => (),
|
||||
scope
|
||||
.into_iter()
|
||||
.for_each(|ScopeEntry { value, alias, .. }| {
|
||||
// Variables with an alias left in the scope become module variables
|
||||
if alias.is_some() {
|
||||
module.variables.insert(*alias.unwrap(), value);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// Modules left in the scope become sub-modules
|
||||
mods.into_iter().for_each(|(alias, m)| {
|
||||
module.modules.insert(alias.to_string(), m);
|
||||
});
|
||||
|
||||
module.merge(ast.lib());
|
||||
|
||||
@ -945,6 +1045,10 @@ impl Module {
|
||||
}
|
||||
}
|
||||
|
||||
if self.indexed {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut variables = Vec::new();
|
||||
let mut functions = Vec::new();
|
||||
|
||||
@ -952,6 +1056,7 @@ impl Module {
|
||||
|
||||
self.all_variables = variables.into_iter().collect();
|
||||
self.all_functions = functions.into_iter().collect();
|
||||
self.indexed = true;
|
||||
}
|
||||
|
||||
/// Does a type iterator exist in the module?
|
||||
@ -962,6 +1067,7 @@ impl Module {
|
||||
/// Set a type iterator into the module.
|
||||
pub fn set_iter(&mut self, typ: TypeId, func: IteratorFn) {
|
||||
self.type_iterators.insert(typ, func);
|
||||
self.indexed = false;
|
||||
}
|
||||
|
||||
/// Get the specified type iterator.
|
||||
@ -1055,6 +1161,8 @@ mod file {
|
||||
|
||||
/// Module resolution service that loads module script files from the file system.
|
||||
///
|
||||
/// Script files are cached so they are are not reloaded and recompiled in subsequent requests.
|
||||
///
|
||||
/// The `new_with_path` and `new_with_path_and_extension` constructor functions
|
||||
/// allow specification of a base directory with module path used as a relative path offset
|
||||
/// to the base directory. The script file is then forced to be in a specified extension
|
||||
@ -1073,10 +1181,16 @@ mod file {
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
/// ```
|
||||
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Clone, Hash)]
|
||||
#[derive(Debug)]
|
||||
pub struct FileModuleResolver {
|
||||
path: PathBuf,
|
||||
extension: String,
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
cache: RefCell<HashMap<PathBuf, AST>>,
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
cache: RwLock<HashMap<PathBuf, AST>>,
|
||||
}
|
||||
|
||||
impl Default for FileModuleResolver {
|
||||
@ -1129,6 +1243,7 @@ mod file {
|
||||
Self {
|
||||
path: path.into(),
|
||||
extension: extension.into(),
|
||||
cache: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1173,12 +1288,45 @@ mod file {
|
||||
file_path.push(path);
|
||||
file_path.set_extension(&self.extension); // Force extension
|
||||
|
||||
// Compile it
|
||||
let ast = engine
|
||||
.compile_file(file_path)
|
||||
.map_err(|err| err.new_position(pos))?;
|
||||
let scope = Default::default();
|
||||
|
||||
Module::eval_ast_as_new(Scope::new(), &ast, engine).map_err(|err| err.new_position(pos))
|
||||
// See if it is cached
|
||||
let (module, ast) = {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
let c = self.cache.borrow();
|
||||
#[cfg(feature = "sync")]
|
||||
let c = self.cache.read().unwrap();
|
||||
|
||||
match c.get(&file_path) {
|
||||
Some(ast) => (
|
||||
Module::eval_ast_as_new(scope, ast, engine)
|
||||
.map_err(|err| err.new_position(pos))?,
|
||||
None,
|
||||
),
|
||||
None => {
|
||||
// Load the file and compile it if not found
|
||||
let ast = engine
|
||||
.compile_file(file_path.clone())
|
||||
.map_err(|err| err.new_position(pos))?;
|
||||
|
||||
(
|
||||
Module::eval_ast_as_new(scope, &ast, engine)
|
||||
.map_err(|err| err.new_position(pos))?,
|
||||
Some(ast),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(ast) = ast {
|
||||
// Put it into the cache
|
||||
#[cfg(not(feature = "sync"))]
|
||||
self.cache.borrow_mut().insert(file_path, ast);
|
||||
#[cfg(feature = "sync")]
|
||||
self.cache.write().unwrap().insert(file_path, ast);
|
||||
}
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1222,7 +1370,7 @@ mod stat {
|
||||
/// let mut resolver = StaticModuleResolver::new();
|
||||
///
|
||||
/// let module = Module::new();
|
||||
/// resolver.insert("hello".to_string(), module);
|
||||
/// resolver.insert("hello", module);
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
@ -1232,17 +1380,43 @@ mod stat {
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for StaticModuleResolver {
|
||||
type Target = HashMap<String, Module>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
impl StaticModuleResolver {
|
||||
/// Add a module keyed by its path.
|
||||
pub fn insert<S: Into<String>>(&mut self, path: S, mut module: Module) {
|
||||
module.index_all_sub_modules();
|
||||
self.0.insert(path.into(), module);
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for StaticModuleResolver {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
/// Remove a module given its path.
|
||||
pub fn remove(&mut self, path: &str) -> Option<Module> {
|
||||
self.0.remove(path)
|
||||
}
|
||||
/// Does the path exist?
|
||||
pub fn contains_path(&self, path: &str) -> bool {
|
||||
self.0.contains_key(path)
|
||||
}
|
||||
/// Get an iterator of all the modules.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&str, &Module)> {
|
||||
self.0.iter().map(|(k, v)| (k.as_str(), v))
|
||||
}
|
||||
/// Get a mutable iterator of all the modules.
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Module)> {
|
||||
self.0.iter_mut().map(|(k, v)| (k.as_str(), v))
|
||||
}
|
||||
/// Get an iterator of all the module paths.
|
||||
pub fn paths(&self) -> impl Iterator<Item = &str> {
|
||||
self.0.keys().map(String::as_str)
|
||||
}
|
||||
/// Get an iterator of all the modules.
|
||||
pub fn values(&self) -> impl Iterator<Item = &Module> {
|
||||
self.0.values()
|
||||
}
|
||||
/// Get a mutable iterator of all the modules.
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Module> {
|
||||
self.0.values_mut()
|
||||
}
|
||||
/// Remove all modules.
|
||||
pub fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::any::Dynamic;
|
||||
use crate::calc_fn_hash;
|
||||
use crate::engine::{Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
||||
use crate::engine::{Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
||||
use crate::module::Module;
|
||||
use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST};
|
||||
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
||||
@ -122,12 +122,14 @@ fn call_fn_with_constant_arguments(
|
||||
.engine
|
||||
.call_fn_raw(
|
||||
&mut Scope::new(),
|
||||
&mut Imports::new(),
|
||||
&mut Default::default(),
|
||||
state.lib,
|
||||
fn_name,
|
||||
(hash_fn, 0),
|
||||
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
0,
|
||||
)
|
||||
@ -408,7 +410,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||
// All other items can be thrown away.
|
||||
state.set_dirty();
|
||||
let pos = m.1;
|
||||
m.0.into_iter().find(|((name, _), _)| name == prop)
|
||||
m.0.into_iter().find(|((name, _), _)| name.as_str() == prop)
|
||||
.map(|(_, expr)| expr.set_position(pos))
|
||||
.unwrap_or_else(|| Expr::Unit(pos))
|
||||
}
|
||||
@ -434,7 +436,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||
// All other items can be thrown away.
|
||||
state.set_dirty();
|
||||
let pos = m.1;
|
||||
m.0.into_iter().find(|((name, _), _)| name == s.0.as_ref())
|
||||
m.0.into_iter().find(|((name, _), _)| *name == s.0)
|
||||
.map(|(_, expr)| expr.set_position(pos))
|
||||
.unwrap_or_else(|| Expr::Unit(pos))
|
||||
}
|
||||
@ -472,7 +474,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||
// "xxx" in #{...}
|
||||
(Expr::StringConstant(a), Expr::Map(b)) => {
|
||||
state.set_dirty();
|
||||
if b.0.iter().find(|((name, _), _)| name == a.0.as_ref()).is_some() {
|
||||
if b.0.iter().find(|((name, _), _)| *name == a.0).is_some() {
|
||||
Expr::True(a.1)
|
||||
} else {
|
||||
Expr::False(a.1)
|
||||
@ -483,7 +485,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||
state.set_dirty();
|
||||
let ch = a.0.to_string();
|
||||
|
||||
if b.0.iter().find(|((name, _), _)| name == &ch).is_some() {
|
||||
if b.0.iter().find(|((name, _), _)| name.as_str() == ch.as_str()).is_some() {
|
||||
Expr::True(a.1)
|
||||
} else {
|
||||
Expr::False(a.1)
|
||||
@ -628,7 +630,7 @@ fn optimize(
|
||||
|
||||
// Add constants from the scope into the state
|
||||
scope
|
||||
.iter()
|
||||
.to_iter()
|
||||
.filter(|ScopeEntry { typ, expr, .. }| {
|
||||
// Get all the constants with definite constant expressions
|
||||
*typ == ScopeEntryType::Constant
|
||||
|
@ -267,6 +267,19 @@ macro_rules! reg_op {
|
||||
$( $lib.set_fn_2($op, $func::<$par>); )*
|
||||
};
|
||||
}
|
||||
macro_rules! reg_sign {
|
||||
($lib:expr, $op:expr, $ret:ty, $($par:ty),*) => {
|
||||
$( $lib.set_fn_1($op, |value: $par| -> Result<$ret, _> {
|
||||
Ok(if value == (0 as $par) {
|
||||
(0 as $ret)
|
||||
} else if value < (0 as $par) {
|
||||
(-1 as $ret)
|
||||
} else {
|
||||
(1 as $ret)
|
||||
})
|
||||
}); )*
|
||||
};
|
||||
}
|
||||
|
||||
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
@ -321,6 +334,11 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
|
||||
reg_op!(lib, "%", modulo_u, i128, u128);
|
||||
}
|
||||
}
|
||||
|
||||
reg_sign!(lib, "sign", INT, i8, i16, i32, i64);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
reg_sign!(lib, "sign", INT, i128);
|
||||
}
|
||||
|
||||
// Basic arithmetic for floating-point - no need to check
|
||||
@ -330,6 +348,8 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
|
||||
reg_op!(lib, "-", sub_u, f32);
|
||||
reg_op!(lib, "*", mul_u, f32);
|
||||
reg_op!(lib, "/", div_u, f32);
|
||||
reg_sign!(lib, "sign", f32, f32);
|
||||
reg_sign!(lib, "sign", f64, f64);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
|
@ -41,12 +41,12 @@ fn pad<T: Variant + Clone>(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncRe
|
||||
}
|
||||
}
|
||||
|
||||
if len >= 0 {
|
||||
let item = args[2].downcast_ref::<T>().unwrap().clone();
|
||||
if len > 0 {
|
||||
let item = args[2].clone();
|
||||
let list = args[0].downcast_mut::<Array>().unwrap();
|
||||
|
||||
while list.len() < len as usize {
|
||||
push(list, item.clone())?;
|
||||
if len as usize > list.len() {
|
||||
list.resize(len as usize, item);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
8
src/packages/fn_basic.rs
Normal file
8
src/packages/fn_basic.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use crate::def_package;
|
||||
use crate::fn_native::FnPtr;
|
||||
|
||||
def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
|
||||
lib.set_fn_1_mut("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone()));
|
||||
lib.set_getter_fn("name", |f: &mut FnPtr| Ok(f.get_fn_name().clone()));
|
||||
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user