Add doc tests to Scope.

This commit is contained in:
Stephen Chung 2020-04-05 23:43:40 +08:00
parent c4498d147d
commit 2bb195cd65
2 changed files with 293 additions and 95 deletions

170
README.md
View File

@ -8,18 +8,21 @@ Rhai - Embedded Scripting for Rust
![crates.io](https://img.shields.io/crates/d/rhai) ![crates.io](https://img.shields.io/crates/d/rhai)
[![API Docs](https://docs.rs/rhai/badge.svg)](https://docs.rs/rhai/) [![API Docs](https://docs.rs/rhai/badge.svg)](https://docs.rs/rhai/)
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application. Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way
to add scripting to any application.
Rhai's current features set: Rhai's current features set:
* `no-std` support * `no-std` support
* Easy integration with Rust functions and data types, supporting getter/setter methods * Easy integration with Rust native functions and data types, including getter/setter methods
* Easily call a script-defined function from Rust * Easily call a script-defined function from Rust
* Freely pass variables/constants into a script via an external [`Scope`]
* Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop) * Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop)
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app) * Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
* Easy-to-use language similar to JS+Rust * Easy-to-use language similar to JS+Rust
* Support for overloaded functions * Support for overloaded functions
* Compiled script is optimized for repeat evaluations * Compiled script is optimized for repeat evaluations
* Support for minimal builds by excluding unneeded language features
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
to do checked arithmetic operations); for [`no_std`] builds, a number of additional dependencies are to do checked arithmetic operations); for [`no_std`] builds, a number of additional dependencies are
pulled in to provide for functionalities that used to be in `std`. pulled in to provide for functionalities that used to be in `std`.
@ -70,7 +73,8 @@ Optional features
| `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`. |
By default, Rhai includes all the standard functionalities in a small, tight package. Most features are here to opt-**out** of certain functionalities that are not needed. By default, Rhai includes all the standard functionalities in a small, tight package.
Most features are here to opt-**out** of certain functionalities that are not needed.
Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language. Excluding unneeded functionalities can result in smaller, faster builds as well as less bugs due to a more restricted language.
[`unchecked`]: #optional-features [`unchecked`]: #optional-features
@ -209,8 +213,8 @@ Compiling a script file is also supported:
let ast = engine.compile_file("hello_world.rhai".into())?; let ast = engine.compile_file("hello_world.rhai".into())?;
``` ```
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - via `call_fn` Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust -
or its cousins `call_fn1` (one argument) and `call_fn0` (no argument). via `call_fn` or its cousins `call_fn1` (one argument) and `call_fn0` (no argument).
```rust ```rust
// Define functions in a script. // Define functions in a script.
@ -295,12 +299,14 @@ The following primitive types are supported natively:
[`()`]: #values-and-types [`()`]: #values-and-types
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different - they even cannot be added together. This is very similar to Rust. All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different -
they even cannot be added together. This is very similar to Rust.
The default integer type is `i64`. If other integer types are not needed, it is possible to exclude them and make a smaller build with the [`only_i64`] feature. The default integer type is `i64`. If other integer types are not needed, it is possible to exclude them and make a
smaller build with the [`only_i64`] feature.
If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`. If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`,
This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty. including `i64`. This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty.
If no floating-point is needed or supported, use the [`no_float`] feature to remove it. If no floating-point is needed or supported, use the [`no_float`] feature to remove it.
@ -358,12 +364,13 @@ if type_of(mystery) == "i64" {
} }
``` ```
In Rust, sometimes a `Dynamic` forms part of the return value - a good example is elements within an `Array` which are `Dynamic`, In Rust, sometimes a `Dynamic` forms part of a returned value - a good example is an array with `Dynamic` elements,
or property values in an object map. In order to get the _real_ value, the actual value type _must_ be known in advance. or an object map with `Dynamic` property values. To get the _real_ values, the actual value types _must_ be known in advance.
There is no easy way for Rust to detect, at run-time, what type the `Dynamic` value is (short of using the `type_name` There is no easy way for Rust to decide, at run-time, what type the `Dynamic` value is (short of using the `type_name`
function to get the textual name of the type and then matching on that). function and match against the name).
To use a `Dynamic` value in Rust, use the `cast` method to convert the value into a specific, known type. A `Dynamic` value's actual type can be checked via the `is` method.
The `cast` method (from the `rhai::AnyExt` trait) then converts the value into a specific, known type.
Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails. Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails.
```rust ```rust
@ -372,13 +379,15 @@ use rhai::AnyExt; // Pull in the trait.
let list: Array = engine.eval("...")?; // return type is 'Array' let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic' let item = list[0]; // an element in an 'Array' is 'Dynamic'
item.is::<i64>() == true; // 'is' returns whether a 'Dynamic' value is of a particular type
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
let value: i64 = item.cast(); // type can also be inferred let value: i64 = item.cast(); // type can also be inferred
let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns an error let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns an error
``` ```
The `type_name` method gets the name of the actual type as a string, which you may match against. The `type_name` method gets the name of the actual type as a static string slice, which you may match against.
```rust ```rust
use rhai::Any; // Pull in the trait. use rhai::Any; // Pull in the trait.
@ -423,7 +432,7 @@ To call these functions, they need to be registered with the [`Engine`].
```rust ```rust
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult};
use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn` use rhai::RegisterFn; // use `RegisterFn` trait for `register_fn`
use rhai::{Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn` use rhai::{Any, Dynamic, RegisterDynamicFn}; // use `RegisterDynamicFn` trait for `register_dynamic_fn`
// Normal function // Normal function
fn add(x: i64, y: i64) -> i64 { fn add(x: i64, y: i64) -> i64 {
@ -432,7 +441,7 @@ fn add(x: i64, y: i64) -> i64 {
// Function that returns a Dynamic value // Function that returns a Dynamic value
fn get_an_any() -> Dynamic { fn get_an_any() -> Dynamic {
Box::new(42_i64) (42_i64).into_dynamic() // 'into_dynamic' is defined by the 'rhai::Any' trait
} }
fn main() -> Result<(), EvalAltResult> fn main() -> Result<(), EvalAltResult>
@ -456,14 +465,17 @@ fn main() -> Result<(), EvalAltResult>
} }
``` ```
To return a [`Dynamic`] value from a Rust function, simply `Box` it and return it. To return a [`Dynamic`] value from a Rust function, use the `into_dynamic()` method
(under the `rhai::Any` trait) to convert it.
```rust ```rust
use rhai::Any; // Pull in the trait
fn decide(yes_no: bool) -> Dynamic { fn decide(yes_no: bool) -> Dynamic {
if yes_no { if yes_no {
Box::new(42_i64) (42_i64).into_dynamic()
} else { } else {
Box::new("hello world!".to_string()) // remember &str is not supported String::from("hello!").into_dynamic() // remember &str is not supported by Rhai
} }
} }
``` ```
@ -471,7 +483,8 @@ fn decide(yes_no: bool) -> Dynamic {
Generic functions Generic functions
----------------- -----------------
Generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately: Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately.
Essentially this is a form of function overloading as Rhai does not support generics.
```rust ```rust
use std::fmt::Display; use std::fmt::Display;
@ -492,15 +505,17 @@ fn main()
} }
``` ```
This example shows how to register multiple functions (or, in this case, multiple instances of the same function) to the same name in script. This example shows how to register multiple functions (or, in this case, multiple overloaded versions of the same function)
This enables function overloading based on the number and types of parameters. under the same name. This enables function overloading based on the number and types of parameters.
Fallible functions Fallible functions
------------------ ------------------
If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be registered with `register_result_fn` (using the `RegisterResultFn` trait). If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be registered with `register_result_fn`
(using the `RegisterResultFn` trait).
The function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements `From<&str>` and `From<String>` etc. and the error text gets converted into `EvalAltResult::ErrorRuntime`. The function must return `Result<_, EvalAltResult>`. `EvalAltResult` implements `From<&str>` and `From<String>` etc.
and the error text gets converted into `EvalAltResult::ErrorRuntime`.
```rust ```rust
use rhai::{Engine, EvalAltResult, Position}; use rhai::{Engine, EvalAltResult, Position};
@ -612,14 +627,15 @@ let mut engine = Engine::new();
engine.register_type::<TestStruct>(); engine.register_type::<TestStruct>();
``` ```
To use methods and functions with the [`Engine`], we need to register them. There are some convenience functions to help with this. To use native types, methods and functions with the [`Engine`], we need to register them.
Below I register update and new with the [`Engine`]. There are some convenience functions to help with these. Below, the `update` and `new` methods are registered with the [`Engine`].
*Note: [`Engine`] follows the convention that methods use a `&mut` first parameter so that invoking methods can update the value in memory.* *Note: [`Engine`] follows the convention that methods use a `&mut` first parameter so that invoking methods
can update the value in memory.*
```rust ```rust
engine.register_fn("update", TestStruct::update); // registers 'update(&mut ts)' engine.register_fn("update", TestStruct::update); // registers 'update(&mut TestStruct)'
engine.register_fn("new_ts", TestStruct::new); // registers 'new' engine.register_fn("new_ts", TestStruct::new); // registers 'new()'
``` ```
Finally, we call our script. The script can see the function and method we registered earlier. Finally, we call our script. The script can see the function and method we registered earlier.
@ -631,8 +647,9 @@ let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
println!("result: {}", result.field); // prints 42 println!("result: {}", result.field); // prints 42
``` ```
In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method-call on that type because internally they are the same thing: In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method call
methods on a type is implemented as a functions taking an first argument. on that type because internally they are the same thing:
methods on a type is implemented as a functions taking a `&mut` first argument.
```rust ```rust
fn foo(ts: &mut TestStruct) -> i64 { fn foo(ts: &mut TestStruct) -> i64 {
@ -646,7 +663,8 @@ let result = engine.eval::<i64>("let x = new_ts(); x.foo()")?;
println!("result: {}", result); // prints 1 println!("result: {}", result); // prints 1
``` ```
If the [`no_object`] feature is turned on, however, the _method_ style of function calls (i.e. calling a function as an object-method) is no longer supported. If the [`no_object`] feature is turned on, however, the _method_ style of function calls
(i.e. calling a function as an object-method) is no longer supported.
```rust ```rust
// Below is a syntax error under 'no_object' because 'len' cannot be called in method style. // Below is a syntax error under 'no_object' because 'len' cannot be called in method style.
@ -709,19 +727,21 @@ println!("Answer: {}", result); // prints 42
Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set` and `register_get_set` Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set` and `register_get_set`
are not available when the [`no_object`] feature is turned on. are not available when the [`no_object`] feature is turned on.
Initializing and maintaining state `Scope` - Initializing and maintaining state
--------------------------------- -------------------------------------------
[`Scope`]: #initializing-and-maintaining-state [`Scope`]: #scope---initializing-and-maintaining-state
By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined but no global state. By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined
This gives each evaluation a clean starting slate. In order to continue using the same global state from one invocation to the next, but no global state. This gives each evaluation a clean starting slate. In order to continue using the same global state
such a state must be manually created and passed in. from one invocation to the next, such a state must be manually created and passed in.
All `Scope` variables are [`Dynamic`], meaning they can store values of any type. If the [`sync`] feature is used, however, then only types All `Scope` variables are [`Dynamic`], meaning they can store values of any type. If the [`sync`] feature is used, however,
that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`. This is extremely useful in multi-threaded applications. then only types that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`.
This is extremely useful in multi-threaded applications.
In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is threaded through multiple invocations: In this example, a global state object (a `Scope`) is created with a few initialized variables, then the same state is
threaded through multiple invocations:
```rust ```rust
use rhai::{Engine, Scope, EvalAltResult}; use rhai::{Engine, Scope, EvalAltResult};
@ -828,7 +848,8 @@ Variables
Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`'). Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`').
Variable names must start with an ASCII letter or an underscore '`_`', must contain at least one ASCII letter, and must start with an ASCII letter before a digit. Variable names must start with an ASCII letter or an underscore '`_`', must contain at least one ASCII letter,
and must start with an ASCII letter before a digit.
Therefore, names like '`_`', '`_42`', '`3a`' etc. are not legal variable names, but '`_c3po`' and '`r2d2`' are. Therefore, names like '`_`', '`_42`', '`3a`' etc. are not legal variable names, but '`_c3po`' and '`r2d2`' are.
Variable names are also case _sensitive_. Variable names are also case _sensitive_.
@ -879,7 +900,8 @@ Integer numbers follow C-style format with support for decimal, binary ('`0b`'),
The default system integer type (also aliased to `INT`) is `i64`. It can be turned into `i32` via the [`only_i32`] feature. The default system integer type (also aliased to `INT`) is `i64`. It can be turned into `i32` via the [`only_i32`] feature.
Floating-point numbers are also supported if not disabled with [`no_float`]. The default system floating-point type is `i64` (also aliased to `FLOAT`). Floating-point numbers are also supported if not disabled with [`no_float`]. The default system floating-point type is `i64`
(also aliased to `FLOAT`).
'`_`' separators can be added freely and are ignored within a number. '`_`' separators can be added freely and are ignored within a number.
@ -935,7 +957,8 @@ number = -5 - +5;
Numeric functions Numeric functions
----------------- -----------------
The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on
`i8`, `i16`, `i32`, `i64`, `f32` and `f64` only:
| Function | Description | | Function | Description |
| ------------ | --------------------------------- | | ------------ | --------------------------------- |
@ -961,18 +984,21 @@ The following standard functions (defined in the standard library but excluded i
Strings and Chars Strings and Chars
----------------- -----------------
String and char literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') and hex ('`\x`_xx_') escape sequences. String and char literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_') and
hex ('`\x`_xx_') escape sequences.
Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full, 32-bit extended Unicode code points. Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full,
32-bit extended Unicode code points.
Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`s!), but there are major differences. Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`s!), but there are major differences.
In Rhai a string is the same as an array of Unicode characters and can be directly indexed (unlike Rust). In Rhai a string is the same as an array of Unicode characters and can be directly indexed (unlike Rust).
This is similar to most other languages where strings are internally represented not as UTF-8 but as arrays of multi-byte Unicode characters. This is similar to most other languages where strings are internally represented not as UTF-8 but as arrays of multi-byte
Unicode characters.
Individual characters within a Rhai string can also be replaced just as if the string is an array of Unicode characters. Individual characters within a Rhai string can also be replaced just as if the string is an array of Unicode characters.
In Rhai, there is also no separate concepts of `String` and `&str` as in Rust. In Rhai, there is also no separate concepts of `String` and `&str` as in Rust.
Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded if [`no_stdlib`]). Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded
This is particularly useful when printing output. if [`no_stdlib`]). This is particularly useful when printing output.
[`type_of()`] a string returns `"string"`. [`type_of()`] a string returns `"string"`.
@ -1236,8 +1262,8 @@ Comparison operators
Comparing most values of the same data type work out-of-the-box for standard types supported by the system. Comparing most values of the same data type work out-of-the-box for standard types supported by the system.
However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system types -
types - `INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`. `INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`.
```rust ```rust
42 == 42; // true 42 == 42; // true
@ -1421,8 +1447,8 @@ return 123 + 456; // returns 579
Errors and `throw`-ing exceptions Errors and `throw`-ing exceptions
-------------------------------- --------------------------------
All of [`Engine`]'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult` holding error information. All of [`Engine`]'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult`
To deliberately return an error during an evaluation, use the `throw` keyword. holding error information. To deliberately return an error during an evaluation, use the `throw` keyword.
```rust ```rust
if some_bad_condition_has_happened { if some_bad_condition_has_happened {
@ -1493,7 +1519,8 @@ fn foo() { x } // <- syntax error: variable 'x' doesn't exist
### Passing arguments by value ### Passing arguments by value
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type). Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
It is important to remember that all arguments are passed by _value_, so all functions are _pure_ (i.e. they never modify their arguments). It is important to remember that all arguments are passed by _value_, so all functions are _pure_
(i.e. they never modifytheir arguments).
Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if not careful. Any update to an argument will **not** be reflected back to the caller. This can introduce subtle bugs, if not careful.
```rust ```rust
@ -1621,8 +1648,8 @@ For example, in the following:
} }
``` ```
Rhai attempts to eliminate _dead code_ (i.e. code that does nothing, for example an expression by itself as a statement, which is allowed in Rhai). Rhai attempts to eliminate _dead code_ (i.e. code that does nothing, for example an expression by itself as a statement,
The above script optimizes to: which is allowed in Rhai). The above script optimizes to:
```rust ```rust
{ {
@ -1750,21 +1777,22 @@ Function side effect considerations
---------------------------------- ----------------------------------
All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_ (i.e. they do not mutate state All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_ (i.e. they do not mutate state
nor cause side any effects, with the exception of `print` and `debug` which are handled specially) so using [`OptimizationLevel::Full`] nor cause side any effects, with the exception of `print` and `debug` which are handled specially) so using
is usually quite safe _unless_ you register your own types and functions. [`OptimizationLevel::Full`] is usually quite safe _unless_ you register your own types and functions.
If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie within a pruned code block). If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie within a pruned code block).
If custom functions are registered to replace built-in operators, they will also be called when the operators are used (in an `if` If custom functions are registered to replace built-in operators, they will also be called when the operators are used
statement, for example) and cause side-effects. (in an `if` statement, for example) and cause side-effects.
Function volatility considerations Function volatility considerations
--------------------------------- ---------------------------------
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 Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_
environment and is not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value! on the external environment and is not _pure_. A perfect example is a function that gets the current time -
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments obviously each run will return a different value! The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that
it will eagerly execute the function call. This causes the script to behave differently from the intended semantics because all functions are _pure_, so when it finds constant arguments it will eagerly execute the function call.
essentially the result of the function call will always be the same value. This causes the script to behave differently from the intended semantics because essentially the result of the function call
will always be the same value.
Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions. Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions.
@ -1799,15 +1827,15 @@ print("start!");
print("end!"); 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. In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to
However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces no side effects), a type error. However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces
thus the script silently runs to completion without errors. no side effects), thus the script silently runs to completion without errors.
Turning off optimizations Turning off optimizations
------------------------- -------------------------
It is usually a 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), It is usually a bad idea to depend on a script failing or such kind of subtleties, but if it turns out to be necessary
turn it off by setting the optimization level to [`OptimizationLevel::None`]. (why? I would never guess), turn it off by setting the optimization level to [`OptimizationLevel::None`].
```rust ```rust
let engine = rhai::Engine::new(); let engine = rhai::Engine::new();

View File

@ -52,9 +52,14 @@ pub(crate) struct EntryRef<'a> {
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// let mut my_scope = Scope::new(); /// let mut my_scope = Scope::new();
/// ///
/// engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;")?; /// my_scope.push("z", 40_i64);
/// ///
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, "x + 1")?, 6); /// engine.eval_with_scope::<()>(&mut my_scope, "let x = z + 1; z = 0;")?;
///
/// assert_eq!(engine.eval_with_scope::<i64>(&mut my_scope, "x + 1")?, 42);
///
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 41);
/// assert_eq!(my_scope.get_value::<i64>("z").unwrap(), 0);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
@ -68,31 +73,106 @@ pub struct Scope<'a>(Vec<Entry<'a>>);
impl<'a> Scope<'a> { impl<'a> Scope<'a> {
/// Create a new Scope. /// Create a new Scope.
///
/// # Examples
///
/// ```
/// use rhai::Scope;
///
/// let mut my_scope = Scope::new();
///
/// my_scope.push("x", 42_i64);
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ```
pub fn new() -> Self { pub fn new() -> Self {
Self(Vec::new()) Self(Vec::new())
} }
/// Empty the Scope. /// Empty the Scope.
///
/// # Examples
///
/// ```
/// use rhai::Scope;
///
/// let mut my_scope = Scope::new();
///
/// my_scope.push("x", 42_i64);
/// assert!(my_scope.contains("x"));
/// assert_eq!(my_scope.len(), 1);
/// assert!(!my_scope.is_empty());
///
/// my_scope.clear();
/// assert!(!my_scope.contains("x"));
/// assert_eq!(my_scope.len(), 0);
/// assert!(my_scope.is_empty());
/// ```
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.0.clear(); self.0.clear();
} }
/// Get the number of entries inside the Scope. /// Get the number of entries inside the Scope.
///
/// # Examples
///
/// ```
/// use rhai::Scope;
///
/// let mut my_scope = Scope::new();
/// assert_eq!(my_scope.len(), 0);
///
/// my_scope.push("x", 42_i64);
/// assert_eq!(my_scope.len(), 1);
/// ```
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.0.len() self.0.len()
} }
/// Is the Scope empty? /// Is the Scope empty?
///
/// # Examples
///
/// ```
/// use rhai::Scope;
///
/// let mut my_scope = Scope::new();
/// assert!(my_scope.is_empty());
///
/// my_scope.push("x", 42_i64);
/// assert!(!my_scope.is_empty());
/// ```
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.0.len() == 0 self.0.len() == 0
} }
/// Add (push) a new entry to the Scope. /// Add (push) a new entry to the Scope.
///
/// # Examples
///
/// ```
/// use rhai::Scope;
///
/// let mut my_scope = Scope::new();
///
/// my_scope.push("x", 42_i64);
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ```
pub fn push<K: Into<Cow<'a, str>>, T: Any + Clone>(&mut self, name: K, value: T) { pub fn push<K: Into<Cow<'a, str>>, T: Any + Clone>(&mut self, name: K, value: T) {
self.push_dynamic_value(name, EntryType::Normal, value.into_dynamic(), false); self.push_dynamic_value(name, EntryType::Normal, value.into_dynamic(), false);
} }
/// Add (push) a new `Dynamic` entry to the Scope. /// Add (push) a new `Dynamic` entry to the Scope.
///
/// # Examples
///
/// ```
/// use rhai::{Any, Scope};
///
/// let mut my_scope = Scope::new();
///
/// my_scope.push_dynamic("x", (42_i64).into_dynamic());
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ```
pub fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) { pub fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) {
self.push_dynamic_value(name, EntryType::Normal, value, false); self.push_dynamic_value(name, EntryType::Normal, value, false);
} }
@ -101,8 +181,20 @@ impl<'a> Scope<'a> {
/// ///
/// Constants are immutable and cannot be assigned to. Their values never change. /// Constants are immutable and cannot be assigned to. Their values never change.
/// Constants propagation is a technique used to optimize an AST. /// Constants propagation is a technique used to optimize an AST.
///
/// However, in order to be used for optimization, constants must be in one of the recognized types: /// However, in order to be used for optimization, constants must be in one of the recognized types:
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`. /// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
///
/// # Examples
///
/// ```
/// use rhai::Scope;
///
/// let mut my_scope = Scope::new();
///
/// my_scope.push_constant("x", 42_i64);
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ```
pub fn push_constant<K: Into<Cow<'a, str>>, T: Any + Clone>(&mut self, name: K, value: T) { pub fn push_constant<K: Into<Cow<'a, str>>, T: Any + Clone>(&mut self, name: K, value: T) {
self.push_dynamic_value(name, EntryType::Constant, value.into_dynamic(), true); self.push_dynamic_value(name, EntryType::Constant, value.into_dynamic(), true);
} }
@ -111,9 +203,21 @@ impl<'a> Scope<'a> {
/// ///
/// Constants are immutable and cannot be assigned to. Their values never change. /// Constants are immutable and cannot be assigned to. Their values never change.
/// Constants propagation is a technique used to optimize an AST. /// Constants propagation is a technique used to optimize an AST.
///
/// However, in order to be used for optimization, the `Dynamic` value must be in one of the /// However, in order to be used for optimization, the `Dynamic` value must be in one of the
/// recognized types: /// recognized types:
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`. /// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
///
/// # Examples
///
/// ```
/// use rhai::{Any, Scope};
///
/// let mut my_scope = Scope::new();
///
/// my_scope.push_constant_dynamic("x", (42_i64).into_dynamic());
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ```
pub fn push_constant_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) { pub fn push_constant_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) {
self.push_dynamic_value(name, EntryType::Constant, value, true); self.push_dynamic_value(name, EntryType::Constant, value, true);
} }
@ -139,63 +243,129 @@ impl<'a> Scope<'a> {
} }
/// Truncate (rewind) the Scope to a previous size. /// Truncate (rewind) the Scope to a previous size.
///
/// # Examples
///
/// ```
/// use rhai::Scope;
///
/// let mut my_scope = Scope::new();
///
/// my_scope.push("x", 42_i64);
/// my_scope.push("y", 123_i64);
/// assert!(my_scope.contains("x"));
/// assert!(my_scope.contains("y"));
/// assert_eq!(my_scope.len(), 2);
///
/// my_scope.rewind(1);
/// assert!(my_scope.contains("x"));
/// assert!(!my_scope.contains("y"));
/// assert_eq!(my_scope.len(), 1);
///
/// my_scope.rewind(0);
/// assert!(!my_scope.contains("x"));
/// assert!(!my_scope.contains("y"));
/// assert_eq!(my_scope.len(), 0);
/// assert!(my_scope.is_empty());
/// ```
pub fn rewind(&mut self, size: usize) { pub fn rewind(&mut self, size: usize) {
self.0.truncate(size); self.0.truncate(size);
} }
/// Does the scope contain the entry? /// Does the scope contain the entry?
pub fn contains(&self, key: &str) -> bool { ///
/// # Examples
///
/// ```
/// use rhai::Scope;
///
/// let mut my_scope = Scope::new();
///
/// my_scope.push("x", 42_i64);
/// assert!(my_scope.contains("x"));
/// assert!(!my_scope.contains("y"));
/// ```
pub fn contains(&self, name: &str) -> bool {
self.0 self.0
.iter() .iter()
.enumerate()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.any(|(_, Entry { name, .. })| name == key) .any(|Entry { name: key, .. }| name == key)
} }
/// Find an entry in the Scope, starting from the last. /// Find an entry in the Scope, starting from the last.
pub(crate) fn get(&self, key: &str) -> Option<(EntryRef, Dynamic)> { pub(crate) fn get(&self, name: &str) -> Option<(EntryRef, Dynamic)> {
self.0 self.0
.iter() .iter()
.enumerate() .enumerate()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.find(|(_, Entry { name, .. })| name == key) .find_map(
.map(
|( |(
index, index,
Entry { Entry {
name, typ, value, .. name: key,
typ,
value,
..
}, },
)| { )| {
( if name == key {
Some((
EntryRef { EntryRef {
name, name: key,
index, index,
typ: *typ, typ: *typ,
}, },
value.clone(), value.clone(),
) ))
} else {
None
}
}, },
) )
} }
/// Get the value of an entry in the Scope, starting from the last. /// Get the value of an entry in the Scope, starting from the last.
///
/// # Examples
///
/// ```
/// use rhai::Scope;
///
/// let mut my_scope = Scope::new();
///
/// my_scope.push("x", 42_i64);
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ```
pub fn get_value<T: Any + Clone>(&self, name: &str) -> Option<T> { pub fn get_value<T: Any + Clone>(&self, name: &str) -> Option<T> {
self.0 self.0
.iter() .iter()
.enumerate() .rev()
.rev() // Always search a Scope in reverse order .find(|Entry { name: key, .. }| name == key)
.find(|(_, Entry { name: key, .. })| name == key) .and_then(|Entry { value, .. }| value.downcast_ref::<T>())
.and_then(|(_, Entry { value, .. })| value.downcast_ref::<T>())
.map(T::clone) .map(T::clone)
} }
/// Update the value of the named variable. /// Update the value of the named entry.
/// Search starts from the last, and only the last variable matching the specified name is updated. /// Search starts backwards from the last, and only the first entry matching the specified name is updated.
/// If no variable matching the specified name is found, a new variable is added. /// If no entry matching the specified name is found, a new one is added.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics when trying to update the value of a constant. /// Panics when trying to update the value of a constant.
///
/// # Examples
///
/// ```
/// use rhai::Scope;
///
/// let mut my_scope = Scope::new();
///
/// my_scope.push("x", 42_i64);
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
///
/// my_scope.set_value("x", 0_i64);
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 0);
/// ```
pub fn set_value<T: Any + Clone>(&mut self, name: &'a str, value: T) { pub fn set_value<T: Any + Clone>(&mut self, name: &'a str, value: T) {
match self.get(name) { match self.get(name) {
Some(( Some((