Refactor.

This commit is contained in:
Stephen Chung 2020-06-29 23:55:28 +08:00
parent 063851a6ad
commit d6a08be223
37 changed files with 386 additions and 172 deletions

View File

@ -31,10 +31,12 @@ Features
one single source file, all with names starting with `"unsafe_"`). one single source file, all with names starting with `"unsafe_"`).
* Re-entrant scripting engine can be made `Send + Sync` (via the [`sync`] feature). * Re-entrant scripting engine can be made `Send + Sync` (via the [`sync`] feature).
* Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`). * Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
* Rugged - protection against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts. * Rugged - protected against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts.
* Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run. * Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run.
* [Function overloading](https://schungx.github.io/rhai/language/overload.html). * [Function overloading](https://schungx.github.io/rhai/language/overload.html).
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html). * [Operator overloading](https://schungx.github.io/rhai/rust/operators.html).
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html).
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
* Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html). * Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html).
* Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations. * Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
* Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html). * Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html).
@ -43,3 +45,9 @@ Documentation
------------- -------------
See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai scripting engine and language. See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai scripting engine and language.
Playground
----------
An [Online Playground](https://alvinhochun.github.io/rhai-demo/) is available with syntax-highlighting editor.
Scripts can be evaluated directly from the editor.

View File

@ -1,30 +1,31 @@
The Rhai Scripting Language The Rhai Scripting Language
========================== ==========================
1. [What is Rhai](about.md) 1. [What is Rhai](about/index.md)
1. [Features](about/features.md) 1. [Features](about/features.md)
2. [Supported Targets and Builds](about/targets.md) 2. [Supported Targets and Builds](about/targets.md)
3. [What Rhai Isn't](about/non-design.md) 3. [What Rhai Isn't](about/non-design.md)
4. [Related Resources](about/related.md) 4. [Related Resources](about/related.md)
2. [Getting Started](start.md) 3. [Getting Started](start/index.md)
1. [Install the Rhai Crate](start/install.md) 1. [Online Playground](start/playground.md)
2. [Optional Features](start/features.md) 2. [Install the Rhai Crate](start/install.md)
3. [Special Builds](start/builds/index.md) 3. [Optional Features](start/features.md)
4. [Special Builds](start/builds/index.md)
1. [Performance](start/builds/performance.md) 1. [Performance](start/builds/performance.md)
2. [Minimal](start/builds/minimal.md) 2. [Minimal](start/builds/minimal.md)
3. [no-std](start/builds/no-std.md) 3. [no-std](start/builds/no-std.md)
4. [WebAssembly (WASM)](start/builds/wasm.md) 4. [WebAssembly (WASM)](start/builds/wasm.md)
4. [Examples](start/examples/index.md) 5. [Examples](start/examples/index.md)
1. [Rust](start/examples/rust.md) 1. [Rust](start/examples/rust.md)
2. [Scripts](start/examples/scripts.md) 2. [Scripts](start/examples/scripts.md)
3. [Using the `Engine`](engine.md) 4. [Using the `Engine`](engine/index.md)
1. [Hello World in Rhai - Evaluate a Script](engine/hello-world.md) 1. [Hello World in Rhai - Evaluate a Script](engine/hello-world.md)
2. [Compile a Script to AST for Repeated Evaluations](engine/compile.md) 2. [Compile a Script to AST for Repeated Evaluations](engine/compile.md)
3. [Call a Rhai Function from Rust](engine/call-fn.md) 3. [Call a Rhai Function from Rust](engine/call-fn.md)
4. [Create a Rust Anonymous Function from a Rhai Function](engine/func.md) 4. [Create a Rust Anonymous Function from a Rhai Function](engine/func.md)
5. [Evaluate Expressions Only](engine/expressions.md) 5. [Evaluate Expressions Only](engine/expressions.md)
6. [Raw Engine](engine/raw.md) 6. [Raw Engine](engine/raw.md)
4. [Extend Rhai with Rust](rust.md) 5. [Extend Rhai with Rust](rust/index.md)
1. [Traits](rust/traits.md) 1. [Traits](rust/traits.md)
2. [Register a Rust Function](rust/functions.md) 2. [Register a Rust Function](rust/functions.md)
1. [String Parameters in Rust Functions](rust/strings.md) 1. [String Parameters in Rust Functions](rust/strings.md)
@ -42,7 +43,7 @@ The Rhai Scripting Language
4. [Printing Custom Types](rust/print-custom.md) 4. [Printing Custom Types](rust/print-custom.md)
9. [Scope - Initializing and Maintaining State](rust/scope.md) 9. [Scope - Initializing and Maintaining State](rust/scope.md)
10. [Engine Configuration Options](rust/options.md) 10. [Engine Configuration Options](rust/options.md)
5. [Rhai Language Reference](language.md) 6. [Rhai Language Reference](language/index.md)
1. [Comments](language/comments.md) 1. [Comments](language/comments.md)
2. [Values and Types](language/values-and-types.md) 2. [Values and Types](language/values-and-types.md)
1. [Dynamic Values](language/dynamic.md) 1. [Dynamic Values](language/dynamic.md)
@ -70,9 +71,10 @@ The Rhai Scripting Language
12. [Return Values](language/return.md) 12. [Return Values](language/return.md)
13. [Throw Exception on Error](language/throw.md) 13. [Throw Exception on Error](language/throw.md)
14. [Functions](language/functions.md) 14. [Functions](language/functions.md)
1. [Function Overloading](language/overload.md) 1. [Call Method as Function](language/method.md)
2. [Call Method as Function](language/method.md) 2. [Overloading](language/overload.md)
3. [Function Pointers](language/fn-ptr.md) 3. [Namespaces](language/fn-namespaces.md)
4. [Function Pointers](language/fn-ptr.md)
15. [Print and Debug](language/print-debug.md) 15. [Print and Debug](language/print-debug.md)
16. [Modules](language/modules/index.md) 16. [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)
@ -81,7 +83,7 @@ The Rhai Scripting Language
4. [Create from AST](language/modules/ast.md) 4. [Create from AST](language/modules/ast.md)
5. [Module Resolvers](language/modules/resolvers.md) 5. [Module Resolvers](language/modules/resolvers.md)
1. [Implement a Custom Module Resolver](language/modules/imp-resolver.md) 1. [Implement a Custom Module Resolver](language/modules/imp-resolver.md)
6. [Safety and Protection](safety/index.md) 7. [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)
3. [Maximum Length of Strings](safety/max-string-size.md) 3. [Maximum Length of Strings](safety/max-string-size.md)
@ -92,7 +94,7 @@ The Rhai Scripting Language
7. [Maximum Number of Modules](safety/max-modules.md) 7. [Maximum Number of Modules](safety/max-modules.md)
8. [Maximum Call Stack Depth](safety/max-call-stack.md) 8. [Maximum Call Stack Depth](safety/max-call-stack.md)
9. [Maximum Statement Depth](safety/max-stmt-depth.md) 9. [Maximum Statement Depth](safety/max-stmt-depth.md)
7. [Advanced Topics](advanced.md) 8. [Advanced Topics](advanced.md)
1. [Object-Oriented Programming (OOP)](language/oop.md) 1. [Object-Oriented Programming (OOP)](language/oop.md)
2. [Script Optimization](engine/optimize/index.md) 2. [Script Optimization](engine/optimize/index.md)
1. [Optimization Levels](engine/optimize/optimize-levels.md) 1. [Optimization Levels](engine/optimize/optimize-levels.md)
@ -102,7 +104,7 @@ The Rhai Scripting Language
5. [Volatility Considerations](engine/optimize/volatility.md) 5. [Volatility Considerations](engine/optimize/volatility.md)
6. [Subtle Semantic Changes](engine/optimize/semantics.md) 6. [Subtle Semantic Changes](engine/optimize/semantics.md)
3. [Eval Statement](language/eval.md) 3. [Eval Statement](language/eval.md)
8. [Appendix](appendix/index.md) 9. [Appendix](appendix/index.md)
1. [Keywords](appendix/keywords.md) 1. [Keywords](appendix/keywords.md)
2. [Operators](appendix/operators.md) 2. [Operators](appendix/operators.md)
3. [Literals](appendix/literals.md) 3. [Literals](appendix/literals.md)

View File

@ -8,7 +8,7 @@ Easy
* Easy-to-use language similar to JavaScript+Rust with dynamic typing. * Easy-to-use language similar to JavaScript+Rust with dynamic typing.
* Tight integration with native Rust [functions] and [types][custom types], including [getters/setters]({{rootUrl}}/rust/getters-setters.md), [methods][custom type] and [indexers]({{rootUrl}}/rust/indexers.md). * Tight integration with native Rust [functions] and [types][custom types], including [getters/setters], [methods][custom type] and [indexers].
* Freely pass Rust variables/constants into a script via an external [`Scope`]. * Freely pass Rust variables/constants into a script via an external [`Scope`].

View File

@ -1,7 +1,7 @@
What is Rhai What is Rhai
============ ============
{{#include links.md}} {{#include ../links.md}}
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way
to add scripting to any application. to add scripting to any application.

View File

@ -11,6 +11,8 @@ Other online documentation resources for Rhai:
* [`LIB.RS`](https://lib.rs/crates/rhai) - Rhai library info * [`LIB.RS`](https://lib.rs/crates/rhai) - Rhai library info
* [Online Playground][playground] - Run scripts directly from editor
Other cool projects to check out: Other cool projects to check out:
* [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being led by my cousin. * [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being led by my cousin.

View File

@ -3,7 +3,7 @@ Compile a Script (to AST)
{{#include ../links.md}} {{#include ../links.md}}
To repeatedly evaluate a script, _compile_ it first into an AST (abstract syntax tree) form: To repeatedly evaluate a script, _compile_ it first into an `AST` (abstract syntax tree) form:
```rust ```rust
// Compile to an AST and store it for later evaluations // Compile to an AST and store it for later evaluations

View File

@ -1,7 +1,7 @@
Using the Engine Using the Engine
================ ================
{{#include links.md}} {{#include ../links.md}}
Rhai's interpreter resides in the [`Engine`] type under the master `rhai` namespace. Rhai's interpreter resides in the [`Engine`] type under the master `rhai` namespace.

View File

@ -4,13 +4,13 @@ Re-Optimize an AST
{{#include ../../links.md}} {{#include ../../links.md}}
Sometimes it is more efficient to store one single, large script with delimited code blocks guarded by Sometimes it is more efficient to store one single, large script with delimited code blocks guarded by
constant variables. This script is compiled once to an `AST`. constant variables. This script is compiled once to an [`AST`].
Then, depending on the execution environment, constants are passed into the [`Engine`] and the `AST` Then, depending on the execution environment, constants are passed into the [`Engine`] and the [`AST`]
is _re_-optimized based on those constants via the `Engine::optimize_ast` method, is _re_-optimized based on those constants via the `Engine::optimize_ast` method,
effectively pruning out unused code sections. effectively pruning out unused code sections.
The final, optimized `AST` is then used for evaluations. The final, optimized [`AST`] is then used for evaluations.
```rust ```rust
// Compile master script to AST // Compile master script to AST

View File

@ -0,0 +1,141 @@
Function Namespaces
==================
{{#include ../links.md}}
Each Function is a Separate Compilation Unit
-------------------------------------------
[Functions] in Rhai are _pure_ and they form individual _compilation units_.
This means that individual functions can be separated, exported, re-grouped, imported,
and generally mix-'n-match-ed with other completely unrelated scripts.
For example, the `AST::merge` method allows merging all functions in one [`AST`] into another,
forming a new, combined, group of functions.
In general, there are two types of _namespaces_ where functions are looked up:
| Namespace | Source | Lookup method | How Many |
| --------- | ---------------------------------------------------------------------- | --------------------------------- | :----------------------: |
| Global | `Engine::register_XXX` API, [`AST`] being evaluated, [packages] loaded | Simple function name | One |
| Module | [`Module`] | Namespace-qualified function name | As many as [`import`]-ed |
Global Namespace
----------------
There is one _global_ namespace for every [`Engine`], which includes:
* All the native Rust functions registered via the `Engine::register_XXX` API.
* All the Rust functions defined in [packages] that are loaded into the [`Engine`].
In addition, during evaluation of an [`AST`], all script-defined functions bundled together within
the [`AST`] are added to the global namespace and override any existing registered functions of
the same names and number of parameters.
Anywhere in a Rhai script, when a function call is made, it is searched within the global namespace.
Therefore, function calls in Rhai are _late_ bound - meaning that the function called cannot be
determined or guaranteed and there is no way to _lock down_ the function being called.
This aspect is very similar to JavaScript before ES6 modules.
```rust
// Compile a script into AST
let ast1 = engine.compile(
r#"
fn message() { "Hello!" } // greeting message
fn say_hello() {
print(message()); // prints message
}
say_hello();
"#
)?;
// Compile another script with an overriding function
let ast2 = engine.compile(r#"fn message() { "Boo!" }"#)?;
// Merge the two AST's
let ast = ast1.merge(ast2); // 'message' will be overwritten
engine.consume_ast(&ast)?; // prints 'Boo!'
```
Therefore, care must be taken when _cross-calling_ functions to make sure that the correct
functions are called.
The only practical way to ensure that a function is a correct one is to use [modules] -
i.e. define the function in a separate module and then [`import`] it:
```rust
message.rhai:
fn message() { "Hello!" }
script.rhai:
fn say_hello() {
import "message" as msg;
print(msg::message());
}
say_hello();
```
Module Namespaces
-----------------
[Modules] can be dynamically loaded into a Rhai script using the [`import`] keyword.
When that happens, functions defined within the [module] can be called with a _qualified_ name.
There is a catch, though, if functions in a module script refer to global functions
defined _within the script_. When called later, those functions will be searched in the
current global namespace and may not be found.
```rust
greeting.rhai:
fn message() { "Hello!" };
fn say_hello() { print(message()); }
say_hello(); // 'message' is looked up in the global namespace
script.rhai:
import "greeting" as g;
g::say_hello(); // <- error: function not found - 'message'
```
In the example above, although the module `greeting.rhai` loads fine (`"Hello!"` is printed),
the subsequent call using the _namespace-qualified_ function name fails to find the same function
'`message`' which now essentially becomes `g::message`. The call fails as there is no more
function named '`message`' in the global namespace.
Therefore, when writing functions for a [module], make sure that those functions are as _pure_
as possible and avoid cross-calling them from each other. A [function pointer] is a valid technique
to call another function within a module-defined function:
```rust
greeting.rhai:
fn message() { "Hello!" };
fn say_hello(msg_func) { // 'msg_func' is a function pointer
print(msg_func.call()); // call via the function pointer
}
say_hello(); // 'message' is looked up in the global namespace
script.rhai:
import "greeting" as g;
fn my_msg() {
import "greeting" as g; // <- must import again here...
g::message() // <- ... otherwise will not find module 'g'
}
g::say_hello(Fn("my_msg")); // prints 'Hello!'
```

View File

@ -54,7 +54,34 @@ let fn_name = "hello"; // the function name does not have to exist yet
let hello = Fn(fn_name + "_world"); let hello = Fn(fn_name + "_world");
hello.call(0); // error: function not found - "hello_world (i64)" hello.call(0); // error: function not found - 'hello_world (i64)'
```
Global Namespace Only
--------------------
Because of their dynamic nature, function pointers cannot refer to functions in a _module_ [namespace][function namespace]
(i.e. functions in [`import`]-ed modules). They can only refer to functions within the global [namespace][function namespace].
See [function namespaces] for more details.
```rust
import "foo" as f; // assume there is 'f::do_something()'
f::do_something(); // works!
let p = Fn("f::do_something");
p.call(); // error: function not found - 'f::do_something'
fn do_something_now() { // call it from a local function
import "foo" as f;
f::do_something();
}
let p = Fn("do_something_now");
p.call(); // works!
``` ```

View File

@ -1,7 +1,7 @@
Rhai Language Reference Rhai Language Reference
====================== ======================
{{#include links.md}} {{#include ../links.md}}
This section outlines the Rhai language. This section outlines the Rhai language.

View File

@ -3,9 +3,9 @@ Call Method as Function
{{#include ../links.md}} {{#include ../links.md}}
Property getters/setters and methods in a Rust custom type registered with the [`Engine`] can be called Property [getters/setters] and [methods][custom types] in a Rust custom type registered with the [`Engine`] can be called
just like a regular function. In fact, like Rust, property getters/setters and object methods just like a regular function. In fact, like Rust, property getters/setters and object methods
are registered as regular functions in Rhai that take a first `&mut` parameter. are registered as regular [functions] in Rhai that take a first `&mut` parameter.
Unlike functions defined in script (for which all arguments are passed by _value_), Unlike functions defined in script (for which all arguments are passed by _value_),
native Rust functions may mutate the object (or the first argument if called in normal function call style). native Rust functions may mutate the object (or the first argument if called in normal function call style).

View File

@ -3,7 +3,7 @@ Create a Module from an AST
{{#include ../../links.md}} {{#include ../../links.md}}
It is easy to convert a pre-compiled `AST` into a module: just use `Module::eval_ast_as_new`. It is easy to convert a pre-compiled [`AST`] into a module: just use `Module::eval_ast_as_new`.
Don't forget the [`export`] statement, otherwise there will be no variables exposed by the module Don't forget the [`export`] statement, otherwise there will be no variables exposed by the module
other than non-[`private`] functions (unless that's intentional). other than non-[`private`] functions (unless that's intentional).

View File

@ -3,9 +3,9 @@ Export Variables, Functions and Sub-Modules in Module
{{#include ../../links.md}} {{#include ../../links.md}}
A _module_ is a single script (or pre-compiled `AST`) containing global variables, functions and sub-modules. A _module_ is a single script (or pre-compiled [`AST`]) containing global variables, functions and sub-modules.
A module can be created from a script via the `Module::eval_ast_as_new` method. When given an `AST`, A module can be created from a script via the `Module::eval_ast_as_new` method. When given an [`AST`],
it is first evaluated, then the following items are exposed as members of the new module: it is first evaluated, then the following items are exposed as members of the new module:
* Global variables - essentially all variables that remain in the [`Scope`] at the end of a script run - that are exported. Variables not exported (via the `export` statement) remain hidden. * Global variables - essentially all variables that remain in the [`Scope`] at the end of a script run - that are exported. Variables not exported (via the `export` statement) remain hidden.

View File

@ -3,7 +3,7 @@ Function Overloading
{{#include ../links.md}} {{#include ../links.md}}
Functions defined in script can be _overloaded_ by _arity_ (i.e. they are resolved purely upon the function's _name_ [Functions] defined in script can be _overloaded_ by _arity_ (i.e. they are resolved purely upon the function's _name_
and _number_ of parameters, but not parameter _types_ since all parameters are the same type - [`Dynamic`]). and _number_ of parameters, but not parameter _types_ since all parameters are the same type - [`Dynamic`]).
New definitions _overwrite_ previous definitions of the same name and number of parameters. New definitions _overwrite_ previous definitions of the same name and number of parameters.

View File

@ -15,11 +15,13 @@
[minimal builds]: {{rootUrl}}/start/builds/minimal.md [minimal builds]: {{rootUrl}}/start/builds/minimal.md
[WASM]: {{rootUrl}}/start/builds/wasm.md [WASM]: {{rootUrl}}/start/builds/wasm.md
[playground]: https://alvinhochun.github.io/rhai-demo
[`Engine`]: {{rootUrl}}/engine/hello-world.md [`Engine`]: {{rootUrl}}/engine/hello-world.md
[traits]: {{rootUrl}}/rust/traits.md [traits]: {{rootUrl}}/rust/traits.md
[`private`]: {{rootUrl}}/engine/call-fn.md [`private`]: {{rootUrl}}/engine/call-fn.md
[`Func`]: {{rootUrl}}/engine/func.md [`Func`]: {{rootUrl}}/engine/func.md
[`AST`]: {{rootUrl}}/engine/compile.md
[`eval_expression`]: {{rootUrl}}/engine/expressions.md [`eval_expression`]: {{rootUrl}}/engine/expressions.md
[`eval_expression_with_scope`]: {{rootUrl}}/engine/expressions.md [`eval_expression_with_scope`]: {{rootUrl}}/engine/expressions.md
[raw `Engine`]: {{rootUrl}}/engine/raw.md [raw `Engine`]: {{rootUrl}}/engine/raw.md
@ -38,6 +40,8 @@
[custom type]: {{rootUrl}}/rust/custom.md [custom type]: {{rootUrl}}/rust/custom.md
[custom types]: {{rootUrl}}/rust/custom.md [custom types]: {{rootUrl}}/rust/custom.md
[getters/setters]: {{rootUrl}}/rust/getters-setters.md
[indexers]: {{rootUrl}}/rust/indexers.md
[`instant::Instant`]: https://crates.io/crates/instant [`instant::Instant`]: https://crates.io/crates/instant
@ -70,6 +74,8 @@
[functions]: {{rootUrl}}/language/functions.md [functions]: {{rootUrl}}/language/functions.md
[function pointer]: {{rootUrl}}/language/fn-ptr.md [function pointer]: {{rootUrl}}/language/fn-ptr.md
[function pointers]: {{rootUrl}}/language/fn-ptr.md [function pointers]: {{rootUrl}}/language/fn-ptr.md
[function namespace]: {{rootUrl}}/language/fn-namespaces.md
[function namespaces]: {{rootUrl}}/language/fn-namespaces.md
[`Module`]: {{rootUrl}}/language/modules/index.md [`Module`]: {{rootUrl}}/language/modules/index.md
[module]: {{rootUrl}}/language/modules/index.md [module]: {{rootUrl}}/language/modules/index.md
@ -89,7 +95,7 @@
[maximum length of strings]: {{rootUrl}}/safety/max-string-size.md [maximum length of strings]: {{rootUrl}}/safety/max-string-size.md
[maximum size of arrays]: {{rootUrl}}/safety/max-array-size.md [maximum size of arrays]: {{rootUrl}}/safety/max-array-size.md
[maximum size of object maps]: {{rootUrl}}/safety/max-map-size.md [maximum size of object maps]: {{rootUrl}}/safety/max-map-size.md
[progress]:/safety/progress.md [progress]: {{rootUrl}}/safety/progress.md
[script optimization]: {{rootUrl}}/engine/optimize/index.md [script optimization]: {{rootUrl}}/engine/optimize/index.md
[`OptimizationLevel::Full`]: {{rootUrl}}/engine/optimize/optimize-levels.md [`OptimizationLevel::Full`]: {{rootUrl}}/engine/optimize/optimize-levels.md

View File

@ -1,7 +1,7 @@
Extend Rhai with Rust Extend Rhai with Rust
==================== ====================
{{#include links.md}} {{#include ../links.md}}
Most features and functionalities required by a Rhai script should actually be coded in Rust, Most features and functionalities required by a Rhai script should actually be coded in Rust,
which leverages the superior native run-time speed. which leverages the superior native run-time speed.

View File

@ -1 +0,0 @@
# Safety and Protection

View File

@ -25,7 +25,7 @@ This limit may be changed via the `Engine::set_max_expr_depths` method.
There are two limits to set, one for the maximum depth at global level, and the other for function bodies. There are two limits to set, one for the maximum depth at global level, and the other for function bodies.
A script exceeding the maximum nesting depths will terminate with a parsing error. A script exceeding the maximum nesting depths will terminate with a parsing error.
The malicious `AST` will not be able to get past parsing in the first place. The malicious [`AST`] will not be able to get past parsing in the first place.
This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).

View File

@ -14,7 +14,7 @@ more control over what a script can (or cannot) do.
| Feature | Description | | Feature | Description |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unchecked` | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.<br/>Beware that a bad script may panic the entire system! | | `unchecked` | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit.<br/>Beware that a bad script may panic the entire system! |
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and `AST`, are all `Send + Sync`. | | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and [`AST`], are all `Send + Sync`. |
| `no_optimize` | Disable [script optimization]. | | `no_optimize` | Disable [script optimization]. |
| `no_float` | Disable floating-point numbers and math. | | `no_float` | Disable floating-point numbers and math. |
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
@ -24,7 +24,7 @@ more control over what a script can (or cannot) do.
| `no_function` | Disable script-defined [functions]. | | `no_function` | Disable script-defined [functions]. |
| `no_module` | Disable loading external [modules]. | | `no_module` | Disable loading external [modules]. |
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
| `internals` | Expose internal data structures (e.g. `AST` nodes). Beware that Rhai internals are volatile and may change from version to version. | | `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. |
Example Example

View File

@ -1,6 +1,6 @@
Getting Started Getting Started
=============== ===============
{{#include links.md}} {{#include ../links.md}}
This section shows how to install the Rhai crate into a Rust application. This section shows how to install the Rhai crate into a Rust application.

View File

@ -3,8 +3,10 @@ Install the Rhai Crate
{{#include ../links.md}} {{#include ../links.md}}
Install the Rhai crate from [`crates.io`](https:/crates.io/crates/rhai/), start by looking up the In order to use Rhai in a project, the Rhai crate must first be made a dependency.
latest version and adding this line under `dependencies` in `Cargo.toml`:
The easiest way is to install the Rhai crate from [`crates.io`](https:/crates.io/crates/rhai/),
starting by looking up the latest version and adding this line under `dependencies` in the project's `Cargo.toml`:
```toml ```toml
[dependencies] [dependencies]

View File

@ -0,0 +1,10 @@
Online Playground
=================
{{#include ../links.md}}
Rhai provides an [online playground][playground] to try out its language and engine features
without having to install anything.
The playground provides a syntax-highlighting script editor with example snippets.
Scripts can be evaluated directly from the editor.

BIN
rhai_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -545,6 +545,7 @@ impl Dynamic {
pub fn as_str(&self) -> Result<&str, &'static str> { pub fn as_str(&self) -> Result<&str, &'static str> {
match &self.0 { match &self.0 {
Union::Str(s) => Ok(s), Union::Str(s) => Ok(s),
Union::FnPtr(f) => Ok(f.fn_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -561,15 +562,7 @@ impl Dynamic {
pub(crate) fn take_immutable_string(self) -> Result<ImmutableString, &'static str> { pub(crate) fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
match self.0 { match self.0 {
Union::Str(s) => Ok(s), Union::Str(s) => Ok(s),
_ => Err(self.type_name()), Union::FnPtr(f) => Ok(f.take_fn_name()),
}
}
/// Cast the `Dynamic` as a `FnPtr` and return the function name.
/// Returns the name of the actual type if the cast fails.
pub(crate) fn as_fn_name(&self) -> Result<&str, &'static str> {
match &self.0 {
Union::FnPtr(f) => Ok(f.fn_name()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }

View File

@ -422,7 +422,7 @@ impl Engine {
self.register_indexer_set(setter); self.register_indexer_set(setter);
} }
/// Compile a string into an `AST`, which can be used later for evaluation. /// Compile a string into an [`AST`], which can be used later for evaluation.
/// ///
/// # Example /// # Example
/// ///
@ -445,7 +445,7 @@ impl Engine {
self.compile_with_scope(&Scope::new(), script) self.compile_with_scope(&Scope::new(), script)
} }
/// Compile a string into an `AST` using own scope, which can be used later for evaluation. /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation.
/// ///
/// The scope is useful for passing constants into the script for optimization /// The scope is useful for passing constants into the script for optimization
/// when using `OptimizationLevel::Full`. /// when using `OptimizationLevel::Full`.
@ -488,7 +488,7 @@ impl Engine {
} }
/// When passed a list of strings, first join the strings into one large script, /// When passed a list of strings, first join the strings into one large script,
/// and then compile them into an `AST` using own scope, which can be used later for evaluation. /// and then compile them into an [`AST`] using own scope, which can be used later for evaluation.
/// ///
/// The scope is useful for passing constants into the script for optimization /// The scope is useful for passing constants into the script for optimization
/// when using `OptimizationLevel::Full`. /// when using `OptimizationLevel::Full`.
@ -541,7 +541,7 @@ impl Engine {
self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level) self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level)
} }
/// Join a list of strings and compile into an `AST` using own scope at a specific optimization level. /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level.
pub(crate) fn compile_with_scope_and_optimization_level( pub(crate) fn compile_with_scope_and_optimization_level(
&self, &self,
scope: &Scope, scope: &Scope,
@ -577,7 +577,7 @@ impl Engine {
Ok(contents) Ok(contents)
} }
/// Compile a script file into an `AST`, which can be used later for evaluation. /// Compile a script file into an [`AST`], which can be used later for evaluation.
/// ///
/// # Example /// # Example
/// ///
@ -603,7 +603,7 @@ impl Engine {
self.compile_file_with_scope(&Scope::new(), path) self.compile_file_with_scope(&Scope::new(), path)
} }
/// Compile a script file into an `AST` using own scope, which can be used later for evaluation. /// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation.
/// ///
/// The scope is useful for passing constants into the script for optimization /// The scope is useful for passing constants into the script for optimization
/// when using `OptimizationLevel::Full`. /// when using `OptimizationLevel::Full`.
@ -685,7 +685,7 @@ impl Engine {
self.eval_ast_with_scope(&mut scope, &ast) self.eval_ast_with_scope(&mut scope, &ast)
} }
/// Compile a string containing an expression into an `AST`, /// Compile a string containing an expression into an [`AST`],
/// which can be used later for evaluation. /// which can be used later for evaluation.
/// ///
/// # Example /// # Example
@ -709,7 +709,7 @@ impl Engine {
self.compile_expression_with_scope(&Scope::new(), script) self.compile_expression_with_scope(&Scope::new(), script)
} }
/// Compile a string containing an expression into an `AST` using own scope, /// Compile a string containing an expression into an [`AST`] using own scope,
/// which can be used later for evaluation. /// which can be used later for evaluation.
/// ///
/// The scope is useful for passing constants into the script for optimization /// The scope is useful for passing constants into the script for optimization
@ -917,7 +917,7 @@ impl Engine {
self.eval_ast_with_scope(scope, &ast) self.eval_ast_with_scope(scope, &ast)
} }
/// Evaluate an `AST`. /// Evaluate an [`AST`].
/// ///
/// # Example /// # Example
/// ///
@ -939,7 +939,7 @@ impl Engine {
self.eval_ast_with_scope(&mut Scope::new(), ast) self.eval_ast_with_scope(&mut Scope::new(), ast)
} }
/// Evaluate an `AST` with own scope. /// Evaluate an [`AST`] with own scope.
/// ///
/// # Example /// # Example
/// ///
@ -986,7 +986,7 @@ impl Engine {
}); });
} }
/// Evaluate an `AST` with own scope. /// Evaluate an [`AST`] with own scope.
pub(crate) fn eval_ast_with_scope_raw<'a>( pub(crate) fn eval_ast_with_scope_raw<'a>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -1052,7 +1052,7 @@ impl Engine {
self.consume_ast_with_scope(&mut Scope::new(), ast) self.consume_ast_with_scope(&mut Scope::new(), ast)
} }
/// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). /// Evaluate an [`AST`] with own scope, 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.
pub fn consume_ast_with_scope( pub fn consume_ast_with_scope(
&self, &self,
@ -1076,7 +1076,7 @@ impl Engine {
) )
} }
/// 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.
/// ///
/// # Example /// # Example
@ -1133,7 +1133,7 @@ impl Engine {
}); });
} }
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments. /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments.
/// ///
/// # Example /// # Example
/// ///
@ -1179,7 +1179,7 @@ impl Engine {
self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut()) self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut())
} }
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments. /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments.
/// ///
/// ## WARNING /// ## WARNING
/// ///
@ -1220,15 +1220,15 @@ impl Engine {
) )
} }
/// Optimize the `AST` with constants defined in an external Scope. /// Optimize the [`AST`] with constants defined in an external Scope.
/// An optimized copy of the `AST` is returned while the original `AST` is consumed. /// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed.
/// ///
/// Although optimization is performed by default during compilation, sometimes it is necessary to /// Although optimization is performed by default during compilation, sometimes it is necessary to
/// _re_-optimize an AST. For example, when working with constants that are passed in via an /// _re_-optimize an AST. For example, when working with constants that are passed in via an
/// external scope, it will be more efficient to optimize the `AST` once again to take advantage /// external scope, it will be more efficient to optimize the [`AST`] once again to take advantage
/// of the new constants. /// of the new constants.
/// ///
/// With this method, it is no longer necessary to recompile a large script. The script `AST` can be /// With this method, it is no longer necessary to recompile a large script. The script [`AST`] can be
/// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope /// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope
/// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running. /// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running.
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]

View File

@ -8,10 +8,10 @@ use crate::module::{resolvers, Module, ModuleRef, ModuleResolver};
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::packages::{Package, PackageLibrary, PackagesCollection, StandardPackage}; use crate::packages::{Package, PackageLibrary, PackagesCollection, StandardPackage};
use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, Stmt, AST, INT}; use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, Stmt, AST, INT};
use crate::r#unsafe::{unsafe_cast_var_name_to_lifetime, unsafe_mut_cast_to_lifetime}; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::token::Position; use crate::token::{is_valid_identifier, Position};
use crate::utils::StaticVec; use crate::utils::StaticVec;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -75,6 +75,7 @@ pub const KEYWORD_PRINT: &str = "print";
pub const KEYWORD_DEBUG: &str = "debug"; pub const KEYWORD_DEBUG: &str = "debug";
pub const KEYWORD_TYPE_OF: &str = "type_of"; pub const KEYWORD_TYPE_OF: &str = "type_of";
pub const KEYWORD_EVAL: &str = "eval"; pub const KEYWORD_EVAL: &str = "eval";
pub const KEYWORD_FN_PTR: &str = "Fn";
pub const KEYWORD_FN_PTR_CALL: &str = "call"; pub const KEYWORD_FN_PTR_CALL: &str = "call";
pub const KEYWORD_THIS: &str = "this"; pub const KEYWORD_THIS: &str = "this";
pub const FN_TO_STRING: &str = "to_string"; pub const FN_TO_STRING: &str = "to_string";
@ -82,7 +83,6 @@ pub const FN_GET: &str = "get$";
pub const FN_SET: &str = "set$"; pub const FN_SET: &str = "set$";
pub const FN_IDX_GET: &str = "$index$get$"; pub const FN_IDX_GET: &str = "$index$get$";
pub const FN_IDX_SET: &str = "$index$set$"; pub const FN_IDX_SET: &str = "$index$set$";
pub const FN_FN_PTR: &str = "Fn";
/// A type specifying the method of chaining. /// A type specifying the method of chaining.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
@ -729,10 +729,17 @@ impl Engine {
// Replace the first reference with a reference to the clone, force-casting the lifetime. // Replace the first reference with a reference to the clone, force-casting the lifetime.
// Keep the original reference. Must remember to restore it later with `restore_first_arg_of_method_call`. // Keep the original reference. Must remember to restore it later with `restore_first_arg_of_method_call`.
let this_pointer = mem::replace( //
args.get_mut(0).unwrap(), // # Safety
unsafe_mut_cast_to_lifetime(this_copy), //
); // Blindly casting a a reference to another lifetime saves on allocations and string cloning,
// but must be used with the utmost care.
//
// We can do this here because, at the end of this scope, we'd restore the original reference
// with `restore_first_arg_of_method_call`. Therefore this shorter lifetime does not get "out".
let this_pointer = mem::replace(args.get_mut(0).unwrap(), unsafe {
mem::transmute(this_copy)
});
*old_this_ptr = Some(this_pointer); *old_this_ptr = Some(this_pointer);
} }
@ -1020,7 +1027,7 @@ impl Engine {
)), )),
// Fn // Fn
FN_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes) => { KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes) => {
Err(Box::new(EvalAltResult::ErrorRuntime( Err(Box::new(EvalAltResult::ErrorRuntime(
"'Fn' should not be called in method style. Try Fn(...);".into(), "'Fn' should not be called in method style. Try Fn(...);".into(),
Position::none(), Position::none(),
@ -1133,7 +1140,7 @@ impl Engine {
state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level, state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level,
new_val, new_val,
) )
.map_err(|err| EvalAltResult::new_position(err, *pos)) .map_err(|err| err.new_position(*pos))
} }
// xxx[rhs] = new_val // xxx[rhs] = new_val
_ if new_val.is_some() => { _ if new_val.is_some() => {
@ -1162,9 +1169,9 @@ 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()).map_err(|err| { obj_ptr
EvalAltResult::new_position(err, rhs.position()) .set_value(new_val.unwrap())
})?; .map_err(|err| err.new_position(rhs.position()))?;
} }
Err(err) => match *err { Err(err) => match *err {
// No index getter - try to call an index setter // No index getter - try to call an index setter
@ -1205,11 +1212,12 @@ impl Engine {
let (result, updated) = { let (result, updated) = {
let obj = target.as_mut(); let obj = target.as_mut();
let idx = idx_val.downcast_mut::<StaticVec<Dynamic>>().unwrap(); let idx = idx_val.downcast_mut::<StaticVec<Dynamic>>().unwrap();
let mut fn_name = name.as_ref();
// Check if it is a FnPtr call // Check if it is a FnPtr call
if name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() { if fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
// Redirect function name // Redirect function name
let fn_name = obj.as_fn_name().unwrap(); fn_name = obj.as_str().unwrap();
// Recalculate hash // Recalculate hash
let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty()); let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty());
// Arguments are passed as-is // Arguments are passed as-is
@ -1222,25 +1230,20 @@ impl Engine {
def_val, level, def_val, level,
) )
} else { } else {
let mut fn_name = name.clone(); let redirected: Option<ImmutableString>;
let mut redirected = None;
let mut hash = *hash; let mut hash = *hash;
// Check if it is a map method call in OOP style // Check if it is a map method call in OOP style
if let Some(map) = obj.downcast_ref::<Map>() { if let Some(map) = obj.downcast_ref::<Map>() {
if let Some(val) = map.get(name.as_ref()) { if let Some(val) = map.get(fn_name) {
if let Some(f) = val.downcast_ref::<FnPtr>() { if let Some(f) = val.downcast_ref::<FnPtr>() {
// Remap the function name // Remap the function name
redirected = Some(f.get_fn_name().clone()); redirected = Some(f.get_fn_name().clone());
fn_name = redirected.as_ref().unwrap().as_str().into(); fn_name = redirected.as_ref().unwrap();
// Recalculate the hash based on the new function name // Recalculate the hash based on the new function name
hash = calc_fn_hash( hash =
empty(), calc_fn_hash(empty(), fn_name, idx.len(), empty());
fn_name.as_ref(),
idx.len(),
empty(),
);
} }
} }
}; };
@ -1249,14 +1252,13 @@ impl Engine {
let mut arg_values = let mut arg_values =
once(obj).chain(idx.iter_mut()).collect::<StaticVec<_>>(); once(obj).chain(idx.iter_mut()).collect::<StaticVec<_>>();
let args = arg_values.as_mut(); let args = arg_values.as_mut();
let fn_name = fn_name.as_ref();
self.exec_fn_call( self.exec_fn_call(
state, lib, fn_name, *native, hash, args, is_ref, true, state, lib, fn_name, *native, hash, args, is_ref, true,
def_val, level, def_val, level,
) )
} }
.map_err(|err| EvalAltResult::new_position(err, *pos))? .map_err(|err| err.new_position(*pos))?
}; };
// Feed the changed temp value back // Feed the changed temp value back
@ -1277,7 +1279,7 @@ impl Engine {
self.get_indexed_mut(state, lib, target, index, *pos, true, level)?; self.get_indexed_mut(state, lib, target, index, *pos, true, level)?;
val.set_value(new_val.unwrap()) val.set_value(new_val.unwrap())
.map_err(|err| EvalAltResult::new_position(err, rhs.position()))?; .map_err(|err| err.new_position(rhs.position()))?;
Ok((Default::default(), true)) Ok((Default::default(), true))
} }
// {xxx:map}.id // {xxx:map}.id
@ -1297,7 +1299,7 @@ impl Engine {
state, lib, setter, true, 0, &mut args, is_ref, true, None, level, state, lib, setter, true, 0, &mut args, is_ref, true, None, level,
) )
.map(|(v, _)| (v, true)) .map(|(v, _)| (v, true))
.map_err(|err| EvalAltResult::new_position(err, *pos)) .map_err(|err| err.new_position(*pos))
} }
// xxx.id // xxx.id
Expr::Property(x) => { Expr::Property(x) => {
@ -1307,7 +1309,7 @@ impl Engine {
state, lib, getter, true, 0, &mut args, is_ref, true, None, level, state, lib, getter, true, 0, &mut args, is_ref, true, None, level,
) )
.map(|(v, _)| (v, false)) .map(|(v, _)| (v, false))
.map_err(|err| EvalAltResult::new_position(err, *pos)) .map_err(|err| err.new_position(*pos))
} }
// {xxx:map}.prop[expr] | {xxx:map}.prop.expr // {xxx:map}.prop[expr] | {xxx:map}.prop.expr
Expr::Index(x) | Expr::Dot(x) if target.is::<Map>() => { Expr::Index(x) | Expr::Dot(x) if target.is::<Map>() => {
@ -1325,7 +1327,7 @@ impl Engine {
state, lib, this_ptr, &mut val, expr, idx_values, next_chain, level, state, lib, this_ptr, &mut val, expr, idx_values, next_chain, level,
new_val, new_val,
) )
.map_err(|err| EvalAltResult::new_position(err, *pos)) .map_err(|err| err.new_position(*pos))
} }
// xxx.prop[expr] | xxx.prop.expr // xxx.prop[expr] | xxx.prop.expr
Expr::Index(x) | Expr::Dot(x) => { Expr::Index(x) | Expr::Dot(x) => {
@ -1338,7 +1340,7 @@ impl Engine {
self.exec_fn_call( self.exec_fn_call(
state, lib, getter, true, 0, args, is_ref, true, None, level, state, lib, getter, true, 0, args, is_ref, true, None, level,
) )
.map_err(|err| EvalAltResult::new_position(err, *pos))? .map_err(|err| err.new_position(*pos))?
} else { } else {
unreachable!(); unreachable!();
}; };
@ -1350,7 +1352,7 @@ impl Engine {
state, lib, this_ptr, target, expr, idx_values, next_chain, level, state, lib, this_ptr, target, expr, idx_values, next_chain, level,
new_val, new_val,
) )
.map_err(|err| EvalAltResult::new_position(err, *pos))?; .map_err(|err| err.new_position(*pos))?;
// Feed the value back via a setter just in case it has been updated // Feed the value back via a setter just in case it has been updated
if updated || may_be_changed { if updated || may_be_changed {
@ -1364,7 +1366,7 @@ impl Engine {
.or_else(|err| match *err { .or_else(|err| match *err {
// If there is no setter, no need to feed it back because the property is read-only // If there is no setter, no need to feed it back because the property is read-only
EvalAltResult::ErrorDotExpr(_, _) => Ok(Default::default()), EvalAltResult::ErrorDotExpr(_, _) => Ok(Default::default()),
err => Err(EvalAltResult::new_position(Box::new(err), *pos)), _ => Err(err.new_position(*pos)),
})?; })?;
} }
} }
@ -1413,7 +1415,7 @@ impl Engine {
let (var_name, var_pos) = &x.0; let (var_name, var_pos) = &x.0;
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| EvalAltResult::new_position(err, *var_pos))?; .map_err(|err| err.new_position(*var_pos))?;
let (target, _, typ, pos) = search_scope(scope, mods, state, this_ptr, dot_lhs)?; let (target, _, typ, pos) = search_scope(scope, mods, state, this_ptr, dot_lhs)?;
@ -1433,7 +1435,7 @@ impl Engine {
state, lib, &mut None, obj_ptr, dot_rhs, idx_values, chain_type, level, new_val, state, lib, &mut None, obj_ptr, dot_rhs, idx_values, chain_type, level, new_val,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
.map_err(|err| EvalAltResult::new_position(err, *op_pos)) .map_err(|err| err.new_position(*op_pos))
} }
// {expr}.??? = ??? or {expr}[???] = ??? // {expr}.??? = ??? or {expr}[???] = ???
expr if new_val.is_some() => { expr if new_val.is_some() => {
@ -1449,7 +1451,7 @@ impl Engine {
state, lib, this_ptr, obj_ptr, dot_rhs, idx_values, chain_type, level, new_val, state, lib, this_ptr, obj_ptr, dot_rhs, idx_values, chain_type, level, new_val,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
.map_err(|err| EvalAltResult::new_position(err, *op_pos)) .map_err(|err| err.new_position(*op_pos))
} }
} }
} }
@ -1472,7 +1474,7 @@ impl Engine {
level: usize, level: usize,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| EvalAltResult::new_position(err, expr.position()))?; .map_err(|err| err.new_position(expr.position()))?;
match expr { match expr {
Expr::FnCall(x) if x.1.is_none() => { Expr::FnCall(x) if x.1.is_none() => {
@ -1627,7 +1629,7 @@ impl Engine {
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| EvalAltResult::new_position(err, rhs.position()))?; .map_err(|err| err.new_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)?;
@ -1655,7 +1657,7 @@ impl Engine {
&mut scope, mods, state, lib, op, hashes, args, false, false, &mut scope, mods, state, lib, op, hashes, args, false, false,
def_value, level, def_value, level,
) )
.map_err(|err| EvalAltResult::new_position(err, rhs.position()))?; .map_err(|err| err.new_position(rhs.position()))?;
if r.as_bool().unwrap_or(false) { if r.as_bool().unwrap_or(false) {
return Ok(true.into()); return Ok(true.into());
} }
@ -1694,7 +1696,7 @@ impl Engine {
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| EvalAltResult::new_position(err, expr.position()))?; .map_err(|err| err.new_position(expr.position()))?;
let result = match expr { let result = match expr {
Expr::Expr(x) => self.eval_expr(scope, mods, state, lib, this_ptr, x.as_ref(), level), Expr::Expr(x) => self.eval_expr(scope, mods, state, lib, this_ptr, x.as_ref(), level),
@ -1728,7 +1730,7 @@ impl Engine {
let (lhs_ptr, name, typ, pos) = let (lhs_ptr, name, typ, pos) =
search_scope(scope, mods, state, this_ptr, lhs_expr)?; search_scope(scope, mods, state, this_ptr, lhs_expr)?;
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| EvalAltResult::new_position(err, pos))?; .map_err(|err| err.new_position(pos))?;
match typ { match typ {
// Assignment to constant variable // Assignment to constant variable
@ -1769,7 +1771,7 @@ impl Engine {
state, lib, op, true, hash, args, false, false, None, level, state, lib, op, true, hash, args, false, false, None, level,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
.map_err(|err| EvalAltResult::new_position(err, *op_pos))?; .map_err(|err| err.new_position(*op_pos))?;
} }
Ok(Default::default()) Ok(Default::default())
} }
@ -1795,7 +1797,7 @@ impl Engine {
]; ];
self.exec_fn_call(state, lib, op, true, hash, args, false, false, None, level) self.exec_fn_call(state, lib, op, true, hash, args, false, false, None, level)
.map(|(v, _)| v) .map(|(v, _)| v)
.map_err(|err| EvalAltResult::new_position(err, *op_pos))? .map_err(|err| err.new_position(*op_pos))?
}); });
match lhs_expr { match lhs_expr {
@ -1859,8 +1861,8 @@ impl Engine {
let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref(); let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref();
let def_val = def_val.as_ref(); let def_val = def_val.as_ref();
// Handle Fn // Handle Fn()
if name == FN_FN_PTR && args_expr.len() == 1 { if name == KEYWORD_FN_PTR && args_expr.len() == 1 {
let hash_fn = let hash_fn =
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>())); calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
@ -1871,17 +1873,27 @@ impl Engine {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
return arg_value return arg_value
.take_immutable_string() .take_immutable_string()
.map(|s| FnPtr::from(s).into())
.map_err(|type_name| { .map_err(|type_name| {
Box::new(EvalAltResult::ErrorMismatchOutputType( Box::new(EvalAltResult::ErrorMismatchOutputType(
type_name.into(), type_name.into(),
Position::none(), expr.position(),
)) ))
}); })
.and_then(|s| {
if is_valid_identifier(s.chars()) {
Ok(s)
} else {
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
s.to_string(),
expr.position(),
)))
}
})
.map(|s| FnPtr::from(s).into());
} }
} }
// Handle eval // Handle eval()
if name == KEYWORD_EVAL && args_expr.len() == 1 { if name == KEYWORD_EVAL && args_expr.len() == 1 {
let hash_fn = let hash_fn =
calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>())); calc_fn_hash(empty(), name, 1, once(TypeId::of::<ImmutableString>()));
@ -1894,7 +1906,7 @@ impl Engine {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let result = self let result = self
.eval_script_expr(scope, mods, state, lib, &script) .eval_script_expr(scope, mods, state, lib, &script)
.map_err(|err| EvalAltResult::new_position(err, expr.position())); .map_err(|err| err.new_position(expr.position()));
if scope.len() != prev_len { if scope.len() != prev_len {
// IMPORTANT! If the eval defines new variables in the current scope, // IMPORTANT! If the eval defines new variables in the current scope,
@ -1932,7 +1944,7 @@ impl Engine {
search_scope(scope, mods, state, this_ptr, lhs)?; search_scope(scope, mods, state, this_ptr, lhs)?;
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| EvalAltResult::new_position(err, pos))?; .map_err(|err| err.new_position(pos))?;
args = once(target).chain(arg_values.iter_mut()).collect(); args = once(target).chain(arg_values.iter_mut()).collect();
@ -1957,7 +1969,7 @@ impl Engine {
state, lib, name, *native, *hash, args, is_ref, false, def_val, level, state, lib, name, *native, *hash, args, is_ref, false, def_val, level,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
.map_err(|err| EvalAltResult::new_position(err, *pos)) .map_err(|err| err.new_position(*pos))
} }
// Module-qualified function call // Module-qualified function call
@ -1979,7 +1991,7 @@ impl Engine {
Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => { Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => {
// Then search in Rust functions // Then search in Rust functions
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| EvalAltResult::new_position(err, *pos))?; .map_err(|err| err.new_position(*pos))?;
// Qualified Rust functions are indexed in two steps: // Qualified Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions, // 1) Calculate a hash in a similar manner to script-defined functions,
@ -2005,7 +2017,7 @@ impl Engine {
self.call_script_fn( self.call_script_fn(
&mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level, &mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level,
) )
.map_err(|err| EvalAltResult::new_position(err, *pos)) .map_err(|err| err.new_position(*pos))
} }
Ok(f) => { Ok(f) => {
f.get_native_fn()(self, args.as_mut()).map_err(|err| err.new_position(*pos)) f.get_native_fn()(self, args.as_mut()).map_err(|err| err.new_position(*pos))
@ -2071,6 +2083,7 @@ impl Engine {
}; };
self.check_data_size(result) self.check_data_size(result)
.map_err(|err| err.new_position(expr.position()))
} }
/// Evaluate a statement /// Evaluate a statement
@ -2085,7 +2098,7 @@ impl Engine {
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| EvalAltResult::new_position(err, stmt.position()))?; .map_err(|err| err.new_position(stmt.position()))?;
let result = match stmt { let result = match stmt {
// No-op // No-op
@ -2200,7 +2213,7 @@ impl Engine {
for loop_var in func(iter_type) { for loop_var in func(iter_type) {
*scope.get_mut(index).0 = loop_var; *scope.get_mut(index).0 = loop_var;
self.inc_operations(state) self.inc_operations(state)
.map_err(|err| EvalAltResult::new_position(err, stmt.position()))?; .map_err(|err| err.new_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(_) => (),
@ -2368,9 +2381,11 @@ impl Engine {
}; };
self.check_data_size(result) self.check_data_size(result)
.map_err(|err| err.new_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.
/// Position in `EvalAltResult` may be None and should be set afterwards.
fn check_data_size( fn check_data_size(
&self, &self,
result: Result<Dynamic, Box<EvalAltResult>>, result: Result<Dynamic, Box<EvalAltResult>>,

View File

@ -15,8 +15,8 @@ use crate::stdlib::{boxed::Box, string::ToString};
pub trait Func<ARGS, RET> { pub trait Func<ARGS, RET> {
type Output; type Output;
/// Create a Rust anonymous function from an `AST`. /// Create a Rust anonymous function from an [`AST`].
/// The `Engine` and `AST` are consumed and basically embedded into the closure. /// The `Engine` and [`AST`] are consumed and basically embedded into the closure.
/// ///
/// # Examples /// # Examples
/// ///

View File

@ -67,6 +67,10 @@ impl FnPtr {
pub(crate) fn get_fn_name(&self) -> &ImmutableString { pub(crate) fn get_fn_name(&self) -> &ImmutableString {
&self.0 &self.0
} }
/// Get the name of the function.
pub(crate) fn take_fn_name(self) -> ImmutableString {
self.0
}
} }
impl fmt::Display for FnPtr { impl fmt::Display for FnPtr {

View File

@ -62,7 +62,7 @@
//! | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | //! | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
//! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | //! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | //! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and `AST` are all `Send + Sync`. | //! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and [`AST`] are all `Send + Sync`. |
//! | `internals` | Expose internal data structures (beware they may be volatile from version to version). | //! | `internals` | Expose internal data structures (beware they may be volatile from version to version). |
//! //!
//! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language. //! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language.

View File

@ -901,7 +901,7 @@ impl Module {
.map(|f| f.get_shared_fn_def()) .map(|f| f.get_shared_fn_def())
} }
/// Create a new `Module` by evaluating an `AST`. /// Create a new `Module` by evaluating an [`AST`].
/// ///
/// # Examples /// # Examples
/// ///

View File

@ -42,12 +42,10 @@ fn pad<T: Variant + Clone>(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncRe
} }
if len >= 0 { if len >= 0 {
let item = args[2].downcast_ref::<T>().unwrap().clone(); let item = args[2].clone();
let list = args[0].downcast_mut::<Array>().unwrap(); let list = args[0].downcast_mut::<Array>().unwrap();
while list.len() < len as usize { list.resize(len as usize, item);
push(list, item.clone())?;
}
} }
Ok(()) Ok(())
} }

View File

@ -245,16 +245,21 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
let ch = *args[2].downcast_ref::< char>().unwrap(); let ch = *args[2].downcast_ref::< char>().unwrap();
let s = args[0].downcast_mut::<ImmutableString>().unwrap(); let s = args[0].downcast_mut::<ImmutableString>().unwrap();
let copy = s.make_mut(); let orig_len = s.chars().count();
for _ in 0..copy.chars().count() - len as usize {
copy.push(ch); if orig_len < len as usize {
let p = s.make_mut();
for _ in 0..(len as usize - orig_len) {
p.push(ch);
}
} }
if engine.max_string_size > 0 && copy.len() > engine.max_string_size { if engine.max_string_size > 0 && s.len() > engine.max_string_size {
Err(Box::new(EvalAltResult::ErrorDataTooLarge( Err(Box::new(EvalAltResult::ErrorDataTooLarge(
"Length of string".to_string(), "Length of string".to_string(),
engine.max_string_size, engine.max_string_size,
copy.len(), s.len(),
Position::none(), Position::none(),
))) )))
} else { } else {

View File

@ -49,7 +49,7 @@ pub use crate::utils::ImmutableString;
/// Compiled AST (abstract syntax tree) of a Rhai script. /// Compiled AST (abstract syntax tree) of a Rhai script.
/// ///
/// 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, Default)] #[derive(Debug, Clone, Default)]
pub struct AST( pub struct AST(
/// Global statements. /// Global statements.
@ -59,7 +59,7 @@ pub struct AST(
); );
impl AST { impl AST {
/// Create a new `AST`. /// Create a new [`AST`].
pub fn new(statements: Vec<Stmt>, lib: Module) -> Self { pub fn new(statements: Vec<Stmt>, lib: Module) -> Self {
Self(statements, lib) Self(statements, lib)
} }
@ -95,16 +95,16 @@ impl AST {
&self.1 &self.1
} }
/// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version
/// is returned. /// is returned.
/// ///
/// The second `AST` is simply appended to the end of the first _without any processing_. /// The second [`AST`] is simply appended to the end of the first _without any processing_.
/// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried.
/// Of course, if the first `AST` uses a `return` statement at the end, then /// Of course, if the first [`AST`] uses a `return` statement at the end, then
/// the second `AST` will essentially be dead code. /// the second [`AST`] will essentially be dead code.
/// ///
/// All script-defined functions in the second `AST` overwrite similarly-named functions /// All script-defined functions in the second [`AST`] overwrite similarly-named functions
/// in the first `AST` with the same number of parameters. /// in the first [`AST`] with the same number of parameters.
/// ///
/// # Example /// # Example
/// ///
@ -157,13 +157,13 @@ impl AST {
Self::new(ast, functions) Self::new(ast, functions)
} }
/// Clear all function definitions in the `AST`. /// Clear all function definitions in the [`AST`].
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub fn clear_functions(&mut self) { pub fn clear_functions(&mut self) {
self.1 = Default::default(); self.1 = Default::default();
} }
/// Clear all statements in the `AST`, leaving only function definitions. /// Clear all statements in the [`AST`], leaving only function definitions.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub fn retain_functions(&mut self) { pub fn retain_functions(&mut self) {
self.0 = vec![]; self.0 = vec![];
@ -802,22 +802,22 @@ fn parse_call_expr(
mut modules: Option<Box<ModuleRef>>, mut modules: Option<Box<ModuleRef>>,
settings: ParseSettings, settings: ParseSettings,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
let (token, _) = input.peek().unwrap(); let (token, token_pos) = input.peek().unwrap();
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let mut args = StaticVec::new(); let mut args = StaticVec::new();
match token { match token {
// id <EOF> // id( <EOF>
Token::EOF => { Token::EOF => {
return Err(PERR::MissingToken( return Err(PERR::MissingToken(
Token::RightParen.into(), Token::RightParen.into(),
format!("to close the arguments list of this function call '{}'", id), format!("to close the arguments list of this function call '{}'", id),
) )
.into_err(settings.pos)) .into_err(*token_pos))
} }
// id <error> // id( <error>
Token::LexError(err) => return Err(err.into_err(settings.pos)), Token::LexError(err) => return Err(err.into_err(*token_pos)),
// id() // id()
Token::RightParen => { Token::RightParen => {
eat_token(input, Token::RightParen); eat_token(input, Token::RightParen);
@ -1259,8 +1259,8 @@ fn parse_primary(
state: &mut ParseState, state: &mut ParseState,
mut settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
let (token, pos1) = input.peek().unwrap(); let (token, token_pos) = input.peek().unwrap();
settings.pos = *pos1; settings.pos = *token_pos;
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let (token, _) = match token { let (token, _) = match token {

View File

@ -453,6 +453,22 @@ pub trait InputStream {
fn peek_next(&mut self) -> Option<char>; fn peek_next(&mut self) -> Option<char>;
} }
pub fn is_valid_identifier(name: impl Iterator<Item = char>) -> bool {
let mut first_alphabetic = false;
for ch in name {
match ch {
'_' => (),
_ if char::is_ascii_alphabetic(&ch) => first_alphabetic = true,
_ if !first_alphabetic => return false,
_ if char::is_ascii_alphanumeric(&ch) => (),
_ => return false,
}
}
first_alphabetic
}
/// Parse a string literal wrapped by `enclosing_char`. /// Parse a string literal wrapped by `enclosing_char`.
pub fn parse_string_literal( pub fn parse_string_literal(
stream: &mut impl InputStream, stream: &mut impl InputStream,
@ -783,13 +799,9 @@ fn get_next_token_inner(
} }
} }
let is_valid_identifier = result let is_valid_identifier = is_valid_identifier(result.iter().cloned());
.iter()
.find(|&ch| char::is_ascii_alphanumeric(ch)) // first alpha-numeric character
.map(char::is_ascii_alphabetic) // is a letter
.unwrap_or(false); // if no alpha-numeric at all - syntax error
let identifier: String = result.iter().collect(); let identifier: String = result.into_iter().collect();
if !is_valid_identifier { if !is_valid_identifier {
return Some(( return Some((

View File

@ -42,16 +42,6 @@ pub fn unsafe_cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, B
} }
} }
/// # DANGEROUS!!!
///
/// A dangerous function that blindly casts a reference from one lifetime to another lifetime.
///
/// Force-casting a a reference to another lifetime saves on allocations and string cloning,
/// but must be used with the utmost care.
pub fn unsafe_mut_cast_to_lifetime<'a, T>(value: &mut T) -> &'a mut T {
unsafe { mem::transmute::<_, &'a mut T>(value) }
}
/// # DANGEROUS!!! /// # DANGEROUS!!!
/// ///
/// A dangerous function that blindly casts a `&str` from one lifetime to a `Cow<str>` of /// A dangerous function that blindly casts a `&str` from one lifetime to a `Cow<str>` of

View File

@ -235,7 +235,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
*engine *engine
.eval_with_scope::<()>(&mut scope, r#"import "testing" as ttt; ttt::hidden()"#) .eval_with_scope::<()>(&mut scope, r#"import "testing" as ttt; ttt::hidden()"#)
.expect_err("should error"), .expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "hidden" EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "ttt::hidden"
)); ));
Ok(()) Ok(())