Merge pull request #251 from schungx/master

Small enhancements.
This commit is contained in:
Stephen Chung 2020-10-07 13:14:21 +08:00 committed by GitHub
commit effb11ab94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 1593 additions and 1031 deletions

View File

@ -6,7 +6,7 @@ members = [
[package] [package]
name = "rhai" name = "rhai"
version = "0.19.0" version = "0.20.0"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"

View File

@ -61,6 +61,10 @@ Documentation
See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai scripting engine and language. 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 Playground
---------- ----------

View File

@ -1,6 +1,27 @@
Rhai Release Notes 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 Version 0.19.0
============== ==============

56
benches/eval_module.rs Normal file
View 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());
}

View File

@ -403,9 +403,9 @@ impl ExportedFn {
pub(crate) fn exported_name<'n>(&'n self) -> Cow<'n, str> { pub(crate) fn exported_name<'n>(&'n self) -> Cow<'n, str> {
if let Some(ref name) = self.params.name { if let Some(ref name) = self.params.name {
Cow::Borrowed(name.last().unwrap().as_str()) name.last().unwrap().as_str().into()
} else { } else {
Cow::Owned(self.signature.ident.to_string()) self.signature.ident.to_string().into()
} }
} }

View File

@ -204,9 +204,9 @@ impl Module {
pub fn exported_name(&self) -> Option<Cow<str>> { pub fn exported_name(&self) -> Option<Cow<str>> {
if let Some(ref s) = self.params.name { if let Some(ref s) = self.params.name {
Some(Cow::Borrowed(s)) Some(s.into())
} else { } else {
self.module_name().map(|m| Cow::Owned(m.to_string())) self.module_name().map(|m| m.to_string().into())
} }
} }

View File

@ -45,25 +45,31 @@ The Rhai Scripting Language
1. [Built-in Packages](rust/packages/builtin.md) 1. [Built-in Packages](rust/packages/builtin.md)
2. [Load a Plugin Module as a Package](rust/packages/plugin.md) 2. [Load a Plugin Module as a Package](rust/packages/plugin.md)
3. [Manually Create a Custom Package](rust/packages/create.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) 1. [Export a Rust Module](plugins/module.md)
2. [Export a Rust Function](plugins/function.md) 2. [Export a Rust Function](plugins/function.md)
5. [Rhai Language Reference](language/index.md) 5. [Rhai Language Reference](language/index.md)
1. [Comments](language/comments.md) 1. [Comments](language/comments.md)
2. [Values and Types](language/values-and-types.md) 2. [Values and Types](language/values-and-types.md)
1. [Dynamic Values](language/dynamic.md) 1. [Dynamic Values](language/dynamic.md)
2. [type_of()](language/type-of.md) 2. [Serialization/Deserialization with `serde`](rust/serde.md)
3. [Numbers](language/numbers.md) 3. [type_of()](language/type-of.md)
4. [Numbers](language/numbers.md)
1. [Operators](language/num-op.md) 1. [Operators](language/num-op.md)
2. [Functions](language/num-fn.md) 2. [Functions](language/num-fn.md)
3. [Value Conversions](language/convert.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) 1. [Built-in Functions](language/string-fn.md)
5. [Arrays](language/arrays.md) 6. [Arrays](language/arrays.md)
6. [Object Maps](language/object-maps.md) 7. [Object Maps](language/object-maps.md)
1. [Parse from JSON](language/json.md) 1. [Parse from JSON](language/json.md)
2. [Special Support for OOP](language/object-maps-oop.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) 3. [Keywords](language/keywords.md)
4. [Statements](language/statements.md) 4. [Statements](language/statements.md)
5. [Variables](language/variables.md) 5. [Variables](language/variables.md)
@ -88,10 +94,6 @@ The Rhai Scripting Language
17. [Modules](language/modules/index.md) 17. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
2. [Import Modules](language/modules/import.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) 18. [Eval Statement](language/eval.md)
6. [Safety and Protection](safety/index.md) 6. [Safety and Protection](safety/index.md)
1. [Checked Arithmetic](safety/checked.md) 1. [Checked Arithmetic](safety/checked.md)
@ -104,29 +106,30 @@ The Rhai Scripting Language
7. [Maximum Number of Modules](safety/max-modules.md) 7. [Maximum Number of Modules](safety/max-modules.md)
8. [Maximum Call Stack Depth](safety/max-call-stack.md) 8. [Maximum Call Stack Depth](safety/max-call-stack.md)
9. [Maximum Statement Depth](safety/max-stmt-depth.md) 9. [Maximum Statement Depth](safety/max-stmt-depth.md)
7. [Advanced Topics](advanced.md) 7. [Script Optimization](engine/optimize/index.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) 1. [Optimization Levels](engine/optimize/optimize-levels.md)
2. [Re-Optimize an AST](engine/optimize/reoptimize.md) 2. [Re-Optimize an AST](engine/optimize/reoptimize.md)
3. [Eager Function Evaluation](engine/optimize/eager.md) 3. [Eager Function Evaluation](engine/optimize/eager.md)
4. [Side-Effect Considerations](engine/optimize/side-effects.md) 4. [Side-Effect Considerations](engine/optimize/side-effects.md)
5. [Volatility Considerations](engine/optimize/volatility.md) 5. [Volatility Considerations](engine/optimize/volatility.md)
6. [Subtle Semantic Changes](engine/optimize/semantics.md) 6. [Subtle Semantic Changes](engine/optimize/semantics.md)
5. [Low-Level API](rust/register-raw.md) 8. [Usage Patterns](patterns/index.md)
6. [Use as DSL](engine/dsl.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) 1. [Disable Keywords and/or Operators](engine/disable.md)
2. [Custom Operators](engine/custom-op.md) 2. [Custom Operators](engine/custom-op.md)
3. [Extending with Custom Syntax](engine/custom-syntax.md) 3. [Extending with Custom Syntax](engine/custom-syntax.md)
7. [Multiple Instantiation](patterns/multiple.md) 4. [Multiple Instantiation](patterns/multiple.md)
8. [Appendix](appendix/index.md) 10. [Appendix](appendix/index.md)
1. [Keywords](appendix/keywords.md) 1. [Keywords](appendix/keywords.md)
2. [Operators and Symbols](appendix/operators.md) 2. [Operators and Symbols](appendix/operators.md)
3. [Literals](appendix/literals.md) 3. [Literals](appendix/literals.md)

View File

@ -14,7 +14,9 @@ Versions
This Book is for version **{{version}}** of Rhai. 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/). For the latest development version, see [here]({{rootUrl}}/vnext/).
{% endif %}
Etymology of the name "Rhai" Etymology of the name "Rhai"

View File

@ -11,8 +11,6 @@ This section covers advanced features such as:
* [`serde`] integration. * [`serde`] integration.
* [Script optimization].
* Low-level [function registration API]({{rootUrl}}/rust/register-raw.md) * Low-level [function registration API]({{rootUrl}}/rust/register-raw.md)
* [Domain-Specific Languages][DSL]. * [Domain-Specific Languages][DSL].

View File

@ -3,36 +3,38 @@ Keywords List
{{#include ../links.md}} {{#include ../links.md}}
| Keyword | Description | Inactive under | Overloadable | | Keyword | Description | Inactive under | Is function? | Overloadable |
| :-------------------: | ------------------------------------------- | :-------------: | :----------: | | :-------------------: | ------------------------------------------- | :-------------: | :----------: | :----------: |
| `true` | boolean true literal | | no | | `true` | boolean true literal | | no | |
| `false` | boolean false literal | | no | | `false` | boolean false literal | | no | |
| `let` | variable declaration | | no | | `let` | variable declaration | | no | |
| `const` | constant declaration | | no | | `const` | constant declaration | | no | |
| `is_shared` | is a value shared? | | no | | `is_def_var` | is a variable declared? | | yes | yes |
| `if` | if statement | | no | | `is_shared` | is a value shared? | [`no_closure`] | yes | no |
| `else` | else block of if statement | | no | | `if` | if statement | | no | |
| `while` | while loop | | no | | `else` | else block of if statement | | no | |
| `loop` | infinite loop | | no | | `while` | while loop | | no | |
| `for` | for loop | | no | | `loop` | infinite loop | | no | |
| `in` | 1) containment test<br/>2) part of for loop | | no | | `for` | for loop | | no | |
| `continue` | continue a loop at the next iteration | | no | | `in` | 1) containment test<br/>2) part of for loop | | no | |
| `break` | break out of loop iteration | | no | | `continue` | continue a loop at the next iteration | | no | |
| `return` | return value | | no | | `break` | break out of loop iteration | | no | |
| `throw` | throw exception | | no | | `return` | return value | | no | |
| `import` | import module | [`no_module`] | no | | `throw` | throw exception | | no | |
| `export` | export variable | [`no_module`] | no | | `import` | import module | [`no_module`] | no | |
| `as` | alias for variable export | [`no_module`] | no | | `export` | export variable | [`no_module`] | no | |
| `private` | mark function private | [`no_function`] | no | | `as` | alias for variable export | [`no_module`] | no | |
| `fn` (lower-case `f`) | function definition | [`no_function`] | no | | `private` | mark function private | [`no_function`] | no | |
| `Fn` (capital `F`) | create a [function pointer] | | yes | | `fn` (lower-case `f`) | function definition | [`no_function`] | no | |
| `call` | call a [function pointer] | | no | | `Fn` (capital `F`) | create a [function pointer] | | yes | yes |
| `curry` | curry a [function pointer] | | no | | `call` | call a [function pointer] | | yes | no |
| `this` | reference to base object for method call | [`no_function`] | no | | `curry` | curry a [function pointer] | | yes | no |
| `type_of` | get type name of value | | yes | | `this` | reference to base object for method call | [`no_function`] | no | |
| `print` | print value | | yes | | `is_def_fn` | is a scripted function defined? | [`no_function`] | yes | yes |
| `debug` | print value in debug format | | yes | | `type_of` | get type name of value | | yes | yes |
| `eval` | evaluate script | | yes | | `print` | print value | | yes | yes |
| `debug` | print value in debug format | | yes | yes |
| `eval` | evaluate script | | yes | yes |
Reserved Keywords Reserved Keywords

View File

@ -8,7 +8,7 @@ Literals Syntax
| `INT` | decimal: `42`, `-123`, `0`<br/>hex: `0x????..`<br/>binary: `0b????..`<br/>octal: `0o????..` | | `INT` | decimal: `42`, `-123`, `0`<br/>hex: `0x????..`<br/>binary: `0b????..`<br/>octal: `0o????..` |
| `FLOAT` | `42.0`, `-123.456`, `0.0` | | `FLOAT` | `42.0`, `-123.456`, `0.0` |
| [String] | `"... \x?? \u???? \U???????? ..."` | | [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`] | `[ ???, ???, ??? ]` | | [`Array`] | `[ ???, ???, ??? ]` |
| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` | | [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` |
| Boolean true | `true` | | Boolean true | `true` |

View File

@ -8,18 +8,19 @@ Operators
--------- ---------
| Operator | Description | Binary? | Binding direction | | Operator | Description | Binary? | Binding direction |
| :---------------: | ------------------------------ | :-----: | :---------------: | | :-----------------------------------------------------------------------------------------: | -------------------------------------- | :--------: | :---------------: |
| `+` | add | yes | left | | `+` | add | yes | left |
| `-` | subtract, Minus | yes/no | left | | `-` | 1) subtract<br/>2) negative | yes<br/>no | left<br/>right |
| `*` | multiply | yes | left | | `*` | multiply | yes | left |
| `/` | divide | yes | left | | `/` | divide | yes | left |
| `%` | modulo | yes | left | | `%` | modulo | yes | left |
| `~` | power | yes | left | | `~` | power | yes | left |
| `>>` | right bit-shift | yes | left | | `>>` | right bit-shift | yes | left |
| `<<` | left bit-shift | yes | left | | `<<` | left bit-shift | yes | left |
| `&` | bit-wise _And_, boolean _And_ | yes | left | | `&` | 1) bit-wise _And_<br/>2) boolean _And_ | yes | left |
| <code>\|</code> | bit-wise _Or_, boolean _Or_ | yes | left | | <code>\|</code> | 1) bit-wise _Or_<br/>2) boolean _Or_ | yes | left |
| `^` | bit-wise _Xor_, boolean _Xor_ | yes | left | | `^` | 1) bit-wise _Xor_<br/>2) boolean _Xor_ | yes | left |
| `=`, `+=`, `-=`, `*=`, `/=`,<br/>`~=`, `%=`, `<<=`, `>>=`, `&=`,<br/><code>\|=</code>, `^=` | assignments | yes | right |
| `==` | equals to | yes | left | | `==` | equals to | yes | left |
| `~=` | not equals to | yes | left | | `~=` | not equals to | yes | left |
| `>` | greater than | yes | left | | `>` | greater than | yes | left |
@ -30,23 +31,38 @@ Operators
| <code>\|\|</code> | boolean _Or_ (short-circuits) | yes | left | | <code>\|\|</code> | boolean _Or_ (short-circuits) | yes | left |
| `!` | boolean _Not_ | no | left | | `!` | boolean _Not_ | no | left |
| `[` .. `]` | indexing | yes | right | | `[` .. `]` | indexing | yes | right |
| `.` | property access, method call | yes | right | | `.` | 1) property access<br/>2) method call | yes | right |
Symbols Symbols and Patterns
------- --------------------
| Symbol | Description | | Symbol | Name | Description |
| ------------ | ------------------------ | | ---------------------------------- | :------------------: | ------------------------------------- |
| `:` | property value separator | | `;` | semicolon | statement separator |
| `::` | module path separator | | `,` | comma | list separator |
| `#` | _reserved_ | | `:` | colon | [object map] property value separator |
| `=>` | _reserved_ | | `::` | path | module path separator |
| `->` | _reserved_ | | `#{` .. `}` | hash map | [object map] literal |
| `<-` | _reserved_ | | `"` .. `"` | double quote | [string] |
| `===` | _reserved_ | | `'` .. `'` | single quote | [character][string] |
| `!==` | _reserved_ | | `\` | escape | escape character literal |
| `:=` | _reserved_ | | `(` .. `)` | parentheses | expression grouping |
| `::<` .. `>` | _reserved_ | | `{` .. `}` | braces | block statement |
| `@` | _reserved_ | | <code>\|</code> .. <code>\|</code> | pipes | closure |
| `(*` .. `*)` | _reserved_ | | `[` .. `]` | 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_ |

View File

@ -1,5 +1,5 @@
{ {
"version": "0.19.0", "version": "0.20.0",
"repoHome": "https://github.com/jonathandturner/rhai/blob/master", "repoHome": "https://github.com/jonathandturner/rhai/blob/master",
"repoTree": "https://github.com/jonathandturner/rhai/tree/master", "repoTree": "https://github.com/jonathandturner/rhai/tree/master",
"rootUrl": "", "rootUrl": "",

View File

@ -131,7 +131,7 @@ It should simply be passed straight-through the the [`Engine`].
### Access Arguments ### Access Arguments
The most important argument is `inputs` where the matched identifiers (`$ident$`), expressions/statements (`$expr$`) 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: To access a particular argument, use the following patterns:

View File

@ -3,8 +3,8 @@ Eager Function Evaluation When Using Full Optimization Level
{{#include ../../links.md}} {{#include ../../links.md}}
When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_ and will _eagerly_ When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_
evaluated all function calls with constant arguments, using the result to replace the call. 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). 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... // When compiling the following with OptimizationLevel::Full...
const DECISION = 1; const DECISION = 1;
// this condition is now eliminated because 'DECISION == 1' // this condition is now eliminated because 'sign(DECISION) > 0'
if DECISION == 1 { // is a function call to the '==' function, and it returns 'true' 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 print("hello!"); // this block is promoted to the parent level
} else { } else {
print("boo!"); // this block is eliminated because it is never reached 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("hello!"); // <- the above is equivalent to this
// ('print' and 'debug' are handled specially) // ('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'
```

View File

@ -47,9 +47,13 @@ Constants propagation is used to remove dead code:
```rust ```rust
const ABC = true; const ABC = true;
if ABC || some_work() { print("done!"); } // 'ABC' is constant so it is replaced by '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 || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called
if true { print("done!"); } // <- the line above is equivalent to this if true { print("done!"); } // <- the line above is equivalent to this
print("done!"); // <- the line above is further simplified to this print("done!"); // <- the line above is further simplified to this
// because the condition is always true // because the condition is always true
``` ```
@ -61,17 +65,65 @@ For fixed script texts, the constant values can be provided in a user-defined [`
to the [`Engine`] for use in compilation and evaluation. 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, 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 ```rust
const DECISION = 1; // The following is most likely generated by machine.
if DECISION == 1 { // NOT optimized away because you can define const DECISION = 1; // this is an integer, one of the standard types
: // your own '==' function to override the built-in default!
if DECISION == 1 { // this is optimized into 'true'
:
} else if DECISION == 2 { // this is optimized into 'false'
:
} else if DECISION == 3 { // this is optimized into 'false'
:
} else {
:
}
```
Because of the eager evaluation of operators for [standard types], many constant expressions will be evaluated
and replaced by the result.
```rust
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'
```
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 == 2 { // same here, NOT optimized away
: :
@ -82,28 +134,4 @@ if DECISION == 1 { // NOT optimized away because you can define
} }
``` ```
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:
```rust
const DECISION_1 = true;
const DECISION_2 = false;
const DECISION_3 = false;
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
}
```
In general, boolean constants are most effective for the optimizer to automatically prune
large `if`-`else` branches because they do not depend on operators.
Alternatively, turn the optimizer to [`OptimizationLevel::Full`]. Alternatively, turn the optimizer to [`OptimizationLevel::Full`].

View File

@ -8,9 +8,10 @@ There are three levels of optimization: `None`, `Simple` and `Full`.
* `None` is obvious - no optimization on the AST is performed. * `None` is obvious - no optimization on the AST is performed.
* `Simple` (default) performs only relatively _safe_ optimizations without causing side-effects * `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. One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators.

View File

@ -16,11 +16,11 @@ The final, optimized [`AST`] is then used for evaluations.
// Compile master script to AST // Compile master script to AST
let master_ast = engine.compile( let master_ast = engine.compile(
r" r"
if SCENARIO_1 { if SCENARIO == 1 {
do_work(); do_work();
} else if SCENARIO_2 { } else if SCENARIO == 2 {
do_something(); do_something();
} else if SCENARIO_3 { } else if SCENARIO == 3 {
do_something_else(); do_something_else();
} else { } else {
do_nothing(); do_nothing();
@ -29,9 +29,7 @@ r"
// Create a new 'Scope' - put constants in it to aid optimization // Create a new 'Scope' - put constants in it to aid optimization
let mut scope = Scope::new(); let mut scope = Scope::new();
scope.push_constant("SCENARIO_1", true); scope.push_constant("SCENARIO", 1_i64);
scope.push_constant("SCENARIO_2", false);
scope.push_constant("SCENARIO_3", false);
// Re-optimize the AST // Re-optimize the AST
let new_ast = engine.optimize_ast(&scope, master_ast.clone(), OptimizationLevel::Simple); let new_ast = engine.optimize_ast(&scope, master_ast.clone(), OptimizationLevel::Simple);

View File

@ -3,17 +3,20 @@ Side-Effect Considerations for Full Optimization Level
{{#include ../../links.md}} {{#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 All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_
nor cause any side-effects, with the exception of `print` and `debug` which are handled specially) so using (i.e. they do not mutate state nor cause any side-effects, with the exception of `print` and `debug`
[`OptimizationLevel::Full`] is usually quite safe _unless_ custom types and functions are registered. 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 If custom functions are registered to overload built-in operators, they will also be called when
(in an `if` statement, for example) causing side-effects. the operators are used (in an `if` statement, for example) causing side-effects.
Therefore, the rule-of-thumb is: Therefore, the rule-of-thumb is:
* _Always_ register custom types and functions _after_ compiling scripts if [`OptimizationLevel::Full`] is used. * _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.

View File

@ -88,84 +88,3 @@ fn say_hello() {
} }
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!'
```

View File

@ -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. 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 Arguments are Passed by Value
---------------------------- ----------------------------

View File

@ -4,6 +4,16 @@ Export Variables, Functions and Sub-Modules in Module
{{#include ../../links.md}} {{#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 Export Global Variables
---------------------- ----------------------

View File

@ -4,6 +4,12 @@ Import a Module
{{#include ../../links.md}} {{#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 `import` Statement
----------------- -----------------

View File

@ -6,13 +6,9 @@ Modules
Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _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. 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 A module is of the type `Module` and holds a collection of functions, variables, iterators and sub-modules.
by that script. 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. Other scripts can then load this module and use the functions and variables exported
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
as if they were defined inside the same script. as if they were defined inside the same script.

View File

@ -37,6 +37,8 @@ If none is provided, it defaults to [`()`].
A variable defined within a statement block is _local_ to that block. A variable defined within a statement block is _local_ to that block.
Use `is_def_var` to detect if a variable is defined.
```rust ```rust
let x; // ok - value is '()' let x; // ok - value is '()'
let x = 3; // ok let x = 3; // ok
@ -57,4 +59,10 @@ X == 123;
x == 999; // access to local 'x' x == 999; // access to local 'x'
} }
x == 42; // the parent block's 'x' is not changed 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;
``` ```

View File

@ -100,10 +100,11 @@
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md [function namespaces]: {{rootUrl}}/language/fn-namespaces.md
[anonymous function]: {{rootUrl}}/language/fn-anon.md [anonymous function]: {{rootUrl}}/language/fn-anon.md
[anonymous functions]: {{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}}/rust/modules/index.md
[module]: {{rootUrl}}/language/modules/index.md [module]: {{rootUrl}}/rust/modules/index.md
[modules]: {{rootUrl}}/language/modules/index.md [modules]: {{rootUrl}}/rust/modules/index.md
[module resolver]: {{rootUrl}}/rust/modules/resolvers.md [module resolver]: {{rootUrl}}/rust/modules/resolvers.md
[`export`]: {{rootUrl}}/language/modules/export.md [`export`]: {{rootUrl}}/language/modules/export.md
[`import`]: {{rootUrl}}/language/modules/import.md [`import`]: {{rootUrl}}/language/modules/import.md

182
doc/src/patterns/events.md Normal file
View 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);
}
```

View File

@ -1,7 +1,7 @@
Advanced Patterns Usage Patterns
================= ==============
{{#include ../links.md}} {{#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.

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

View File

@ -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 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 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, 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. 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, All `pub` functions become registered functions, all `pub` constants become [module] constant variables,
and all sub-modules become Rhai sub-modules. 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 ```rust
use rhai::plugin::*; // a "prelude" import for macros use rhai::plugin::*; // a "prelude" import for macros
@ -57,7 +62,7 @@ mod my_module {
42 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 { pub mod my_sub_module {
// This function is ignored when loaded as a package. // This function is ignored when loaded as a package.
// Otherwise it is a valid registered function under a sub-module. // Otherwise it is a valid registered function under a sub-module.
@ -65,16 +70,32 @@ mod my_module {
"hello".to_string() "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 ```rust
fn main() { fn main() {
let mut engine = Engine::new(); 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); let module = exported_module!(my_module);
// A module can simply be loaded, registering all public functions. // 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_ 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. 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 ### Use as loadable `Module`
be used to serve the module, and the module is loaded via `import` statements.
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. 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 Function Overloading and Operators
--------------------------------- ---------------------------------

View File

@ -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, 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. 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: 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`. * Functions not specifically marked `private`.
* Global modules that remain in the [`Scope`] at the end of a script run. * Global modules that remain in the [`Scope`] at the end of a script run.
`Module::eval_ast_as_new` encapsulates the entire `AST` into each function call, merging the module namespace
`merge_namespaces` Parameter with the global namespace. Therefore, functions defined within the same module script can cross-call each other.
---------------------------
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 |
Examples Examples
@ -73,13 +67,9 @@ let ast = engine.compile(r#"
"#)?; "#)?;
// Convert the 'AST' into a module, using the 'Engine' to evaluate it first // Convert the 'AST' into a module, using the 'Engine' to evaluate it first
// // A copy of the entire 'AST' is encapsulated into each function,
// The second parameter ('merge_namespaces'), when set to true, will encapsulate // allowing functions in the module script to cross-call each other.
// a copy of the entire 'AST' into each function, allowing functions in the module script let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
// 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)?;
// 'module' now contains: // 'module' now contains:
// - sub-module: 'foobar' (renamed from 'extra') // - sub-module: 'foobar' (renamed from 'extra')

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

View File

@ -5,6 +5,8 @@ Module Resolvers
When encountering an [`import`] statement, Rhai attempts to _resolve_ the module based on the path string. 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. _Module Resolvers_ are service types that implement the [`ModuleResolver`][traits] trait.
@ -32,75 +34,97 @@ are _merged_ into a _unified_ namespace.
| my_module.rhai | | my_module.rhai |
------------------ ------------------
// This function overrides any in the main script.
private fn inner_message() { "hello! from module!" } 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 | | 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; 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(); // prints "main here!"
m::greet(|| main_message()); // works - function in global
``` ```
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` One such situation is the need to provide a _default implementation_ to a simulated _virtual_ function:
-------------------------
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.
```rust ```rust
------------------ ------------------
| my_module.rhai | | 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() { // This function acts as the default implementation.
print(inner_message()); // cross-calling a module function! private fn default_message() { "hello! from module!" }
// there will be trouble because each function
// in the module is supposed to be independent
// of each other
}
// This function depends on a 'virtual' function 'message'
// which is not defined in the module script.
fn greet() { 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 | | 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; 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. 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` `StaticModuleResolver`

View File

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

View File

@ -146,7 +146,7 @@ fn main() {
#[cfg(not(feature = "no_optimize"))] #[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")] #[cfg(feature = "no_optimize")]

View File

@ -21,7 +21,7 @@ use crate::stdlib::{
boxed::Box, boxed::Box,
fmt, fmt,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
string::String, string::{String, ToString},
}; };
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -531,7 +531,7 @@ impl Dynamic {
/// assert_eq!(result.type_name(), "i64"); /// assert_eq!(result.type_name(), "i64");
/// assert_eq!(result.to_string(), "42"); /// 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.type_name(), "string");
/// assert_eq!(result.to_string(), "hello"); /// assert_eq!(result.to_string(), "hello");
/// ///
@ -572,6 +572,12 @@ impl Dynamic {
.clone() .clone()
.into(); .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::<()>() { if TypeId::of::<T>() == TypeId::of::<()>() {
return ().into(); return ().into();
} }

View File

@ -28,10 +28,7 @@ use crate::{
use crate::fn_register::{RegisterFn, RegisterResultFn}; use crate::fn_register::{RegisterFn, RegisterResultFn};
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::{ use crate::{fn_args::FuncArgs, fn_call::ensure_no_data_race, utils::StaticVec};
engine::get_script_function_by_signature, fn_args::FuncArgs, fn_call::ensure_no_data_race,
utils::StaticVec,
};
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
use crate::optimize::optimize_into_ast; use crate::optimize::optimize_into_ast;
@ -1598,7 +1595,8 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
args: &mut [&mut Dynamic], args: &mut [&mut Dynamic],
) -> FuncReturn<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()))?; .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::none()))?;
let mut state = State::new(); let mut state = State::new();

View File

@ -7,7 +7,7 @@ use crate::fn_native::{Callback, FnPtr};
use crate::module::{Module, ModuleRef}; use crate::module::{Module, ModuleRef};
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::packages::{Package, PackagesCollection, StandardPackage}; 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::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope}; 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")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
use crate::any::Variant; use crate::any::Variant;
#[cfg(not(feature = "no_function"))]
use crate::parser::ScriptFnDef;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
use crate::module::ModuleResolver; use crate::module::ModuleResolver;
@ -36,7 +33,7 @@ use crate::utils::ImmutableString;
use crate::any::DynamicWriteLock; use crate::any::DynamicWriteLock;
use crate::stdlib::{ use crate::stdlib::{
borrow::Cow, any::type_name,
boxed::Box, boxed::Box,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
fmt, format, fmt, format,
@ -64,13 +61,17 @@ pub type Array = Vec<Dynamic>;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub type Map = HashMap<ImmutableString, Dynamic>; pub type Map = HashMap<ImmutableString, Dynamic>;
/// [INTERNALS] A stack of imported modules. /// _[INTERNALS]_ A stack of imported modules.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
/// ## WARNING /// ## WARNING
/// ///
/// This type is volatile and may change. /// 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(not(feature = "unchecked"))]
#[cfg(debug_assertions)] #[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: &str = "Fn";
pub const KEYWORD_FN_PTR_CALL: &str = "call"; pub const KEYWORD_FN_PTR_CALL: &str = "call";
pub const KEYWORD_FN_PTR_CURRY: &str = "curry"; 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_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 KEYWORD_THIS: &str = "this";
pub const FN_TO_STRING: &str = "to_string"; pub const FN_TO_STRING: &str = "to_string";
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -147,6 +151,7 @@ pub enum Target<'a> {
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl Target<'_> { impl Target<'_> {
/// Is the `Target` a reference pointing to other data? /// Is the `Target` a reference pointing to other data?
#[allow(dead_code)]
#[inline(always)] #[inline(always)]
pub fn is_ref(&self) -> bool { pub fn is_ref(&self) -> bool {
match self { match self {
@ -160,6 +165,7 @@ impl Target<'_> {
} }
} }
/// Is the `Target` an owned value? /// Is the `Target` an owned value?
#[allow(dead_code)]
#[inline(always)] #[inline(always)]
pub fn is_value(&self) -> bool { pub fn is_value(&self) -> bool {
match self { match self {
@ -173,6 +179,7 @@ impl Target<'_> {
} }
} }
/// Is the `Target` a shared value? /// Is the `Target` a shared value?
#[allow(dead_code)]
#[inline(always)] #[inline(always)]
pub fn is_shared(&self) -> bool { pub fn is_shared(&self) -> bool {
match self { match self {
@ -225,25 +232,49 @@ impl Target<'_> {
Self::StringChar(_, _, ref mut r) => r, Self::StringChar(_, _, ref mut r) => r,
} }
} }
/// Update the value of the `Target`. /// Propagate a changed value back to the original source.
/// Position in `EvalAltResult` is `None` and must be set afterwards. /// This has no effect except for string indexing.
pub fn set_value(&mut self, new_val: Dynamic) -> Result<(), Box<EvalAltResult>> { #[cfg(not(feature = "no_object"))]
#[inline(always)]
pub fn propagate_changed_value(&mut self) {
match 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_closure"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Self::LockGuard((r, _)) => **r = new_val, Self::LockGuard((r, _)) => **r = new_val.0,
Self::Value(_) => { Self::Value(_) => {
return EvalAltResult::ErrorAssignmentToUnknownLHS(Position::none()).into(); return EvalAltResult::ErrorAssignmentToUnknownLHS(target_pos).into();
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::StringChar(string, index, _) if string.is::<ImmutableString>() => { Self::StringChar(string, index, _) if string.is::<ImmutableString>() => {
let mut s = string.write_lock::<ImmutableString>().unwrap(); let mut s = string.write_lock::<ImmutableString>().unwrap();
// Replace the character at the specified index position // Replace the character at the specified index position
let new_ch = new_val let new_ch = new_val.0.as_char().map_err(|err| {
.as_char() Box::new(EvalAltResult::ErrorMismatchDataType(
.map_err(|_| EvalAltResult::ErrorCharMismatch(Position::none()))?; err.to_string(),
"char".to_string(),
new_val.1,
))
})?;
let mut chars = s.chars().collect::<StaticVec<_>>(); let mut chars = s.chars().collect::<StaticVec<_>>();
let ch = chars[*index]; 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. /// Exported under the `internals` feature only.
/// ///
/// ## WARNING /// ## WARNING
@ -315,25 +346,7 @@ impl State {
} }
} }
/// Get a script-defined function definition from a module. /// _[INTERNALS]_ A type containing all the limits imposed by the `Engine`.
#[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`.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
/// ## WARNING /// ## WARNING
@ -428,56 +441,7 @@ impl fmt::Debug for Engine {
impl Default for Engine { impl Default for Engine {
fn default() -> Self { fn default() -> Self {
// Create the new scripting Engine Self::new()
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
} }
} }
@ -643,7 +607,56 @@ pub fn search_scope_only<'s, 'a>(
impl Engine { impl Engine {
/// Create a new `Engine` /// Create a new `Engine`
pub fn new() -> Self { 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. /// Create a new `Engine` with minimal built-in functions.
@ -700,7 +713,7 @@ impl Engine {
idx_values: &mut StaticVec<Dynamic>, idx_values: &mut StaticVec<Dynamic>,
chain_type: ChainType, chain_type: ChainType,
level: usize, level: usize,
new_val: Option<Dynamic>, new_val: Option<(Dynamic, Position)>,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
if chain_type == ChainType::None { if chain_type == ChainType::None {
panic!(); panic!();
@ -747,10 +760,7 @@ impl Engine {
{ {
// Indexed value is a reference - update directly // Indexed value is a reference - update directly
Ok(ref mut obj_ptr) => { Ok(ref mut obj_ptr) => {
obj_ptr obj_ptr.set_value(new_val.unwrap(), rhs.position())?;
.set_value(new_val.unwrap())
.map_err(|err| err.new_position(rhs.position()))?;
None None
} }
Err(err) => match *err { Err(err) => match *err {
@ -766,7 +776,7 @@ impl Engine {
if let Some(mut new_val) = _call_setter { if let Some(mut new_val) = _call_setter {
let val = target.as_mut(); let val = target.as_mut();
let val_type_name = val.type_name(); 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( self.exec_fn_call(
state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, &None, state, lib, FN_IDX_SET, 0, args, is_ref, true, false, None, &None,
@ -814,8 +824,7 @@ impl Engine {
let mut val = self let mut val = self
.get_indexed_mut(state, lib, target, index, *pos, true, false, level)?; .get_indexed_mut(state, lib, target, index, *pos, true, false, level)?;
val.set_value(new_val.unwrap()) val.set_value(new_val.unwrap(), rhs.position())?;
.map_err(|err| err.new_position(rhs.position()))?;
Ok((Default::default(), true)) Ok((Default::default(), true))
} }
// {xxx:map}.id // {xxx:map}.id
@ -832,7 +841,7 @@ impl Engine {
Expr::Property(x) if new_val.is_some() => { Expr::Property(x) if new_val.is_some() => {
let ((_, _, setter), pos) = x.as_ref(); let ((_, _, setter), pos) = x.as_ref();
let mut new_val = new_val; 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( self.exec_fn_call(
state, lib, setter, 0, &mut args, is_ref, true, false, None, &None, state, lib, setter, 0, &mut args, is_ref, true, false, None, &None,
level, level,
@ -986,7 +995,7 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
expr: &Expr, expr: &Expr,
level: usize, level: usize,
new_val: Option<Dynamic>, new_val: Option<(Dynamic, Position)>,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let ((dot_lhs, dot_rhs, op_pos), chain_type) = match expr { let ((dot_lhs, dot_rhs, op_pos), chain_type) = match expr {
Expr::Index(x) => (x.as_ref(), ChainType::Index), Expr::Index(x) => (x.as_ref(), ChainType::Index),
@ -1145,7 +1154,7 @@ impl Engine {
// val_array[idx] // val_array[idx]
let index = idx let index = idx
.as_int() .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(); let arr_len = arr.len();
@ -1164,15 +1173,15 @@ impl Engine {
Dynamic(Union::Map(map)) => { Dynamic(Union::Map(map)) => {
// val_map[idx] // val_map[idx]
Ok(if _create { Ok(if _create {
let index = idx let index = idx.take_immutable_string().map_err(|err| {
.take_immutable_string() self.make_type_mismatch_err::<ImmutableString>(err, idx_pos)
.map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; })?;
map.entry(index).or_insert_with(Default::default).into() map.entry(index).or_insert_with(Default::default).into()
} else { } else {
let index = idx let index = idx.read_lock::<ImmutableString>().ok_or_else(|| {
.read_lock::<ImmutableString>() self.make_type_mismatch_err::<ImmutableString>("", idx_pos)
.ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; })?;
map.get_mut(&*index) map.get_mut(&*index)
.map(Target::from) .map(Target::from)
@ -1186,7 +1195,7 @@ impl Engine {
let chars_len = s.chars().count(); let chars_len = s.chars().count();
let index = idx let index = idx
.as_int() .as_int()
.map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_pos))?; .map_err(|err| self.make_type_mismatch_err::<INT>(err, idx_pos))?;
if index >= 0 { if index >= 0 {
let offset = index as usize; let offset = index as usize;
@ -1424,9 +1433,9 @@ impl Engine {
let mut rhs_val = let mut rhs_val =
self.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?; 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 // Normal assignment
rhs_val Some((rhs_val, rhs_expr.position()))
} else { } else {
// Op-assignment - always map to `lhs = lhs op rhs` // Op-assignment - always map to `lhs = lhs op rhs`
let op = &op[..op.len() - 1]; // extract operator without = 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 self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?,
&mut rhs_val, &mut rhs_val,
]; ];
self.exec_fn_call(
let result = self
.exec_fn_call(
state, lib, op, 0, args, false, false, false, None, &None, level, state, lib, op, 0, args, false, false, false, None, &None, level,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
.map_err(|err| err.new_position(*op_pos))? .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 { match lhs_expr {
// name op= rhs // name op= rhs (handled above)
Expr::Variable(_) => unreachable!(), Expr::Variable(_) => unreachable!(),
// idx_lhs[idx_expr] op= rhs // idx_lhs[idx_expr] op= rhs
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -1460,12 +1474,8 @@ impl Engine {
)?; )?;
Ok(Default::default()) Ok(Default::default())
} }
// Error assignment to constant // Constant expression (should be caught during parsing)
expr if expr.is_constant() => EvalAltResult::ErrorAssignmentToConstant( expr if expr.is_constant() => unreachable!(),
expr.get_constant_str(),
expr.position(),
)
.into(),
// Syntax error // Syntax error
expr => EvalAltResult::ErrorAssignmentToUnknownLHS(expr.position()).into(), expr => EvalAltResult::ErrorAssignmentToUnknownLHS(expr.position()).into(),
} }
@ -1528,16 +1538,12 @@ impl Engine {
Ok((self Ok((self
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)? .eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?
.as_bool() .as_bool()
.map_err(|_| { .map_err(|err| self.make_type_mismatch_err::<bool>(err, lhs.position()))?
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position())
})?
&& // Short-circuit using && && // Short-circuit using &&
self self
.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)? .eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?
.as_bool() .as_bool()
.map_err(|_| { .map_err(|err| self.make_type_mismatch_err::<bool>(err, rhs.position()))?)
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position())
})?)
.into()) .into())
} }
@ -1546,16 +1552,12 @@ impl Engine {
Ok((self Ok((self
.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)? .eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?
.as_bool() .as_bool()
.map_err(|_| { .map_err(|err| self.make_type_mismatch_err::<bool>(err, lhs.position()))?
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position())
})?
|| // Short-circuit using || || // Short-circuit using ||
self self
.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)? .eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?
.as_bool() .as_bool()
.map_err(|_| { .map_err(|err| self.make_type_mismatch_err::<bool>(err, rhs.position()))?)
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position())
})?)
.into()) .into())
} }
@ -1637,7 +1639,7 @@ impl Engine {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.as_bool() .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| { .and_then(|guard_val| {
if guard_val { if guard_val {
self.eval_stmt(scope, mods, state, lib, this_ptr, if_block, level) self.eval_stmt(scope, mods, state, lib, this_ptr, if_block, level)
@ -1670,7 +1672,9 @@ impl Engine {
} }
} }
Ok(false) => return Ok(Default::default()), 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 { if let Some((name, _)) = alias {
module.index_all_sub_modules(); module.index_all_sub_modules();
mods.push((name.clone().into(), module)); mods.push((name.clone(), module));
} }
state.modules += 1; state.modules += 1;
@ -1839,7 +1843,7 @@ impl Engine {
) )
} }
} else { } 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)) .and_then(|t| t.get(name).map(String::as_str))
.unwrap_or_else(|| map_std_type_name(name)) .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()
}
} }

View File

@ -10,7 +10,7 @@ use crate::stdlib::{
string::{String, ToString}, 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. /// Exported under the `internals` feature only.
/// ///
/// ## WARNING /// ## WARNING

View File

@ -4,8 +4,8 @@ use crate::any::Dynamic;
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{ use crate::engine::{
search_imports, search_namespace, search_scope_only, Engine, Imports, State, KEYWORD_DEBUG, 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_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_FN,
KEYWORD_PRINT, KEYWORD_TYPE_OF, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
}; };
use crate::error::ParseErrorType; use crate::error::ParseErrorType;
use crate::fn_native::{FnCallArgs, FnPtr}; use crate::fn_native::{FnCallArgs, FnPtr};
@ -33,6 +33,9 @@ use crate::engine::{FN_IDX_GET, FN_IDX_SET};
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::engine::{Map, Target, FN_GET, FN_SET}; 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_closure"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
use crate::scope::Entry as ScopeEntry; use crate::scope::Entry as ScopeEntry;
@ -436,7 +439,33 @@ impl Engine {
} }
// Has a system function an override? // 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 // NOTE: We skip script functions for global_module and packages, and native functions for lib
// First check script-defined functions // First check script-defined functions
@ -611,7 +640,7 @@ impl Engine {
mods: &mut Imports, mods: &mut Imports,
state: &mut State, state: &mut State,
lib: &Module, lib: &Module,
script_expr: &Dynamic, script: &str,
_level: usize, _level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
self.inc_operations(state)?; 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 // Compile the script text
// No optimizations because we only run it once // No optimizations because we only run it once
let mut ast = self.compile_with_scope_and_optimization_level( 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 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()); return Err(ParseErrorType::WrongFnDefinition.into());
} }
@ -675,7 +696,6 @@ impl Engine {
level: usize, level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
let is_ref = target.is_ref(); let is_ref = target.is_ref();
let is_value = target.is_value();
// Get a reference to the mutation target Dynamic // Get a reference to the mutation target Dynamic
let obj = target.as_mut(); let obj = target.as_mut();
@ -744,15 +764,18 @@ impl Engine {
.into(), .into(),
false, false,
)) ))
} else if cfg!(not(feature = "no_closure")) } else if {
&& _fn_name == KEYWORD_IS_SHARED #[cfg(not(feature = "no_closure"))]
&& idx.is_empty()
{ {
_fn_name == KEYWORD_IS_SHARED && idx.is_empty()
}
#[cfg(feature = "no_closure")]
false
} {
// is_shared call // is_shared call
Ok((target.is_shared().into(), false)) Ok((target.is_shared().into(), false))
} else { } else {
#[cfg(not(feature = "no_object"))] let _redirected;
let redirected;
let mut hash = hash_script; let mut hash = hash_script;
// Check if it is a map method call in OOP style // 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(val) = map.get(_fn_name) {
if let Some(fn_ptr) = val.read_lock::<FnPtr>() { if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
// Remap the function name // Remap the function name
redirected = fn_ptr.get_fn_name().clone(); _redirected = fn_ptr.get_fn_name().clone();
_fn_name = &redirected; _fn_name = &_redirected;
// Add curried arguments // Add curried arguments
if !fn_ptr.curry().is_empty() { if !fn_ptr.curry().is_empty() {
fn_ptr fn_ptr
@ -795,10 +818,9 @@ impl Engine {
) )
}?; }?;
// Feed the changed temp value back // Propagate the changed value back to the source if necessary
if updated && !is_ref && !is_value { if updated {
let new_val = target.as_mut().clone(); target.propagate_changed_value();
target.set_value(new_val)?;
} }
Ok((result, updated)) Ok((result, updated))
@ -834,12 +856,7 @@ impl Engine {
return arg_value return arg_value
.take_immutable_string() .take_immutable_string()
.map_err(|typ| { .map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType( self.make_type_mismatch_err::<ImmutableString>(typ, expr.position())
self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(),
expr.position(),
)
.into()
}) })
.and_then(|s| FnPtr::try_from(s)) .and_then(|s| FnPtr::try_from(s))
.map(Into::<Dynamic>::into) .map(Into::<Dynamic>::into)
@ -850,18 +867,16 @@ impl Engine {
// Handle curry() // Handle curry()
if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 { if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 {
let expr = args_expr.get(0).unwrap(); 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>() { if !arg_value.is::<FnPtr>() {
return EvalAltResult::ErrorMismatchOutputType( return Err(self.make_type_mismatch_err::<FnPtr>(
self.map_type_name(type_name::<FnPtr>()).into(), self.map_type_name(arg_value.type_name()),
self.map_type_name(fn_ptr.type_name()).into(),
expr.position(), 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 let curry: StaticVec<_> = args_expr
.iter() .iter()
@ -877,7 +892,8 @@ impl Engine {
} }
// Handle is_shared() // 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 expr = args_expr.get(0).unwrap();
let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; 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) && !self.has_override(lib, 0, hash_script, pub_only)
{ {
let expr = args_expr.get(0).unwrap(); 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>() { if arg_value.is::<FnPtr>() {
let fn_ptr = fn_name.cast::<FnPtr>(); let fn_ptr = arg_value.cast::<FnPtr>();
curry = fn_ptr.curry().iter().cloned().collect(); curry = fn_ptr.curry().iter().cloned().collect();
// Redirect function name // Redirect function name
redirected = fn_ptr.take_data().0; redirected = fn_ptr.take_data().0;
@ -908,12 +924,64 @@ impl Engine {
// Recalculate hash // Recalculate hash
hash_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty()); hash_script = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty());
} else { } else {
return EvalAltResult::ErrorMismatchOutputType( return Err(self.make_type_mismatch_err::<FnPtr>(
self.map_type_name(type_name::<FnPtr>()).into(), self.map_type_name(arg_value.type_name()),
fn_name.type_name().into(),
expr.position(), 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 // eval - only in function call style
let prev_len = scope.len(); let prev_len = scope.len();
let expr = args_expr.get(0).unwrap(); let expr = args_expr.get(0).unwrap();
let script = 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)?;
let result = self let script = arg_value.as_str().map_err(|typ| {
.eval_script_expr(scope, mods, state, lib, &script, level + 1) self.make_type_mismatch_err::<ImmutableString>(typ, expr.position())
.map_err(|err| err.new_position(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, // IMPORTANT! If the eval defines new variables in the current scope,
// all variable offsets from this point on will be mis-aligned. // all variable offsets from this point on will be mis-aligned.

View File

@ -351,10 +351,10 @@ impl CallableFunction {
/// ///
/// Panics if the `CallableFunction` is not `Script`. /// Panics if the `CallableFunction` is not `Script`.
#[cfg(not(feature = "no_function"))] #[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 { match self {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => unreachable!(), 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. /// Get a reference to a script-defined function definition.

View File

@ -178,7 +178,7 @@ macro_rules! def_register {
(imp $abi:ident : $($par:ident => $arg:expr => $mark:ty => $param:ty => $let:stmt => $clone:expr),*) => { (imp $abi:ident : $($par:ident => $arg:expr => $mark:ty => $param:ty => $let:stmt => $clone:expr),*) => {
// ^ function ABI type // ^ function ABI type
// ^ function parameter generic type name (A, B, C etc.) // ^ 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 marker type (T, Ref<T> or Mut<T>)
// ^ function parameter actual type (T, &T or &mut T) // ^ function parameter actual type (T, &T or &mut T)
// ^ argument let statement // ^ argument let statement

View File

@ -128,16 +128,14 @@ pub mod module_resolvers {
pub use crate::module::resolvers::*; pub use crate::module::resolvers::*;
} }
/// Serialization support for [`serde`](https://crates.io/crates/serde). /// _[SERDE]_ Serialization support for [`serde`](https://crates.io/crates/serde).
/// /// Exported under the `serde` feature.
/// Requires the `serde` feature.
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
pub mod ser { pub mod ser {
pub use crate::serde::ser::to_dynamic; pub use crate::serde::ser::to_dynamic;
} }
/// Deserialization support for [`serde`](https://crates.io/crates/serde). /// _[SERDE]_ Deserialization support for [`serde`](https://crates.io/crates/serde).
/// /// Exported under the `serde` feature.
/// Requires the `serde` feature.
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
pub mod de { pub mod de {
pub use crate::serde::de::from_dynamic; pub use crate::serde::de::from_dynamic;

View File

@ -5,7 +5,7 @@ use crate::calc_fn_hash;
use crate::engine::Engine; use crate::engine::Engine;
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync}; use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync};
use crate::fn_register::by_value as cast_arg; use crate::fn_register::by_value as cast_arg;
use crate::parser::{FnAccess, FnAccess::Public}; use crate::parser::FnAccess;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::{Position, Token}; use crate::token::{Position, Token};
use crate::utils::{ImmutableString, StaticVec, StraightHasherBuilder}; use crate::utils::{ImmutableString, StaticVec, StraightHasherBuilder};
@ -85,7 +85,7 @@ impl fmt::Debug for Module {
"Module(\n modules: {}\n vars: {}\n functions: {}\n)", "Module(\n modules: {}\n vars: {}\n functions: {}\n)",
self.modules self.modules
.keys() .keys()
.map(|k| k.as_str()) .map(String::as_str)
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "), .join(", "),
self.variables self.variables
@ -294,6 +294,24 @@ impl Module {
hash_script 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? /// Does a sub-module exist in the module?
/// ///
/// # Examples /// # Examples
@ -382,10 +400,7 @@ impl Module {
} else if public_only { } else if public_only {
self.functions self.functions
.get(&hash_fn) .get(&hash_fn)
.map(|(_, access, _, _, _)| match access { .map(|(_, access, _, _, _)| access.is_public())
FnAccess::Public => true,
FnAccess::Private => false,
})
.unwrap_or(false) .unwrap_or(false)
} else { } else {
self.functions.contains_key(&hash_fn) self.functions.contains_key(&hash_fn)
@ -457,6 +472,9 @@ impl Module {
/// Arguments are simply passed in as a mutable array of `&mut Dynamic`, /// Arguments are simply passed in as a mutable array of `&mut Dynamic`,
/// which is guaranteed to contain enough arguments of the correct types. /// 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 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>()`. /// 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( self.set_fn(
name, name,
Public, FnAccess::Public,
arg_types, arg_types,
CallableFunction::from_method(Box::new(f)), CallableFunction::from_method(Box::new(f)),
) )
@ -519,19 +537,19 @@ impl Module {
pub(crate) fn set_raw_fn_as_scripted( pub(crate) fn set_raw_fn_as_scripted(
&mut self, &mut self,
name: impl Into<String>, name: impl Into<String>,
num_args: usize, num_params: usize,
func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<Dynamic> + SendSync + 'static, func: impl Fn(&Engine, &Module, &mut [&mut Dynamic]) -> FuncReturn<Dynamic> + SendSync + 'static,
) -> u64 { ) -> u64 {
// None + function name + number of arguments. // None + function name + number of arguments.
let name = name.into(); 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); let f = move |engine: &Engine, lib: &Module, args: &mut FnCallArgs| func(engine, lib, args);
self.functions.insert( self.functions.insert(
hash_script, hash_script,
( (
name, name,
FnAccess::Public, FnAccess::Public,
num_args, num_params,
None, None,
CallableFunction::from_pure(Box::new(f)), CallableFunction::from_pure(Box::new(f)),
), ),
@ -562,7 +580,7 @@ impl Module {
let arg_types = []; let arg_types = [];
self.set_fn( self.set_fn(
name, name,
Public, FnAccess::Public,
&arg_types, &arg_types,
CallableFunction::from_pure(Box::new(f)), CallableFunction::from_pure(Box::new(f)),
) )
@ -592,7 +610,7 @@ impl Module {
let arg_types = [TypeId::of::<A>()]; let arg_types = [TypeId::of::<A>()];
self.set_fn( self.set_fn(
name, name,
Public, FnAccess::Public,
&arg_types, &arg_types,
CallableFunction::from_pure(Box::new(f)), CallableFunction::from_pure(Box::new(f)),
) )
@ -622,7 +640,7 @@ impl Module {
let arg_types = [TypeId::of::<A>()]; let arg_types = [TypeId::of::<A>()];
self.set_fn( self.set_fn(
name, name,
Public, FnAccess::Public,
&arg_types, &arg_types,
CallableFunction::from_method(Box::new(f)), CallableFunction::from_method(Box::new(f)),
) )
@ -679,7 +697,7 @@ impl Module {
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn( self.set_fn(
name, name,
Public, FnAccess::Public,
&arg_types, &arg_types,
CallableFunction::from_pure(Box::new(f)), CallableFunction::from_pure(Box::new(f)),
) )
@ -715,7 +733,7 @@ impl Module {
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn( self.set_fn(
name, name,
Public, FnAccess::Public,
&arg_types, &arg_types,
CallableFunction::from_method(Box::new(f)), CallableFunction::from_method(Box::new(f)),
) )
@ -825,7 +843,7 @@ impl Module {
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn( self.set_fn(
name, name,
Public, FnAccess::Public,
&arg_types, &arg_types,
CallableFunction::from_pure(Box::new(f)), CallableFunction::from_pure(Box::new(f)),
) )
@ -867,7 +885,7 @@ impl Module {
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn( self.set_fn(
name, name,
Public, FnAccess::Public,
&arg_types, &arg_types,
CallableFunction::from_method(Box::new(f)), CallableFunction::from_method(Box::new(f)),
) )
@ -924,7 +942,7 @@ impl Module {
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]; let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn( self.set_fn(
FN_IDX_SET, FN_IDX_SET,
Public, FnAccess::Public,
&arg_types, &arg_types,
CallableFunction::from_method(Box::new(f)), CallableFunction::from_method(Box::new(f)),
) )
@ -1012,7 +1030,7 @@ impl Module {
]; ];
self.set_fn( self.set_fn(
name, name,
Public, FnAccess::Public,
&arg_types, &arg_types,
CallableFunction::from_pure(Box::new(f)), CallableFunction::from_pure(Box::new(f)),
) )
@ -1061,7 +1079,7 @@ impl Module {
]; ];
self.set_fn( self.set_fn(
name, name,
Public, FnAccess::Public,
&arg_types, &arg_types,
CallableFunction::from_method(Box::new(f)), CallableFunction::from_method(Box::new(f)),
) )
@ -1152,11 +1170,11 @@ impl Module {
) -> &mut Self { ) -> &mut Self {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if !other.modules.is_empty() { if !other.modules.is_empty() {
for (k, v) in &other.modules { other.modules.iter().for_each(|(k, v)| {
let mut m = Self::new(); let mut m = Self::new();
m.merge_filtered(v, _filter); m.merge_filtered(v, _filter);
self.modules.insert(k.clone(), m); self.modules.insert(k.clone(), m);
} });
} }
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
if !other.modules.is_empty() { if !other.modules.is_empty() {
@ -1210,17 +1228,13 @@ impl Module {
self self
} }
/// Get the number of variables in the module. /// Get the number of variables, functions and type iterators in the module.
pub fn num_var(&self) -> usize { pub fn count(&self) -> (usize, usize, usize) {
self.variables.len() (
} self.variables.len(),
/// Get the number of functions in the module. self.variables.len(),
pub fn num_fn(&self) -> usize { self.variables.len(),
self.variables.len() )
}
/// Get the number of type iterators in the module.
pub fn num_iter(&self) -> usize {
self.variables.len()
} }
/// Get an iterator to the variables in the module. /// 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. /// 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"))] #[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 self.functions
.values() .values()
.map(|(_, _, _, _, f)| f) .map(|(_, _, _, _, f)| f)
.filter(|f| f.is_script()) .filter(|f| f.is_script())
.map(|f| f.get_shared_fn_def()) .map(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"))] #[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 self.functions
.iter() .values()
.for_each(|(_, (_, _, _, _, v))| match v { .filter(|(_, _, _, _, f)| f.is_script())
CallableFunction::Script(f) => action(f.access, f.name.as_str(), f.params.len()), .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`. /// Create a new `Module` by evaluating an `AST`.
/// ///
/// ### `merge_namespaces` parameter /// The entire `AST` is encapsulated into each function, allowing functions
///
/// * 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 /// 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. /// 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. /// Therefore, all functions will be found.
///
/// * If `false`, each function is registered independently and cannot cross-call
/// each other. Functions are searched in the global namespace.
/// ///
/// # Examples /// # Examples
/// ///
@ -1273,19 +1316,14 @@ impl Module {
/// ///
/// let engine = Engine::new(); /// let engine = Engine::new();
/// let ast = engine.compile("let answer = 42; export answer;")?; /// 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!(module.contains_var("answer"));
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42); /// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub fn eval_ast_as_new( pub fn eval_ast_as_new(mut scope: Scope, ast: &AST, engine: &Engine) -> FuncReturn<Self> {
mut scope: Scope,
ast: &AST,
merge_namespaces: bool,
engine: &Engine,
) -> FuncReturn<Self> {
let mut mods = Imports::new(); let mut mods = Imports::new();
// Run the script // Run the script
@ -1298,8 +1336,8 @@ impl Module {
.into_iter() .into_iter()
.for_each(|ScopeEntry { value, alias, .. }| { .for_each(|ScopeEntry { value, alias, .. }| {
// Variables with an alias left in the scope become module variables // Variables with an alias left in the scope become module variables
if alias.is_some() { if let Some(alias) = alias {
module.variables.insert(*alias.unwrap(), value); module.variables.insert(*alias, value);
} }
}); });
@ -1309,16 +1347,17 @@ impl Module {
}); });
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if merge_namespaces { {
ast.iter_functions(|access, name, num_args| match access { let ast_lib: Shared<Module> = ast.lib().clone().into();
FnAccess::Private => (),
FnAccess::Public => { ast.iter_functions()
let fn_name = name.to_string(); .filter(|(access, _, _, _)| access.is_public())
let ast_lib = ast.lib().clone(); .for_each(|(_, name, num_params, func)| {
let ast_lib = ast_lib.clone();
module.set_raw_fn_as_scripted( module.set_raw_fn_as_scripted(
name, name,
num_args, num_params,
move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| {
let mut lib_merged; let mut lib_merged;
@ -1332,12 +1371,16 @@ impl Module {
}; };
engine engine
.call_fn_dynamic_raw( .call_script_fn(
&mut Scope::new(), &mut Default::default(),
&unified_lib, &mut Default::default(),
&fn_name, &mut Default::default(),
unified_lib,
&mut None, &mut None,
&func.name,
func.as_ref(),
args, args,
0,
) )
.map_err(|err| { .map_err(|err| {
// Wrap the error in a module-error // Wrap the error in a module-error
@ -1349,10 +1392,7 @@ impl Module {
}) })
}, },
); );
}
}); });
} else {
module.merge(ast.lib());
} }
Ok(module) Ok(module)
@ -1369,45 +1409,31 @@ impl Module {
variables: &mut Vec<(u64, Dynamic)>, variables: &mut Vec<(u64, Dynamic)>,
functions: &mut Vec<(u64, CallableFunction)>, functions: &mut Vec<(u64, CallableFunction)>,
) { ) {
for (name, m) in &module.modules { module.modules.iter().for_each(|(name, m)| {
// Index all the sub-modules first. // Index all the sub-modules first.
qualifiers.push(name); qualifiers.push(name);
index_module(m, qualifiers, variables, functions); index_module(m, qualifiers, variables, functions);
qualifiers.pop(); qualifiers.pop();
} });
// Index all variables // Index all variables
for (var_name, value) in &module.variables { module.variables.iter().for_each(|(var_name, value)| {
// Qualifiers + variable name // Qualifiers + variable name
let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0, empty()); let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0, empty());
variables.push((hash_var, value.clone())); variables.push((hash_var, value.clone()));
} });
// Index all Rust functions // Index all Rust functions
for (&_hash, (name, access, _num_args, params, func)) in module.functions.iter() { module
match access { .functions
// Private functions are not exported .iter()
FnAccess::Private => continue, .filter(|(_, (_, access, _, _, _))| access.is_public())
FnAccess::Public => (), .for_each(|(&_hash, (name, _, _num_params, params, func))| {
}
#[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 { if let Some(params) = params {
// Qualified Rust functions are indexed in two steps: // Qualified Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions, // 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + number of arguments. // i.e. qualifiers + function name + number of arguments.
let hash_qualified_script = let hash_qualified_script =
calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty()); calc_fn_hash(qualifiers.iter().cloned(), name, params.len(), empty());
// 2) Calculate a second hash with no qualifiers, empty function name, // 2) Calculate a second hash with no qualifiers, empty function name,
// zero number of arguments, and the actual list of argument `TypeId`'.s // zero number of arguments, and the actual list of argument `TypeId`'.s
let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned()); let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
@ -1415,14 +1441,24 @@ impl Module {
let hash_qualified_fn = hash_qualified_script ^ hash_fn_args; 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 qualifiers: Vec<_> = Default::default();
let mut variables: Vec<_> = Default::default(); let mut variables: Vec<_> = Default::default();
let mut functions: Vec<_> = Default::default(); let mut functions: Vec<_> = Default::default();
@ -1435,6 +1471,7 @@ impl Module {
self.all_functions = functions.into_iter().collect(); self.all_functions = functions.into_iter().collect();
self.indexed = true; self.indexed = true;
} }
}
/// Does a type iterator exist in the module? /// Does a type iterator exist in the module?
pub fn contains_iter(&self, id: TypeId) -> bool { pub fn contains_iter(&self, id: TypeId) -> bool {
@ -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. /// Exported under the `internals` feature only.
/// ///
/// A `u64` hash key is cached for quick search purposes. /// A `u64` hash key is cached for quick search purposes.

View File

@ -153,7 +153,7 @@ impl ModuleResolver for FileModuleResolver {
let c = self.cache.read().unwrap(); let c = self.cache.read().unwrap();
if let Some(ast) = c.get(&file_path) { 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)) Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos))
})?; })?;
None None
@ -163,7 +163,7 @@ impl ModuleResolver for FileModuleResolver {
Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos)) 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)) Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos))
})?; })?;
Some(ast) Some(ast)

View File

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

View File

@ -17,14 +17,6 @@ mod file;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub use file::FileModuleResolver; 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; mod stat;
pub use stat::StaticModuleResolver; pub use stat::StaticModuleResolver;

View File

@ -3,8 +3,10 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{ 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::fn_native::FnPtr;
use crate::module::Module; use crate::module::Module;
use crate::parser::{map_dynamic_to_expr, Expr, ScriptFnDef, Stmt, AST}; 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. /// Optimize an expression.
fn optimize_expr(expr: Expr, state: &mut State) -> Expr { fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// These keywords are handled specially // 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 { match expr {
// expr - do not promote because there is a reason it is wrapped in an `Expr::Expr` // expr - do not promote because there is a reason it is wrapped in an `Expr::Expr`
@ -568,29 +576,49 @@ 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 // Eagerly call functions
Expr::FnCall(mut x) Expr::FnCall(mut x)
if x.1.is_none() // Non-qualified if x.1.is_none() // Non-qualified
&& state.optimization_level == OptimizationLevel::Full // full optimizations && 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(); let ((name, _, _, pos), _, _, args, def_value) = x.as_mut();
// First search in functions lib (can override built-in) // First search for script-defined functions (can override built-in)
// Cater for both normal function call style and method call style (one additional arguments) #[cfg(not(feature = "no_function"))]
let has_script_fn = cfg!(not(feature = "no_function")) && state.lib.iter_fn().find(|(_, _, _, _,f)| { let has_script_fn = state.lib.get_script_fn(name, args.len(), false).is_some();
if !f.is_script() { return false; } #[cfg(feature = "no_function")]
let fn_def = f.get_fn_def(); let has_script_fn = false;
fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
}).is_some();
if has_script_fn { if !has_script_fn {
// A script-defined function overrides the built-in function - do not make the call let mut arg_values: StaticVec<_> = args.iter().map(|e| e.get_constant_value().unwrap()).collect();
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
return Expr::FnCall(x);
}
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()` // Save the typename of the first argument if it is `type_of()`
// This is to avoid `call_args` being passed into the closure // This is to avoid `call_args` being passed into the closure
@ -600,7 +628,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
"" ""
}; };
call_fn_with_constant_arguments(&state, name, arg_values.as_mut()) if let Some(expr) = call_fn_with_constant_arguments(&state, name, arg_values.as_mut())
.or_else(|| { .or_else(|| {
if !arg_for_type_of.is_empty() { if !arg_for_type_of.is_empty() {
// Handle `type_of()` // Handle `type_of()`
@ -611,15 +639,14 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
} }
}) })
.and_then(|result| map_dynamic_to_expr(result, *pos)) .and_then(|result| map_dynamic_to_expr(result, *pos))
.map(|expr| { {
state.set_dirty(); state.set_dirty();
expr return expr;
}) }
.unwrap_or_else(|| { }
// Optimize function call arguments
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
Expr::FnCall(x) Expr::FnCall(x)
})
} }
// id(args ..) -> optimize function call arguments // id(args ..) -> optimize function call arguments

View File

@ -12,13 +12,17 @@ use crate::syntax::FnCustomSyntaxEval;
use crate::token::{is_keyword_function, is_valid_identifier, Position, Token, TokenStream}; use crate::token::{is_keyword_function, is_valid_identifier, Position, Token, TokenStream};
use crate::utils::{StaticVec, StraightHasherBuilder}; 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"))] #[cfg(not(feature = "no_function"))]
use crate::engine::{FN_ANONYMOUS, KEYWORD_FN_PTR_CURRY}; use crate::engine::{FN_ANONYMOUS, KEYWORD_FN_PTR_CURRY};
#[cfg(not(feature = "no_object"))]
use crate::engine::{make_getter, make_setter};
use crate::stdlib::{ use crate::stdlib::{
any::TypeId,
borrow::Cow, borrow::Cow,
boxed::Box, boxed::Box,
char, char,
@ -91,7 +95,8 @@ impl AST {
&self.0 &self.0
} }
/// [INTERNALS] Get the statements. /// _[INTERNALS]_ Get the statements.
/// Exported under the `internals` feature only.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated(note = "this method is volatile and may change")] #[deprecated(note = "this method is volatile and may change")]
pub fn statements(&self) -> &[Stmt] { pub fn statements(&self) -> &[Stmt] {
@ -109,7 +114,8 @@ impl AST {
&self.1 &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")] #[cfg(feature = "internals")]
#[deprecated(note = "this method is volatile and may change")] #[deprecated(note = "this method is volatile and may change")]
pub fn lib(&self) -> &Module { pub fn lib(&self) -> &Module {
@ -301,8 +307,10 @@ impl AST {
/// Iterate through all functions /// Iterate through all functions
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub fn iter_functions(&self, action: impl FnMut(FnAccess, &str, usize)) { pub fn iter_functions<'a>(
self.1.iter_script_fn_info(action); &'a self,
) -> impl Iterator<Item = (FnAccess, &str, usize, Shared<ScriptFnDef>)> + 'a {
self.1.iter_script_fn()
} }
/// Clear all function definitions in the `AST`. /// 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. /// A type representing the access mode of a scripted function.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum FnAccess { pub enum FnAccess {
/// Private function.
Private,
/// Public function. /// Public function.
Public, Public,
/// Private function.
Private,
} }
impl fmt::Display for FnAccess { 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. /// Exported under the `internals` feature only.
/// ///
/// ## WARNING /// ## 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. /// Exported under the `internals` feature only.
/// ///
/// ## WARNING /// ## WARNING
@ -542,7 +567,7 @@ impl ParseSettings {
} }
} }
/// [INTERNALS] A Rhai statement. /// _[INTERNALS]_ A Rhai statement.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
/// Each variant is at most one pointer in size (for speed), /// 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)>), ReturnWithVal(Box<((ReturnType, Position), Option<Expr>, Position)>),
/// import expr as module /// import expr as module
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Import(Box<(Expr, Option<(String, Position)>, Position)>), Import(Box<(Expr, Option<(ImmutableString, Position)>, Position)>),
/// expr id as name, ... /// expr id as name, ...
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Export( 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. /// Exported under the `internals` feature only.
/// ///
/// ## WARNING /// ## 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. /// Exported under the `internals` feature only.
/// ///
/// This type is mainly used to provide a standard `Hash` implementation /// 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. /// Exported under the `internals` feature only.
/// ///
/// Each variant is at most one pointer in size (for speed), /// Each variant is at most one pointer in size (for speed),
@ -826,14 +851,40 @@ impl Default for Expr {
} }
impl 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. /// Get the `Dynamic` value of a constant expression.
/// ///
/// # Panics /// Returns `None` if the expression is not constant.
/// pub fn get_constant_value(&self) -> Option<Dynamic> {
/// Panics when the expression is not constant. Some(match self {
pub fn get_constant_value(&self) -> Dynamic { Self::Expr(x) => return x.get_constant_value(),
match self {
Self::Expr(x) => x.get_constant_value(),
Self::IntegerConstant(x) => x.0.into(), Self::IntegerConstant(x) => x.0.into(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -850,45 +901,22 @@ impl Expr {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Self::Array(x) if x.0.iter().all(Self::is_constant) => Dynamic(Union::Array(Box::new( 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"))] #[cfg(not(feature = "no_object"))]
Self::Map(x) if x.0.iter().all(|(_, v)| v.is_constant()) => { Self::Map(x) if x.0.iter().all(|(_, v)| v.is_constant()) => {
Dynamic(Union::Map(Box::new( Dynamic(Union::Map(Box::new(
x.0.iter() x.0.iter()
.map(|((k, _), v)| (k.clone(), v.get_constant_value())) .map(|((k, _), v)| (k.clone(), v.get_constant_value().unwrap()))
.collect::<HashMap<_, _>>(), .collect(),
))) )))
} }
_ => unreachable!("cannot get value of non-constant expression"), _ => return None,
} })
}
/// 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"),
}
} }
/// Get the `Position` of the expression. /// Get the `Position` of the expression.
@ -2736,7 +2764,7 @@ fn parse_import(
Ok(Stmt::Import(Box::new(( Ok(Stmt::Import(Box::new((
expr, expr,
Some((name, settings.pos)), Some((name.into(), settings.pos)),
token_pos, token_pos,
)))) ))))
} }

View File

@ -34,20 +34,26 @@ pub enum EvalAltResult {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
ErrorReadingScriptFile(PathBuf, Position, std::io::Error), 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), ErrorFunctionNotFound(String, Position),
/// An error has occurred inside a called function. /// 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), 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. /// 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), ErrorInModule(String, Box<EvalAltResult>, Position),
/// Access to `this` that is not bound. /// Access to `this` that is not bound.
ErrorUnboundThis(Position), ErrorUnboundThis(Position),
/// Non-boolean operand encountered for boolean operator. Wrapped value is the operator. /// Data is not of the required type.
ErrorBooleanArgMismatch(String, Position), /// Wrapped values are the type requested and type of the actual result.
/// Non-character value encountered where a character is required. ErrorMismatchDataType(String, String, Position),
ErrorCharMismatch(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. /// Array access out-of-bounds.
/// Wrapped values are the current number of elements in the array and the index number. /// Wrapped values are the current number of elements in the array and the index number.
ErrorArrayBounds(usize, INT, Position), 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. /// Wrapped values are the current number of characters in the string and the index number.
ErrorStringBounds(usize, INT, Position), 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. /// 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), 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. /// Invalid arguments for `in` operator.
ErrorInExpr(Position), 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. /// The `for` statement encounters a type that is not an iterator.
ErrorFor(Position), ErrorFor(Position),
/// Usage of an unknown variable. Wrapped value is the name of the variable. /// Data race detected when accessing a variable. Wrapped value is the variable name.
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.
ErrorDataRace(String, Position), ErrorDataRace(String, Position),
/// Assignment to an inappropriate LHS (left-hand-side) expression. /// Assignment to an inappropriate LHS (left-hand-side) expression.
ErrorAssignmentToUnknownLHS(Position), ErrorAssignmentToUnknownLHS(Position),
/// Assignment to a constant variable. /// Assignment to a constant variable. Wrapped value is the variable name.
ErrorAssignmentToConstant(String, Position), ErrorAssignmentToConstant(String, Position),
/// Returned type is not the same as the required output type. /// Inappropriate property access. Wrapped value is the property name.
/// Wrapped values are the type requested and type of the actual result.
ErrorMismatchOutputType(String, String, Position),
/// Inappropriate member access.
ErrorDotExpr(String, Position), ErrorDotExpr(String, Position),
/// Arithmetic error encountered. Wrapped value is the error message. /// Arithmetic error encountered. Wrapped value is the error message.
ErrorArithmetic(String, Position), ErrorArithmetic(String, Position),
@ -91,7 +83,7 @@ pub enum EvalAltResult {
ErrorTooManyModules(Position), ErrorTooManyModules(Position),
/// Call stack over maximum limit. /// Call stack over maximum limit.
ErrorStackOverflow(Position), 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), ErrorDataTooLarge(String, usize, usize, Position),
/// The script is prematurely terminated. /// The script is prematurely terminated.
ErrorTerminated(Position), ErrorTerminated(Position),
@ -119,16 +111,10 @@ impl EvalAltResult {
Self::ErrorInModule(_, _, _) => "Error in module", Self::ErrorInModule(_, _, _) => "Error in module",
Self::ErrorFunctionNotFound(_, _) => "Function not found", Self::ErrorFunctionNotFound(_, _) => "Function not found",
Self::ErrorUnboundThis(_) => "'this' is not bound", Self::ErrorUnboundThis(_) => "'this' is not bound",
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands", Self::ErrorMismatchDataType(_, _, _) => "Data type is incorrect",
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::ErrorIndexingType(_, _) => { Self::ErrorIndexingType(_, _) => {
"Indexing can only be performed on an array, an object map, a string, or a type with an indexer function defined" "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 => { Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
"Array access expects non-negative index" "Array access expects non-negative index"
} }
@ -139,7 +125,6 @@ impl EvalAltResult {
} }
Self::ErrorStringBounds(0, _, _) => "Empty string has nothing to index", Self::ErrorStringBounds(0, _, _) => "Empty string has nothing to index",
Self::ErrorStringBounds(_, _, _) => "String index out of bounds", Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
Self::ErrorLogicGuard(_) => "Boolean value expected",
Self::ErrorFor(_) => "For loop expects an array, object map, or range", Self::ErrorFor(_) => "For loop expects an array, object map, or range",
Self::ErrorVariableNotFound(_, _) => "Variable not found", Self::ErrorVariableNotFound(_, _) => "Variable not found",
Self::ErrorModuleNotFound(_, _) => "Module 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::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{}", s)?,
Self::ErrorIndexingType(_, _) Self::ErrorIndexingType(_, _)
| Self::ErrorNumericIndexExpr(_)
| Self::ErrorStringIndexExpr(_)
| Self::ErrorUnboundThis(_) | Self::ErrorUnboundThis(_)
| Self::ErrorImportExpr(_)
| Self::ErrorLogicGuard(_)
| Self::ErrorFor(_) | Self::ErrorFor(_)
| Self::ErrorAssignmentToUnknownLHS(_) | Self::ErrorAssignmentToUnknownLHS(_)
| Self::ErrorInExpr(_) | Self::ErrorInExpr(_)
@ -215,17 +196,19 @@ impl fmt::Display for EvalAltResult {
Self::ErrorAssignmentToConstant(s, _) => write!(f, "{}: '{}'", desc, s)?, Self::ErrorAssignmentToConstant(s, _) => write!(f, "{}: '{}'", desc, s)?,
Self::ErrorMismatchOutputType(r, 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::ErrorArithmetic(s, _) => f.write_str(s)?,
Self::ErrorLoopBreak(_, _) => f.write_str(desc)?, Self::ErrorLoopBreak(_, _) => f.write_str(desc)?,
Self::Return(_, _) => 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 => { Self::ErrorArrayBounds(_, index, _) if *index < 0 => {
write!(f, "{}: {} < 0", desc, index)? write!(f, "{}: {} < 0", desc, index)?
} }
@ -290,15 +273,10 @@ impl EvalAltResult {
| Self::ErrorInFunctionCall(_, _, pos) | Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorInModule(_, _, pos) | Self::ErrorInModule(_, _, pos)
| Self::ErrorUnboundThis(pos) | Self::ErrorUnboundThis(pos)
| Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorMismatchDataType(_, _, pos)
| Self::ErrorCharMismatch(pos)
| Self::ErrorArrayBounds(_, _, pos) | Self::ErrorArrayBounds(_, _, pos)
| Self::ErrorStringBounds(_, _, pos) | Self::ErrorStringBounds(_, _, pos)
| Self::ErrorIndexingType(_, pos) | Self::ErrorIndexingType(_, pos)
| Self::ErrorNumericIndexExpr(pos)
| Self::ErrorStringIndexExpr(pos)
| Self::ErrorImportExpr(pos)
| Self::ErrorLogicGuard(pos)
| Self::ErrorFor(pos) | Self::ErrorFor(pos)
| Self::ErrorVariableNotFound(_, pos) | Self::ErrorVariableNotFound(_, pos)
| Self::ErrorModuleNotFound(_, pos) | Self::ErrorModuleNotFound(_, pos)
@ -332,15 +310,10 @@ impl EvalAltResult {
| Self::ErrorInFunctionCall(_, _, pos) | Self::ErrorInFunctionCall(_, _, pos)
| Self::ErrorInModule(_, _, pos) | Self::ErrorInModule(_, _, pos)
| Self::ErrorUnboundThis(pos) | Self::ErrorUnboundThis(pos)
| Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorMismatchDataType(_, _, pos)
| Self::ErrorCharMismatch(pos)
| Self::ErrorArrayBounds(_, _, pos) | Self::ErrorArrayBounds(_, _, pos)
| Self::ErrorStringBounds(_, _, pos) | Self::ErrorStringBounds(_, _, pos)
| Self::ErrorIndexingType(_, pos) | Self::ErrorIndexingType(_, pos)
| Self::ErrorNumericIndexExpr(pos)
| Self::ErrorStringIndexExpr(pos)
| Self::ErrorImportExpr(pos)
| Self::ErrorLogicGuard(pos)
| Self::ErrorFor(pos) | Self::ErrorFor(pos)
| Self::ErrorVariableNotFound(_, pos) | Self::ErrorVariableNotFound(_, pos)
| Self::ErrorModuleNotFound(_, pos) | Self::ErrorModuleNotFound(_, pos)

View File

@ -74,8 +74,8 @@ impl fmt::Debug for CustomSyntax {
/// Context of a script evaluation process. /// Context of a script evaluation process.
#[derive(Debug)] #[derive(Debug)]
pub struct EvalContext<'a, 'b: 'a, 's, 'm, 't, 'd: 't> { pub struct EvalContext<'a, 's, 'm, 't, 'd: 't> {
pub(crate) mods: &'a mut Imports<'b>, pub(crate) mods: &'a mut Imports,
pub(crate) state: &'s mut State, pub(crate) state: &'s mut State,
pub(crate) lib: &'m Module, pub(crate) lib: &'m Module,
pub(crate) this_ptr: &'t mut Option<&'d mut Dynamic>, pub(crate) this_ptr: &'t mut Option<&'d mut Dynamic>,

View File

@ -2,9 +2,12 @@
use crate::engine::{ use crate::engine::{
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, 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::error::LexError;
use crate::parser::INT; use crate::parser::INT;
use crate::utils::StaticVec; 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. /// Exported under the `internals` feature only.
/// ///
/// ## WARNING /// ## WARNING
@ -507,9 +510,11 @@ impl Token {
| "await" | "yield" => Reserved(syntax.into()), | "await" | "yield" => Reserved(syntax.into()),
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR 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 => { | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR
Reserved(syntax.into()) | KEYWORD_IS_DEF_FN | KEYWORD_THIS => Reserved(syntax.into()),
}
#[cfg(not(feature = "no_closure"))]
KEYWORD_IS_SHARED => Reserved(syntax.into()),
_ => return None, _ => 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. /// Exported under the `internals` feature only.
/// ///
/// ## WARNING /// ## WARNING
@ -724,7 +729,7 @@ pub struct TokenizeState {
pub include_comments: bool, 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. /// Exported under the `internals` feature only.
/// ///
/// ## WARNING /// ## WARNING
@ -738,7 +743,7 @@ pub trait InputStream {
fn peek_next(&mut self) -> Option<char>; 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. /// Exported under the `internals` feature only.
/// ///
/// ## WARNING /// ## 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. /// Exported under the `internals` feature only.
/// ///
/// ## WARNING /// ## WARNING
@ -1386,6 +1391,8 @@ fn get_next_token_inner(
('@', _) => return Some((Token::Reserved("@".into()), start_pos)), ('@', _) => return Some((Token::Reserved("@".into()), start_pos)),
('$', _) => return Some((Token::Reserved("$".into()), start_pos)),
('\0', _) => unreachable!(), ('\0', _) => unreachable!(),
(ch, _) if ch.is_whitespace() => (), (ch, _) if ch.is_whitespace() => (),
@ -1455,7 +1462,9 @@ pub fn is_keyword_function(name: &str) -> bool {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
KEYWORD_IS_SHARED => true, KEYWORD_IS_SHARED => true,
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR 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, _ => false,
} }
} }
@ -1465,7 +1474,8 @@ pub fn is_keyword_function(name: &str) -> bool {
#[inline(always)] #[inline(always)]
pub fn can_override_keyword(name: &str) -> bool { pub fn can_override_keyword(name: &str) -> bool {
match name { 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, _ => false,
} }
} }

View File

@ -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. /// Exported under the `internals` feature only.
/// ///
/// Module names are passed in via `&str` references from an iterator. /// Module names are passed in via `&str` references from an iterator.
@ -89,7 +89,7 @@ pub fn calc_fn_hash<'a>(
s.finish() 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. /// which is a specialized `Vec` backed by a small, fixed-size array when there are <= 4 items stored.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
pub type StaticVec<T> = SmallVec<[T; 4]>; pub type StaticVec<T> = SmallVec<[T; 4]>;

View File

@ -21,3 +21,29 @@ fn test_constant() -> Result<(), Box<EvalAltResult>> {
Ok(()) 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(())
}

View File

@ -173,3 +173,29 @@ fn test_function_captures() -> Result<(), Box<EvalAltResult>> {
Ok(()) 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(())
}

View File

@ -265,7 +265,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
engine.set_module_resolver(Some(resolver1)); 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(); let mut resolver2 = StaticModuleResolver::new();
resolver2.insert("testing", module); resolver2.insert("testing", module);
@ -361,3 +361,41 @@ fn test_module_str() -> Result<(), Box<EvalAltResult>> {
Ok(()) 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(())
}

View File

@ -36,12 +36,9 @@ fn test_packages_with_script() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let ast = engine.compile("fn foo(x) { x + 1 } fn bar(x) { foo(x) + 1 }")?; 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); engine.load_package(module);
assert_eq!(engine.eval::<INT>("foo(41)")?, 42); 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); assert_eq!(engine.eval::<INT>("bar(40)")?, 42);
Ok(()) Ok(())

View File

@ -47,6 +47,7 @@ mod test {
macro_rules! gen_unary_functions { macro_rules! gen_unary_functions {
($op_name:ident = $op_fn:ident ( $($arg_type:ident),+ ) -> $return_type:ident) => { ($op_name:ident = $op_fn:ident ( $($arg_type:ident),+ ) -> $return_type:ident) => {
mod $op_name { $( mod $op_name { $(
#[allow(non_snake_case)]
pub mod $arg_type { pub mod $arg_type {
use super::super::*; use super::super::*;

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, ImmutableString, RegisterFn, INT}; use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, RegisterFn, Scope, INT};
#[test] #[test]
fn test_string() -> Result<(), Box<EvalAltResult>> { fn test_string() -> Result<(), Box<EvalAltResult>> {
@ -49,6 +49,21 @@ fn test_string() -> Result<(), Box<EvalAltResult>> {
Ok(()) 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"))] #[cfg(not(feature = "no_object"))]
#[test] #[test]
fn test_string_substring() -> Result<(), Box<EvalAltResult>> { fn test_string_substring() -> Result<(), Box<EvalAltResult>> {

View File

@ -35,9 +35,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
if !engine if !engine
.eval_expression_tree(context, scope, expr)? .eval_expression_tree(context, scope, expr)?
.as_bool() .as_bool()
.map_err(|_| { .map_err(|err| engine.make_type_mismatch_err::<bool>(err, expr.position()))?
EvalAltResult::ErrorBooleanArgMismatch("do-while".into(), expr.position())
})?
{ {
break; break;
} }