Merge pull request #242 from schungx/master

FileModuleResolver that encapsulates module namespace.
This commit is contained in:
Stephen Chung 2020-09-24 23:43:00 +08:00 committed by GitHub
commit 90cdf170a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 687 additions and 372 deletions

View File

@ -25,7 +25,9 @@ New features
* Plugins support via procedural macros. * Plugins support via procedural macros.
* Scripted functions are allowed in packages. * Scripted functions are allowed in packages.
* `parse_int` and `parse_float` functions. * `parse_int` and `parse_float` functions.
* `AST::iter_functions` and `Module::iter_script_fn_info` to iterate functions.
* Functions iteration functions now take `FnMut` instead of `Fn`.
* New `FileModuleResolver` that encapsulates the entire `AST` of the module script, allowing function cross-calling. The old version is renamed `MergingFileModuleResolver`.
Version 0.18.3 Version 0.18.3
============== ==============

View File

@ -5,34 +5,34 @@ Keywords List
| Keyword | Description | Inactive under | Overloadable | | Keyword | Description | Inactive under | Overloadable |
| :-------------------: | ---------------------------------------- | :-------------: | :----------: | | :-------------------: | ---------------------------------------- | :-------------: | :----------: |
| `true` | Boolean true literal | | No | | `true` | boolean true literal | | no |
| `false` | Boolean false literal | | No | | `false` | boolean false literal | | no |
| `let` | Variable declaration | | No | | `let` | variable declaration | | no |
| `const` | Constant declaration | | No | | `const` | constant declaration | | no |
| `is_shared` | Is a value shared? | | No | | `is_shared` | is a value shared? | | no |
| `if` | If statement | | No | | `if` | if statement | | no |
| `else` | else block of if statement | | No | | `else` | else block of if statement | | no |
| `while` | While loop | | No | | `while` | while loop | | no |
| `loop` | Infinite loop | | No | | `loop` | infinite loop | | no |
| `for` | For loop | | No | | `for` | for loop | | no |
| `in` | Containment test, part of for loop | | No | | `in` | containment test, part of for loop | | no |
| `continue` | Continue a loop at the next iteration | | No | | `continue` | continue a loop at the next iteration | | no |
| `break` | Loop breaking | | No | | `break` | loop breaking | | no |
| `return` | Return value | | No | | `return` | return value | | no |
| `throw` | Throw exception | | No | | `throw` | throw exception | | no |
| `import` | Import module | [`no_module`] | No | | `import` | import module | [`no_module`] | no |
| `export` | Export variable | [`no_module`] | No | | `export` | export variable | [`no_module`] | no |
| `as` | Alias for variable export | [`no_module`] | No | | `as` | alias for variable export | [`no_module`] | no |
| `private` | Mark function private | [`no_function`] | No | | `private` | mark function private | [`no_function`] | no |
| `fn` (lower-case `f`) | Function definition | [`no_function`] | No | | `fn` (lower-case `f`) | function definition | [`no_function`] | no |
| `Fn` (capital `F`) | Function to create a [function pointer] | | Yes | | `Fn` (capital `F`) | function to create a [function pointer] | | yes |
| `call` | Call a [function pointer] | | No | | `call` | call a [function pointer] | | no |
| `curry` | Curry a [function pointer] | | No | | `curry` | curry a [function pointer] | | no |
| `this` | Reference to base object for method call | [`no_function`] | No | | `this` | reference to base object for method call | [`no_function`] | no |
| `type_of` | Get type name of value | | Yes | | `type_of` | get type name of value | | yes |
| `print` | Print value | | Yes | | `print` | print value | | yes |
| `debug` | Print value in debug format | | Yes | | `debug` | print value in debug format | | yes |
| `eval` | Evaluate script | | Yes | | `eval` | evaluate script | | yes |
Reserved Keywords Reserved Keywords
@ -40,32 +40,32 @@ Reserved Keywords
| Keyword | Potential usage | | Keyword | Potential usage |
| --------- | --------------------- | | --------- | --------------------- |
| `var` | Variable declaration | | `var` | variable declaration |
| `static` | Variable declaration | | `static` | variable declaration |
| `shared` | Share value | | `shared` | share value |
| `do` | Looping | | `do` | looping |
| `each` | Looping | | `each` | looping |
| `then` | Control flow | | `then` | control flow |
| `goto` | Control flow | | `goto` | control flow |
| `exit` | Control flow | | `exit` | control flow |
| `switch` | Matching | | `switch` | matching |
| `match` | Matching | | `match` | matching |
| `case` | Matching | | `case` | matching |
| `public` | Function/field access | | `public` | function/field access |
| `new` | Constructor | | `new` | constructor |
| `try` | Trap exception | | `try` | trap exception |
| `catch` | Catch exception | | `catch` | catch exception |
| `use` | Import namespace | | `use` | import namespace |
| `with` | Scope | | `with` | scope |
| `module` | Module | | `module` | module |
| `package` | Package | | `package` | package |
| `spawn` | Threading | | `spawn` | threading |
| `go` | Threading | | `go` | threading |
| `await` | Async | | `await` | async |
| `async` | Async | | `async` | async |
| `sync` | Async | | `sync` | async |
| `yield` | Async | | `yield` | async |
| `default` | Special value | | `default` | special value |
| `void` | Special value | | `void` | special value |
| `null` | Special value | | `null` | special value |
| `nil` | Special value | | `nil` | special value |

View File

@ -9,29 +9,28 @@ Operators
| Operator | Description | Binary? | Binding direction | | Operator | Description | Binary? | Binding direction |
| :---------------: | ------------------------------ | :-----: | :---------------: | | :---------------: | ------------------------------ | :-----: | :---------------: |
| `+` | Add | Yes | Left | | `+` | add | yes | left |
| `-` | Subtract, Minus | Yes/No | Left | | `-` | subtract, Minus | yes/no | left |
| `*` | Multiply | Yes | Left | | `*` | multiply | yes | left |
| `/` | Divide | Yes | Left | | `/` | divide | yes | left |
| `%` | Modulo | Yes | Left | | `%` | modulo | yes | left |
| `~` | Power | Yes | Left | | `~` | power | yes | left |
| `>>` | Right bit-shift | Yes | Left | | `>>` | right bit-shift | yes | left |
| `<<` | Left bit-shift | Yes | Left | | `<<` | left bit-shift | yes | left |
| `&` | Bit-wise _And_, Boolean _And_ | Yes | Left | | `&` | bit-wise _And_, boolean _And_ | yes | left |
| <code>\|</code> | Bit-wise _Or_, Boolean _Or_ | Yes | Left | | <code>\|</code> | bit-wise _Or_, boolean _Or_ | yes | left |
| `^` | Bit-wise _Xor_, Boolean _Xor_ | Yes | Left | | `^` | bit-wise _Xor_, boolean _Xor_ | yes | left |
| `==` | Equals to | Yes | Left | | `==` | equals to | yes | left |
| `~=` | Not equals to | Yes | Left | | `~=` | not equals to | yes | left |
| `>` | Greater than | Yes | Left | | `>` | greater than | yes | left |
| `>=` | Greater than or equals to | Yes | Left | | `>=` | greater than or equals to | yes | left |
| `<` | Less than | Yes | Left | | `<` | less than | yes | left |
| `<=` | Less than or equals to | Yes | Left | | `<=` | less than or equals to | yes | left |
| `>=` | Greater than or equals to | Yes | Left | | `&&` | boolean _And_ (short-circuits) | yes | left |
| `&&` | Boolean _And_ (short-circuits) | Yes | Left | | <code>\|\|</code> | boolean _Or_ (short-circuits) | yes | left |
| <code>\|\|</code> | Boolean _Or_ (short-circuits) | Yes | Left | | `!` | boolean _Not_ | no | left |
| `!` | Boolean _Not_ | No | Left | | `[` .. `]` | indexing | yes | right |
| `[` .. `]` | Indexing | Yes | Right | | `.` | property access, method call | yes | right |
| `.` | Property access, Method call | Yes | Right |
Symbols Symbols
@ -39,8 +38,8 @@ Symbols
| Symbol | Description | | Symbol | Description |
| ------------ | ------------------------ | | ------------ | ------------------------ |
| `:` | Property value separator | | `:` | property value separator |
| `::` | Module path separator | | `::` | module path separator |
| `#` | _Reserved_ | | `#` | _Reserved_ |
| `=>` | _Reserved_ | | `=>` | _Reserved_ |
| `->` | _Reserved_ | | `->` | _Reserved_ |

View File

@ -5,14 +5,14 @@ Engine Configuration Options
A number of other configuration options are available from the `Engine` to fine-tune behavior and safeguards. A number of other configuration options are available from the `Engine` to fine-tune behavior and safeguards.
| Method | Not available under | Description | | Method | Not available under | Description |
| ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `set_optimization_level` | [`no_optimize`] | Set the amount of script _optimizations_ performed. See [script optimization]. | | `set_optimization_level` | [`no_optimize`] | sets the amount of script _optimizations_ performed. See [script optimization]. |
| `set_max_expr_depths` | [`unchecked`] | Set the maximum nesting levels of an expression/statement. See [maximum statement depth]. | | `set_max_expr_depths` | [`unchecked`] | sets the maximum nesting levels of an expression/statement. See [maximum statement depth]. |
| `set_max_call_levels` | [`unchecked`] | Set the maximum number of function call levels (default 50) to avoid infinite recursion. See [maximum call stack depth]. | | `set_max_call_levels` | [`unchecked`] | sets the maximum number of function call levels (default 50) to avoid infinite recursion. See [maximum call stack depth]. |
| `set_max_operations` | [`unchecked`] | Set the maximum number of _operations_ that a script is allowed to consume. See [maximum number of operations]. | | `set_max_operations` | [`unchecked`] | sets the maximum number of _operations_ that a script is allowed to consume. See [maximum number of operations]. |
| `set_max_modules` | [`unchecked`] | Set the maximum number of [modules] that a script is allowed to load. See [maximum number of modules]. | | `set_max_modules` | [`unchecked`] | sets the maximum number of [modules] that a script is allowed to load. See [maximum number of modules]. |
| `set_max_string_size` | [`unchecked`] | Set the maximum length (in UTF-8 bytes) for [strings]. See [maximum length of strings]. | | `set_max_string_size` | [`unchecked`] | sets the maximum length (in UTF-8 bytes) for [strings]. See [maximum length of strings]. |
| `set_max_array_size` | [`unchecked`], [`no_index`] | Set the maximum size for [arrays]. See [maximum size of arrays]. | | `set_max_array_size` | [`unchecked`], [`no_index`] | sets the maximum size for [arrays]. See [maximum size of arrays]. |
| `set_max_map_size` | [`unchecked`], [`no_object`] | Set the maximum number of properties for [object maps]. See [maximum size of object maps]. | | `set_max_map_size` | [`unchecked`], [`no_object`] | sets the maximum number of properties for [object maps]. See [maximum size of object maps]. |
| `disable_symbol` | | Disable a certain keyword or operator. See [disable keywords and operators]. | | `disable_symbol` | | disables a certain keyword or operator. See [disable keywords and operators]. |

View File

@ -37,10 +37,11 @@ The following methods (mostly defined in the [`BasicArrayPackage`][packages] but
| `append` | array to append | concatenates the second array to the end of the first | | `append` | array to append | concatenates the second array to the end of the first |
| `+=` operator | array, array to append | concatenates the second array to the end of the first | | `+=` operator | array, array to append | concatenates the second array to the end of the first |
| `+` operator | first array, second array | concatenates the first array with the second | | `+` operator | first array, second array | concatenates the first array with the second |
| `insert` | element to insert, position<br/>(beginning if <= 0, end if >= length) | insert an element at a certain index | | `insert` | element to insert, position<br/>(beginning if <= 0, end if >= length) | inserts an element at a certain index |
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | | `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | | `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid | | `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
| `reverse` | _none_ | reverses the array |
| `len` method and property | _none_ | returns the number of elements | | `len` method and property | _none_ | returns the number of elements |
| `pad` | element to pad, target length | pads the array with an element to at least a specified length | | `pad` | element to pad, target length | pads the array with an element to at least a specified length |
| `clear` | _none_ | empties the array | | `clear` | _none_ | empties the array |
@ -51,7 +52,7 @@ Use Custom Types With Arrays
--------------------------- ---------------------------
To use a [custom type] with arrays, a number of array functions need to be manually implemented, To use a [custom type] with arrays, a number of array functions need to be manually implemented,
in particular `push`, `pad` and the `+=` operator. In addition, the `==` operator must be in particular `push`, `insert`, `pad` and the `+=` operator. In addition, the `==` operator must be
implemented for the [custom type] in order to support the `in` operator which uses `==` to implemented for the [custom type] in order to support the `in` operator which uses `==` to
compare elements. compare elements.

View File

@ -10,15 +10,15 @@ Each Function is a Separate Compilation Unit
This means that individual functions can be separated, exported, re-grouped, imported, This means that individual functions can be separated, exported, re-grouped, imported,
and generally mix-'n-match-ed with other completely unrelated scripts. 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, For example, the `AST::merge` method allows Global all functions in one [`AST`] into another,
forming a new, combined, group of functions. forming a new, combined, group of functions.
In general, there are two types of _namespaces_ where functions are looked up: In general, there are two types of _namespaces_ where functions are looked up:
| Namespace | Source | Lookup method | How Many | | Namespace | Source | Lookup method | How Many |
| --------- | ---------------------------------------------------------------------- | --------------------------------- | :----------------------: | | --------- | ---------------------------------------------------------------------- | --------------------------------- | :----------------------: |
| Global | `Engine::register_XXX` API, [`AST`] being evaluated, [packages] loaded | Simple function name | One | | 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 | | Module | [`Module`] | namespace-qualified function name | as many as [`import`]-ed |
Global Namespace Global Namespace
@ -43,10 +43,10 @@ This aspect is very similar to JavaScript before ES6 modules.
// Compile a script into AST // Compile a script into AST
let ast1 = engine.compile( let ast1 = engine.compile(
r#" r#"
fn message() { "Hello!" } // greeting message fn get_message() { "Hello!" } // greeting message
fn say_hello() { fn say_hello() {
print(message()); // prints message print(get_message()); // prints message
} }
say_hello(); say_hello();
@ -54,7 +54,7 @@ let ast1 = engine.compile(
)?; )?;
// Compile another script with an overriding function // Compile another script with an overriding function
let ast2 = engine.compile(r#"fn message() { "Boo!" }"#)?; let ast2 = engine.compile(r#"fn get_message() { "Boo!" }"#)?;
// Merge the two AST's // Merge the two AST's
let ast = ast1.merge(ast2); // 'message' will be overwritten let ast = ast1.merge(ast2); // 'message' will be overwritten
@ -73,7 +73,7 @@ i.e. define the function in a separate module and then [`import`] it:
| message.rhai | | message.rhai |
---------------- ----------------
fn message() { "Hello!" } fn get_message() { "Hello!" }
--------------- ---------------
@ -82,40 +82,52 @@ fn message() { "Hello!" }
fn say_hello() { fn say_hello() {
import "message" as msg; import "message" as msg;
print(msg::message()); print(msg::get_message());
} }
say_hello(); say_hello();
``` ```
Module Namespaces Namespace Consideration When Not Using `FileModuleResolver`
----------------- ---------------------------------------------------------
[Modules] can be dynamically loaded into a Rhai script using the [`import`] keyword. [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. 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 The [`FileModuleResolver`][module resolver] encapsulates the namespace inside the module itself,
defined _within the script_. When called later, those functions will be searched in the so everything works as expected. A function defined in the module script cannot access functions
current global namespace and may not be found. defined in the calling script, but it can freely call functions defined within the same module script.
There is a catch, though. When using anything other than the [`FileModuleResolver`][module resolver],
functions in a module script refer to functions defined in the _global namespace_.
When called later, those functions may not be found.
When using the [`GlobalFileModuleResolver`][module resolver], for example:
```rust ```rust
Using GlobalFileModuleResolver
==============================
----------------- -----------------
| greeting.rhai | | greeting.rhai |
----------------- -----------------
fn message() { "Hello!" }; fn get_message() { "Hello!" };
fn say_hello() { print(message()); } fn say_hello() {
print(get_message()); // 'get_message' is looked up in the global namespace
say_hello(); // 'message' is looked up in the global namespace // when exported
}
say_hello(); // Here, 'get_message' is found in the module namespace
--------------- ---------------
| script.rhai | | script.rhai |
--------------- ---------------
import "greeting" as g; import "greeting" as g;
g::say_hello(); // <- error: function not found - 'message' g::say_hello(); // <- error: function not found - 'get_message'
// because it does not exist in the global namespace
``` ```
In the example above, although the module `greeting.rhai` loads fine (`"Hello!"` is printed), In the example above, although the module `greeting.rhai` loads fine (`"Hello!"` is printed),
@ -123,22 +135,23 @@ the subsequent call using the _namespace-qualified_ function name fails to find
'`message`' which now essentially becomes `g::message`. The call fails as there is no more '`message`' which now essentially becomes `g::message`. The call fails as there is no more
function named '`message`' in the global namespace. function named '`message`' in the global namespace.
Therefore, when writing functions for a [module], make sure that those functions are as _pure_ Therefore, when writing functions for a [module] intended for the [`GlobalFileModuleResolver`][module resolver],
as possible and avoid cross-calling them from each other. A [function pointer] is a valid technique make sure that those functions are as _pure_ as possible and avoid cross-calling them from each other.
to call another function within a module-defined function:
A [function pointer] is a valid technique to call another function in an environment-independent manner:
```rust ```rust
----------------- -----------------
| greeting.rhai | | greeting.rhai |
----------------- -----------------
fn message() { "Hello!" }; fn get_message() { "Hello!" };
fn say_hello(msg_func) { // 'msg_func' is a function pointer fn say_hello(msg_func) { // 'msg_func' is a function pointer
print(msg_func.call()); // call via the function pointer print(msg_func.call()); // call via the function pointer
} }
say_hello(); // 'message' is looked up in the global namespace say_hello(Fn("get_message"));
--------------- ---------------
@ -149,7 +162,7 @@ import "greeting" as g;
fn my_msg() { fn my_msg() {
import "greeting" as g; // <- must import again here... import "greeting" as g; // <- must import again here...
g::message() // <- ... otherwise will not find module 'g' g::get_message() // <- ... otherwise will not find module 'g'
} }
g::say_hello(Fn("my_msg")); // prints 'Hello!' g::say_hello(Fn("my_msg")); // prints 'Hello!'

View File

@ -7,19 +7,19 @@ The following are reserved keywords in Rhai:
| Active keywords | Reserved keywords | Usage | Inactive under feature | | Active keywords | Reserved keywords | Usage | Inactive under feature |
| ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: | | ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: |
| `true`, `false` | | Boolean constants | | | `true`, `false` | | boolean constants | |
| `let`, `const` | `var`, `static` | Variable declarations | | | `let`, `const` | `var`, `static` | variable declarations | |
| `is_shared` | | Shared values | [`no_closure`] | | `is_shared` | | shared values | [`no_closure`] |
| `if`, `else` | `then`, `goto`, `exit` | Control flow | | | `if`, `else` | `then`, `goto`, `exit` | control flow | |
| | `switch`, `match`, `case` | Matching | | | | `switch`, `match`, `case` | matching | |
| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | | | `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | looping | |
| `fn`, `private` | `public`, `new` | Functions | [`no_function`] | | `fn`, `private` | `public`, `new` | functions | [`no_function`] |
| `return` | | Return values | | | `return` | | return values | |
| `throw` | `try`, `catch` | Throw exceptions | | | `throw` | `try`, `catch` | throw exceptions | |
| `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] | | `import`, `export`, `as` | `use`, `with`, `module`, `package` | modules/packages | [`no_module`] |
| `Fn`, `call`, `curry` | | Function pointers | | | `Fn`, `call`, `curry` | | function pointers | |
| | `spawn`, `go`, `sync`, `async`, `await`, `yield` | Threading/async | | | | `spawn`, `go`, `sync`, `async`, `await`, `yield` | threading/async | |
| `type_of`, `print`, `debug`, `eval` | | Special functions | | | `type_of`, `print`, `debug`, `eval` | | special functions | |
| | `default`, `void`, `null`, `nil` | Special values | | | | `default`, `void`, `null`, `nil` | special values | |
Keywords cannot become the name of a [function] or [variable], even when they are disabled. Keywords cannot become the name of a [function] or [variable], even when they are disabled.

View File

@ -45,11 +45,11 @@ Boolean operators
| Operator | Description | | Operator | Description |
| ----------------- | ------------------------------------- | | ----------------- | ------------------------------------- |
| `!` | Boolean _Not_ | | `!` | boolean _Not_ |
| `&&` | Boolean _And_ (short-circuits) | | `&&` | boolean _And_ (short-circuits) |
| <code>\|\|</code> | Boolean _Or_ (short-circuits) | | <code>\|\|</code> | boolean _Or_ (short-circuits) |
| `&` | Boolean _And_ (doesn't short-circuit) | | `&` | boolean _And_ (doesn't short-circuit) |
| <code>\|</code> | Boolean _Or_ (doesn't short-circuit) | | <code>\|</code> | boolean _Or_ (doesn't short-circuit) |
Double boolean operators `&&` and `||` _short-circuit_, meaning that the second operand will not be evaluated Double boolean operators `&&` and `||` _short-circuit_, meaning that the second operand will not be evaluated
if the first one already proves the condition wrong. if the first one already proves the condition wrong.

View File

@ -49,7 +49,7 @@ The following table illustrates the differences:
| Function type | Parameters | Object reference | Function signature | | Function type | Parameters | Object reference | Function signature |
| :-----------: | :--------: | :--------------------: | :-----------------------------------------------------: | | :-----------: | :--------: | :--------------------: | :-----------------------------------------------------: |
| Native Rust | _n_ + 1 | First `&mut` parameter | `fn method<T, U, V>`<br/>`(obj: &mut T, x: U, y: V) {}` | | Native Rust | _n_ + 1 | first `&mut` parameter | `fn method<T, U, V>`<br/>`(obj: &mut T, x: U, y: V) {}` |
| Rhai script | _n_ | `this` | `fn method(x, y) {}` | | Rhai script | _n_ | `this` | `fn method(x, y) {}` |

View File

@ -9,10 +9,10 @@ Integer Functions
The following standard functions (defined in the [`BasicMathPackage`][packages] 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: operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only:
| Function | No available under | Description | | Function | No available under | Description |
| -------- | :----------------: | ---------------------------------------------------------------------- | | -------- | :----------------: | ----------------------------------------------------------------------- |
| `abs` | | absolute value | | `abs` | | absolute value |
| `sign` | | return -1 (`INT`) if the number is negative, +1 if positive, 0 if zero | | `sign` | | returns -1 (`INT`) if the number is negative, +1 if positive, 0 if zero |
Floating-Point Functions Floating-Point Functions
@ -39,8 +39,8 @@ Conversion Functions
The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`]) The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`])
parse numbers: parse numbers:
| Function | No available under | Description | | Function | No available under | Description |
| --------------- | :----------------: | -------------------------------------------------- | | --------------- | :----------------: | --------------------------------------------------- |
| [`to_float`] | [`no_float`] | convert an integer type to `FLOAT` | | [`to_float`] | [`no_float`] | converts an integer type to `FLOAT` |
| [`parse_int`] | | convert a [string] to `INT` with an optional radix | | [`parse_int`] | | converts a [string] to `INT` with an optional radix |
| [`parse_float`] | [`no_float`] | convert a [string] to `FLOAT` | | [`parse_float`] | [`no_float`] | converts a [string] to `FLOAT` |

View File

@ -10,8 +10,8 @@ Unary Operators
| Operator | Description | | Operator | Description |
| -------- | ----------- | | -------- | ----------- |
| `+` | Positive | | `+` | positive |
| `-` | Negative | | `-` | negative |
```rust ```rust
let number = -5; let number = -5;
@ -24,17 +24,17 @@ Binary Operators
| Operator | Description | Integers only | | Operator | Description | Integers only |
| --------------- | ---------------------------------------------------- | :-----------: | | --------------- | ---------------------------------------------------- | :-----------: |
| `+` | Plus | | | `+` | plus | |
| `-` | Minus | | | `-` | minus | |
| `*` | Multiply | | | `*` | multiply | |
| `/` | Divide (integer division if acting on integer types) | | | `/` | divide (integer division if acting on integer types) | |
| `%` | Modulo (remainder) | | | `%` | modulo (remainder) | |
| `~` | Power | | | `~` | power | |
| `&` | Bit-wise _And_ | Yes | | `&` | bit-wise _And_ | Yes |
| <code>\|</code> | Bit-wise _Or_ | Yes | | <code>\|</code> | bit-wise _Or_ | Yes |
| `^` | Bit-wise _Xor_ | Yes | | `^` | bit-wise _Xor_ | Yes |
| `<<` | Left bit-shift | Yes | | `<<` | left bit-shift | Yes |
| `>>` | Right bit-shift | Yes | | `>>` | right bit-shift | Yes |
```rust ```rust
let x = (1 + 2) * (6 - 4) / 2; // arithmetic, with parentheses let x = (1 + 2) * (6 - 4) / 2; // arithmetic, with parentheses

View File

@ -11,11 +11,11 @@ individual functions instead of a full-blown [plugin module].
Macros Macros
------ ------
| Macro | Apply to | Behavior | | Macro | Apply to | Description |
| ----------------------- | --------------------------------------------------------------- | -------------------------------------------------------- | | ----------------------- | --------------------------------------------------------------- | ------------------------------------------------------------- |
| `#[export_fn]` | Rust function defined in a Rust module | Export the function | | `#[export_fn]` | rust function defined in a Rust module | exports the function |
| `register_exported_fn!` | [`Engine`] instance, register name string, use path to function | Register function into an [`Engine`] under specific name | | `register_exported_fn!` | [`Engine`] instance, register name string, use path to function | registers the function into an [`Engine`] under specific name |
| `set_exported_fn!` | [`Module`] instance, register name string, use path to function | Register function into an [`Module`] under specific name | | `set_exported_fn!` | [`Module`] instance, register name string, use path to function | registers the function into an [`Module`] under specific name |
`#[export_fn]` and `register_exported_fn!` `#[export_fn]` and `register_exported_fn!`

View File

@ -204,11 +204,11 @@ The above function can be called in five ways:
| Parameter for `#[rhai_fn(...)]` | Type | Call style | | Parameter for `#[rhai_fn(...)]` | Type | Call style |
| ------------------------------- | :-------------: | --------------------------------------------- | | ------------------------------- | :-------------: | --------------------------------------------- |
| `name = "get_prop_value"` | Method function | `get_prop_value(x, 0)`, `x.get_prop_value(0)` | | `name = "get_prop_value"` | method function | `get_prop_value(x, 0)`, `x.get_prop_value(0)` |
| `name = "prop"` | Method function | `prop(x, 0)`, `x.prop(0)` | | `name = "prop"` | method function | `prop(x, 0)`, `x.prop(0)` |
| `name = "+"` | Operator | `x + 42` | | `name = "+"` | operator | `x + 42` |
| `set = "prop"` | Setter | `x.prop = 42` | | `set = "prop"` | setter | `x.prop = 42` |
| `index_get` | Index getter | `x[0]` | | `index_get` | index getter | `x[0]` |
Fallible Functions Fallible Functions
@ -244,11 +244,11 @@ mod my_module {
Parameters can be applied to the `#[export_module]` attribute to override its default behavior. Parameters can be applied to the `#[export_module]` attribute to override its default behavior.
| Parameter | Behavior | | Parameter | Description |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| _None_ | Export only public (i.e. `pub`) functions | | _none_ | exports only public (i.e. `pub`) functions |
| `export_all` | Export all functions (including private, non-`pub` functions); use `#[rhai_fn(skip)]` on individual functions to avoid export | | `export_all` | exports all functions (including private, non-`pub` functions); use `#[rhai_fn(skip)]` on individual functions to avoid export |
| `export_prefix = "..."` | Export functions (including private, non-`pub` functions) with names starting with a specific prefix | | `export_prefix = "..."` | exports functions (including private, non-`pub` functions) with names starting with a specific prefix |
Inner Attributes Inner Attributes
@ -260,12 +260,12 @@ Inner attributes can be applied to the inner items of a module to tweak the expo
Parameters should be set on inner attributes to specify the desired behavior. Parameters should be set on inner attributes to specify the desired behavior.
| Attribute Parameter | Use with | Apply to | Behavior | | Attribute Parameter | Use with | Apply to | Description |
| ------------------- | --------------------------- | -------------------------------------------------------- | ----------------------------------------------------- | | ------------------- | --------------------------- | -------------------------------------------------------- | ------------------------------------------------------ |
| `skip` | `#[rhai_fn]`, `#[rhai_mod]` | Function or sub-module | Do not export this function/sub-module | | `skip` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | do not export this function/sub-module |
| `name = "..."` | `#[rhai_fn]`, `#[rhai_mod]` | Function or sub-module | Register function/sub-module under the specified name | | `name = "..."` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | registers function/sub-module under the specified name |
| `get = "..."` | `#[rhai_fn]` | Function with `&mut` first parameter | Register a getter for the named property | | `get = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a getter for the named property |
| `set = "..."` | `#[rhai_fn]` | Function with `&mut` first parameter | Register a setter for the named property | | `set = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a setter for the named property |
| `index_get` | `#[rhai_fn]` | Function with `&mut` first parameter | Register an index getter | | `index_get` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index getter |
| `index_set` | `#[rhai_fn]` | Function with `&mut` first parameter | Register an index setter | | `index_set` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index setter |
| `return_raw` | `#[rhai_fn]` | Function returning `Result<Dynamic, Box<EvalAltResult>>` | Mark this as a [fallible function] | | `return_raw` | `#[rhai_fn]` | function returning `Result<Dynamic, Box<EvalAltResult>>` | marks this as a [fallible function] |

View File

@ -161,8 +161,8 @@ x.type_of() == "Hello";
Use the Custom Type With Arrays Use the Custom Type With Arrays
------------------------------ ------------------------------
The `push` and `pad` functions, as well as the `+=` operator, for [arrays] are only defined for The `push`, `insert`, `pad` functions, as well as the `+=` operator, for [arrays] are only
standard built-in types. For custom types, type-specific versions must be registered: defined for standard built-in types. For custom types, type-specific versions must be registered:
```rust ```rust
engine engine
@ -170,6 +170,14 @@ engine
list.push(Dynamic::from(item)); list.push(Dynamic::from(item));
}).register_fn("+=", |list: &mut Array, item: TestStruct| { }).register_fn("+=", |list: &mut Array, item: TestStruct| {
list.push(Dynamic::from(item)); list.push(Dynamic::from(item));
}).register_fn("insert", |list: &mut Array, position: i64, item: TestStruct| {
if position <= 0 {
list.insert(0, Dynamic::from(item));
} else if (position as usize) >= list.len() - 1 {
list.push(item);
} else {
list.insert(position as usize, Dynamic::from(item));
}
}).register_fn("pad", |list: &mut Array, len: i64, item: TestStruct| { }).register_fn("pad", |list: &mut Array, len: i64, item: TestStruct| {
if len as usize > list.len() { if len as usize > list.len() {
list.resize(len as usize, item); list.resize(len as usize, item);

View File

@ -11,11 +11,11 @@ Getters and setters are disabled when the [`no_object`] feature is used.
| `Engine` API | Description | Return Value of Function | | `Engine` API | Description | Return Value of Function |
| --------------------- | ------------------------------------------------- | :-----------------------------------: | | --------------------- | ------------------------------------------------- | :-----------------------------------: |
| `register_get` | Register a getter | _Any_ | | `register_get` | register a getter | _any_ |
| `register_set` | Register a setter | _None_ | | `register_set` | register a setter | _none_ |
| `register_get_set` | Short-hand to register both a getter and a setter | _None_ | | `register_get_set` | short-hand to register both a getter and a setter | _none_ |
| `register_get_result` | Register a getter | `Result<Dynamic, Box<EvalAltResult>>` | | `register_get_result` | register a getter | `Result<Dynamic, Box<EvalAltResult>>` |
| `register_set_result` | Register a setter | `Result<(), Box<EvalAltResult>>` | | `register_set_result` | register a setter | `Result<(), Box<EvalAltResult>>` |
Cannot Override Object Maps Cannot Override Object Maps

View File

@ -15,11 +15,11 @@ Indexers are disabled when the [`no_index`] feature is used.
| `Engine` API | Description | Return Value of Function | | `Engine` API | Description | Return Value of Function |
| ----------------------------- | -------------------------------------------------------- | :-----------------------------------: | | ----------------------------- | -------------------------------------------------------- | :-----------------------------------: |
| `register_indexer_get` | Register an index getter | _Any_ | | `register_indexer_get` | register an index getter | _any_ |
| `register_indexer_set` | Register an index setter | _None_ | | `register_indexer_set` | register an index setter | _none_ |
| `register_indexer_get_set` | Short-hand to register both an index getter and a setter | _None_ | | `register_indexer_get_set` | short-hand to register both an index getter and a setter | _none_ |
| `register_indexer_get_result` | Register an index getter | `Result<Dynamic, Box<EvalAltResult>>` | | `register_indexer_get_result` | register an index getter | `Result<Dynamic, Box<EvalAltResult>>` |
| `register_indexer_set_result` | Register an index setter | `Result<(), Box<EvalAltResult>>` | | `register_indexer_set_result` | register an index setter | `Result<(), Box<EvalAltResult>>` |
Cannot Override Arrays, Object Maps and Strings Cannot Override Arrays, Object Maps and Strings

View File

@ -16,11 +16,12 @@ which simply loads a script file based on the path (with `.rhai` extension attac
Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace. Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
| Module Resolver | Description | | Module Resolver | Description | Namespace |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------: |
| `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. | | `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. | Module (cannot access global namespace) |
| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. | | `GlobalFileModuleResolver` | A simpler but more efficient version of `FileModuleResolver`, intended for short utility modules. Not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.<br/>**Note:** All functions are assumed absolutely _pure_ and cannot cross-call each other.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. | Global |
| `ModuleResolversCollection` | A collection of module resolvers. Modules will be resolved from each resolver in sequential order.<br/>This is useful when multiple types of modules are needed simultaneously. | | `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. | Global |
| `ModuleResolversCollection` | A collection of module resolvers. Modules will be resolved from each resolver in sequential order.<br/>This is useful when multiple types of modules are needed simultaneously. | Global |
Set into `Engine` Set into `Engine`

View File

@ -9,19 +9,19 @@ Built-In Packages
| Package | Description | In `Core` | In `Standard` | | Package | Description | In `Core` | In `Standard` |
| ---------------------- | ------------------------------------------------------------------------------------------------------ | :-------: | :-----------: | | ---------------------- | ------------------------------------------------------------------------------------------------------ | :-------: | :-----------: |
| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) for numeric types that are not built in (e.g. `u16`) | Yes | Yes | | `ArithmeticPackage` | arithmetic operators (e.g. `+`, `-`, `*`, `/`) for numeric types that are not built in (e.g. `u16`) | yes | yes |
| `BasicIteratorPackage` | Numeric ranges (e.g. `range(1, 10)`) | Yes | Yes | | `BasicIteratorPackage` | numeric ranges (e.g. `range(1, 10)`) | yes | yes |
| `LogicPackage` | Logical and comparison operators (e.g. `==`, `>`) for numeric types that are not built in (e.g. `u16`) | Yes | Yes | | `LogicPackage` | logical and comparison operators (e.g. `==`, `>`) for numeric types that are not built in (e.g. `u16`) | yes | yes |
| `BasicStringPackage` | Basic string functions (e.g. `print`, `debug`, `len`) that are not built in | Yes | Yes | | `BasicStringPackage` | basic string functions (e.g. `print`, `debug`, `len`) that are not built in | yes | yes |
| `BasicTimePackage` | Basic time functions (e.g. [timestamps]) | Yes | Yes | | `BasicTimePackage` | basic time functions (e.g. [timestamps]) | yes | yes |
| `MoreStringPackage` | Additional string functions, including converting common types to string | No | Yes | | `MoreStringPackage` | additional string functions, including converting common types to string | no | yes |
| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes | | `BasicMathPackage` | basic math functions (e.g. `sin`, `sqrt`) | no | yes |
| `BasicArrayPackage` | Basic [array] functions (not available under `no_index`) | 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 | | `BasicMapPackage` | basic [object map] functions (not available under `no_object`) | no | yes |
| `BasicFnPackage` | Basic methods for [function pointers]. | Yes | Yes | | `BasicFnPackage` | basic methods for [function pointers]. | yes | yes |
| `EvalPackage` | Disable [`eval`] | No | No | | `EvalPackage` | disable [`eval`] | no | no |
| `CorePackage` | Basic essentials | Yes | Yes | | `CorePackage` | basic essentials | yes | yes |
| `StandardPackage` | Standard library (default for `Engine::new`) | No | Yes | | `StandardPackage` | standard library (default for `Engine::new`) | no | yes |
Load the `CorePackage` Load the `CorePackage`

View File

@ -7,11 +7,11 @@ To use custom types for [`print`] and [`debug`], or convert its value into a [st
it is necessary that the following functions be registered (assuming the custom type it is necessary that the following functions be registered (assuming the custom type
is `T : Display + Debug`): is `T : Display + Debug`):
| Function | Signature | Typical implementation | Usage | | Function | Signature | Typical implementation | Usage |
| ----------- | ------------------------------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------- | | ----------- | ------------------------------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------- |
| `to_string` | <code>\|s: &mut T\| -> ImmutableString</code> | `s.to_string().into()` | Converts the custom type into a [string] | | `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`] 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 | | `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>\|s1: ImmutableString, s: T\| -> ImmutableString</code> | `s1 + s` | appends 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>\|s: T, s2: ImmutableString\| -> ImmutableString</code> | `s.to_string().push_str(&s2).into();` | appends 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 | | `+=` | <code>\|s1: &mut ImmutableString, s: T\|</code> | `s1 += s.to_string()` | appends the custom type to an existing [string], for `s += type;` usage |

View File

@ -84,12 +84,12 @@ Extract Arguments
To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following: To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following:
| Argument type | Access (`n` = argument position) | Result | | Argument type | Access (`n` = argument position) | Result |
| ------------------------------ | ------------------------------------- | ---------------------------------------------------------- | | ------------------------------ | ------------------------------------- | --------------------------------------------------------- |
| [Primary type][standard types] | `args[n].clone().cast::<T>()` | Copy of value. | | [Primary type][standard types] | `args[n].clone().cast::<T>()` | copy of value |
| Custom type | `args[n].read_lock::<T>().unwrap()` | Immutable reference to value. | | Custom type | `args[n].read_lock::<T>().unwrap()` | immutable reference to value |
| Custom type (consumed) | `std::mem::take(args[n]).cast::<T>()` | The _consumed_ value.<br/>The original value becomes `()`. | | Custom type (consumed) | `std::mem::take(args[n]).cast::<T>()` | the _consumed_ value.<br/>The original value becomes `()` |
| `this` object | `args[0].write_lock::<T>().unwrap()` | Mutable reference to value. | | `this` object | `args[0].write_lock::<T>().unwrap()` | mutable reference to value |
When there is a mutable reference to the `this` object (i.e. the first argument), When there is a mutable reference to the `this` object (i.e. the first argument),
there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain. there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain.

View File

@ -7,7 +7,7 @@ A number of traits, under the `rhai::` module namespace, provide additional func
| Trait | Description | Methods | | Trait | Description | Methods |
| ------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------- | | ------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------- |
| `RegisterFn` | Trait for registering functions | `register_fn` | | `RegisterFn` | trait for registering functions | `register_fn` |
| `RegisterResultFn` | Trait for registering fallible functions returning `Result<Dynamic, Box<EvalAltResult>>` | `register_result_fn` | | `RegisterResultFn` | trait for registering fallible functions returning `Result<Dynamic, Box<EvalAltResult>>` | `register_result_fn` |
| `Func` | Trait for creating Rust closures from script | `create_from_ast`, `create_from_script` | | `Func` | trait for creating Rust closures from script | `create_from_ast`, `create_from_script` |
| `ModuleResolver` | Trait implemented by module resolution services | `resolve` | | `ModuleResolver` | trait implemented by module resolution services | `resolve` |

View File

@ -19,7 +19,7 @@ This check can be disabled via the [`unchecked`] feature for higher performance
```rust ```rust
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_max_array_size(500); // allow arrays only up to 500 items engine.set_max_array_size(500); // allow arrays only up to 500 items
engine.set_max_array_size(0); // allow unlimited arrays engine.set_max_array_size(0); // allow unlimited arrays
``` ```

View File

@ -5,17 +5,17 @@ Rust Examples
A number of examples can be found in the `examples` directory: A number of examples can be found in the `examples` directory:
| Example | Description | | Example | Description |
| ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| [`arrays_and_structs`]({{repoTree}}/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. | | [`arrays_and_structs`]({{repoTree}}/examples/arrays_and_structs.rs) | shows how to register a custom Rust type and using [arrays] on it |
| [`custom_types_and_methods`]({{repoTree}}/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. | | [`custom_types_and_methods`]({{repoTree}}/examples/custom_types_and_methods.rs) | shows how to register a custom Rust type and methods for it |
| [`hello`]({{repoTree}}/examples/hello.rs) | Simple example that evaluates an expression and prints the result. | | [`hello`]({{repoTree}}/examples/hello.rs) | simple example that evaluates an expression and prints the result |
| [`reuse_scope`]({{repoTree}}/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. | | [`reuse_scope`]({{repoTree}}/examples/reuse_scope.rs) | evaluates two pieces of code in separate runs, but using a common [`Scope`] |
| [`rhai_runner`]({{repoTree}}/examples/rhai_runner.rs) | Runs each filename passed to it as a Rhai script. | | [`rhai_runner`]({{repoTree}}/examples/rhai_runner.rs) | runs each filename passed to it as a Rhai script |
| [`serde`]({{repoTree}}/examples/serde.rs) | Example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).<br/>The [`serde`] feature is required to run. | | [`serde`]({{repoTree}}/examples/serde.rs) | example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).<br/>The [`serde`] feature is required to run |
| [`simple_fn`]({{repoTree}}/examples/simple_fn.rs) | Shows how to register a simple function. | | [`simple_fn`]({{repoTree}}/examples/simple_fn.rs) | shows how to register a simple function |
| [`strings`]({{repoTree}}/examples/strings.rs) | Shows different ways to register functions taking string arguments. | | [`strings`]({{repoTree}}/examples/strings.rs) | shows different ways to register functions taking string arguments |
| [`repl`]({{repoTree}}/examples/repl.rs) | A simple REPL, interactively evaluate statements from stdin. | | [`repl`]({{repoTree}}/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 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). language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop).
@ -35,9 +35,9 @@ cargo run --example {example_name}
To illustrate `no-std` builds, a number of sample applications are available under the `no_std` directory: To illustrate `no-std` builds, a number of sample applications are available under the `no_std` directory:
| Sample | Description | Optimization | Allocator | Panics | | Sample | Description | Optimization | Allocator | Panics |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | :----------: | :-----------------------------------------------: | :----: | | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | :----------: | :-----------------------------------------------: | :----: |
| [`no_std_test`]({{repoTree}}/no_std/no_std_test) | Bare-bones test application that evaluates a Rhai expression and sets the result as the return value. | Size | [`wee_alloc`](https://crates.io/crates/wee_alloc) | Abort | | [`no_std_test`]({{repoTree}}/no_std/no_std_test) | bare-bones test application that evaluates a Rhai expression and sets the result as the return value | size | [`wee_alloc`](https://crates.io/crates/wee_alloc) | abort |
`cargo run` cannot be used to run a `no-std` sample. It must first be built: `cargo run` cannot be used to run a `no-std` sample. It must first be built:

View File

@ -10,22 +10,22 @@ There are also a number of examples scripts that showcase Rhai's features, all i
| Script | Description | | Script | Description |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| [`array.rhai`]({{repoTree}}/scripts/array.rhai) | [Arrays] | | [`array.rhai`]({{repoTree}}/scripts/array.rhai) | [arrays] |
| [`assignment.rhai`]({{repoTree}}/scripts/assignment.rhai) | Variable declarations | | [`assignment.rhai`]({{repoTree}}/scripts/assignment.rhai) | variable declarations |
| [`comments.rhai`]({{repoTree}}/scripts/comments.rhai) | Just comments | | [`comments.rhai`]({{repoTree}}/scripts/comments.rhai) | just comments |
| [`for1.rhai`]({{repoTree}}/scripts/for1.rhai) | [`for`]({{rootUrl}}/language/for.md) loops | | [`for1.rhai`]({{repoTree}}/scripts/for1.rhai) | [`for`]({{rootUrl}}/language/for.md) loops |
| [`for2.rhai`]({{repoTree}}/scripts/for2.rhai) | [`for`]({{rootUrl}}/language/for.md) loops on [arrays] | | [`for2.rhai`]({{repoTree}}/scripts/for2.rhai) | [`for`]({{rootUrl}}/language/for.md) loops on [arrays] |
| [`function_decl1.rhai`]({{repoTree}}/scripts/function_decl1.rhai) | A [function] without parameters | | [`function_decl1.rhai`]({{repoTree}}/scripts/function_decl1.rhai) | a [function] without parameters |
| [`function_decl2.rhai`]({{repoTree}}/scripts/function_decl2.rhai) | A [function] with two parameters | | [`function_decl2.rhai`]({{repoTree}}/scripts/function_decl2.rhai) | a [function] with two parameters |
| [`function_decl3.rhai`]({{repoTree}}/scripts/function_decl3.rhai) | A [function] with many parameters | | [`function_decl3.rhai`]({{repoTree}}/scripts/function_decl3.rhai) | a [function] with many parameters |
| [`if1.rhai`]({{repoTree}}/scripts/if1.rhai) | [`if`]({{rootUrl}}/language/if.md) example | | [`if1.rhai`]({{repoTree}}/scripts/if1.rhai) | [`if`]({{rootUrl}}/language/if.md) example |
| [`loop.rhai`]({{repoTree}}/scripts/loop.rhai) | Count-down [`loop`]({{rootUrl}}/language/loop.md) in Rhai, emulating a `do` .. `while` loop | | [`loop.rhai`]({{repoTree}}/scripts/loop.rhai) | count-down [`loop`]({{rootUrl}}/language/loop.md) in Rhai, emulating a `do` .. `while` loop |
| [`oop.rhai`]({{repoTree}}/scripts/oop.rhai) | Simulate [object-oriented programming (OOP)][OOP] with [closures] | | [`oop.rhai`]({{repoTree}}/scripts/oop.rhai) | simulate [object-oriented programming (OOP)][OOP] with [closures] |
| [`op1.rhai`]({{repoTree}}/scripts/op1.rhai) | Just simple addition | | [`op1.rhai`]({{repoTree}}/scripts/op1.rhai) | just simple addition |
| [`op2.rhai`]({{repoTree}}/scripts/op2.rhai) | Simple addition and multiplication | | [`op2.rhai`]({{repoTree}}/scripts/op2.rhai) | simple addition and multiplication |
| [`op3.rhai`]({{repoTree}}/scripts/op3.rhai) | Change evaluation order with parenthesis | | [`op3.rhai`]({{repoTree}}/scripts/op3.rhai) | change evaluation order with parenthesis |
| [`string.rhai`]({{repoTree}}/scripts/string.rhai) | [String] operations | | [`string.rhai`]({{repoTree}}/scripts/string.rhai) | [string] operations |
| [`strings_map.rhai`]({{repoTree}}/scripts/strings_map.rhai) | [String] and [object map] operations | | [`strings_map.rhai`]({{repoTree}}/scripts/strings_map.rhai) | [string] and [object map] operations |
| [`while.rhai`]({{repoTree}}/scripts/while.rhai) | [`while`]({{rootUrl}}/language/while.md) loop | | [`while.rhai`]({{repoTree}}/scripts/while.rhai) | [`while`]({{rootUrl}}/language/while.md) loop |
@ -34,12 +34,12 @@ Benchmark Scripts
The following scripts are for benchmarking the speed of Rhai: The following scripts are for benchmarking the speed of Rhai:
| Scripts | Description | | Scripts | Description |
| --------------------------------------------------------- | --------------------------------------------------------------------------------------- | | --------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| [`speed_test.rhai`]({{repoTree}}/scripts/speed_test.rhai) | A simple application to measure the speed of Rhai's interpreter (1 million iterations). | | [`speed_test.rhai`]({{repoTree}}/scripts/speed_test.rhai) | a simple application to measure the speed of Rhai's interpreter (1 million iterations) |
| [`primes.rhai`]({{repoTree}}/scripts/primes.rhai) | Use Sieve of Eratosthenes to find all primes smaller than a limit. | | [`primes.rhai`]({{repoTree}}/scripts/primes.rhai) | use Sieve of Eratosthenes to find all primes smaller than a limit |
| [`fibonacci.rhai`]({{repoTree}}/scripts/fibonacci.rhai) | Calculate the n-th Fibonacci number using a really dumb algorithm. | | [`fibonacci.rhai`]({{repoTree}}/scripts/fibonacci.rhai) | calculate the n-th Fibonacci number using a really dumb algorithm |
| [`mat_mul.rhai`]({{repoTree}}/scripts/mat_mul.rhai) | Matrix multiplication test to measure the speed of multi-dimensional array access. | | [`mat_mul.rhai`]({{repoTree}}/scripts/mat_mul.rhai) | matrix multiplication test to measure the speed of multi-dimensional array access |
Running Example Scripts Running Example Scripts

View File

@ -11,23 +11,23 @@ Notice that this deviates from Rust norm where features are _additive_.
Excluding unneeded functionalities can result in smaller, faster builds as well as Excluding unneeded functionalities can result in smaller, faster builds as well as
more control over what a script can (or cannot) do. more control over what a script can (or cannot) do.
| Feature | Additive? | Description | | Feature | Additive? | Description |
| ------------------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------- | :-------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unchecked` | No | 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! | | `unchecked` | no | disables 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` | No | 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`. | | `sync` | no | restricts all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync` |
| `no_optimize` | No | Disable [script optimization]. | | `no_optimize` | no | disables [script optimization] |
| `no_float` | No | Disable floating-point numbers and math. | | `no_float` | no | disables floating-point numbers and math |
| `only_i32` | No | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | | `only_i32` | no | sets the system integer type to `i32` and disable all other integer types. `INT` is set to `i32` |
| `only_i64` | No | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | | `only_i64` | no | sets the system integer type to `i64` and disable all other integer types. `INT` is set to `i64` |
| `no_index` | No | Disable [arrays] and indexing features. | | `no_index` | no | disables [arrays] and indexing features |
| `no_object` | No | Disable support for [custom types] and [object maps]. | | `no_object` | no | disables support for [custom types] and [object maps] |
| `no_function` | No | Disable script-defined [functions]. | | `no_function` | no | disables script-defined [functions] |
| `no_module` | No | Disable loading external [modules]. | | `no_module` | no | disables loading external [modules] |
| `no_closure` | No | Disable [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls. | | `no_closure` | no | disables [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls |
| `no_std` | No | Build for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features. | | `no_std` | no | builds for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features |
| `serde` | Yes | Enable serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies. | | `serde` | yes | enables serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies |
| `internals` | Yes | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | | `internals` | yes | exposes internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version |
| `unicode-xid-ident` | No | Allow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers. | | `unicode-xid-ident` | no | allows [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers |
Example Example

View File

@ -1502,7 +1502,9 @@ impl Engine {
args: A, args: A,
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = args.into_vec(); let mut arg_values = args.into_vec();
let result = self.call_fn_dynamic_raw(scope, ast, name, &mut None, arg_values.as_mut())?; let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect();
let result = self.call_fn_dynamic_raw(scope, ast, name, &mut None, args.as_mut())?;
let typ = self.map_type_name(result.type_name()); let typ = self.map_type_name(result.type_name());
@ -1574,7 +1576,9 @@ impl Engine {
mut this_ptr: Option<&mut Dynamic>, mut this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>, mut arg_values: impl AsMut<[Dynamic]>,
) -> FuncReturn<Dynamic> { ) -> FuncReturn<Dynamic> {
self.call_fn_dynamic_raw(scope, lib, name, &mut this_ptr, arg_values.as_mut()) let mut args: StaticVec<_> = arg_values.as_mut().iter_mut().collect();
self.call_fn_dynamic_raw(scope, lib, name, &mut this_ptr, args.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.
@ -1592,16 +1596,14 @@ impl Engine {
lib: impl AsRef<Module>, lib: impl AsRef<Module>,
name: &str, name: &str,
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
arg_values: &mut [Dynamic], args: &mut [&mut Dynamic],
) -> FuncReturn<Dynamic> { ) -> FuncReturn<Dynamic> {
let lib = lib.as_ref(); let lib = lib.as_ref();
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let fn_def = get_script_function_by_signature(lib, name, args.len(), true) let fn_def = get_script_function_by_signature(lib, name, args.len(), true)
.ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::none()))?; .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::none()))?;
let mut state = State::new(); let mut state = State::new();
let mut mods = Imports::new(); let mut mods = Imports::new();
let args = args.as_mut();
// Check for data race. // Check for data race.
if cfg!(not(feature = "no_closure")) { if cfg!(not(feature = "no_closure")) {
@ -1634,8 +1636,8 @@ impl Engine {
let lib = if cfg!(not(feature = "no_function")) { let lib = if cfg!(not(feature = "no_function")) {
ast.lib() ast.lib()
.iter_fn() .iter_fn()
.filter(|(_, _, _, f)| f.is_script()) .filter(|(_, _, _, _, f)| f.is_script())
.map(|(_, _, _, f)| f.get_fn_def().clone()) .map(|(_, _, _, _, f)| f.get_fn_def().clone())
.collect() .collect()
} else { } else {
Default::default() Default::default()

View File

@ -5,13 +5,13 @@ use crate::calc_fn_hash;
use crate::engine::Engine; use crate::engine::Engine;
use crate::fn_native::{CallableFunction as Func, FnCallArgs, IteratorFn, SendSync}; use crate::fn_native::{CallableFunction as Func, FnCallArgs, IteratorFn, SendSync};
use crate::fn_register::by_value as cast_arg; use crate::fn_register::by_value as cast_arg;
use crate::parser::{FnAccess, FnAccess::Public, ScriptFnDef}; use crate::parser::{FnAccess, FnAccess::Public};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::{Position, Token}; use crate::token::{Position, Token};
use crate::utils::{ImmutableString, StaticVec, StraightHasherBuilder}; use crate::utils::{ImmutableString, StaticVec, StraightHasherBuilder};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::fn_native::Shared; use crate::{fn_native::Shared, parser::ScriptFnDef};
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
use crate::{ use crate::{
@ -67,7 +67,11 @@ pub struct Module {
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>, all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
/// External Rust functions. /// External Rust functions.
functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, Func), StraightHasherBuilder>, functions: HashMap<
u64,
(String, FnAccess, usize, Option<StaticVec<TypeId>>, Func),
StraightHasherBuilder,
>,
/// Iterator functions, keyed by the type producing the iterator. /// Iterator functions, keyed by the type producing the iterator.
type_iterators: HashMap<TypeId, IteratorFn>, type_iterators: HashMap<TypeId, IteratorFn>,
@ -97,7 +101,7 @@ impl fmt::Debug for Module {
.join(", "), .join(", "),
self.functions self.functions
.values() .values()
.map(|(_, _, _, f)| f.to_string()) .map(|(_, _, _, _, f)| f.to_string())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "), .join(", "),
) )
@ -258,20 +262,23 @@ impl Module {
/// Set a script-defined function into the module. /// Set a script-defined function into the module.
/// ///
/// If there is an existing function of the same name and number of arguments, it is replaced. /// If there is an existing function of the same name and number of arguments, it is replaced.
pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) -> &mut Self { #[cfg(not(feature = "no_function"))]
pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) -> u64 {
// None + function name + number of arguments. // None + function name + number of arguments.
let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty()); let num_params = fn_def.params.len();
let hash_script = calc_fn_hash(empty(), &fn_def.name, num_params, empty());
self.functions.insert( self.functions.insert(
hash_script, hash_script,
( (
fn_def.name.to_string(), fn_def.name.to_string(),
fn_def.access, fn_def.access,
Default::default(), num_params,
None,
fn_def.into(), fn_def.into(),
), ),
); );
self.indexed = false; self.indexed = false;
self hash_script
} }
/// Does a sub-module exist in the module? /// Does a sub-module exist in the module?
@ -362,7 +369,7 @@ impl Module {
} else if public_only { } else if public_only {
self.functions self.functions
.get(&hash_fn) .get(&hash_fn)
.map(|(_, access, _, _)| match access { .map(|(_, access, _, _, _)| match access {
FnAccess::Public => true, FnAccess::Public => true,
FnAccess::Private => false, FnAccess::Private => false,
}) })
@ -412,7 +419,7 @@ impl Module {
let hash_fn = calc_fn_hash(empty(), &name, args_len, arg_types.iter().cloned()); let hash_fn = calc_fn_hash(empty(), &name, args_len, arg_types.iter().cloned());
self.functions self.functions
.insert(hash_fn, (name, access, params, func.into())); .insert(hash_fn, (name, access, args_len, Some(params), func.into()));
self.indexed = false; self.indexed = false;
@ -487,6 +494,32 @@ impl Module {
self.set_fn(name, Public, arg_types, Func::from_method(Box::new(f))) self.set_fn(name, Public, arg_types, Func::from_method(Box::new(f)))
} }
/// Set a raw function but with a signature that is a scripted function, but the implementation is in Rust.
#[cfg(not(feature = "no_function"))]
pub(crate) fn set_raw_fn_as_scripted(
&mut self,
name: impl Into<String>,
num_args: usize,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<Dynamic> + SendSync + 'static,
) -> u64 {
// None + function name + number of arguments.
let name = name.into();
let hash_script = calc_fn_hash(empty(), &name, num_args, empty());
let f = move |engine: &Engine, lib: &Module, args: &mut FnCallArgs| func(engine, lib, args);
self.functions.insert(
hash_script,
(
name,
FnAccess::Public,
num_args,
None,
Func::from_pure(Box::new(f)),
),
);
self.indexed = false;
hash_script
}
/// Set a Rust function taking no parameters into the module, returning a hash key. /// Set a Rust function taking no parameters into the module, returning a hash key.
/// ///
/// If there is a similar existing Rust function, it is replaced. /// If there is a similar existing Rust function, it is replaced.
@ -979,7 +1012,7 @@ impl Module {
} else { } else {
self.functions self.functions
.get(&hash_fn) .get(&hash_fn)
.and_then(|(_, access, _, f)| match access { .and_then(|(_, access, _, _, f)| match access {
_ if !public_only => Some(f), _ if !public_only => Some(f),
FnAccess::Public => Some(f), FnAccess::Public => Some(f),
FnAccess::Private => None, FnAccess::Private => None,
@ -1028,14 +1061,14 @@ impl Module {
/// Merge another module into this module. /// Merge another module into this module.
pub fn merge(&mut self, other: &Self) -> &mut Self { pub fn merge(&mut self, other: &Self) -> &mut Self {
self.merge_filtered(other, &|_, _, _| true) self.merge_filtered(other, &mut |_, _, _| true)
} }
/// Merge another module into this module, with only selected script-defined functions based on a filter predicate. /// Merge another module into this module, with only selected script-defined functions based on a filter predicate.
pub(crate) fn merge_filtered( pub(crate) fn merge_filtered(
&mut self, &mut self,
other: &Self, other: &Self,
_filter: &impl Fn(FnAccess, &str, usize) -> bool, mut _filter: &mut impl FnMut(FnAccess, &str, usize) -> bool,
) -> &mut Self { ) -> &mut Self {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
for (k, v) in &other.modules { for (k, v) in &other.modules {
@ -1055,7 +1088,7 @@ impl Module {
other other
.functions .functions
.iter() .iter()
.filter(|(_, (_, _, _, v))| match v { .filter(|(_, (_, _, _, _, v))| match v {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Func::Script(ref f) => _filter(f.access, f.name.as_str(), f.params.len()), Func::Script(ref f) => _filter(f.access, f.name.as_str(), f.params.len()),
_ => true, _ => true,
@ -1076,9 +1109,9 @@ impl Module {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub(crate) fn retain_functions( pub(crate) fn retain_functions(
&mut self, &mut self,
filter: impl Fn(FnAccess, &str, usize) -> bool, mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
) -> &mut Self { ) -> &mut Self {
self.functions.retain(|_, (_, _, _, v)| match v { self.functions.retain(|_, (_, _, _, _, v)| match v {
Func::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()), Func::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()),
_ => true, _ => true,
}); });
@ -1110,7 +1143,7 @@ impl Module {
/// Get an iterator to the functions in the module. /// Get an iterator to the functions in the module.
pub(crate) fn iter_fn( pub(crate) fn iter_fn(
&self, &self,
) -> impl Iterator<Item = &(String, FnAccess, StaticVec<TypeId>, Func)> { ) -> impl Iterator<Item = &(String, FnAccess, usize, Option<StaticVec<TypeId>>, Func)> {
self.functions.values() self.functions.values()
} }
@ -1119,11 +1152,21 @@ impl Module {
pub fn iter_script_fn<'a>(&'a self) -> impl Iterator<Item = Shared<ScriptFnDef>> + 'a { pub fn iter_script_fn<'a>(&'a self) -> impl Iterator<Item = Shared<ScriptFnDef>> + 'a {
self.functions self.functions
.values() .values()
.map(|(_, _, _, f)| f) .map(|(_, _, _, _, f)| f)
.filter(|f| f.is_script()) .filter(|f| f.is_script())
.map(|f| f.get_shared_fn_def()) .map(|f| f.get_shared_fn_def())
} }
#[cfg(not(feature = "no_function"))]
pub fn iter_script_fn_info(&self, mut action: impl FnMut(FnAccess, &str, usize)) {
self.functions
.iter()
.for_each(|(_, (_, _, _, _, v))| match v {
Func::Script(ref f) => action(f.access, f.name.as_str(), f.params.len()),
_ => (),
});
}
/// Create a new `Module` by evaluating an `AST`. /// Create a new `Module` by evaluating an `AST`.
/// ///
/// # Examples /// # Examples
@ -1194,7 +1237,7 @@ impl Module {
variables.push((hash_var, value.clone())); variables.push((hash_var, value.clone()));
} }
// Index all Rust functions // Index all Rust functions
for (name, access, params, func) in module.functions.values() { for (&_hash, (name, access, _num_args, params, func)) in module.functions.iter() {
match access { match access {
// Private functions are not exported // Private functions are not exported
FnAccess::Private => continue, FnAccess::Private => continue,
@ -1202,31 +1245,31 @@ impl Module {
} }
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if func.is_script() { if params.is_none() {
let fn_def = func.get_shared_fn_def(); let hash_qualified_script = if qualifiers.is_empty() {
// Qualifiers + function name + number of arguments. _hash
let hash_qualified_script = calc_fn_hash( } else {
qualifiers.iter().map(|&v| v), // Qualifiers + function name + number of arguments.
&fn_def.name, calc_fn_hash(qualifiers.iter().map(|&v| v), &name, *_num_args, empty())
fn_def.params.len(), };
empty(), functions.push((hash_qualified_script, func.clone()));
);
functions.push((hash_qualified_script, fn_def.into()));
continue; continue;
} }
// Qualified Rust functions are indexed in two steps: if let Some(params) = params {
// 1) Calculate a hash in a similar manner to script-defined functions, // Qualified Rust functions are indexed in two steps:
// i.e. qualifiers + function name + number of arguments. // 1) Calculate a hash in a similar manner to script-defined functions,
let hash_qualified_script = // i.e. qualifiers + function name + number of arguments.
calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty()); let hash_qualified_script =
// 2) Calculate a second hash with no qualifiers, empty function name, calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
// zero number of arguments, and the actual list of argument `TypeId`'.s // 2) Calculate a second hash with no qualifiers, empty function name,
let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned()); // zero number of arguments, and the actual list of argument `TypeId`'.s
// 3) The final hash is the XOR of the two hashes. let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; // 3) The final hash is the XOR of the two hashes.
let hash_qualified_fn = hash_qualified_script ^ hash_fn_args;
functions.push((hash_qualified_fn, func.clone())); functions.push((hash_qualified_fn, func.clone()));
}
} }
} }
@ -1341,7 +1384,7 @@ pub mod resolvers {
pub use super::collection::ModuleResolversCollection; pub use super::collection::ModuleResolversCollection;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub use super::file::FileModuleResolver; pub use super::file::{FileModuleResolver, GlobalFileModuleResolver};
pub use super::stat::StaticModuleResolver; pub use super::stat::StaticModuleResolver;
} }
#[cfg(feature = "no_module")] #[cfg(feature = "no_module")]
@ -1355,6 +1398,181 @@ mod file {
use super::*; use super::*;
use crate::stdlib::path::PathBuf; use crate::stdlib::path::PathBuf;
/// Module resolution service that loads module script files from the file system.
///
/// All functions in each module are treated as strictly _pure_ and cannot refer to
/// other functions within the same module. Functions are searched in the _global_ namespace.
///
/// For simple utility libraries, this usually performs better than the full `FileModuleResolver`.
///
/// 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
/// (default `.rhai`).
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::GlobalFileModuleResolver;
///
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.x'.
/// let resolver = GlobalFileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
#[derive(Debug)]
pub struct GlobalFileModuleResolver {
path: PathBuf,
extension: String,
#[cfg(not(feature = "sync"))]
cache: RefCell<HashMap<PathBuf, AST>>,
#[cfg(feature = "sync")]
cache: RwLock<HashMap<PathBuf, AST>>,
}
impl Default for GlobalFileModuleResolver {
fn default() -> Self {
Self::new_with_path(PathBuf::default())
}
}
impl GlobalFileModuleResolver {
/// Create a new `GlobalFileModuleResolver` with a specific base path.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::GlobalFileModuleResolver;
///
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.rhai' (the default).
/// let resolver = GlobalFileModuleResolver::new_with_path("./scripts");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new_with_path<P: Into<PathBuf>>(path: P) -> Self {
Self::new_with_path_and_extension(path, "rhai")
}
/// Create a new `GlobalFileModuleResolver` with a specific base path and file extension.
///
/// The default extension is `.rhai`.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::GlobalFileModuleResolver;
///
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the 'scripts' subdirectory
/// // with file extension '.x'.
/// let resolver = GlobalFileModuleResolver::new_with_path_and_extension("./scripts", "x");
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new_with_path_and_extension<P: Into<PathBuf>, E: Into<String>>(
path: P,
extension: E,
) -> Self {
Self {
path: path.into(),
extension: extension.into(),
cache: Default::default(),
}
}
/// Create a new `GlobalFileModuleResolver` with the current directory as base path.
///
/// # Examples
///
/// ```
/// use rhai::Engine;
/// use rhai::module_resolvers::GlobalFileModuleResolver;
///
/// // Create a new 'GlobalFileModuleResolver' loading scripts from the current directory
/// // with file extension '.rhai' (the default).
/// let resolver = GlobalFileModuleResolver::new();
///
/// let mut engine = Engine::new();
/// engine.set_module_resolver(Some(resolver));
/// ```
pub fn new() -> Self {
Default::default()
}
/// Create a `Module` from a file path.
pub fn create_module<P: Into<PathBuf>>(
&self,
engine: &Engine,
path: &str,
) -> Result<Module, Box<EvalAltResult>> {
self.resolve(engine, path, Default::default())
}
}
impl ModuleResolver for GlobalFileModuleResolver {
fn resolve(
&self,
engine: &Engine,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>> {
// Construct the script file path
let mut file_path = self.path.clone();
file_path.push(path);
file_path.set_extension(&self.extension); // Force extension
let scope = Default::default();
// 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();
if let Some(ast) = c.get(&file_path) {
(
Module::eval_ast_as_new(scope, ast, engine)
.map_err(|err| err.new_position(pos))?,
None,
)
} else {
// 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)
}
}
/// Module resolution service that loads module script files from the file system. /// 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. /// Script files are cached so they are are not reloaded and recompiled in subsequent requests.
@ -1484,45 +1702,62 @@ mod file {
file_path.push(path); file_path.push(path);
file_path.set_extension(&self.extension); // Force extension file_path.set_extension(&self.extension); // Force extension
let scope = Default::default();
// See if it is cached // See if it is cached
let (module, ast) = { let exists = {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
let c = self.cache.borrow(); let c = self.cache.borrow();
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
let c = self.cache.read().unwrap(); let c = self.cache.read().unwrap();
match c.get(&file_path) { c.contains_key(&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 { if !exists {
// Load the file and compile it if not found
let ast = engine
.compile_file(file_path.clone())
.map_err(|err| err.new_position(pos))?;
// Put it into the cache // Put it into the cache
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
self.cache.borrow_mut().insert(file_path, ast); self.cache.borrow_mut().insert(file_path.clone(), ast);
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
self.cache.write().unwrap().insert(file_path, ast); self.cache.write().unwrap().insert(file_path.clone(), ast);
} }
Ok(module) #[cfg(not(feature = "sync"))]
let c = self.cache.borrow();
#[cfg(feature = "sync")]
let c = self.cache.read().unwrap();
let ast = c.get(&file_path).unwrap();
let mut _module = Module::eval_ast_as_new(Scope::new(), ast, engine)?;
#[cfg(not(feature = "no_function"))]
ast.iter_functions(|access, name, num_args| match access {
FnAccess::Private => (),
FnAccess::Public => {
let fn_name = name.to_string();
let ast_lib = ast.lib().clone();
_module.set_raw_fn_as_scripted(
name,
num_args,
move |engine: &Engine, _, args: &mut [&mut Dynamic]| {
engine.call_fn_dynamic_raw(
&mut Scope::new(),
&ast_lib,
&fn_name,
&mut None,
args,
)
},
);
}
});
Ok(_module)
} }
} }
} }

View File

@ -7,10 +7,13 @@ use crate::engine::{
}; };
use crate::fn_native::FnPtr; use crate::fn_native::FnPtr;
use crate::module::Module; use crate::module::Module;
use crate::parser::{map_dynamic_to_expr, Expr, ReturnType, ScriptFnDef, Stmt, AST}; use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST};
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::utils::StaticVec; use crate::utils::StaticVec;
#[cfg(not(feature = "no_function"))]
use crate::parser::ReturnType;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
use crate::parser::CustomExpr; use crate::parser::CustomExpr;
@ -575,7 +578,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// First search in functions lib (can override built-in) // First search in functions lib (can override built-in)
// Cater for both normal function call style and method call style (one additional arguments) // Cater for both normal function call style and method call style (one additional arguments)
let has_script_fn = cfg!(not(feature = "no_function")) && state.lib.iter_fn().find(|(_, _, _, f)| { let has_script_fn = cfg!(not(feature = "no_function")) && state.lib.iter_fn().find(|(_, _, _, _,f)| {
if !f.is_script() { return false; } if !f.is_script() { return false; }
let fn_def = f.get_fn_def(); let fn_def = f.get_fn_def();
fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len()) fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
@ -749,7 +752,8 @@ pub fn optimize_into_ast(
level level
}; };
let lib = if cfg!(not(feature = "no_function")) { #[cfg(not(feature = "no_function"))]
let lib = {
let mut module = Module::new(); let mut module = Module::new();
if !level.is_none() { if !level.is_none() {
@ -811,10 +815,11 @@ pub fn optimize_into_ast(
} }
module module
} else {
Default::default()
}; };
#[cfg(feature = "no_function")]
let lib = Default::default();
AST::new( AST::new(
match level { match level {
OptimizationLevel::None => statements, OptimizationLevel::None => statements,

View File

@ -133,6 +133,10 @@ mod array_functions {
list.clear(); list.clear();
} }
} }
#[inline(always)]
pub fn reverse(list: &mut Array) {
list.reverse();
}
} }
fn pad<T: Variant + Clone>( fn pad<T: Variant + Clone>(

View File

@ -130,10 +130,10 @@ impl AST {
/// This operation is cheap because functions are shared. /// This operation is cheap because functions are shared.
pub fn clone_functions_only_filtered( pub fn clone_functions_only_filtered(
&self, &self,
filter: impl Fn(FnAccess, &str, usize) -> bool, mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
) -> Self { ) -> Self {
let mut functions: Module = Default::default(); let mut functions: Module = Default::default();
functions.merge_filtered(&self.1, &filter); functions.merge_filtered(&self.1, &mut filter);
Self(Default::default(), functions) Self(Default::default(), functions)
} }
@ -250,7 +250,7 @@ impl AST {
pub fn merge_filtered( pub fn merge_filtered(
&self, &self,
other: &Self, other: &Self,
filter: impl Fn(FnAccess, &str, usize) -> bool, mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
) -> Self { ) -> Self {
let Self(statements, functions) = self; let Self(statements, functions) = self;
@ -266,7 +266,7 @@ impl AST {
}; };
let mut functions = functions.clone(); let mut functions = functions.clone();
functions.merge_filtered(&other.1, &filter); functions.merge_filtered(&other.1, &mut filter);
Self::new(ast, functions) Self::new(ast, functions)
} }
@ -295,10 +295,16 @@ impl AST {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub fn retain_functions(&mut self, filter: impl Fn(FnAccess, &str, usize) -> bool) { pub fn retain_functions(&mut self, filter: impl FnMut(FnAccess, &str, usize) -> bool) {
self.1.retain_functions(filter); self.1.retain_functions(filter);
} }
/// Iterate through all functions
#[cfg(not(feature = "no_function"))]
pub fn iter_functions(&self, action: impl FnMut(FnAccess, &str, usize)) {
self.1.iter_script_fn_info(action);
}
/// Clear all function definitions in the `AST`. /// Clear all function definitions in the `AST`.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub fn clear_functions(&mut self) { pub fn clear_functions(&mut self) {

View File

@ -731,6 +731,7 @@ pub struct TokenizeState {
/// ///
/// This trait is volatile and may change. /// This trait is volatile and may change.
pub trait InputStream { pub trait InputStream {
fn unread(&mut self, ch: char);
/// Get the next character /// Get the next character
fn get_next(&mut self) -> Option<char>; fn get_next(&mut self) -> Option<char>;
/// Peek the next character /// Peek the next character
@ -1014,8 +1015,21 @@ fn get_next_token_inner(
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
'.' => { '.' => {
result.push(next_char); stream.get_next().unwrap();
eat_next(stream, pos);
// Check if followed by digits (or _)
match stream.peek_next().unwrap_or('\0') {
'0'..='9' | '_' => {
result.push(next_char);
pos.advance()
}
_ => {
// Not a floating-point number
stream.unread(next_char);
break;
}
}
while let Some(next_char_in_float) = stream.peek_next() { while let Some(next_char_in_float) = stream.peek_next() {
match next_char_in_float { match next_char_in_float {
'0'..='9' | '_' => { '0'..='9' | '_' => {
@ -1499,6 +1513,8 @@ fn is_id_continue(x: char) -> bool {
/// A type that implements the `InputStream` trait. /// A type that implements the `InputStream` trait.
/// Multiple character streams are jointed together to form one single stream. /// Multiple character streams are jointed together to form one single stream.
pub struct MultiInputsStream<'a> { pub struct MultiInputsStream<'a> {
/// Buffered character, if any.
buf: Option<char>,
/// The input character streams. /// The input character streams.
streams: StaticVec<Peekable<Chars<'a>>>, streams: StaticVec<Peekable<Chars<'a>>>,
/// The current stream index. /// The current stream index.
@ -1506,8 +1522,16 @@ pub struct MultiInputsStream<'a> {
} }
impl InputStream for MultiInputsStream<'_> { impl InputStream for MultiInputsStream<'_> {
/// Buffer a character.
fn unread(&mut self, ch: char) {
self.buf = Some(ch);
}
/// Get the next character /// Get the next character
fn get_next(&mut self) -> Option<char> { fn get_next(&mut self) -> Option<char> {
if let Some(ch) = self.buf.take() {
return Some(ch);
}
loop { loop {
if self.index >= self.streams.len() { if self.index >= self.streams.len() {
// No more streams // No more streams
@ -1523,6 +1547,10 @@ impl InputStream for MultiInputsStream<'_> {
} }
/// Peek the next character /// Peek the next character
fn peek_next(&mut self) -> Option<char> { fn peek_next(&mut self) -> Option<char> {
if let Some(ch) = self.buf {
return Some(ch);
}
loop { loop {
if self.index >= self.streams.len() { if self.index >= self.streams.len() {
// No more streams // No more streams
@ -1673,6 +1701,7 @@ pub fn lex<'a, 'e>(
}, },
pos: Position::new(1, 0), pos: Position::new(1, 0),
stream: MultiInputsStream { stream: MultiInputsStream {
buf: None,
streams: input.iter().map(|s| s.chars().peekable()).collect(), streams: input.iter().map(|s| s.chars().peekable()).collect(),
index: 0, index: 0,
}, },

View File

@ -4,7 +4,17 @@ use rhai::{Engine, EvalAltResult, INT};
fn test_number_literal() -> Result<(), Box<EvalAltResult>> { fn test_number_literal() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
assert_eq!(engine.eval::<INT>("65")?, 65); assert_eq!(engine.eval::<INT>("42")?, 42);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<String>("42.type_of()")?,
if cfg!(feature = "only_i32") {
"i32"
} else {
"i64"
}
);
Ok(()) Ok(())
} }