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)
[![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:
* `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
* 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)
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
* Easy-to-use language similar to JS+Rust
* Support for overloaded functions
* 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/)
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`.
@ -70,7 +73,8 @@ Optional 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`. |
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.
[`unchecked`]: #optional-features
@ -209,8 +213,8 @@ Compiling a script file is also supported:
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`
or its cousins `call_fn1` (one argument) and `call_fn0` (no argument).
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust -
via `call_fn` or its cousins `call_fn1` (one argument) and `call_fn0` (no argument).
```rust
// Define functions in a script.
@ -295,12 +299,14 @@ The following primitive types are supported natively:
[`()`]: #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`.
This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty.
If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`,
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.
@ -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`,
or property values in an object map. In order to get the _real_ value, the actual value type _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`
function to get the textual name of the type and then matching on that).
In Rust, sometimes a `Dynamic` forms part of a returned value - a good example is an array with `Dynamic` elements,
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 decide, at run-time, what type the `Dynamic` value is (short of using the `type_name`
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.
```rust
@ -372,13 +379,15 @@ use rhai::AnyExt; // Pull in the trait.
let list: Array = engine.eval("...")?; // return type is 'Array'
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: 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
```
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
use rhai::Any; // Pull in the trait.
@ -423,7 +432,7 @@ To call these functions, they need to be registered with the [`Engine`].
```rust
use rhai::{Engine, EvalAltResult};
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
fn add(x: i64, y: i64) -> i64 {
@ -432,7 +441,7 @@ fn add(x: i64, y: i64) -> i64 {
// Function that returns a Dynamic value
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>
@ -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
use rhai::Any; // Pull in the trait
fn decide(yes_no: bool) -> Dynamic {
if yes_no {
Box::new(42_i64)
(42_i64).into_dynamic()
} 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 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
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 enables function overloading based on the number and types of parameters.
This example shows how to register multiple functions (or, in this case, multiple overloaded versions of the same function)
under the same name. This enables function overloading based on the number and types of parameters.
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
use rhai::{Engine, EvalAltResult, Position};
@ -612,14 +627,15 @@ let mut engine = Engine::new();
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.
Below I register update and new with the [`Engine`].
To use native types, methods and functions with the [`Engine`], we need to register them.
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
engine.register_fn("update", TestStruct::update); // registers 'update(&mut ts)'
engine.register_fn("new_ts", TestStruct::new); // registers 'new'
engine.register_fn("update", TestStruct::update); // registers 'update(&mut TestStruct)'
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.
@ -631,8 +647,9 @@ let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
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:
methods on a type is implemented as a functions taking an first argument.
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:
methods on a type is implemented as a functions taking a `&mut` first argument.
```rust
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
```
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
// 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`
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.
This gives each evaluation a clean starting slate. In order to continue using the same global state from one invocation to the next,
such a state must be manually created and passed in.
By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined
but no global state. This gives each evaluation a clean starting slate. In order to continue using the same global state
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
that are `Send + Sync` are supported, and the entire `Scope` itself will also be `Send + Sync`. This is extremely useful in multi-threaded applications.
All `Scope` variables are [`Dynamic`], meaning they can store values of any type. If the [`sync`] feature is used, however,
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
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 '`_`').
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.
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.
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.
@ -935,7 +957,8 @@ number = -5 - +5;
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 |
| ------------ | --------------------------------- |
@ -961,18 +984,21 @@ The following standard functions (defined in the standard library but excluded i
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.
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.
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`]).
This is particularly useful when printing output.
Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded
if [`no_stdlib`]). This is particularly useful when printing output.
[`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.
However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system
types - `INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`.
However, if the [`no_stdlib`] feature is turned on, comparisons can only be made between restricted system types -
`INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), string, array, `bool`, `char`.
```rust
42 == 42; // true
@ -1421,8 +1447,8 @@ return 123 + 456; // returns 579
Errors and `throw`-ing exceptions
--------------------------------
All of [`Engine`]'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult` holding error information.
To deliberately return an error during an evaluation, use the `throw` keyword.
All of [`Engine`]'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult`
holding error information. To deliberately return an error during an evaluation, use the `throw` keyword.
```rust
if some_bad_condition_has_happened {
@ -1493,7 +1519,8 @@ fn foo() { x } // <- syntax error: variable 'x' doesn't exist
### Passing arguments by value
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.
```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).
The above script optimizes to:
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). The above script optimizes to:
```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
nor cause side any effects, with the exception of `print` and `debug` which are handled specially) so using [`OptimizationLevel::Full`]
is usually quite safe _unless_ you register your own types and functions.
nor cause side any effects, with the exception of `print` and `debug` which are handled specially) so using
[`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 to replace built-in operators, they will also be called when the operators are used (in an `if`
statement, for example) and cause side-effects.
If custom functions are registered to replace built-in operators, they will also be called when the operators are used
(in an `if` statement, for example) and cause side-effects.
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
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`], _assumes_ that all functions are _pure_, so when it finds constant arguments
it will eagerly execute the function call. 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.
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`], _assumes_ that
all functions are _pure_, so when it finds constant arguments it will eagerly execute the function call.
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.
@ -1799,15 +1827,15 @@ 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.
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.
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),
turn it off by setting the optimization level to [`OptimizationLevel::None`].
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), turn it off by setting the optimization level to [`OptimizationLevel::None`].
```rust
let engine = rhai::Engine::new();

View File

@ -52,9 +52,14 @@ pub(crate) struct EntryRef<'a> {
/// let mut engine = Engine::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(())
/// # }
/// ```
@ -68,31 +73,106 @@ pub struct Scope<'a>(Vec<Entry<'a>>);
impl<'a> Scope<'a> {
/// 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 {
Self(Vec::new())
}
/// 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) {
self.0.clear();
}
/// 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 {
self.0.len()
}
/// 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 {
self.0.len() == 0
}
/// 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) {
self.push_dynamic_value(name, EntryType::Normal, value.into_dynamic(), false);
}
/// 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) {
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 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:
/// `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) {
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 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
/// recognized types:
/// `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) {
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.
///
/// # 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) {
self.0.truncate(size);
}
/// 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
.iter()
.enumerate()
.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.
pub(crate) fn get(&self, key: &str) -> Option<(EntryRef, Dynamic)> {
pub(crate) fn get(&self, name: &str) -> Option<(EntryRef, Dynamic)> {
self.0
.iter()
.enumerate()
.rev() // Always search a Scope in reverse order
.find(|(_, Entry { name, .. })| name == key)
.map(
.find_map(
|(
index,
Entry {
name, typ, value, ..
name: key,
typ,
value,
..
},
)| {
(
if name == key {
Some((
EntryRef {
name,
name: key,
index,
typ: *typ,
},
value.clone(),
)
))
} else {
None
}
},
)
}
/// 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> {
self.0
.iter()
.enumerate()
.rev() // Always search a Scope in reverse order
.find(|(_, Entry { name: key, .. })| name == key)
.and_then(|(_, Entry { value, .. })| value.downcast_ref::<T>())
.rev()
.find(|Entry { name: key, .. }| name == key)
.and_then(|Entry { value, .. }| value.downcast_ref::<T>())
.map(T::clone)
}
/// Update the value of the named variable.
/// Search starts from the last, and only the last variable matching the specified name is updated.
/// If no variable matching the specified name is found, a new variable is added.
/// Update the value of the named entry.
/// Search starts backwards from the last, and only the first entry matching the specified name is updated.
/// If no entry matching the specified name is found, a new one is added.
///
/// # Panics
///
/// 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) {
match self.get(name) {
Some((