Merge pull request #311 from schungx/master
A bunch of new features and bug fixes.
This commit is contained in:
commit
b1e8f52135
@ -42,6 +42,7 @@ no_closure = [] # no automatic sharing and capture of anonymous functions to
|
|||||||
no_module = [] # no modules
|
no_module = [] # no modules
|
||||||
internals = [] # expose internal data structures
|
internals = [] # expose internal data structures
|
||||||
unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers.
|
unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers.
|
||||||
|
metadata = [ "serde", "serde_json"] # enables exporting functions metadata to JSON
|
||||||
|
|
||||||
# compiling for no-std
|
# compiling for no-std
|
||||||
no_std = [ "smallvec/union", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]
|
no_std = [ "smallvec/union", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]
|
||||||
@ -86,6 +87,12 @@ default_features = false
|
|||||||
features = ["derive", "alloc"]
|
features = ["derive", "alloc"]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.serde_json]
|
||||||
|
version = "1.0.60"
|
||||||
|
default_features = false
|
||||||
|
features = ["alloc"]
|
||||||
|
optional = true
|
||||||
|
|
||||||
[dependencies.unicode-xid]
|
[dependencies.unicode-xid]
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
default_features = false
|
default_features = false
|
||||||
|
10
RELEASES.md
10
RELEASES.md
@ -10,9 +10,14 @@ Each function defined in an `AST` can optionally attach _doc-comments_ (which, a
|
|||||||
are comments prefixed by either `///` or `/**`). Doc-comments allow third-party tools to
|
are comments prefixed by either `///` or `/**`). Doc-comments allow third-party tools to
|
||||||
automatically generate documentation for functions defined in a Rhai script.
|
automatically generate documentation for functions defined in a Rhai script.
|
||||||
|
|
||||||
|
A new API, `Engine::gen_fn_metadata_to_json`, paired with the new `metadata` feature,
|
||||||
|
exports the full list of functions metadata (including those in an `AST`) as a JSON document.
|
||||||
|
|
||||||
Bug fixes
|
Bug fixes
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
* Unary prefix operators `-`, `+` and `!` now bind correctly when applied to an expression. Previously, `-x.len` is parsed as `(-x).len` which is obviously counter-intuitive.
|
||||||
|
* Indexing of namespace-qualified variables now work properly, such as `path::to::var[x]`.
|
||||||
* Constants are no longer propagated by the optimizer if shadowed by a non-constant variable.
|
* Constants are no longer propagated by the optimizer if shadowed by a non-constant variable.
|
||||||
* Constants passed as the `this` parameter to Rhai functions now throws an error if assigned to.
|
* Constants passed as the `this` parameter to Rhai functions now throws an error if assigned to.
|
||||||
* Generic type parameter of `Engine::register_iterator` is `IntoIterator` instead of `Iterator`.
|
* Generic type parameter of `Engine::register_iterator` is `IntoIterator` instead of `Iterator`.
|
||||||
@ -22,7 +27,7 @@ Breaking changes
|
|||||||
----------------
|
----------------
|
||||||
|
|
||||||
* `Engine::on_progress` now takes `u64` instead of `&u64`.
|
* `Engine::on_progress` now takes `u64` instead of `&u64`.
|
||||||
* The closure for `Engine::on_debug` now takes an additional `Position` parameter.
|
* The closure for `Engine::on_debug` now takes two additional parameters: `source: Option<&str>` and `pos: Position`.
|
||||||
* `AST::iter_functions` now returns `ScriptFnMetadata`.
|
* `AST::iter_functions` now returns `ScriptFnMetadata`.
|
||||||
* The parser function passed to `Engine::register_custom_syntax_raw` now takes an additional parameter containing the _look-ahead_ symbol.
|
* The parser function passed to `Engine::register_custom_syntax_raw` now takes an additional parameter containing the _look-ahead_ symbol.
|
||||||
|
|
||||||
@ -30,10 +35,13 @@ New features
|
|||||||
------------
|
------------
|
||||||
|
|
||||||
* `AST::iter_functions` now returns `ScriptFnMetadata` which includes, among others, _doc-comments_ for functions prefixed by `///` or `/**`.
|
* `AST::iter_functions` now returns `ScriptFnMetadata` which includes, among others, _doc-comments_ for functions prefixed by `///` or `/**`.
|
||||||
|
* _Doc-comments_ can be enabled/disabled with the new `Engine::set_doc_comments` method.
|
||||||
|
* A new feature `metadata` is added that pulls in `serde_json` and enables `Engine::gen_fn_metadata_to_json` which exports the full list of functions metadata (including those inside an `AST`) in JSON format.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
* A functions lookup cache is added to make function call resolution faster.
|
||||||
* Capturing a constant variable in a closure is now supported, with no cloning.
|
* Capturing a constant variable in a closure is now supported, with no cloning.
|
||||||
* Provides position info for `debug` statements.
|
* Provides position info for `debug` statements.
|
||||||
* A _look-ahead_ symbol is provided to custom syntax parsers, which can be used to parse variable-length symbol streams.
|
* A _look-ahead_ symbol is provided to custom syntax parsers, which can be used to parse variable-length symbol streams.
|
||||||
|
@ -55,6 +55,7 @@ The Rhai Scripting Language
|
|||||||
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)
|
||||||
|
1. [Doc-Comments](language/doc-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. [Serialization/Deserialization with `serde`](rust/serde.md)
|
2. [Serialization/Deserialization with `serde`](rust/serde.md)
|
||||||
@ -79,14 +80,14 @@ The Rhai Scripting Language
|
|||||||
9. [If Statement](language/if.md)
|
9. [If Statement](language/if.md)
|
||||||
10. [Switch Expression](language/switch.md)
|
10. [Switch Expression](language/switch.md)
|
||||||
11. [While Loop](language/while.md)
|
11. [While Loop](language/while.md)
|
||||||
11. [Do Loop](language/do.md)
|
12. [Do Loop](language/do.md)
|
||||||
12. [Loop Statement](language/loop.md)
|
13. [Loop Statement](language/loop.md)
|
||||||
13. [For Loop](language/for.md)
|
14. [For Loop](language/for.md)
|
||||||
1. [Iterators for Custom Types](language/iterator.md)
|
1. [Iterators for Custom Types](language/iterator.md)
|
||||||
14. [Return Values](language/return.md)
|
15. [Return Values](language/return.md)
|
||||||
15. [Throw Exception on Error](language/throw.md)
|
16. [Throw Exception on Error](language/throw.md)
|
||||||
16. [Catch Exceptions](language/try-catch.md)
|
17. [Catch Exceptions](language/try-catch.md)
|
||||||
17. [Functions](language/functions.md)
|
18. [Functions](language/functions.md)
|
||||||
1. [Call Method as Function](language/method.md)
|
1. [Call Method as Function](language/method.md)
|
||||||
2. [Overloading](language/overload.md)
|
2. [Overloading](language/overload.md)
|
||||||
3. [Namespaces](language/fn-namespaces.md)
|
3. [Namespaces](language/fn-namespaces.md)
|
||||||
@ -94,11 +95,11 @@ The Rhai Scripting Language
|
|||||||
5. [Currying](language/fn-curry.md)
|
5. [Currying](language/fn-curry.md)
|
||||||
6. [Anonymous Functions](language/fn-anon.md)
|
6. [Anonymous Functions](language/fn-anon.md)
|
||||||
7. [Closures](language/fn-closure.md)
|
7. [Closures](language/fn-closure.md)
|
||||||
18. [Print and Debug](language/print-debug.md)
|
19. [Print and Debug](language/print-debug.md)
|
||||||
19. [Modules](language/modules/index.md)
|
20. [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)
|
||||||
20. [Eval Function](language/eval.md)
|
21. [Eval Function](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)
|
||||||
2. [Sand-Boxing](safety/sandbox.md)
|
2. [Sand-Boxing](safety/sandbox.md)
|
||||||
@ -136,7 +137,9 @@ The Rhai Scripting Language
|
|||||||
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)
|
||||||
5. [Multiple Instantiation](patterns/multiple.md)
|
5. [Multiple Instantiation](patterns/multiple.md)
|
||||||
6. [Get Function Signatures](engine/get_fn_sig.md)
|
6. [Functions Metadata](engine/metadata/index.md)
|
||||||
|
1. [Generate Function Signatures](engine/metadata/gen_fn_sig.md)
|
||||||
|
2. [Export Metadata to JSON](engine/metadata/export_to_json.md)
|
||||||
10. [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)
|
||||||
|
@ -27,7 +27,6 @@ Fast
|
|||||||
* Fairly low compile-time overhead.
|
* Fairly low compile-time overhead.
|
||||||
|
|
||||||
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
|
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
|
||||||
An unofficial Fibonacci benchmark puts Rhai somewhere between Wren and Python.
|
|
||||||
|
|
||||||
* Scripts are [optimized][script optimization] (useful for template-based machine-generated scripts) for repeated evaluations.
|
* Scripts are [optimized][script optimization] (useful for template-based machine-generated scripts) for repeated evaluations.
|
||||||
|
|
||||||
|
@ -27,8 +27,8 @@ It doesn't attempt to be a new language. For example:
|
|||||||
There is, however, support for simulated [closures] via [currying] a [function pointer] with
|
There is, however, support for simulated [closures] via [currying] a [function pointer] with
|
||||||
captured shared variables.
|
captured shared variables.
|
||||||
|
|
||||||
* **No byte-codes/JIT** - Rhai has an optimized AST-walking interpreter which is fast enough for most usage scenarios.
|
* **No byte-codes/JIT** - Rhai has an optimized AST-walking interpreter which is fast enough for most casual
|
||||||
Essential AST data structures are packed and kept together to maximize cache friendliness.
|
usage scenarios. Essential AST data structures are packed and kept together to maximize cache friendliness.
|
||||||
|
|
||||||
Functions are dispatched based on pre-calculated hashes and accessing variables are mostly through pre-calculated
|
Functions are dispatched based on pre-calculated hashes and accessing variables are mostly through pre-calculated
|
||||||
offsets to the variables file (a [`Scope`]), so it is seldom necessary to look something up by text name.
|
offsets to the variables file (a [`Scope`]), so it is seldom necessary to look something up by text name.
|
||||||
|
106
doc/src/engine/metadata/export_to_json.md
Normal file
106
doc/src/engine/metadata/export_to_json.md
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
Export Functions Metadata to JSON
|
||||||
|
================================
|
||||||
|
|
||||||
|
{{#include ../../links.md}}
|
||||||
|
|
||||||
|
|
||||||
|
`Engine::gen_fn_metadata_to_json`
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
As part of a _reflections_ API, `Engine::gen_fn_metadata_to_json` exports the full list
|
||||||
|
of [functions metadata] in JSON format.
|
||||||
|
|
||||||
|
The [`metadata`] feature must be used to turn on this method, which requires
|
||||||
|
the [`serde_json`](https://crates.io/crates/serde_json) crate.
|
||||||
|
|
||||||
|
### Sources
|
||||||
|
|
||||||
|
Functions from the following sources are included:
|
||||||
|
|
||||||
|
1) Script-defined functions in an [`AST`], if provided
|
||||||
|
2) Native Rust functions registered into the global namespace via the `Engine::register_XXX` API
|
||||||
|
3) _Public_ (i.e. non-[`private`]) functions (native Rust or Rhai scripted) in global sub-modules registered via
|
||||||
|
[`Engine::register_module`]({{rootUrl}}/rust/modules/create.md)
|
||||||
|
4) Native Rust functions in registered [packages] (optional)
|
||||||
|
|
||||||
|
Notice that if a function has been [overloaded][function overloading], only the overriding function's
|
||||||
|
metadata is included.
|
||||||
|
|
||||||
|
|
||||||
|
JSON Schema
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The JSON schema used to hold functions metadata is very simple, containing a nested structure of
|
||||||
|
`modules` and a list of `functions`.
|
||||||
|
|
||||||
|
### Modules Schema
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"modules":
|
||||||
|
{
|
||||||
|
"sub_module_1":
|
||||||
|
{
|
||||||
|
"modules":
|
||||||
|
{
|
||||||
|
"sub_sub_module_A":
|
||||||
|
{
|
||||||
|
"functions":
|
||||||
|
[
|
||||||
|
{ ... function metadata ... },
|
||||||
|
{ ... function metadata ... },
|
||||||
|
{ ... function metadata ... },
|
||||||
|
{ ... function metadata ... },
|
||||||
|
...
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sub_sub_module_B":
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sub_module_2":
|
||||||
|
{
|
||||||
|
...
|
||||||
|
},
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"functions":
|
||||||
|
[
|
||||||
|
{ ... function metadata ... },
|
||||||
|
{ ... function metadata ... },
|
||||||
|
{ ... function metadata ... },
|
||||||
|
{ ... function metadata ... },
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Function Metadata Schema
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"namespace": "internal" | "global",
|
||||||
|
"access": "public" | "private",
|
||||||
|
"name": "fn_name",
|
||||||
|
"type": "native" | "script",
|
||||||
|
"numParams": 42, /* number of parameters */
|
||||||
|
"params": /* omitted if no parameters */
|
||||||
|
[
|
||||||
|
{ "name": "param_1", "type": "type_1" },
|
||||||
|
{ "name": "param_2" }, /* no type info */
|
||||||
|
{ "name": "_", "type": "type_3" },
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"returnType": "ret_type", /* omitted if unknown */
|
||||||
|
"signature": "[private] fn_name(param_1: type_1, param_2, _: type_3) -> ret_type",
|
||||||
|
"docComments": /* omitted if none */
|
||||||
|
[
|
||||||
|
"/// doc-comment line 1",
|
||||||
|
"/// doc-comment line 2",
|
||||||
|
"/** doc-comment block */",
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
@ -1,26 +1,29 @@
|
|||||||
Get Function Signatures
|
Generate Function Signatures
|
||||||
=======================
|
===========================
|
||||||
|
|
||||||
{{#include ../links.md}}
|
{{#include ../../links.md}}
|
||||||
|
|
||||||
|
|
||||||
`Engine::gen_fn_signatures`
|
`Engine::gen_fn_signatures`
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
As part of a _reflections_ API, `Engine::gen_fn_signatures` returns a list of function signatures
|
As part of a _reflections_ API, `Engine::gen_fn_signatures` returns a list of function _signatures_
|
||||||
(`Vec<String>`), each corresponding to a particular function available to that [`Engine`] instance.
|
(as `Vec<String>`), each corresponding to a particular function available to that [`Engine`] instance.
|
||||||
|
|
||||||
|
> `fn_name ( param_1: type_1, param_2: type_2, ... , param_n : type_n ) -> return_type`
|
||||||
|
|
||||||
|
### Sources
|
||||||
|
|
||||||
Functions from the following sources are included, in order:
|
Functions from the following sources are included, in order:
|
||||||
|
|
||||||
1) Functions registered into the global namespace via the `Engine::register_XXX` API,
|
1) Native Rust functions registered into the global namespace via the `Engine::register_XXX` API
|
||||||
2) Functions in global sub-modules registered via [`Engine::register_module`]({{rootUrl}}/rust/modules/create.md),
|
2) _Public_ (i.e. non-[`private`]) functions (native Rust or Rhai scripted) in global sub-modules registered via
|
||||||
3) Functions in registered [packages] (optional)
|
[`Engine::register_module`]({{rootUrl}}/rust/modules/create.md)
|
||||||
|
3) Native Rust functions in registered [packages] (optional)
|
||||||
Included are both native Rust as well as script-defined functions (except [`private`] ones).
|
|
||||||
|
|
||||||
|
|
||||||
Function Metadata
|
Functions Metadata
|
||||||
-----------------
|
------------------
|
||||||
|
|
||||||
Beware, however, that not all function signatures contain parameters and return value information.
|
Beware, however, that not all function signatures contain parameters and return value information.
|
||||||
|
|
||||||
@ -30,7 +33,7 @@ For instance, functions registered via `Engine::register_XXX` contain no informa
|
|||||||
the names of parameter and their actual types because Rust simply does not make such metadata
|
the names of parameter and their actual types because Rust simply does not make such metadata
|
||||||
available natively. The return type is also undetermined.
|
available natively. The return type is also undetermined.
|
||||||
|
|
||||||
A function registered under the name 'foo' with three parameters and unknown return type:
|
A function registered under the name `foo` with three parameters and unknown return type:
|
||||||
|
|
||||||
> `foo(_, _, _)`
|
> `foo(_, _, _)`
|
||||||
|
|
||||||
@ -41,18 +44,18 @@ Notice that function names do not need to be valid identifiers.
|
|||||||
|
|
||||||
A [property setter][getters/setters] - again, unknown parameters and return type.
|
A [property setter][getters/setters] - again, unknown parameters and return type.
|
||||||
Notice that function names do not need to be valid identifiers.
|
Notice that function names do not need to be valid identifiers.
|
||||||
In this case, the first parameter should be '&mut T' of the custom type and the return value is '()':
|
In this case, the first parameter should be `&mut T` of the custom type and the return value is `()`:
|
||||||
|
|
||||||
> `set$prop(_, _, _)`
|
> `set$prop(_, _, _)`
|
||||||
|
|
||||||
### Script-defined functions
|
### Script-Defined Functions
|
||||||
|
|
||||||
Script-defined [function] signatures contain parameter names. Since all parameters, as well as
|
Script-defined [function] signatures contain parameter names. Since all parameters, as well as
|
||||||
the return value, are [`Dynamic`] the types are simply not shown.
|
the return value, are [`Dynamic`] the types are simply not shown.
|
||||||
|
|
||||||
A script-defined function always takes dynamic arguments, and the return type is also dynamic:
|
A script-defined function always takes dynamic arguments, and the return type is also dynamic:
|
||||||
|
|
||||||
> `foo(x, y, z)`
|
> `foo(x, y, z) -> Dynamic`
|
||||||
|
|
||||||
probably defined as:
|
probably defined as:
|
||||||
|
|
||||||
@ -66,22 +69,22 @@ is the same as:
|
|||||||
|
|
||||||
> `foo(x: Dynamic, y: Dynamic, z: Dynamic) -> Result<Dynamic, Box<EvalAltResult>>`
|
> `foo(x: Dynamic, y: Dynamic, z: Dynamic) -> Result<Dynamic, Box<EvalAltResult>>`
|
||||||
|
|
||||||
### Plugin functions
|
### Plugin Functions
|
||||||
|
|
||||||
Functions defined in [plugin modules] are the best. They contain all the metadata
|
Functions defined in [plugin modules] are the best. They contain all the metadata
|
||||||
describing the functions.
|
describing the functions.
|
||||||
|
|
||||||
A plugin function `merge`:
|
For example, a plugin function `merge`:
|
||||||
|
|
||||||
> `merge(list: &mut MyStruct<i64>, num: usize, name: &str) -> Option<bool>`
|
> `merge(list: &mut MyStruct<i64>, num: usize, name: &str) -> Option<bool>`
|
||||||
|
|
||||||
Notice that function names do not need to be valid identifiers.
|
Notice that function names do not need to be valid identifiers.
|
||||||
|
|
||||||
An operator defined as a [fallible function] in a [plugin module] via `#[rhai_fn(name="+=", return_raw)]`
|
For example, an operator defined as a [fallible function] in a [plugin module] via
|
||||||
returns `Result<bool, Box<EvalAltResult>>`:
|
`#[rhai_fn(name="+=", return_raw)]` returns `Result<bool, Box<EvalAltResult>>`:
|
||||||
|
|
||||||
> `+=(list: &mut MyStruct<i64>, num: usize, name: &str) -> Result<bool, Box<EvalAltResult>>`
|
> `+=(list: &mut MyStruct<i64>, num: usize, name: &str) -> Result<bool, Box<EvalAltResult>>`
|
||||||
|
|
||||||
A [property getter][getters/setters] defined in a [plugin module]:
|
For example, a [property getter][getters/setters] defined in a [plugin module]:
|
||||||
|
|
||||||
> `get$prop(obj: &mut MyStruct<i64>) -> String`
|
> `get$prop(obj: &mut MyStruct<i64>) -> String`
|
28
doc/src/engine/metadata/index.md
Normal file
28
doc/src/engine/metadata/index.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
Functions Metadata
|
||||||
|
==================
|
||||||
|
|
||||||
|
{{#include ../../links.md}}
|
||||||
|
|
||||||
|
The _metadata_ of a [function] means all relevant information related to a function's
|
||||||
|
definition including:
|
||||||
|
|
||||||
|
1. Its callable name
|
||||||
|
|
||||||
|
2. Its access mode (public or [private][`private`])
|
||||||
|
|
||||||
|
3. Its parameters and types (if any)
|
||||||
|
|
||||||
|
4. Its return value and type (if any)
|
||||||
|
|
||||||
|
5. Its nature (i.e. native Rust-based or Rhai script-based)
|
||||||
|
|
||||||
|
6. Its [namespace][function namespace] (module or global)
|
||||||
|
|
||||||
|
7. Its purpose, in the form of [doc-comments]
|
||||||
|
|
||||||
|
8. Usage notes, warnings, etc., in the form of [doc-comments]
|
||||||
|
|
||||||
|
A function's _signature_ encapsulates the first four pieces of information in a single
|
||||||
|
concise line of definition:
|
||||||
|
|
||||||
|
> `[private] fn_name ( param_1: type_1, param_2: type_2, ... , param_n : type_n ) -> return_type`
|
@ -6,13 +6,14 @@ Engine Configuration Options
|
|||||||
A number of other configuration options are available from the `Engine` to fine-tune behavior and safeguards.
|
A number of other configuration options are available from the `Engine` to fine-tune behavior and safeguards.
|
||||||
|
|
||||||
| Method | Not available under | Description |
|
| Method | Not available under | Description |
|
||||||
| ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------------ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `set_optimization_level` | [`no_optimize`] | sets the amount of script _optimizations_ performed. See [script optimization]. |
|
| `set_doc_comments` | | enables/disables [doc-comments] |
|
||||||
| `set_max_expr_depths` | [`unchecked`] | sets the maximum nesting levels of an expression/statement. See [maximum statement depth]. |
|
| `set_optimization_level` | [`no_optimize`] | sets the amount of script _optimizations_ performedSee [script optimization] |
|
||||||
| `set_max_call_levels` | [`unchecked`] | sets the maximum number of function call levels (default 50) to avoid infinite recursion. See [maximum call stack depth]. |
|
| `set_max_expr_depths` | [`unchecked`] | sets the maximum nesting levels of an expression/statementSee [maximum statement depth] |
|
||||||
| `set_max_operations` | [`unchecked`] | sets the maximum number of _operations_ that a script is allowed to consume. See [maximum number of operations]. |
|
| `set_max_call_levels` | [`unchecked`] | sets the maximum number of function call levels (default 50) to avoid infinite recursionSee [maximum call stack depth] |
|
||||||
| `set_max_modules` | [`unchecked`] | sets the maximum number of [modules] that a script is allowed to load. See [maximum number of modules]. |
|
| `set_max_operations` | [`unchecked`] | sets the maximum number of _operations_ that a script is allowed to consumeSee [maximum number of operations] |
|
||||||
| `set_max_string_size` | [`unchecked`] | sets the maximum length (in UTF-8 bytes) for [strings]. See [maximum length of strings]. |
|
| `set_max_modules` | [`unchecked`] | sets the maximum number of [modules] that a script is allowed to loadSee [maximum number of modules] |
|
||||||
| `set_max_array_size` | [`unchecked`], [`no_index`] | sets the maximum size for [arrays]. See [maximum size of arrays]. |
|
| `set_max_string_size` | [`unchecked`] | sets the maximum length (in UTF-8 bytes) for [strings]See [maximum length of strings] |
|
||||||
| `set_max_map_size` | [`unchecked`], [`no_object`] | sets the maximum number of properties for [object maps]. See [maximum size of object maps]. |
|
| `set_max_array_size` | [`unchecked`], [`no_index`] | sets the maximum size for [arrays]See [maximum size of arrays] |
|
||||||
| `disable_symbol` | | disables a certain keyword or operator. See [disable keywords and operators]. |
|
| `set_max_map_size` | [`unchecked`], [`no_object`] | sets the maximum number of properties for [object maps]See [maximum size of object maps] |
|
||||||
|
| `disable_symbol` | | disables a certain keyword or operatorSee [disable keywords and operators] |
|
||||||
|
@ -22,43 +22,3 @@ let /* intruder comment */ name = "Bob";
|
|||||||
/*/*/*/*/**/*/*/*/*/
|
/*/*/*/*/**/*/*/*/*/
|
||||||
*/
|
*/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Doc-Comments
|
|
||||||
------------
|
|
||||||
|
|
||||||
Similar to Rust, comments starting with `///` (three slashes) or `/**` (two asterisks) are
|
|
||||||
_doc-comments_.
|
|
||||||
|
|
||||||
Doc-comments can only appear in front of [function] definitions, not any other elements:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
/// This is a valid one-line doc-comment
|
|
||||||
fn foo() {}
|
|
||||||
|
|
||||||
/** This is a
|
|
||||||
** valid block
|
|
||||||
** doc-comment
|
|
||||||
**/
|
|
||||||
fn bar(x) {
|
|
||||||
/// Syntax error - this doc-comment is invalid
|
|
||||||
x + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Syntax error - this doc-comment is invalid */
|
|
||||||
let x = 42;
|
|
||||||
|
|
||||||
/// Syntax error - this doc-comment is also invalid
|
|
||||||
{
|
|
||||||
let x = 42;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Doc-comments are stored within the script's [`AST`] after compilation.
|
|
||||||
|
|
||||||
The `AST::iter_functions` method provides a `ScriptFnMetadata` instance
|
|
||||||
for each function defined within the script, which includes doc-comments.
|
|
||||||
|
|
||||||
Doc-comments never affect the evaluation of a script nor do they incur
|
|
||||||
significant performance overhead. However, third party tools can take advantage
|
|
||||||
of this information to auto-generate documentation for Rhai script functions.
|
|
||||||
|
76
doc/src/language/doc-comments.md
Normal file
76
doc/src/language/doc-comments.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
Doc-Comments
|
||||||
|
============
|
||||||
|
|
||||||
|
Similar to Rust, comments starting with `///` (three slashes) or `/**` (two asterisks) are
|
||||||
|
_doc-comments_.
|
||||||
|
|
||||||
|
Doc-comments can only appear in front of [function] definitions, not any other elements:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// This is a valid one-line doc-comment
|
||||||
|
fn foo() {}
|
||||||
|
|
||||||
|
/** This is a
|
||||||
|
** valid block
|
||||||
|
** doc-comment
|
||||||
|
**/
|
||||||
|
fn bar(x) {
|
||||||
|
/// Syntax error - this doc-comment is invalid
|
||||||
|
x + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Syntax error - this doc-comment is invalid */
|
||||||
|
let x = 42;
|
||||||
|
|
||||||
|
/// Syntax error - this doc-comment is also invalid
|
||||||
|
{
|
||||||
|
let x = 42;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Special Cases
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Long streams of `//////...` and `/*****...` do _NOT_ form doc-comments.
|
||||||
|
This is consistent with popular comment block styles for C-like languages.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/////////////////////////////// <- this is not a doc-comment
|
||||||
|
// This is not a doc-comment // <- this is a normal comment
|
||||||
|
/////////////////////////////// <- this is not a doc-comment
|
||||||
|
|
||||||
|
// However, watch out for comment lines starting with '///'
|
||||||
|
|
||||||
|
////////////////////////////////////////// <- this is not a doc-comment
|
||||||
|
/// This, however, IS a doc-comment!!! /// <- this starts with '///'
|
||||||
|
////////////////////////////////////////// <- this is not a doc-comment
|
||||||
|
|
||||||
|
/****************************************
|
||||||
|
* *
|
||||||
|
* This is also not a doc-comment block *
|
||||||
|
* so we don't have to put this in *
|
||||||
|
* front of a function. *
|
||||||
|
* *
|
||||||
|
****************************************/
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Using Doc-Comments
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Doc-comments are stored within the script's [`AST`] after compilation.
|
||||||
|
|
||||||
|
The `AST::iter_functions` method provides a `ScriptFnMetadata` instance
|
||||||
|
for each function defined within the script, which includes doc-comments.
|
||||||
|
|
||||||
|
Doc-comments never affect the evaluation of a script nor do they incur
|
||||||
|
significant performance overhead. However, third party tools can take advantage
|
||||||
|
of this information to auto-generate documentation for Rhai script functions.
|
||||||
|
|
||||||
|
|
||||||
|
Disabling Doc-Comments
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Doc-comments can be disabled via the `Engine::set_doc_comments` method.
|
@ -27,7 +27,7 @@ engine.on_print(|x| println!("hello: {}", x));
|
|||||||
|
|
||||||
// Any function or closure that takes a '&str' and a 'Position' argument can be used to
|
// Any function or closure that takes a '&str' and a 'Position' argument can be used to
|
||||||
// override 'debug'.
|
// override 'debug'.
|
||||||
engine.on_debug(|x, pos| println!("DEBUG at {:?}: {}", pos, x));
|
engine.on_debug(|x, src, pos| println!("DEBUG of {} at {:?}: {}", src.unwrap_or("unknown"), pos, x));
|
||||||
|
|
||||||
// Example: quick-'n-dirty logging
|
// Example: quick-'n-dirty logging
|
||||||
let logbook = Arc::new(RwLock::new(Vec::<String>::new()));
|
let logbook = Arc::new(RwLock::new(Vec::<String>::new()));
|
||||||
@ -37,8 +37,8 @@ let log = logbook.clone();
|
|||||||
engine.on_print(move |s| log.write().unwrap().push(format!("entry: {}", s)));
|
engine.on_print(move |s| log.write().unwrap().push(format!("entry: {}", s)));
|
||||||
|
|
||||||
let log = logbook.clone();
|
let log = logbook.clone();
|
||||||
engine.on_debug(move |s, pos| log.write().unwrap().push(
|
engine.on_debug(move |s, src, pos| log.write().unwrap().push(
|
||||||
format!("DEBUG at {:?}: {}", pos, s)
|
format!("DEBUG of {} at {:?}: {}", src.unwrap_or("unknown"), pos, s)
|
||||||
));
|
));
|
||||||
|
|
||||||
// Evaluate script
|
// Evaluate script
|
||||||
@ -49,3 +49,24 @@ for entry in logbook.read().unwrap().iter() {
|
|||||||
println!("{}", entry);
|
println!("{}", entry);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
`on_debug` Callback Signature
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
The function signature passed to `Engine::on_debug` takes the following form:
|
||||||
|
|
||||||
|
> `Fn(text: &str, source: Option<&str>, pos: Position) + 'static`
|
||||||
|
|
||||||
|
where:
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --------- | :------------: | --------------------------------------------------------------- |
|
||||||
|
| `text` | `&str` | text to display |
|
||||||
|
| `source` | `Option<&str>` | source of the current evaluation, if any |
|
||||||
|
| `pos` | `Position` | position (line number and character offset) of the `debug` call |
|
||||||
|
|
||||||
|
The _source_ of a script evaluation is any text string provided to an [`AST`] via the `AST::set_source` method.
|
||||||
|
|
||||||
|
If a [module] is loaded via an [`import`] statement, then the _source_ of functions defined
|
||||||
|
within the module will be the module's _path_.
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
[`no_closure`]: {{rootUrl}}/start/features.md
|
[`no_closure`]: {{rootUrl}}/start/features.md
|
||||||
[`no_std`]: {{rootUrl}}/start/features.md
|
[`no_std`]: {{rootUrl}}/start/features.md
|
||||||
[`no-std`]: {{rootUrl}}/start/features.md
|
[`no-std`]: {{rootUrl}}/start/features.md
|
||||||
|
[`metadata`]: {{rootUrl}}/start/features.md
|
||||||
[`internals`]: {{rootUrl}}/start/features.md
|
[`internals`]: {{rootUrl}}/start/features.md
|
||||||
[`unicode-xid-ident`]: {{rootUrl}}/start/features.md
|
[`unicode-xid-ident`]: {{rootUrl}}/start/features.md
|
||||||
|
|
||||||
@ -40,6 +41,7 @@
|
|||||||
[plugin modules]: {{rootUrl}}/plugins/module.md
|
[plugin modules]: {{rootUrl}}/plugins/module.md
|
||||||
[plugin function]: {{rootUrl}}/plugins/function.md
|
[plugin function]: {{rootUrl}}/plugins/function.md
|
||||||
[plugin functions]: {{rootUrl}}/plugins/function.md
|
[plugin functions]: {{rootUrl}}/plugins/function.md
|
||||||
|
[functions metadata]: {{rootUrl}}/engine/metadata/index.md
|
||||||
[`Scope`]: {{rootUrl}}/engine/scope.md
|
[`Scope`]: {{rootUrl}}/engine/scope.md
|
||||||
[`serde`]: {{rootUrl}}/rust/serde.md
|
[`serde`]: {{rootUrl}}/rust/serde.md
|
||||||
|
|
||||||
@ -89,6 +91,7 @@
|
|||||||
[timestamp]: {{rootUrl}}/language/timestamps.md
|
[timestamp]: {{rootUrl}}/language/timestamps.md
|
||||||
[timestamps]: {{rootUrl}}/language/timestamps.md
|
[timestamps]: {{rootUrl}}/language/timestamps.md
|
||||||
|
|
||||||
|
[doc-comments]: {{rootUrl}}/language/doc-comments.md
|
||||||
[function]: {{rootUrl}}/language/functions.md
|
[function]: {{rootUrl}}/language/functions.md
|
||||||
[functions]: {{rootUrl}}/language/functions.md
|
[functions]: {{rootUrl}}/language/functions.md
|
||||||
[function overloading]: {{rootUrl}}/rust/functions.md#function-overloading
|
[function overloading]: {{rootUrl}}/rust/functions.md#function-overloading
|
||||||
|
@ -178,22 +178,21 @@ Use `switch` Through Arrays
|
|||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
Another way to work with Rust enums in a `switch` expression is through exposing the internal data
|
Another way to work with Rust enums in a `switch` expression is through exposing the internal data
|
||||||
of each enum variant as a variable-length [array], usually with the name of the variant as
|
(or at least those that act as effective _discriminants_) of each enum variant as a variable-length
|
||||||
the first item for convenience:
|
[array], usually with the name of the variant as the first item for convenience:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::Array;
|
use rhai::Array;
|
||||||
|
|
||||||
engine.register_get("enum_data", |x: &mut Enum| {
|
engine.register_get("enum_data", |x: &mut Enum| {
|
||||||
match x {
|
match x {
|
||||||
Enum::Foo => vec![
|
Enum::Foo => vec![ "Foo".into() ] as Array,
|
||||||
"Foo".into()
|
|
||||||
] as Array,
|
|
||||||
|
|
||||||
Enum::Bar(value) => vec![
|
// Say, skip the data field because it is not
|
||||||
"Bar".into(), (*value).into()
|
// used as a discriminant
|
||||||
] as Array,
|
Enum::Bar(value) => vec![ "Bar".into() ] as Array,
|
||||||
|
|
||||||
|
// Say, all fields act as discriminants
|
||||||
Enum::Baz(val1, val2) => vec![
|
Enum::Baz(val1, val2) => vec![
|
||||||
"Baz".into(), val1.clone().into(), (*val2).into()
|
"Baz".into(), val1.clone().into(), (*val2).into()
|
||||||
] as Array
|
] as Array
|
||||||
@ -208,8 +207,7 @@ Then it is a simple matter to match an enum via the `switch` expression:
|
|||||||
// 'enum_data' creates a variable-length array with 'MyEnum' data
|
// 'enum_data' creates a variable-length array with 'MyEnum' data
|
||||||
let x = switch value.enum_data {
|
let x = switch value.enum_data {
|
||||||
["Foo"] => 1,
|
["Foo"] => 1,
|
||||||
["Bar", 42] => 2,
|
["Bar"] => value.field_1,
|
||||||
["Bar", 123] => 3,
|
|
||||||
["Baz", "hello", false] => 4,
|
["Baz", "hello", false] => 4,
|
||||||
["Baz", "hello", true] => 5,
|
["Baz", "hello", true] => 5,
|
||||||
_ => 9
|
_ => 9
|
||||||
@ -220,10 +218,20 @@ x == 5;
|
|||||||
// Which is essentially the same as:
|
// Which is essentially the same as:
|
||||||
let x = switch [value.type, value.field_0, value.field_1] {
|
let x = switch [value.type, value.field_0, value.field_1] {
|
||||||
["Foo", (), ()] => 1,
|
["Foo", (), ()] => 1,
|
||||||
["Bar", 42, ()] => 2,
|
["Bar", 42, ()] => 42,
|
||||||
["Bar", 123, ()] => 3,
|
["Bar", 123, ()] => 123,
|
||||||
|
:
|
||||||
["Baz", "hello", false] => 4,
|
["Baz", "hello", false] => 4,
|
||||||
["Baz", "hello", true] => 5,
|
["Baz", "hello", true] => 5,
|
||||||
_ => 9
|
_ => 9
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Usually, a helper method returns an array of values that can uniquely determine
|
||||||
|
the switch case based on actual usage requirements - which means that it probably
|
||||||
|
skips fields that contain data instead of discriminants.
|
||||||
|
|
||||||
|
Then `switch` is used to very quickly match through a large number of array shapes
|
||||||
|
and jump to the appropriate case implementation.
|
||||||
|
|
||||||
|
Data fields can then be extracted from the enum independently.
|
||||||
|
@ -26,10 +26,10 @@ Use Closures to Define Methods
|
|||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
[Anonymous functions] or [closures] defined as values for [object map] properties take on
|
[Anonymous functions] or [closures] defined as values for [object map] properties take on
|
||||||
a syntactic shape that resembles very closely that of class methods in an OOP language.
|
a syntactic shape which resembles very closely that of class methods in an OOP language.
|
||||||
|
|
||||||
Closures also _[capture][automatic currying]_ variables from the defining environment, which is a very
|
Closures also _[capture][automatic currying]_ variables from the defining environment, which is a very
|
||||||
common OOP pattern. Capturing is accomplished via a feature called _[automatic currying]_ and
|
common language feature. Capturing is accomplished via a feature called _[automatic currying]_ and
|
||||||
can be turned off via the [`no_closure`] feature.
|
can be turned off via the [`no_closure`] feature.
|
||||||
|
|
||||||
|
|
||||||
@ -59,3 +59,51 @@ factor = 2;
|
|||||||
obj.update(42);
|
obj.update(42);
|
||||||
obj.action(); // prints 84
|
obj.action(); // prints 84
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Simulating Inheritance With Mixin
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
The `fill_with` method of [object maps] can be conveniently used to _polyfill_ default
|
||||||
|
method implementations from a _base class_, as per OOP lingo.
|
||||||
|
|
||||||
|
Do not use the `mixin` method because it _overwrites_ existing fields.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Define base class
|
||||||
|
let BaseClass = #{
|
||||||
|
factor: 1,
|
||||||
|
data: 42,
|
||||||
|
|
||||||
|
get_data: || this.data * 2,
|
||||||
|
update: |x| this.data += x * this.factor
|
||||||
|
};
|
||||||
|
|
||||||
|
let obj = #{
|
||||||
|
// Override base class field
|
||||||
|
factor: 100,
|
||||||
|
|
||||||
|
// Override base class method
|
||||||
|
// Notice that the base class can also be accessed, if in scope
|
||||||
|
get_data: || this.call(BaseClass.get_data) * 999,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill missing fields/methods
|
||||||
|
obj.fill_with(BaseClass);
|
||||||
|
|
||||||
|
// By this point, 'obj' has the following:
|
||||||
|
//
|
||||||
|
// #{
|
||||||
|
// factor: 100
|
||||||
|
// data: 42,
|
||||||
|
// get_data: || this.call(BaseClass.get_data) * 999,
|
||||||
|
// update: |x| this.data += x * this.factor
|
||||||
|
// }
|
||||||
|
|
||||||
|
// obj.get_data() => (this.data (42) * 2) * 999
|
||||||
|
obj.get_data() == 83916;
|
||||||
|
|
||||||
|
obj.update(1);
|
||||||
|
|
||||||
|
obj.data == 142
|
||||||
|
```
|
||||||
|
@ -12,7 +12,7 @@ Excluding unneeded functionalities can result in smaller, faster builds as well
|
|||||||
more control over what a script can (or cannot) do.
|
more control over what a script can (or cannot) do.
|
||||||
|
|
||||||
| Feature | Additive? | Description |
|
| Feature | Additive? | Description |
|
||||||
| ------------------- | :-------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `unchecked` | no | disables arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.<br/>Beware that a bad script may panic the entire system! |
|
| `unchecked` | no | disables arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.<br/>Beware that a bad script may panic the entire system! |
|
||||||
| `sync` | no | restricts all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync` |
|
| `sync` | no | restricts all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync` |
|
||||||
| `no_optimize` | no | disables [script optimization] |
|
| `no_optimize` | no | disables [script optimization] |
|
||||||
@ -22,12 +22,13 @@ more control over what a script can (or cannot) do.
|
|||||||
| `only_i64` | no | sets the system integer type to `i64` and disable all other integer types. `INT` is set to `i64` |
|
| `only_i64` | no | sets the system integer type to `i64` and disable all other integer types. `INT` is set to `i64` |
|
||||||
| `no_index` | no | disables [arrays] and indexing features |
|
| `no_index` | no | disables [arrays] and indexing features |
|
||||||
| `no_object` | no | disables support for [custom types] and [object maps] |
|
| `no_object` | no | disables support for [custom types] and [object maps] |
|
||||||
| `no_function` | no | disables script-defined [functions] |
|
| `no_function` | no | disables script-defined [functions] (implies `no_closure`) |
|
||||||
| `no_module` | no | disables loading external [modules] |
|
| `no_module` | no | disables loading external [modules] |
|
||||||
| `no_closure` | no | disables [capturing][automatic currying] external variables in [anonymous functions] to simulate _closures_, or [capturing the calling scope]({{rootUrl}}/language/fn-capture.md) in function calls |
|
| `no_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` (requires the [`serde`](https://crates.io/crates/serde) crate) |
|
||||||
| `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 |
|
||||||
|
| `metadata` | yes | enables exporting [functions metadata] to [JSON format]({{rootUrl}}/engine/metadata/export_to_json.md) (implies `serde` and additionally requires the [`serde_json`](https://crates.io/crates/serde_json) crate) |
|
||||||
| `internals` | yes | exposes internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version |
|
| `internals` | yes | exposes internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version |
|
||||||
|
|
||||||
|
|
||||||
|
@ -147,6 +147,15 @@ fn main() {
|
|||||||
println!();
|
println!();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// "json" => {
|
||||||
|
// println!(
|
||||||
|
// "{}",
|
||||||
|
// engine
|
||||||
|
// .gen_fn_metadata_to_json(Some(&main_ast), false)
|
||||||
|
// .unwrap()
|
||||||
|
// );
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
48
src/ast.rs
48
src/ast.rs
@ -163,6 +163,8 @@ impl<'a> Into<ScriptFnMetadata<'a>> for &'a ScriptFnDef {
|
|||||||
/// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
|
/// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AST {
|
pub struct AST {
|
||||||
|
/// Source of the [`AST`].
|
||||||
|
source: Option<ImmutableString>,
|
||||||
/// Global statements.
|
/// Global statements.
|
||||||
statements: Vec<Stmt>,
|
statements: Vec<Stmt>,
|
||||||
/// Script-defined functions.
|
/// Script-defined functions.
|
||||||
@ -172,6 +174,7 @@ pub struct AST {
|
|||||||
impl Default for AST {
|
impl Default for AST {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
source: None,
|
||||||
statements: Vec::with_capacity(16),
|
statements: Vec::with_capacity(16),
|
||||||
functions: Default::default(),
|
functions: Default::default(),
|
||||||
}
|
}
|
||||||
@ -186,10 +189,36 @@ impl AST {
|
|||||||
functions: impl Into<Shared<Module>>,
|
functions: impl Into<Shared<Module>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
source: None,
|
||||||
statements: statements.into_iter().collect(),
|
statements: statements.into_iter().collect(),
|
||||||
functions: functions.into(),
|
functions: functions.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Create a new [`AST`] with a source name.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn new_with_source(
|
||||||
|
statements: impl IntoIterator<Item = Stmt>,
|
||||||
|
functions: impl Into<Shared<Module>>,
|
||||||
|
source: impl Into<ImmutableString>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
source: Some(source.into()),
|
||||||
|
statements: statements.into_iter().collect(),
|
||||||
|
functions: functions.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Get the source.
|
||||||
|
pub fn source(&self) -> Option<&str> {
|
||||||
|
self.source.as_ref().map(|s| s.as_str())
|
||||||
|
}
|
||||||
|
/// Clone the source.
|
||||||
|
pub(crate) fn clone_source(&self) -> Option<ImmutableString> {
|
||||||
|
self.source.clone()
|
||||||
|
}
|
||||||
|
/// Set the source.
|
||||||
|
pub fn set_source<S: Into<ImmutableString>>(&mut self, source: Option<S>) {
|
||||||
|
self.source = source.map(|s| s.into())
|
||||||
|
}
|
||||||
/// Get the statements.
|
/// Get the statements.
|
||||||
#[cfg(not(feature = "internals"))]
|
#[cfg(not(feature = "internals"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -253,6 +282,7 @@ impl AST {
|
|||||||
let mut functions: Module = Default::default();
|
let mut functions: Module = Default::default();
|
||||||
functions.merge_filtered(&self.functions, &mut filter);
|
functions.merge_filtered(&self.functions, &mut filter);
|
||||||
Self {
|
Self {
|
||||||
|
source: self.source.clone(),
|
||||||
statements: Default::default(),
|
statements: Default::default(),
|
||||||
functions: functions.into(),
|
functions: functions.into(),
|
||||||
}
|
}
|
||||||
@ -262,6 +292,7 @@ impl AST {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn clone_statements_only(&self) -> Self {
|
pub fn clone_statements_only(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
source: self.source.clone(),
|
||||||
statements: self.statements.clone(),
|
statements: self.statements.clone(),
|
||||||
functions: Default::default(),
|
functions: Default::default(),
|
||||||
}
|
}
|
||||||
@ -432,6 +463,7 @@ impl AST {
|
|||||||
let Self {
|
let Self {
|
||||||
statements,
|
statements,
|
||||||
functions,
|
functions,
|
||||||
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let ast = match (statements.is_empty(), other.statements.is_empty()) {
|
let ast = match (statements.is_empty(), other.statements.is_empty()) {
|
||||||
@ -445,11 +477,21 @@ impl AST {
|
|||||||
(true, true) => vec![],
|
(true, true) => vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let source = if other.source.is_some() {
|
||||||
|
other.source.clone()
|
||||||
|
} else {
|
||||||
|
self.source.clone()
|
||||||
|
};
|
||||||
|
|
||||||
let mut functions = functions.as_ref().clone();
|
let mut functions = functions.as_ref().clone();
|
||||||
functions.merge_filtered(&other.functions, &mut filter);
|
functions.merge_filtered(&other.functions, &mut filter);
|
||||||
|
|
||||||
|
if let Some(source) = source {
|
||||||
|
Self::new_with_source(ast, functions, source)
|
||||||
|
} else {
|
||||||
Self::new(ast, functions)
|
Self::new(ast, functions)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/// Combine one [`AST`] with another. The second [`AST`] is consumed.
|
/// Combine one [`AST`] with another. The second [`AST`] is consumed.
|
||||||
///
|
///
|
||||||
/// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_.
|
/// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_.
|
||||||
@ -1191,12 +1233,16 @@ impl Expr {
|
|||||||
| Self::Map(_, _) => match token {
|
| Self::Map(_, _) => match token {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Token::LeftBracket => true,
|
Token::LeftBracket => true,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Token::Period => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
|
||||||
Self::Variable(_) => match token {
|
Self::Variable(_) => match token {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Token::LeftBracket => true,
|
Token::LeftBracket => true,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Token::Period => true,
|
||||||
Token::LeftParen => true,
|
Token::LeftParen => true,
|
||||||
Token::Bang => true,
|
Token::Bang => true,
|
||||||
Token::DoubleColon => true,
|
Token::DoubleColon => true,
|
||||||
@ -1206,6 +1252,8 @@ impl Expr {
|
|||||||
Self::Property(_) => match token {
|
Self::Property(_) => match token {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Token::LeftBracket => true,
|
Token::LeftBracket => true,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Token::Period => true,
|
||||||
Token::LeftParen => true,
|
Token::LeftParen => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
124
src/engine.rs
124
src/engine.rs
@ -24,7 +24,7 @@ use crate::stdlib::{
|
|||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
};
|
};
|
||||||
use crate::syntax::CustomSyntax;
|
use crate::syntax::CustomSyntax;
|
||||||
use crate::utils::get_hasher;
|
use crate::utils::{get_hasher, StraightHasherBuilder};
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_native_fn_hash, Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, Scope,
|
calc_native_fn_hash, Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, Scope,
|
||||||
Shared, StaticVec,
|
Shared, StaticVec,
|
||||||
@ -100,19 +100,21 @@ impl Imports {
|
|||||||
}
|
}
|
||||||
/// Get an iterator to this stack of imported [modules][Module] in reverse order.
|
/// Get an iterator to this stack of imported [modules][Module] in reverse order.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (ImmutableString, Shared<Module>)> + 'a {
|
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a str, &'a Module)> + 'a {
|
||||||
self.0.iter().flat_map(|lib| {
|
self.0.iter().flat_map(|lib| {
|
||||||
lib.iter()
|
lib.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.map(|(name, module)| (name.clone(), module.clone()))
|
.map(|(name, module)| (name.as_str(), module.as_ref()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/// Get an iterator to this stack of imported [modules][Module] in reverse order.
|
/// Get an iterator to this stack of imported [modules][Module] in reverse order.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn iter_raw<'a>(
|
pub(crate) fn iter_raw<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
) -> impl Iterator<Item = (ImmutableString, Shared<Module>)> + 'a {
|
) -> impl Iterator<Item = (&'a ImmutableString, &'a Shared<Module>)> + 'a {
|
||||||
self.0.iter().flat_map(|lib| lib.iter().rev().cloned())
|
self.0
|
||||||
|
.iter()
|
||||||
|
.flat_map(|lib| lib.iter().rev().map(|(n, m)| (n, m)))
|
||||||
}
|
}
|
||||||
/// Get a consuming iterator to this stack of imported [modules][Module] in reverse order.
|
/// Get a consuming iterator to this stack of imported [modules][Module] in reverse order.
|
||||||
pub fn into_iter(self) -> impl Iterator<Item = (ImmutableString, Shared<Module>)> {
|
pub fn into_iter(self) -> impl Iterator<Item = (ImmutableString, Shared<Module>)> {
|
||||||
@ -125,16 +127,24 @@ impl Imports {
|
|||||||
/// Does the specified function hash key exist in this stack of imported [modules][Module]?
|
/// Does the specified function hash key exist in this stack of imported [modules][Module]?
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn contains_fn(&self, hash: u64) -> bool {
|
pub fn contains_fn(&self, hash: u64) -> bool {
|
||||||
|
if hash == 0 {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
self.0.as_ref().map_or(false, |x| {
|
self.0.as_ref().map_or(false, |x| {
|
||||||
x.iter().any(|(_, m)| m.contains_qualified_fn(hash))
|
x.iter().any(|(_, m)| m.contains_qualified_fn(hash))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/// Get specified function via its hash key.
|
/// Get specified function via its hash key.
|
||||||
pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
|
pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
|
||||||
|
if hash == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
self.0
|
self.0
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|x| x.iter().rev().find_map(|(_, m)| m.get_qualified_fn(hash)))
|
.and_then(|x| x.iter().rev().find_map(|(_, m)| m.get_qualified_fn(hash)))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/// Does the specified [`TypeId`][std::any::TypeId] iterator exist in this stack of imported [modules][Module]?
|
/// Does the specified [`TypeId`][std::any::TypeId] iterator exist in this stack of imported [modules][Module]?
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn contains_iter(&self, id: TypeId) -> bool {
|
pub fn contains_iter(&self, id: TypeId) -> bool {
|
||||||
@ -368,29 +378,29 @@ impl<'a> Target<'a> {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Self::StringChar(_, _, ch) => {
|
Self::StringChar(_, _, ch) => {
|
||||||
let char_value = ch.clone();
|
let char_value = ch.clone();
|
||||||
self.set_value((char_value, Position::NONE)).unwrap();
|
self.set_value(char_value, Position::NONE).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Update the value of the `Target`.
|
/// Update the value of the `Target`.
|
||||||
#[cfg(any(not(feature = "no_object"), not(feature = "no_index")))]
|
#[cfg(any(not(feature = "no_object"), not(feature = "no_index")))]
|
||||||
pub fn set_value(&mut self, new_val: (Dynamic, Position)) -> Result<(), Box<EvalAltResult>> {
|
pub fn set_value(&mut self, new_val: Dynamic, pos: Position) -> Result<(), Box<EvalAltResult>> {
|
||||||
match self {
|
match self {
|
||||||
Self::Ref(r) => **r = new_val.0,
|
Self::Ref(r) => **r = new_val,
|
||||||
#[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.0,
|
Self::LockGuard((r, _)) => **r = new_val,
|
||||||
Self::Value(_) => unreachable!(),
|
Self::Value(_) => unreachable!(),
|
||||||
#[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.0.as_char().map_err(|err| {
|
let new_ch = new_val.as_char().map_err(|err| {
|
||||||
Box::new(EvalAltResult::ErrorMismatchDataType(
|
Box::new(EvalAltResult::ErrorMismatchDataType(
|
||||||
err.to_string(),
|
err.to_string(),
|
||||||
"char".to_string(),
|
"char".to_string(),
|
||||||
new_val.1,
|
pos,
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -468,8 +478,10 @@ impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
|||||||
/// ## WARNING
|
/// ## WARNING
|
||||||
///
|
///
|
||||||
/// This type is volatile and may change.
|
/// This type is volatile and may change.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
|
/// Source of the current context.
|
||||||
|
pub source: Option<ImmutableString>,
|
||||||
/// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup.
|
/// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup.
|
||||||
/// In some situation, e.g. after running an `eval` statement, subsequent offsets become mis-aligned.
|
/// In some situation, e.g. after running an `eval` statement, subsequent offsets become mis-aligned.
|
||||||
/// When that happens, this flag is turned on to force a scope lookup by name.
|
/// When that happens, this flag is turned on to force a scope lookup by name.
|
||||||
@ -481,6 +493,8 @@ pub struct State {
|
|||||||
pub operations: u64,
|
pub operations: u64,
|
||||||
/// Number of modules loaded.
|
/// Number of modules loaded.
|
||||||
pub modules: usize,
|
pub modules: usize,
|
||||||
|
/// Cached lookup values for function hashes.
|
||||||
|
pub functions_cache: HashMap<u64, Option<CallableFunction>, StraightHasherBuilder>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
@ -644,6 +658,9 @@ pub struct Engine {
|
|||||||
/// Max limits.
|
/// Max limits.
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
pub(crate) limits: Limits,
|
pub(crate) limits: Limits,
|
||||||
|
|
||||||
|
/// Disable doc-comments?
|
||||||
|
pub(crate) disable_doc_comments: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Engine {
|
impl fmt::Debug for Engine {
|
||||||
@ -695,11 +712,15 @@ fn default_print(_s: &str) {
|
|||||||
|
|
||||||
/// Debug to stdout
|
/// Debug to stdout
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn default_debug(_s: &str, _pos: Position) {
|
fn default_debug(_s: &str, _source: Option<&str>, _pos: Position) {
|
||||||
#[cfg(not(feature = "no_std"))]
|
#[cfg(not(feature = "no_std"))]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
if let Some(source) = _source {
|
||||||
|
println!("{} @ {:?} | {}", source, _pos, _s);
|
||||||
|
} else {
|
||||||
println!("{:?} | {}", _pos, _s);
|
println!("{:?} | {}", _pos, _s);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Search for a module within an imports stack.
|
/// Search for a module within an imports stack.
|
||||||
/// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards.
|
/// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards.
|
||||||
@ -784,6 +805,8 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
max_map_size: 0,
|
max_map_size: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
disable_doc_comments: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
engine.load_package(StandardPackage::new().get());
|
engine.load_package(StandardPackage::new().get());
|
||||||
@ -813,7 +836,7 @@ impl Engine {
|
|||||||
resolve_var: None,
|
resolve_var: None,
|
||||||
|
|
||||||
print: Box::new(|_| {}),
|
print: Box::new(|_| {}),
|
||||||
debug: Box::new(|_, _| {}),
|
debug: Box::new(|_, _, _| {}),
|
||||||
progress: None,
|
progress: None,
|
||||||
|
|
||||||
optimization_level: if cfg!(feature = "no_optimize") {
|
optimization_level: if cfg!(feature = "no_optimize") {
|
||||||
@ -837,6 +860,8 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
max_map_size: 0,
|
max_map_size: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
disable_doc_comments: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1014,7 +1039,8 @@ 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.set_value(new_val.unwrap())?;
|
let (new_val, new_val_pos) = new_val.unwrap();
|
||||||
|
obj_ptr.set_value(new_val, new_val_pos)?;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
Err(err) => match *err {
|
Err(err) => match *err {
|
||||||
@ -1090,7 +1116,9 @@ impl Engine {
|
|||||||
mods, state, lib, target_val, index, *pos, true, is_ref, false, level,
|
mods, state, lib, target_val, index, *pos, true, is_ref, false, level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
val.set_value(new_val.unwrap())?;
|
let (new_val, new_val_pos) = new_val.unwrap();
|
||||||
|
val.set_value(new_val, new_val_pos)?;
|
||||||
|
|
||||||
Ok((Default::default(), true))
|
Ok((Default::default(), true))
|
||||||
}
|
}
|
||||||
// {xxx:map}.id
|
// {xxx:map}.id
|
||||||
@ -1291,8 +1319,7 @@ impl Engine {
|
|||||||
pos: var_pos,
|
pos: var_pos,
|
||||||
} = &x.3;
|
} = &x.3;
|
||||||
|
|
||||||
self.inc_operations(state)
|
self.inc_operations(state, *var_pos)?;
|
||||||
.map_err(|err| err.fill_position(*var_pos))?;
|
|
||||||
|
|
||||||
let (target, _, pos) =
|
let (target, _, pos) =
|
||||||
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
|
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
|
||||||
@ -1343,8 +1370,7 @@ impl Engine {
|
|||||||
size: usize,
|
size: usize,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)
|
self.inc_operations(state, expr.position())?;
|
||||||
.map_err(|err| err.fill_position(expr.position()))?;
|
|
||||||
|
|
||||||
match expr {
|
match expr {
|
||||||
Expr::FnCall(x, _) if x.namespace.is_none() => {
|
Expr::FnCall(x, _) if x.namespace.is_none() => {
|
||||||
@ -1418,7 +1444,7 @@ impl Engine {
|
|||||||
_indexers: bool,
|
_indexers: bool,
|
||||||
_level: usize,
|
_level: usize,
|
||||||
) -> Result<Target<'t>, Box<EvalAltResult>> {
|
) -> Result<Target<'t>, Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)?;
|
self.inc_operations(state, Position::NONE)?;
|
||||||
|
|
||||||
match target {
|
match target {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
@ -1521,8 +1547,7 @@ impl Engine {
|
|||||||
rhs: &Expr,
|
rhs: &Expr,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)
|
self.inc_operations(state, rhs.position())?;
|
||||||
.map_err(|err| err.fill_position(rhs.position()))?;
|
|
||||||
|
|
||||||
let lhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?;
|
let lhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?;
|
||||||
let rhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?;
|
let rhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?;
|
||||||
@ -1695,8 +1720,7 @@ impl Engine {
|
|||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)
|
self.inc_operations(state, expr.position())?;
|
||||||
.map_err(|err| err.fill_position(expr.position()))?;
|
|
||||||
|
|
||||||
let result = match expr {
|
let result = match expr {
|
||||||
Expr::Expr(x) => self.eval_expr(scope, mods, state, lib, this_ptr, x, level),
|
Expr::Expr(x) => self.eval_expr(scope, mods, state, lib, this_ptr, x, level),
|
||||||
@ -1851,8 +1875,7 @@ impl Engine {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.check_data_size(result)
|
self.check_data_size(result, expr.position())
|
||||||
.map_err(|err| err.fill_position(expr.position()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a statements block.
|
/// Evaluate a statements block.
|
||||||
@ -1878,6 +1901,10 @@ impl Engine {
|
|||||||
});
|
});
|
||||||
|
|
||||||
scope.rewind(prev_scope_len);
|
scope.rewind(prev_scope_len);
|
||||||
|
if mods.len() != prev_mods_len {
|
||||||
|
// If imports list is modified, clear the functions lookup cache
|
||||||
|
state.functions_cache.clear();
|
||||||
|
}
|
||||||
mods.truncate(prev_mods_len);
|
mods.truncate(prev_mods_len);
|
||||||
state.scope_level -= 1;
|
state.scope_level -= 1;
|
||||||
|
|
||||||
@ -1904,8 +1931,7 @@ impl Engine {
|
|||||||
stmt: &Stmt,
|
stmt: &Stmt,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)
|
self.inc_operations(state, stmt.position())?;
|
||||||
.map_err(|err| err.fill_position(stmt.position()))?;
|
|
||||||
|
|
||||||
let result = match stmt {
|
let result = match stmt {
|
||||||
// No-op
|
// No-op
|
||||||
@ -1927,8 +1953,7 @@ impl Engine {
|
|||||||
return EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos).into();
|
return EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos).into();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.inc_operations(state)
|
self.inc_operations(state, pos)?;
|
||||||
.map_err(|err| err.fill_position(pos))?;
|
|
||||||
|
|
||||||
if lhs_ptr.as_ref().is_read_only() {
|
if lhs_ptr.as_ref().is_read_only() {
|
||||||
// Assignment to constant variable
|
// Assignment to constant variable
|
||||||
@ -2187,8 +2212,7 @@ impl Engine {
|
|||||||
*loop_var = value;
|
*loop_var = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.inc_operations(state)
|
self.inc_operations(state, stmt.position())?;
|
||||||
.map_err(|err| err.fill_position(stmt.position()))?;
|
|
||||||
|
|
||||||
match self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) {
|
match self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
@ -2357,6 +2381,8 @@ impl Engine {
|
|||||||
} else {
|
} else {
|
||||||
mods.push(name_def.name.clone(), module);
|
mods.push(name_def.name.clone(), module);
|
||||||
}
|
}
|
||||||
|
// When imports list is modified, clear the functions lookup cache
|
||||||
|
state.functions_cache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
state.modules += 1;
|
state.modules += 1;
|
||||||
@ -2404,8 +2430,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.check_data_size(result)
|
self.check_data_size(result, stmt.position())
|
||||||
.map_err(|err| err.fill_position(stmt.position()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check a result to ensure that the data size is within allowable limit.
|
/// Check a result to ensure that the data size is within allowable limit.
|
||||||
@ -2415,16 +2440,17 @@ impl Engine {
|
|||||||
fn check_data_size(
|
fn check_data_size(
|
||||||
&self,
|
&self,
|
||||||
result: Result<Dynamic, Box<EvalAltResult>>,
|
result: Result<Dynamic, Box<EvalAltResult>>,
|
||||||
|
_pos: Position,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check a result to ensure that the data size is within allowable limit.
|
/// Check a result to ensure that the data size is within allowable limit.
|
||||||
/// [`Position`] in [`EvalAltResult`] may be None and should be set afterwards.
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
fn check_data_size(
|
fn check_data_size(
|
||||||
&self,
|
&self,
|
||||||
result: Result<Dynamic, Box<EvalAltResult>>,
|
result: Result<Dynamic, Box<EvalAltResult>>,
|
||||||
|
pos: Position,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
// If no data size limits, just return
|
// If no data size limits, just return
|
||||||
let mut total = 0;
|
let mut total = 0;
|
||||||
@ -2513,47 +2539,41 @@ impl Engine {
|
|||||||
let (_arr, _map, s) = calc_size(result.as_ref().unwrap());
|
let (_arr, _map, s) = calc_size(result.as_ref().unwrap());
|
||||||
|
|
||||||
if s > self.max_string_size() {
|
if s > self.max_string_size() {
|
||||||
return EvalAltResult::ErrorDataTooLarge(
|
return EvalAltResult::ErrorDataTooLarge("Length of string".to_string(), pos).into();
|
||||||
"Length of string".to_string(),
|
|
||||||
Position::NONE,
|
|
||||||
)
|
|
||||||
.into();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
if _arr > self.max_array_size() {
|
if _arr > self.max_array_size() {
|
||||||
return EvalAltResult::ErrorDataTooLarge("Size of array".to_string(), Position::NONE)
|
return EvalAltResult::ErrorDataTooLarge("Size of array".to_string(), pos).into();
|
||||||
.into();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
if _map > self.max_map_size() {
|
if _map > self.max_map_size() {
|
||||||
return EvalAltResult::ErrorDataTooLarge(
|
return EvalAltResult::ErrorDataTooLarge("Size of object map".to_string(), pos).into();
|
||||||
"Size of object map".to_string(),
|
|
||||||
Position::NONE,
|
|
||||||
)
|
|
||||||
.into();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the number of operations stay within limit.
|
/// Check if the number of operations stay within limit.
|
||||||
/// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards.
|
pub(crate) fn inc_operations(
|
||||||
pub(crate) fn inc_operations(&self, state: &mut State) -> Result<(), Box<EvalAltResult>> {
|
&self,
|
||||||
|
state: &mut State,
|
||||||
|
pos: Position,
|
||||||
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
state.operations += 1;
|
state.operations += 1;
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
// Guard against too many operations
|
// Guard against too many operations
|
||||||
if self.max_operations() > 0 && state.operations > self.max_operations() {
|
if self.max_operations() > 0 && state.operations > self.max_operations() {
|
||||||
return EvalAltResult::ErrorTooManyOperations(Position::NONE).into();
|
return EvalAltResult::ErrorTooManyOperations(pos).into();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report progress - only in steps
|
// Report progress - only in steps
|
||||||
if let Some(progress) = &self.progress {
|
if let Some(progress) = &self.progress {
|
||||||
if let Some(token) = progress(state.operations) {
|
if let Some(token) = progress(state.operations) {
|
||||||
// Terminate script if progress returns a termination token
|
// Terminate script if progress returns a termination token
|
||||||
return EvalAltResult::ErrorTerminated(token, Position::NONE).into();
|
return EvalAltResult::ErrorTerminated(token, pos).into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Module that defines the extern API of [`Engine`].
|
//! Module that defines the extern API of [`Engine`].
|
||||||
|
|
||||||
use crate::dynamic::Variant;
|
use crate::dynamic::Variant;
|
||||||
use crate::engine::{EvalContext, Imports};
|
use crate::engine::{EvalContext, Imports, State};
|
||||||
use crate::fn_native::{FnCallArgs, SendSync};
|
use crate::fn_native::{FnCallArgs, SendSync};
|
||||||
use crate::optimize::OptimizationLevel;
|
use crate::optimize::OptimizationLevel;
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
@ -1368,9 +1368,9 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
let mut mods = self.global_sub_modules.clone();
|
let mods = &mut self.global_sub_modules.clone();
|
||||||
|
|
||||||
let (result, _) = self.eval_ast_with_scope_raw(scope, &mut mods, ast)?;
|
let result = self.eval_ast_with_scope_raw(scope, mods, ast)?;
|
||||||
|
|
||||||
let typ = self.map_type_name(result.type_name());
|
let typ = self.map_type_name(result.type_name());
|
||||||
|
|
||||||
@ -1390,8 +1390,12 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
ast: &'a AST,
|
ast: &'a AST,
|
||||||
) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
self.eval_statements_raw(scope, mods, ast.statements(), &[ast.lib()])
|
let state = &mut State {
|
||||||
|
source: ast.clone_source(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
self.eval_statements_raw(scope, mods, state, ast.statements(), &[ast.lib()])
|
||||||
}
|
}
|
||||||
/// Evaluate a file, but throw away the result and only return error (if any).
|
/// Evaluate a file, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
@ -1450,10 +1454,13 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
let mut mods = self.global_sub_modules.clone();
|
let mods = &mut self.global_sub_modules.clone();
|
||||||
|
let state = &mut State {
|
||||||
self.eval_statements_raw(scope, &mut mods, ast.statements(), &[ast.lib()])
|
source: ast.clone_source(),
|
||||||
.map(|_| ())
|
..Default::default()
|
||||||
|
};
|
||||||
|
self.eval_statements_raw(scope, mods, state, ast.statements(), &[ast.lib()])?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
/// Call a script function defined in an [`AST`] with multiple arguments.
|
/// Call a script function defined in an [`AST`] with multiple arguments.
|
||||||
/// Arguments are passed as a tuple.
|
/// Arguments are passed as a tuple.
|
||||||
@ -1808,20 +1815,22 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// // Override action of 'print' function
|
/// // Override action of 'print' function
|
||||||
/// let logger = result.clone();
|
/// let logger = result.clone();
|
||||||
/// engine.on_debug(move |s, pos| logger.write().unwrap().push_str(
|
/// engine.on_debug(move |s, src, pos| logger.write().unwrap().push_str(
|
||||||
/// &format!("{:?} > {}", pos, s)
|
/// &format!("{} @ {:?} > {}", src.unwrap_or("unknown"), pos, s)
|
||||||
/// ));
|
/// ));
|
||||||
///
|
///
|
||||||
/// engine.consume(r#"let x = "hello"; debug(x);"#)?;
|
/// let mut ast = engine.compile(r#"let x = "hello"; debug(x);"#)?;
|
||||||
|
/// ast.set_source(Some("world"));
|
||||||
|
/// engine.consume_ast(&ast)?;
|
||||||
///
|
///
|
||||||
/// assert_eq!(*result.read().unwrap(), r#"1:18 > "hello""#);
|
/// assert_eq!(*result.read().unwrap(), r#"world @ 1:18 > "hello""#);
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn on_debug(
|
pub fn on_debug(
|
||||||
&mut self,
|
&mut self,
|
||||||
callback: impl Fn(&str, Position) + SendSync + 'static,
|
callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.debug = Box::new(callback);
|
self.debug = Box::new(callback);
|
||||||
self
|
self
|
||||||
|
@ -40,6 +40,12 @@ impl Engine {
|
|||||||
pub fn optimization_level(&self) -> crate::OptimizationLevel {
|
pub fn optimization_level(&self) -> crate::OptimizationLevel {
|
||||||
self.optimization_level
|
self.optimization_level
|
||||||
}
|
}
|
||||||
|
/// Enable/disable doc-comments.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_doc_comments(&mut self, enable: bool) -> &mut Self {
|
||||||
|
self.disable_doc_comments = !enable;
|
||||||
|
self
|
||||||
|
}
|
||||||
/// Set the maximum levels of function calls allowed for a script in order to avoid
|
/// Set the maximum levels of function calls allowed for a script in order to avoid
|
||||||
/// infinite recursion and stack overflows.
|
/// infinite recursion and stack overflows.
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
128
src/fn_call.rs
128
src/fn_call.rs
@ -168,16 +168,27 @@ impl Engine {
|
|||||||
pos: Position,
|
pos: Position,
|
||||||
def_val: Option<&Dynamic>,
|
def_val: Option<&Dynamic>,
|
||||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)?;
|
self.inc_operations(state, pos)?;
|
||||||
|
|
||||||
|
let func = state.functions_cache.get(&hash_fn).cloned();
|
||||||
|
|
||||||
|
let func = if let Some(ref f) = func {
|
||||||
|
f.as_ref()
|
||||||
|
} else {
|
||||||
// Search for the native function
|
// Search for the native function
|
||||||
// First search registered functions (can override packages)
|
// First search registered functions (can override packages)
|
||||||
// Then search packages
|
// Then search packages
|
||||||
let func = //lib.get_fn(hash_fn, pub_only)
|
// lib.get_fn(hash_fn, pub_only)
|
||||||
self.global_namespace.get_fn(hash_fn, pub_only)
|
let f = self
|
||||||
|
.global_namespace
|
||||||
|
.get_fn(hash_fn, pub_only)
|
||||||
.or_else(|| self.packages.get_fn(hash_fn))
|
.or_else(|| self.packages.get_fn(hash_fn))
|
||||||
.or_else(|| mods.get_fn(hash_fn));
|
.or_else(|| mods.get_fn(hash_fn));
|
||||||
|
|
||||||
|
state.functions_cache.insert(hash_fn, f.cloned());
|
||||||
|
f
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(func) = func {
|
if let Some(func) = func {
|
||||||
assert!(func.is_native());
|
assert!(func.is_native());
|
||||||
|
|
||||||
@ -210,20 +221,17 @@ impl Engine {
|
|||||||
.into(),
|
.into(),
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
KEYWORD_DEBUG => (
|
KEYWORD_DEBUG => {
|
||||||
(self.debug)(
|
let text = result.as_str().map_err(|typ| {
|
||||||
result.as_str().map_err(|typ| {
|
|
||||||
EvalAltResult::ErrorMismatchOutputType(
|
EvalAltResult::ErrorMismatchOutputType(
|
||||||
self.map_type_name(type_name::<ImmutableString>()).into(),
|
self.map_type_name(type_name::<ImmutableString>()).into(),
|
||||||
typ.into(),
|
typ.into(),
|
||||||
pos,
|
pos,
|
||||||
)
|
)
|
||||||
})?,
|
})?;
|
||||||
pos,
|
let source = state.source.as_ref().map(|s| s.as_str());
|
||||||
)
|
((self.debug)(text, source, pos).into(), false)
|
||||||
.into(),
|
}
|
||||||
false,
|
|
||||||
),
|
|
||||||
_ => (result, func.is_method()),
|
_ => (result, func.is_method()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -337,7 +345,7 @@ impl Engine {
|
|||||||
pos: Position,
|
pos: Position,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)?;
|
self.inc_operations(state, pos)?;
|
||||||
|
|
||||||
// Check for stack overflow
|
// Check for stack overflow
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -370,6 +378,9 @@ impl Engine {
|
|||||||
let mut lib_merged: StaticVec<_>;
|
let mut lib_merged: StaticVec<_>;
|
||||||
|
|
||||||
let unified_lib = if let Some(ref env_lib) = fn_def.lib {
|
let unified_lib = if let Some(ref env_lib) = fn_def.lib {
|
||||||
|
// If the library is modified, clear the functions lookup cache
|
||||||
|
state.functions_cache.clear();
|
||||||
|
|
||||||
lib_merged = Default::default();
|
lib_merged = Default::default();
|
||||||
lib_merged.push(env_lib.as_ref());
|
lib_merged.push(env_lib.as_ref());
|
||||||
lib_merged.extend(lib.iter().cloned());
|
lib_merged.extend(lib.iter().cloned());
|
||||||
@ -380,7 +391,7 @@ impl Engine {
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
if !fn_def.mods.is_empty() {
|
if !fn_def.mods.is_empty() {
|
||||||
mods.extend(fn_def.mods.iter_raw());
|
mods.extend(fn_def.mods.iter_raw().map(|(n, m)| (n.clone(), m.clone())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the function at one higher level of call depth
|
// Evaluate the function at one higher level of call depth
|
||||||
@ -440,19 +451,18 @@ impl Engine {
|
|||||||
hash_script: u64,
|
hash_script: u64,
|
||||||
pub_only: bool,
|
pub_only: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// NOTE: We skip script functions for global_namespace and packages, and native functions for lib
|
|
||||||
|
|
||||||
// First check script-defined functions
|
// First check script-defined functions
|
||||||
lib.iter().any(|&m| m.contains_fn(hash_script, pub_only))
|
(hash_script != 0 && lib.iter().any(|&m| m.contains_fn(hash_script, pub_only)))
|
||||||
//|| lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only))
|
//|| lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only))
|
||||||
// Then check registered functions
|
// Then check registered functions
|
||||||
//|| self.global_namespace.contains_fn(hash_script, pub_only)
|
//|| (hash_script != 0 && self.global_namespace.contains_fn(hash_script, pub_only))
|
||||||
|| self.global_namespace.contains_fn(hash_fn, false)
|
|| self.global_namespace.contains_fn(hash_fn, false)
|
||||||
// Then check packages
|
// Then check packages
|
||||||
|| self.packages.contains_fn(hash_script)
|
|| (hash_script != 0 && self.packages.contains_fn(hash_script))
|
||||||
|| self.packages.contains_fn(hash_fn)
|
|| self.packages.contains_fn(hash_fn)
|
||||||
// Then check imported modules
|
// Then check imported modules
|
||||||
|| mods.map(|m| m.contains_fn(hash_script) || m.contains_fn(hash_fn)).unwrap_or(false)
|
|| (hash_script != 0 && mods.map(|m| m.contains_fn(hash_script)).unwrap_or(false))
|
||||||
|
|| mods.map(|m| m.contains_fn(hash_fn)).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform an actual function call, native Rust or scripted, taking care of special functions.
|
/// Perform an actual function call, native Rust or scripted, taking care of special functions.
|
||||||
@ -518,14 +528,16 @@ impl Engine {
|
|||||||
|
|
||||||
// Script-like function found
|
// Script-like function found
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
_ if self.has_override(Some(mods), lib, 0, hash_script, pub_only) => {
|
_ if hash_script != 0
|
||||||
|
&& self.has_override(Some(mods), lib, 0, hash_script, pub_only) =>
|
||||||
|
{
|
||||||
// Get function
|
// Get function
|
||||||
let func = lib
|
let (func, mut source) = lib
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|&m| m.get_fn(hash_script, pub_only))
|
.find_map(|&m| m.get_fn(hash_script, pub_only).map(|f| (f, m.clone_id())))
|
||||||
//.or_else(|| self.global_namespace.get_fn(hash_script, pub_only))
|
//.or_else(|| self.global_namespace.get_fn(hash_script, pub_only))
|
||||||
.or_else(|| self.packages.get_fn(hash_script))
|
.or_else(|| self.packages.get_fn(hash_script).map(|f| (f, None)))
|
||||||
//.or_else(|| mods.get_fn(hash_script))
|
//.or_else(|| mods.iter().find_map(|(_, m)| m.get_qualified_fn(hash_script).map(|f| (f, m.clone_id()))))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if func.is_script() {
|
if func.is_script() {
|
||||||
@ -550,7 +562,10 @@ impl Engine {
|
|||||||
let result = if _is_method {
|
let result = if _is_method {
|
||||||
// Method call of script function - map first argument to `this`
|
// Method call of script function - map first argument to `this`
|
||||||
let (first, rest) = args.split_first_mut().unwrap();
|
let (first, rest) = args.split_first_mut().unwrap();
|
||||||
self.call_script_fn(
|
|
||||||
|
mem::swap(&mut state.source, &mut source);
|
||||||
|
|
||||||
|
let result = self.call_script_fn(
|
||||||
scope,
|
scope,
|
||||||
mods,
|
mods,
|
||||||
state,
|
state,
|
||||||
@ -560,17 +575,27 @@ impl Engine {
|
|||||||
rest,
|
rest,
|
||||||
pos,
|
pos,
|
||||||
_level,
|
_level,
|
||||||
)?
|
);
|
||||||
|
|
||||||
|
// Restore the original source
|
||||||
|
state.source = source;
|
||||||
|
|
||||||
|
result?
|
||||||
} else {
|
} else {
|
||||||
// Normal call of script function
|
// Normal call of script function
|
||||||
// The first argument is a reference?
|
// The first argument is a reference?
|
||||||
let mut backup: ArgBackup = Default::default();
|
let mut backup: ArgBackup = Default::default();
|
||||||
backup.change_first_arg_to_copy(is_ref, args);
|
backup.change_first_arg_to_copy(is_ref, args);
|
||||||
|
|
||||||
|
mem::swap(&mut state.source, &mut source);
|
||||||
|
|
||||||
let result = self.call_script_fn(
|
let result = self.call_script_fn(
|
||||||
scope, mods, state, lib, &mut None, func, args, pos, _level,
|
scope, mods, state, lib, &mut None, func, args, pos, _level,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Restore the original source
|
||||||
|
state.source = source;
|
||||||
|
|
||||||
// Restore the original reference
|
// Restore the original reference
|
||||||
backup.restore_first_arg(args);
|
backup.restore_first_arg(args);
|
||||||
|
|
||||||
@ -609,26 +634,24 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
|
state: &mut State,
|
||||||
statements: impl IntoIterator<Item = &'a Stmt>,
|
statements: impl IntoIterator<Item = &'a Stmt>,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
let mut state = Default::default();
|
|
||||||
|
|
||||||
statements
|
statements
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.try_fold(().into(), |_, stmt| {
|
.try_fold(().into(), |_, stmt| {
|
||||||
self.eval_stmt(scope, mods, &mut state, lib, &mut None, stmt, 0)
|
self.eval_stmt(scope, mods, state, lib, &mut None, stmt, 0)
|
||||||
})
|
})
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
EvalAltResult::Return(out, _) => Ok(out),
|
EvalAltResult::Return(out, _) => Ok(out),
|
||||||
EvalAltResult::LoopBreak(_, _) => unreachable!(),
|
EvalAltResult::LoopBreak(_, _) => unreachable!(),
|
||||||
_ => Err(err),
|
_ => Err(err),
|
||||||
})
|
})
|
||||||
.map(|v| (v, state.operations))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate a text string as a script - used primarily for 'eval'.
|
/// Evaluate a text script in place - used primarily for 'eval'.
|
||||||
fn eval_script_expr(
|
fn eval_script_expr_in_place(
|
||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
@ -638,7 +661,7 @@ impl Engine {
|
|||||||
pos: Position,
|
pos: Position,
|
||||||
_level: usize,
|
_level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
self.inc_operations(state)?;
|
self.inc_operations(state, pos)?;
|
||||||
|
|
||||||
let script = script.trim();
|
let script = script.trim();
|
||||||
if script.is_empty() {
|
if script.is_empty() {
|
||||||
@ -666,12 +689,16 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the AST
|
// Evaluate the AST
|
||||||
let (result, operations) = self.eval_statements_raw(scope, mods, ast.statements(), lib)?;
|
let mut new_state = State {
|
||||||
|
source: state.source.clone(),
|
||||||
|
operations: state.operations,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
state.operations += operations;
|
let result = self.eval_statements_raw(scope, mods, &mut new_state, ast.statements(), lib);
|
||||||
self.inc_operations(state)?;
|
|
||||||
|
|
||||||
return Ok(result);
|
state.operations = new_state.operations;
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a dot method.
|
/// Call a dot method.
|
||||||
@ -963,7 +990,8 @@ impl Engine {
|
|||||||
self.make_type_mismatch_err::<ImmutableString>(typ, args_expr[0].position())
|
self.make_type_mismatch_err::<ImmutableString>(typ, args_expr[0].position())
|
||||||
})?;
|
})?;
|
||||||
let pos = args_expr[0].position();
|
let pos = args_expr[0].position();
|
||||||
let result = self.eval_script_expr(scope, mods, state, lib, script, pos, level + 1);
|
let result =
|
||||||
|
self.eval_script_expr_in_place(scope, mods, state, lib, script, pos, level + 1);
|
||||||
|
|
||||||
// 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.
|
||||||
@ -1006,8 +1034,7 @@ impl Engine {
|
|||||||
target = target.into_owned();
|
target = target.into_owned();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.inc_operations(state)
|
self.inc_operations(state, pos)?;
|
||||||
.map_err(|err| err.fill_position(pos))?;
|
|
||||||
|
|
||||||
args = if target.is_shared() || target.is_value() {
|
args = if target.is_shared() || target.is_value() {
|
||||||
arg_values.insert(0, target.take_or_clone().flatten());
|
arg_values.insert(0, target.take_or_clone().flatten());
|
||||||
@ -1089,8 +1116,7 @@ impl Engine {
|
|||||||
let (target, _, pos) =
|
let (target, _, pos) =
|
||||||
self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?;
|
self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?;
|
||||||
|
|
||||||
self.inc_operations(state)
|
self.inc_operations(state, pos)?;
|
||||||
.map_err(|err| err.fill_position(pos))?;
|
|
||||||
|
|
||||||
if target.is_shared() || target.is_value() {
|
if target.is_shared() || target.is_value() {
|
||||||
arg_values[0] = target.take_or_clone().flatten();
|
arg_values[0] = target.take_or_clone().flatten();
|
||||||
@ -1119,7 +1145,7 @@ impl Engine {
|
|||||||
let func = match module.get_qualified_fn(hash_script) {
|
let func = match module.get_qualified_fn(hash_script) {
|
||||||
// Then search in Rust functions
|
// Then search in Rust functions
|
||||||
None => {
|
None => {
|
||||||
self.inc_operations(state)?;
|
self.inc_operations(state, pos)?;
|
||||||
|
|
||||||
// Namespace-qualified Rust functions are indexed in two steps:
|
// Namespace-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,
|
||||||
@ -1149,9 +1175,17 @@ impl Engine {
|
|||||||
let args = args.as_mut();
|
let args = args.as_mut();
|
||||||
let new_scope = &mut Default::default();
|
let new_scope = &mut Default::default();
|
||||||
let fn_def = f.get_fn_def().clone();
|
let fn_def = f.get_fn_def().clone();
|
||||||
self.call_script_fn(
|
|
||||||
|
let mut source = module.clone_id();
|
||||||
|
mem::swap(&mut state.source, &mut source);
|
||||||
|
|
||||||
|
let result = self.call_script_fn(
|
||||||
new_scope, mods, state, lib, &mut None, &fn_def, args, pos, level,
|
new_scope, mods, state, lib, &mut None, &fn_def, args, pos, level,
|
||||||
)
|
);
|
||||||
|
|
||||||
|
state.source = source;
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
Some(f) if f.is_plugin_fn() => f
|
Some(f) if f.is_plugin_fn() => f
|
||||||
.get_plugin_fn()
|
.get_plugin_fn()
|
||||||
|
@ -360,10 +360,10 @@ pub type OnPrintCallback = Box<dyn Fn(&str) + Send + Sync + 'static>;
|
|||||||
|
|
||||||
/// A standard callback function for debugging.
|
/// A standard callback function for debugging.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
pub type OnDebugCallback = Box<dyn Fn(&str, Position) + 'static>;
|
pub type OnDebugCallback = Box<dyn Fn(&str, Option<&str>, Position) + 'static>;
|
||||||
/// A standard callback function for debugging.
|
/// A standard callback function for debugging.
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub type OnDebugCallback = Box<dyn Fn(&str, Position) + Send + Sync + 'static>;
|
pub type OnDebugCallback = Box<dyn Fn(&str, Option<&str>, Position) + Send + Sync + 'static>;
|
||||||
|
|
||||||
/// A standard callback function for variable access.
|
/// A standard callback function for variable access.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
|
@ -119,6 +119,8 @@ impl FuncInfo {
|
|||||||
/// Not available under the `no_module` feature.
|
/// Not available under the `no_module` feature.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Module {
|
pub struct Module {
|
||||||
|
/// ID identifying the module.
|
||||||
|
id: Option<ImmutableString>,
|
||||||
/// Sub-modules.
|
/// Sub-modules.
|
||||||
modules: HashMap<ImmutableString, Shared<Module>>,
|
modules: HashMap<ImmutableString, Shared<Module>>,
|
||||||
/// Module variables.
|
/// Module variables.
|
||||||
@ -141,6 +143,7 @@ pub struct Module {
|
|||||||
impl Default for Module {
|
impl Default for Module {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
id: None,
|
||||||
modules: Default::default(),
|
modules: Default::default(),
|
||||||
variables: Default::default(),
|
variables: Default::default(),
|
||||||
all_variables: Default::default(),
|
all_variables: Default::default(),
|
||||||
@ -157,7 +160,12 @@ impl fmt::Debug for Module {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"Module(\n modules: {}\n vars: {}\n functions: {}\n)",
|
"Module({}\n modules: {}\n vars: {}\n functions: {}\n)",
|
||||||
|
if let Some(ref id) = self.id {
|
||||||
|
format!("id: {:?}", id)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
},
|
||||||
self.modules
|
self.modules
|
||||||
.keys()
|
.keys()
|
||||||
.map(|m| m.as_str())
|
.map(|m| m.as_str())
|
||||||
@ -220,6 +228,41 @@ impl Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the ID of the module, if any.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Module;
|
||||||
|
///
|
||||||
|
/// let mut module = Module::new();
|
||||||
|
/// module.set_id(Some("hello"));
|
||||||
|
/// assert_eq!(module.id(), Some("hello"));
|
||||||
|
/// ```
|
||||||
|
pub fn id(&self) -> Option<&str> {
|
||||||
|
self.id.as_ref().map(|s| s.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the ID of the module, if any.
|
||||||
|
pub(crate) fn clone_id(&self) -> Option<ImmutableString> {
|
||||||
|
self.id.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the ID of the module.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::Module;
|
||||||
|
///
|
||||||
|
/// let mut module = Module::new();
|
||||||
|
/// module.set_id(Some("hello"));
|
||||||
|
/// assert_eq!(module.id(), Some("hello"));
|
||||||
|
/// ```
|
||||||
|
pub fn set_id<S: Into<ImmutableString>>(&mut self, id: Option<S>) {
|
||||||
|
self.id = id.map(|s| s.into());
|
||||||
|
}
|
||||||
|
|
||||||
/// Is the module empty?
|
/// Is the module empty?
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -1338,8 +1381,12 @@ impl Module {
|
|||||||
/// the hash calculated by [`build_index`][Module::build_index].
|
/// the hash calculated by [`build_index`][Module::build_index].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn contains_qualified_fn(&self, hash_fn: u64) -> bool {
|
pub fn contains_qualified_fn(&self, hash_fn: u64) -> bool {
|
||||||
|
if hash_fn == 0 {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
self.all_functions.contains_key(&hash_fn)
|
self.all_functions.contains_key(&hash_fn)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a namespace-qualified function.
|
/// Get a namespace-qualified function.
|
||||||
/// Name and Position in `EvalAltResult` are None and must be set afterwards.
|
/// Name and Position in `EvalAltResult` are None and must be set afterwards.
|
||||||
@ -1348,8 +1395,12 @@ impl Module {
|
|||||||
/// the hash calculated by [`build_index`][Module::build_index].
|
/// the hash calculated by [`build_index`][Module::build_index].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> {
|
pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> {
|
||||||
|
if hash_qualified_fn == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
self.all_functions.get(&hash_qualified_fn)
|
self.all_functions.get(&hash_qualified_fn)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Combine another module into this module.
|
/// Combine another module into this module.
|
||||||
/// The other module is consumed to merge into this module.
|
/// The other module is consumed to merge into this module.
|
||||||
@ -1679,6 +1730,7 @@ impl Module {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.set_id(ast.clone_source());
|
||||||
module.build_index();
|
module.build_index();
|
||||||
|
|
||||||
Ok(module)
|
Ok(module)
|
||||||
|
@ -163,10 +163,11 @@ impl ModuleResolver for FileModuleResolver {
|
|||||||
_ => Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos)),
|
_ => Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos)),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let m = Module::eval_ast_as_new(scope, &ast, engine).map_err(|err| {
|
let mut m = 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))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
m.set_id(Some(path));
|
||||||
module = Some(m.into());
|
module = Some(m.into());
|
||||||
module_ref = module.clone();
|
module_ref = module.clone();
|
||||||
};
|
};
|
||||||
|
@ -143,6 +143,10 @@ macro_rules! gen_signed_functions {
|
|||||||
Ok(Dynamic::from(-x))
|
Ok(Dynamic::from(-x))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[rhai_fn(name = "+")]
|
||||||
|
pub fn plus(x: $arg_type) -> $arg_type {
|
||||||
|
x
|
||||||
|
}
|
||||||
#[rhai_fn(return_raw)]
|
#[rhai_fn(return_raw)]
|
||||||
pub fn abs(x: $arg_type) -> Result<Dynamic, Box<EvalAltResult>> {
|
pub fn abs(x: $arg_type) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
if cfg!(not(feature = "unchecked")) {
|
if cfg!(not(feature = "unchecked")) {
|
||||||
@ -251,6 +255,10 @@ mod f32_functions {
|
|||||||
pub fn neg(x: f32) -> f32 {
|
pub fn neg(x: f32) -> f32 {
|
||||||
-x
|
-x
|
||||||
}
|
}
|
||||||
|
#[rhai_fn(name = "+")]
|
||||||
|
pub fn plus(x: f32) -> f32 {
|
||||||
|
-x
|
||||||
|
}
|
||||||
pub fn abs(x: f32) -> f32 {
|
pub fn abs(x: f32) -> f32 {
|
||||||
x.abs()
|
x.abs()
|
||||||
}
|
}
|
||||||
@ -310,6 +318,10 @@ mod f64_functions {
|
|||||||
pub fn neg(x: f64) -> f64 {
|
pub fn neg(x: f64) -> f64 {
|
||||||
-x
|
-x
|
||||||
}
|
}
|
||||||
|
#[rhai_fn(name = "+")]
|
||||||
|
pub fn plus(x: f64) -> f64 {
|
||||||
|
-x
|
||||||
|
}
|
||||||
pub fn abs(x: f64) -> f64 {
|
pub fn abs(x: f64) -> f64 {
|
||||||
x.abs()
|
x.abs()
|
||||||
}
|
}
|
||||||
|
@ -128,8 +128,8 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> Array {
|
|||||||
.for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into()));
|
.for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into()));
|
||||||
|
|
||||||
if let Some(mods) = ctx.mods {
|
if let Some(mods) = ctx.mods {
|
||||||
mods.iter()
|
mods.iter_raw()
|
||||||
.for_each(|(ns, m)| scan_module(&mut list, &dict, ns, m.as_ref()));
|
.for_each(|(ns, m)| scan_module(&mut list, &dict, ns.clone(), m.as_ref()));
|
||||||
}
|
}
|
||||||
|
|
||||||
list
|
list
|
||||||
|
@ -64,16 +64,24 @@ impl PackagesCollection {
|
|||||||
/// Does the specified function hash key exist in the [`PackagesCollection`]?
|
/// Does the specified function hash key exist in the [`PackagesCollection`]?
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn contains_fn(&self, hash: u64) -> bool {
|
pub fn contains_fn(&self, hash: u64) -> bool {
|
||||||
|
if hash == 0 {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
self.0
|
self.0
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(false, |x| x.iter().any(|p| p.contains_fn(hash, false)))
|
.map_or(false, |x| x.iter().any(|p| p.contains_fn(hash, false)))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/// Get specified function via its hash key.
|
/// Get specified function via its hash key.
|
||||||
pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
|
pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
|
||||||
|
if hash == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
self.0
|
self.0
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|x| x.iter().find_map(|p| p.get_fn(hash, false)))
|
.and_then(|x| x.iter().find_map(|p| p.get_fn(hash, false)))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/// Does the specified [`TypeId`] iterator exist in the [`PackagesCollection`]?
|
/// Does the specified [`TypeId`] iterator exist in the [`PackagesCollection`]?
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn contains_iter(&self, id: TypeId) -> bool {
|
pub fn contains_iter(&self, id: TypeId) -> bool {
|
||||||
|
@ -357,6 +357,7 @@ fn parse_fn_call(
|
|||||||
return Ok(Expr::FnCall(
|
return Ok(Expr::FnCall(
|
||||||
Box::new(FnCallExpr {
|
Box::new(FnCallExpr {
|
||||||
name: id.to_string().into(),
|
name: id.to_string().into(),
|
||||||
|
native_only: !is_valid_identifier(id.chars()), // script functions can only be valid identifiers
|
||||||
capture,
|
capture,
|
||||||
namespace,
|
namespace,
|
||||||
hash: hash_script,
|
hash: hash_script,
|
||||||
@ -404,6 +405,7 @@ fn parse_fn_call(
|
|||||||
return Ok(Expr::FnCall(
|
return Ok(Expr::FnCall(
|
||||||
Box::new(FnCallExpr {
|
Box::new(FnCallExpr {
|
||||||
name: id.to_string().into(),
|
name: id.to_string().into(),
|
||||||
|
native_only: !is_valid_identifier(id.chars()), // script functions can only be valid identifiers
|
||||||
capture,
|
capture,
|
||||||
namespace,
|
namespace,
|
||||||
hash: hash_script,
|
hash: hash_script,
|
||||||
@ -1103,6 +1105,12 @@ fn parse_primary(
|
|||||||
(expr, Token::LeftBracket) => {
|
(expr, Token::LeftBracket) => {
|
||||||
parse_index_chain(input, state, lib, expr, settings.level_up())?
|
parse_index_chain(input, state, lib, expr, settings.level_up())?
|
||||||
}
|
}
|
||||||
|
// Method access
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
(expr, Token::Period) => {
|
||||||
|
let rhs = parse_unary(input, state, lib, settings.level_up())?;
|
||||||
|
make_dot_expr(state, expr, rhs, token_pos)?
|
||||||
|
}
|
||||||
// Unknown postfix operator
|
// Unknown postfix operator
|
||||||
(expr, token) => unreachable!(
|
(expr, token) => unreachable!(
|
||||||
"unknown postfix operator '{}' for {:?}",
|
"unknown postfix operator '{}' for {:?}",
|
||||||
@ -1112,9 +1120,16 @@ fn parse_primary(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match &mut root_expr {
|
|
||||||
// Cache the hash key for namespace-qualified variables
|
// Cache the hash key for namespace-qualified variables
|
||||||
Expr::Variable(x) if x.1.is_some() => {
|
match &mut root_expr {
|
||||||
|
Expr::Variable(x) if x.1.is_some() => Some(x),
|
||||||
|
Expr::Index(x, _) | Expr::Dot(x, _) => match &mut x.lhs {
|
||||||
|
Expr::Variable(x) if x.1.is_some() => Some(x),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
.map(|x| {
|
||||||
let (_, modules, hash, IdentX { name, .. }) = x.as_mut();
|
let (_, modules, hash, IdentX { name, .. }) = x.as_mut();
|
||||||
let namespace = modules.as_mut().unwrap();
|
let namespace = modules.as_mut().unwrap();
|
||||||
|
|
||||||
@ -1123,9 +1138,7 @@ fn parse_primary(
|
|||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
namespace.set_index(state.find_module(&namespace[0].name));
|
namespace.set_index(state.find_module(&namespace[0].name));
|
||||||
}
|
});
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure identifiers are valid
|
// Make sure identifiers are valid
|
||||||
Ok(root_expr)
|
Ok(root_expr)
|
||||||
@ -1199,8 +1212,33 @@ fn parse_unary(
|
|||||||
}
|
}
|
||||||
// +expr
|
// +expr
|
||||||
Token::UnaryPlus => {
|
Token::UnaryPlus => {
|
||||||
eat_token(input, Token::UnaryPlus);
|
let pos = eat_token(input, Token::UnaryPlus);
|
||||||
parse_unary(input, state, lib, settings.level_up())
|
|
||||||
|
match parse_unary(input, state, lib, settings.level_up())? {
|
||||||
|
expr @ Expr::IntegerConstant(_, _) => Ok(expr),
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
expr @ Expr::FloatConstant(_, _) => Ok(expr),
|
||||||
|
|
||||||
|
// Call plus function
|
||||||
|
expr => {
|
||||||
|
let op = "+";
|
||||||
|
let hash = calc_script_fn_hash(empty(), op, 1);
|
||||||
|
let mut args = StaticVec::new();
|
||||||
|
args.push(expr);
|
||||||
|
|
||||||
|
Ok(Expr::FnCall(
|
||||||
|
Box::new(FnCallExpr {
|
||||||
|
name: op.into(),
|
||||||
|
native_only: true,
|
||||||
|
namespace: None,
|
||||||
|
hash,
|
||||||
|
args,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
pos,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// !expr
|
// !expr
|
||||||
Token::Bang => {
|
Token::Bang => {
|
||||||
@ -1768,6 +1806,7 @@ fn parse_binary_op(
|
|||||||
make_in_expr(current_lhs, rhs, pos)?
|
make_in_expr(current_lhs, rhs, pos)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is needed to parse closure followed by a dot.
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Token::Period => {
|
Token::Period => {
|
||||||
let rhs = args.pop().unwrap();
|
let rhs = args.pop().unwrap();
|
||||||
|
257
src/serde_impl/metadata.rs
Normal file
257
src/serde_impl/metadata.rs
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
use crate::stdlib::{
|
||||||
|
cmp::Ordering,
|
||||||
|
collections::BTreeMap,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use crate::{Engine, AST};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
enum FnType {
|
||||||
|
Script,
|
||||||
|
Native,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
enum FnNamespace {
|
||||||
|
Global,
|
||||||
|
Internal,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::FnNamespace> for FnNamespace {
|
||||||
|
fn from(value: crate::FnNamespace) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::FnNamespace::Global => Self::Global,
|
||||||
|
crate::FnNamespace::Internal => Self::Internal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
enum FnAccess {
|
||||||
|
Public,
|
||||||
|
Private,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::FnAccess> for FnAccess {
|
||||||
|
fn from(value: crate::FnAccess) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::FnAccess::Public => Self::Public,
|
||||||
|
crate::FnAccess::Private => Self::Private,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Ord, Hash, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct FnParam {
|
||||||
|
pub name: String,
|
||||||
|
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub typ: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for FnParam {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(match self.name.partial_cmp(&other.name).unwrap() {
|
||||||
|
Ordering::Less => Ordering::Less,
|
||||||
|
Ordering::Greater => Ordering::Greater,
|
||||||
|
Ordering::Equal => match (self.typ.is_none(), other.typ.is_none()) {
|
||||||
|
(true, true) => Ordering::Equal,
|
||||||
|
(true, false) => Ordering::Greater,
|
||||||
|
(false, true) => Ordering::Less,
|
||||||
|
(false, false) => self
|
||||||
|
.typ
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.partial_cmp(other.typ.as_ref().unwrap())
|
||||||
|
.unwrap(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct FnMetadata {
|
||||||
|
pub namespace: FnNamespace,
|
||||||
|
pub access: FnAccess,
|
||||||
|
pub name: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub typ: FnType,
|
||||||
|
pub num_params: usize,
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub params: Vec<FnParam>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub return_type: Option<String>,
|
||||||
|
pub signature: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub doc_comments: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for FnMetadata {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(match self.name.partial_cmp(&other.name).unwrap() {
|
||||||
|
Ordering::Less => Ordering::Less,
|
||||||
|
Ordering::Greater => Ordering::Greater,
|
||||||
|
Ordering::Equal => match self.num_params.partial_cmp(&other.num_params).unwrap() {
|
||||||
|
Ordering::Less => Ordering::Less,
|
||||||
|
Ordering::Greater => Ordering::Greater,
|
||||||
|
Ordering::Equal => self.params.partial_cmp(&other.params).unwrap(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for FnMetadata {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.partial_cmp(other).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&crate::module::FuncInfo> for FnMetadata {
|
||||||
|
fn from(info: &crate::module::FuncInfo) -> Self {
|
||||||
|
Self {
|
||||||
|
namespace: info.namespace.into(),
|
||||||
|
access: info.access.into(),
|
||||||
|
name: info.name.to_string(),
|
||||||
|
typ: if info.func.is_script() {
|
||||||
|
FnType::Script
|
||||||
|
} else {
|
||||||
|
FnType::Native
|
||||||
|
},
|
||||||
|
num_params: info.params,
|
||||||
|
params: if let Some(ref names) = info.param_names {
|
||||||
|
names
|
||||||
|
.iter()
|
||||||
|
.take(info.params)
|
||||||
|
.map(|s| {
|
||||||
|
let mut seg = s.splitn(2, ':');
|
||||||
|
let name = seg
|
||||||
|
.next()
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.unwrap_or("_".to_string());
|
||||||
|
let typ = seg.next().map(|s| s.trim().to_string());
|
||||||
|
FnParam { name, typ }
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
},
|
||||||
|
return_type: if let Some(ref names) = info.param_names {
|
||||||
|
names
|
||||||
|
.last()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.or_else(|| Some("()".to_string()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
signature: info.gen_signature(),
|
||||||
|
doc_comments: if info.func.is_script() {
|
||||||
|
Some(info.func.get_fn_def().comments.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crate::ScriptFnMetadata<'_>> for FnMetadata {
|
||||||
|
fn from(info: crate::ScriptFnMetadata) -> Self {
|
||||||
|
Self {
|
||||||
|
namespace: FnNamespace::Global,
|
||||||
|
access: info.access.into(),
|
||||||
|
name: info.name.to_string(),
|
||||||
|
typ: FnType::Script,
|
||||||
|
num_params: info.params.len(),
|
||||||
|
params: info
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
|
.map(|s| FnParam {
|
||||||
|
name: s.to_string(),
|
||||||
|
typ: Some("Dynamic".to_string()),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
return_type: Some("Dynamic".to_string()),
|
||||||
|
signature: info.to_string(),
|
||||||
|
doc_comments: if info.comments.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(info.comments.iter().map(|s| s.to_string()).collect())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ModuleMetadata {
|
||||||
|
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
|
||||||
|
pub modules: BTreeMap<String, Self>,
|
||||||
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub functions: Vec<FnMetadata>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&crate::Module> for ModuleMetadata {
|
||||||
|
fn from(module: &crate::Module) -> Self {
|
||||||
|
let mut functions: Vec<_> = module.iter_fn().map(|f| f.into()).collect();
|
||||||
|
functions.sort();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
modules: module
|
||||||
|
.iter_sub_modules()
|
||||||
|
.map(|(name, m)| (name.to_string(), m.as_ref().into()))
|
||||||
|
.collect(),
|
||||||
|
functions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
impl Engine {
|
||||||
|
/// Generate a list of all functions (including those defined in an [`AST`][crate::AST], if provided)
|
||||||
|
/// in JSON format. Available only under the `metadata` feature.
|
||||||
|
///
|
||||||
|
/// Functions from the following sources are included:
|
||||||
|
/// 1) Functions defined in an [`AST`][crate::AST] (if provided)
|
||||||
|
/// 2) Functions registered into the global namespace
|
||||||
|
/// 3) Functions in registered sub-modules
|
||||||
|
/// 4) Functions in packages (optional)
|
||||||
|
pub fn gen_fn_metadata_to_json(
|
||||||
|
&self,
|
||||||
|
ast: Option<&AST>,
|
||||||
|
include_packages: bool,
|
||||||
|
) -> serde_json::Result<String> {
|
||||||
|
let mut global: ModuleMetadata = Default::default();
|
||||||
|
|
||||||
|
if include_packages {
|
||||||
|
self.packages
|
||||||
|
.iter()
|
||||||
|
.flat_map(|m| m.iter_fn().map(|f| f.into()))
|
||||||
|
.for_each(|info| global.functions.push(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.global_sub_modules.iter().for_each(|(name, m)| {
|
||||||
|
global.modules.insert(name.to_string(), m.as_ref().into());
|
||||||
|
});
|
||||||
|
|
||||||
|
self.global_namespace
|
||||||
|
.iter_fn()
|
||||||
|
.map(|f| f.into())
|
||||||
|
.for_each(|info| global.functions.push(info));
|
||||||
|
|
||||||
|
if let Some(ast) = ast {
|
||||||
|
ast.iter_functions()
|
||||||
|
.map(|f| f.into())
|
||||||
|
.for_each(|info| global.functions.push(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
global.functions.sort();
|
||||||
|
|
||||||
|
serde_json::to_string_pretty(&global)
|
||||||
|
}
|
||||||
|
}
|
@ -3,3 +3,6 @@
|
|||||||
pub mod de;
|
pub mod de;
|
||||||
pub mod ser;
|
pub mod ser;
|
||||||
mod str;
|
mod str;
|
||||||
|
|
||||||
|
#[cfg(feature = "metadata")]
|
||||||
|
pub mod metadata;
|
||||||
|
32
src/token.rs
32
src/token.rs
@ -354,9 +354,7 @@ impl Token {
|
|||||||
Reserved(s) => s.clone().into(),
|
Reserved(s) => s.clone().into(),
|
||||||
Custom(s) => s.clone().into(),
|
Custom(s) => s.clone().into(),
|
||||||
LexError(err) => err.to_string().into(),
|
LexError(err) => err.to_string().into(),
|
||||||
|
Comment(s) => s.clone().into(),
|
||||||
Comment(s) if is_doc_comment(s) => s[..3].to_string().into(),
|
|
||||||
Comment(s) => s[..2].to_string().into(),
|
|
||||||
|
|
||||||
token => match token {
|
token => match token {
|
||||||
LeftBrace => "{",
|
LeftBrace => "{",
|
||||||
@ -759,6 +757,8 @@ pub struct TokenizeState {
|
|||||||
pub end_with_none: bool,
|
pub end_with_none: bool,
|
||||||
/// Include comments?
|
/// Include comments?
|
||||||
pub include_comments: bool,
|
pub include_comments: bool,
|
||||||
|
/// Disable doc-comments?
|
||||||
|
pub disable_doc_comments: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// _(INTERNALS)_ Trait that encapsulates a peekable character input stream.
|
/// _(INTERNALS)_ Trait that encapsulates a peekable character input stream.
|
||||||
@ -1020,7 +1020,8 @@ fn is_binary_char(c: char) -> bool {
|
|||||||
/// Test if the comment block is a doc-comment.
|
/// Test if the comment block is a doc-comment.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn is_doc_comment(comment: &str) -> bool {
|
pub fn is_doc_comment(comment: &str) -> bool {
|
||||||
comment.starts_with("///") || comment.starts_with("/**")
|
(comment.starts_with("///") && !comment.starts_with("////"))
|
||||||
|
|| (comment.starts_with("/**") && !comment.starts_with("/***"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the next token.
|
/// Get the next token.
|
||||||
@ -1040,7 +1041,9 @@ fn get_next_token_inner(
|
|||||||
|
|
||||||
state.comment_level = scan_block_comment(stream, state.comment_level, pos, &mut comment);
|
state.comment_level = scan_block_comment(stream, state.comment_level, pos, &mut comment);
|
||||||
|
|
||||||
if state.include_comments || is_doc_comment(comment.as_ref().unwrap()) {
|
if state.include_comments
|
||||||
|
|| (!state.disable_doc_comments && is_doc_comment(comment.as_ref().unwrap()))
|
||||||
|
{
|
||||||
return Some((Token::Comment(comment.unwrap()), start_pos));
|
return Some((Token::Comment(comment.unwrap()), start_pos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1288,9 +1291,14 @@ fn get_next_token_inner(
|
|||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
|
|
||||||
let mut comment = match stream.peek_next() {
|
let mut comment = match stream.peek_next() {
|
||||||
Some('/') => {
|
Some('/') if !state.disable_doc_comments => {
|
||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
Some("///".to_string())
|
|
||||||
|
// Long streams of `///...` are not doc-comments
|
||||||
|
match stream.peek_next() {
|
||||||
|
Some('/') => None,
|
||||||
|
_ => Some("///".to_string()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ if state.include_comments => Some("//".to_string()),
|
_ if state.include_comments => Some("//".to_string()),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -1316,9 +1324,14 @@ fn get_next_token_inner(
|
|||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
|
|
||||||
let mut comment = match stream.peek_next() {
|
let mut comment = match stream.peek_next() {
|
||||||
Some('*') => {
|
Some('*') if !state.disable_doc_comments => {
|
||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
Some("/**".to_string())
|
|
||||||
|
// Long streams of `/****...` are not doc-comments
|
||||||
|
match stream.peek_next() {
|
||||||
|
Some('*') => None,
|
||||||
|
_ => Some("/**".to_string()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ if state.include_comments => Some("/*".to_string()),
|
_ if state.include_comments => Some("/*".to_string()),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -1785,6 +1798,7 @@ impl Engine {
|
|||||||
comment_level: 0,
|
comment_level: 0,
|
||||||
end_with_none: false,
|
end_with_none: false,
|
||||||
include_comments: false,
|
include_comments: false,
|
||||||
|
disable_doc_comments: self.disable_doc_comments,
|
||||||
},
|
},
|
||||||
pos: Position::new(1, 0),
|
pos: Position::new(1, 0),
|
||||||
stream: MultiInputsStream {
|
stream: MultiInputsStream {
|
||||||
|
@ -29,7 +29,7 @@ fn test_comments() -> Result<(), Box<EvalAltResult>> {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
|
fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
|
||||||
let engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
let ast = engine.compile(
|
let ast = engine.compile(
|
||||||
r"
|
r"
|
||||||
@ -54,6 +54,16 @@ fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
|
|||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
|
engine.compile(
|
||||||
|
r"
|
||||||
|
///////////////
|
||||||
|
let x = 42;
|
||||||
|
|
||||||
|
/***************/
|
||||||
|
let x = 42;
|
||||||
|
",
|
||||||
|
)?;
|
||||||
|
|
||||||
let ast = engine.compile(
|
let ast = engine.compile(
|
||||||
r"
|
r"
|
||||||
/** Hello world
|
/** Hello world
|
||||||
@ -78,5 +88,17 @@ fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
|
|||||||
)
|
)
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
|
engine.set_doc_comments(false);
|
||||||
|
|
||||||
|
engine.compile(
|
||||||
|
r"
|
||||||
|
/// Hello world!
|
||||||
|
let x = 42;
|
||||||
|
|
||||||
|
/** Hello world! */
|
||||||
|
let x = 42;
|
||||||
|
",
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("question::MYSTIC_NUMBER")?, 42);
|
assert_eq!(engine.eval::<INT>("question::MYSTIC_NUMBER")?, 42);
|
||||||
assert!(engine.eval::<INT>("MYSTIC_NUMBER").is_err());
|
assert!(engine.eval::<INT>("MYSTIC_NUMBER").is_err());
|
||||||
|
assert_eq!(engine.eval::<INT>("question::life::universe::answer")?, 41);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>("question::life::universe::answer + 1")?,
|
engine.eval::<INT>("question::life::universe::answer + 1")?,
|
||||||
42
|
42
|
||||||
@ -60,6 +61,8 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
|
|||||||
.eval::<INT>("inc(question::life::universe::answer)")
|
.eval::<INT>("inc(question::life::universe::answer)")
|
||||||
.is_err());
|
.is_err());
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
assert_eq!(engine.eval::<INT>("question::MYSTIC_NUMBER.doubled")?, 84);
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>("question::life::universe::answer.doubled")?,
|
engine.eval::<INT>("question::life::universe::answer.doubled")?,
|
||||||
82
|
82
|
||||||
|
@ -55,17 +55,20 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
|
let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?;
|
||||||
|
|
||||||
assert!(format!("{:?}", ast).starts_with(r#"AST { statements: [Block([Const(IdentX { name: "DECISION", pos: 1:9 }, Some(Unit(0:0)), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)]"#));
|
assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, statements: [Block([Const(IdentX { name: "DECISION", pos: 1:9 }, Some(Unit(0:0)), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)]"#));
|
||||||
|
|
||||||
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
let ast = engine.compile("if 1 == 2 { 42 }")?;
|
||||||
|
|
||||||
assert!(format!("{:?}", ast).starts_with("AST { statements: [], functions: Module("));
|
assert!(
|
||||||
|
format!("{:?}", ast).starts_with("AST { source: None, statements: [], functions: Module(")
|
||||||
|
);
|
||||||
|
|
||||||
engine.set_optimization_level(OptimizationLevel::Full);
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
|
|
||||||
let ast = engine.compile("abs(-42)")?;
|
let ast = engine.compile("abs(-42)")?;
|
||||||
|
|
||||||
assert!(format!("{:?}", ast).starts_with(r"AST { statements: [Expr(IntegerConstant(42, 1:1))]"));
|
assert!(format!("{:?}", ast)
|
||||||
|
.starts_with(r"AST { source: None, statements: [Expr(IntegerConstant(42, 1:1))]"));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -13,20 +13,28 @@ fn test_print_debug() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
engine
|
engine
|
||||||
.on_print(move |s| log1.write().unwrap().push(format!("entry: {}", s)))
|
.on_print(move |s| log1.write().unwrap().push(format!("entry: {}", s)))
|
||||||
.on_debug(move |s, pos| {
|
.on_debug(move |s, src, pos| {
|
||||||
log2.write()
|
log2.write().unwrap().push(format!(
|
||||||
.unwrap()
|
"DEBUG of {} at {:?}: {}",
|
||||||
.push(format!("DEBUG at {:?}: {}", pos, s))
|
src.unwrap_or("unknown"),
|
||||||
|
pos,
|
||||||
|
s
|
||||||
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
// Evaluate script
|
// Evaluate script
|
||||||
engine.eval::<()>("print(40 + 2)")?;
|
engine.consume("print(40 + 2)")?;
|
||||||
engine.eval::<()>(r#"let x = "hello!"; debug(x)"#)?;
|
let mut ast = engine.compile(r#"let x = "hello!"; debug(x)"#)?;
|
||||||
|
ast.set_source(Some("world"));
|
||||||
|
engine.consume_ast(&ast)?;
|
||||||
|
|
||||||
// 'logbook' captures all the 'print' and 'debug' output
|
// 'logbook' captures all the 'print' and 'debug' output
|
||||||
assert_eq!(logbook.read().unwrap().len(), 2);
|
assert_eq!(logbook.read().unwrap().len(), 2);
|
||||||
assert_eq!(logbook.read().unwrap()[0], "entry: 42");
|
assert_eq!(logbook.read().unwrap()[0], "entry: 42");
|
||||||
assert_eq!(logbook.read().unwrap()[1], r#"DEBUG at 1:19: "hello!""#);
|
assert_eq!(
|
||||||
|
logbook.read().unwrap()[1],
|
||||||
|
r#"DEBUG of world at 1:19: "hello!""#
|
||||||
|
);
|
||||||
|
|
||||||
for entry in logbook.read().unwrap().iter() {
|
for entry in logbook.read().unwrap().iter() {
|
||||||
println!("{}", entry);
|
println!("{}", entry);
|
||||||
|
Loading…
Reference in New Issue
Block a user