commit
effb11ab94
@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "rhai"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
||||
description = "Embedded scripting for Rust"
|
||||
|
@ -61,6 +61,10 @@ Documentation
|
||||
|
||||
See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai scripting engine and language.
|
||||
|
||||
To build _The Book_, first install [`mdbook`](https://github.com/rust-lang/mdBook)
|
||||
and [`mdbook-tera`](https://github.com/avitex/mdbook-tera) (for templating).
|
||||
Running `mdbook build` builds it.
|
||||
|
||||
Playground
|
||||
----------
|
||||
|
||||
|
21
RELEASES.md
21
RELEASES.md
@ -1,6 +1,27 @@
|
||||
Rhai Release Notes
|
||||
==================
|
||||
|
||||
Version 0.20.0
|
||||
==============
|
||||
|
||||
Breaking changes
|
||||
----------------
|
||||
|
||||
* `AST::iter_functions` now returns an iterator instead of taking a closure.
|
||||
* `Module::get_script_function_by_signature` renamed to `Module::get_script_fn` and returns `&<Shared<ScriptFnDef>>`.
|
||||
* `Module::num_fn`, `Module::num_var` and `Module::num_iter` are removed and merged into `Module::count`.
|
||||
* The `merge_namespaces` parameter to `Module::eval_ast_as_new` is removed and now defaults to `true`.
|
||||
* `GlobalFileModuleResolver` is removed because its performance gain over the `FileModuleResolver` is no longer very significant.
|
||||
* The following `EvalAltResult` variants are removed and merged into `EvalAltResult::ErrorMismatchDataType`: `ErrorCharMismatch`, `ErrorNumericIndexExpr`, `ErrorStringIndexExpr`, `ErrorImportExpr`, `ErrorLogicGuard`, `ErrorBooleanArgMismatch`
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
* `OptimizationLevel::Simple` now eagerly evaluates built-in binary operators of primary types (if not overloaded).
|
||||
* Added `is_def_var()` to detect if variable is defined, and `is_def_fn()` to detect if script function is defined.
|
||||
* `Dynamic::from(&str)` now constructs a `Dynamic` with a copy of the string as value.
|
||||
|
||||
|
||||
Version 0.19.0
|
||||
==============
|
||||
|
||||
|
56
benches/eval_module.rs
Normal file
56
benches/eval_module.rs
Normal file
@ -0,0 +1,56 @@
|
||||
#![feature(test)]
|
||||
|
||||
///! Test evaluating with scope
|
||||
extern crate test;
|
||||
|
||||
use rhai::{module_resolvers::StaticModuleResolver, Engine, Module, OptimizationLevel};
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_eval_module(bench: &mut Bencher) {
|
||||
let script = r#"
|
||||
fn foo(x) { x + 1 }
|
||||
fn bar(x) { foo(x) }
|
||||
"#;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.set_optimization_level(OptimizationLevel::None);
|
||||
|
||||
let ast = engine.compile(script).unwrap();
|
||||
|
||||
let module = Module::eval_ast_as_new(Default::default(), &ast, &engine).unwrap();
|
||||
|
||||
let mut resolver = StaticModuleResolver::new();
|
||||
resolver.insert("testing", module);
|
||||
engine.set_module_resolver(Some(resolver));
|
||||
|
||||
let ast = engine
|
||||
.compile(
|
||||
r#"
|
||||
fn foo(x) { x - 1 }
|
||||
import "testing" as t;
|
||||
t::bar(41)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
bench.iter(|| engine.consume_ast(&ast).unwrap());
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_eval_function_call(bench: &mut Bencher) {
|
||||
let mut engine = Engine::new();
|
||||
engine.set_optimization_level(OptimizationLevel::None);
|
||||
|
||||
let ast = engine
|
||||
.compile(
|
||||
r#"
|
||||
fn foo(x) { x - 1 }
|
||||
fn bar(x) { foo(x) }
|
||||
bar(41)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
bench.iter(|| engine.consume_ast(&ast).unwrap());
|
||||
}
|
@ -403,9 +403,9 @@ impl ExportedFn {
|
||||
|
||||
pub(crate) fn exported_name<'n>(&'n self) -> Cow<'n, str> {
|
||||
if let Some(ref name) = self.params.name {
|
||||
Cow::Borrowed(name.last().unwrap().as_str())
|
||||
name.last().unwrap().as_str().into()
|
||||
} else {
|
||||
Cow::Owned(self.signature.ident.to_string())
|
||||
self.signature.ident.to_string().into()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,9 +204,9 @@ impl Module {
|
||||
|
||||
pub fn exported_name(&self) -> Option<Cow<str>> {
|
||||
if let Some(ref s) = self.params.name {
|
||||
Some(Cow::Borrowed(s))
|
||||
Some(s.into())
|
||||
} else {
|
||||
self.module_name().map(|m| Cow::Owned(m.to_string()))
|
||||
self.module_name().map(|m| m.to_string().into())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,25 +45,31 @@ The Rhai Scripting Language
|
||||
1. [Built-in Packages](rust/packages/builtin.md)
|
||||
2. [Load a Plugin Module as a Package](rust/packages/plugin.md)
|
||||
3. [Manually Create a Custom Package](rust/packages/create.md)
|
||||
10. [Plugins](plugins/index.md)
|
||||
10. [Modules](rust/modules/index.md)
|
||||
1. [Create from Rust](rust/modules/create.md)
|
||||
2. [Create from AST](rust/modules/ast.md)
|
||||
3. [Module Resolvers](rust/modules/resolvers.md)
|
||||
1. [Custom Implementation](rust/modules/imp-resolver.md)
|
||||
11. [Plugins](plugins/index.md)
|
||||
1. [Export a Rust Module](plugins/module.md)
|
||||
2. [Export a Rust Function](plugins/function.md)
|
||||
5. [Rhai Language Reference](language/index.md)
|
||||
1. [Comments](language/comments.md)
|
||||
2. [Values and Types](language/values-and-types.md)
|
||||
1. [Dynamic Values](language/dynamic.md)
|
||||
2. [type_of()](language/type-of.md)
|
||||
3. [Numbers](language/numbers.md)
|
||||
2. [Serialization/Deserialization with `serde`](rust/serde.md)
|
||||
3. [type_of()](language/type-of.md)
|
||||
4. [Numbers](language/numbers.md)
|
||||
1. [Operators](language/num-op.md)
|
||||
2. [Functions](language/num-fn.md)
|
||||
3. [Value Conversions](language/convert.md)
|
||||
4. [Strings and Characters](language/strings-chars.md)
|
||||
5. [Strings and Characters](language/strings-chars.md)
|
||||
1. [Built-in Functions](language/string-fn.md)
|
||||
5. [Arrays](language/arrays.md)
|
||||
6. [Object Maps](language/object-maps.md)
|
||||
6. [Arrays](language/arrays.md)
|
||||
7. [Object Maps](language/object-maps.md)
|
||||
1. [Parse from JSON](language/json.md)
|
||||
2. [Special Support for OOP](language/object-maps-oop.md)
|
||||
7. [Time-Stamps](language/timestamps.md)
|
||||
8. [Time-Stamps](language/timestamps.md)
|
||||
3. [Keywords](language/keywords.md)
|
||||
4. [Statements](language/statements.md)
|
||||
5. [Variables](language/variables.md)
|
||||
@ -88,10 +94,6 @@ The Rhai Scripting Language
|
||||
17. [Modules](language/modules/index.md)
|
||||
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
|
||||
2. [Import Modules](language/modules/import.md)
|
||||
3. [Create from Rust](rust/modules/create.md)
|
||||
4. [Create from AST](rust/modules/ast.md)
|
||||
5. [Module Resolvers](rust/modules/resolvers.md)
|
||||
1. [Custom Implementation](rust/modules/imp-resolver.md)
|
||||
18. [Eval Statement](language/eval.md)
|
||||
6. [Safety and Protection](safety/index.md)
|
||||
1. [Checked Arithmetic](safety/checked.md)
|
||||
@ -104,29 +106,30 @@ The Rhai Scripting Language
|
||||
7. [Maximum Number of Modules](safety/max-modules.md)
|
||||
8. [Maximum Call Stack Depth](safety/max-call-stack.md)
|
||||
9. [Maximum Statement Depth](safety/max-stmt-depth.md)
|
||||
7. [Advanced Topics](advanced.md)
|
||||
1. [Advanced Patterns](patterns/index.md)
|
||||
1. [Object-Oriented Programming (OOP)](patterns/oop.md)
|
||||
2. [Loadable Configuration](patterns/config.md)
|
||||
3. [Control Layer](patterns/control.md)
|
||||
4. [Singleton Command](patterns/singleton.md)
|
||||
5. [One Engine Instance Per Call](patterns/parallel.md)
|
||||
2. [Capture Scope for Function Call](language/fn-capture.md)
|
||||
3. [Serialization/Deserialization of `Dynamic` with `serde`](rust/serde.md)
|
||||
4. [Script Optimization](engine/optimize/index.md)
|
||||
1. [Optimization Levels](engine/optimize/optimize-levels.md)
|
||||
2. [Re-Optimize an AST](engine/optimize/reoptimize.md)
|
||||
3. [Eager Function Evaluation](engine/optimize/eager.md)
|
||||
4. [Side-Effect Considerations](engine/optimize/side-effects.md)
|
||||
5. [Volatility Considerations](engine/optimize/volatility.md)
|
||||
6. [Subtle Semantic Changes](engine/optimize/semantics.md)
|
||||
5. [Low-Level API](rust/register-raw.md)
|
||||
6. [Use as DSL](engine/dsl.md)
|
||||
7. [Script Optimization](engine/optimize/index.md)
|
||||
1. [Optimization Levels](engine/optimize/optimize-levels.md)
|
||||
2. [Re-Optimize an AST](engine/optimize/reoptimize.md)
|
||||
3. [Eager Function Evaluation](engine/optimize/eager.md)
|
||||
4. [Side-Effect Considerations](engine/optimize/side-effects.md)
|
||||
5. [Volatility Considerations](engine/optimize/volatility.md)
|
||||
6. [Subtle Semantic Changes](engine/optimize/semantics.md)
|
||||
8. [Usage Patterns](patterns/index.md)
|
||||
1. [Object-Oriented Programming (OOP)](patterns/oop.md)
|
||||
2. [Loadable Configuration](patterns/config.md)
|
||||
3. [Control Layer](patterns/control.md)
|
||||
4. [Singleton Command](patterns/singleton.md)
|
||||
5. [Multi-Layer Functions](patterns/multi-layer.md)
|
||||
6. [One Engine Instance Per Call](patterns/parallel.md)
|
||||
7. [Scriptable Event Handler with State](patterns/events.md)
|
||||
9. [Advanced Topics](advanced.md)
|
||||
1. [Capture Scope for Function Call](language/fn-capture.md)
|
||||
2. [Low-Level API](rust/register-raw.md)
|
||||
3. [Use as DSL](engine/dsl.md)
|
||||
1. [Disable Keywords and/or Operators](engine/disable.md)
|
||||
2. [Custom Operators](engine/custom-op.md)
|
||||
3. [Extending with Custom Syntax](engine/custom-syntax.md)
|
||||
7. [Multiple Instantiation](patterns/multiple.md)
|
||||
8. [Appendix](appendix/index.md)
|
||||
1. [Keywords](appendix/keywords.md)
|
||||
2. [Operators and Symbols](appendix/operators.md)
|
||||
3. [Literals](appendix/literals.md)
|
||||
4. [Multiple Instantiation](patterns/multiple.md)
|
||||
10. [Appendix](appendix/index.md)
|
||||
1. [Keywords](appendix/keywords.md)
|
||||
2. [Operators and Symbols](appendix/operators.md)
|
||||
3. [Literals](appendix/literals.md)
|
||||
|
@ -14,7 +14,9 @@ Versions
|
||||
|
||||
This Book is for version **{{version}}** of Rhai.
|
||||
|
||||
{% if rootUrl != "" and not rootUrl is ending_with("vnext") %}
|
||||
For the latest development version, see [here]({{rootUrl}}/vnext/).
|
||||
{% endif %}
|
||||
|
||||
|
||||
Etymology of the name "Rhai"
|
||||
|
@ -11,8 +11,6 @@ This section covers advanced features such as:
|
||||
|
||||
* [`serde`] integration.
|
||||
|
||||
* [Script optimization].
|
||||
|
||||
* Low-level [function registration API]({{rootUrl}}/rust/register-raw.md)
|
||||
|
||||
* [Domain-Specific Languages][DSL].
|
||||
|
@ -3,36 +3,38 @@ Keywords List
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
| 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` | 1) containment test<br/>2) part of for loop | | no |
|
||||
| `continue` | continue a loop at the next iteration | | no |
|
||||
| `break` | break out of loop iteration | | no |
|
||||
| `return` | return value | | no |
|
||||
| `throw` | throw exception | | no |
|
||||
| `import` | import module | [`no_module`] | no |
|
||||
| `export` | export variable | [`no_module`] | no |
|
||||
| `as` | alias for variable export | [`no_module`] | no |
|
||||
| `private` | mark function private | [`no_function`] | no |
|
||||
| `fn` (lower-case `f`) | function definition | [`no_function`] | no |
|
||||
| `Fn` (capital `F`) | create a [function pointer] | | yes |
|
||||
| `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 |
|
||||
| Keyword | Description | Inactive under | Is function? | Overloadable |
|
||||
| :-------------------: | ------------------------------------------- | :-------------: | :----------: | :----------: |
|
||||
| `true` | boolean true literal | | no | |
|
||||
| `false` | boolean false literal | | no | |
|
||||
| `let` | variable declaration | | no | |
|
||||
| `const` | constant declaration | | no | |
|
||||
| `is_def_var` | is a variable declared? | | yes | yes |
|
||||
| `is_shared` | is a value shared? | [`no_closure`] | yes | 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` | 1) containment test<br/>2) part of for loop | | no | |
|
||||
| `continue` | continue a loop at the next iteration | | no | |
|
||||
| `break` | break out of loop iteration | | no | |
|
||||
| `return` | return value | | no | |
|
||||
| `throw` | throw exception | | no | |
|
||||
| `import` | import module | [`no_module`] | no | |
|
||||
| `export` | export variable | [`no_module`] | no | |
|
||||
| `as` | alias for variable export | [`no_module`] | no | |
|
||||
| `private` | mark function private | [`no_function`] | no | |
|
||||
| `fn` (lower-case `f`) | function definition | [`no_function`] | no | |
|
||||
| `Fn` (capital `F`) | create a [function pointer] | | yes | yes |
|
||||
| `call` | call a [function pointer] | | yes | no |
|
||||
| `curry` | curry a [function pointer] | | yes | no |
|
||||
| `this` | reference to base object for method call | [`no_function`] | no | |
|
||||
| `is_def_fn` | is a scripted function defined? | [`no_function`] | yes | yes |
|
||||
| `type_of` | get type name of value | | yes | yes |
|
||||
| `print` | print value | | yes | yes |
|
||||
| `debug` | print value in debug format | | yes | yes |
|
||||
| `eval` | evaluate script | | yes | yes |
|
||||
|
||||
|
||||
Reserved Keywords
|
||||
|
@ -8,7 +8,7 @@ Literals Syntax
|
||||
| `INT` | decimal: `42`, `-123`, `0`<br/>hex: `0x????..`<br/>binary: `0b????..`<br/>octal: `0o????..` |
|
||||
| `FLOAT` | `42.0`, `-123.456`, `0.0` |
|
||||
| [String] | `"... \x?? \u???? \U???????? ..."` |
|
||||
| Character | single: `'?'`<br/>ASCII hex: `'\x??'`<br/>Unicode: `'\u????'`, `'\U????????'` |
|
||||
| [Character][string] | single: `'?'`<br/>ASCII hex: `'\x??'`<br/>Unicode: `'\u????'`, `'\U????????'` |
|
||||
| [`Array`] | `[ ???, ???, ??? ]` |
|
||||
| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` |
|
||||
| Boolean true | `true` |
|
||||
|
@ -7,46 +7,62 @@ Operators and Symbols
|
||||
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 |
|
||||
| `&&` | 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 |
|
||||
| Operator | Description | Binary? | Binding direction |
|
||||
| :-----------------------------------------------------------------------------------------: | -------------------------------------- | :--------: | :---------------: |
|
||||
| `+` | add | yes | left |
|
||||
| `-` | 1) subtract<br/>2) negative | yes<br/>no | left<br/>right |
|
||||
| `*` | multiply | yes | left |
|
||||
| `/` | divide | yes | left |
|
||||
| `%` | modulo | yes | left |
|
||||
| `~` | power | yes | left |
|
||||
| `>>` | right bit-shift | yes | left |
|
||||
| `<<` | left bit-shift | yes | left |
|
||||
| `&` | 1) bit-wise _And_<br/>2) boolean _And_ | yes | left |
|
||||
| <code>\|</code> | 1) bit-wise _Or_<br/>2) boolean _Or_ | yes | left |
|
||||
| `^` | 1) bit-wise _Xor_<br/>2) boolean _Xor_ | yes | left |
|
||||
| `=`, `+=`, `-=`, `*=`, `/=`,<br/>`~=`, `%=`, `<<=`, `>>=`, `&=`,<br/><code>\|=</code>, `^=` | assignments | yes | right |
|
||||
| `==` | equals to | yes | left |
|
||||
| `~=` | not equals to | yes | left |
|
||||
| `>` | greater than | yes | left |
|
||||
| `>=` | greater than or equals to | yes | left |
|
||||
| `<` | less than | yes | left |
|
||||
| `<=` | less than or equals to | yes | left |
|
||||
| `&&` | boolean _And_ (short-circuits) | yes | left |
|
||||
| <code>\|\|</code> | boolean _Or_ (short-circuits) | yes | left |
|
||||
| `!` | boolean _Not_ | no | left |
|
||||
| `[` .. `]` | indexing | yes | right |
|
||||
| `.` | 1) property access<br/>2) method call | yes | right |
|
||||
|
||||
|
||||
Symbols
|
||||
-------
|
||||
Symbols and Patterns
|
||||
--------------------
|
||||
|
||||
| Symbol | Description |
|
||||
| ------------ | ------------------------ |
|
||||
| `:` | property value separator |
|
||||
| `::` | module path separator |
|
||||
| `#` | _reserved_ |
|
||||
| `=>` | _reserved_ |
|
||||
| `->` | _reserved_ |
|
||||
| `<-` | _reserved_ |
|
||||
| `===` | _reserved_ |
|
||||
| `!==` | _reserved_ |
|
||||
| `:=` | _reserved_ |
|
||||
| `::<` .. `>` | _reserved_ |
|
||||
| `@` | _reserved_ |
|
||||
| `(*` .. `*)` | _reserved_ |
|
||||
| Symbol | Name | Description |
|
||||
| ---------------------------------- | :------------------: | ------------------------------------- |
|
||||
| `;` | semicolon | statement separator |
|
||||
| `,` | comma | list separator |
|
||||
| `:` | colon | [object map] property value separator |
|
||||
| `::` | path | module path separator |
|
||||
| `#{` .. `}` | hash map | [object map] literal |
|
||||
| `"` .. `"` | double quote | [string] |
|
||||
| `'` .. `'` | single quote | [character][string] |
|
||||
| `\` | escape | escape character literal |
|
||||
| `(` .. `)` | parentheses | expression grouping |
|
||||
| `{` .. `}` | braces | block statement |
|
||||
| <code>\|</code> .. <code>\|</code> | pipes | closure |
|
||||
| `[` .. `]` | brackets | [array] literal |
|
||||
| `!` | bang | function call in calling scope |
|
||||
| `//` | comment | line comment |
|
||||
| `/*` .. `*/` | comment | block comment |
|
||||
| `(*` .. `*)` | comment | _reserved_ |
|
||||
| `<` .. `>` | angular brackets | _reserved_ |
|
||||
| `#` | hash | _reserved_ |
|
||||
| `@` | at | _reserved_ |
|
||||
| `$` | dollar | _reserved_ |
|
||||
| `=>` | double arrow | _reserved_ |
|
||||
| `->` | arrow | _reserved_ |
|
||||
| `<-` | left arrow | _reserved_ |
|
||||
| `===` | strict equals to | _reserved_ |
|
||||
| `!==` | strict not equals to | _reserved_ |
|
||||
| `:=` | assignment | _reserved_ |
|
||||
| `::<` .. `>` | turbofish | _reserved_ |
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "0.19.0",
|
||||
"version": "0.20.0",
|
||||
"repoHome": "https://github.com/jonathandturner/rhai/blob/master",
|
||||
"repoTree": "https://github.com/jonathandturner/rhai/tree/master",
|
||||
"rootUrl": "",
|
||||
|
@ -131,7 +131,7 @@ It should simply be passed straight-through the the [`Engine`].
|
||||
### Access Arguments
|
||||
|
||||
The most important argument is `inputs` where the matched identifiers (`$ident$`), expressions/statements (`$expr$`)
|
||||
and statement blocks (`$block$) are provided.
|
||||
and statement blocks (`$block$`) are provided.
|
||||
|
||||
To access a particular argument, use the following patterns:
|
||||
|
||||
|
@ -3,8 +3,8 @@ Eager Function Evaluation When Using Full Optimization Level
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_ and will _eagerly_
|
||||
evaluated all function calls with constant arguments, using the result to replace the call.
|
||||
When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_
|
||||
and will _eagerly_ evaluated all function calls with constant arguments, using the result to replace the call.
|
||||
|
||||
This also applies to all operators (which are implemented as functions).
|
||||
|
||||
@ -14,8 +14,8 @@ For instance, the same example above:
|
||||
// When compiling the following with OptimizationLevel::Full...
|
||||
|
||||
const DECISION = 1;
|
||||
// this condition is now eliminated because 'DECISION == 1'
|
||||
if DECISION == 1 { // is a function call to the '==' function, and it returns 'true'
|
||||
// this condition is now eliminated because 'sign(DECISION) > 0'
|
||||
if DECISION.sign() > 0 { // is a call to the 'sign' and '>' functions, and they return 'true'
|
||||
print("hello!"); // this block is promoted to the parent level
|
||||
} else {
|
||||
print("boo!"); // this block is eliminated because it is never reached
|
||||
@ -24,13 +24,3 @@ if DECISION == 1 { // is a function call to the '==' function, and it r
|
||||
print("hello!"); // <- the above is equivalent to this
|
||||
// ('print' and 'debug' are handled specially)
|
||||
```
|
||||
|
||||
Because of the eager evaluation of functions, many constant expressions will be evaluated and replaced by the result.
|
||||
This does not happen with [`OptimizationLevel::Simple`] which doesn't assume all functions to be _pure_.
|
||||
|
||||
```rust
|
||||
// When compiling the following with OptimizationLevel::Full...
|
||||
|
||||
let x = (1+2)*3-4/5%6; // <- will be replaced by 'let x = 9'
|
||||
let y = (1>2) || (3<=4); // <- will be replaced by 'let y = true'
|
||||
```
|
||||
|
@ -47,9 +47,13 @@ Constants propagation is used to remove dead code:
|
||||
|
||||
```rust
|
||||
const ABC = true;
|
||||
|
||||
if ABC || some_work() { print("done!"); } // 'ABC' is constant so it is replaced by 'true'...
|
||||
|
||||
if true || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called
|
||||
|
||||
if true { print("done!"); } // <- the line above is equivalent to this
|
||||
|
||||
print("done!"); // <- the line above is further simplified to this
|
||||
// because the condition is always true
|
||||
```
|
||||
@ -61,49 +65,73 @@ For fixed script texts, the constant values can be provided in a user-defined [`
|
||||
to the [`Engine`] for use in compilation and evaluation.
|
||||
|
||||
|
||||
Watch Out for Function Calls
|
||||
---------------------------
|
||||
Eager Operator Evaluations
|
||||
-------------------------
|
||||
|
||||
Beware, however, that most operators are actually function calls, and those functions can be overridden,
|
||||
so they are not optimized away:
|
||||
so whether they are optimized away depends on the situation:
|
||||
|
||||
* If the operands are not _constant_ values, it is not optimized.
|
||||
|
||||
* If the operator is [overloaded][operator overloading], it is not optimized because the overloading function may not be _pure_
|
||||
(i.e. may cause side-effects when called).
|
||||
|
||||
* If the operator is not _binary_, it is not optimized. Only binary operators are built-in to Rhai.
|
||||
|
||||
* If the operands are not of the same type, it is not optimized.
|
||||
|
||||
* If the operator is not _built-in_ (see list of [built-in operators]), it is not optimized.
|
||||
|
||||
* If the operator is a binary built-in operator for a [standard type][standard types], it is called and replaced by a constant result.
|
||||
|
||||
Rhai guarantees that no external function will be run (in order not to trigger side-effects) during the
|
||||
optimization process (unless the optimization level is set to [`OptimizationLevel::Full`]).
|
||||
|
||||
```rust
|
||||
const DECISION = 1;
|
||||
// The following is most likely generated by machine.
|
||||
|
||||
if DECISION == 1 { // NOT optimized away because you can define
|
||||
: // your own '==' function to override the built-in default!
|
||||
const DECISION = 1; // this is an integer, one of the standard types
|
||||
|
||||
if DECISION == 1 { // this is optimized into 'true'
|
||||
:
|
||||
} else if DECISION == 2 { // same here, NOT optimized away
|
||||
} else if DECISION == 2 { // this is optimized into 'false'
|
||||
:
|
||||
} else if DECISION == 3 { // same here, NOT optimized away
|
||||
} else if DECISION == 3 { // this is optimized into 'false'
|
||||
:
|
||||
} else {
|
||||
:
|
||||
}
|
||||
```
|
||||
|
||||
because no operator functions will be run (in order not to trigger side-effects) during the optimization process
|
||||
(unless the optimization level is set to [`OptimizationLevel::Full`]).
|
||||
|
||||
So, instead, do this:
|
||||
Because of the eager evaluation of operators for [standard types], many constant expressions will be evaluated
|
||||
and replaced by the result.
|
||||
|
||||
```rust
|
||||
const DECISION_1 = true;
|
||||
const DECISION_2 = false;
|
||||
const DECISION_3 = false;
|
||||
let x = (1+2) * 3-4 / 5%6; // will be replaced by 'let x = 9'
|
||||
|
||||
if DECISION_1 {
|
||||
: // this branch is kept and promoted to the parent level
|
||||
} else if DECISION_2 {
|
||||
: // this branch is eliminated
|
||||
} else if DECISION_3 {
|
||||
: // this branch is eliminated
|
||||
} else {
|
||||
: // this branch is eliminated
|
||||
}
|
||||
let y = (1 > 2) || (3 < =4); // will be replaced by 'let y = true'
|
||||
```
|
||||
|
||||
In general, boolean constants are most effective for the optimizer to automatically prune
|
||||
large `if`-`else` branches because they do not depend on operators.
|
||||
For operators that are not optimized away due to one of the above reasons, the function calls
|
||||
are simply left behind:
|
||||
|
||||
```rust
|
||||
// Assume 'new_state' returns some custom type that is NOT one of the standard types.
|
||||
// Also assume that the '==; operator is defined for that custom type.
|
||||
const DECISION_1 = new_state(1);
|
||||
const DECISION_2 = new_state(2);
|
||||
const DECISION_3 = new_state(3);
|
||||
|
||||
if DECISION == 1 { // NOT optimized away because the operator is not built-in
|
||||
: // and may cause side-effects if called!
|
||||
:
|
||||
} else if DECISION == 2 { // same here, NOT optimized away
|
||||
:
|
||||
} else if DECISION == 3 { // same here, NOT optimized away
|
||||
:
|
||||
} else {
|
||||
:
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, turn the optimizer to [`OptimizationLevel::Full`].
|
||||
|
@ -8,9 +8,10 @@ There are three levels of optimization: `None`, `Simple` and `Full`.
|
||||
* `None` is obvious - no optimization on the AST is performed.
|
||||
|
||||
* `Simple` (default) performs only relatively _safe_ optimizations without causing side-effects
|
||||
(i.e. it only relies on static analysis and will not actually perform any function calls).
|
||||
(i.e. it only relies on static analysis and [built-in operators] for constant [standard types],
|
||||
and will not perform any external function calls).
|
||||
|
||||
* `Full` is _much_ more aggressive, _including_ running functions on constant arguments to determine their result.
|
||||
* `Full` is _much_ more aggressive, _including_ calling external functions on constant arguments to determine their result.
|
||||
One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators.
|
||||
|
||||
|
||||
|
@ -16,11 +16,11 @@ The final, optimized [`AST`] is then used for evaluations.
|
||||
// Compile master script to AST
|
||||
let master_ast = engine.compile(
|
||||
r"
|
||||
if SCENARIO_1 {
|
||||
if SCENARIO == 1 {
|
||||
do_work();
|
||||
} else if SCENARIO_2 {
|
||||
} else if SCENARIO == 2 {
|
||||
do_something();
|
||||
} else if SCENARIO_3 {
|
||||
} else if SCENARIO == 3 {
|
||||
do_something_else();
|
||||
} else {
|
||||
do_nothing();
|
||||
@ -29,9 +29,7 @@ r"
|
||||
|
||||
// Create a new 'Scope' - put constants in it to aid optimization
|
||||
let mut scope = Scope::new();
|
||||
scope.push_constant("SCENARIO_1", true);
|
||||
scope.push_constant("SCENARIO_2", false);
|
||||
scope.push_constant("SCENARIO_3", false);
|
||||
scope.push_constant("SCENARIO", 1_i64);
|
||||
|
||||
// Re-optimize the AST
|
||||
let new_ast = engine.optimize_ast(&scope, master_ast.clone(), OptimizationLevel::Simple);
|
||||
|
@ -3,17 +3,20 @@ Side-Effect Considerations for Full Optimization Level
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_ (i.e. they do not mutate state
|
||||
nor cause any side-effects, with the exception of `print` and `debug` which are handled specially) so using
|
||||
[`OptimizationLevel::Full`] is usually quite safe _unless_ custom types and functions are registered.
|
||||
All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_
|
||||
(i.e. they do not mutate state nor cause any side-effects, with the exception of `print` and `debug`
|
||||
which are handled specially) so using [`OptimizationLevel::Full`] is usually quite safe _unless_
|
||||
custom types and functions are registered.
|
||||
|
||||
If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie within a pruned code block).
|
||||
If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie
|
||||
within a pruned code block).
|
||||
|
||||
If custom functions are registered to overload built-in operators, they will also be called when the operators are used
|
||||
(in an `if` statement, for example) causing side-effects.
|
||||
If custom functions are registered to overload built-in operators, they will also be called when
|
||||
the operators are used (in an `if` statement, for example) causing side-effects.
|
||||
|
||||
Therefore, the rule-of-thumb is:
|
||||
|
||||
* _Always_ register custom types and functions _after_ compiling scripts if [`OptimizationLevel::Full`] is used.
|
||||
|
||||
* _DO NOT_ depend on knowledge that the functions have no side-effects, because those functions can change later on and, when that happens, existing scripts may break in subtle ways.
|
||||
* _DO NOT_ depend on knowledge that the functions have no side-effects, because those functions can change later on and,
|
||||
when that happens, existing scripts may break in subtle ways.
|
||||
|
@ -88,84 +88,3 @@ fn say_hello() {
|
||||
}
|
||||
say_hello();
|
||||
```
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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 get_message() { "Hello!" };
|
||||
|
||||
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 - '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),
|
||||
the subsequent call using the _namespace-qualified_ function name fails to find the same function
|
||||
'`message`' which now essentially becomes `g::message`. The call fails as there is no more
|
||||
function named '`message`' in the global namespace.
|
||||
|
||||
Therefore, when writing functions for a [module] intended for the [`GlobalFileModuleResolver`][module resolver],
|
||||
make sure that those functions are as independent 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 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(Fn("get_message"));
|
||||
|
||||
|
||||
---------------
|
||||
| script.rhai |
|
||||
---------------
|
||||
|
||||
import "greeting" as g;
|
||||
|
||||
fn my_msg() {
|
||||
import "greeting" as g; // <- must import again here...
|
||||
g::get_message() // <- ... otherwise will not find module 'g'
|
||||
}
|
||||
|
||||
g::say_hello(Fn("my_msg")); // prints 'Hello!'
|
||||
```
|
||||
|
@ -101,6 +101,21 @@ a statement in the script can freely call a function defined afterwards.
|
||||
This is similar to Rust and many other modern languages, such as JavaScript's `function` keyword.
|
||||
|
||||
|
||||
`is_def_fn`
|
||||
-----------
|
||||
|
||||
Use `is_def_fn` to detect if a function is defined (and therefore callable), based on its name
|
||||
and the number of parameters.
|
||||
|
||||
```rust
|
||||
fn foo(x) { x + 1 }
|
||||
|
||||
is_def_fn("foo", 1) == true;
|
||||
|
||||
is_def_fn("bar", 1) == false;
|
||||
```
|
||||
|
||||
|
||||
Arguments are Passed by Value
|
||||
----------------------------
|
||||
|
||||
|
@ -4,6 +4,16 @@ Export Variables, Functions and Sub-Modules in Module
|
||||
{{#include ../../links.md}}
|
||||
|
||||
|
||||
The easiest way to expose a package of functions as a self-contained [module] is to do it via a Rhai script itself.
|
||||
|
||||
See the section on [_Creating a Module from AST_]({{rootUrl}}/rust/modules/ast.md) for more details.
|
||||
|
||||
The script text is evaluated, variables are then selectively exposed via the [`export`] statement.
|
||||
Functions defined by the script are automatically exported.
|
||||
|
||||
Modules loaded within this module at the global level become _sub-modules_ and are also automatically exported.
|
||||
|
||||
|
||||
Export Global Variables
|
||||
----------------------
|
||||
|
||||
|
@ -4,6 +4,12 @@ Import a Module
|
||||
{{#include ../../links.md}}
|
||||
|
||||
|
||||
Before a module can be used (via an `import` statement) in a script, there must be a [module resolver]
|
||||
registered into the [`Engine`], the default being the `FileModuleResolver`.
|
||||
|
||||
See the section on [_Module Resolvers_][module resolver] for more details.
|
||||
|
||||
|
||||
`import` Statement
|
||||
-----------------
|
||||
|
||||
|
@ -6,13 +6,9 @@ Modules
|
||||
Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_.
|
||||
Modules can be disabled via the [`no_module`] feature.
|
||||
|
||||
A module is of the type `Module` and encapsulates a Rhai script together with the functions defined
|
||||
by that script.
|
||||
A module is of the type `Module` and holds a collection of functions, variables, iterators and sub-modules.
|
||||
It may be created entirely from Rust functions, or it may encapsulate a Rhai script together with the functions
|
||||
and variables defined by that script.
|
||||
|
||||
The script text is run, variables are then selectively exposed via the [`export`] statement.
|
||||
Functions defined by the script are automatically exported.
|
||||
|
||||
Modules loaded within this module at the global level become _sub-modules_ and are also automatically exported.
|
||||
|
||||
Other scripts can then load this module and use the variables and functions exported
|
||||
Other scripts can then load this module and use the functions and variables exported
|
||||
as if they were defined inside the same script.
|
||||
|
@ -37,6 +37,8 @@ If none is provided, it defaults to [`()`].
|
||||
|
||||
A variable defined within a statement block is _local_ to that block.
|
||||
|
||||
Use `is_def_var` to detect if a variable is defined.
|
||||
|
||||
```rust
|
||||
let x; // ok - value is '()'
|
||||
let x = 3; // ok
|
||||
@ -57,4 +59,10 @@ X == 123;
|
||||
x == 999; // access to local 'x'
|
||||
}
|
||||
x == 42; // the parent block's 'x' is not changed
|
||||
|
||||
is_def_var("x") == true;
|
||||
|
||||
is_def_var("_x") == true;
|
||||
|
||||
is_def_var("y") == false;
|
||||
```
|
||||
|
@ -100,10 +100,11 @@
|
||||
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md
|
||||
[anonymous function]: {{rootUrl}}/language/fn-anon.md
|
||||
[anonymous functions]: {{rootUrl}}/language/fn-anon.md
|
||||
[operator overloading]: {{rootUrl}}/rust/operators.md
|
||||
|
||||
[`Module`]: {{rootUrl}}/language/modules/index.md
|
||||
[module]: {{rootUrl}}/language/modules/index.md
|
||||
[modules]: {{rootUrl}}/language/modules/index.md
|
||||
[`Module`]: {{rootUrl}}/rust/modules/index.md
|
||||
[module]: {{rootUrl}}/rust/modules/index.md
|
||||
[modules]: {{rootUrl}}/rust/modules/index.md
|
||||
[module resolver]: {{rootUrl}}/rust/modules/resolvers.md
|
||||
[`export`]: {{rootUrl}}/language/modules/export.md
|
||||
[`import`]: {{rootUrl}}/language/modules/import.md
|
||||
|
182
doc/src/patterns/events.md
Normal file
182
doc/src/patterns/events.md
Normal file
@ -0,0 +1,182 @@
|
||||
Scriptable Event Handler with State
|
||||
==================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Usage Scenario
|
||||
--------------
|
||||
|
||||
* A system sends _events_ that must be handled.
|
||||
|
||||
* Flexibility in event handling must be provided, through user-side scripting.
|
||||
|
||||
* State must be kept between invocations of event handlers.
|
||||
|
||||
* Default implementations of event handlers can be provided.
|
||||
|
||||
|
||||
Key Concepts
|
||||
------------
|
||||
|
||||
* An _event handler_ object is declared that holds the following items:
|
||||
* [`Engine`] with registered functions serving as an API,
|
||||
* [`AST`] of the user script,
|
||||
* a [`Scope`] containing state.
|
||||
|
||||
* Upon an event, the appropriate event handler function in the script is called via [`Engine::call_fn`][`call_fn`].
|
||||
|
||||
* Optionally, trap the `EvalAltResult::ErrorFunctionNotFound` error to provide a default implementation.
|
||||
|
||||
|
||||
Implementation
|
||||
--------------
|
||||
|
||||
### Declare Handler Object
|
||||
|
||||
In most cases, it would be simpler to store an [`Engine`] instance together with the handler object
|
||||
because it only requires registering all API functions only once.
|
||||
|
||||
In rare cases where handlers are created and destroyed in a tight loop, a new [`Engine`] instance
|
||||
can be created for each event. See [_One Engine Instance Per Call_](parallel.md) for more details.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope, AST, EvalAltResult};
|
||||
|
||||
// Event handler
|
||||
struct Handler {
|
||||
// Scripting engine
|
||||
pub engine: Engine,
|
||||
// Use a custom 'Scope' to keep stored state
|
||||
pub scope: Scope<'static>,
|
||||
// Program script
|
||||
pub ast: AST
|
||||
}
|
||||
```
|
||||
|
||||
### Initialize Handler Object
|
||||
|
||||
Steps to initialize the event handler:
|
||||
|
||||
1. Register an API with the [`Engine`],
|
||||
2. Create a custom [`Scope`] to serve as the stored state,
|
||||
3. Add default state variables into the custom [`Scope`],
|
||||
4. Get the handler script and [compile][`AST`] it,
|
||||
5. Store the compiled [`AST`] for future evaluations,
|
||||
6. Run the [`AST`] to initialize event handler state variables.
|
||||
|
||||
```rust
|
||||
impl Handler {
|
||||
pub new(path: impl Into<PathBuf>) -> Self {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register API functions here
|
||||
engine
|
||||
.register_fn("func1", func1)
|
||||
.register_fn("func2", func2)
|
||||
.register_fn("func3", func3)
|
||||
.register_type_with_name::<SomeType>("SomeType")
|
||||
.register_get_set("value",
|
||||
|obj: &mut SomeType| obj.data,
|
||||
|obj: &mut SomeType, value: i64| obj.data = value
|
||||
);
|
||||
|
||||
// Create a custom 'Scope' to hold state
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Add initialized state into the custom 'Scope'
|
||||
scope.push("state1", false);
|
||||
scope.push("state2", SomeType::new(42));
|
||||
|
||||
// Compile the handler script.
|
||||
// In a real application you'd be handling errors...
|
||||
let ast = engine.compile_file(path).unwrap();
|
||||
|
||||
// Evaluate the script to initialize it and other state variables.
|
||||
// In a real application you'd again be handling errors...
|
||||
engine.consume_ast_with_scope(&mut scope, &ast).unwrap();
|
||||
|
||||
// The event handler is essentially these three items:
|
||||
Handler { engine, scope, ast }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Hook up events
|
||||
|
||||
There is usually an interface or trait that gets called when an event comes from the system.
|
||||
|
||||
Mapping an event from the system into a scripted handler is straight-forward:
|
||||
|
||||
```rust
|
||||
impl Handler {
|
||||
// Say there are three events: 'start', 'end', 'update'.
|
||||
// In a real application you'd be handling errors...
|
||||
pub fn on_event(&mut self, event_name: &str, event_data: i64) {
|
||||
match event_name {
|
||||
// The 'start' event maps to function 'start'.
|
||||
// In a real application you'd be handling errors...
|
||||
"start" =>
|
||||
self.engine
|
||||
.call_fn(&mut self.scope, &self.ast, "start", (event_data,)).unwrap(),
|
||||
|
||||
// The 'end' event maps to function 'end'.
|
||||
// In a real application you'd be handling errors...
|
||||
"end" =>
|
||||
self.engine
|
||||
.call_fn(&mut self.scope, &self.ast, "end", (event_data,)).unwrap(),
|
||||
|
||||
// The 'update' event maps to function 'update'.
|
||||
// This event provides a default implementation when the scripted function
|
||||
// is not found.
|
||||
"update" =>
|
||||
self.engine
|
||||
.call_fn(&mut self.scope, &self.ast, "update", (event_data,))
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "update" => {
|
||||
// Default implementation of 'update' event handler
|
||||
self.scope.set_value("state2", SomeType::new(42));
|
||||
// Turn function-not-found into a success
|
||||
Ok(().into())
|
||||
}
|
||||
_ => Err(err.into())
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sample Handler Script
|
||||
|
||||
Because the stored state is kept in a custom [`Scope`], it is possible for all functions defined
|
||||
in the handler script to access and modify these state variables.
|
||||
|
||||
The API registered with the [`Engine`] can be also used throughout the script.
|
||||
|
||||
```rust
|
||||
fn start(data) {
|
||||
if state1 {
|
||||
throw "Already started!";
|
||||
}
|
||||
if func1(state2) || func2() {
|
||||
throw "Conditions not yet ready to start!";
|
||||
}
|
||||
state1 = true;
|
||||
state2.value = 0;
|
||||
}
|
||||
|
||||
fn end(data) {
|
||||
if !state1 {
|
||||
throw "Not yet started!";
|
||||
}
|
||||
if func1(state2) || func2() {
|
||||
throw "Conditions not yet ready to start!";
|
||||
}
|
||||
state1 = false;
|
||||
}
|
||||
|
||||
fn update(data) {
|
||||
state2.value += func3(data);
|
||||
}
|
||||
```
|
@ -1,7 +1,7 @@
|
||||
Advanced Patterns
|
||||
=================
|
||||
Usage Patterns
|
||||
==============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Leverage the full power and flexibility of Rhai in advanced scenarios.
|
||||
Leverage the full power and flexibility of Rhai in different scenarios.
|
||||
|
117
doc/src/patterns/multi-layer.md
Normal file
117
doc/src/patterns/multi-layer.md
Normal file
@ -0,0 +1,117 @@
|
||||
Multi-Layer Functions
|
||||
=====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Usage Scenario
|
||||
--------------
|
||||
|
||||
* A system is divided into separate _layers_, each providing logic in terms of scripted [functions].
|
||||
|
||||
* A lower layer provides _default implementations_ of certain functions.
|
||||
|
||||
* Higher layers each provide progressively more specific implementations of the same functions.
|
||||
|
||||
* A more specific function, if defined in a higher layer, always overrides the implementation in a lower layer.
|
||||
|
||||
* This is akin to object-oriented programming but with functions.
|
||||
|
||||
* This type of system is extremely convenient for dynamic business rules configuration, setting corporate-wide
|
||||
policies, granting permissions for specific roles etc. where specific, local rules need to override
|
||||
corporate-wide defaults.
|
||||
|
||||
|
||||
Key Concepts
|
||||
------------
|
||||
|
||||
* Each layer is a separate script.
|
||||
|
||||
* The lowest layer script is compiled into a base [`AST`].
|
||||
|
||||
* Higher layer scripts are also compiled into [`AST`] and _merged_ into the base using `AST::merge`,
|
||||
overriding any existing functions.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Assume the following four scripts:
|
||||
|
||||
```rust
|
||||
----------------
|
||||
| default.rhai |
|
||||
----------------
|
||||
|
||||
// Default implementation of 'foo'.
|
||||
fn foo(x) { x + 1 }
|
||||
|
||||
// Default implementation of 'bar'.
|
||||
fn bar(x, y) { x + y }
|
||||
|
||||
// Default implementation of 'no_touch'.
|
||||
fn no_touch() { throw "do not touch me!"; }
|
||||
|
||||
|
||||
---------------
|
||||
| lowest.rhai |
|
||||
---------------
|
||||
|
||||
// Specific implementation of 'foo'.
|
||||
fn foo(x) { x * 2 }
|
||||
|
||||
// New implementation for this layer.
|
||||
fn baz() { print("hello!"); }
|
||||
|
||||
|
||||
---------------
|
||||
| middle.rhai |
|
||||
---------------
|
||||
|
||||
// Specific implementation of 'bar'.
|
||||
fn bar(x, y) { x - y }
|
||||
|
||||
// Specific implementation of 'baz'.
|
||||
fn baz() { print("hey!"); }
|
||||
|
||||
|
||||
----------------
|
||||
| highest.rhai |
|
||||
----------------
|
||||
|
||||
// Specific implementation of 'foo'.
|
||||
fn foo(x) { x + 42 }
|
||||
```
|
||||
|
||||
Load and merge them sequentially:
|
||||
|
||||
```rust
|
||||
let engine = Engine::new();
|
||||
|
||||
// Compile the baseline default implementations.
|
||||
let mut ast = engine.compile_file("default.rhai".into())?;
|
||||
|
||||
// Merge in the first layer.
|
||||
let lowest = engine.compile_file("lowest.rhai".into())?;
|
||||
ast = ast.merge(&lowest);
|
||||
|
||||
// Merge in the second layer.
|
||||
let middle = engine.compile_file("middle.rhai".into())?;
|
||||
ast = ast.merge(&middle);
|
||||
|
||||
// Merge in the third layer.
|
||||
let highest = engine.compile_file("highest.rhai".into())?;
|
||||
ast = ast.merge(&highest);
|
||||
|
||||
// Now, 'ast' contains the following functions:
|
||||
//
|
||||
// fn no_touch() { // from 'default.rhai'
|
||||
// throw "do not touch me!";
|
||||
// }
|
||||
// fn foo(x) { x + 42 } // from 'highest.rhai'
|
||||
// fn bar(x, y) { x - y } // from 'middle.rhai'
|
||||
// fn baz() { print("hey!"); } // from 'middle.rhai'
|
||||
```
|
||||
|
||||
Unfortunately, there is no `super` call that calls the base implementation
|
||||
(i.e. no way for a higher-layer function to call an equivalent lower-layer function).
|
@ -15,8 +15,8 @@ use rhai::plugin::*;
|
||||
```
|
||||
|
||||
|
||||
`#[export_module]` and `exported_module!`
|
||||
----------------------------------------
|
||||
`#[export_module]`
|
||||
------------------
|
||||
|
||||
When applied to a Rust module, the `#[export_module]` attribute generates the necessary
|
||||
code and metadata to allow Rhai access to its public (i.e. marked `pub`) functions, constants
|
||||
@ -25,12 +25,17 @@ and sub-modules.
|
||||
This code is exactly what would need to be written by hand to achieve the same goal,
|
||||
and is custom fit to each exported item.
|
||||
|
||||
This Rust module can then either be loaded into an [`Engine`] as a normal [module] or
|
||||
registered as a [custom package]. This is done by using the `exported_module!` macro.
|
||||
|
||||
All `pub` functions become registered functions, all `pub` constants become [module] constant variables,
|
||||
and all sub-modules become Rhai sub-modules.
|
||||
|
||||
This Rust module can then either be loaded into an [`Engine`] as a normal [module] or
|
||||
registered as a [package]. This is done by using the `exported_module!` macro.
|
||||
|
||||
The macro `combine_with_exported_module!` can also be used to _combine_ all the functions
|
||||
and variables into an existing module, _flattening_ the namespace - i.e. all sub-modules
|
||||
are eliminated and their contents promoted to the top level. This is typical for
|
||||
developing [custom packages].
|
||||
|
||||
```rust
|
||||
use rhai::plugin::*; // a "prelude" import for macros
|
||||
|
||||
@ -57,7 +62,7 @@ mod my_module {
|
||||
42
|
||||
}
|
||||
|
||||
// This sub-module is ignored when loaded as a package.
|
||||
// Sub-modules are ignored when the Module is loaded as a package.
|
||||
pub mod my_sub_module {
|
||||
// This function is ignored when loaded as a package.
|
||||
// Otherwise it is a valid registered function under a sub-module.
|
||||
@ -65,16 +70,32 @@ mod my_module {
|
||||
"hello".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// Sub-modules are commonly used to put feature gates on a group of
|
||||
// functions because feature gates cannot be put on function definitions.
|
||||
// This is currently a limitation of the plugin procedural macros.
|
||||
#[cfg(feature = "advanced_functions")]
|
||||
pub mod advanced {
|
||||
// This function is ignored when loaded as a package.
|
||||
// Otherwise it is a valid registered function under a sub-module
|
||||
// which only exists when the 'advanced_functions' feature is used.
|
||||
pub fn advanced_calc(input: i64) -> i64 {
|
||||
input * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The simplest way to load this into an [`Engine`] is to use the `load_package` method on the exported module:
|
||||
### Use `Engine::load_package`
|
||||
|
||||
The simplest way to load this into an [`Engine`] is to first use the `exported_module!` macro
|
||||
to turn it into a normal Rhai [module], then use the `Engine::load_package` method on it:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// The macro call creates the Rhai module.
|
||||
// The macro call creates a Rhai module from the plugin module.
|
||||
let module = exported_module!(my_module);
|
||||
|
||||
// A module can simply be loaded, registering all public functions.
|
||||
@ -102,10 +123,77 @@ x == 43;
|
||||
Notice that, when using a [module] as a [package], only functions registered at the _top level_
|
||||
can be accessed. Variables as well as sub-modules are ignored.
|
||||
|
||||
Using this directly as a Rhai module is almost the same, except that a [module resolver] must
|
||||
be used to serve the module, and the module is loaded via `import` statements.
|
||||
### Use as loadable `Module`
|
||||
|
||||
Using this directly as a dynamically-loadable Rhai [module] is almost the same, except that a
|
||||
[module resolver] must be used to serve the module, and the module is loaded via `import` statements.
|
||||
|
||||
See the [module] section for more information.
|
||||
|
||||
### Use as custom package
|
||||
|
||||
Finally the plugin module can also be used to develop a [custom package],
|
||||
using `combine_with_exported_module!`:
|
||||
|
||||
```rust
|
||||
def_package!(rhai:MyPackage:"My own personal super package", module, {
|
||||
combine_with_exported_module!(module, "my_module_ID", my_module));
|
||||
});
|
||||
```
|
||||
|
||||
`combine_with_exported_module!` automatically _flattens_ the module namespace so that all
|
||||
functions in sub-modules are promoted to the top level. This is convenient for [custom packages].
|
||||
|
||||
|
||||
Sub-Modules and Feature Gates
|
||||
----------------------------
|
||||
|
||||
Sub-modules in a plugin module definition are turned into valid sub-modules in the resultant
|
||||
Rhai `Module`.
|
||||
|
||||
They are also commonly used to put _feature gates_ or _compile-time gates_ on a group of functions,
|
||||
because currently attributes do not work on individual function definitions due to a limitation of
|
||||
the procedural macros system.
|
||||
|
||||
This is especially convenient when using the `combine_with_exported_module!` macro to develop
|
||||
[custom packages] because selected groups of functions can easily be included or excluded based on
|
||||
different combinations of feature flags instead of having to manually include/exclude every
|
||||
single function.
|
||||
|
||||
```rust
|
||||
#[export_module]
|
||||
mod my_module {
|
||||
// Always available
|
||||
pub fn func0() {}
|
||||
|
||||
// The following sub-module is only available under 'feature1'
|
||||
#[cfg(feature = "feature1")]
|
||||
pub mod feature1 {
|
||||
fn func1() {}
|
||||
fn func2() {}
|
||||
fn func3() {}
|
||||
}
|
||||
|
||||
// The following sub-module is only available under 'feature2'
|
||||
#[cfg(feature = "feature2")]
|
||||
pub mod feature2 {
|
||||
fn func4() {}
|
||||
fn func5() {}
|
||||
fn func6() {}
|
||||
}
|
||||
}
|
||||
|
||||
// Registered functions:
|
||||
// func0 - always available
|
||||
// func1 - available under 'feature1'
|
||||
// func2 - available under 'feature1'
|
||||
// func3 - available under 'feature1'
|
||||
// func4 - available under 'feature2'
|
||||
// func5 - available under 'feature2'
|
||||
// func6 - available under 'feature2'
|
||||
combine_with_exported_module!(module, "my_module_ID", my_module);
|
||||
```
|
||||
|
||||
|
||||
Function Overloading and Operators
|
||||
---------------------------------
|
||||
|
@ -10,25 +10,19 @@ Create a Module from an AST
|
||||
A _module_ can be created from a single script (or pre-compiled [`AST`]) containing global variables,
|
||||
functions and sub-modules via the `Module::eval_ast_as_new` method.
|
||||
|
||||
See the section on [_Exporting Variables, Functions and Sub-Modules_][`export`] for details on how to prepare
|
||||
a Rhai script for this purpose as well as to control which functions/variables to export.
|
||||
|
||||
When given an [`AST`], it is first evaluated, then the following items are exposed as members of the new module:
|
||||
|
||||
* Global variables - essentially all variables that remain in the [`Scope`] at the end of a script run - that are exported. Variables not exported (via the `export` statement) remain hidden.
|
||||
* Global variables - all variables exported via the `export` statement (those not exported remain hidden).
|
||||
|
||||
* Functions not specifically marked `private`.
|
||||
|
||||
* Global modules that remain in the [`Scope`] at the end of a script run.
|
||||
|
||||
|
||||
`merge_namespaces` Parameter
|
||||
---------------------------
|
||||
|
||||
The parameter `merge_namespaces` in `Module::eval_ast_as_new` determines the exact behavior of
|
||||
functions exposed by the module and the namespace that they can access:
|
||||
|
||||
| `merge_namespaces` value | Description | Namespace | Performance | Call global functions | Call functions in same module |
|
||||
| :----------------------: | ------------------------------------------------ | :-----------------: | :---------: | :-------------------: | :---------------------------: |
|
||||
| `true` | encapsulate entire `AST` into each function call | module, then global | slower | yes | yes |
|
||||
| `false` | register each function independently | global only | fast | yes | no |
|
||||
`Module::eval_ast_as_new` encapsulates the entire `AST` into each function call, merging the module namespace
|
||||
with the global namespace. Therefore, functions defined within the same module script can cross-call each other.
|
||||
|
||||
|
||||
Examples
|
||||
@ -73,13 +67,9 @@ let ast = engine.compile(r#"
|
||||
"#)?;
|
||||
|
||||
// Convert the 'AST' into a module, using the 'Engine' to evaluate it first
|
||||
//
|
||||
// The second parameter ('merge_namespaces'), when set to true, will encapsulate
|
||||
// a copy of the entire 'AST' into each function, allowing functions in the module script
|
||||
// to cross-call each other.
|
||||
//
|
||||
// This incurs additional overhead, avoidable by setting 'merge_namespaces' to false.
|
||||
let module = Module::eval_ast_as_new(Scope::new(), &ast, true, &engine)?;
|
||||
// A copy of the entire 'AST' is encapsulated into each function,
|
||||
// allowing functions in the module script to cross-call each other.
|
||||
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
||||
|
||||
// 'module' now contains:
|
||||
// - sub-module: 'foobar' (renamed from 'extra')
|
||||
|
14
doc/src/rust/modules/index.md
Normal file
14
doc/src/rust/modules/index.md
Normal file
@ -0,0 +1,14 @@
|
||||
Modules
|
||||
=======
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_.
|
||||
Modules can be disabled via the [`no_module`] feature.
|
||||
|
||||
A module is of the type `Module` and holds a collection of functions, variables, iterators and sub-modules.
|
||||
It may be created entirely from Rust functions, or it may encapsulate a Rhai script together with the functions
|
||||
and variables defined by that script.
|
||||
|
||||
Other scripts can then load this module and use the functions and variables exported
|
||||
as if they were defined inside the same script.
|
@ -5,6 +5,8 @@ Module Resolvers
|
||||
|
||||
When encountering an [`import`] statement, Rhai attempts to _resolve_ the module based on the path string.
|
||||
|
||||
See the section on [_Importing Modules_][`import`] for more details.
|
||||
|
||||
_Module Resolvers_ are service types that implement the [`ModuleResolver`][traits] trait.
|
||||
|
||||
|
||||
@ -32,75 +34,97 @@ are _merged_ into a _unified_ namespace.
|
||||
| my_module.rhai |
|
||||
------------------
|
||||
|
||||
// This function overrides any in the main script.
|
||||
private fn inner_message() { "hello! from module!" }
|
||||
|
||||
fn greet(callback) { print(callback.call()); }
|
||||
fn greet() {
|
||||
print(inner_message()); // call function in module script
|
||||
}
|
||||
|
||||
fn greet_main() {
|
||||
print(main_message()); // call function not in module script
|
||||
}
|
||||
|
||||
-------------
|
||||
| main.rhai |
|
||||
-------------
|
||||
|
||||
fn main_message() { "hi! from main!" }
|
||||
// This function is overridden by the module script.
|
||||
fn inner_message() { "hi! from main!" }
|
||||
|
||||
// This function is found by the module script.
|
||||
fn main_message() { "main here!" }
|
||||
|
||||
import "my_module" as m;
|
||||
|
||||
m::greet(|| "hello, " + "world!"); // works - anonymous function in global
|
||||
m::greet(); // prints "hello! from module!"
|
||||
|
||||
m::greet(|| inner_message()); // works - function in module
|
||||
|
||||
m::greet(|| main_message()); // works - function in global
|
||||
m::greet_main(); // prints "main here!"
|
||||
```
|
||||
|
||||
The base directory can be changed via the `FileModuleResolver::new_with_path` constructor function.
|
||||
### Simulating virtual functions
|
||||
|
||||
`FileModuleResolver::create_module` loads a script file and returns a module.
|
||||
When calling a namespace-qualified function defined within a module, other functions defined within
|
||||
the same module script override any similar-named functions (with the same number of parameters)
|
||||
defined in the global namespace. This is to ensure that a module acts as a self-contained unit and
|
||||
functions defined in the calling script do not override module code.
|
||||
|
||||
In some situations, however, it is actually beneficial to do it in reverse: have module code call functions
|
||||
defined in the calling script (i.e. in the global namespace) if they exist, and only call those defined
|
||||
in the module script if none are found.
|
||||
|
||||
`GlobalFileModuleResolver`
|
||||
-------------------------
|
||||
|
||||
A simpler but more efficient version of `FileModuleResolver`, intended for short utility modules.
|
||||
Not available for [`no_std`] or [WASM] builds.
|
||||
Loads a script file (based off the current directory) with `.rhai` extension.
|
||||
|
||||
All functions are assumed **independent** and _cannot_ cross-call each other.
|
||||
Functions are searched _only_ in the _global_ namespace.
|
||||
One such situation is the need to provide a _default implementation_ to a simulated _virtual_ function:
|
||||
|
||||
```rust
|
||||
------------------
|
||||
| my_module.rhai |
|
||||
------------------
|
||||
|
||||
private fn inner_message() { "hello! from module!" }
|
||||
// Do not do this (it will override the main script):
|
||||
// fn message() { "hello! from module!" }
|
||||
|
||||
fn greet_inner() {
|
||||
print(inner_message()); // cross-calling a module function!
|
||||
// there will be trouble because each function
|
||||
// in the module is supposed to be independent
|
||||
// of each other
|
||||
}
|
||||
// This function acts as the default implementation.
|
||||
private fn default_message() { "hello! from module!" }
|
||||
|
||||
// This function depends on a 'virtual' function 'message'
|
||||
// which is not defined in the module script.
|
||||
fn greet() {
|
||||
print(main_message()); // function is searched in global namespace
|
||||
if is_def_fn("message", 0) { // 'is_def_fn' detects if 'message' is defined.
|
||||
print(message());
|
||||
} else {
|
||||
print(default_message());
|
||||
}
|
||||
}
|
||||
|
||||
-------------
|
||||
| main.rhai |
|
||||
-------------
|
||||
|
||||
fn main_message() { "hi! from main!" }
|
||||
// The main script defines 'message' which is needed by the module script.
|
||||
fn message() { "hi! from main!" }
|
||||
|
||||
import "my_module" as m;
|
||||
|
||||
m::greet_inner(); // <- function not found: 'inner_message'
|
||||
m::greet(); // prints "hi! from main!"
|
||||
|
||||
m::greet(); // works because 'main_message' exists in
|
||||
// the global namespace
|
||||
--------------
|
||||
| main2.rhai |
|
||||
--------------
|
||||
|
||||
// The main script does not define 'message' which is needed by the module script.
|
||||
|
||||
import "my_module" as m;
|
||||
|
||||
m::greet(); // prints "hello! from module!"
|
||||
```
|
||||
|
||||
### Changing the base directory
|
||||
|
||||
The base directory can be changed via the `FileModuleResolver::new_with_path` constructor function.
|
||||
|
||||
`GlobalFileModuleResolver::create_module` loads a script file and returns a module.
|
||||
### Returning a module instead
|
||||
|
||||
`FileModuleResolver::create_module` loads a script file and returns a module with the standard behavior.
|
||||
|
||||
|
||||
`StaticModuleResolver`
|
||||
|
@ -26,8 +26,8 @@ more control over what a script can (or cannot) do.
|
||||
| `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 |
|
||||
| `internals` | yes | exposes internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version |
|
||||
|
||||
|
||||
Example
|
||||
|
@ -146,7 +146,7 @@ fn main() {
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
{
|
||||
ast = engine.optimize_ast(&scope, r, OptimizationLevel::Full);
|
||||
ast = engine.optimize_ast(&scope, r, OptimizationLevel::Simple);
|
||||
}
|
||||
|
||||
#[cfg(feature = "no_optimize")]
|
||||
|
10
src/any.rs
10
src/any.rs
@ -21,7 +21,7 @@ use crate::stdlib::{
|
||||
boxed::Box,
|
||||
fmt,
|
||||
ops::{Deref, DerefMut},
|
||||
string::String,
|
||||
string::{String, ToString},
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
@ -531,7 +531,7 @@ impl Dynamic {
|
||||
/// assert_eq!(result.type_name(), "i64");
|
||||
/// assert_eq!(result.to_string(), "42");
|
||||
///
|
||||
/// let result = Dynamic::from("hello".to_string());
|
||||
/// let result = Dynamic::from("hello");
|
||||
/// assert_eq!(result.type_name(), "string");
|
||||
/// assert_eq!(result.to_string(), "hello");
|
||||
///
|
||||
@ -572,6 +572,12 @@ impl Dynamic {
|
||||
.clone()
|
||||
.into();
|
||||
}
|
||||
if TypeId::of::<T>() == TypeId::of::<&str>() {
|
||||
return <dyn Any>::downcast_ref::<&str>(&value)
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.into();
|
||||
}
|
||||
if TypeId::of::<T>() == TypeId::of::<()>() {
|
||||
return ().into();
|
||||
}
|
||||
|
@ -28,10 +28,7 @@ use crate::{
|
||||
use crate::fn_register::{RegisterFn, RegisterResultFn};
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
use crate::{
|
||||
engine::get_script_function_by_signature, fn_args::FuncArgs, fn_call::ensure_no_data_race,
|
||||
utils::StaticVec,
|
||||
};
|
||||
use crate::{fn_args::FuncArgs, fn_call::ensure_no_data_race, utils::StaticVec};
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
use crate::optimize::optimize_into_ast;
|
||||
@ -1598,7 +1595,8 @@ impl Engine {
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
args: &mut [&mut Dynamic],
|
||||
) -> FuncReturn<Dynamic> {
|
||||
let fn_def = get_script_function_by_signature(lib, name, args.len(), true)
|
||||
let fn_def = lib
|
||||
.get_script_fn(name, args.len(), true)
|
||||
.ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::none()))?;
|
||||
|
||||
let mut state = State::new();
|
||||
|
286
src/engine.rs
286
src/engine.rs
@ -7,7 +7,7 @@ use crate::fn_native::{Callback, FnPtr};
|
||||
use crate::module::{Module, ModuleRef};
|
||||
use crate::optimize::OptimizationLevel;
|
||||
use crate::packages::{Package, PackagesCollection, StandardPackage};
|
||||
use crate::parser::{Expr, ReturnType, Stmt};
|
||||
use crate::parser::{Expr, ReturnType, Stmt, INT};
|
||||
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||
@ -18,9 +18,6 @@ use crate::utils::StaticVec;
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
use crate::any::Variant;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
use crate::parser::ScriptFnDef;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
use crate::module::ModuleResolver;
|
||||
|
||||
@ -36,7 +33,7 @@ use crate::utils::ImmutableString;
|
||||
use crate::any::DynamicWriteLock;
|
||||
|
||||
use crate::stdlib::{
|
||||
borrow::Cow,
|
||||
any::type_name,
|
||||
boxed::Box,
|
||||
collections::{HashMap, HashSet},
|
||||
fmt, format,
|
||||
@ -64,13 +61,17 @@ pub type Array = Vec<Dynamic>;
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
pub type Map = HashMap<ImmutableString, Dynamic>;
|
||||
|
||||
/// [INTERNALS] A stack of imported modules.
|
||||
/// _[INTERNALS]_ A stack of imported modules.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// ## WARNING
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
pub type Imports<'a> = Vec<(Cow<'a, str>, Module)>;
|
||||
//
|
||||
// Note - We cannot use &str or Cow<str> here because `eval` may load a module
|
||||
// and the module name will live beyond the AST of the eval script text.
|
||||
// The best we can do is a shared reference.
|
||||
pub type Imports = Vec<(ImmutableString, Module)>;
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[cfg(debug_assertions)]
|
||||
@ -99,7 +100,10 @@ pub const KEYWORD_EVAL: &str = "eval";
|
||||
pub const KEYWORD_FN_PTR: &str = "Fn";
|
||||
pub const KEYWORD_FN_PTR_CALL: &str = "call";
|
||||
pub const KEYWORD_FN_PTR_CURRY: &str = "curry";
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
pub const KEYWORD_IS_SHARED: &str = "is_shared";
|
||||
pub const KEYWORD_IS_DEF_VAR: &str = "is_def_var";
|
||||
pub const KEYWORD_IS_DEF_FN: &str = "is_def_fn";
|
||||
pub const KEYWORD_THIS: &str = "this";
|
||||
pub const FN_TO_STRING: &str = "to_string";
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
@ -147,6 +151,7 @@ pub enum Target<'a> {
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
impl Target<'_> {
|
||||
/// Is the `Target` a reference pointing to other data?
|
||||
#[allow(dead_code)]
|
||||
#[inline(always)]
|
||||
pub fn is_ref(&self) -> bool {
|
||||
match self {
|
||||
@ -160,6 +165,7 @@ impl Target<'_> {
|
||||
}
|
||||
}
|
||||
/// Is the `Target` an owned value?
|
||||
#[allow(dead_code)]
|
||||
#[inline(always)]
|
||||
pub fn is_value(&self) -> bool {
|
||||
match self {
|
||||
@ -173,6 +179,7 @@ impl Target<'_> {
|
||||
}
|
||||
}
|
||||
/// Is the `Target` a shared value?
|
||||
#[allow(dead_code)]
|
||||
#[inline(always)]
|
||||
pub fn is_shared(&self) -> bool {
|
||||
match self {
|
||||
@ -225,25 +232,49 @@ impl Target<'_> {
|
||||
Self::StringChar(_, _, ref mut r) => r,
|
||||
}
|
||||
}
|
||||
/// Update the value of the `Target`.
|
||||
/// Position in `EvalAltResult` is `None` and must be set afterwards.
|
||||
pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> {
|
||||
/// Propagate a changed value back to the original source.
|
||||
/// This has no effect except for string indexing.
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[inline(always)]
|
||||
pub fn propagate_changed_value(&mut self) {
|
||||
match self {
|
||||
Self::Ref(r) => **r = new_val,
|
||||
Self::Ref(_) | Self::Value(_) => (),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Self::LockGuard(_) => (),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::StringChar(_, _, ch) => {
|
||||
let new_val = ch.clone();
|
||||
self.set_value((new_val, Position::none()), Position::none())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Update the value of the `Target`.
|
||||
pub fn set_value(
|
||||
&mut self,
|
||||
new_val: (Dynamic, Position),
|
||||
target_pos: Position,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
match self {
|
||||
Self::Ref(r) => **r = new_val.0,
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Self::LockGuard((r, _)) => **r = new_val,
|
||||
Self::LockGuard((r, _)) => **r = new_val.0,
|
||||
Self::Value(_) => {
|
||||
return EvalAltResult::ErrorAssignmentToUnknownLHS(Position::none()).into();
|
||||
return EvalAltResult::ErrorAssignmentToUnknownLHS(target_pos).into();
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::StringChar(string, index, _) if string.is::<ImmutableString>() => {
|
||||
let mut s = string.write_lock::<ImmutableString>().unwrap();
|
||||
|
||||
// Replace the character at the specified index position
|
||||
let new_ch = new_val
|
||||
.as_char()
|
||||
.map_err(|_| EvalAltResult::ErrorCharMismatch(Position::none()))?;
|
||||
let new_ch = new_val.0.as_char().map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorMismatchDataType(
|
||||
err.to_string(),
|
||||
"char".to_string(),
|
||||
new_val.1,
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut chars = s.chars().collect::<StaticVec<_>>();
|
||||
let ch = chars[*index];
|
||||
@ -286,7 +317,7 @@ impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// [INTERNALS] A type that holds all the current states of the Engine.
|
||||
/// _[INTERNALS]_ A type that holds all the current states of the Engine.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// ## WARNING
|
||||
@ -315,25 +346,7 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a script-defined function definition from a module.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn get_script_function_by_signature<'a>(
|
||||
module: &'a Module,
|
||||
name: &str,
|
||||
params: usize,
|
||||
pub_only: bool,
|
||||
) -> Option<&'a ScriptFnDef> {
|
||||
// Qualifiers (none) + function name + number of arguments.
|
||||
let hash_script = calc_fn_hash(empty(), name, params, empty());
|
||||
let func = module.get_fn(hash_script, pub_only)?;
|
||||
if func.is_script() {
|
||||
Some(func.get_fn_def())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// [INTERNALS] A type containing all the limits imposed by the `Engine`.
|
||||
/// _[INTERNALS]_ A type containing all the limits imposed by the `Engine`.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// ## WARNING
|
||||
@ -428,56 +441,7 @@ impl fmt::Debug for Engine {
|
||||
|
||||
impl Default for Engine {
|
||||
fn default() -> Self {
|
||||
// Create the new scripting Engine
|
||||
let mut engine = Self {
|
||||
id: None,
|
||||
|
||||
packages: Default::default(),
|
||||
global_module: Default::default(),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(any(feature = "no_std", target_arch = "wasm32",))]
|
||||
module_resolver: None,
|
||||
|
||||
type_names: None,
|
||||
disabled_symbols: None,
|
||||
custom_keywords: None,
|
||||
custom_syntax: None,
|
||||
|
||||
// default print/debug implementations
|
||||
print: Box::new(default_print),
|
||||
debug: Box::new(default_print),
|
||||
|
||||
// progress callback
|
||||
progress: None,
|
||||
|
||||
// optimization level
|
||||
optimization_level: if cfg!(feature = "no_optimize") {
|
||||
OptimizationLevel::None
|
||||
} else {
|
||||
OptimizationLevel::Simple
|
||||
},
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
limits: Limits {
|
||||
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
||||
max_expr_depth: MAX_EXPR_DEPTH,
|
||||
max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH,
|
||||
max_operations: 0,
|
||||
max_modules: usize::MAX,
|
||||
max_string_size: 0,
|
||||
max_array_size: 0,
|
||||
max_map_size: 0,
|
||||
},
|
||||
};
|
||||
|
||||
engine.load_package(StandardPackage::new().get());
|
||||
|
||||
engine
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
@ -643,7 +607,56 @@ pub fn search_scope_only<'s, 'a>(
|
||||
impl Engine {
|
||||
/// Create a new `Engine`
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
// Create the new scripting Engine
|
||||
let mut engine = Self {
|
||||
id: None,
|
||||
|
||||
packages: Default::default(),
|
||||
global_module: Default::default(),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(any(feature = "no_std", target_arch = "wasm32",))]
|
||||
module_resolver: None,
|
||||
|
||||
type_names: None,
|
||||
disabled_symbols: None,
|
||||
custom_keywords: None,
|
||||
custom_syntax: None,
|
||||
|
||||
// default print/debug implementations
|
||||
print: Box::new(default_print),
|
||||
debug: Box::new(default_print),
|
||||
|
||||
// progress callback
|
||||
progress: None,
|
||||
|
||||
// optimization level
|
||||
optimization_level: if cfg!(feature = "no_optimize") {
|
||||
OptimizationLevel::None
|
||||
} else {
|
||||
OptimizationLevel::Simple
|
||||
},
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
limits: Limits {
|
||||
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
||||
max_expr_depth: MAX_EXPR_DEPTH,
|
||||
max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH,
|
||||
max_operations: 0,
|
||||
max_modules: usize::MAX,
|
||||
max_string_size: 0,
|
||||
max_array_size: 0,
|
||||
max_map_size: 0,
|
||||
},
|
||||
};
|
||||
|
||||
engine.load_package(StandardPackage::new().get());
|
||||
|
||||
engine
|
||||
}
|
||||
|
||||
/// Create a new `Engine` with minimal built-in functions.
|
||||
@ -700,7 +713,7 @@ impl Engine {
|
||||
idx_values: &mut StaticVec<Dynamic>,
|
||||
chain_type: ChainType,
|
||||
level: usize,
|
||||
new_val: Option<Dynamic>,
|
||||
new_val: Option<(Dynamic, Position)>,
|
||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||
if chain_type == ChainType::None {
|
||||
panic!();
|
||||
@ -747,10 +760,7 @@ impl Engine {
|
||||
{
|
||||
// Indexed value is a reference - update directly
|
||||
Ok(ref mut obj_ptr) => {
|
||||
obj_ptr
|
||||
.set_value(new_val.unwrap())
|
||||
.map_err(|err| err.new_position(rhs.position()))?;
|
||||
|
||||
obj_ptr.set_value(new_val.unwrap(), rhs.position())?;
|
||||
None
|
||||
}
|
||||
Err(err) => match *err {
|
||||
@ -766,7 +776,7 @@ impl Engine {
|
||||
if let Some(mut new_val) = _call_setter {
|
||||
let val = target.as_mut();
|
||||
let val_type_name = val.type_name();
|
||||
let args = &mut [val, &mut idx_val2, &mut new_val];
|
||||
let args = &mut [val, &mut idx_val2, &mut new_val.0];
|
||||
|
||||
self.exec_fn_call(
|
||||
state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, &None,
|
||||
@ -814,8 +824,7 @@ impl Engine {
|
||||
let mut val = self
|
||||
.get_indexed_mut(state, lib, target, index, *pos, true, false, level)?;
|
||||
|
||||
val.set_value(new_val.unwrap())
|
||||
.map_err(|err| err.new_position(rhs.position()))?;
|
||||
val.set_value(new_val.unwrap(), rhs.position())?;
|
||||
Ok((Default::default(), true))
|
||||
}
|
||||
// {xxx:map}.id
|
||||
@ -832,7 +841,7 @@ impl Engine {
|
||||
Expr::Property(x) if new_val.is_some() => {
|
||||
let ((_, _, setter), pos) = x.as_ref();
|
||||
let mut new_val = new_val;
|
||||
let mut args = [target.as_mut(), new_val.as_mut().unwrap()];
|
||||
let mut args = [target.as_mut(), &mut new_val.as_mut().unwrap().0];
|
||||
self.exec_fn_call(
|
||||
state, lib, setter, 0, &mut args, is_ref, true, false, None, &None,
|
||||
level,
|
||||
@ -986,7 +995,7 @@ impl Engine {
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
expr: &Expr,
|
||||
level: usize,
|
||||
new_val: Option<Dynamic>,
|
||||
new_val: Option<(Dynamic, Position)>,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let ((dot_lhs, dot_rhs, op_pos), chain_type) = match expr {
|
||||
Expr::Index(x) => (x.as_ref(), ChainType::Index),
|
||||
@ -1145,7 +1154,7 @@ impl Engine {
|
||||
// val_array[idx]
|
||||
let index = idx
|
||||
.as_int()
|
||||
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_pos))?;
|
||||
.map_err(|err| self.make_type_mismatch_err::<INT>(err, idx_pos))?;
|
||||
|
||||
let arr_len = arr.len();
|
||||
|
||||
@ -1164,15 +1173,15 @@ impl Engine {
|
||||
Dynamic(Union::Map(map)) => {
|
||||
// val_map[idx]
|
||||
Ok(if _create {
|
||||
let index = idx
|
||||
.take_immutable_string()
|
||||
.map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_pos))?;
|
||||
let index = idx.take_immutable_string().map_err(|err| {
|
||||
self.make_type_mismatch_err::<ImmutableString>(err, idx_pos)
|
||||
})?;
|
||||
|
||||
map.entry(index).or_insert_with(Default::default).into()
|
||||
} else {
|
||||
let index = idx
|
||||
.read_lock::<ImmutableString>()
|
||||
.ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?;
|
||||
let index = idx.read_lock::<ImmutableString>().ok_or_else(|| {
|
||||
self.make_type_mismatch_err::<ImmutableString>("", idx_pos)
|
||||
})?;
|
||||
|
||||
map.get_mut(&*index)
|
||||
.map(Target::from)
|
||||
@ -1186,7 +1195,7 @@ impl Engine {
|
||||
let chars_len = s.chars().count();
|
||||
let index = idx
|
||||
.as_int()
|
||||
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_pos))?;
|
||||
.map_err(|err| self.make_type_mismatch_err::<INT>(err, idx_pos))?;
|
||||
|
||||
if index >= 0 {
|
||||
let offset = index as usize;
|
||||
@ -1424,9 +1433,9 @@ impl Engine {
|
||||
let mut rhs_val =
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?;
|
||||
|
||||
let _new_val = Some(if op.is_empty() {
|
||||
let _new_val = if op.is_empty() {
|
||||
// Normal assignment
|
||||
rhs_val
|
||||
Some((rhs_val, rhs_expr.position()))
|
||||
} else {
|
||||
// Op-assignment - always map to `lhs = lhs op rhs`
|
||||
let op = &op[..op.len() - 1]; // extract operator without =
|
||||
@ -1434,15 +1443,20 @@ impl Engine {
|
||||
&mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?,
|
||||
&mut rhs_val,
|
||||
];
|
||||
self.exec_fn_call(
|
||||
state, lib, op, 0, args, false, false, false, None, &None, level,
|
||||
)
|
||||
.map(|(v, _)| v)
|
||||
.map_err(|err| err.new_position(*op_pos))?
|
||||
});
|
||||
|
||||
let result = self
|
||||
.exec_fn_call(
|
||||
state, lib, op, 0, args, false, false, false, None, &None, level,
|
||||
)
|
||||
.map(|(v, _)| v)
|
||||
.map_err(|err| err.new_position(*op_pos))?;
|
||||
|
||||
Some((result, rhs_expr.position()))
|
||||
};
|
||||
|
||||
// Must be either `var[index] op= val` or `var.prop op= val`
|
||||
match lhs_expr {
|
||||
// name op= rhs
|
||||
// name op= rhs (handled above)
|
||||
Expr::Variable(_) => unreachable!(),
|
||||
// idx_lhs[idx_expr] op= rhs
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
@ -1460,12 +1474,8 @@ impl Engine {
|
||||
)?;
|
||||
Ok(Default::default())
|
||||
}
|
||||
// Error assignment to constant
|
||||
expr if expr.is_constant() => EvalAltResult::ErrorAssignmentToConstant(
|
||||
expr.get_constant_str(),
|
||||
expr.position(),
|
||||
)
|
||||
.into(),
|
||||
// Constant expression (should be caught during parsing)
|
||||
expr if expr.is_constant() => unreachable!(),
|
||||
// Syntax error
|
||||
expr => EvalAltResult::ErrorAssignmentToUnknownLHS(expr.position()).into(),
|
||||
}
|
||||
@ -1528,16 +1538,12 @@ impl Engine {
|
||||
Ok((self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?
|
||||
.as_bool()
|
||||
.map_err(|_| {
|
||||
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position())
|
||||
})?
|
||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, lhs.position()))?
|
||||
&& // Short-circuit using &&
|
||||
self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?
|
||||
.as_bool()
|
||||
.map_err(|_| {
|
||||
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position())
|
||||
})?)
|
||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, rhs.position()))?)
|
||||
.into())
|
||||
}
|
||||
|
||||
@ -1546,16 +1552,12 @@ impl Engine {
|
||||
Ok((self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?
|
||||
.as_bool()
|
||||
.map_err(|_| {
|
||||
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position())
|
||||
})?
|
||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, lhs.position()))?
|
||||
|| // Short-circuit using ||
|
||||
self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?
|
||||
.as_bool()
|
||||
.map_err(|_| {
|
||||
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position())
|
||||
})?)
|
||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, rhs.position()))?)
|
||||
.into())
|
||||
}
|
||||
|
||||
@ -1637,7 +1639,7 @@ impl Engine {
|
||||
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
|
||||
.as_bool()
|
||||
.map_err(|_| EvalAltResult::ErrorLogicGuard(expr.position()).into())
|
||||
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))
|
||||
.and_then(|guard_val| {
|
||||
if guard_val {
|
||||
self.eval_stmt(scope, mods, state, lib, this_ptr, if_block, level)
|
||||
@ -1670,7 +1672,9 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
Ok(false) => return Ok(Default::default()),
|
||||
Err(_) => return EvalAltResult::ErrorLogicGuard(expr.position()).into(),
|
||||
Err(err) => {
|
||||
return Err(self.make_type_mismatch_err::<bool>(err, expr.position()))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -1826,7 +1830,7 @@ impl Engine {
|
||||
|
||||
if let Some((name, _)) = alias {
|
||||
module.index_all_sub_modules();
|
||||
mods.push((name.clone().into(), module));
|
||||
mods.push((name.clone(), module));
|
||||
}
|
||||
|
||||
state.modules += 1;
|
||||
@ -1839,7 +1843,7 @@ impl Engine {
|
||||
)
|
||||
}
|
||||
} else {
|
||||
EvalAltResult::ErrorImportExpr(expr.position()).into()
|
||||
Err(self.make_type_mismatch_err::<ImmutableString>("", expr.position()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -2034,4 +2038,14 @@ impl Engine {
|
||||
.and_then(|t| t.get(name).map(String::as_str))
|
||||
.unwrap_or_else(|| map_std_type_name(name))
|
||||
}
|
||||
|
||||
/// Make a Box<EvalAltResult<ErrorMismatchDataType>>.
|
||||
pub fn make_type_mismatch_err<T>(&self, typ: &str, pos: Position) -> Box<EvalAltResult> {
|
||||
EvalAltResult::ErrorMismatchDataType(
|
||||
typ.into(),
|
||||
self.map_type_name(type_name::<T>()).into(),
|
||||
pos,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use crate::stdlib::{
|
||||
string::{String, ToString},
|
||||
};
|
||||
|
||||
/// [INTERNALS] Error encountered when tokenizing the script text.
|
||||
/// _[INTERNALS]_ Error encountered when tokenizing the script text.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// ## WARNING
|
||||
|
180
src/fn_call.rs
180
src/fn_call.rs
@ -4,8 +4,8 @@ use crate::any::Dynamic;
|
||||
use crate::calc_fn_hash;
|
||||
use crate::engine::{
|
||||
search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG,
|
||||
KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_SHARED,
|
||||
KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||
KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_FN,
|
||||
KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||
};
|
||||
use crate::error::ParseErrorType;
|
||||
use crate::fn_native::{FnCallArgs, FnPtr};
|
||||
@ -33,6 +33,9 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET};
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::engine::{Map, Target, FN_GET, FN_SET};
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
use crate::engine::KEYWORD_IS_SHARED;
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
use crate::scope::Entry as ScopeEntry;
|
||||
@ -436,7 +439,33 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Has a system function an override?
|
||||
fn has_override(&self, lib: &Module, hash_fn: u64, hash_script: u64, pub_only: bool) -> bool {
|
||||
pub(crate) fn has_override_by_name_and_arguments(
|
||||
&self,
|
||||
lib: &Module,
|
||||
name: &str,
|
||||
arg_types: &[TypeId],
|
||||
pub_only: bool,
|
||||
) -> bool {
|
||||
let arg_len = if arg_types.is_empty() {
|
||||
usize::MAX
|
||||
} else {
|
||||
arg_types.len()
|
||||
};
|
||||
|
||||
let hash_fn = calc_fn_hash(empty(), name, arg_len, arg_types.iter().cloned());
|
||||
let hash_script = calc_fn_hash(empty(), name, arg_types.len(), empty());
|
||||
|
||||
self.has_override(lib, hash_fn, hash_script, pub_only)
|
||||
}
|
||||
|
||||
// Has a system function an override?
|
||||
pub(crate) fn has_override(
|
||||
&self,
|
||||
lib: &Module,
|
||||
hash_fn: u64,
|
||||
hash_script: u64,
|
||||
pub_only: bool,
|
||||
) -> bool {
|
||||
// NOTE: We skip script functions for global_module and packages, and native functions for lib
|
||||
|
||||
// First check script-defined functions
|
||||
@ -611,7 +640,7 @@ impl Engine {
|
||||
mods: &mut Imports,
|
||||
state: &mut State,
|
||||
lib: &Module,
|
||||
script_expr: &Dynamic,
|
||||
script: &str,
|
||||
_level: usize,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
self.inc_operations(state)?;
|
||||
@ -625,14 +654,6 @@ impl Engine {
|
||||
));
|
||||
}
|
||||
|
||||
let script = script_expr.as_str().map_err(|typ| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name(type_name::<ImmutableString>()).into(),
|
||||
typ.into(),
|
||||
Position::none(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Compile the script text
|
||||
// No optimizations because we only run it once
|
||||
let mut ast = self.compile_with_scope_and_optimization_level(
|
||||
@ -642,7 +663,7 @@ impl Engine {
|
||||
)?;
|
||||
|
||||
// If new functions are defined within the eval string, it is an error
|
||||
if ast.lib().num_fn() != 0 {
|
||||
if ast.lib().count().0 != 0 {
|
||||
return Err(ParseErrorType::WrongFnDefinition.into());
|
||||
}
|
||||
|
||||
@ -675,7 +696,6 @@ impl Engine {
|
||||
level: usize,
|
||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||
let is_ref = target.is_ref();
|
||||
let is_value = target.is_value();
|
||||
|
||||
// Get a reference to the mutation target Dynamic
|
||||
let obj = target.as_mut();
|
||||
@ -744,15 +764,18 @@ impl Engine {
|
||||
.into(),
|
||||
false,
|
||||
))
|
||||
} else if cfg!(not(feature = "no_closure"))
|
||||
&& _fn_name == KEYWORD_IS_SHARED
|
||||
&& idx.is_empty()
|
||||
{
|
||||
} else if {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
{
|
||||
_fn_name == KEYWORD_IS_SHARED && idx.is_empty()
|
||||
}
|
||||
#[cfg(feature = "no_closure")]
|
||||
false
|
||||
} {
|
||||
// is_shared call
|
||||
Ok((target.is_shared().into(), false))
|
||||
} else {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
let redirected;
|
||||
let _redirected;
|
||||
let mut hash = hash_script;
|
||||
|
||||
// Check if it is a map method call in OOP style
|
||||
@ -761,8 +784,8 @@ impl Engine {
|
||||
if let Some(val) = map.get(_fn_name) {
|
||||
if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
|
||||
// Remap the function name
|
||||
redirected = fn_ptr.get_fn_name().clone();
|
||||
_fn_name = &redirected;
|
||||
_redirected = fn_ptr.get_fn_name().clone();
|
||||
_fn_name = &_redirected;
|
||||
// Add curried arguments
|
||||
if !fn_ptr.curry().is_empty() {
|
||||
fn_ptr
|
||||
@ -795,10 +818,9 @@ impl Engine {
|
||||
)
|
||||
}?;
|
||||
|
||||
// Feed the changed temp value back
|
||||
if updated && !is_ref && !is_value {
|
||||
let new_val = target.as_mut().clone();
|
||||
target.set_value(new_val)?;
|
||||
// Propagate the changed value back to the source if necessary
|
||||
if updated {
|
||||
target.propagate_changed_value();
|
||||
}
|
||||
|
||||
Ok((result, updated))
|
||||
@ -834,12 +856,7 @@ impl Engine {
|
||||
return arg_value
|
||||
.take_immutable_string()
|
||||
.map_err(|typ| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name(type_name::<ImmutableString>()).into(),
|
||||
typ.into(),
|
||||
expr.position(),
|
||||
)
|
||||
.into()
|
||||
self.make_type_mismatch_err::<ImmutableString>(typ, expr.position())
|
||||
})
|
||||
.and_then(|s| FnPtr::try_from(s))
|
||||
.map(Into::<Dynamic>::into)
|
||||
@ -850,18 +867,16 @@ impl Engine {
|
||||
// Handle curry()
|
||||
if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 {
|
||||
let expr = args_expr.get(0).unwrap();
|
||||
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
|
||||
if !fn_ptr.is::<FnPtr>() {
|
||||
return EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name(type_name::<FnPtr>()).into(),
|
||||
self.map_type_name(fn_ptr.type_name()).into(),
|
||||
if !arg_value.is::<FnPtr>() {
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||
self.map_type_name(arg_value.type_name()),
|
||||
expr.position(),
|
||||
)
|
||||
.into();
|
||||
));
|
||||
}
|
||||
|
||||
let (fn_name, fn_curry) = fn_ptr.cast::<FnPtr>().take_data();
|
||||
let (fn_name, fn_curry) = arg_value.cast::<FnPtr>().take_data();
|
||||
|
||||
let curry: StaticVec<_> = args_expr
|
||||
.iter()
|
||||
@ -877,7 +892,8 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Handle is_shared()
|
||||
if cfg!(not(feature = "no_closure")) && name == KEYWORD_IS_SHARED && args_expr.len() == 1 {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
if name == KEYWORD_IS_SHARED && args_expr.len() == 1 {
|
||||
let expr = args_expr.get(0).unwrap();
|
||||
let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
|
||||
@ -895,10 +911,10 @@ impl Engine {
|
||||
&& !self.has_override(lib, 0, hash_script, pub_only)
|
||||
{
|
||||
let expr = args_expr.get(0).unwrap();
|
||||
let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
|
||||
if fn_name.is::<FnPtr>() {
|
||||
let fn_ptr = fn_name.cast::<FnPtr>();
|
||||
if arg_value.is::<FnPtr>() {
|
||||
let fn_ptr = arg_value.cast::<FnPtr>();
|
||||
curry = fn_ptr.curry().iter().cloned().collect();
|
||||
// Redirect function name
|
||||
redirected = fn_ptr.take_data().0;
|
||||
@ -908,12 +924,64 @@ impl Engine {
|
||||
// Recalculate hash
|
||||
hash_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty());
|
||||
} else {
|
||||
return EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name(type_name::<FnPtr>()).into(),
|
||||
fn_name.type_name().into(),
|
||||
return Err(self.make_type_mismatch_err::<FnPtr>(
|
||||
self.map_type_name(arg_value.type_name()),
|
||||
expr.position(),
|
||||
)
|
||||
.into();
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Handle is_def_var()
|
||||
if name == KEYWORD_IS_DEF_VAR && args_expr.len() == 1 {
|
||||
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
|
||||
|
||||
if !self.has_override(lib, hash_fn, hash_script, pub_only) {
|
||||
let expr = args_expr.get(0).unwrap();
|
||||
let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
let var_name = arg_value.as_str().map_err(|err| {
|
||||
self.make_type_mismatch_err::<ImmutableString>(err, expr.position())
|
||||
})?;
|
||||
if var_name.is_empty() {
|
||||
return Ok(false.into());
|
||||
} else {
|
||||
return Ok(scope.contains(var_name).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle is_def_fn()
|
||||
if name == KEYWORD_IS_DEF_FN && args_expr.len() == 2 {
|
||||
let hash_fn = calc_fn_hash(
|
||||
empty(),
|
||||
name,
|
||||
2,
|
||||
[TypeId::of::<ImmutableString>(), TypeId::of::<INT>()]
|
||||
.iter()
|
||||
.cloned(),
|
||||
);
|
||||
|
||||
if !self.has_override(lib, hash_fn, hash_script, pub_only) {
|
||||
let fn_name_expr = args_expr.get(0).unwrap();
|
||||
let num_params_expr = args_expr.get(1).unwrap();
|
||||
|
||||
let arg0_value =
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, fn_name_expr, level)?;
|
||||
let arg1_value =
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, num_params_expr, level)?;
|
||||
|
||||
let fn_name = arg0_value.as_str().map_err(|err| {
|
||||
self.make_type_mismatch_err::<ImmutableString>(err, fn_name_expr.position())
|
||||
})?;
|
||||
let num_params = arg1_value.as_int().map_err(|err| {
|
||||
self.make_type_mismatch_err::<INT>(err, num_params_expr.position())
|
||||
})?;
|
||||
|
||||
if fn_name.is_empty() || num_params < 0 {
|
||||
return Ok(false.into());
|
||||
} else {
|
||||
let hash = calc_fn_hash(empty(), fn_name, num_params as usize, empty());
|
||||
return Ok(lib.contains_fn(hash, false).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -925,10 +993,16 @@ impl Engine {
|
||||
// eval - only in function call style
|
||||
let prev_len = scope.len();
|
||||
let expr = args_expr.get(0).unwrap();
|
||||
let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
let result = self
|
||||
.eval_script_expr(scope, mods, state, lib, &script, level + 1)
|
||||
.map_err(|err| err.new_position(expr.position()));
|
||||
let arg_value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
let script = arg_value.as_str().map_err(|typ| {
|
||||
self.make_type_mismatch_err::<ImmutableString>(typ, expr.position())
|
||||
})?;
|
||||
let result = if !script.is_empty() {
|
||||
self.eval_script_expr(scope, mods, state, lib, script, level + 1)
|
||||
.map_err(|err| err.new_position(expr.position()))
|
||||
} else {
|
||||
Ok(().into())
|
||||
};
|
||||
|
||||
// IMPORTANT! If the eval defines new variables in the current scope,
|
||||
// all variable offsets from this point on will be mis-aligned.
|
||||
|
@ -351,10 +351,10 @@ impl CallableFunction {
|
||||
///
|
||||
/// Panics if the `CallableFunction` is not `Script`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn get_shared_fn_def(&self) -> Shared<ScriptFnDef> {
|
||||
pub fn get_shared_fn_def(&self) -> &Shared<ScriptFnDef> {
|
||||
match self {
|
||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(),
|
||||
Self::Script(f) => f.clone(),
|
||||
Self::Script(f) => f,
|
||||
}
|
||||
}
|
||||
/// Get a reference to a script-defined function definition.
|
||||
|
@ -178,7 +178,7 @@ macro_rules! def_register {
|
||||
(imp $abi:ident : $($par:ident => $arg:expr => $mark:ty => $param:ty => $let:stmt => $clone:expr),*) => {
|
||||
// ^ function ABI type
|
||||
// ^ function parameter generic type name (A, B, C etc.)
|
||||
// ^ call argument(like A, *B, &mut C etc)
|
||||
// ^ call argument(like A, *B, &mut C etc)
|
||||
// ^ function parameter marker type (T, Ref<T> or Mut<T>)
|
||||
// ^ function parameter actual type (T, &T or &mut T)
|
||||
// ^ argument let statement
|
||||
|
10
src/lib.rs
10
src/lib.rs
@ -128,16 +128,14 @@ pub mod module_resolvers {
|
||||
pub use crate::module::resolvers::*;
|
||||
}
|
||||
|
||||
/// Serialization support for [`serde`](https://crates.io/crates/serde).
|
||||
///
|
||||
/// Requires the `serde` feature.
|
||||
/// _[SERDE]_ Serialization support for [`serde`](https://crates.io/crates/serde).
|
||||
/// Exported under the `serde` feature.
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod ser {
|
||||
pub use crate::serde::ser::to_dynamic;
|
||||
}
|
||||
/// Deserialization support for [`serde`](https://crates.io/crates/serde).
|
||||
///
|
||||
/// Requires the `serde` feature.
|
||||
/// _[SERDE]_ Deserialization support for [`serde`](https://crates.io/crates/serde).
|
||||
/// Exported under the `serde` feature.
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod de {
|
||||
pub use crate::serde::de::from_dynamic;
|
||||
|
@ -5,7 +5,7 @@ use crate::calc_fn_hash;
|
||||
use crate::engine::Engine;
|
||||
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync};
|
||||
use crate::fn_register::by_value as cast_arg;
|
||||
use crate::parser::{FnAccess, FnAccess::Public};
|
||||
use crate::parser::FnAccess;
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::token::{Position, Token};
|
||||
use crate::utils::{ImmutableString, StaticVec, StraightHasherBuilder};
|
||||
@ -85,7 +85,7 @@ impl fmt::Debug for Module {
|
||||
"Module(\n modules: {}\n vars: {}\n functions: {}\n)",
|
||||
self.modules
|
||||
.keys()
|
||||
.map(|k| k.as_str())
|
||||
.map(String::as_str)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
self.variables
|
||||
@ -294,6 +294,24 @@ impl Module {
|
||||
hash_script
|
||||
}
|
||||
|
||||
/// Get a script-defined function in the module based on name and number of parameters.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn get_script_fn(
|
||||
&self,
|
||||
name: &str,
|
||||
num_params: usize,
|
||||
public_only: bool,
|
||||
) -> Option<&Shared<ScriptFnDef>> {
|
||||
self.functions
|
||||
.values()
|
||||
.find(|(fn_name, access, num, _, _)| {
|
||||
(!public_only || *access == FnAccess::Public)
|
||||
&& *num == num_params
|
||||
&& fn_name == name
|
||||
})
|
||||
.map(|(_, _, _, _, f)| f.get_shared_fn_def())
|
||||
}
|
||||
|
||||
/// Does a sub-module exist in the module?
|
||||
///
|
||||
/// # Examples
|
||||
@ -382,10 +400,7 @@ impl Module {
|
||||
} else if public_only {
|
||||
self.functions
|
||||
.get(&hash_fn)
|
||||
.map(|(_, access, _, _, _)| match access {
|
||||
FnAccess::Public => true,
|
||||
FnAccess::Private => false,
|
||||
})
|
||||
.map(|(_, access, _, _, _)| access.is_public())
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
self.functions.contains_key(&hash_fn)
|
||||
@ -457,6 +472,9 @@ impl Module {
|
||||
/// Arguments are simply passed in as a mutable array of `&mut Dynamic`,
|
||||
/// which is guaranteed to contain enough arguments of the correct types.
|
||||
///
|
||||
/// The function is assumed to be a _method_, meaning that the first argument should not be consumed.
|
||||
/// All other arguments can be consumed.
|
||||
///
|
||||
/// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::<T>()`
|
||||
///
|
||||
/// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::<T>()`.
|
||||
@ -506,7 +524,7 @@ impl Module {
|
||||
};
|
||||
self.set_fn(
|
||||
name,
|
||||
Public,
|
||||
FnAccess::Public,
|
||||
arg_types,
|
||||
CallableFunction::from_method(Box::new(f)),
|
||||
)
|
||||
@ -519,19 +537,19 @@ impl Module {
|
||||
pub(crate) fn set_raw_fn_as_scripted(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
num_args: usize,
|
||||
num_params: 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 hash_script = calc_fn_hash(empty(), &name, num_params, 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,
|
||||
num_params,
|
||||
None,
|
||||
CallableFunction::from_pure(Box::new(f)),
|
||||
),
|
||||
@ -562,7 +580,7 @@ impl Module {
|
||||
let arg_types = [];
|
||||
self.set_fn(
|
||||
name,
|
||||
Public,
|
||||
FnAccess::Public,
|
||||
&arg_types,
|
||||
CallableFunction::from_pure(Box::new(f)),
|
||||
)
|
||||
@ -592,7 +610,7 @@ impl Module {
|
||||
let arg_types = [TypeId::of::<A>()];
|
||||
self.set_fn(
|
||||
name,
|
||||
Public,
|
||||
FnAccess::Public,
|
||||
&arg_types,
|
||||
CallableFunction::from_pure(Box::new(f)),
|
||||
)
|
||||
@ -622,7 +640,7 @@ impl Module {
|
||||
let arg_types = [TypeId::of::<A>()];
|
||||
self.set_fn(
|
||||
name,
|
||||
Public,
|
||||
FnAccess::Public,
|
||||
&arg_types,
|
||||
CallableFunction::from_method(Box::new(f)),
|
||||
)
|
||||
@ -679,7 +697,7 @@ impl Module {
|
||||
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
|
||||
self.set_fn(
|
||||
name,
|
||||
Public,
|
||||
FnAccess::Public,
|
||||
&arg_types,
|
||||
CallableFunction::from_pure(Box::new(f)),
|
||||
)
|
||||
@ -715,7 +733,7 @@ impl Module {
|
||||
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
|
||||
self.set_fn(
|
||||
name,
|
||||
Public,
|
||||
FnAccess::Public,
|
||||
&arg_types,
|
||||
CallableFunction::from_method(Box::new(f)),
|
||||
)
|
||||
@ -825,7 +843,7 @@ impl Module {
|
||||
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
|
||||
self.set_fn(
|
||||
name,
|
||||
Public,
|
||||
FnAccess::Public,
|
||||
&arg_types,
|
||||
CallableFunction::from_pure(Box::new(f)),
|
||||
)
|
||||
@ -867,7 +885,7 @@ impl Module {
|
||||
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
|
||||
self.set_fn(
|
||||
name,
|
||||
Public,
|
||||
FnAccess::Public,
|
||||
&arg_types,
|
||||
CallableFunction::from_method(Box::new(f)),
|
||||
)
|
||||
@ -924,7 +942,7 @@ impl Module {
|
||||
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
|
||||
self.set_fn(
|
||||
FN_IDX_SET,
|
||||
Public,
|
||||
FnAccess::Public,
|
||||
&arg_types,
|
||||
CallableFunction::from_method(Box::new(f)),
|
||||
)
|
||||
@ -1012,7 +1030,7 @@ impl Module {
|
||||
];
|
||||
self.set_fn(
|
||||
name,
|
||||
Public,
|
||||
FnAccess::Public,
|
||||
&arg_types,
|
||||
CallableFunction::from_pure(Box::new(f)),
|
||||
)
|
||||
@ -1061,7 +1079,7 @@ impl Module {
|
||||
];
|
||||
self.set_fn(
|
||||
name,
|
||||
Public,
|
||||
FnAccess::Public,
|
||||
&arg_types,
|
||||
CallableFunction::from_method(Box::new(f)),
|
||||
)
|
||||
@ -1152,11 +1170,11 @@ impl Module {
|
||||
) -> &mut Self {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if !other.modules.is_empty() {
|
||||
for (k, v) in &other.modules {
|
||||
other.modules.iter().for_each(|(k, v)| {
|
||||
let mut m = Self::new();
|
||||
m.merge_filtered(v, _filter);
|
||||
self.modules.insert(k.clone(), m);
|
||||
}
|
||||
});
|
||||
}
|
||||
#[cfg(feature = "no_function")]
|
||||
if !other.modules.is_empty() {
|
||||
@ -1210,17 +1228,13 @@ impl Module {
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the number of variables in the module.
|
||||
pub fn num_var(&self) -> usize {
|
||||
self.variables.len()
|
||||
}
|
||||
/// Get the number of functions in the module.
|
||||
pub fn num_fn(&self) -> usize {
|
||||
self.variables.len()
|
||||
}
|
||||
/// Get the number of type iterators in the module.
|
||||
pub fn num_iter(&self) -> usize {
|
||||
self.variables.len()
|
||||
/// Get the number of variables, functions and type iterators in the module.
|
||||
pub fn count(&self) -> (usize, usize, usize) {
|
||||
(
|
||||
self.variables.len(),
|
||||
self.variables.len(),
|
||||
self.variables.len(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get an iterator to the variables in the module.
|
||||
@ -1234,36 +1248,65 @@ impl Module {
|
||||
}
|
||||
|
||||
/// Get an iterator over all script-defined functions in the module.
|
||||
///
|
||||
/// Function metadata includes:
|
||||
/// 1) Access mode (`FnAccess::Public` or `FnAccess::Private`).
|
||||
/// 2) Function name (as string slice).
|
||||
/// 3) Number of parameters.
|
||||
/// 4) Shared reference to function definition `ScriptFnDef`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn iter_script_fn<'a>(&'a self) -> impl Iterator<Item = Shared<ScriptFnDef>> + 'a {
|
||||
pub(crate) fn iter_script_fn<'a>(
|
||||
&'a self,
|
||||
) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<ScriptFnDef>)> + 'a {
|
||||
self.functions
|
||||
.values()
|
||||
.map(|(_, _, _, _, f)| f)
|
||||
.filter(|f| f.is_script())
|
||||
.map(|f| f.get_shared_fn_def())
|
||||
.map(CallableFunction::get_shared_fn_def)
|
||||
.map(|f| {
|
||||
let func = f.clone();
|
||||
(f.access, f.name.as_str(), f.params.len(), func)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get an iterator over all script-defined functions in the module.
|
||||
///
|
||||
/// Function metadata includes:
|
||||
/// 1) Access mode (`FnAccess::Public` or `FnAccess::Private`).
|
||||
/// 2) Function name (as string slice).
|
||||
/// 3) Number of parameters.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn iter_script_fn_info(&self, mut action: impl FnMut(FnAccess, &str, usize)) {
|
||||
#[cfg(not(feature = "internals"))]
|
||||
pub fn iter_script_fn_info(&self) -> impl Iterator<Item = (FnAccess, &str, usize)> {
|
||||
self.functions
|
||||
.iter()
|
||||
.for_each(|(_, (_, _, _, _, v))| match v {
|
||||
CallableFunction::Script(f) => action(f.access, f.name.as_str(), f.params.len()),
|
||||
_ => (),
|
||||
});
|
||||
.values()
|
||||
.filter(|(_, _, _, _, f)| f.is_script())
|
||||
.map(|(name, access, num_params, _, _)| (*access, name.as_str(), *num_params))
|
||||
}
|
||||
|
||||
/// Get an iterator over all script-defined functions in the module.
|
||||
///
|
||||
/// Function metadata includes:
|
||||
/// 1) Access mode (`FnAccess::Public` or `FnAccess::Private`).
|
||||
/// 2) Function name (as string slice).
|
||||
/// 3) Number of parameters.
|
||||
/// 4) _[INTERNALS]_ Shared reference to function definition `ScriptFnDef`.
|
||||
/// Exported under the internals feature only.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "internals")]
|
||||
#[inline(always)]
|
||||
pub fn iter_script_fn_info(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<ScriptFnDef>)> {
|
||||
self.iter_script_fn()
|
||||
}
|
||||
|
||||
/// Create a new `Module` by evaluating an `AST`.
|
||||
///
|
||||
/// ### `merge_namespaces` parameter
|
||||
///
|
||||
/// * If `true`, the entire `AST` is encapsulated into each function, allowing functions
|
||||
/// to cross-call each other. Functions in the global namespace, plus all functions
|
||||
/// defined in the module, are _merged_ into a _unified_ namespace before each call.
|
||||
/// Therefore, all functions will be found, at the expense of some performance.
|
||||
///
|
||||
/// * If `false`, each function is registered independently and cannot cross-call
|
||||
/// each other. Functions are searched in the global namespace.
|
||||
/// The entire `AST` is encapsulated into each function, allowing functions
|
||||
/// to cross-call each other. Functions in the global namespace, plus all functions
|
||||
/// defined in the module, are _merged_ into a _unified_ namespace before each call.
|
||||
/// Therefore, all functions will be found.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -1273,19 +1316,14 @@ impl Module {
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
/// let ast = engine.compile("let answer = 42; export answer;")?;
|
||||
/// let module = Module::eval_ast_as_new(Scope::new(), &ast, true, &engine)?;
|
||||
/// let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
||||
/// assert!(module.contains_var("answer"));
|
||||
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub fn eval_ast_as_new(
|
||||
mut scope: Scope,
|
||||
ast: &AST,
|
||||
merge_namespaces: bool,
|
||||
engine: &Engine,
|
||||
) -> FuncReturn<Self> {
|
||||
pub fn eval_ast_as_new(mut scope: Scope, ast: &AST, engine: &Engine) -> FuncReturn<Self> {
|
||||
let mut mods = Imports::new();
|
||||
|
||||
// Run the script
|
||||
@ -1298,8 +1336,8 @@ impl Module {
|
||||
.into_iter()
|
||||
.for_each(|ScopeEntry { value, alias, .. }| {
|
||||
// Variables with an alias left in the scope become module variables
|
||||
if alias.is_some() {
|
||||
module.variables.insert(*alias.unwrap(), value);
|
||||
if let Some(alias) = alias {
|
||||
module.variables.insert(*alias, value);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1309,16 +1347,17 @@ impl Module {
|
||||
});
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if merge_namespaces {
|
||||
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();
|
||||
{
|
||||
let ast_lib: Shared<Module> = ast.lib().clone().into();
|
||||
|
||||
ast.iter_functions()
|
||||
.filter(|(access, _, _, _)| access.is_public())
|
||||
.for_each(|(_, name, num_params, func)| {
|
||||
let ast_lib = ast_lib.clone();
|
||||
|
||||
module.set_raw_fn_as_scripted(
|
||||
name,
|
||||
num_args,
|
||||
num_params,
|
||||
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
|
||||
let mut lib_merged;
|
||||
|
||||
@ -1332,12 +1371,16 @@ impl Module {
|
||||
};
|
||||
|
||||
engine
|
||||
.call_fn_dynamic_raw(
|
||||
&mut Scope::new(),
|
||||
&unified_lib,
|
||||
&fn_name,
|
||||
.call_script_fn(
|
||||
&mut Default::default(),
|
||||
&mut Default::default(),
|
||||
&mut Default::default(),
|
||||
unified_lib,
|
||||
&mut None,
|
||||
&func.name,
|
||||
func.as_ref(),
|
||||
args,
|
||||
0,
|
||||
)
|
||||
.map_err(|err| {
|
||||
// Wrap the error in a module-error
|
||||
@ -1349,10 +1392,7 @@ impl Module {
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
module.merge(ast.lib());
|
||||
});
|
||||
}
|
||||
|
||||
Ok(module)
|
||||
@ -1369,71 +1409,68 @@ impl Module {
|
||||
variables: &mut Vec<(u64, Dynamic)>,
|
||||
functions: &mut Vec<(u64, CallableFunction)>,
|
||||
) {
|
||||
for (name, m) in &module.modules {
|
||||
module.modules.iter().for_each(|(name, m)| {
|
||||
// Index all the sub-modules first.
|
||||
qualifiers.push(name);
|
||||
index_module(m, qualifiers, variables, functions);
|
||||
qualifiers.pop();
|
||||
}
|
||||
});
|
||||
|
||||
// Index all variables
|
||||
for (var_name, value) in &module.variables {
|
||||
module.variables.iter().for_each(|(var_name, value)| {
|
||||
// Qualifiers + variable name
|
||||
let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0, empty());
|
||||
variables.push((hash_var, value.clone()));
|
||||
}
|
||||
});
|
||||
// Index all Rust functions
|
||||
for (&_hash, (name, access, _num_args, params, func)) in module.functions.iter() {
|
||||
match access {
|
||||
// Private functions are not exported
|
||||
FnAccess::Private => continue,
|
||||
FnAccess::Public => (),
|
||||
}
|
||||
module
|
||||
.functions
|
||||
.iter()
|
||||
.filter(|(_, (_, access, _, _, _))| access.is_public())
|
||||
.for_each(|(&_hash, (name, _, _num_params, params, func))| {
|
||||
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().cloned(), 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;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
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;
|
||||
}
|
||||
|
||||
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()));
|
||||
} else if cfg!(not(feature = "no_function")) {
|
||||
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_params,
|
||||
empty(),
|
||||
)
|
||||
};
|
||||
functions.push((hash_qualified_script, func.clone()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if self.indexed {
|
||||
return;
|
||||
if !self.indexed {
|
||||
let mut qualifiers: Vec<_> = Default::default();
|
||||
let mut variables: Vec<_> = Default::default();
|
||||
let mut functions: Vec<_> = Default::default();
|
||||
|
||||
qualifiers.push("root");
|
||||
|
||||
index_module(self, &mut qualifiers, &mut variables, &mut functions);
|
||||
|
||||
self.all_variables = variables.into_iter().collect();
|
||||
self.all_functions = functions.into_iter().collect();
|
||||
self.indexed = true;
|
||||
}
|
||||
|
||||
let mut qualifiers: Vec<_> = Default::default();
|
||||
let mut variables: Vec<_> = Default::default();
|
||||
let mut functions: Vec<_> = Default::default();
|
||||
|
||||
qualifiers.push("root");
|
||||
|
||||
index_module(self, &mut qualifiers, &mut variables, &mut functions);
|
||||
|
||||
self.all_variables = variables.into_iter().collect();
|
||||
self.all_functions = functions.into_iter().collect();
|
||||
self.indexed = true;
|
||||
}
|
||||
|
||||
/// Does a type iterator exist in the module?
|
||||
@ -1454,7 +1491,7 @@ impl Module {
|
||||
}
|
||||
}
|
||||
|
||||
/// [INTERNALS] A chain of module names to qualify a variable or function call.
|
||||
/// _[INTERNALS]_ A chain of module names to qualify a variable or function call.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// A `u64` hash key is cached for quick search purposes.
|
||||
|
@ -153,7 +153,7 @@ impl ModuleResolver for FileModuleResolver {
|
||||
let c = self.cache.read().unwrap();
|
||||
|
||||
if let Some(ast) = c.get(&file_path) {
|
||||
module = Module::eval_ast_as_new(scope, ast, true, engine).map_err(|err| {
|
||||
module = Module::eval_ast_as_new(scope, ast, engine).map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos))
|
||||
})?;
|
||||
None
|
||||
@ -163,7 +163,7 @@ impl ModuleResolver for FileModuleResolver {
|
||||
Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos))
|
||||
})?;
|
||||
|
||||
module = Module::eval_ast_as_new(scope, &ast, true, engine).map_err(|err| {
|
||||
module = Module::eval_ast_as_new(scope, &ast, engine).map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos))
|
||||
})?;
|
||||
Some(ast)
|
||||
|
@ -1,188 +0,0 @@
|
||||
use crate::engine::Engine;
|
||||
use crate::module::{Module, ModuleResolver};
|
||||
use crate::parser::AST;
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::token::Position;
|
||||
|
||||
use crate::stdlib::{boxed::Box, collections::HashMap, path::PathBuf, string::String};
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
use crate::stdlib::cell::RefCell;
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
use crate::stdlib::sync::RwLock;
|
||||
|
||||
/// Module resolution service that loads module script files from the file system.
|
||||
///
|
||||
/// All functions in each module are treated as strictly independent 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();
|
||||
let module;
|
||||
|
||||
// See if it is cached
|
||||
let 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 = Module::eval_ast_as_new(scope, ast, false, engine).map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos))
|
||||
})?;
|
||||
None
|
||||
} else {
|
||||
// Load the file and compile it if not found
|
||||
let ast = engine.compile_file(file_path.clone()).map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos))
|
||||
})?;
|
||||
|
||||
module = Module::eval_ast_as_new(scope, &ast, false, engine).map_err(|err| {
|
||||
Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, 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)
|
||||
}
|
||||
}
|
@ -17,14 +17,6 @@ mod file;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use file::FileModuleResolver;
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod global_file;
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use global_file::GlobalFileModuleResolver;
|
||||
|
||||
mod stat;
|
||||
pub use stat::StaticModuleResolver;
|
||||
|
||||
|
115
src/optimize.rs
115
src/optimize.rs
@ -3,8 +3,10 @@
|
||||
use crate::any::Dynamic;
|
||||
use crate::calc_fn_hash;
|
||||
use crate::engine::{
|
||||
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR,
|
||||
KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||
};
|
||||
use crate::fn_call::run_builtin_binary_op;
|
||||
use crate::fn_native::FnPtr;
|
||||
use crate::module::Module;
|
||||
use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST};
|
||||
@ -384,7 +386,13 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt {
|
||||
/// Optimize an expression.
|
||||
fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||
// These keywords are handled specially
|
||||
const DONT_EVAL_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_EVAL];
|
||||
const DONT_EVAL_KEYWORDS: &[&str] = &[
|
||||
KEYWORD_PRINT,
|
||||
KEYWORD_DEBUG,
|
||||
KEYWORD_EVAL,
|
||||
KEYWORD_IS_DEF_FN,
|
||||
KEYWORD_IS_DEF_VAR,
|
||||
];
|
||||
|
||||
match expr {
|
||||
// expr - do not promote because there is a reason it is wrapped in an `Expr::Expr`
|
||||
@ -568,58 +576,77 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||
}
|
||||
}
|
||||
|
||||
// Call built-in functions
|
||||
Expr::FnCall(mut x)
|
||||
if x.1.is_none() // Non-qualified
|
||||
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
|
||||
&& x.3.len() == 2 // binary call
|
||||
&& x.3.iter().all(Expr::is_constant) // all arguments are constants
|
||||
=> {
|
||||
let ((name, _, _, pos), _, _, args, _) = x.as_mut();
|
||||
|
||||
let arg_values: StaticVec<_> = args.iter().map(|e| e.get_constant_value().unwrap()).collect();
|
||||
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
|
||||
|
||||
// Search for overloaded operators (can override built-in).
|
||||
if !state.engine.has_override_by_name_and_arguments(state.lib, name, arg_types.as_ref(), false) {
|
||||
if let Some(expr) = run_builtin_binary_op(name, &arg_values[0], &arg_values[1])
|
||||
.ok().flatten()
|
||||
.and_then(|result| map_dynamic_to_expr(result, *pos))
|
||||
{
|
||||
state.set_dirty();
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
|
||||
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
|
||||
Expr::FnCall(x)
|
||||
}
|
||||
|
||||
// Eagerly call functions
|
||||
Expr::FnCall(mut x)
|
||||
if x.1.is_none() // Non-qualified
|
||||
&& state.optimization_level == OptimizationLevel::Full // full optimizations
|
||||
&& x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants
|
||||
&& x.3.iter().all(Expr::is_constant) // all arguments are constants
|
||||
=> {
|
||||
let ((name, _, _, pos), _, _, args, def_value) = x.as_mut();
|
||||
|
||||
// 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)| {
|
||||
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())
|
||||
}).is_some();
|
||||
// First search for script-defined functions (can override built-in)
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let has_script_fn = state.lib.get_script_fn(name, args.len(), false).is_some();
|
||||
#[cfg(feature = "no_function")]
|
||||
let has_script_fn = false;
|
||||
|
||||
if has_script_fn {
|
||||
// A script-defined function overrides the built-in function - do not make the call
|
||||
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
|
||||
return Expr::FnCall(x);
|
||||
if !has_script_fn {
|
||||
let mut arg_values: StaticVec<_> = args.iter().map(|e| e.get_constant_value().unwrap()).collect();
|
||||
|
||||
// Save the typename of the first argument if it is `type_of()`
|
||||
// This is to avoid `call_args` being passed into the closure
|
||||
let arg_for_type_of = if name == KEYWORD_TYPE_OF && arg_values.len() == 1 {
|
||||
state.engine.map_type_name(arg_values[0].type_name())
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
if let Some(expr) = call_fn_with_constant_arguments(&state, name, arg_values.as_mut())
|
||||
.or_else(|| {
|
||||
if !arg_for_type_of.is_empty() {
|
||||
// Handle `type_of()`
|
||||
Some(arg_for_type_of.to_string().into())
|
||||
} else {
|
||||
// Otherwise use the default value, if any
|
||||
def_value.map(|v| v.into())
|
||||
}
|
||||
})
|
||||
.and_then(|result| map_dynamic_to_expr(result, *pos))
|
||||
{
|
||||
state.set_dirty();
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
|
||||
let mut arg_values: StaticVec<_> = args.iter().map(Expr::get_constant_value).collect();
|
||||
|
||||
// Save the typename of the first argument if it is `type_of()`
|
||||
// This is to avoid `call_args` being passed into the closure
|
||||
let arg_for_type_of = if name == KEYWORD_TYPE_OF && arg_values.len() == 1 {
|
||||
state.engine.map_type_name(arg_values[0].type_name())
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
call_fn_with_constant_arguments(&state, name, arg_values.as_mut())
|
||||
.or_else(|| {
|
||||
if !arg_for_type_of.is_empty() {
|
||||
// Handle `type_of()`
|
||||
Some(arg_for_type_of.to_string().into())
|
||||
} else {
|
||||
// Otherwise use the default value, if any
|
||||
def_value.map(|v| v.into())
|
||||
}
|
||||
})
|
||||
.and_then(|result| map_dynamic_to_expr(result, *pos))
|
||||
.map(|expr| {
|
||||
state.set_dirty();
|
||||
expr
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
// Optimize function call arguments
|
||||
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
|
||||
Expr::FnCall(x)
|
||||
})
|
||||
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
|
||||
Expr::FnCall(x)
|
||||
}
|
||||
|
||||
// id(args ..) -> optimize function call arguments
|
||||
|
134
src/parser.rs
134
src/parser.rs
@ -12,13 +12,17 @@ use crate::syntax::FnCustomSyntaxEval;
|
||||
use crate::token::{is_keyword_function, is_valid_identifier, Position, Token, TokenStream};
|
||||
use crate::utils::{StaticVec, StraightHasherBuilder};
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::engine::Array;
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::engine::{make_getter, make_setter, Map};
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
use crate::engine::{FN_ANONYMOUS, KEYWORD_FN_PTR_CURRY};
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::engine::{make_getter, make_setter};
|
||||
|
||||
use crate::stdlib::{
|
||||
any::TypeId,
|
||||
borrow::Cow,
|
||||
boxed::Box,
|
||||
char,
|
||||
@ -91,7 +95,8 @@ impl AST {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// [INTERNALS] Get the statements.
|
||||
/// _[INTERNALS]_ Get the statements.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated(note = "this method is volatile and may change")]
|
||||
pub fn statements(&self) -> &[Stmt] {
|
||||
@ -109,7 +114,8 @@ impl AST {
|
||||
&self.1
|
||||
}
|
||||
|
||||
/// [INTERNALS] Get the internal `Module` containing all script-defined functions.
|
||||
/// _[INTERNALS]_ Get the internal `Module` containing all script-defined functions.
|
||||
/// Exported under the `internals` feature only.
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated(note = "this method is volatile and may change")]
|
||||
pub fn lib(&self) -> &Module {
|
||||
@ -301,8 +307,10 @@ impl AST {
|
||||
|
||||
/// 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);
|
||||
pub fn iter_functions<'a>(
|
||||
&'a self,
|
||||
) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<ScriptFnDef>)> + 'a {
|
||||
self.1.iter_script_fn()
|
||||
}
|
||||
|
||||
/// Clear all function definitions in the `AST`.
|
||||
@ -340,10 +348,10 @@ impl AsRef<Module> for AST {
|
||||
/// A type representing the access mode of a scripted function.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub enum FnAccess {
|
||||
/// Private function.
|
||||
Private,
|
||||
/// Public function.
|
||||
Public,
|
||||
/// Private function.
|
||||
Private,
|
||||
}
|
||||
|
||||
impl fmt::Display for FnAccess {
|
||||
@ -355,7 +363,24 @@ impl fmt::Display for FnAccess {
|
||||
}
|
||||
}
|
||||
|
||||
/// [INTERNALS] A type containing information on a scripted function.
|
||||
impl FnAccess {
|
||||
/// Is this access mode private?
|
||||
pub fn is_private(self) -> bool {
|
||||
match self {
|
||||
Self::Public => false,
|
||||
Self::Private => true,
|
||||
}
|
||||
}
|
||||
/// Is this access mode public?
|
||||
pub fn is_public(self) -> bool {
|
||||
match self {
|
||||
Self::Public => true,
|
||||
Self::Private => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// _[INTERNALS]_ A type containing information on a scripted function.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// ## WARNING
|
||||
@ -397,7 +422,7 @@ impl fmt::Display for ScriptFnDef {
|
||||
}
|
||||
}
|
||||
|
||||
/// [INTERNALS] A type encapsulating the mode of a `return`/`throw` statement.
|
||||
/// _[INTERNALS]_ A type encapsulating the mode of a `return`/`throw` statement.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// ## WARNING
|
||||
@ -542,7 +567,7 @@ impl ParseSettings {
|
||||
}
|
||||
}
|
||||
|
||||
/// [INTERNALS] A Rhai statement.
|
||||
/// _[INTERNALS]_ A Rhai statement.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// Each variant is at most one pointer in size (for speed),
|
||||
@ -575,7 +600,7 @@ pub enum Stmt {
|
||||
ReturnWithVal(Box<((ReturnType, Position), Option<Expr>, Position)>),
|
||||
/// import expr as module
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Import(Box<(Expr, Option<(String, Position)>, Position)>),
|
||||
Import(Box<(Expr, Option<(ImmutableString, Position)>, Position)>),
|
||||
/// expr id as name, ...
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Export(
|
||||
@ -702,7 +727,7 @@ impl Stmt {
|
||||
}
|
||||
}
|
||||
|
||||
/// [INTERNALS] A type wrapping a custom syntax definition.
|
||||
/// _[INTERNALS]_ A type wrapping a custom syntax definition.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// ## WARNING
|
||||
@ -723,7 +748,7 @@ impl Hash for CustomExpr {
|
||||
}
|
||||
}
|
||||
|
||||
/// [INTERNALS] A type wrapping a floating-point number.
|
||||
/// _[INTERNALS]_ A type wrapping a floating-point number.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// This type is mainly used to provide a standard `Hash` implementation
|
||||
@ -744,7 +769,7 @@ impl Hash for FloatWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
/// [INTERNALS] An expression sub-tree.
|
||||
/// _[INTERNALS]_ An expression sub-tree.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// Each variant is at most one pointer in size (for speed),
|
||||
@ -826,14 +851,40 @@ impl Default for Expr {
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
/// Get the type of an expression.
|
||||
///
|
||||
/// Returns `None` if the expression's result type is not constant.
|
||||
pub fn get_type_id(&self) -> Option<TypeId> {
|
||||
Some(match self {
|
||||
Self::Expr(x) => return x.get_type_id(),
|
||||
|
||||
Self::IntegerConstant(_) => TypeId::of::<INT>(),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Self::FloatConstant(_) => TypeId::of::<FLOAT>(),
|
||||
Self::CharConstant(_) => TypeId::of::<char>(),
|
||||
Self::StringConstant(_) => TypeId::of::<ImmutableString>(),
|
||||
Self::FnPointer(_) => TypeId::of::<FnPtr>(),
|
||||
Self::True(_) | Self::False(_) | Self::In(_) | Self::And(_) | Self::Or(_) => {
|
||||
TypeId::of::<bool>()
|
||||
}
|
||||
Self::Unit(_) => TypeId::of::<()>(),
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::Array(_) => TypeId::of::<Array>(),
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Self::Map(_) => TypeId::of::<Map>(),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the `Dynamic` value of a constant expression.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when the expression is not constant.
|
||||
pub fn get_constant_value(&self) -> Dynamic {
|
||||
match self {
|
||||
Self::Expr(x) => x.get_constant_value(),
|
||||
/// Returns `None` if the expression is not constant.
|
||||
pub fn get_constant_value(&self) -> Option<Dynamic> {
|
||||
Some(match self {
|
||||
Self::Expr(x) => return x.get_constant_value(),
|
||||
|
||||
Self::IntegerConstant(x) => x.0.into(),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
@ -850,45 +901,22 @@ impl Expr {
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Self::Array(x) if x.0.iter().all(Self::is_constant) => Dynamic(Union::Array(Box::new(
|
||||
x.0.iter().map(Self::get_constant_value).collect::<Vec<_>>(),
|
||||
x.0.iter()
|
||||
.map(|v| v.get_constant_value().unwrap())
|
||||
.collect(),
|
||||
))),
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Self::Map(x) if x.0.iter().all(|(_, v)| v.is_constant()) => {
|
||||
Dynamic(Union::Map(Box::new(
|
||||
x.0.iter()
|
||||
.map(|((k, _), v)| (k.clone(), v.get_constant_value()))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
.map(|((k, _), v)| (k.clone(), v.get_constant_value().unwrap()))
|
||||
.collect(),
|
||||
)))
|
||||
}
|
||||
|
||||
_ => unreachable!("cannot get value of non-constant expression"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the display value of a constant expression.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when the expression is not constant.
|
||||
pub fn get_constant_str(&self) -> String {
|
||||
match self {
|
||||
Self::Expr(x) => x.get_constant_str(),
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Self::FloatConstant(x) => x.0.to_string(),
|
||||
|
||||
Self::IntegerConstant(x) => x.0.to_string(),
|
||||
Self::CharConstant(x) => x.0.to_string(),
|
||||
Self::StringConstant(_) => "string".to_string(),
|
||||
Self::True(_) => "true".to_string(),
|
||||
Self::False(_) => "false".to_string(),
|
||||
Self::Unit(_) => "()".to_string(),
|
||||
|
||||
Self::Array(x) if x.0.iter().all(Self::is_constant) => "array".to_string(),
|
||||
|
||||
_ => unreachable!("cannot get value of non-constant expression"),
|
||||
}
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the `Position` of the expression.
|
||||
@ -2736,7 +2764,7 @@ fn parse_import(
|
||||
|
||||
Ok(Stmt::Import(Box::new((
|
||||
expr,
|
||||
Some((name, settings.pos)),
|
||||
Some((name.into(), settings.pos)),
|
||||
token_pos,
|
||||
))))
|
||||
}
|
||||
|
@ -34,20 +34,26 @@ pub enum EvalAltResult {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
ErrorReadingScriptFile(PathBuf, Position, std::io::Error),
|
||||
|
||||
/// Call to an unknown function. Wrapped value is the signature of the function.
|
||||
/// Usage of an unknown variable. Wrapped value is the variable name.
|
||||
ErrorVariableNotFound(String, Position),
|
||||
/// Call to an unknown function. Wrapped value is the function signature.
|
||||
ErrorFunctionNotFound(String, Position),
|
||||
/// An error has occurred inside a called function.
|
||||
/// Wrapped values are the name of the function and the interior error.
|
||||
/// Wrapped values are the function name and the interior error.
|
||||
ErrorInFunctionCall(String, Box<EvalAltResult>, Position),
|
||||
/// Usage of an unknown module. Wrapped value is the module name.
|
||||
ErrorModuleNotFound(String, Position),
|
||||
/// An error has occurred while loading a module.
|
||||
/// Wrapped value are the name of the module and the interior error.
|
||||
/// Wrapped value are the module name and the interior error.
|
||||
ErrorInModule(String, Box<EvalAltResult>, Position),
|
||||
/// Access to `this` that is not bound.
|
||||
ErrorUnboundThis(Position),
|
||||
/// Non-boolean operand encountered for boolean operator. Wrapped value is the operator.
|
||||
ErrorBooleanArgMismatch(String, Position),
|
||||
/// Non-character value encountered where a character is required.
|
||||
ErrorCharMismatch(Position),
|
||||
/// Data is not of the required type.
|
||||
/// Wrapped values are the type requested and type of the actual result.
|
||||
ErrorMismatchDataType(String, String, Position),
|
||||
/// Returned type is not the same as the required output type.
|
||||
/// Wrapped values are the type requested and type of the actual result.
|
||||
ErrorMismatchOutputType(String, String, Position),
|
||||
/// Array access out-of-bounds.
|
||||
/// Wrapped values are the current number of elements in the array and the index number.
|
||||
ErrorArrayBounds(usize, INT, Position),
|
||||
@ -55,33 +61,19 @@ pub enum EvalAltResult {
|
||||
/// Wrapped values are the current number of characters in the string and the index number.
|
||||
ErrorStringBounds(usize, INT, Position),
|
||||
/// Trying to index into a type that is not an array, an object map, or a string, and has no indexer function defined.
|
||||
/// Wrapped value is the type name.
|
||||
ErrorIndexingType(String, Position),
|
||||
/// Trying to index into an array or string with an index that is not `i64`.
|
||||
ErrorNumericIndexExpr(Position),
|
||||
/// Trying to index into a map with an index that is not `String`.
|
||||
ErrorStringIndexExpr(Position),
|
||||
/// Trying to import with an expression that is not `String`.
|
||||
ErrorImportExpr(Position),
|
||||
/// Invalid arguments for `in` operator.
|
||||
ErrorInExpr(Position),
|
||||
/// The guard expression in an `if` or `while` statement does not return a boolean value.
|
||||
ErrorLogicGuard(Position),
|
||||
/// The `for` statement encounters a type that is not an iterator.
|
||||
ErrorFor(Position),
|
||||
/// Usage of an unknown variable. Wrapped value is the name of the variable.
|
||||
ErrorVariableNotFound(String, Position),
|
||||
/// Usage of an unknown module. Wrapped value is the name of the module.
|
||||
ErrorModuleNotFound(String, Position),
|
||||
/// Data race detected when accessing a variable. Wrapped value is the name of the variable.
|
||||
/// Data race detected when accessing a variable. Wrapped value is the variable name.
|
||||
ErrorDataRace(String, Position),
|
||||
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
||||
ErrorAssignmentToUnknownLHS(Position),
|
||||
/// Assignment to a constant variable.
|
||||
/// Assignment to a constant variable. Wrapped value is the variable name.
|
||||
ErrorAssignmentToConstant(String, Position),
|
||||
/// Returned type is not the same as the required output type.
|
||||
/// Wrapped values are the type requested and type of the actual result.
|
||||
ErrorMismatchOutputType(String, String, Position),
|
||||
/// Inappropriate member access.
|
||||
/// Inappropriate property access. Wrapped value is the property name.
|
||||
ErrorDotExpr(String, Position),
|
||||
/// Arithmetic error encountered. Wrapped value is the error message.
|
||||
ErrorArithmetic(String, Position),
|
||||
@ -91,7 +83,7 @@ pub enum EvalAltResult {
|
||||
ErrorTooManyModules(Position),
|
||||
/// Call stack over maximum limit.
|
||||
ErrorStackOverflow(Position),
|
||||
/// Data value over maximum size limit. Wrapped values are the data type, maximum size and current size.
|
||||
/// Data value over maximum size limit. Wrapped values are the type name, maximum size and current size.
|
||||
ErrorDataTooLarge(String, usize, usize, Position),
|
||||
/// The script is prematurely terminated.
|
||||
ErrorTerminated(Position),
|
||||
@ -119,16 +111,10 @@ impl EvalAltResult {
|
||||
Self::ErrorInModule(_, _, _) => "Error in module",
|
||||
Self::ErrorFunctionNotFound(_, _) => "Function not found",
|
||||
Self::ErrorUnboundThis(_) => "'this' is not bound",
|
||||
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
|
||||
Self::ErrorCharMismatch(_) => "Character expected",
|
||||
Self::ErrorNumericIndexExpr(_) => {
|
||||
"Indexing into an array or string expects an integer index"
|
||||
}
|
||||
Self::ErrorStringIndexExpr(_) => "Indexing into an object map expects a string index",
|
||||
Self::ErrorMismatchDataType(_, _, _) => "Data type is incorrect",
|
||||
Self::ErrorIndexingType(_, _) => {
|
||||
"Indexing can only be performed on an array, an object map, a string, or a type with an indexer function defined"
|
||||
}
|
||||
Self::ErrorImportExpr(_) => "Importing a module expects a string path",
|
||||
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
|
||||
"Array access expects non-negative index"
|
||||
}
|
||||
@ -139,7 +125,6 @@ impl EvalAltResult {
|
||||
}
|
||||
Self::ErrorStringBounds(0, _, _) => "Empty string has nothing to index",
|
||||
Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
|
||||
Self::ErrorLogicGuard(_) => "Boolean value expected",
|
||||
Self::ErrorFor(_) => "For loop expects an array, object map, or range",
|
||||
Self::ErrorVariableNotFound(_, _) => "Variable not found",
|
||||
Self::ErrorModuleNotFound(_, _) => "Module not found",
|
||||
@ -197,11 +182,7 @@ impl fmt::Display for EvalAltResult {
|
||||
Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{}", s)?,
|
||||
|
||||
Self::ErrorIndexingType(_, _)
|
||||
| Self::ErrorNumericIndexExpr(_)
|
||||
| Self::ErrorStringIndexExpr(_)
|
||||
| Self::ErrorUnboundThis(_)
|
||||
| Self::ErrorImportExpr(_)
|
||||
| Self::ErrorLogicGuard(_)
|
||||
| Self::ErrorFor(_)
|
||||
| Self::ErrorAssignmentToUnknownLHS(_)
|
||||
| Self::ErrorInExpr(_)
|
||||
@ -215,17 +196,19 @@ impl fmt::Display for EvalAltResult {
|
||||
|
||||
Self::ErrorAssignmentToConstant(s, _) => write!(f, "{}: '{}'", desc, s)?,
|
||||
Self::ErrorMismatchOutputType(r, s, _) => {
|
||||
write!(f, "{} (expecting {}): {}", desc, s, r)?
|
||||
write!(f, "Output type is incorrect: {} (expecting {})", r, s)?
|
||||
}
|
||||
Self::ErrorMismatchDataType(r, s, _) if r.is_empty() => {
|
||||
write!(f, "Data type is incorrect, expecting {}", s)?
|
||||
}
|
||||
Self::ErrorMismatchDataType(r, s, _) => {
|
||||
write!(f, "Data type is incorrect: {} (expecting {})", r, s)?
|
||||
}
|
||||
Self::ErrorArithmetic(s, _) => f.write_str(s)?,
|
||||
|
||||
Self::ErrorLoopBreak(_, _) => f.write_str(desc)?,
|
||||
Self::Return(_, _) => f.write_str(desc)?,
|
||||
|
||||
Self::ErrorBooleanArgMismatch(op, _) => {
|
||||
write!(f, "{} operator expects boolean operands", op)?
|
||||
}
|
||||
Self::ErrorCharMismatch(_) => write!(f, "string indexing expects a character value")?,
|
||||
Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
|
||||
write!(f, "{}: {} < 0", desc, index)?
|
||||
}
|
||||
@ -290,15 +273,10 @@ impl EvalAltResult {
|
||||
| Self::ErrorInFunctionCall(_, _, pos)
|
||||
| Self::ErrorInModule(_, _, pos)
|
||||
| Self::ErrorUnboundThis(pos)
|
||||
| Self::ErrorBooleanArgMismatch(_, pos)
|
||||
| Self::ErrorCharMismatch(pos)
|
||||
| Self::ErrorMismatchDataType(_, _, pos)
|
||||
| Self::ErrorArrayBounds(_, _, pos)
|
||||
| Self::ErrorStringBounds(_, _, pos)
|
||||
| Self::ErrorIndexingType(_, pos)
|
||||
| Self::ErrorNumericIndexExpr(pos)
|
||||
| Self::ErrorStringIndexExpr(pos)
|
||||
| Self::ErrorImportExpr(pos)
|
||||
| Self::ErrorLogicGuard(pos)
|
||||
| Self::ErrorFor(pos)
|
||||
| Self::ErrorVariableNotFound(_, pos)
|
||||
| Self::ErrorModuleNotFound(_, pos)
|
||||
@ -332,15 +310,10 @@ impl EvalAltResult {
|
||||
| Self::ErrorInFunctionCall(_, _, pos)
|
||||
| Self::ErrorInModule(_, _, pos)
|
||||
| Self::ErrorUnboundThis(pos)
|
||||
| Self::ErrorBooleanArgMismatch(_, pos)
|
||||
| Self::ErrorCharMismatch(pos)
|
||||
| Self::ErrorMismatchDataType(_, _, pos)
|
||||
| Self::ErrorArrayBounds(_, _, pos)
|
||||
| Self::ErrorStringBounds(_, _, pos)
|
||||
| Self::ErrorIndexingType(_, pos)
|
||||
| Self::ErrorNumericIndexExpr(pos)
|
||||
| Self::ErrorStringIndexExpr(pos)
|
||||
| Self::ErrorImportExpr(pos)
|
||||
| Self::ErrorLogicGuard(pos)
|
||||
| Self::ErrorFor(pos)
|
||||
| Self::ErrorVariableNotFound(_, pos)
|
||||
| Self::ErrorModuleNotFound(_, pos)
|
||||
|
@ -74,8 +74,8 @@ impl fmt::Debug for CustomSyntax {
|
||||
|
||||
/// Context of a script evaluation process.
|
||||
#[derive(Debug)]
|
||||
pub struct EvalContext<'a, 'b: 'a, 's, 'm, 't, 'd: 't> {
|
||||
pub(crate) mods: &'a mut Imports<'b>,
|
||||
pub struct EvalContext<'a, 's, 'm, 't, 'd: 't> {
|
||||
pub(crate) mods: &'a mut Imports,
|
||||
pub(crate) state: &'s mut State,
|
||||
pub(crate) lib: &'m Module,
|
||||
pub(crate) this_ptr: &'t mut Option<&'d mut Dynamic>,
|
||||
|
32
src/token.rs
32
src/token.rs
@ -2,9 +2,12 @@
|
||||
|
||||
use crate::engine::{
|
||||
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY,
|
||||
KEYWORD_IS_SHARED, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
|
||||
KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
use crate::engine::KEYWORD_IS_SHARED;
|
||||
|
||||
use crate::error::LexError;
|
||||
use crate::parser::INT;
|
||||
use crate::utils::StaticVec;
|
||||
@ -144,7 +147,7 @@ impl fmt::Debug for Position {
|
||||
}
|
||||
}
|
||||
|
||||
/// [INTERNALS] A Rhai language token.
|
||||
/// _[INTERNALS]_ A Rhai language token.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// ## WARNING
|
||||
@ -507,9 +510,11 @@ impl Token {
|
||||
| "await" | "yield" => Reserved(syntax.into()),
|
||||
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_SHARED | KEYWORD_THIS => {
|
||||
Reserved(syntax.into())
|
||||
}
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR
|
||||
| KEYWORD_IS_DEF_FN | KEYWORD_THIS => Reserved(syntax.into()),
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
KEYWORD_IS_SHARED => Reserved(syntax.into()),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
@ -704,7 +709,7 @@ impl From<Token> for String {
|
||||
}
|
||||
}
|
||||
|
||||
/// [INTERNALS] State of the tokenizer.
|
||||
/// _[INTERNALS]_ State of the tokenizer.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// ## WARNING
|
||||
@ -724,7 +729,7 @@ pub struct TokenizeState {
|
||||
pub include_comments: bool,
|
||||
}
|
||||
|
||||
/// [INTERNALS] Trait that encapsulates a peekable character input stream.
|
||||
/// _[INTERNALS]_ Trait that encapsulates a peekable character input stream.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// ## WARNING
|
||||
@ -738,7 +743,7 @@ pub trait InputStream {
|
||||
fn peek_next(&mut self) -> Option<char>;
|
||||
}
|
||||
|
||||
/// [INTERNALS] Parse a string literal wrapped by `enclosing_char`.
|
||||
/// _[INTERNALS]_ Parse a string literal wrapped by `enclosing_char`.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// ## WARNING
|
||||
@ -926,7 +931,7 @@ fn scan_comment(
|
||||
}
|
||||
}
|
||||
|
||||
/// [INTERNALS] Get the next token from the `InputStream`.
|
||||
/// _[INTERNALS]_ Get the next token from the `InputStream`.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// ## WARNING
|
||||
@ -1386,6 +1391,8 @@ fn get_next_token_inner(
|
||||
|
||||
('@', _) => return Some((Token::Reserved("@".into()), start_pos)),
|
||||
|
||||
('$', _) => return Some((Token::Reserved("$".into()), start_pos)),
|
||||
|
||||
('\0', _) => unreachable!(),
|
||||
|
||||
(ch, _) if ch.is_whitespace() => (),
|
||||
@ -1455,7 +1462,9 @@ pub fn is_keyword_function(name: &str) -> bool {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
KEYWORD_IS_SHARED => true,
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => true,
|
||||
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR | KEYWORD_IS_DEF_FN => {
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -1465,7 +1474,8 @@ pub fn is_keyword_function(name: &str) -> bool {
|
||||
#[inline(always)]
|
||||
pub fn can_override_keyword(name: &str) -> bool {
|
||||
match name {
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR => true,
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
| KEYWORD_IS_DEF_VAR | KEYWORD_IS_DEF_FN => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ impl BuildHasher for StraightHasherBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// [INTERNALS] Calculate a `u64` hash key from a module-qualified function name and parameter types.
|
||||
/// _[INTERNALS]_ Calculate a `u64` hash key from a module-qualified function name and parameter types.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// Module names are passed in via `&str` references from an iterator.
|
||||
@ -89,7 +89,7 @@ pub fn calc_fn_hash<'a>(
|
||||
s.finish()
|
||||
}
|
||||
|
||||
/// [INTERNALS] Alias to [`smallvec::SmallVec<[T; 4]>`](https://crates.io/crates/smallvec),
|
||||
/// _[INTERNALS]_ Alias to [`smallvec::SmallVec<[T; 4]>`](https://crates.io/crates/smallvec),
|
||||
/// which is a specialized `Vec` backed by a small, fixed-size array when there are <= 4 items stored.
|
||||
/// Exported under the `internals` feature only.
|
||||
pub type StaticVec<T> = SmallVec<[T; 4]>;
|
||||
|
@ -21,3 +21,29 @@ fn test_constant() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_var_is_def() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert!(engine.eval::<bool>(
|
||||
r#"
|
||||
let x = 42;
|
||||
is_def_var("x")
|
||||
"#
|
||||
)?);
|
||||
assert!(!engine.eval::<bool>(
|
||||
r#"
|
||||
let x = 42;
|
||||
is_def_var("y")
|
||||
"#
|
||||
)?);
|
||||
assert!(engine.eval::<bool>(
|
||||
r#"
|
||||
const x = 42;
|
||||
is_def_var("x")
|
||||
"#
|
||||
)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -173,3 +173,29 @@ fn test_function_captures() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_is_def() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
||||
assert!(engine.eval::<bool>(
|
||||
r#"
|
||||
fn foo(x) { x + 1 }
|
||||
is_def_fn("foo", 1)
|
||||
"#
|
||||
)?);
|
||||
assert!(!engine.eval::<bool>(
|
||||
r#"
|
||||
fn foo(x) { x + 1 }
|
||||
is_def_fn("bar", 1)
|
||||
"#
|
||||
)?);
|
||||
assert!(!engine.eval::<bool>(
|
||||
r#"
|
||||
fn foo(x) { x + 1 }
|
||||
is_def_fn("foo", 0)
|
||||
"#
|
||||
)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -265,7 +265,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
engine.set_module_resolver(Some(resolver1));
|
||||
|
||||
let module = Module::eval_ast_as_new(Scope::new(), &ast, true, &engine)?;
|
||||
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
||||
|
||||
let mut resolver2 = StaticModuleResolver::new();
|
||||
resolver2.insert("testing", module);
|
||||
@ -361,3 +361,41 @@ fn test_module_str() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[test]
|
||||
fn test_module_ast_namespace() -> Result<(), Box<EvalAltResult>> {
|
||||
let script = r#"
|
||||
fn foo(x) { x + 1 }
|
||||
fn bar(x) { foo(x) }
|
||||
"#;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
let ast = engine.compile(script)?;
|
||||
|
||||
let module = Module::eval_ast_as_new(Default::default(), &ast, &engine)?;
|
||||
|
||||
let mut resolver = StaticModuleResolver::new();
|
||||
resolver.insert("testing", module);
|
||||
engine.set_module_resolver(Some(resolver));
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(r#"import "testing" as t; t::foo(41)"#)?,
|
||||
42
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(r#"import "testing" as t; t::bar(41)"#)?,
|
||||
42
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(r#"fn foo(x) { x - 1 } import "testing" as t; t::foo(41)"#)?,
|
||||
42
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(r#"fn foo(x) { x - 1 } import "testing" as t; t::bar(41)"#)?,
|
||||
42
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -36,12 +36,9 @@ fn test_packages_with_script() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let ast = engine.compile("fn foo(x) { x + 1 } fn bar(x) { foo(x) + 1 }")?;
|
||||
|
||||
let module = Module::eval_ast_as_new(Scope::new(), &ast, false, &engine)?;
|
||||
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
||||
engine.load_package(module);
|
||||
assert_eq!(engine.eval::<INT>("foo(41)")?, 42);
|
||||
|
||||
let module = Module::eval_ast_as_new(Scope::new(), &ast, true, &engine)?;
|
||||
engine.load_package(module);
|
||||
assert_eq!(engine.eval::<INT>("bar(40)")?, 42);
|
||||
|
||||
Ok(())
|
||||
|
@ -47,6 +47,7 @@ mod test {
|
||||
macro_rules! gen_unary_functions {
|
||||
($op_name:ident = $op_fn:ident ( $($arg_type:ident),+ ) -> $return_type:ident) => {
|
||||
mod $op_name { $(
|
||||
#[allow(non_snake_case)]
|
||||
pub mod $arg_type {
|
||||
use super::super::*;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use rhai::{Engine, EvalAltResult, ImmutableString, RegisterFn, INT};
|
||||
use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, RegisterFn, Scope, INT};
|
||||
|
||||
#[test]
|
||||
fn test_string() -> Result<(), Box<EvalAltResult>> {
|
||||
@ -49,6 +49,21 @@ fn test_string() -> Result<(), Box<EvalAltResult>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_dynamic() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
scope.push("x", Dynamic::from("foo"));
|
||||
scope.push("y", String::from("foo"));
|
||||
scope.push("z", "foo");
|
||||
|
||||
assert!(engine.eval_with_scope::<bool>(&mut scope, r#"x == "foo""#)?);
|
||||
assert!(engine.eval_with_scope::<bool>(&mut scope, r#"y == "foo""#)?);
|
||||
assert!(engine.eval_with_scope::<bool>(&mut scope, r#"z == "foo""#)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
#[test]
|
||||
fn test_string_substring() -> Result<(), Box<EvalAltResult>> {
|
||||
|
@ -35,9 +35,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||
if !engine
|
||||
.eval_expression_tree(context, scope, expr)?
|
||||
.as_bool()
|
||||
.map_err(|_| {
|
||||
EvalAltResult::ErrorBooleanArgMismatch("do-while".into(), expr.position())
|
||||
})?
|
||||
.map_err(|err| engine.make_type_mismatch_err::<bool>(err, expr.position()))?
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user