Refactor.
This commit is contained in:
parent
063851a6ad
commit
d6a08be223
10
README.md
10
README.md
@ -31,10 +31,12 @@ Features
|
||||
one single source file, all with names starting with `"unsafe_"`).
|
||||
* 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`).
|
||||
* 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.
|
||||
* [Function overloading](https://schungx.github.io/rhai/language/overload.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).
|
||||
* 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).
|
||||
@ -43,3 +45,9 @@ Documentation
|
||||
-------------
|
||||
|
||||
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.
|
||||
|
@ -1,30 +1,31 @@
|
||||
The Rhai Scripting Language
|
||||
==========================
|
||||
|
||||
1. [What is Rhai](about.md)
|
||||
1. [What is Rhai](about/index.md)
|
||||
1. [Features](about/features.md)
|
||||
2. [Supported Targets and Builds](about/targets.md)
|
||||
3. [What Rhai Isn't](about/non-design.md)
|
||||
4. [Related Resources](about/related.md)
|
||||
2. [Getting Started](start.md)
|
||||
1. [Install the Rhai Crate](start/install.md)
|
||||
2. [Optional Features](start/features.md)
|
||||
3. [Special Builds](start/builds/index.md)
|
||||
3. [Getting Started](start/index.md)
|
||||
1. [Online Playground](start/playground.md)
|
||||
2. [Install the Rhai Crate](start/install.md)
|
||||
3. [Optional Features](start/features.md)
|
||||
4. [Special Builds](start/builds/index.md)
|
||||
1. [Performance](start/builds/performance.md)
|
||||
2. [Minimal](start/builds/minimal.md)
|
||||
3. [no-std](start/builds/no-std.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)
|
||||
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)
|
||||
2. [Compile a Script to AST for Repeated Evaluations](engine/compile.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)
|
||||
5. [Evaluate Expressions Only](engine/expressions.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)
|
||||
2. [Register a Rust Function](rust/functions.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)
|
||||
9. [Scope - Initializing and Maintaining State](rust/scope.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)
|
||||
2. [Values and Types](language/values-and-types.md)
|
||||
1. [Dynamic Values](language/dynamic.md)
|
||||
@ -70,9 +71,10 @@ The Rhai Scripting Language
|
||||
12. [Return Values](language/return.md)
|
||||
13. [Throw Exception on Error](language/throw.md)
|
||||
14. [Functions](language/functions.md)
|
||||
1. [Function Overloading](language/overload.md)
|
||||
2. [Call Method as Function](language/method.md)
|
||||
3. [Function Pointers](language/fn-ptr.md)
|
||||
1. [Call Method as Function](language/method.md)
|
||||
2. [Overloading](language/overload.md)
|
||||
3. [Namespaces](language/fn-namespaces.md)
|
||||
4. [Function Pointers](language/fn-ptr.md)
|
||||
15. [Print and Debug](language/print-debug.md)
|
||||
16. [Modules](language/modules/index.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)
|
||||
5. [Module Resolvers](language/modules/resolvers.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)
|
||||
2. [Sand-Boxing](safety/sandbox.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)
|
||||
8. [Maximum Call Stack Depth](safety/max-call-stack.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)
|
||||
2. [Script Optimization](engine/optimize/index.md)
|
||||
1. [Optimization Levels](engine/optimize/optimize-levels.md)
|
||||
@ -102,7 +104,7 @@ The Rhai Scripting Language
|
||||
5. [Volatility Considerations](engine/optimize/volatility.md)
|
||||
6. [Subtle Semantic Changes](engine/optimize/semantics.md)
|
||||
3. [Eval Statement](language/eval.md)
|
||||
8. [Appendix](appendix/index.md)
|
||||
9. [Appendix](appendix/index.md)
|
||||
1. [Keywords](appendix/keywords.md)
|
||||
2. [Operators](appendix/operators.md)
|
||||
3. [Literals](appendix/literals.md)
|
||||
|
@ -8,7 +8,7 @@ Easy
|
||||
|
||||
* 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`].
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
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
|
||||
to add scripting to any application.
|
@ -11,6 +11,8 @@ Other online documentation resources for Rhai:
|
||||
|
||||
* [`LIB.RS`](https://lib.rs/crates/rhai) - Rhai library info
|
||||
|
||||
* [Online Playground][playground] - Run scripts directly from editor
|
||||
|
||||
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.
|
||||
|
@ -3,7 +3,7 @@ Compile a Script (to AST)
|
||||
|
||||
{{#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
|
||||
// Compile to an AST and store it for later evaluations
|
||||
|
@ -1,7 +1,7 @@
|
||||
Using the Engine
|
||||
================
|
||||
|
||||
{{#include links.md}}
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai's interpreter resides in the [`Engine`] type under the master `rhai` namespace.
|
||||
|
@ -4,13 +4,13 @@ Re-Optimize an AST
|
||||
{{#include ../../links.md}}
|
||||
|
||||
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,
|
||||
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
|
||||
// Compile master script to AST
|
||||
|
141
doc/src/language/fn-namespaces.md
Normal file
141
doc/src/language/fn-namespaces.md
Normal 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!'
|
||||
```
|
@ -54,7 +54,34 @@ let fn_name = "hello"; // the function name does not have to exist yet
|
||||
|
||||
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!
|
||||
```
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
Rhai Language Reference
|
||||
======================
|
||||
|
||||
{{#include links.md}}
|
||||
{{#include ../links.md}}
|
||||
|
||||
This section outlines the Rhai language.
|
||||
|
@ -3,9 +3,9 @@ Call Method as Function
|
||||
|
||||
{{#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
|
||||
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_),
|
||||
native Rust functions may mutate the object (or the first argument if called in normal function call style).
|
||||
|
@ -3,7 +3,7 @@ Create a Module from an AST
|
||||
|
||||
{{#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
|
||||
other than non-[`private`] functions (unless that's intentional).
|
||||
|
@ -3,9 +3,9 @@ Export Variables, Functions and Sub-Modules in Module
|
||||
|
||||
{{#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:
|
||||
|
||||
* 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.
|
||||
|
@ -3,7 +3,7 @@ Function Overloading
|
||||
|
||||
{{#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`]).
|
||||
|
||||
New definitions _overwrite_ previous definitions of the same name and number of parameters.
|
||||
|
@ -15,11 +15,13 @@
|
||||
|
||||
[minimal builds]: {{rootUrl}}/start/builds/minimal.md
|
||||
[WASM]: {{rootUrl}}/start/builds/wasm.md
|
||||
[playground]: https://alvinhochun.github.io/rhai-demo
|
||||
|
||||
[`Engine`]: {{rootUrl}}/engine/hello-world.md
|
||||
[traits]: {{rootUrl}}/rust/traits.md
|
||||
[`private`]: {{rootUrl}}/engine/call-fn.md
|
||||
[`Func`]: {{rootUrl}}/engine/func.md
|
||||
[`AST`]: {{rootUrl}}/engine/compile.md
|
||||
[`eval_expression`]: {{rootUrl}}/engine/expressions.md
|
||||
[`eval_expression_with_scope`]: {{rootUrl}}/engine/expressions.md
|
||||
[raw `Engine`]: {{rootUrl}}/engine/raw.md
|
||||
@ -38,6 +40,8 @@
|
||||
|
||||
[custom type]: {{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
|
||||
|
||||
@ -70,6 +74,8 @@
|
||||
[functions]: {{rootUrl}}/language/functions.md
|
||||
[function pointer]: {{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
|
||||
@ -89,7 +95,7 @@
|
||||
[maximum length of strings]: {{rootUrl}}/safety/max-string-size.md
|
||||
[maximum size of arrays]: {{rootUrl}}/safety/max-array-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
|
||||
[`OptimizationLevel::Full`]: {{rootUrl}}/engine/optimize/optimize-levels.md
|
||||
|
@ -1,7 +1,7 @@
|
||||
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,
|
||||
which leverages the superior native run-time speed.
|
@ -1 +0,0 @@
|
||||
# Safety and Protection
|
@ -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.
|
||||
|
||||
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).
|
||||
|
||||
|
@ -14,7 +14,7 @@ more control over what a script can (or cannot) do.
|
||||
| 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! |
|
||||
| `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_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`. |
|
||||
@ -24,7 +24,7 @@ more control over what a script can (or cannot) do.
|
||||
| `no_function` | Disable script-defined [functions]. |
|
||||
| `no_module` | Disable loading external [modules]. |
|
||||
| `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
|
||||
|
@ -1,6 +1,6 @@
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
{{#include links.md}}
|
||||
{{#include ../links.md}}
|
||||
|
||||
This section shows how to install the Rhai crate into a Rust application.
|
@ -3,8 +3,10 @@ Install the Rhai Crate
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Install the Rhai crate from [`crates.io`](https:/crates.io/crates/rhai/), start by looking up the
|
||||
latest version and adding this line under `dependencies` in `Cargo.toml`:
|
||||
In order to use Rhai in a project, the Rhai crate must first be made a dependency.
|
||||
|
||||
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
|
||||
[dependencies]
|
||||
|
10
doc/src/start/playground.md
Normal file
10
doc/src/start/playground.md
Normal 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
BIN
rhai_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
11
src/any.rs
11
src/any.rs
@ -545,6 +545,7 @@ impl Dynamic {
|
||||
pub fn as_str(&self) -> Result<&str, &'static str> {
|
||||
match &self.0 {
|
||||
Union::Str(s) => Ok(s),
|
||||
Union::FnPtr(f) => Ok(f.fn_name()),
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
@ -561,15 +562,7 @@ impl Dynamic {
|
||||
pub(crate) fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
|
||||
match self.0 {
|
||||
Union::Str(s) => Ok(s),
|
||||
_ => Err(self.type_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()),
|
||||
Union::FnPtr(f) => Ok(f.take_fn_name()),
|
||||
_ => Err(self.type_name()),
|
||||
}
|
||||
}
|
||||
|
38
src/api.rs
38
src/api.rs
@ -422,7 +422,7 @@ impl Engine {
|
||||
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
|
||||
///
|
||||
@ -445,7 +445,7 @@ impl Engine {
|
||||
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
|
||||
/// when using `OptimizationLevel::Full`.
|
||||
@ -488,7 +488,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// when using `OptimizationLevel::Full`.
|
||||
@ -541,7 +541,7 @@ impl Engine {
|
||||
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(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
@ -577,7 +577,7 @@ impl Engine {
|
||||
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
|
||||
///
|
||||
@ -603,7 +603,7 @@ impl Engine {
|
||||
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
|
||||
/// when using `OptimizationLevel::Full`.
|
||||
@ -685,7 +685,7 @@ impl Engine {
|
||||
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.
|
||||
///
|
||||
/// # Example
|
||||
@ -709,7 +709,7 @@ impl Engine {
|
||||
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.
|
||||
///
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Evaluate an `AST`.
|
||||
/// Evaluate an [`AST`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -939,7 +939,7 @@ impl Engine {
|
||||
self.eval_ast_with_scope(&mut Scope::new(), ast)
|
||||
}
|
||||
|
||||
/// Evaluate an `AST` with own scope.
|
||||
/// Evaluate an [`AST`] with own scope.
|
||||
///
|
||||
/// # 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>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
@ -1052,7 +1052,7 @@ impl Engine {
|
||||
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.
|
||||
pub fn consume_ast_with_scope(
|
||||
&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.
|
||||
///
|
||||
/// # 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
|
||||
///
|
||||
@ -1179,7 +1179,7 @@ impl Engine {
|
||||
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
|
||||
///
|
||||
@ -1220,15 +1220,15 @@ impl Engine {
|
||||
)
|
||||
}
|
||||
|
||||
/// Optimize the `AST` with constants defined in an external Scope.
|
||||
/// An optimized copy of the `AST` is returned while the original `AST` is consumed.
|
||||
/// Optimize the [`AST`] with constants defined in an external Scope.
|
||||
/// 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
|
||||
/// _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.
|
||||
///
|
||||
/// 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
|
||||
/// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
|
127
src/engine.rs
127
src/engine.rs
@ -8,10 +8,10 @@ use crate::module::{resolvers, Module, ModuleRef, ModuleResolver};
|
||||
use crate::optimize::OptimizationLevel;
|
||||
use crate::packages::{Package, PackageLibrary, PackagesCollection, StandardPackage};
|
||||
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::scope::{EntryType as ScopeEntryType, Scope};
|
||||
use crate::token::Position;
|
||||
use crate::token::{is_valid_identifier, Position};
|
||||
use crate::utils::StaticVec;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
@ -75,6 +75,7 @@ pub const KEYWORD_PRINT: &str = "print";
|
||||
pub const KEYWORD_DEBUG: &str = "debug";
|
||||
pub const KEYWORD_TYPE_OF: &str = "type_of";
|
||||
pub const KEYWORD_EVAL: &str = "eval";
|
||||
pub const KEYWORD_FN_PTR: &str = "Fn";
|
||||
pub const KEYWORD_FN_PTR_CALL: &str = "call";
|
||||
pub const KEYWORD_THIS: &str = "this";
|
||||
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_IDX_GET: &str = "$index$get$";
|
||||
pub const FN_IDX_SET: &str = "$index$set$";
|
||||
pub const FN_FN_PTR: &str = "Fn";
|
||||
|
||||
/// A type specifying the method of chaining.
|
||||
#[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.
|
||||
// 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(),
|
||||
unsafe_mut_cast_to_lifetime(this_copy),
|
||||
);
|
||||
//
|
||||
// # Safety
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
@ -1020,7 +1027,7 @@ impl Engine {
|
||||
)),
|
||||
|
||||
// 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(
|
||||
"'Fn' should not be called in method style. Try Fn(...);".into(),
|
||||
Position::none(),
|
||||
@ -1133,7 +1140,7 @@ impl Engine {
|
||||
state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level,
|
||||
new_val,
|
||||
)
|
||||
.map_err(|err| EvalAltResult::new_position(err, *pos))
|
||||
.map_err(|err| err.new_position(*pos))
|
||||
}
|
||||
// xxx[rhs] = new_val
|
||||
_ if new_val.is_some() => {
|
||||
@ -1162,9 +1169,9 @@ impl Engine {
|
||||
}
|
||||
// Indexed value is a reference - update directly
|
||||
Ok(ref mut obj_ptr) => {
|
||||
obj_ptr.set_value(new_val.unwrap()).map_err(|err| {
|
||||
EvalAltResult::new_position(err, rhs.position())
|
||||
})?;
|
||||
obj_ptr
|
||||
.set_value(new_val.unwrap())
|
||||
.map_err(|err| err.new_position(rhs.position()))?;
|
||||
}
|
||||
Err(err) => match *err {
|
||||
// No index getter - try to call an index setter
|
||||
@ -1205,11 +1212,12 @@ impl Engine {
|
||||
let (result, updated) = {
|
||||
let obj = target.as_mut();
|
||||
let idx = idx_val.downcast_mut::<StaticVec<Dynamic>>().unwrap();
|
||||
let mut fn_name = name.as_ref();
|
||||
|
||||
// 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
|
||||
let fn_name = obj.as_fn_name().unwrap();
|
||||
fn_name = obj.as_str().unwrap();
|
||||
// Recalculate hash
|
||||
let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty());
|
||||
// Arguments are passed as-is
|
||||
@ -1222,25 +1230,20 @@ impl Engine {
|
||||
def_val, level,
|
||||
)
|
||||
} else {
|
||||
let mut fn_name = name.clone();
|
||||
let mut redirected = None;
|
||||
let redirected: Option<ImmutableString>;
|
||||
let mut hash = *hash;
|
||||
|
||||
// Check if it is a map method call in OOP style
|
||||
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>() {
|
||||
// Remap the function name
|
||||
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
|
||||
hash = calc_fn_hash(
|
||||
empty(),
|
||||
fn_name.as_ref(),
|
||||
idx.len(),
|
||||
empty(),
|
||||
);
|
||||
hash =
|
||||
calc_fn_hash(empty(), fn_name, idx.len(), empty());
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1249,14 +1252,13 @@ impl Engine {
|
||||
let mut arg_values =
|
||||
once(obj).chain(idx.iter_mut()).collect::<StaticVec<_>>();
|
||||
let args = arg_values.as_mut();
|
||||
let fn_name = fn_name.as_ref();
|
||||
|
||||
self.exec_fn_call(
|
||||
state, lib, fn_name, *native, hash, args, is_ref, true,
|
||||
def_val, level,
|
||||
)
|
||||
}
|
||||
.map_err(|err| EvalAltResult::new_position(err, *pos))?
|
||||
.map_err(|err| err.new_position(*pos))?
|
||||
};
|
||||
|
||||
// Feed the changed temp value back
|
||||
@ -1277,7 +1279,7 @@ impl Engine {
|
||||
self.get_indexed_mut(state, lib, target, index, *pos, true, level)?;
|
||||
|
||||
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))
|
||||
}
|
||||
// {xxx:map}.id
|
||||
@ -1297,7 +1299,7 @@ impl Engine {
|
||||
state, lib, setter, true, 0, &mut args, is_ref, true, None, level,
|
||||
)
|
||||
.map(|(v, _)| (v, true))
|
||||
.map_err(|err| EvalAltResult::new_position(err, *pos))
|
||||
.map_err(|err| err.new_position(*pos))
|
||||
}
|
||||
// xxx.id
|
||||
Expr::Property(x) => {
|
||||
@ -1307,7 +1309,7 @@ impl Engine {
|
||||
state, lib, getter, true, 0, &mut args, is_ref, true, None, level,
|
||||
)
|
||||
.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
|
||||
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,
|
||||
new_val,
|
||||
)
|
||||
.map_err(|err| EvalAltResult::new_position(err, *pos))
|
||||
.map_err(|err| err.new_position(*pos))
|
||||
}
|
||||
// xxx.prop[expr] | xxx.prop.expr
|
||||
Expr::Index(x) | Expr::Dot(x) => {
|
||||
@ -1338,7 +1340,7 @@ impl Engine {
|
||||
self.exec_fn_call(
|
||||
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 {
|
||||
unreachable!();
|
||||
};
|
||||
@ -1350,7 +1352,7 @@ impl Engine {
|
||||
state, lib, this_ptr, target, expr, idx_values, next_chain, level,
|
||||
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
|
||||
if updated || may_be_changed {
|
||||
@ -1364,7 +1366,7 @@ impl Engine {
|
||||
.or_else(|err| match *err {
|
||||
// If there is no setter, no need to feed it back because the property is read-only
|
||||
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;
|
||||
|
||||
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)?;
|
||||
|
||||
@ -1433,7 +1435,7 @@ impl Engine {
|
||||
state, lib, &mut None, obj_ptr, dot_rhs, idx_values, chain_type, level, new_val,
|
||||
)
|
||||
.map(|(v, _)| v)
|
||||
.map_err(|err| EvalAltResult::new_position(err, *op_pos))
|
||||
.map_err(|err| err.new_position(*op_pos))
|
||||
}
|
||||
// {expr}.??? = ??? or {expr}[???] = ???
|
||||
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,
|
||||
)
|
||||
.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,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
self.inc_operations(state)
|
||||
.map_err(|err| EvalAltResult::new_position(err, expr.position()))?;
|
||||
.map_err(|err| err.new_position(expr.position()))?;
|
||||
|
||||
match expr {
|
||||
Expr::FnCall(x) if x.1.is_none() => {
|
||||
@ -1627,7 +1629,7 @@ impl Engine {
|
||||
level: usize,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
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 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,
|
||||
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) {
|
||||
return Ok(true.into());
|
||||
}
|
||||
@ -1694,7 +1696,7 @@ impl Engine {
|
||||
level: usize,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
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 {
|
||||
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) =
|
||||
search_scope(scope, mods, state, this_ptr, lhs_expr)?;
|
||||
self.inc_operations(state)
|
||||
.map_err(|err| EvalAltResult::new_position(err, pos))?;
|
||||
.map_err(|err| err.new_position(pos))?;
|
||||
|
||||
match typ {
|
||||
// Assignment to constant variable
|
||||
@ -1769,7 +1771,7 @@ impl Engine {
|
||||
state, lib, op, true, hash, args, false, false, None, level,
|
||||
)
|
||||
.map(|(v, _)| v)
|
||||
.map_err(|err| EvalAltResult::new_position(err, *op_pos))?;
|
||||
.map_err(|err| err.new_position(*op_pos))?;
|
||||
}
|
||||
Ok(Default::default())
|
||||
}
|
||||
@ -1795,7 +1797,7 @@ impl Engine {
|
||||
];
|
||||
self.exec_fn_call(state, lib, op, true, hash, args, false, false, None, level)
|
||||
.map(|(v, _)| v)
|
||||
.map_err(|err| EvalAltResult::new_position(err, *op_pos))?
|
||||
.map_err(|err| err.new_position(*op_pos))?
|
||||
});
|
||||
|
||||
match lhs_expr {
|
||||
@ -1859,8 +1861,8 @@ impl Engine {
|
||||
let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref();
|
||||
let def_val = def_val.as_ref();
|
||||
|
||||
// Handle Fn
|
||||
if name == FN_FN_PTR && args_expr.len() == 1 {
|
||||
// Handle Fn()
|
||||
if name == KEYWORD_FN_PTR && args_expr.len() == 1 {
|
||||
let hash_fn =
|
||||
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)?;
|
||||
return arg_value
|
||||
.take_immutable_string()
|
||||
.map(|s| FnPtr::from(s).into())
|
||||
.map_err(|type_name| {
|
||||
Box::new(EvalAltResult::ErrorMismatchOutputType(
|
||||
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 {
|
||||
let hash_fn =
|
||||
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)?;
|
||||
let result = self
|
||||
.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 {
|
||||
// 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)?;
|
||||
|
||||
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();
|
||||
|
||||
@ -1957,7 +1969,7 @@ impl Engine {
|
||||
state, lib, name, *native, *hash, args, is_ref, false, def_val, level,
|
||||
)
|
||||
.map(|(v, _)| v)
|
||||
.map_err(|err| EvalAltResult::new_position(err, *pos))
|
||||
.map_err(|err| err.new_position(*pos))
|
||||
}
|
||||
|
||||
// Module-qualified function call
|
||||
@ -1979,7 +1991,7 @@ impl Engine {
|
||||
Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => {
|
||||
// Then search in Rust functions
|
||||
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:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
@ -2005,7 +2017,7 @@ impl Engine {
|
||||
self.call_script_fn(
|
||||
&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) => {
|
||||
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)
|
||||
.map_err(|err| err.new_position(expr.position()))
|
||||
}
|
||||
|
||||
/// Evaluate a statement
|
||||
@ -2085,7 +2098,7 @@ impl Engine {
|
||||
level: usize,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
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 {
|
||||
// No-op
|
||||
@ -2200,7 +2213,7 @@ impl Engine {
|
||||
for loop_var in func(iter_type) {
|
||||
*scope.get_mut(index).0 = loop_var;
|
||||
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) {
|
||||
Ok(_) => (),
|
||||
@ -2368,9 +2381,11 @@ impl Engine {
|
||||
};
|
||||
|
||||
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.
|
||||
/// Position in `EvalAltResult` may be None and should be set afterwards.
|
||||
fn check_data_size(
|
||||
&self,
|
||||
result: Result<Dynamic, Box<EvalAltResult>>,
|
||||
|
@ -15,8 +15,8 @@ use crate::stdlib::{boxed::Box, string::ToString};
|
||||
pub trait Func<ARGS, RET> {
|
||||
type Output;
|
||||
|
||||
/// Create a Rust anonymous function from an `AST`.
|
||||
/// The `Engine` and `AST` are consumed and basically embedded into the closure.
|
||||
/// Create a Rust anonymous function from an [`AST`].
|
||||
/// The `Engine` and [`AST`] are consumed and basically embedded into the closure.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -67,6 +67,10 @@ impl FnPtr {
|
||||
pub(crate) fn get_fn_name(&self) -> &ImmutableString {
|
||||
&self.0
|
||||
}
|
||||
/// Get the name of the function.
|
||||
pub(crate) fn take_fn_name(self) -> ImmutableString {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FnPtr {
|
||||
|
@ -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_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. |
|
||||
//! | `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). |
|
||||
//!
|
||||
//! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language.
|
||||
|
@ -901,7 +901,7 @@ impl Module {
|
||||
.map(|f| f.get_shared_fn_def())
|
||||
}
|
||||
|
||||
/// Create a new `Module` by evaluating an `AST`.
|
||||
/// Create a new `Module` by evaluating an [`AST`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -42,12 +42,10 @@ fn pad<T: Variant + Clone>(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncRe
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
while list.len() < len as usize {
|
||||
push(list, item.clone())?;
|
||||
}
|
||||
list.resize(len as usize, item);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -245,16 +245,21 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|
||||
let ch = *args[2].downcast_ref::< char>().unwrap();
|
||||
let s = args[0].downcast_mut::<ImmutableString>().unwrap();
|
||||
|
||||
let copy = s.make_mut();
|
||||
for _ in 0..copy.chars().count() - len as usize {
|
||||
copy.push(ch);
|
||||
let orig_len = s.chars().count();
|
||||
|
||||
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(
|
||||
"Length of string".to_string(),
|
||||
engine.max_string_size,
|
||||
copy.len(),
|
||||
s.len(),
|
||||
Position::none(),
|
||||
)))
|
||||
} else {
|
||||
|
@ -49,7 +49,7 @@ pub use crate::utils::ImmutableString;
|
||||
|
||||
/// 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)]
|
||||
pub struct AST(
|
||||
/// Global statements.
|
||||
@ -59,7 +59,7 @@ pub struct AST(
|
||||
);
|
||||
|
||||
impl AST {
|
||||
/// Create a new `AST`.
|
||||
/// Create a new [`AST`].
|
||||
pub fn new(statements: Vec<Stmt>, lib: Module) -> Self {
|
||||
Self(statements, lib)
|
||||
}
|
||||
@ -95,16 +95,16 @@ impl AST {
|
||||
&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.
|
||||
///
|
||||
/// 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.
|
||||
/// 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`] 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.
|
||||
/// Of course, if the first [`AST`] uses a `return` statement at the end, then
|
||||
/// the second [`AST`] will essentially be dead code.
|
||||
///
|
||||
/// All script-defined functions in the second `AST` overwrite similarly-named functions
|
||||
/// in the first `AST` with the same number of parameters.
|
||||
/// All script-defined functions in the second [`AST`] overwrite similarly-named functions
|
||||
/// in the first [`AST`] with the same number of parameters.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -157,13 +157,13 @@ impl AST {
|
||||
Self::new(ast, functions)
|
||||
}
|
||||
|
||||
/// Clear all function definitions in the `AST`.
|
||||
/// Clear all function definitions in the [`AST`].
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn clear_functions(&mut self) {
|
||||
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"))]
|
||||
pub fn retain_functions(&mut self) {
|
||||
self.0 = vec![];
|
||||
@ -802,22 +802,22 @@ fn parse_call_expr(
|
||||
mut modules: Option<Box<ModuleRef>>,
|
||||
settings: ParseSettings,
|
||||
) -> 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)?;
|
||||
|
||||
let mut args = StaticVec::new();
|
||||
|
||||
match token {
|
||||
// id <EOF>
|
||||
// id( <EOF>
|
||||
Token::EOF => {
|
||||
return Err(PERR::MissingToken(
|
||||
Token::RightParen.into(),
|
||||
format!("to close the arguments list of this function call '{}'", id),
|
||||
)
|
||||
.into_err(settings.pos))
|
||||
.into_err(*token_pos))
|
||||
}
|
||||
// id <error>
|
||||
Token::LexError(err) => return Err(err.into_err(settings.pos)),
|
||||
// id( <error>
|
||||
Token::LexError(err) => return Err(err.into_err(*token_pos)),
|
||||
// id()
|
||||
Token::RightParen => {
|
||||
eat_token(input, Token::RightParen);
|
||||
@ -1259,8 +1259,8 @@ fn parse_primary(
|
||||
state: &mut ParseState,
|
||||
mut settings: ParseSettings,
|
||||
) -> Result<Expr, ParseError> {
|
||||
let (token, pos1) = input.peek().unwrap();
|
||||
settings.pos = *pos1;
|
||||
let (token, token_pos) = input.peek().unwrap();
|
||||
settings.pos = *token_pos;
|
||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||
|
||||
let (token, _) = match token {
|
||||
|
24
src/token.rs
24
src/token.rs
@ -453,6 +453,22 @@ pub trait InputStream {
|
||||
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`.
|
||||
pub fn parse_string_literal(
|
||||
stream: &mut impl InputStream,
|
||||
@ -783,13 +799,9 @@ fn get_next_token_inner(
|
||||
}
|
||||
}
|
||||
|
||||
let is_valid_identifier = result
|
||||
.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 is_valid_identifier = is_valid_identifier(result.iter().cloned());
|
||||
|
||||
let identifier: String = result.iter().collect();
|
||||
let identifier: String = result.into_iter().collect();
|
||||
|
||||
if !is_valid_identifier {
|
||||
return Some((
|
||||
|
@ -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!!!
|
||||
///
|
||||
/// A dangerous function that blindly casts a `&str` from one lifetime to a `Cow<str>` of
|
||||
|
@ -235,7 +235,7 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
|
||||
*engine
|
||||
.eval_with_scope::<()>(&mut scope, r#"import "testing" as ttt; ttt::hidden()"#)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "hidden"
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "ttt::hidden"
|
||||
));
|
||||
|
||||
Ok(())
|
||||
|
Loading…
Reference in New Issue
Block a user