Merge pull request #172 from schungx/oop

OOP support
This commit is contained in:
Stephen Chung 2020-07-01 09:08:25 +08:00 committed by GitHub
commit 3b16fae455
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
116 changed files with 3524 additions and 1900 deletions

View File

@ -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" ]

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -0,0 +1,6 @@
Appendix
========
{{#include ../links.md}}
This section contains miscellaneous reference materials.

View 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 | |

View 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 | `()` |

View 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 |

View File

@ -1,4 +1,5 @@
{
"version": "0.16.0",
"rootUrl": "",
"rootUrlX": "/rhai"
}

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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()'
```

View File

@ -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
--------

View File

@ -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

View File

@ -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"
```

View File

@ -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 {

View File

@ -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
```

View 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
View 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);
```

View File

@ -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);
}

View File

@ -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
```

View File

@ -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
```

View File

@ -1,7 +1,7 @@
Rhai Language Reference
======================
{{#include links.md}}
{{#include ../links.md}}
This section outlines the Rhai language.

View File

@ -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`].

View File

@ -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]:

View File

@ -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.

View File

@ -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'
```

View File

@ -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).

View File

@ -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.

View File

@ -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();
"#)?;
```

View File

@ -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();
```

View File

@ -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.

View File

@ -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.

View File

@ -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 |

View File

@ -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

View 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'
```

View File

@ -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
View 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
```

View File

@ -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."
```

View File

@ -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
```

View File

@ -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.

View File

@ -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 [`()`].

View File

@ -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 |

View File

@ -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"`.

View File

@ -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 |
| ----------------------------- | ---------------------------------- | -------------------------------------------------------- |

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -1 +0,0 @@
# Built-in Packages

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -28,7 +28,7 @@ impl TestStruct {
}
}
let engine = Engine::new();
let mut engine = Engine::new();
engine.register_type::<TestStruct>();

View File

@ -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.

View File

@ -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].

View File

@ -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

View File

@ -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 |

View File

@ -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`.

View File

@ -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 |

View File

@ -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);
```

View File

@ -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.

View File

@ -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).

View File

@ -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).

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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. |

View File

@ -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.

View File

@ -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).

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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]

View 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.

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -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
View 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
}

View File

@ -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?'

View File

@ -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.

View File

@ -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"))]

File diff suppressed because it is too large Load Diff

View File

@ -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),

View File

@ -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
///

View File

@ -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,

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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

View File

@ -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"))]

View File

@ -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
View 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