Merge pull request #242 from schungx/master
FileModuleResolver that encapsulates module namespace.
This commit is contained in:
commit
90cdf170a3
@ -25,7 +25,9 @@ New features
|
||||
* Plugins support via procedural macros.
|
||||
* Scripted functions are allowed in packages.
|
||||
* `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
|
||||
==============
|
||||
|
@ -5,34 +5,34 @@ Keywords List
|
||||
|
||||
| Keyword | Description | Inactive under | Overloadable |
|
||||
| :-------------------: | ---------------------------------------- | :-------------: | :----------: |
|
||||
| `true` | Boolean true literal | | No |
|
||||
| `false` | Boolean false literal | | No |
|
||||
| `let` | Variable declaration | | No |
|
||||
| `const` | Constant declaration | | No |
|
||||
| `is_shared` | Is a value shared? | | No |
|
||||
| `if` | If statement | | No |
|
||||
| `else` | else block of if statement | | No |
|
||||
| `while` | While loop | | No |
|
||||
| `loop` | Infinite loop | | No |
|
||||
| `for` | For loop | | No |
|
||||
| `in` | Containment test, part of for loop | | No |
|
||||
| `continue` | Continue a loop at the next iteration | | No |
|
||||
| `break` | Loop breaking | | No |
|
||||
| `return` | Return value | | No |
|
||||
| `throw` | Throw exception | | No |
|
||||
| `import` | Import module | [`no_module`] | No |
|
||||
| `export` | Export variable | [`no_module`] | No |
|
||||
| `as` | Alias for variable export | [`no_module`] | No |
|
||||
| `private` | Mark function private | [`no_function`] | No |
|
||||
| `fn` (lower-case `f`) | Function definition | [`no_function`] | No |
|
||||
| `Fn` (capital `F`) | Function to create a [function pointer] | | Yes |
|
||||
| `call` | Call a [function pointer] | | No |
|
||||
| `curry` | Curry a [function pointer] | | No |
|
||||
| `this` | Reference to base object for method call | [`no_function`] | No |
|
||||
| `type_of` | Get type name of value | | Yes |
|
||||
| `print` | Print value | | Yes |
|
||||
| `debug` | Print value in debug format | | Yes |
|
||||
| `eval` | Evaluate script | | Yes |
|
||||
| `true` | boolean true literal | | no |
|
||||
| `false` | boolean false literal | | no |
|
||||
| `let` | variable declaration | | no |
|
||||
| `const` | constant declaration | | no |
|
||||
| `is_shared` | is a value shared? | | no |
|
||||
| `if` | if statement | | no |
|
||||
| `else` | else block of if statement | | no |
|
||||
| `while` | while loop | | no |
|
||||
| `loop` | infinite loop | | no |
|
||||
| `for` | for loop | | no |
|
||||
| `in` | containment test, part of for loop | | no |
|
||||
| `continue` | continue a loop at the next iteration | | no |
|
||||
| `break` | loop breaking | | no |
|
||||
| `return` | return value | | no |
|
||||
| `throw` | throw exception | | no |
|
||||
| `import` | import module | [`no_module`] | no |
|
||||
| `export` | export variable | [`no_module`] | no |
|
||||
| `as` | alias for variable export | [`no_module`] | no |
|
||||
| `private` | mark function private | [`no_function`] | no |
|
||||
| `fn` (lower-case `f`) | function definition | [`no_function`] | no |
|
||||
| `Fn` (capital `F`) | function to create a [function pointer] | | yes |
|
||||
| `call` | call a [function pointer] | | no |
|
||||
| `curry` | curry a [function pointer] | | no |
|
||||
| `this` | reference to base object for method call | [`no_function`] | no |
|
||||
| `type_of` | get type name of value | | yes |
|
||||
| `print` | print value | | yes |
|
||||
| `debug` | print value in debug format | | yes |
|
||||
| `eval` | evaluate script | | yes |
|
||||
|
||||
|
||||
Reserved Keywords
|
||||
@ -40,32 +40,32 @@ Reserved Keywords
|
||||
|
||||
| Keyword | Potential usage |
|
||||
| --------- | --------------------- |
|
||||
| `var` | Variable declaration |
|
||||
| `static` | Variable declaration |
|
||||
| `shared` | Share value |
|
||||
| `do` | Looping |
|
||||
| `each` | Looping |
|
||||
| `then` | Control flow |
|
||||
| `goto` | Control flow |
|
||||
| `exit` | Control flow |
|
||||
| `switch` | Matching |
|
||||
| `match` | Matching |
|
||||
| `case` | Matching |
|
||||
| `public` | Function/field access |
|
||||
| `new` | Constructor |
|
||||
| `try` | Trap exception |
|
||||
| `catch` | Catch exception |
|
||||
| `use` | Import namespace |
|
||||
| `with` | Scope |
|
||||
| `module` | Module |
|
||||
| `package` | Package |
|
||||
| `spawn` | Threading |
|
||||
| `go` | Threading |
|
||||
| `await` | Async |
|
||||
| `async` | Async |
|
||||
| `sync` | Async |
|
||||
| `yield` | Async |
|
||||
| `default` | Special value |
|
||||
| `void` | Special value |
|
||||
| `null` | Special value |
|
||||
| `nil` | Special value |
|
||||
| `var` | variable declaration |
|
||||
| `static` | variable declaration |
|
||||
| `shared` | share value |
|
||||
| `do` | looping |
|
||||
| `each` | looping |
|
||||
| `then` | control flow |
|
||||
| `goto` | control flow |
|
||||
| `exit` | control flow |
|
||||
| `switch` | matching |
|
||||
| `match` | matching |
|
||||
| `case` | matching |
|
||||
| `public` | function/field access |
|
||||
| `new` | constructor |
|
||||
| `try` | trap exception |
|
||||
| `catch` | catch exception |
|
||||
| `use` | import namespace |
|
||||
| `with` | scope |
|
||||
| `module` | module |
|
||||
| `package` | package |
|
||||
| `spawn` | threading |
|
||||
| `go` | threading |
|
||||
| `await` | async |
|
||||
| `async` | async |
|
||||
| `sync` | async |
|
||||
| `yield` | async |
|
||||
| `default` | special value |
|
||||
| `void` | special value |
|
||||
| `null` | special value |
|
||||
| `nil` | special value |
|
||||
|
@ -9,29 +9,28 @@ Operators
|
||||
|
||||
| Operator | Description | Binary? | Binding direction |
|
||||
| :---------------: | ------------------------------ | :-----: | :---------------: |
|
||||
| `+` | Add | Yes | Left |
|
||||
| `-` | Subtract, Minus | Yes/No | Left |
|
||||
| `*` | Multiply | Yes | Left |
|
||||
| `/` | Divide | Yes | Left |
|
||||
| `%` | Modulo | Yes | Left |
|
||||
| `~` | Power | Yes | Left |
|
||||
| `>>` | Right bit-shift | Yes | Left |
|
||||
| `<<` | Left bit-shift | Yes | Left |
|
||||
| `&` | Bit-wise _And_, Boolean _And_ | Yes | Left |
|
||||
| <code>\|</code> | Bit-wise _Or_, Boolean _Or_ | Yes | Left |
|
||||
| `^` | Bit-wise _Xor_, Boolean _Xor_ | Yes | Left |
|
||||
| `==` | Equals to | Yes | Left |
|
||||
| `~=` | Not equals to | Yes | Left |
|
||||
| `>` | Greater than | Yes | Left |
|
||||
| `>=` | Greater than or equals to | Yes | Left |
|
||||
| `<` | Less than | Yes | Left |
|
||||
| `<=` | Less than or equals to | Yes | Left |
|
||||
| `>=` | Greater than or equals to | Yes | Left |
|
||||
| `&&` | Boolean _And_ (short-circuits) | Yes | Left |
|
||||
| <code>\|\|</code> | Boolean _Or_ (short-circuits) | Yes | Left |
|
||||
| `!` | Boolean _Not_ | No | Left |
|
||||
| `[` .. `]` | Indexing | Yes | Right |
|
||||
| `.` | Property access, Method call | Yes | Right |
|
||||
| `+` | add | yes | left |
|
||||
| `-` | subtract, Minus | yes/no | left |
|
||||
| `*` | multiply | yes | left |
|
||||
| `/` | divide | yes | left |
|
||||
| `%` | modulo | yes | left |
|
||||
| `~` | power | yes | left |
|
||||
| `>>` | right bit-shift | yes | left |
|
||||
| `<<` | left bit-shift | yes | left |
|
||||
| `&` | bit-wise _And_, boolean _And_ | yes | left |
|
||||
| <code>\|</code> | bit-wise _Or_, boolean _Or_ | yes | left |
|
||||
| `^` | bit-wise _Xor_, boolean _Xor_ | yes | left |
|
||||
| `==` | equals to | yes | left |
|
||||
| `~=` | not equals to | yes | left |
|
||||
| `>` | greater than | yes | left |
|
||||
| `>=` | greater than or equals to | yes | left |
|
||||
| `<` | less than | yes | left |
|
||||
| `<=` | less than or equals to | yes | left |
|
||||
| `&&` | boolean _And_ (short-circuits) | yes | left |
|
||||
| <code>\|\|</code> | boolean _Or_ (short-circuits) | yes | left |
|
||||
| `!` | boolean _Not_ | no | left |
|
||||
| `[` .. `]` | indexing | yes | right |
|
||||
| `.` | property access, method call | yes | right |
|
||||
|
||||
|
||||
Symbols
|
||||
@ -39,8 +38,8 @@ Symbols
|
||||
|
||||
| Symbol | Description |
|
||||
| ------------ | ------------------------ |
|
||||
| `:` | Property value separator |
|
||||
| `::` | Module path separator |
|
||||
| `:` | property value separator |
|
||||
| `::` | module path separator |
|
||||
| `#` | _Reserved_ |
|
||||
| `=>` | _Reserved_ |
|
||||
| `->` | _Reserved_ |
|
||||
|
@ -5,14 +5,14 @@ Engine Configuration Options
|
||||
|
||||
A number of other configuration options are available from the `Engine` to fine-tune behavior and safeguards.
|
||||
|
||||
| Method | Not available under | Description |
|
||||
| ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `set_optimization_level` | [`no_optimize`] | Set 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_call_levels` | [`unchecked`] | Set 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_modules` | [`unchecked`] | Set 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_array_size` | [`unchecked`], [`no_index`] | Set 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]. |
|
||||
| `disable_symbol` | | Disable a certain keyword or operator. See [disable keywords and operators]. |
|
||||
| Method | Not available under | Description |
|
||||
| ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `set_optimization_level` | [`no_optimize`] | sets the amount of script _optimizations_ performed. See [script optimization]. |
|
||||
| `set_max_expr_depths` | [`unchecked`] | sets the maximum nesting levels of an expression/statement. See [maximum statement 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`] | sets the maximum number of _operations_ that a script is allowed to consume. See [maximum number of operations]. |
|
||||
| `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`] | sets the maximum length (in UTF-8 bytes) for [strings]. See [maximum length of strings]. |
|
||||
| `set_max_array_size` | [`unchecked`], [`no_index`] | sets the maximum size for [arrays]. See [maximum size of arrays]. |
|
||||
| `set_max_map_size` | [`unchecked`], [`no_object`] | sets the maximum number of properties for [object maps]. See [maximum size of object maps]. |
|
||||
| `disable_symbol` | | disables a certain keyword or operator. See [disable keywords and operators]. |
|
||||
|
@ -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 |
|
||||
| `+=` operator | array, array to append | concatenates the second array to the end of the first |
|
||||
| `+` operator | first array, second array | concatenates the first array with the second |
|
||||
| `insert` | element to insert, position<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) |
|
||||
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
|
||||
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
|
||||
| `reverse` | _none_ | reverses the array |
|
||||
| `len` method and property | _none_ | returns the number of elements |
|
||||
| `pad` | element to pad, target length | pads the array with an element to at least a specified length |
|
||||
| `clear` | _none_ | empties the array |
|
||||
@ -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,
|
||||
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
|
||||
compare elements.
|
||||
|
||||
|
@ -10,15 +10,15 @@ Each Function is a Separate Compilation Unit
|
||||
This means that individual functions can be separated, exported, re-grouped, imported,
|
||||
and generally mix-'n-match-ed with other completely unrelated scripts.
|
||||
|
||||
For example, the `AST::merge` method allows merging all functions in one [`AST`] into another,
|
||||
For example, the `AST::merge` method allows Global all functions in one [`AST`] into another,
|
||||
forming a new, combined, group of functions.
|
||||
|
||||
In general, there are two types of _namespaces_ where functions are looked up:
|
||||
|
||||
| Namespace | Source | Lookup method | How Many |
|
||||
| --------- | ---------------------------------------------------------------------- | --------------------------------- | :----------------------: |
|
||||
| Global | `Engine::register_XXX` API, [`AST`] being evaluated, [packages] loaded | Simple function name | One |
|
||||
| Module | [`Module`] | Namespace-qualified function name | As many as [`import`]-ed |
|
||||
| Global | `Engine::register_XXX` API, [`AST`] being evaluated, [packages] loaded | simple function name | one |
|
||||
| Module | [`Module`] | namespace-qualified function name | as many as [`import`]-ed |
|
||||
|
||||
|
||||
Global Namespace
|
||||
@ -43,10 +43,10 @@ This aspect is very similar to JavaScript before ES6 modules.
|
||||
// Compile a script into AST
|
||||
let ast1 = engine.compile(
|
||||
r#"
|
||||
fn message() { "Hello!" } // greeting message
|
||||
fn get_message() { "Hello!" } // greeting message
|
||||
|
||||
fn say_hello() {
|
||||
print(message()); // prints message
|
||||
print(get_message()); // prints message
|
||||
}
|
||||
|
||||
say_hello();
|
||||
@ -54,7 +54,7 @@ let ast1 = engine.compile(
|
||||
)?;
|
||||
|
||||
// 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
|
||||
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 |
|
||||
----------------
|
||||
|
||||
fn message() { "Hello!" }
|
||||
fn get_message() { "Hello!" }
|
||||
|
||||
|
||||
---------------
|
||||
@ -82,40 +82,52 @@ fn message() { "Hello!" }
|
||||
|
||||
fn say_hello() {
|
||||
import "message" as msg;
|
||||
print(msg::message());
|
||||
print(msg::get_message());
|
||||
}
|
||||
say_hello();
|
||||
```
|
||||
|
||||
|
||||
Module Namespaces
|
||||
-----------------
|
||||
Namespace Consideration When Not Using `FileModuleResolver`
|
||||
---------------------------------------------------------
|
||||
|
||||
[Modules] can be dynamically loaded into a Rhai script using the [`import`] keyword.
|
||||
When that happens, functions defined within the [module] can be called with a _qualified_ name.
|
||||
|
||||
There is a catch, though, if functions in a module script refer to global functions
|
||||
defined _within the script_. When called later, those functions will be searched in the
|
||||
current global namespace and may not be found.
|
||||
The [`FileModuleResolver`][module resolver] encapsulates the namespace inside the module itself,
|
||||
so everything works as expected. A function defined in the module script cannot access functions
|
||||
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
|
||||
Using GlobalFileModuleResolver
|
||||
==============================
|
||||
|
||||
-----------------
|
||||
| greeting.rhai |
|
||||
-----------------
|
||||
|
||||
fn message() { "Hello!" };
|
||||
fn get_message() { "Hello!" };
|
||||
|
||||
fn say_hello() { print(message()); }
|
||||
|
||||
say_hello(); // 'message' is looked up in the global namespace
|
||||
fn say_hello() {
|
||||
print(get_message()); // 'get_message' is looked up in the global namespace
|
||||
// when exported
|
||||
}
|
||||
|
||||
say_hello(); // Here, 'get_message' is found in the module namespace
|
||||
|
||||
---------------
|
||||
| script.rhai |
|
||||
---------------
|
||||
|
||||
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),
|
||||
@ -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
|
||||
function named '`message`' in the global namespace.
|
||||
|
||||
Therefore, when writing functions for a [module], make sure that those functions are as _pure_
|
||||
as possible and avoid cross-calling them from each other. A [function pointer] is a valid technique
|
||||
to call another function within a module-defined function:
|
||||
Therefore, when writing functions for a [module] intended for the [`GlobalFileModuleResolver`][module resolver],
|
||||
make sure that those functions are as _pure_ as possible and avoid cross-calling them from each other.
|
||||
|
||||
A [function pointer] is a valid technique to call another function in an environment-independent manner:
|
||||
|
||||
```rust
|
||||
-----------------
|
||||
| greeting.rhai |
|
||||
-----------------
|
||||
|
||||
fn message() { "Hello!" };
|
||||
fn get_message() { "Hello!" };
|
||||
|
||||
fn say_hello(msg_func) { // 'msg_func' is a function pointer
|
||||
print(msg_func.call()); // call via the function pointer
|
||||
}
|
||||
|
||||
say_hello(); // 'message' is looked up in the global namespace
|
||||
say_hello(Fn("get_message"));
|
||||
|
||||
|
||||
---------------
|
||||
@ -149,7 +162,7 @@ import "greeting" as g;
|
||||
|
||||
fn my_msg() {
|
||||
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!'
|
||||
|
@ -7,19 +7,19 @@ The following are reserved keywords in Rhai:
|
||||
|
||||
| Active keywords | Reserved keywords | Usage | Inactive under feature |
|
||||
| ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: |
|
||||
| `true`, `false` | | Boolean constants | |
|
||||
| `let`, `const` | `var`, `static` | Variable declarations | |
|
||||
| `is_shared` | | Shared values | [`no_closure`] |
|
||||
| `if`, `else` | `then`, `goto`, `exit` | Control flow | |
|
||||
| | `switch`, `match`, `case` | Matching | |
|
||||
| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | Looping | |
|
||||
| `fn`, `private` | `public`, `new` | Functions | [`no_function`] |
|
||||
| `return` | | Return values | |
|
||||
| `throw` | `try`, `catch` | Throw exceptions | |
|
||||
| `import`, `export`, `as` | `use`, `with`, `module`, `package` | Modules/packages | [`no_module`] |
|
||||
| `Fn`, `call`, `curry` | | Function pointers | |
|
||||
| | `spawn`, `go`, `sync`, `async`, `await`, `yield` | Threading/async | |
|
||||
| `type_of`, `print`, `debug`, `eval` | | Special functions | |
|
||||
| | `default`, `void`, `null`, `nil` | Special values | |
|
||||
| `true`, `false` | | boolean constants | |
|
||||
| `let`, `const` | `var`, `static` | variable declarations | |
|
||||
| `is_shared` | | shared values | [`no_closure`] |
|
||||
| `if`, `else` | `then`, `goto`, `exit` | control flow | |
|
||||
| | `switch`, `match`, `case` | matching | |
|
||||
| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | looping | |
|
||||
| `fn`, `private` | `public`, `new` | functions | [`no_function`] |
|
||||
| `return` | | return values | |
|
||||
| `throw` | `try`, `catch` | throw exceptions | |
|
||||
| `import`, `export`, `as` | `use`, `with`, `module`, `package` | modules/packages | [`no_module`] |
|
||||
| `Fn`, `call`, `curry` | | function pointers | |
|
||||
| | `spawn`, `go`, `sync`, `async`, `await`, `yield` | threading/async | |
|
||||
| `type_of`, `print`, `debug`, `eval` | | special functions | |
|
||||
| | `default`, `void`, `null`, `nil` | special values | |
|
||||
|
||||
Keywords cannot become the name of a [function] or [variable], even when they are disabled.
|
||||
|
@ -45,11 +45,11 @@ Boolean operators
|
||||
|
||||
| Operator | Description |
|
||||
| ----------------- | ------------------------------------- |
|
||||
| `!` | Boolean _Not_ |
|
||||
| `&&` | Boolean _And_ (short-circuits) |
|
||||
| <code>\|\|</code> | Boolean _Or_ (short-circuits) |
|
||||
| `&` | Boolean _And_ (doesn't short-circuit) |
|
||||
| <code>\|</code> | Boolean _Or_ (doesn't short-circuit) |
|
||||
| `!` | boolean _Not_ |
|
||||
| `&&` | boolean _And_ (short-circuits) |
|
||||
| <code>\|\|</code> | boolean _Or_ (short-circuits) |
|
||||
| `&` | boolean _And_ (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
|
||||
if the first one already proves the condition wrong.
|
||||
|
@ -49,7 +49,7 @@ The following table illustrates the differences:
|
||||
|
||||
| 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) {}` |
|
||||
|
||||
|
||||
|
@ -9,10 +9,10 @@ Integer Functions
|
||||
The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`])
|
||||
operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only:
|
||||
|
||||
| Function | No available under | Description |
|
||||
| -------- | :----------------: | ---------------------------------------------------------------------- |
|
||||
| `abs` | | absolute value |
|
||||
| `sign` | | return -1 (`INT`) if the number is negative, +1 if positive, 0 if zero |
|
||||
| Function | No available under | Description |
|
||||
| -------- | :----------------: | ----------------------------------------------------------------------- |
|
||||
| `abs` | | absolute value |
|
||||
| `sign` | | returns -1 (`INT`) if the number is negative, +1 if positive, 0 if zero |
|
||||
|
||||
|
||||
Floating-Point Functions
|
||||
@ -39,8 +39,8 @@ Conversion Functions
|
||||
The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`])
|
||||
parse numbers:
|
||||
|
||||
| Function | No available under | Description |
|
||||
| --------------- | :----------------: | -------------------------------------------------- |
|
||||
| [`to_float`] | [`no_float`] | convert an integer type to `FLOAT` |
|
||||
| [`parse_int`] | | convert a [string] to `INT` with an optional radix |
|
||||
| [`parse_float`] | [`no_float`] | convert a [string] to `FLOAT` |
|
||||
| Function | No available under | Description |
|
||||
| --------------- | :----------------: | --------------------------------------------------- |
|
||||
| [`to_float`] | [`no_float`] | converts an integer type to `FLOAT` |
|
||||
| [`parse_int`] | | converts a [string] to `INT` with an optional radix |
|
||||
| [`parse_float`] | [`no_float`] | converts a [string] to `FLOAT` |
|
||||
|
@ -10,8 +10,8 @@ Unary Operators
|
||||
|
||||
| Operator | Description |
|
||||
| -------- | ----------- |
|
||||
| `+` | Positive |
|
||||
| `-` | Negative |
|
||||
| `+` | positive |
|
||||
| `-` | negative |
|
||||
|
||||
```rust
|
||||
let number = -5;
|
||||
@ -24,17 +24,17 @@ Binary Operators
|
||||
|
||||
| Operator | Description | Integers only |
|
||||
| --------------- | ---------------------------------------------------- | :-----------: |
|
||||
| `+` | Plus | |
|
||||
| `-` | Minus | |
|
||||
| `*` | Multiply | |
|
||||
| `/` | Divide (integer division if acting on integer types) | |
|
||||
| `%` | Modulo (remainder) | |
|
||||
| `~` | Power | |
|
||||
| `&` | Bit-wise _And_ | Yes |
|
||||
| <code>\|</code> | Bit-wise _Or_ | Yes |
|
||||
| `^` | Bit-wise _Xor_ | Yes |
|
||||
| `<<` | Left bit-shift | Yes |
|
||||
| `>>` | Right bit-shift | Yes |
|
||||
| `+` | plus | |
|
||||
| `-` | minus | |
|
||||
| `*` | multiply | |
|
||||
| `/` | divide (integer division if acting on integer types) | |
|
||||
| `%` | modulo (remainder) | |
|
||||
| `~` | power | |
|
||||
| `&` | bit-wise _And_ | Yes |
|
||||
| <code>\|</code> | bit-wise _Or_ | Yes |
|
||||
| `^` | bit-wise _Xor_ | Yes |
|
||||
| `<<` | left bit-shift | Yes |
|
||||
| `>>` | right bit-shift | Yes |
|
||||
|
||||
```rust
|
||||
let x = (1 + 2) * (6 - 4) / 2; // arithmetic, with parentheses
|
||||
|
@ -11,11 +11,11 @@ individual functions instead of a full-blown [plugin module].
|
||||
Macros
|
||||
------
|
||||
|
||||
| Macro | Apply to | Behavior |
|
||||
| ----------------------- | --------------------------------------------------------------- | -------------------------------------------------------- |
|
||||
| `#[export_fn]` | Rust function defined in a Rust module | Export the function |
|
||||
| `register_exported_fn!` | [`Engine`] instance, register name string, use path to function | Register 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 |
|
||||
| Macro | Apply to | Description |
|
||||
| ----------------------- | --------------------------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `#[export_fn]` | rust function defined in a Rust module | exports the function |
|
||||
| `register_exported_fn!` | [`Engine`] instance, register name string, use path to function | registers the function into an [`Engine`] under specific name |
|
||||
| `set_exported_fn!` | [`Module`] instance, register name string, use path to function | registers the function into an [`Module`] under specific name |
|
||||
|
||||
|
||||
`#[export_fn]` and `register_exported_fn!`
|
||||
|
@ -204,11 +204,11 @@ The above function can be called in five ways:
|
||||
|
||||
| Parameter for `#[rhai_fn(...)]` | Type | Call style |
|
||||
| ------------------------------- | :-------------: | --------------------------------------------- |
|
||||
| `name = "get_prop_value"` | Method function | `get_prop_value(x, 0)`, `x.get_prop_value(0)` |
|
||||
| `name = "prop"` | Method function | `prop(x, 0)`, `x.prop(0)` |
|
||||
| `name = "+"` | Operator | `x + 42` |
|
||||
| `set = "prop"` | Setter | `x.prop = 42` |
|
||||
| `index_get` | Index getter | `x[0]` |
|
||||
| `name = "get_prop_value"` | method function | `get_prop_value(x, 0)`, `x.get_prop_value(0)` |
|
||||
| `name = "prop"` | method function | `prop(x, 0)`, `x.prop(0)` |
|
||||
| `name = "+"` | operator | `x + 42` |
|
||||
| `set = "prop"` | setter | `x.prop = 42` |
|
||||
| `index_get` | index getter | `x[0]` |
|
||||
|
||||
|
||||
Fallible Functions
|
||||
@ -244,11 +244,11 @@ mod my_module {
|
||||
|
||||
Parameters can be applied to the `#[export_module]` attribute to override its default behavior.
|
||||
|
||||
| Parameter | Behavior |
|
||||
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| _None_ | Export 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_prefix = "..."` | Export functions (including private, non-`pub` functions) with names starting with a specific prefix |
|
||||
| Parameter | Description |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| _none_ | exports only public (i.e. `pub`) functions |
|
||||
| `export_all` | exports all functions (including private, non-`pub` functions); use `#[rhai_fn(skip)]` on individual functions to avoid export |
|
||||
| `export_prefix = "..."` | exports functions (including private, non-`pub` functions) with names starting with a specific prefix |
|
||||
|
||||
|
||||
Inner Attributes
|
||||
@ -260,12 +260,12 @@ Inner attributes can be applied to the inner items of a module to tweak the expo
|
||||
|
||||
Parameters should be set on inner attributes to specify the desired behavior.
|
||||
|
||||
| Attribute Parameter | Use with | Apply to | Behavior |
|
||||
| ------------------- | --------------------------- | -------------------------------------------------------- | ----------------------------------------------------- |
|
||||
| `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 |
|
||||
| `get = "..."` | `#[rhai_fn]` | Function with `&mut` first parameter | Register a getter for the named property |
|
||||
| `set = "..."` | `#[rhai_fn]` | Function with `&mut` first parameter | Register a setter for the named property |
|
||||
| `index_get` | `#[rhai_fn]` | Function with `&mut` first parameter | Register an index getter |
|
||||
| `index_set` | `#[rhai_fn]` | Function with `&mut` first parameter | Register an index setter |
|
||||
| `return_raw` | `#[rhai_fn]` | Function returning `Result<Dynamic, Box<EvalAltResult>>` | Mark this as a [fallible function] |
|
||||
| Attribute Parameter | Use with | Apply to | Description |
|
||||
| ------------------- | --------------------------- | -------------------------------------------------------- | ------------------------------------------------------ |
|
||||
| `skip` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | do not export this function/sub-module |
|
||||
| `name = "..."` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | registers function/sub-module under the specified name |
|
||||
| `get = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a getter for the named property |
|
||||
| `set = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a setter for the named property |
|
||||
| `index_get` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index getter |
|
||||
| `index_set` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index setter |
|
||||
| `return_raw` | `#[rhai_fn]` | function returning `Result<Dynamic, Box<EvalAltResult>>` | marks this as a [fallible function] |
|
||||
|
@ -161,8 +161,8 @@ x.type_of() == "Hello";
|
||||
Use the Custom Type With Arrays
|
||||
------------------------------
|
||||
|
||||
The `push` and `pad` functions, as well as the `+=` operator, for [arrays] are only defined for
|
||||
standard built-in types. For custom types, type-specific versions must be registered:
|
||||
The `push`, `insert`, `pad` functions, as well as the `+=` operator, for [arrays] are only
|
||||
defined for standard built-in types. For custom types, type-specific versions must be registered:
|
||||
|
||||
```rust
|
||||
engine
|
||||
@ -170,6 +170,14 @@ engine
|
||||
list.push(Dynamic::from(item));
|
||||
}).register_fn("+=", |list: &mut Array, item: TestStruct| {
|
||||
list.push(Dynamic::from(item));
|
||||
}).register_fn("insert", |list: &mut Array, position: i64, item: TestStruct| {
|
||||
if position <= 0 {
|
||||
list.insert(0, Dynamic::from(item));
|
||||
} else if (position as usize) >= list.len() - 1 {
|
||||
list.push(item);
|
||||
} else {
|
||||
list.insert(position as usize, Dynamic::from(item));
|
||||
}
|
||||
}).register_fn("pad", |list: &mut Array, len: i64, item: TestStruct| {
|
||||
if len as usize > list.len() {
|
||||
list.resize(len as usize, item);
|
||||
|
@ -11,11 +11,11 @@ Getters and setters are disabled when the [`no_object`] feature is used.
|
||||
|
||||
| `Engine` API | Description | Return Value of Function |
|
||||
| --------------------- | ------------------------------------------------- | :-----------------------------------: |
|
||||
| `register_get` | Register a getter | _Any_ |
|
||||
| `register_set` | Register 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_set_result` | Register a setter | `Result<(), Box<EvalAltResult>>` |
|
||||
| `register_get` | register a getter | _any_ |
|
||||
| `register_set` | register 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_set_result` | register a setter | `Result<(), Box<EvalAltResult>>` |
|
||||
|
||||
|
||||
Cannot Override Object Maps
|
||||
|
@ -15,11 +15,11 @@ Indexers are disabled when the [`no_index`] feature is used.
|
||||
|
||||
| `Engine` API | Description | Return Value of Function |
|
||||
| ----------------------------- | -------------------------------------------------------- | :-----------------------------------: |
|
||||
| `register_indexer_get` | Register an index getter | _Any_ |
|
||||
| `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_result` | Register an index getter | `Result<Dynamic, Box<EvalAltResult>>` |
|
||||
| `register_indexer_set_result` | Register an index setter | `Result<(), Box<EvalAltResult>>` |
|
||||
| `register_indexer_get` | register an index getter | _any_ |
|
||||
| `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_result` | register an index getter | `Result<Dynamic, Box<EvalAltResult>>` |
|
||||
| `register_indexer_set_result` | register an index setter | `Result<(), Box<EvalAltResult>>` |
|
||||
|
||||
|
||||
Cannot Override Arrays, Object Maps and Strings
|
||||
|
@ -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.
|
||||
|
||||
| Module Resolver | Description |
|
||||
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `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. |
|
||||
| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. |
|
||||
| `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. |
|
||||
| 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. | Module (cannot access global namespace) |
|
||||
| `GlobalFileModuleResolver` | A simpler but more efficient version of `FileModuleResolver`, intended for short utility modules. Not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.<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 |
|
||||
| `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`
|
||||
|
@ -9,19 +9,19 @@ Built-In Packages
|
||||
|
||||
| Package | Description | In `Core` | In `Standard` |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------------------ | :-------: | :-----------: |
|
||||
| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) for numeric types that are not built in (e.g. `u16`) | Yes | Yes |
|
||||
| `BasicIteratorPackage` | Numeric ranges (e.g. `range(1, 10)`) | Yes | Yes |
|
||||
| `LogicPackage` | Logical and comparison operators (e.g. `==`, `>`) for numeric types that are not built in (e.g. `u16`) | Yes | Yes |
|
||||
| `BasicStringPackage` | Basic string functions (e.g. `print`, `debug`, `len`) that are not built in | Yes | Yes |
|
||||
| `BasicTimePackage` | Basic time functions (e.g. [timestamps]) | Yes | Yes |
|
||||
| `MoreStringPackage` | Additional string functions, including converting common types to string | No | Yes |
|
||||
| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes |
|
||||
| `BasicArrayPackage` | Basic [array] functions (not available under `no_index`) | No | Yes |
|
||||
| `BasicMapPackage` | Basic [object map] functions (not available under `no_object`) | No | Yes |
|
||||
| `BasicFnPackage` | Basic methods for [function pointers]. | Yes | Yes |
|
||||
| `EvalPackage` | Disable [`eval`] | No | No |
|
||||
| `CorePackage` | Basic essentials | Yes | Yes |
|
||||
| `StandardPackage` | Standard library (default for `Engine::new`) | No | 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 |
|
||||
| `LogicPackage` | logical and comparison operators (e.g. `==`, `>`) for numeric types that are not built in (e.g. `u16`) | yes | yes |
|
||||
| `BasicStringPackage` | basic string functions (e.g. `print`, `debug`, `len`) that are not built in | yes | yes |
|
||||
| `BasicTimePackage` | basic time functions (e.g. [timestamps]) | yes | yes |
|
||||
| `MoreStringPackage` | additional string functions, including converting common types to string | no | yes |
|
||||
| `BasicMathPackage` | basic math functions (e.g. `sin`, `sqrt`) | no | yes |
|
||||
| `BasicArrayPackage` | basic [array] functions (not available under `no_index`) | no | yes |
|
||||
| `BasicMapPackage` | basic [object map] functions (not available under `no_object`) | no | yes |
|
||||
| `BasicFnPackage` | basic methods for [function pointers]. | yes | yes |
|
||||
| `EvalPackage` | disable [`eval`] | no | no |
|
||||
| `CorePackage` | basic essentials | yes | yes |
|
||||
| `StandardPackage` | standard library (default for `Engine::new`) | no | yes |
|
||||
|
||||
|
||||
Load the `CorePackage`
|
||||
|
@ -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
|
||||
is `T : Display + Debug`):
|
||||
|
||||
| Function | Signature | Typical implementation | Usage |
|
||||
| ----------- | ------------------------------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------- |
|
||||
| `to_string` | <code>\|s: &mut T\| -> ImmutableString</code> | `s.to_string().into()` | Converts the custom type into a [string] |
|
||||
| `print` | <code>\|s: &mut T\| -> ImmutableString</code> | `s.to_string().into()` | Converts the custom type into a [string] for the [`print`] statement |
|
||||
| `debug` | <code>\|s: &mut T\| -> ImmutableString</code> | `format!("{:?}", s).into()` | Converts the custom type into a [string] for the [`debug`] statement |
|
||||
| `+` | <code>\|s1: ImmutableString, s: T\| -> ImmutableString</code> | `s1 + s` | Append the custom type to another [string], for `print("Answer: " + type);` usage |
|
||||
| `+` | <code>\|s: T, s2: ImmutableString\| -> ImmutableString</code> | `s.to_string().push_str(&s2).into();` | Append another [string] to the custom type, for `print(type + " is the answer");` usage |
|
||||
| `+=` | <code>\|s1: &mut ImmutableString, s: T\|</code> | `s1 += s.to_string()` | Append the custom type to an existing [string], for `s += type;` usage |
|
||||
| Function | Signature | Typical implementation | Usage |
|
||||
| ----------- | ------------------------------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| `to_string` | <code>\|s: &mut T\| -> ImmutableString</code> | `s.to_string().into()` | converts the custom type into a [string] |
|
||||
| `print` | <code>\|s: &mut T\| -> ImmutableString</code> | `s.to_string().into()` | converts the custom type into a [string] for the [`print`] 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` | 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();` | 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()` | appends the custom type to an existing [string], for `s += type;` usage |
|
||||
|
@ -84,12 +84,12 @@ Extract Arguments
|
||||
|
||||
To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following:
|
||||
|
||||
| Argument type | Access (`n` = argument position) | Result |
|
||||
| ------------------------------ | ------------------------------------- | ---------------------------------------------------------- |
|
||||
| [Primary type][standard types] | `args[n].clone().cast::<T>()` | Copy of 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 `()`. |
|
||||
| `this` object | `args[0].write_lock::<T>().unwrap()` | Mutable reference to value. |
|
||||
| Argument type | Access (`n` = argument position) | Result |
|
||||
| ------------------------------ | ------------------------------------- | --------------------------------------------------------- |
|
||||
| [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 (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 |
|
||||
|
||||
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.
|
||||
|
@ -7,7 +7,7 @@ A number of traits, under the `rhai::` module namespace, provide additional func
|
||||
|
||||
| Trait | Description | Methods |
|
||||
| ------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------- |
|
||||
| `RegisterFn` | Trait for registering functions | `register_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` |
|
||||
| `ModuleResolver` | Trait implemented by module resolution services | `resolve` |
|
||||
| `RegisterFn` | trait for registering functions | `register_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` |
|
||||
| `ModuleResolver` | trait implemented by module resolution services | `resolve` |
|
||||
|
@ -19,7 +19,7 @@ This check can be disabled via the [`unchecked`] feature for higher performance
|
||||
```rust
|
||||
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
|
||||
```
|
||||
|
@ -5,17 +5,17 @@ Rust Examples
|
||||
|
||||
A number of examples can be found in the `examples` directory:
|
||||
|
||||
| Example | Description |
|
||||
| ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`arrays_and_structs`]({{repoTree}}/examples/arrays_and_structs.rs) | Shows how to register a custom Rust type and using [arrays] on it. |
|
||||
| [`custom_types_and_methods`]({{repoTree}}/examples/custom_types_and_methods.rs) | Shows how to register a custom Rust type and methods for it. |
|
||||
| [`hello`]({{repoTree}}/examples/hello.rs) | Simple example that evaluates an expression and prints the result. |
|
||||
| [`reuse_scope`]({{repoTree}}/examples/reuse_scope.rs) | Evaluates two pieces of code in separate runs, but using a common [`Scope`]. |
|
||||
| [`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. |
|
||||
| [`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. |
|
||||
| [`repl`]({{repoTree}}/examples/repl.rs) | A simple REPL, interactively evaluate statements from stdin. |
|
||||
| Example | Description |
|
||||
| ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`arrays_and_structs`]({{repoTree}}/examples/arrays_and_structs.rs) | shows how to register a custom Rust type and using [arrays] on it |
|
||||
| [`custom_types_and_methods`]({{repoTree}}/examples/custom_types_and_methods.rs) | shows how to register a custom Rust type and methods for it |
|
||||
| [`hello`]({{repoTree}}/examples/hello.rs) | simple example that evaluates an expression and prints the result |
|
||||
| [`reuse_scope`]({{repoTree}}/examples/reuse_scope.rs) | evaluates two pieces of code in separate runs, but using a common [`Scope`] |
|
||||
| [`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 |
|
||||
| [`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 |
|
||||
| [`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
|
||||
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:
|
||||
|
||||
| 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 |
|
||||
| Sample | Description | Optimization | Allocator | Panics |
|
||||
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | :----------: | :-----------------------------------------------: | :----: |
|
||||
| [`no_std_test`]({{repoTree}}/no_std/no_std_test) | bare-bones test application that evaluates a Rhai expression and sets the result as the return value | size | [`wee_alloc`](https://crates.io/crates/wee_alloc) | abort |
|
||||
|
||||
`cargo run` cannot be used to run a `no-std` sample. It must first be built:
|
||||
|
||||
|
@ -10,22 +10,22 @@ There are also a number of examples scripts that showcase Rhai's features, all i
|
||||
|
||||
| Script | Description |
|
||||
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| [`array.rhai`]({{repoTree}}/scripts/array.rhai) | [Arrays] |
|
||||
| [`assignment.rhai`]({{repoTree}}/scripts/assignment.rhai) | Variable declarations |
|
||||
| [`comments.rhai`]({{repoTree}}/scripts/comments.rhai) | Just comments |
|
||||
| [`array.rhai`]({{repoTree}}/scripts/array.rhai) | [arrays] |
|
||||
| [`assignment.rhai`]({{repoTree}}/scripts/assignment.rhai) | variable declarations |
|
||||
| [`comments.rhai`]({{repoTree}}/scripts/comments.rhai) | just comments |
|
||||
| [`for1.rhai`]({{repoTree}}/scripts/for1.rhai) | [`for`]({{rootUrl}}/language/for.md) loops |
|
||||
| [`for2.rhai`]({{repoTree}}/scripts/for2.rhai) | [`for`]({{rootUrl}}/language/for.md) loops on [arrays] |
|
||||
| [`function_decl1.rhai`]({{repoTree}}/scripts/function_decl1.rhai) | A [function] without parameters |
|
||||
| [`function_decl2.rhai`]({{repoTree}}/scripts/function_decl2.rhai) | A [function] with two parameters |
|
||||
| [`function_decl3.rhai`]({{repoTree}}/scripts/function_decl3.rhai) | A [function] with many parameters |
|
||||
| [`function_decl1.rhai`]({{repoTree}}/scripts/function_decl1.rhai) | a [function] without parameters |
|
||||
| [`function_decl2.rhai`]({{repoTree}}/scripts/function_decl2.rhai) | a [function] with two parameters |
|
||||
| [`function_decl3.rhai`]({{repoTree}}/scripts/function_decl3.rhai) | a [function] with many parameters |
|
||||
| [`if1.rhai`]({{repoTree}}/scripts/if1.rhai) | [`if`]({{rootUrl}}/language/if.md) example |
|
||||
| [`loop.rhai`]({{repoTree}}/scripts/loop.rhai) | Count-down [`loop`]({{rootUrl}}/language/loop.md) in Rhai, emulating a `do` .. `while` loop |
|
||||
| [`oop.rhai`]({{repoTree}}/scripts/oop.rhai) | Simulate [object-oriented programming (OOP)][OOP] with [closures] |
|
||||
| [`op1.rhai`]({{repoTree}}/scripts/op1.rhai) | Just simple addition |
|
||||
| [`op2.rhai`]({{repoTree}}/scripts/op2.rhai) | Simple addition and multiplication |
|
||||
| [`op3.rhai`]({{repoTree}}/scripts/op3.rhai) | Change evaluation order with parenthesis |
|
||||
| [`string.rhai`]({{repoTree}}/scripts/string.rhai) | [String] operations |
|
||||
| [`strings_map.rhai`]({{repoTree}}/scripts/strings_map.rhai) | [String] and [object map] operations |
|
||||
| [`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] |
|
||||
| [`op1.rhai`]({{repoTree}}/scripts/op1.rhai) | just simple addition |
|
||||
| [`op2.rhai`]({{repoTree}}/scripts/op2.rhai) | simple addition and multiplication |
|
||||
| [`op3.rhai`]({{repoTree}}/scripts/op3.rhai) | change evaluation order with parenthesis |
|
||||
| [`string.rhai`]({{repoTree}}/scripts/string.rhai) | [string] operations |
|
||||
| [`strings_map.rhai`]({{repoTree}}/scripts/strings_map.rhai) | [string] and [object map] operations |
|
||||
| [`while.rhai`]({{repoTree}}/scripts/while.rhai) | [`while`]({{rootUrl}}/language/while.md) loop |
|
||||
|
||||
|
||||
@ -34,12 +34,12 @@ Benchmark Scripts
|
||||
|
||||
The following scripts are for benchmarking the speed of Rhai:
|
||||
|
||||
| Scripts | Description |
|
||||
| --------------------------------------------------------- | --------------------------------------------------------------------------------------- |
|
||||
| [`speed_test.rhai`]({{repoTree}}/scripts/speed_test.rhai) | A simple application to measure the speed of Rhai's interpreter (1 million iterations). |
|
||||
| [`primes.rhai`]({{repoTree}}/scripts/primes.rhai) | Use Sieve of Eratosthenes to find all primes smaller than a limit. |
|
||||
| [`fibonacci.rhai`]({{repoTree}}/scripts/fibonacci.rhai) | Calculate the n-th Fibonacci number using a really dumb algorithm. |
|
||||
| [`mat_mul.rhai`]({{repoTree}}/scripts/mat_mul.rhai) | Matrix multiplication test to measure the speed of multi-dimensional array access. |
|
||||
| Scripts | Description |
|
||||
| --------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| [`speed_test.rhai`]({{repoTree}}/scripts/speed_test.rhai) | a simple application to measure the speed of Rhai's interpreter (1 million iterations) |
|
||||
| [`primes.rhai`]({{repoTree}}/scripts/primes.rhai) | use Sieve of Eratosthenes to find all primes smaller than a limit |
|
||||
| [`fibonacci.rhai`]({{repoTree}}/scripts/fibonacci.rhai) | calculate the n-th Fibonacci number using a really dumb algorithm |
|
||||
| [`mat_mul.rhai`]({{repoTree}}/scripts/mat_mul.rhai) | matrix multiplication test to measure the speed of multi-dimensional array access |
|
||||
|
||||
|
||||
Running Example Scripts
|
||||
|
@ -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
|
||||
more control over what a script can (or cannot) do.
|
||||
|
||||
| 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! |
|
||||
| `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`. |
|
||||
| `no_optimize` | No | Disable [script optimization]. |
|
||||
| `no_float` | No | Disable 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_i64` | No | Set 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_object` | No | Disable support for [custom types] and [object maps]. |
|
||||
| `no_function` | No | Disable script-defined [functions]. |
|
||||
| `no_module` | No | Disable 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_std` | No | Build 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. |
|
||||
| `internals` | Yes | Expose 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. |
|
||||
| Feature | Additive? | Description |
|
||||
| ------------------- | :-------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `unchecked` | no | disables arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.<br/>Beware that a bad script may panic the entire system! |
|
||||
| `sync` | no | restricts all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync` |
|
||||
| `no_optimize` | no | disables [script optimization] |
|
||||
| `no_float` | no | disables floating-point numbers and math |
|
||||
| `only_i32` | no | sets the system integer type to `i32` and disable all other integer types. `INT` is set to `i32` |
|
||||
| `only_i64` | no | sets the system integer type to `i64` and disable all other integer types. `INT` is set to `i64` |
|
||||
| `no_index` | no | disables [arrays] and indexing features |
|
||||
| `no_object` | no | disables support for [custom types] and [object maps] |
|
||||
| `no_function` | no | disables script-defined [functions] |
|
||||
| `no_module` | no | disables loading external [modules] |
|
||||
| `no_closure` | no | disables [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls |
|
||||
| `no_std` | no | builds for `no-std` (implies `no_closure`). Notice that additional dependencies will be pulled in to replace `std` features |
|
||||
| `serde` | yes | enables serialization/deserialization via `serde`. Notice that the [`serde`](https://crates.io/crates/serde) crate will be pulled in together with its dependencies |
|
||||
| `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 | allows [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/) as identifiers |
|
||||
|
||||
|
||||
Example
|
||||
|
16
src/api.rs
16
src/api.rs
@ -1502,7 +1502,9 @@ impl Engine {
|
||||
args: A,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
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());
|
||||
|
||||
@ -1574,7 +1576,9 @@ impl Engine {
|
||||
mut this_ptr: Option<&mut Dynamic>,
|
||||
mut arg_values: impl AsMut<[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.
|
||||
@ -1592,16 +1596,14 @@ impl Engine {
|
||||
lib: impl AsRef<Module>,
|
||||
name: &str,
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
arg_values: &mut [Dynamic],
|
||||
args: &mut [&mut Dynamic],
|
||||
) -> FuncReturn<Dynamic> {
|
||||
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)
|
||||
.ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::none()))?;
|
||||
|
||||
let mut state = State::new();
|
||||
let mut mods = Imports::new();
|
||||
let args = args.as_mut();
|
||||
|
||||
// Check for data race.
|
||||
if cfg!(not(feature = "no_closure")) {
|
||||
@ -1634,8 +1636,8 @@ impl Engine {
|
||||
let lib = if cfg!(not(feature = "no_function")) {
|
||||
ast.lib()
|
||||
.iter_fn()
|
||||
.filter(|(_, _, _, f)| f.is_script())
|
||||
.map(|(_, _, _, f)| f.get_fn_def().clone())
|
||||
.filter(|(_, _, _, _, f)| f.is_script())
|
||||
.map(|(_, _, _, _, f)| f.get_fn_def().clone())
|
||||
.collect()
|
||||
} else {
|
||||
Default::default()
|
||||
|
369
src/module.rs
369
src/module.rs
@ -5,13 +5,13 @@ use crate::calc_fn_hash;
|
||||
use crate::engine::Engine;
|
||||
use crate::fn_native::{CallableFunction as Func, FnCallArgs, IteratorFn, SendSync};
|
||||
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::token::{Position, Token};
|
||||
use crate::utils::{ImmutableString, StaticVec, StraightHasherBuilder};
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
use crate::fn_native::Shared;
|
||||
use crate::{fn_native::Shared, parser::ScriptFnDef};
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
use crate::{
|
||||
@ -67,7 +67,11 @@ pub struct Module {
|
||||
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
|
||||
|
||||
/// 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.
|
||||
type_iterators: HashMap<TypeId, IteratorFn>,
|
||||
@ -97,7 +101,7 @@ impl fmt::Debug for Module {
|
||||
.join(", "),
|
||||
self.functions
|
||||
.values()
|
||||
.map(|(_, _, _, f)| f.to_string())
|
||||
.map(|(_, _, _, _, f)| f.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
)
|
||||
@ -258,20 +262,23 @@ impl 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.
|
||||
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.
|
||||
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(
|
||||
hash_script,
|
||||
(
|
||||
fn_def.name.to_string(),
|
||||
fn_def.access,
|
||||
Default::default(),
|
||||
num_params,
|
||||
None,
|
||||
fn_def.into(),
|
||||
),
|
||||
);
|
||||
self.indexed = false;
|
||||
self
|
||||
hash_script
|
||||
}
|
||||
|
||||
/// Does a sub-module exist in the module?
|
||||
@ -362,7 +369,7 @@ impl Module {
|
||||
} else if public_only {
|
||||
self.functions
|
||||
.get(&hash_fn)
|
||||
.map(|(_, access, _, _)| match access {
|
||||
.map(|(_, access, _, _, _)| match access {
|
||||
FnAccess::Public => true,
|
||||
FnAccess::Private => false,
|
||||
})
|
||||
@ -412,7 +419,7 @@ impl Module {
|
||||
let hash_fn = calc_fn_hash(empty(), &name, args_len, arg_types.iter().cloned());
|
||||
|
||||
self.functions
|
||||
.insert(hash_fn, (name, access, params, func.into()));
|
||||
.insert(hash_fn, (name, access, args_len, Some(params), func.into()));
|
||||
|
||||
self.indexed = false;
|
||||
|
||||
@ -487,6 +494,32 @@ impl Module {
|
||||
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.
|
||||
///
|
||||
/// If there is a similar existing Rust function, it is replaced.
|
||||
@ -979,7 +1012,7 @@ impl Module {
|
||||
} else {
|
||||
self.functions
|
||||
.get(&hash_fn)
|
||||
.and_then(|(_, access, _, f)| match access {
|
||||
.and_then(|(_, access, _, _, f)| match access {
|
||||
_ if !public_only => Some(f),
|
||||
FnAccess::Public => Some(f),
|
||||
FnAccess::Private => None,
|
||||
@ -1028,14 +1061,14 @@ impl Module {
|
||||
|
||||
/// Merge another module into this module.
|
||||
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.
|
||||
pub(crate) fn merge_filtered(
|
||||
&mut self,
|
||||
other: &Self,
|
||||
_filter: &impl Fn(FnAccess, &str, usize) -> bool,
|
||||
mut _filter: &mut impl FnMut(FnAccess, &str, usize) -> bool,
|
||||
) -> &mut Self {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
for (k, v) in &other.modules {
|
||||
@ -1055,7 +1088,7 @@ impl Module {
|
||||
other
|
||||
.functions
|
||||
.iter()
|
||||
.filter(|(_, (_, _, _, v))| match v {
|
||||
.filter(|(_, (_, _, _, _, v))| match v {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Func::Script(ref f) => _filter(f.access, f.name.as_str(), f.params.len()),
|
||||
_ => true,
|
||||
@ -1076,9 +1109,9 @@ impl Module {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub(crate) fn retain_functions(
|
||||
&mut self,
|
||||
filter: impl Fn(FnAccess, &str, usize) -> bool,
|
||||
mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
|
||||
) -> &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()),
|
||||
_ => true,
|
||||
});
|
||||
@ -1110,7 +1143,7 @@ impl Module {
|
||||
/// Get an iterator to the functions in the module.
|
||||
pub(crate) fn iter_fn(
|
||||
&self,
|
||||
) -> impl Iterator<Item = &(String, FnAccess, StaticVec<TypeId>, Func)> {
|
||||
) -> impl Iterator<Item = &(String, FnAccess, usize, Option<StaticVec<TypeId>>, Func)> {
|
||||
self.functions.values()
|
||||
}
|
||||
|
||||
@ -1119,11 +1152,21 @@ impl Module {
|
||||
pub fn iter_script_fn<'a>(&'a self) -> impl Iterator<Item = Shared<ScriptFnDef>> + 'a {
|
||||
self.functions
|
||||
.values()
|
||||
.map(|(_, _, _, f)| f)
|
||||
.map(|(_, _, _, _, f)| f)
|
||||
.filter(|f| f.is_script())
|
||||
.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`.
|
||||
///
|
||||
/// # Examples
|
||||
@ -1194,7 +1237,7 @@ impl Module {
|
||||
variables.push((hash_var, value.clone()));
|
||||
}
|
||||
// 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 {
|
||||
// Private functions are not exported
|
||||
FnAccess::Private => continue,
|
||||
@ -1202,31 +1245,31 @@ impl Module {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if func.is_script() {
|
||||
let fn_def = func.get_shared_fn_def();
|
||||
// Qualifiers + function name + number of arguments.
|
||||
let hash_qualified_script = calc_fn_hash(
|
||||
qualifiers.iter().map(|&v| v),
|
||||
&fn_def.name,
|
||||
fn_def.params.len(),
|
||||
empty(),
|
||||
);
|
||||
functions.push((hash_qualified_script, fn_def.into()));
|
||||
if params.is_none() {
|
||||
let hash_qualified_script = if qualifiers.is_empty() {
|
||||
_hash
|
||||
} else {
|
||||
// Qualifiers + function name + number of arguments.
|
||||
calc_fn_hash(qualifiers.iter().map(|&v| v), &name, *_num_args, empty())
|
||||
};
|
||||
functions.push((hash_qualified_script, func.clone()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Qualified Rust functions are indexed in two steps:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
// i.e. qualifiers + function name + number of arguments.
|
||||
let hash_qualified_script =
|
||||
calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name,
|
||||
// zero number of arguments, and the actual list of argument `TypeId`'.s
|
||||
let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
|
||||
// 3) The final hash is the XOR of the two hashes.
|
||||
let hash_qualified_fn = hash_qualified_script ^ hash_fn_args;
|
||||
if let Some(params) = params {
|
||||
// Qualified Rust functions are indexed in two steps:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
// i.e. qualifiers + function name + number of arguments.
|
||||
let hash_qualified_script =
|
||||
calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name,
|
||||
// zero number of arguments, and the actual list of argument `TypeId`'.s
|
||||
let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
|
||||
// 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;
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use super::file::FileModuleResolver;
|
||||
pub use super::file::{FileModuleResolver, GlobalFileModuleResolver};
|
||||
pub use super::stat::StaticModuleResolver;
|
||||
}
|
||||
#[cfg(feature = "no_module")]
|
||||
@ -1355,6 +1398,181 @@ mod file {
|
||||
use super::*;
|
||||
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.
|
||||
///
|
||||
/// 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.set_extension(&self.extension); // Force extension
|
||||
|
||||
let scope = Default::default();
|
||||
|
||||
// See if it is cached
|
||||
let (module, ast) = {
|
||||
let exists = {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
let c = self.cache.borrow();
|
||||
#[cfg(feature = "sync")]
|
||||
let c = self.cache.read().unwrap();
|
||||
|
||||
match c.get(&file_path) {
|
||||
Some(ast) => (
|
||||
Module::eval_ast_as_new(scope, ast, engine)
|
||||
.map_err(|err| err.new_position(pos))?,
|
||||
None,
|
||||
),
|
||||
None => {
|
||||
// Load the file and compile it if not found
|
||||
let ast = engine
|
||||
.compile_file(file_path.clone())
|
||||
.map_err(|err| err.new_position(pos))?;
|
||||
|
||||
(
|
||||
Module::eval_ast_as_new(scope, &ast, engine)
|
||||
.map_err(|err| err.new_position(pos))?,
|
||||
Some(ast),
|
||||
)
|
||||
}
|
||||
}
|
||||
c.contains_key(&file_path)
|
||||
};
|
||||
|
||||
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
|
||||
#[cfg(not(feature = "sync"))]
|
||||
self.cache.borrow_mut().insert(file_path, ast);
|
||||
self.cache.borrow_mut().insert(file_path.clone(), ast);
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,13 @@ use crate::engine::{
|
||||
};
|
||||
use crate::fn_native::FnPtr;
|
||||
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::utils::StaticVec;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
use crate::parser::ReturnType;
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
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)
|
||||
// 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; }
|
||||
let fn_def = f.get_fn_def();
|
||||
fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
|
||||
@ -749,7 +752,8 @@ pub fn optimize_into_ast(
|
||||
level
|
||||
};
|
||||
|
||||
let lib = if cfg!(not(feature = "no_function")) {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let lib = {
|
||||
let mut module = Module::new();
|
||||
|
||||
if !level.is_none() {
|
||||
@ -811,10 +815,11 @@ pub fn optimize_into_ast(
|
||||
}
|
||||
|
||||
module
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
#[cfg(feature = "no_function")]
|
||||
let lib = Default::default();
|
||||
|
||||
AST::new(
|
||||
match level {
|
||||
OptimizationLevel::None => statements,
|
||||
|
@ -133,6 +133,10 @@ mod array_functions {
|
||||
list.clear();
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn reverse(list: &mut Array) {
|
||||
list.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
fn pad<T: Variant + Clone>(
|
||||
|
@ -130,10 +130,10 @@ impl AST {
|
||||
/// This operation is cheap because functions are shared.
|
||||
pub fn clone_functions_only_filtered(
|
||||
&self,
|
||||
filter: impl Fn(FnAccess, &str, usize) -> bool,
|
||||
mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
|
||||
) -> Self {
|
||||
let mut functions: Module = Default::default();
|
||||
functions.merge_filtered(&self.1, &filter);
|
||||
functions.merge_filtered(&self.1, &mut filter);
|
||||
Self(Default::default(), functions)
|
||||
}
|
||||
|
||||
@ -250,7 +250,7 @@ impl AST {
|
||||
pub fn merge_filtered(
|
||||
&self,
|
||||
other: &Self,
|
||||
filter: impl Fn(FnAccess, &str, usize) -> bool,
|
||||
mut filter: impl FnMut(FnAccess, &str, usize) -> bool,
|
||||
) -> Self {
|
||||
let Self(statements, functions) = self;
|
||||
|
||||
@ -266,7 +266,7 @@ impl AST {
|
||||
};
|
||||
|
||||
let mut functions = functions.clone();
|
||||
functions.merge_filtered(&other.1, &filter);
|
||||
functions.merge_filtered(&other.1, &mut filter);
|
||||
|
||||
Self::new(ast, functions)
|
||||
}
|
||||
@ -295,10 +295,16 @@ impl AST {
|
||||
/// # }
|
||||
/// ```
|
||||
#[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);
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn clear_functions(&mut self) {
|
||||
|
33
src/token.rs
33
src/token.rs
@ -731,6 +731,7 @@ pub struct TokenizeState {
|
||||
///
|
||||
/// This trait is volatile and may change.
|
||||
pub trait InputStream {
|
||||
fn unread(&mut self, ch: char);
|
||||
/// Get the next character
|
||||
fn get_next(&mut self) -> Option<char>;
|
||||
/// Peek the next character
|
||||
@ -1014,8 +1015,21 @@ fn get_next_token_inner(
|
||||
}
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
'.' => {
|
||||
result.push(next_char);
|
||||
eat_next(stream, pos);
|
||||
stream.get_next().unwrap();
|
||||
|
||||
// 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() {
|
||||
match next_char_in_float {
|
||||
'0'..='9' | '_' => {
|
||||
@ -1499,6 +1513,8 @@ fn is_id_continue(x: char) -> bool {
|
||||
/// A type that implements the `InputStream` trait.
|
||||
/// Multiple character streams are jointed together to form one single stream.
|
||||
pub struct MultiInputsStream<'a> {
|
||||
/// Buffered character, if any.
|
||||
buf: Option<char>,
|
||||
/// The input character streams.
|
||||
streams: StaticVec<Peekable<Chars<'a>>>,
|
||||
/// The current stream index.
|
||||
@ -1506,8 +1522,16 @@ pub struct MultiInputsStream<'a> {
|
||||
}
|
||||
|
||||
impl InputStream for MultiInputsStream<'_> {
|
||||
/// Buffer a character.
|
||||
fn unread(&mut self, ch: char) {
|
||||
self.buf = Some(ch);
|
||||
}
|
||||
/// Get the next character
|
||||
fn get_next(&mut self) -> Option<char> {
|
||||
if let Some(ch) = self.buf.take() {
|
||||
return Some(ch);
|
||||
}
|
||||
|
||||
loop {
|
||||
if self.index >= self.streams.len() {
|
||||
// No more streams
|
||||
@ -1523,6 +1547,10 @@ impl InputStream for MultiInputsStream<'_> {
|
||||
}
|
||||
/// Peek the next character
|
||||
fn peek_next(&mut self) -> Option<char> {
|
||||
if let Some(ch) = self.buf {
|
||||
return Some(ch);
|
||||
}
|
||||
|
||||
loop {
|
||||
if self.index >= self.streams.len() {
|
||||
// No more streams
|
||||
@ -1673,6 +1701,7 @@ pub fn lex<'a, 'e>(
|
||||
},
|
||||
pos: Position::new(1, 0),
|
||||
stream: MultiInputsStream {
|
||||
buf: None,
|
||||
streams: input.iter().map(|s| s.chars().peekable()).collect(),
|
||||
index: 0,
|
||||
},
|
||||
|
@ -4,7 +4,17 @@ use rhai::{Engine, EvalAltResult, INT};
|
||||
fn test_number_literal() -> Result<(), Box<EvalAltResult>> {
|
||||
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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user