Add Rhai book.

This commit is contained in:
Stephen Chung
2020-06-20 12:06:17 +08:00
parent 7e80d62df5
commit c7f1e12d6a
101 changed files with 3827 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
Turn Off Script Optimizations
============================
{{#include ../../links.md}}
When scripts:
* are known to be run only _once_,
* are known to contain no dead code,
* do not use constants in calculations
the optimization pass may be a waste of time and resources. In that case, turn optimization off
by setting the optimization level to [`OptimizationLevel::None`].
Alternatively, turn off optimizations via the [`no_optimize`] feature.
```rust
let engine = rhai::Engine::new();
// Turn off the optimizer
engine.set_optimization_level(rhai::OptimizationLevel::None);
```

View File

@@ -0,0 +1,36 @@
Eager Function Evaluation When Using Full Optimization Level
==========================================================
{{#include ../../links.md}}
When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_ and will _eagerly_
evaluated all function calls with constant arguments, using the result to replace the call.
This also applies to all operators (which are implemented as functions).
For instance, the same example above:
```rust
// When compiling the following with OptimizationLevel::Full...
const DECISION = 1;
// this condition is now eliminated because 'DECISION == 1'
if DECISION == 1 { // is a function call to the '==' function, and it returns 'true'
print("hello!"); // this block is promoted to the parent level
} else {
print("boo!"); // this block is eliminated because it is never reached
}
print("hello!"); // <- the above is equivalent to this
// ('print' and 'debug' are handled specially)
```
Because of the eager evaluation of functions, many constant expressions will be evaluated and replaced by the result.
This does not happen with [`OptimizationLevel::Simple`] which doesn't assume all functions to be _pure_.
```rust
// When compiling the following with OptimizationLevel::Full...
let x = (1+2)*3-4/5%6; // <- will be replaced by 'let x = 9'
let y = (1>2) || (3<=4); // <- will be replaced by 'let y = true'
```

View File

@@ -0,0 +1,24 @@
Optimization Levels
==================
{{#include ../../links.md}}
Set Optimization Level
---------------------
There are actually three levels of optimizations: `None`, `Simple` and `Full`.
* `None` is obvious - no optimization on the AST is performed.
* `Simple` (default) performs only relatively _safe_ optimizations without causing side-effects
(i.e. it only relies on static analysis and will not actually perform any function calls).
* `Full` is _much_ more aggressive, _including_ running functions on constant arguments to determine their result.
One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators.
An [`Engine`]'s optimization level is set via a call to `Engine::set_optimization_level`:
```rust
// Turn on aggressive optimizations
engine.set_optimization_level(rhai::OptimizationLevel::Full);
```

View File

@@ -0,0 +1,17 @@
Re-Optimize an AST
==================
{{#include ../../links.md}}
If it is ever needed to _re_-optimize an `AST`, use the `optimize_ast` method:
```rust
// Compile script to AST
let ast = engine.compile("40 + 2")?;
// Create a new 'Scope' - put constants in it to aid optimization if using 'OptimizationLevel::Full'
let scope = Scope::new();
// Re-optimize the AST
let ast = engine.optimize_ast(&scope, &ast, OptimizationLevel::Full);
```

View File

@@ -0,0 +1,43 @@
Subtle Semantic Changes After Optimization
=========================================
{{#include ../../links.md}}
Some optimizations can alter subtle semantics of the script.
For example:
```rust
if true { // condition always true
123.456; // eliminated
hello; // eliminated, EVEN THOUGH the variable doesn't exist!
foo(42) // promoted up-level
}
foo(42) // <- the above optimizes to this
```
If the original script were evaluated instead, it would have been an error - the variable `hello` does not exist,
so the script would have been terminated at that point with an error return.
In fact, any errors inside a statement that has been eliminated will silently _disappear_:
```rust
print("start!");
if my_decision { /* do nothing... */ } // eliminated due to no effect
print("end!");
// The above optimizes to:
print("start!");
print("end!");
```
In the script above, if `my_decision` holds anything other than a boolean value,
the script should have been terminated due to a type error.
However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces
no side-effects), thus the script silently runs to completion without errors.
It is usually a _Very Bad Idea™_ to depend on a script failing or such kind of subtleties, but if it turns out to be necessary
(why? I would never guess), turn script optimization off by setting the optimization level to [`OptimizationLevel::None`].

View File

@@ -0,0 +1,19 @@
Side-Effect Considerations for Full Optimization Level
====================================================
{{#include ../../links.md}}
All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_ (i.e. they do not mutate state
nor cause any side-effects, with the exception of `print` and `debug` which are handled specially) so using
[`OptimizationLevel::Full`] is usually quite safe _unless_ custom types and functions are registered.
If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie within a pruned code block).
If custom functions are registered to overload built-in operators, they will also be called when the operators are used
(in an `if` statement, for example) causing side-effects.
Therefore, the rule-of-thumb is:
* _Always_ register custom types and functions _after_ compiling scripts if [`OptimizationLevel::Full`] is used.
* _DO NOT_ depend on knowledge that the functions have no side-effects, because those functions can change later on and, when that happens, existing scripts may break in subtle ways.

View File

@@ -0,0 +1,16 @@
Volatility Considerations for Full Optimization Level
===================================================
{{#include ../../links.md}}
Even if a custom function does not mutate state nor cause side-effects, it may still be _volatile_,
i.e. it _depends_ on the external environment and is not _pure_.
A perfect example is a function that gets the current time - obviously each run will return a different value!
The optimizer, when using [`OptimizationLevel::Full`], will _merrily assume_ that all functions are _pure_,
so when it finds constant arguments (or none) it eagerly executes the function call and replaces it with the result.
This causes the script to behave differently from the intended semantics.
Therefore, **avoid using [`OptimizationLevel::Full`]** if non-_pure_ custom types and/or functions are involved.