...
This commit is contained in:
76
rhai_engine/rhaibook/language/arrays-oob.md
Normal file
76
rhai_engine/rhaibook/language/arrays-oob.md
Normal file
@@ -0,0 +1,76 @@
|
||||
Out-of-Bounds Index for Arrays
|
||||
==============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
[`Engine::on_invalid_array_index`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_invalid_array_index
|
||||
[`Target`]: https://docs.rs/rhai/latest/rhai/enum.Target.html
|
||||
|
||||
|
||||
~~~admonish warning.small "Requires `internals`"
|
||||
|
||||
This is an advanced feature that requires the [`internals`] feature to be enabled.
|
||||
~~~
|
||||
|
||||
Normally, when an index is out-of-bounds for an [array], an error is raised.
|
||||
|
||||
It is possible to completely control this behavior via a special callback function
|
||||
registered into an [`Engine`] via `on_invalid_array_index`.
|
||||
|
||||
Using this callback, for instance, it is simple to instruct Rhai to extend the [array] to
|
||||
accommodate this new element, or to return a default value instead of raising an error.
|
||||
|
||||
|
||||
Function Signature
|
||||
------------------
|
||||
|
||||
The function signature passed to [`Engine::on_invalid_array_index`] takes the following form.
|
||||
|
||||
> ```rust
|
||||
> Fn(array: &mut Array, index: i64, context: EvalContext) -> Result<Target, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :-------------------: | -------------------------------- |
|
||||
| `array` | [`&mut Array`][array] | the [array] being accessed |
|
||||
| `index` | `i64` | index value |
|
||||
| `context` | [`EvalContext`] | the current _evaluation context_ |
|
||||
|
||||
### Return value
|
||||
|
||||
The return value is `Result<Target, Box<EvalAltResult>>`.
|
||||
|
||||
[`Target`] is an advanced type, available only under the [`internals`] feature, that represents a
|
||||
_reference_ to a [`Dynamic`] value.
|
||||
|
||||
It can be used to point to a particular value within the [array] or a new temporary value.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
engine.on_invalid_array_index(|arr, index, _| {
|
||||
match index {
|
||||
-100 => {
|
||||
// The array can be modified in place
|
||||
arr.push((42_i64).into());
|
||||
|
||||
// Return a mutable reference to an element
|
||||
let value_ref = arr.last_mut().unwrap();
|
||||
Ok(value_ref.into())
|
||||
}
|
||||
100 => {
|
||||
// Return a temporary value (not a reference)
|
||||
let value = Dynamic::from(100_i64);
|
||||
Ok(value.into())
|
||||
}
|
||||
// Return the standard out-of-bounds error
|
||||
_ => Err(EvalAltResult::ErrorArrayBounds(
|
||||
arr.len(), index, Position::NONE
|
||||
).into()),
|
||||
}
|
||||
});
|
||||
```
|
300
rhai_engine/rhaibook/language/arrays.md
Normal file
300
rhai_engine/rhaibook/language/arrays.md
Normal file
@@ -0,0 +1,300 @@
|
||||
Arrays
|
||||
======
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish tip.side "Safety"
|
||||
|
||||
Always limit the [maximum size of arrays].
|
||||
```
|
||||
|
||||
Arrays are first-class citizens in Rhai.
|
||||
|
||||
All elements stored in an array are [`Dynamic`], and the array can freely grow or shrink with
|
||||
elements added or removed.
|
||||
|
||||
The Rust type of a Rhai array is `rhai::Array` which is an alias to `Vec<Dynamic>`.
|
||||
|
||||
[`type_of()`] an array returns `"array"`.
|
||||
|
||||
Arrays are disabled via the [`no_index`] feature.
|
||||
|
||||
|
||||
Literal Syntax
|
||||
--------------
|
||||
|
||||
Array literals are built within square brackets `[` ... `]` and separated by commas `,`:
|
||||
|
||||
> `[` _value_`,` _value_`,` ... `,` _value_ `]`
|
||||
>
|
||||
> `[` _value_`,` _value_`,` ... `,` _value_ `,` `]` `// trailing comma is OK`
|
||||
|
||||
|
||||
Element Access Syntax
|
||||
---------------------
|
||||
|
||||
### From beginning
|
||||
|
||||
Like C, arrays are accessed with zero-based, non-negative integer indices:
|
||||
|
||||
> _array_ `[` _index position from 0 to (length−1)_ `]`
|
||||
|
||||
### From end
|
||||
|
||||
A _negative_ position accesses an element in the array counting from the _end_, with −1 being the
|
||||
_last_ element.
|
||||
|
||||
> _array_ `[` _index position from −1 to −length_ `]`
|
||||
|
||||
|
||||
Out-of-Bounds Index
|
||||
-------------------
|
||||
|
||||
Trying to read from an index that is out of bounds causes an error.
|
||||
|
||||
```admonish tip.small "Advanced tip: Override standard behavior"
|
||||
|
||||
For fine-tuned control on what happens when an out-of-bounds index is accessed,
|
||||
see [_Out-of-Bounds Index for Arrays_](arrays-oob.md).
|
||||
```
|
||||
|
||||
Built-in Functions
|
||||
------------------
|
||||
|
||||
The following methods (mostly defined in the [`BasicArrayPackage`][built-in packages] but excluded
|
||||
when using a [raw `Engine`]) operate on arrays.
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `get` | position, counting from end if < 0 | gets a copy of the element at a certain position ([`()`] if the position is not valid) |
|
||||
| `set` | <ol><li>position, counting from end if < 0</li><li>new element</li></ol> | sets a certain position to a new value (no effect if the position is not valid) |
|
||||
| `push`, `+=` operator | element to append (not an array) | appends an element to the end |
|
||||
| `append`, `+=` operator | array to append | concatenates the second array to the end of the first |
|
||||
| `+` operator | <ol><li>first array</li><li>second array</li></ol> | concatenates the first array with the second |
|
||||
| `==` operator | <ol><li>first array</li><li>second array</li></ol> | are two arrays the same (elements compared with the `==` operator, if defined)? |
|
||||
| `!=` operator | <ol><li>first array</li><li>second array</li></ol> | are two arrays different (elements compared with the `==` operator, if defined)? |
|
||||
| `insert` | <ol><li>position, counting from end if < 0, end if ≥ length</li><li>element to insert</li></ol> | inserts an element at a certain position |
|
||||
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
|
||||
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
|
||||
| `extract` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>_(optional)_ number of elements to extract, none if ≤ 0, to end if omitted</li></ol> | extracts a portion of the array into a new array |
|
||||
| `extract` | [range] of elements to extract, from beginning if ≤ 0, to end if ≥ length | extracts a portion of the array into a new array |
|
||||
| `remove` | position, counting from end if < 0 | removes an element at a particular position and returns it ([`()`] if the position is not valid) |
|
||||
| `reverse` | _none_ | reverses the array |
|
||||
| `len` method and property | _none_ | returns the number of elements |
|
||||
| `is_empty` method and property | _none_ | returns `true` if the array is empty |
|
||||
| `pad` | <ol><li>target length</li><li>element to pad</li></ol> | pads the array with an element to at least a specified length |
|
||||
| `clear` | _none_ | empties the array |
|
||||
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
|
||||
| `chop` | target length | cuts off the head of the array, leaving the tail at exactly a specified length |
|
||||
| `split` | <ol><li>array</li><li>position to split at, counting from end if < 0, end if ≥ length</li></ol> | splits the array into two arrays, starting from a specified position |
|
||||
| `for_each` | [function pointer] for processing elements | run through each element in the array in order, binding each to `this` and calling the processing function taking the following parameters: <ol><li>`this`: array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `drain` | [function pointer] to predicate (usually a [closure]) | removes all elements (returning them) that return `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `drain` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of elements to remove, none if ≤ 0</li></ol> | removes a portion of the array, returning the removed elements as a new array |
|
||||
| `drain` | [range] of elements to remove, from beginning if ≤ 0, to end if ≥ length | removes a portion of the array, returning the removed elements as a new array |
|
||||
| `retain` | [function pointer] to predicate (usually a [closure]) | removes all elements (returning them) that do not return `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `retain` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of elements to retain, none if ≤ 0</li></ol> | retains a portion of the array, removes all other elements and returning them as a new array |
|
||||
| `retain` | [range] of elements to retain, from beginning if ≤ 0, to end if ≥ length | retains a portion of the array, removes all other bytes and returning them as a new array |
|
||||
| `splice` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of elements to remove, none if ≤ 0</li><li>array to insert</li></ol> | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) |
|
||||
| `splice` | <ol><li>[range] of elements to remove, from beginning if ≤ 0, to end if ≥ length</li><li>array to insert</li></ol> | replaces a portion of the array with another (not necessarily of the same length as the replaced portion) |
|
||||
| `filter` | [function pointer] to predicate (usually a [closure]) | constructs a new array with all elements that return `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `contains`, [`in`] operator | element to find | does the array contain an element? The `==` operator (if defined) is used to compare [custom types] |
|
||||
| `index_of` | <ol><li>element to find (not a [function pointer])</li><li>_(optional)_ start position, counting from end if < 0, end if ≥ length</li></ol> | returns the position of the first element in the array that equals the supplied element (using the `==` operator, if defined), or −1 if not found</li></ol> |
|
||||
| `index_of` | <ol><li>[function pointer] to predicate (usually a [closure])</li><li>_(optional)_ start position, counting from end if < 0, end if ≥ length</li></ol> | returns the position of the first element in the array that returns `true` when called with the predicate function, or −1 if not found:<ol><li>array element (if none, the array element is bound to `this`)</li><li>_(optional)_ index position</li></ol> |
|
||||
| `find` | <ol><li>[function pointer] to predicate (usually a [closure])</li><li>_(optional)_ start position, counting from end if < 0, end if ≥ length</li></ol> | returns the first element in the array that returns `true` when called with the predicate function, or [`()`] if not found:<ol><li>array element (if none, the array element is bound to `this`)</li><li>_(optional)_ index position</li></ol> |
|
||||
| `find_map` | <ol><li>[function pointer] to predicate (usually a [closure])</li><li>_(optional)_ start position, counting from end if < 0, end if ≥ length</li></ol> | returns the first non-[`()`] value of the first element in the array when called with the predicate function, or [`()`] if not found:<ol><li>array element (if none, the array element is bound to `this`)</li><li>_(optional)_ index position</li></ol> |
|
||||
| `dedup` | _(optional)_ [function pointer] to predicate (usually a [closure]); if omitted, the `==` operator is used, if defined | removes all but the first of _consecutive_ elements in the array that return `true` when called with the predicate function (non-consecutive duplicates are _not_ removed):<br/>1st & 2nd parameters: two elements in the array |
|
||||
| `map` | [function pointer] to conversion function (usually a [closure]) | constructs a new array with all elements mapped to the result of applying the conversion function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `reduce` | <ol><li>[function pointer] to accumulator function (usually a [closure])</li><li>_(optional)_ the initial value</li></ol> | reduces the array into a single value via the accumulator function taking the following parameters (if the second parameter is omitted, the array element is bound to `this`):<ol><li>accumulated value ([`()`] initially)</li><li>`this`: array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `reduce_rev` | <ol><li>[function pointer] to accumulator function (usually a [closure])</li><li>_(optional)_ the initial value</li></ol> | reduces the array (in reverse order) into a single value via the accumulator function taking the following parameters (if the second parameter is omitted, the array element is bound to `this`):<ol><li>accumulated value ([`()`] initially)</li><li>`this`: array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `zip` | <ol><li>array to zip</li><li>[function pointer] to conversion function (usually a [closure])</li></ol> | constructs a new array with all element pairs from two arrays mapped to the result of applying the conversion function taking the following parameters:<ol><li>first array element</li><li>second array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `some` | [function pointer] to predicate (usually a [closure]) | returns `true` if any element returns `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `all` | [function pointer] to predicate (usually a [closure]) | returns `true` if all elements return `true` when called with the predicate function taking the following parameters (if none, the array element is bound to `this`):<ol><li>array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `sort` | [function pointer] to a comparison function (usually a [closure]) | sorts the array with a comparison function taking the following parameters:<ol><li>first element</li><li>second element<br/>return value: `INT` < 0 if first < second, > 0 if first > second, 0 if first == second</li></ol> |
|
||||
| `sort` | _none_ | sorts a _homogeneous_ array containing only elements of the same comparable built-in type (`INT`, `FLOAT`, [`Decimal`][rust_decimal], [string], [character], `bool`, [`()`]) |
|
||||
|
||||
|
||||
```admonish tip.small "Tip: Use custom types with arrays"
|
||||
|
||||
To use a [custom type] with arrays, a number of functions need to be manually implemented,
|
||||
in particular the `==` operator in order to support the [`in`] operator which uses `==` (via the
|
||||
`contains` method) to compare elements.
|
||||
|
||||
See the section on [custom types] for more details.
|
||||
```
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let y = [2, 3]; // y == [2, 3]
|
||||
|
||||
let y = [2, 3,]; // y == [2, 3]
|
||||
|
||||
y.insert(0, 1); // y == [1, 2, 3]
|
||||
|
||||
y.insert(999, 4); // y == [1, 2, 3, 4]
|
||||
|
||||
y.len == 4;
|
||||
|
||||
y[0] == 1;
|
||||
y[1] == 2;
|
||||
y[2] == 3;
|
||||
y[3] == 4;
|
||||
|
||||
(1 in y) == true; // use 'in' to test if an element exists in the array
|
||||
|
||||
(42 in y) == false; // 'in' uses the 'contains' function, which uses the
|
||||
// '==' operator (that users can override)
|
||||
// to check if the target element exists in the array
|
||||
|
||||
y.contains(1) == true; // the above de-sugars to this
|
||||
|
||||
y[1] = 42; // y == [1, 42, 3, 4]
|
||||
|
||||
(42 in y) == true;
|
||||
|
||||
y.remove(2) == 3; // y == [1, 42, 4]
|
||||
|
||||
y.len == 3;
|
||||
|
||||
y[2] == 4; // elements after the removed element are shifted
|
||||
|
||||
ts.list = y; // arrays can be assigned completely (by value copy)
|
||||
|
||||
ts.list[1] == 42;
|
||||
|
||||
[1, 2, 3][0] == 1; // indexing on array literal
|
||||
|
||||
[1, 2, 3][-1] == 3; // negative position counts from the end
|
||||
|
||||
fn abc() {
|
||||
[42, 43, 44] // a function returning an array
|
||||
}
|
||||
|
||||
abc()[0] == 42;
|
||||
|
||||
y.push(4); // y == [1, 42, 4, 4]
|
||||
|
||||
y += 5; // y == [1, 42, 4, 4, 5]
|
||||
|
||||
y.len == 5;
|
||||
|
||||
y.shift() == 1; // y == [42, 4, 4, 5]
|
||||
|
||||
y.chop(3); // y == [4, 4, 5]
|
||||
|
||||
y.len == 3;
|
||||
|
||||
y.pop() == 5; // y == [4, 4]
|
||||
|
||||
y.len == 2;
|
||||
|
||||
for element in y { // arrays can be iterated with a 'for' statement
|
||||
print(element);
|
||||
}
|
||||
|
||||
y.pad(6, "hello"); // y == [4, 4, "hello", "hello", "hello", "hello"]
|
||||
|
||||
y.len == 6;
|
||||
|
||||
y.truncate(4); // y == [4, 4, "hello", "hello"]
|
||||
|
||||
y.len == 4;
|
||||
|
||||
y.clear(); // y == []
|
||||
|
||||
y.len == 0;
|
||||
|
||||
// The examples below use 'a' as the master array
|
||||
|
||||
let a = [42, 123, 99];
|
||||
|
||||
a.for_each(|| this *= 2);
|
||||
|
||||
a == [84, 246, 198];
|
||||
|
||||
a.for_each(|i| this /= 2);
|
||||
|
||||
a == [42, 123, 99];
|
||||
|
||||
a.map(|v| v + 1); // returns [43, 124, 100]
|
||||
|
||||
a.map(|| this + 1); // returns [43, 124, 100]
|
||||
|
||||
a.map(|v, i| v + i); // returns [42, 124, 101]
|
||||
|
||||
a.filter(|v| v > 50); // returns [123, 99]
|
||||
|
||||
a.filter(|| this > 50); // returns [123, 99]
|
||||
|
||||
a.filter(|v, i| i == 1); // returns [123]
|
||||
|
||||
a.filter("is_odd"); // returns [123, 99]
|
||||
|
||||
a.filter(Fn("is_odd")); // <- previous statement is equivalent to this...
|
||||
|
||||
a.filter(|v| is_odd(v)); // <- or this
|
||||
|
||||
a.some(|v| v > 50); // returns true
|
||||
|
||||
a.some(|| this > 50); // returns true
|
||||
|
||||
a.some(|v, i| v < i); // returns false
|
||||
|
||||
a.all(|v| v > 50); // returns false
|
||||
|
||||
a.all(|| this > 50); // returns false
|
||||
|
||||
a.all(|v, i| v > i); // returns true
|
||||
|
||||
// Reducing - initial value provided directly
|
||||
a.reduce(|sum| sum + this, 0) == 264;
|
||||
|
||||
// Reducing - initial value provided directly
|
||||
a.reduce(|sum, v| sum + v, 0) == 264;
|
||||
|
||||
// Reducing - initial value is '()'
|
||||
a.reduce(
|
||||
|sum, v| if sum.type_of() == "()" { v } else { sum + v }
|
||||
) == 264;
|
||||
|
||||
// Reducing - initial value has index position == 0
|
||||
a.reduce(|sum, v, i|
|
||||
if i == 0 { v } else { sum + v }
|
||||
) == 264;
|
||||
|
||||
// Reducing in reverse - initial value provided directly
|
||||
a.reduce_rev(|sum| sum + this, 0) == 264;
|
||||
|
||||
// Reducing in reverse - initial value provided directly
|
||||
a.reduce_rev(|sum, v| sum + v, 0) == 264;
|
||||
|
||||
// Reducing in reverse - initial value is '()'
|
||||
a.reduce_rev(
|
||||
|sum, v| if sum.type_of() == "()" { v } else { sum + v }
|
||||
) == 264;
|
||||
|
||||
// Reducing in reverse - initial value has index position == 0
|
||||
a.reduce_rev(|sum, v, i|
|
||||
if i == 2 { v } else { sum + v }
|
||||
) == 264;
|
||||
|
||||
// In-place modification
|
||||
|
||||
a.splice(1..=1, [1, 3, 2]); // a == [42, 1, 3, 2, 99]
|
||||
|
||||
a.extract(1..=3); // returns [1, 3, 2]
|
||||
|
||||
a.sort(|x, y| y - x); // a == [99, 42, 3, 2, 1]
|
||||
|
||||
a.sort(); // a == [1, 2, 3, 42, 99]
|
||||
|
||||
a.drain(|v| v <= 1); // a == [2, 3, 42, 99]
|
||||
|
||||
a.drain(|v, i| i ≥ 3); // a == [2, 3, 42]
|
||||
|
||||
a.retain(|v| v > 10); // a == [42]
|
||||
|
||||
a.retain(|v, i| i > 0); // a == []
|
||||
```
|
89
rhai_engine/rhaibook/language/assignment-op.md
Normal file
89
rhai_engine/rhaibook/language/assignment-op.md
Normal file
@@ -0,0 +1,89 @@
|
||||
Compound Assignments
|
||||
====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Compound assignments are assignments with a [binary operator][operators] attached.
|
||||
|
||||
```rust
|
||||
number += 8; // number = number + 8
|
||||
|
||||
number -= 7; // number = number - 7
|
||||
|
||||
number *= 6; // number = number * 6
|
||||
|
||||
number /= 5; // number = number / 5
|
||||
|
||||
number %= 4; // number = number % 4
|
||||
|
||||
number **= 3; // number = number ** 3
|
||||
|
||||
number <<= 2; // number = number << 2
|
||||
|
||||
number >>= 1; // number = number >> 1
|
||||
|
||||
number &= 0x00ff; // number = number & 0x00ff;
|
||||
|
||||
number |= 0x00ff; // number = number | 0x00ff;
|
||||
|
||||
number ^= 0x00ff; // number = number ^ 0x00ff;
|
||||
```
|
||||
|
||||
|
||||
The Flexible `+=`
|
||||
-----------------
|
||||
|
||||
The the `+` and `+=` operators are often [overloaded][function overloading] to perform build-up
|
||||
operations for different data types.
|
||||
|
||||
### Build strings
|
||||
|
||||
```rust
|
||||
let my_str = "abc";
|
||||
|
||||
my_str += "ABC";
|
||||
my_str += 12345;
|
||||
|
||||
my_str == "abcABC12345"
|
||||
```
|
||||
|
||||
### Concatenate arrays
|
||||
|
||||
```rust
|
||||
let my_array = [1, 2, 3];
|
||||
|
||||
my_array += [4, 5];
|
||||
|
||||
my_array == [1, 2, 3, 4, 5];
|
||||
```
|
||||
|
||||
### Concatenate BLOB's
|
||||
|
||||
```rust
|
||||
let my_blob = blob(3, 0x42);
|
||||
|
||||
my_blob += blob(5, 0x89);
|
||||
|
||||
my_blob.to_string() == "[4242428989898989]";
|
||||
```
|
||||
|
||||
### Mix two object maps together
|
||||
|
||||
```rust
|
||||
let my_obj = #{ a:1, b:2 };
|
||||
|
||||
my_obj += #{ c:3, d:4, e:5 };
|
||||
|
||||
my_obj == #{ a:1, b:2, c:3, d:4, e:5 };
|
||||
```
|
||||
|
||||
### Add seconds to timestamps
|
||||
|
||||
```rust
|
||||
let now = timestamp();
|
||||
|
||||
now += 42.0;
|
||||
|
||||
(now - timestamp()).round() == 42.0;
|
||||
```
|
125
rhai_engine/rhaibook/language/assignment.md
Normal file
125
rhai_engine/rhaibook/language/assignment.md
Normal file
@@ -0,0 +1,125 @@
|
||||
Assignments
|
||||
===========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Value assignments to [variables] use the `=` symbol.
|
||||
|
||||
```rust
|
||||
let foo = 42;
|
||||
|
||||
bar = 123 * 456 - 789;
|
||||
|
||||
x[1][2].prop = do_calculation();
|
||||
```
|
||||
|
||||
|
||||
Valid Assignment Targets
|
||||
------------------------
|
||||
|
||||
The left-hand-side (LHS) of an assignment statement must be a valid
|
||||
_[l-value](https://en.wikipedia.org/wiki/Value_(computer_science))_, which must be rooted in a
|
||||
[variable], potentially extended via indexing or properties.
|
||||
|
||||
~~~admonish bug "Assigning to invalid l-value"
|
||||
|
||||
Expressions that are not valid _l-values_ cannot be assigned to.
|
||||
|
||||
```rust
|
||||
x = 42; // variable is an l-value
|
||||
|
||||
x[1][2][3] = 42 // variable indexing is an l-value
|
||||
|
||||
x.prop1.prop2 = 42; // variable property is an l-value
|
||||
|
||||
foo(x) = 42; // syntax error: function call is not an l-value
|
||||
|
||||
x.foo() = 42; // syntax error: method call is not an l-value
|
||||
|
||||
(x + y) = 42; // syntax error: binary op is not an l-value
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
Values are Cloned
|
||||
-----------------
|
||||
|
||||
Values assigned are always _cloned_.
|
||||
So care must be taken when assigning large data types (such as [arrays]).
|
||||
|
||||
```rust
|
||||
x = y; // value of 'y' is cloned
|
||||
|
||||
x == y; // both 'x' and 'y' hold different copies
|
||||
// of the same value
|
||||
```
|
||||
|
||||
|
||||
Moving Data
|
||||
-----------
|
||||
|
||||
When assigning large data types, sometimes it is desirable to _move_ the data instead of cloning it.
|
||||
|
||||
Use the `take` function (defined in the [`LangCorePackage`][built-in packages] but excluded
|
||||
when using a [raw `Engine`]) to _move_ data.
|
||||
|
||||
### The original variable is left with `()`
|
||||
|
||||
```rust
|
||||
x = take(y); // value of 'y' is moved to 'x'
|
||||
|
||||
y == (); // 'y' now holds '()'
|
||||
|
||||
x != y; // 'x' holds the original value of 'y'
|
||||
```
|
||||
|
||||
### Return large data types from functions
|
||||
|
||||
`take` is convenient when returning large data types from a [function].
|
||||
|
||||
```rust
|
||||
fn get_large_value_naive() {
|
||||
let large_result = do_complex_calculation();
|
||||
|
||||
large_result.done = true;
|
||||
|
||||
// Return a cloned copy of the result, then the
|
||||
// local variable 'large_result' is thrown away!
|
||||
large_result
|
||||
}
|
||||
|
||||
fn get_large_value_smart() {
|
||||
let large_result = do_complex_calculation();
|
||||
|
||||
large_result.done = true;
|
||||
|
||||
// Return the result without cloning!
|
||||
// Method style call is also OK.
|
||||
large_result.take()
|
||||
}
|
||||
```
|
||||
|
||||
### Assigning large data types to object map properties
|
||||
|
||||
`take` is useful when assigning large data types to [object map] properties.
|
||||
|
||||
```rust
|
||||
let x = [];
|
||||
|
||||
// Build a large array
|
||||
for n in 0..1000000 { x += n; }
|
||||
|
||||
// The following clones the large array from 'x'.
|
||||
// Both 'my_object.my_property' and 'x' now hold exact copies
|
||||
// of the same large array!
|
||||
my_object.my_property = x;
|
||||
|
||||
// Move it to object map property via 'take' without cloning.
|
||||
// 'x' now holds '()'.
|
||||
my_object.my_property = x.take();
|
||||
|
||||
// Without 'take', the following must be done to avoid cloning:
|
||||
my_object.my_property = [];
|
||||
|
||||
for n in 0..1000000 { my_object.my_property += n; }
|
||||
```
|
125
rhai_engine/rhaibook/language/bit-fields.md
Normal file
125
rhai_engine/rhaibook/language/bit-fields.md
Normal file
@@ -0,0 +1,125 @@
|
||||
Integer as Bit-Fields
|
||||
=====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish note.side
|
||||
|
||||
Nothing here cannot be done via standard bit-manipulation (i.e. shifting and masking).
|
||||
|
||||
Built-in support is more elegant and performant since it usually replaces a sequence of multiple steps.
|
||||
|
||||
```
|
||||
|
||||
Since bit-wise operators are defined on integer numbers, individual bits can also be accessed and
|
||||
manipulated via an indexing syntax.
|
||||
|
||||
If a bit is set (i.e. `1`), the index access returns `true`.
|
||||
|
||||
If a bit is not set (i.e. `0`), the index access returns `false`.
|
||||
|
||||
When a [range] is used, the bits within the [range] are shifted and extracted as an integer value.
|
||||
|
||||
Bit-fields are very commonly used in embedded systems which must squeeze data into limited memory.
|
||||
Built-in support makes handling them efficient.
|
||||
|
||||
Indexing an integer as a bit-field is disabled for the [`no_index`] feature.
|
||||
|
||||
|
||||
Syntax
|
||||
------
|
||||
|
||||
### From Least-Significant Bit (LSB)
|
||||
|
||||
Bits in a bit-field are accessed with zero-based, non-negative integer indices:
|
||||
|
||||
> _integer_ `[` _index from 0 to 63 or 31_ `]`
|
||||
>
|
||||
> _integer_ `[` _index from 0 to 63 or 31_ `] =` `true` or `false` ;
|
||||
|
||||
[Ranges] can also be used:
|
||||
|
||||
> _integer_ `[` _start_ `..` _end_ `]`
|
||||
> _integer_ `[` _start_ `..=` _end_ `]`
|
||||
>
|
||||
> _integer_ `[` _start_ `..` _end_ `] =` _new integer value_ ;
|
||||
> _integer_ `[` _start_ `..=` _end_ `] =` _new integer value_ ;
|
||||
|
||||
```admonish warning.small "Number of bits"
|
||||
|
||||
The maximum bit number that can be accessed is 63 (or 31 under [`only_i32`]).
|
||||
|
||||
Bits outside of the range are ignored.
|
||||
```
|
||||
|
||||
|
||||
### From Most-Significant Bit (MSB)
|
||||
|
||||
A _negative_ index accesses a bit in the bit-field counting from the _end_, or from the
|
||||
_most-significant bit_, with −1 being the _highest_ bit.
|
||||
|
||||
> _integer_ `[` _index from −1 to −64 or −32_ `]`
|
||||
>
|
||||
> _integer_ `[` _index from −1 to −64 or −32_ `] =` `true` or `false` ;
|
||||
|
||||
[Ranges] always count from the least-significant bit (LSB) and has no support for negative positions.
|
||||
|
||||
```admonish warning.small "Number of bits"
|
||||
|
||||
The maximum bit number that can be accessed is −64 (or −32 under [`only_i32`]).
|
||||
```
|
||||
|
||||
|
||||
Bit-Field Functions
|
||||
-------------------
|
||||
|
||||
The following standard functions (defined in the [`BitFieldPackage`][built-in packages] but excluded
|
||||
when using a [raw `Engine`]) operate on `INT` bit-fields.
|
||||
|
||||
These functions are available even under the [`no_index`] feature.
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
|
||||
| `get_bit` | bit number, counting from MSB if < 0 | returns the state of a bit: `true` if `1`, `false` if `0` |
|
||||
| `set_bit` | <ol><li>bit number, counting from MSB if < 0</li><li>new state: `true` if `1`, `false` if `0`</li></ol> | sets the state of a bit |
|
||||
| `get_bits` | <ol><li>starting bit number, counting from MSB if < 0</li><li>number of bits to extract, none if < 1, to MSB if ≥ _length_</li></ol> | extracts a number of bits, shifted towards LSB |
|
||||
| `get_bits` | [range] of bits | extracts a number of bits, shifted towards LSB |
|
||||
| `set_bits` | <ol><li>starting bit number, counting from MSB if < 0</li><li>number of bits to set, none if < 1, to MSB if ≥ _length_<br/>3) new value</li></ol> | sets a number of bits from the new value |
|
||||
| `set_bits` | <ol><li>[range] of bits</li><li>new value</li></ol> | sets a number of bits from the new value |
|
||||
| `bits` method and property | <ol><li>_(optional)_ starting bit number, counting from MSB if < 0</li><li>_(optional)_ number of bits to extract, none if < 1, to MSB if ≥ _length_</li></ol> | allows iteration over the bits of a bit-field |
|
||||
| `bits` | [range] of bits | allows iteration over the bits of a bit-field |
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```js , no_run
|
||||
// Assume the following bits fields in a single 16-bit word:
|
||||
// ┌─────────┬────────────┬──────┬─────────┐
|
||||
// │ 15-12 │ 11-4 │ 3 │ 2-0 │
|
||||
// ├─────────┼────────────┼──────┼─────────┤
|
||||
// │ 0 │ 0-255 data │ flag │ command │
|
||||
// └─────────┴────────────┴──────┴─────────┘
|
||||
|
||||
let value = read_start_hw_register(42);
|
||||
|
||||
let command = value.get_bits(0, 3); // Command = bits 0-2
|
||||
|
||||
let flag = value[3]; // Flag = bit 3
|
||||
|
||||
let data = value[4..=11]; // Data = bits 4-11
|
||||
let data = value.get_bits(4..=11); // <- above is the same as this
|
||||
|
||||
let reserved = value.get_bits(-4); // Reserved = last 4 bits
|
||||
|
||||
if reserved != 0 {
|
||||
throw reserved;
|
||||
}
|
||||
|
||||
switch command {
|
||||
0 => print(`Data = ${data}`),
|
||||
1 => value[4..=11] = data / 2,
|
||||
2 => value[3] = !flag,
|
||||
_ => print(`Unknown: ${command}`)
|
||||
}
|
||||
```
|
202
rhai_engine/rhaibook/language/blobs.md
Normal file
202
rhai_engine/rhaibook/language/blobs.md
Normal file
@@ -0,0 +1,202 @@
|
||||
BLOB's
|
||||
======
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish tip.side "Safety"
|
||||
|
||||
Always limit the [maximum size of arrays].
|
||||
```
|
||||
|
||||
BLOB's (**B**inary **L**arge **OB**jects), used to hold packed arrays of bytes, have built-in support in Rhai.
|
||||
|
||||
A BLOB has no literal representation, but is created via the `blob` function, or simply returned as
|
||||
the result of a function call (e.g. `generate_thumbnail_image` that generates a thumbnail version of
|
||||
a large image as a BLOB).
|
||||
|
||||
All items stored in a BLOB are bytes (i.e. `u8`) and the BLOB can freely grow or shrink with bytes
|
||||
added or removed.
|
||||
|
||||
The Rust type of a Rhai BLOB is `rhai::Blob` which is an alias to `Vec<u8>`.
|
||||
|
||||
[`type_of()`] a BLOB returns `"blob"`.
|
||||
|
||||
BLOB's are disabled via the [`no_index`] feature.
|
||||
|
||||
|
||||
Element Access Syntax
|
||||
---------------------
|
||||
|
||||
### From beginning
|
||||
|
||||
Like [arrays], BLOB's are accessed with zero-based, non-negative integer indices:
|
||||
|
||||
> _blob_ `[` _index position from 0 to (length−1)_ `]`
|
||||
|
||||
### From end
|
||||
|
||||
A _negative_ position accesses an element in the BLOB counting from the _end_, with −1 being the
|
||||
_last_ element.
|
||||
|
||||
> _blob_ `[` _index position from −1 to −length_ `]`
|
||||
|
||||
```admonish info.small "Byte values"
|
||||
|
||||
The value of a particular byte in a BLOB is mapped to an `INT` (which can be 64-bit or 32-bit
|
||||
depending on the [`only_i32`] feature).
|
||||
|
||||
Only the lowest 8 bits are significant, all other bits are ignored.
|
||||
```
|
||||
|
||||
|
||||
Create a BLOB
|
||||
-------------
|
||||
|
||||
The function `blob` allows creating an empty BLOB, optionally filling it to a required size with a
|
||||
particular value (default zero).
|
||||
|
||||
```rust
|
||||
let x = blob(); // empty BLOB
|
||||
|
||||
let x = blob(10); // BLOB with ten zeros
|
||||
|
||||
let x = blob(50, 42); // BLOB with 50x 42's
|
||||
```
|
||||
|
||||
```admonish tip "Tip: Initialize with byte stream"
|
||||
|
||||
To quickly initialize a BLOB with a particular byte stream, the `write_be` method can be used to
|
||||
write eight bytes at a time (four under [`only_i32`]) in big-endian byte order.
|
||||
|
||||
If fewer than eight bytes are needed, remember to right-pad the number as big-endian byte order is used.
|
||||
|
||||
~~~rust
|
||||
let buf = blob(12, 0); // BLOB with 12x zeros
|
||||
|
||||
// Write eight bytes at a time, in big-endian order
|
||||
buf.write_be(0, 8, 0xab_cd_ef_12_34_56_78_90);
|
||||
buf.write_be(8, 8, 0x0a_0b_0c_0d_00_00_00_00);
|
||||
// ^^^^^^^^^^^ remember to pad unused bytes
|
||||
|
||||
print(buf); // prints "[abcdef1234567890 0a0b0c0d]"
|
||||
|
||||
buf[3] == 0x12;
|
||||
buf[10] == 0x0c;
|
||||
|
||||
// Under 'only_i32', write four bytes at a time:
|
||||
buf.write_be(0, 4, 0xab_cd_ef_12);
|
||||
buf.write_be(4, 4, 0x34_56_78_90);
|
||||
buf.write_be(8, 4, 0x0a_0b_0c_0d);
|
||||
~~~
|
||||
```
|
||||
|
||||
|
||||
Writing ASCII Bytes
|
||||
-------------------
|
||||
|
||||
```admonish warning.side "Non-ASCII"
|
||||
|
||||
Non-ASCII characters (i.e. characters not within 1-127) are ignored.
|
||||
```
|
||||
|
||||
For many embedded applications, it is necessary to encode an ASCII [string] as a byte stream.
|
||||
|
||||
Use the `write_ascii` method to write ASCII [strings] into any specific [range] within a BLOB.
|
||||
|
||||
The following is an example of a building a 16-byte command to send to an embedded device.
|
||||
|
||||
```rust
|
||||
// Assume the following 16-byte command for an embedded device:
|
||||
// ┌─────────┬───────────────┬──────────────────────────────────┬───────┐
|
||||
// │ 0 │ 1 │ 2-13 │ 14-15 │
|
||||
// ├─────────┼───────────────┼──────────────────────────────────┼───────┤
|
||||
// │ command │ string length │ ASCII string, max. 12 characters │ CRC │
|
||||
// └─────────┴───────────────┴──────────────────────────────────┴───────┘
|
||||
|
||||
let buf = blob(16, 0); // initialize command buffer
|
||||
|
||||
let text = "foo & bar"; // text string to send to device
|
||||
|
||||
buf[0] = 0x42; // command code
|
||||
buf[1] = s.len(); // length of string
|
||||
|
||||
buf.write_ascii(2..14, text); // write the string
|
||||
|
||||
let crc = buf.calc_crc(); // calculate CRC
|
||||
|
||||
buf.write_le(14, 2, crc); // write CRC
|
||||
|
||||
print(buf); // prints "[4209666f6f202620 626172000000abcd]"
|
||||
// ^^ command code ^^^^ CRC
|
||||
// ^^ string length
|
||||
// ^^^^^^^^^^^^^^^^^^^ foo & bar
|
||||
|
||||
device.send(buf); // send command to device
|
||||
```
|
||||
|
||||
```admonish question.small "What if I need UTF-8?"
|
||||
|
||||
The `write_utf8` function writes a string in UTF-8 encoding.
|
||||
|
||||
UTF-8, however, is not very common for embedded applications.
|
||||
```
|
||||
|
||||
|
||||
Built-in Functions
|
||||
------------------
|
||||
|
||||
The following functions (mostly defined in the [`BasicBlobPackage`][built-in packages] but excluded
|
||||
when using a [raw `Engine`]) operate on BLOB's.
|
||||
|
||||
| Functions | Parameter(s) | Description |
|
||||
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `blob` constructor function | <ol><li>_(optional)_ initial length of the BLOB</li><li>_(optional)_ initial byte value</li></ol> | creates a new BLOB, optionally of a particular length filled with an initial byte value (default = 0) |
|
||||
| `to_array` | _none_ | converts the BLOB into an [array] of integers |
|
||||
| `as_string` | _none_ | converts the BLOB into a [string] (the byte stream is interpreted as UTF-8) |
|
||||
| `get` | position, counting from end if < 0 | gets a copy of the byte at a certain position (0 if the position is not valid) |
|
||||
| `set` | <ol><li>position, counting from end if < 0</li><li>new byte value</li></ol> | sets a certain position to a new value (no effect if the position is not valid) |
|
||||
| `push`, `append`, `+=` operator | <ol><li>BLOB</li><li>byte to append</li></ol> | appends a byte to the end |
|
||||
| `append`, `+=` operator | <ol><li>BLOB</li><li>BLOB to append</li></ol> | concatenates the second BLOB to the end of the first |
|
||||
| `append`, `+=` operator | <ol><li>BLOB</li><li>[string]/[character] to append</li></ol> | concatenates a [string] or [character] (as UTF-8 encoded byte-stream) to the end of the BLOB |
|
||||
| `+` operator | <ol><li>first BLOB</li><li>[string] to append</li></ol> | creates a new [string] by concatenating the BLOB (as UTF-8 encoded byte-stream) with the the [string] |
|
||||
| `+` operator | <ol><li>[string]</li><li>BLOB to append</li></ol> | creates a new [string] by concatenating the BLOB (as UTF-8 encoded byte-stream) to the end of the [string] |
|
||||
| `+` operator | <ol><li>first BLOB</li><li>second BLOB</li></ol> | concatenates the first BLOB with the second |
|
||||
| `==` operator | <ol><li>first BLOB</li><li>second BLOB</li></ol> | are two BLOB's the same? |
|
||||
| `!=` operator | <ol><li>first BLOB</li><li>second BLOB</li></ol> | are two BLOB's different? |
|
||||
| `insert` | <ol><li>position, counting from end if < 0, end if ≥ length</li><li>byte to insert</li></ol> | inserts a byte at a certain position |
|
||||
| `pop` | _none_ | removes the last byte and returns it (0 if empty) |
|
||||
| `shift` | _none_ | removes the first byte and returns it (0 if empty) |
|
||||
| `extract` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>_(optional)_ number of bytes to extract, none if ≤ 0</li></ol> | extracts a portion of the BLOB into a new BLOB |
|
||||
| `extract` | [range] of bytes to extract, from beginning if ≤ 0, to end if ≥ length | extracts a portion of the BLOB into a new BLOB |
|
||||
| `remove` | position, counting from end if < 0 | removes a byte at a particular position and returns it (0 if the position is not valid) |
|
||||
| `reverse` | _none_ | reverses the BLOB byte by byte |
|
||||
| `len` method and property | _none_ | returns the number of bytes in the BLOB |
|
||||
| `is_empty` method and property | _none_ | returns `true` if the BLOB is empty |
|
||||
| `pad` | <ol><li>target length</li><li>byte value to pad</li></ol> | pads the BLOB with a byte value to at least a specified length |
|
||||
| `clear` | _none_ | empties the BLOB |
|
||||
| `truncate` | target length | cuts off the BLOB at exactly a specified length (discarding all subsequent bytes) |
|
||||
| `chop` | target length | cuts off the head of the BLOB, leaving the tail at exactly a specified length |
|
||||
| `contains`, [`in`] operator | byte value to find | does the BLOB contain a particular byte value? |
|
||||
| `split` | <ol><li>BLOB</li><li>position to split at, counting from end if < 0, end if ≥ length</li></ol> | splits the BLOB into two BLOB's, starting from a specified position |
|
||||
| `drain` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to remove, none if ≤ 0</li></ol> | removes a portion of the BLOB, returning the removed bytes as a new BLOB |
|
||||
| `drain` | [range] of bytes to remove, from beginning if ≤ 0, to end if ≥ length | removes a portion of the BLOB, returning the removed bytes as a new BLOB |
|
||||
| `retain` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to retain, none if ≤ 0</li></ol> | retains a portion of the BLOB, removes all other bytes and returning them as a new BLOB |
|
||||
| `retain` | [range] of bytes to retain, from beginning if ≤ 0, to end if ≥ length | retains a portion of the BLOB, removes all other bytes and returning them as a new BLOB |
|
||||
| `splice` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to remove, none if ≤ 0</li><li>BLOB to insert</li></ol> | replaces a portion of the BLOB with another (not necessarily of the same length as the replaced portion) |
|
||||
| `splice` | <ol><li>[range] of bytes to remove, from beginning if ≤ 0, to end if ≥ length</li><li>BLOB to insert | replaces a portion of the BLOB with another (not necessarily of the same length as the replaced portion) |
|
||||
| `parse_le_int` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to parse, 8 if > 8 (4 under [`only_i32`]), none if ≤ 0</li></ol> | parses an integer at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `parse_le_int` | [range] of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`only_i32`]) | parses an integer at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `parse_be_int` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to parse, 8 if > 8 (4 under [`only_i32`]), none if ≤ 0</li></ol> | parses an integer at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `parse_be_int` | [range] of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`only_i32`]) | parses an integer at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `parse_le_float`<br/>(not available under [`no_float`]) | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to parse, 8 if > 8 (4 under [`f32_float`]), none if ≤ 0</li></ol> | parses a floating-point number at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `parse_le_float`<br/>(not available under [`no_float`]) | [range] of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`f32_float`]) | parses a floating-point number at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `parse_be_float`<br/>(not available under [`no_float`]) | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to parse, 8 if > 8 (4 under [`f32_float`]), none if ≤ 0</li></ol> | parses a floating-point number at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `parse_be_float`<br/>(not available under [`no_float`]) | [range] of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`f32_float`]) | parses a floating-point number at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `write_le` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to write, 8 if > 8 (4 under [`only_i32`] or [`f32_float`]), none if ≤ 0</li><li>integer or floating-point value</li></ol> | writes a value at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `write_le` | <ol><li>[range] of bytes to write, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`only_i32`] or [`f32_float`])</li><li>integer or floating-point value</li></ol> | writes a value at the particular offset in little-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `write_be` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to write, 8 if > 8 (4 under [`only_i32`] or [`f32_float`]), none if ≤ 0</li><li>integer or floating-point value</li></ol> | writes a value at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `write_be` | <ol><li>[range] of bytes to write, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under [`only_i32`] or [`f32_float`])</li><li>integer or floating-point value</li></ol> | writes a value at the particular offset in big-endian byte order (if not enough bytes, zeros are padded; extra bytes are ignored) |
|
||||
| `write_utf8` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to write, none if ≤ 0, to end if ≥ length</li><li>[string] to write</li></ol> | writes a [string] to the particular offset in UTF-8 encoding |
|
||||
| `write_utf8` | <ol><li>[range] of bytes to write, from beginning if ≤ 0, to end if ≥ length, to end if ≥ length</li><li>[string] to write</li></ol> | writes a [string] to the particular offset in UTF-8 encoding |
|
||||
| `write_ascii` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of [characters] to write, none if ≤ 0, to end if ≥ length</li><li>[string] to write</li></ol> | writes a [string] to the particular offset in 7-bit ASCII encoding (non-ASCII [characters] are skipped) |
|
||||
| `write_ascii` | <ol><li>[range] of bytes to write, from beginning if ≤ 0, to end if ≥ length, to end if ≥ length</li><li>[string] to write</li></ol> | writes a [string] to the particular offset in 7-bit ASCII encoding (non-ASCII [characters] are skipped) |
|
65
rhai_engine/rhaibook/language/comments.md
Normal file
65
rhai_engine/rhaibook/language/comments.md
Normal file
@@ -0,0 +1,65 @@
|
||||
Comments
|
||||
========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Comments are C-style, including `/*` ... `*/` pairs for block comments and `//` for comments to the
|
||||
end of the line.
|
||||
|
||||
Block comments can be nested.
|
||||
|
||||
```rust
|
||||
let /* intruder comment */ name = "Bob";
|
||||
|
||||
// This is a very important one-line comment
|
||||
|
||||
/* This comment spans
|
||||
multiple lines, so it
|
||||
only makes sense that
|
||||
it is even more important */
|
||||
|
||||
/* Fear not, Rhai satisfies all nesting needs with nested comments:
|
||||
/*/*/*/*/**/*/*/*/*/
|
||||
*/
|
||||
```
|
||||
|
||||
|
||||
Module Documentation
|
||||
--------------------
|
||||
|
||||
Comment lines starting with `//!` make up the _module documentation_.
|
||||
|
||||
They are used to document the containing [module] – or for a Rhai script file,
|
||||
to document the file itself.
|
||||
|
||||
~~~admonish warning.small "Requires `metadata`"
|
||||
|
||||
Module documentation is only supported under the [`metadata`] feature.
|
||||
|
||||
If [`metadata`] is not active, they are treated as normal [comments].
|
||||
~~~
|
||||
|
||||
```rust
|
||||
//! Documentation for this script file.
|
||||
//! This script is used to calculate something and display the result.
|
||||
|
||||
fn calculate(x) {
|
||||
...
|
||||
}
|
||||
|
||||
fn display(msg) {
|
||||
//! Module documentation can be placed anywhere within the file.
|
||||
...
|
||||
}
|
||||
|
||||
//! All module documentation lines will be collected into a single block.
|
||||
```
|
||||
|
||||
For the example above, the module documentation block is:
|
||||
|
||||
```rust
|
||||
//! Documentation for this script file.
|
||||
//! This script is used to calculate something and display the result.
|
||||
//! Module documentation can be placed anywhere within the file.
|
||||
//! All module documentation lines will be collected into a single block.
|
||||
```
|
145
rhai_engine/rhaibook/language/constants.md
Normal file
145
rhai_engine/rhaibook/language/constants.md
Normal file
@@ -0,0 +1,145 @@
|
||||
Constants
|
||||
=========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Constants can be defined using the `const` keyword and are immutable.
|
||||
|
||||
```rust
|
||||
const X; // 'X' is a constant '()'
|
||||
|
||||
const X = 40 + 2; // 'X' is a constant 42
|
||||
|
||||
print(X * 2); // prints 84
|
||||
|
||||
X = 123; // <- syntax error: constant modified
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Naming"
|
||||
|
||||
Constants follow the same naming rules as [variables], but as a convention are often named with
|
||||
all-capital letters.
|
||||
```
|
||||
|
||||
|
||||
Manually Add Constant into Custom Scope
|
||||
---------------------------------------
|
||||
|
||||
```admonish tip.side "Tip: Singleton"
|
||||
|
||||
A constant value holding a [custom type] essentially acts
|
||||
as a [_singleton_]({{rootUrl}}/patterns/singleton.md).
|
||||
```
|
||||
|
||||
It is possible to add a constant into a custom [`Scope`] via `Scope::push_constant` so it'll be
|
||||
available to scripts running with that [`Scope`].
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct TestStruct(i64); // custom type
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine
|
||||
.register_type_with_name::<TestStruct>("TestStruct") // register custom type
|
||||
.register_get("value", |obj: &mut TestStruct| obj.0), // property getter
|
||||
.register_fn("update_value",
|
||||
|obj: &mut TestStruct, value: i64| obj.0 = value // mutating method
|
||||
);
|
||||
|
||||
let script =
|
||||
"
|
||||
MY_NUMBER.update_value(42);
|
||||
print(MY_NUMBER.value);
|
||||
";
|
||||
|
||||
let ast = engine.compile(script)?;
|
||||
|
||||
let mut scope = Scope::new(); // create custom scope
|
||||
|
||||
scope.push_constant("MY_NUMBER", TestStruct(123_i64)); // add constant variable
|
||||
|
||||
// Beware: constant objects can still be modified via a method call!
|
||||
engine.run_ast_with_scope(&mut scope, &ast)?; // prints 42
|
||||
|
||||
// Running the script directly, as below, is less desirable because
|
||||
// the constant 'MY_NUMBER' will be propagated and copied into each usage
|
||||
// during the script optimization step
|
||||
engine.run_with_scope(&mut scope, script)?;
|
||||
```
|
||||
|
||||
|
||||
Caveat – Constants Can be Modified via Rust
|
||||
-------------------------------------------------
|
||||
|
||||
```admonish tip.side.wide "Tip: Plugin functions"
|
||||
|
||||
In [plugin functions], `&mut` parameters disallow constant values by default.
|
||||
|
||||
This is different from the `Engine::register_XXX` API.
|
||||
|
||||
However, if a [plugin function] is marked with `#[export_fn(pure)]` or `#[rhai_fn(pure)]`,
|
||||
it is assumed _pure_ (i.e. will not modify its arguments) and so constants are allowed.
|
||||
```
|
||||
|
||||
A [custom type] stored as a constant cannot be modified via script, but _can_ be modified via a
|
||||
registered Rust function that takes a first `&mut` parameter – because there is no way for
|
||||
Rhai to know whether the Rust function modifies its argument!
|
||||
|
||||
By default, native Rust functions with a first `&mut` parameter always allow constants to be passed
|
||||
to them. This is because using `&mut` can avoid unnecessary cloning of a [custom type] value, even
|
||||
though it is actually not modified – for example returning the size of a collection type.
|
||||
|
||||
In line with intuition, Rhai is smart enough to always pass a _cloned copy_ of a constant as the
|
||||
first `&mut` argument if the function is called in normal function call style.
|
||||
|
||||
If it is called as a [method], however, the Rust function will be able to modify the constant's value.
|
||||
|
||||
Also, property [setters][getters/setters] and [indexers] are always assumed to mutate the first
|
||||
`&mut` parameter and so they always raise errors when passed constants by default.
|
||||
|
||||
```rust
|
||||
// For the below, assume 'increment' is a Rust function with '&mut' first parameter
|
||||
|
||||
const X = 42; // a constant
|
||||
|
||||
increment(X); // call 'increment' in normal FUNCTION-CALL style
|
||||
// since 'X' is constant, a COPY is passed instead
|
||||
|
||||
X == 42; // value is 'X" is unchanged
|
||||
|
||||
X.increment(); // call 'increment' in METHOD-CALL style
|
||||
|
||||
X == 43; // value of 'X' is changed!
|
||||
// must use 'Dynamic::is_read_only' to check if parameter is constant
|
||||
|
||||
fn double() {
|
||||
this *= 2; // function doubles 'this'
|
||||
}
|
||||
|
||||
let y = 1; // 'y' is not constant and mutable
|
||||
|
||||
y.double(); // double it...
|
||||
|
||||
y == 2; // value of 'y' is changed as expected
|
||||
|
||||
X.double(); // since 'X' is constant, a COPY is passed to 'this'
|
||||
|
||||
X == 43; // value of 'X' is unchanged by script
|
||||
```
|
||||
|
||||
```admonish info.small "Implications on script optimization"
|
||||
|
||||
Rhai _assumes_ that constants are never changed, even via Rust functions.
|
||||
|
||||
This is important to keep in mind because the script [optimizer][script optimization]
|
||||
by default does _constant propagation_ as a operation.
|
||||
|
||||
If a constant is eventually modified by a Rust function, the optimizer will not see
|
||||
the updated value and will propagate the original initialization value instead.
|
||||
|
||||
`Dynamic::is_read_only` can be used to detect whether a [`Dynamic`] value is constant or not within
|
||||
a Rust function.
|
||||
```
|
98
rhai_engine/rhaibook/language/convert.md
Normal file
98
rhai_engine/rhaibook/language/convert.md
Normal file
@@ -0,0 +1,98 @@
|
||||
Value Conversions
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Convert Between Integer and Floating-Point
|
||||
------------------------------------------
|
||||
|
||||
| Function | Not available under | From type | To type |
|
||||
| ------------ | :-----------------: | :---------------------------------------: | :-----------------------: |
|
||||
| `to_int` | | `INT`, `FLOAT`, [`Decimal`][rust_decimal] | `INT` |
|
||||
| `to_float` | [`no_float`] | `INT`, `FLOAT`, [`Decimal`][rust_decimal] | `FLOAT` |
|
||||
| `to_decimal` | non-[`decimal`] | `INT`, `FLOAT`, [`Decimal`][rust_decimal] | [`Decimal`][rust_decimal] |
|
||||
|
||||
That's it; for other conversions, register custom conversion functions.
|
||||
|
||||
```js
|
||||
let x = 42; // 'x' is an integer
|
||||
|
||||
let y = x * 100.0; // integer and floating-point can inter-operate
|
||||
|
||||
let y = x.to_float() * 100.0; // convert integer to floating-point with 'to_float'
|
||||
|
||||
let z = y.to_int() + x; // convert floating-point to integer with 'to_int'
|
||||
|
||||
let d = y.to_decimal(); // convert floating-point to Decimal with 'to_decimal'
|
||||
|
||||
let w = z.to_decimal() + x; // Decimal and integer can inter-operate
|
||||
|
||||
let c = 'X'; // character
|
||||
|
||||
print(`c is '${c}' and its code is ${c.to_int()}`); // prints "c is 'X' and its code is 88"
|
||||
```
|
||||
|
||||
|
||||
Parse String into Number
|
||||
------------------------
|
||||
|
||||
| Function | From type | To type |
|
||||
| ---------------------------------------- | :-------: | :-----------------------: |
|
||||
| `parse_int` | [string] | `INT` |
|
||||
| `parse_int` with radix 2-36 | [string] | `INT` (specified radix) |
|
||||
| `parse_float` (not [`no_float`]) | [string] | `FLOAT` |
|
||||
| `parse_float` ([`no_float`]+[`decimal`]) | [string] | [`Decimal`][rust_decimal] |
|
||||
| `parse_decimal` (requires [`decimal`]) | [string] | [`Decimal`][rust_decimal] |
|
||||
|
||||
```rust
|
||||
let x = parse_float("123.4"); // parse as floating-point
|
||||
x == 123.4;
|
||||
type_of(x) == "f64";
|
||||
|
||||
let x = parse_decimal("123.4"); // parse as Decimal value
|
||||
type_of(x) == "decimal";
|
||||
|
||||
let x = 1234.to_decimal() / 10; // alternate method to create a Decimal value
|
||||
type_of(x) == "decimal";
|
||||
|
||||
let dec = parse_int("42"); // parse as integer
|
||||
dec == 42;
|
||||
type_of(dec) == "i64";
|
||||
|
||||
let dec = parse_int("42", 10); // radix = 10 is the default
|
||||
dec == 42;
|
||||
type_of(dec) == "i64";
|
||||
|
||||
let bin = parse_int("110", 2); // parse as binary (radix = 2)
|
||||
bin == 0b110;
|
||||
type_of(bin) == "i64";
|
||||
|
||||
let hex = parse_int("ab", 16); // parse as hex (radix = 16)
|
||||
hex == 0xab;
|
||||
type_of(hex) == "i64";
|
||||
```
|
||||
|
||||
|
||||
Format Numbers
|
||||
--------------
|
||||
|
||||
| Function | From type | To type | Format |
|
||||
| ----------- | :-------: | :------: | :----------------------------: |
|
||||
| `to_binary` | `INT` | [string] | binary (i.e. only `1` and `0`) |
|
||||
| `to_octal` | `INT` | [string] | octal (i.e. `0` ... `7`) |
|
||||
| `to_hex` | `INT` | [string] | hex (i.e. `0` ... `f`) |
|
||||
|
||||
```rust
|
||||
let x = 0x1234abcd;
|
||||
|
||||
x == 305441741;
|
||||
|
||||
x.to_string() == "305441741";
|
||||
|
||||
x.to_binary() == "10010001101001010101111001101";
|
||||
|
||||
x.to_octal() == "2215125715";
|
||||
|
||||
x.to_hex() == "1234abcd";
|
||||
```
|
70
rhai_engine/rhaibook/language/do.md
Normal file
70
rhai_engine/rhaibook/language/do.md
Normal file
@@ -0,0 +1,70 @@
|
||||
Do Loop
|
||||
=======
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
`do` loops have two opposite variants: `do` ... `while` and `do` ... `until`.
|
||||
|
||||
Like the [`while`] loop, `continue` can be used to skip to the next iteration, by-passing all
|
||||
following statements; `break` can be used to break out of the loop unconditionally.
|
||||
|
||||
~~~admonish tip.small "Tip: Disable `do` loops"
|
||||
|
||||
`do` loops can be disabled via [`Engine::set_allow_looping`][options].
|
||||
~~~
|
||||
|
||||
```rust
|
||||
let x = 10;
|
||||
|
||||
do {
|
||||
x -= 1;
|
||||
if x < 6 { continue; } // skip to the next iteration
|
||||
print(x);
|
||||
if x == 5 { break; } // break out of do loop
|
||||
} while x > 0;
|
||||
|
||||
|
||||
do {
|
||||
x -= 1;
|
||||
if x < 6 { continue; } // skip to the next iteration
|
||||
print(x);
|
||||
if x == 5 { break; } // break out of do loop
|
||||
} until x == 0;
|
||||
```
|
||||
|
||||
|
||||
Do Expression
|
||||
-------------
|
||||
|
||||
Like Rust, `do` statements can also be used as _expressions_.
|
||||
|
||||
The `break` statement takes an optional expression that provides the return value.
|
||||
|
||||
The default return value of a `do` expression is [`()`].
|
||||
|
||||
~~~admonish tip.small "Tip: Disable all loop expressions"
|
||||
|
||||
Loop expressions can be disabled via [`Engine::set_allow_loop_expressions`][options].
|
||||
~~~
|
||||
|
||||
```js
|
||||
let x = 0;
|
||||
|
||||
// 'do' can be used just like an expression
|
||||
let result = do {
|
||||
if is_magic_number(x) {
|
||||
// if the 'do' loop breaks here, return a specific value
|
||||
break get_magic_result(x);
|
||||
}
|
||||
|
||||
x += 1;
|
||||
|
||||
// ... if the 'do' loop exits here, the return value is ()
|
||||
} until x >= 100;
|
||||
|
||||
if result == () {
|
||||
print("Magic number not found!");
|
||||
} else {
|
||||
print(`Magic result = ${result}!`);
|
||||
}
|
||||
```
|
79
rhai_engine/rhaibook/language/doc-comments.md
Normal file
79
rhai_engine/rhaibook/language/doc-comments.md
Normal file
@@ -0,0 +1,79 @@
|
||||
Doc-Comments
|
||||
============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Similar to Rust, [comments] starting with `///` (three slashes) or `/**` (two asterisks)
|
||||
are _doc-comments_.
|
||||
|
||||
Doc-comments can only appear in front of [function] definitions, not any other elements.
|
||||
Therefore, doc-comments are not available under [`no_function`].
|
||||
|
||||
~~~admonish warning.small "Requires `metadata`"
|
||||
|
||||
Doc-comments are only supported under the [`metadata`] feature.
|
||||
|
||||
If [`metadata`] is not active, doc-comments are treated as normal [comments].
|
||||
~~~
|
||||
|
||||
```rust
|
||||
/// This is a valid one-line doc-comment
|
||||
fn foo() {}
|
||||
|
||||
/** This is a
|
||||
** valid block
|
||||
** doc-comment
|
||||
**/
|
||||
fn bar(x) {
|
||||
/// Syntax error: this doc-comment is invalid
|
||||
x + 1
|
||||
}
|
||||
|
||||
/** Syntax error: this doc-comment is invalid */
|
||||
let x = 42;
|
||||
|
||||
/// Syntax error: this doc-comment is also invalid
|
||||
{
|
||||
let x = 42;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
~~~admonish tip "Tip: Special cases"
|
||||
|
||||
Long streams of `//////`... and `/*****`... do _NOT_ form doc-comments.
|
||||
This is consistent with popular [comment] block styles for C-like languages.
|
||||
|
||||
```rust
|
||||
/////////////////////////////// <- this is not a doc-comment
|
||||
// This is not a doc-comment // <- this is not a doc-comment
|
||||
/////////////////////////////// <- this is not a doc-comment
|
||||
|
||||
// However, watch out for comment lines starting with '///'
|
||||
|
||||
////////////////////////////////////////// <- this is not a doc-comment
|
||||
/// This, however, IS a doc-comment!!! /// <- doc-comment!
|
||||
////////////////////////////////////////// <- this is not a doc-comment
|
||||
|
||||
/****************************************
|
||||
* *
|
||||
* This is also not a doc-comment block *
|
||||
* so we don't have to put this in *
|
||||
* front of a function. *
|
||||
* *
|
||||
****************************************/
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
Using Doc-Comments
|
||||
------------------
|
||||
|
||||
Doc-comments are stored within the script's [`AST`] after compilation.
|
||||
|
||||
The `AST::iter_functions` method provides a `ScriptFnMetadata` instance for each function defined
|
||||
within the script, which includes doc-comments.
|
||||
|
||||
Doc-comments never affect the evaluation of a script nor do they incur significant performance overhead.
|
||||
However, third party tools can take advantage of this information to auto-generate documentation for
|
||||
Rhai script functions.
|
257
rhai_engine/rhaibook/language/dynamic-rust.md
Normal file
257
rhai_engine/rhaibook/language/dynamic-rust.md
Normal file
@@ -0,0 +1,257 @@
|
||||
Interop `Dynamic` Data with Rust
|
||||
================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Create a `Dynamic` from Rust Type
|
||||
---------------------------------
|
||||
|
||||
| Rust type<br/>`T: Clone`,<br/>`K: Into<String>` | Unavailable under | Use API |
|
||||
| ------------------------------------------------------------------ | :-----------------------: | ---------------------------- |
|
||||
| `INT` (`i64` or `i32`) | | `value.into()` |
|
||||
| `FLOAT` (`f64` or `f32`) | [`no_float`] | `value.into()` |
|
||||
| [`Decimal`][rust_decimal] (requires [`decimal`]) | | `value.into()` |
|
||||
| `bool` | | `value.into()` |
|
||||
| [`()`] | | `value.into()` |
|
||||
| [`String`][string], [`&str`][string], [`ImmutableString`] | | `value.into()` |
|
||||
| `char` | | `value.into()` |
|
||||
| [`Array`][array] | [`no_index`] | `Dynamic::from_array(value)` |
|
||||
| [`Blob`][BLOB] | [`no_index`] | `Dynamic::from_blob(value)` |
|
||||
| `Vec<T>`, `&[T]`, `Iterator<T>` | [`no_index`] | `value.into()` |
|
||||
| [`Map`][object map] | [`no_object`] | `Dynamic::from_map(value)` |
|
||||
| `HashMap<K, T>`, `HashSet<K>`,<br/>`BTreeMap<K, T>`, `BTreeSet<K>` | [`no_object`] | `value.into()` |
|
||||
| [`INT..INT`][range], [`INT..=INT`][range] | | `value.into()` |
|
||||
| `Rc<RwLock<T>>` or `Arc<Mutex<T>>` | [`no_closure`] | `value.into()` |
|
||||
| [`Instant`][timestamp] | [`no_time`] or [`no_std`] | `value.into()` |
|
||||
| All types (including above) | | `Dynamic::from(value)` |
|
||||
|
||||
|
||||
Type Checking and Casting
|
||||
-------------------------
|
||||
|
||||
~~~admonish tip.side "Tip: `try_cast` and `try_cast_result`"
|
||||
|
||||
The `try_cast` method does not panic but returns `None` upon failure.
|
||||
|
||||
The `try_cast_result` method also does not panic but returns the original value upon failure.
|
||||
~~~
|
||||
|
||||
A [`Dynamic`] value's actual type can be checked via `Dynamic::is`.
|
||||
|
||||
The `cast` method then converts the value into a specific, known type.
|
||||
|
||||
Use `clone_cast` to clone a reference to [`Dynamic`].
|
||||
|
||||
```rust
|
||||
let list: Array = engine.eval("...")?; // return type is 'Array'
|
||||
let item = list[0].clone(); // 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 'None'
|
||||
|
||||
let value = list[0].clone_cast::<i64>(); // use 'clone_cast' on '&Dynamic'
|
||||
let value: i64 = list[0].clone_cast();
|
||||
```
|
||||
|
||||
|
||||
Type Name and Matching Types
|
||||
----------------------------
|
||||
|
||||
The `type_name` method gets the name of the actual type as a static string slice,
|
||||
which can be `match`-ed against.
|
||||
|
||||
This is a very simple and direct way to act on a [`Dynamic`] value based on the actual type of
|
||||
the data value.
|
||||
|
||||
```rust
|
||||
let list: Array = engine.eval("...")?; // return type is 'Array'
|
||||
let item = list[0]; // an element in an 'Array' is 'Dynamic'
|
||||
|
||||
match item.type_name() { // 'type_name' returns the name of the actual Rust type
|
||||
"()" => ...
|
||||
"i64" => ...
|
||||
"f64" => ...
|
||||
"rust_decimal::Decimal" => ...
|
||||
"core::ops::range::Range<i64>" => ...
|
||||
"core::ops::range::RangeInclusive<i64>" => ...
|
||||
"alloc::string::String" => ...
|
||||
"bool" => ...
|
||||
"char" => ...
|
||||
"rhai::FnPtr" => ...
|
||||
"std::time::Instant" => ...
|
||||
"crate::path::to::module::TestStruct" => ...
|
||||
:
|
||||
}
|
||||
```
|
||||
|
||||
```admonish warning.small "Always full path name"
|
||||
|
||||
`type_name` always returns the _full_ Rust path name of the type, even when the type
|
||||
has been registered with a friendly name via `Engine::register_type_with_name`.
|
||||
|
||||
This behavior is different from that of the [`type_of`][`type_of()`] function in Rhai.
|
||||
```
|
||||
|
||||
|
||||
Getting a Reference to Data
|
||||
---------------------------
|
||||
|
||||
Use `Dynamic::read_lock` and `Dynamic::write_lock` to get an immutable/mutable reference to the data
|
||||
inside a [`Dynamic`].
|
||||
|
||||
```rust
|
||||
struct TheGreatQuestion {
|
||||
answer: i64
|
||||
}
|
||||
|
||||
let question = TheGreatQuestion { answer: 42 };
|
||||
|
||||
let mut value: Dynamic = Dynamic::from(question);
|
||||
|
||||
let q_ref: &TheGreatQuestion =
|
||||
&*value.read_lock::<TheGreatQuestion>().unwrap();
|
||||
// ^^^^^^^^^^^^^^^^^^^^ cast to data type
|
||||
|
||||
println!("answer = {}", q_ref.answer); // prints 42
|
||||
|
||||
let q_mut: &mut TheGreatQuestion =
|
||||
&mut *value.write_lock::<TheGreatQuestion>().unwrap();
|
||||
// ^^^^^^^^^^^^^^^^^^^^ cast to data type
|
||||
|
||||
q_mut.answer = 0; // mutate value
|
||||
|
||||
let value = value.cast::<TheGreatQuestion>();
|
||||
|
||||
println!("new answer = {}", value.answer); // prints 0
|
||||
```
|
||||
|
||||
~~~admonish question "TL;DR – Why `read_lock` and `write_lock`?"
|
||||
|
||||
As the naming shows, something is _locked_ in order to allow accessing the data within a [`Dynamic`],
|
||||
and that something is a _shared value_ created by capturing variables from [closures].
|
||||
|
||||
Shared values are implemented as `Rc<RefCell<Dynamic>>` (`Arc<RwLock<Dynamic>>` under [`sync`]).
|
||||
|
||||
If the value is _not_ a shared value, or if running under [`no_closure`] where there is
|
||||
no capturing, this API de-sugars to a simple reference cast.
|
||||
|
||||
In other words, there is no locking and reference counting overhead for the vast majority of
|
||||
non-shared values.
|
||||
|
||||
If the value _is_ a shared value, then it is first _locked_ and the returned _lock guard_
|
||||
allows access to the underlying value in the specified type.
|
||||
~~~
|
||||
|
||||
|
||||
Methods and Traits
|
||||
------------------
|
||||
|
||||
The following methods are available when working with [`Dynamic`]:
|
||||
|
||||
| Method | Not available under | Return type | Description |
|
||||
| --------------- | :-------------------------: | :-----------------------: | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `type_name` | | `&str` | name of the value's type |
|
||||
| `into_shared` | [`no_closure`] | [`Dynamic`] | turn the value into a _shared_ value |
|
||||
| `flatten_clone` | | [`Dynamic`] | clone the value (a _shared_ value, if any, is cloned into a separate copy) |
|
||||
| `flatten` | | [`Dynamic`] | clone the value into a separate copy if it is _shared_ and there are multiple outstanding references, otherwise _shared_ values are turned unshared |
|
||||
| `read_lock<T>` | [`no_closure`] (pass thru') | `Option<` _guard to_ `T>` | lock the value for reading |
|
||||
| `write_lock<T>` | [`no_closure`] (pass thru') | `Option<` _guard to_ `T>` | lock the value exclusively for writing |
|
||||
| `deep_scan` | | | recursively scan for [`Dynamic`] values (e.g. items inside an [array] or [object map], or [curried arguments][currying] in a [function pointer]) |
|
||||
|
||||
### Constructor instance methods
|
||||
|
||||
| Method | Not available under | Value type | Data type |
|
||||
| ---------------- | :-----------------------: | :---------------------------------------------------------: | :-----------------------: |
|
||||
| `from_bool` | | `bool` | `bool` |
|
||||
| `from_int` | | `INT` | integer number |
|
||||
| `from_float` | [`no_float`] | `FLOAT` | floating-point number |
|
||||
| `from_decimal` | non-[`decimal`] | [`Decimal`][rust_decimal] | [`Decimal`][rust_decimal] |
|
||||
| `from_str` | | `&str` | [string] |
|
||||
| `from_char` | | `char` | [character] |
|
||||
| `from_array` | [`no_index`] | `Vec<T>` | [array] |
|
||||
| `from_blob` | [`no_index`] | `Vec<u8>` | [BLOB] |
|
||||
| `from_map` | [`no_object`] | `Map` | [object map] |
|
||||
| `from_timestamp` | [`no_time`] or [`no_std`] | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | [timestamp] |
|
||||
| `from<T>` | | `T` | [custom type] |
|
||||
|
||||
### Detection methods
|
||||
|
||||
| Method | Not available under | Return type | Description |
|
||||
| -------------- | :-----------------------: | :---------: | ---------------------------------------------------------------------- |
|
||||
| `is<T>` | | `bool` | is the value of type `T`? |
|
||||
| `is_variant` | | `bool` | is the value a trait object (i.e. not one of Rhai's [standard types])? |
|
||||
| `is_read_only` | | `bool` | is the value [constant]? A [constant] value should not be modified. |
|
||||
| `is_shared` | [`no_closure`] | `bool` | is the value _shared_ via a [closure]? |
|
||||
| `is_locked` | [`no_closure`] | `bool` | is the value _shared_ and locked (i.e. currently being read)? |
|
||||
| `is_unit` | | `bool` | is the value [`()`]? |
|
||||
| `is_int` | | `bool` | is the value an integer? |
|
||||
| `is_float` | [`no_float`] | `bool` | is the value a floating-point number? |
|
||||
| `is_decimal` | non-[`decimal`] | `bool` | is the value a [`Decimal`][rust_decimal]? |
|
||||
| `is_bool` | | `bool` | is the value a `bool`? |
|
||||
| `is_char` | | `bool` | is the value a [character]? |
|
||||
| `is_string` | | `bool` | is the value a [string]? |
|
||||
| `is_array` | [`no_index`] | `bool` | is the value an [array]? |
|
||||
| `is_blob` | [`no_index`] | `bool` | is the value a [BLOB]? |
|
||||
| `is_map` | [`no_object`] | `bool` | is the value an [object map]? |
|
||||
| `is_timestamp` | [`no_time`] or [`no_std`] | `bool` | is the value a [timestamp]? |
|
||||
|
||||
|
||||
### Casting methods
|
||||
|
||||
The following methods cast a [`Dynamic`] into a specific type:
|
||||
|
||||
| Method | Not available under | Return type (error is name of actual type if `&str`) |
|
||||
| ------------------------- | :-----------------: | :------------------------------------------------------------------------: |
|
||||
| `cast<T>` | | `T` (panics on failure) |
|
||||
| `try_cast<T>` | | `Option<T>` |
|
||||
| `try_cast_result<T>` | | `Result<T, Dynamic>` |
|
||||
| `clone_cast<T>` | | cloned copy of `T` (panics on failure) |
|
||||
| `as_unit` | | `Result<(), &str>` |
|
||||
| `as_int` | | `Result<INT, &str>` |
|
||||
| `as_float` | [`no_float`] | `Result<FLOAT, &str>` |
|
||||
| `as_decimal` | non-[`decimal`] | [`Result<Decimal, &str>`][rust_decimal] |
|
||||
| `as_bool` | | `Result<bool, &str>` |
|
||||
| `as_char` | | `Result<char, &str>` |
|
||||
| `as_immutable_string_ref` | | [`Result<impl Deref<Target=ImmutableString>, &str>`][`ImmutableString`] |
|
||||
| `as_immutable_string_mut` | | [`Result<impl DerefMut<Target=ImmutableString>, &str>`][`ImmutableString`] |
|
||||
| `as_array_ref` | [`no_index`] | [`Result<impl Deref<Target=Array>, &str>`][array] |
|
||||
| `as_array_mut` | [`no_index`] | [`Result<impl DerefMut<Target=Array>, &str>`][array] |
|
||||
| `as_blob_ref` | [`no_index`] | [`Result<impl Deref<Target=Blob>, &str>`][BLOB] |
|
||||
| `as_blob_mut` | [`no_index`] | [`Result<impl DerefMut<Target=Blob>, &str>`][BLOB] |
|
||||
| `as_map_ref` | [`no_object`] | [`Result<impl Deref<Target=Map>, &str>`][object map] |
|
||||
| `as_map_mut` | [`no_object`] | [`Result<impl DerefMut<Target=Map>, &str>`][object map] |
|
||||
| `into_string` | | `Result<String, &str>` |
|
||||
| `into_immutable_string` | | [`Result<ImmutableString, &str>`][`ImmutableString`] |
|
||||
| `into_array` | [`no_index`] | [`Result<Array, &str>`][array] |
|
||||
| `into_blob` | [`no_index`] | [`Result<Blob, &str>`][BLOB] |
|
||||
| `into_typed_array<T>` | [`no_index`] | `Result<Vec<T>, &str>` |
|
||||
|
||||
### Constructor traits
|
||||
|
||||
The following constructor traits are implemented for [`Dynamic`] where `T: Clone`:
|
||||
|
||||
| Trait | Not available under | Data type |
|
||||
| ------------------------------------------------------------------------------ | :----------------------------: | :-----------------------: |
|
||||
| `From<()>` | | `()` |
|
||||
| `From<INT>` | | integer number |
|
||||
| `From<FLOAT>` | [`no_float`] | floating-point number |
|
||||
| `From<Decimal>` | non-[`decimal`] | [`Decimal`][rust_decimal] |
|
||||
| `From<bool>` | | `bool` |
|
||||
| `From<S: Into<ImmutableString>>`<br/>e.g. `From<String>`, `From<&str>` | | [`ImmutableString`] |
|
||||
| `From<char>` | | [character] |
|
||||
| `From<Vec<T>>` | [`no_index`] | [array] |
|
||||
| `From<&[T]>` | [`no_index`] | [array] |
|
||||
| `From<BTreeMap<K: Into<SmartString>, T>>`<br/>e.g. `From<BTreeMap<String, T>>` | [`no_object`] | [object map] |
|
||||
| `From<BTreeSet<K: Into<SmartString>>>`<br/>e.g. `From<BTreeSet<String>>` | [`no_object`] | [object map] |
|
||||
| `From<HashMap<K: Into<SmartString>, T>>`<br/>e.g. `From<HashMap<String, T>>` | [`no_object`] or [`no_std`] | [object map] |
|
||||
| `From<HashSet<K: Into<SmartString>>>`<br/>e.g. `From<HashSet<String>>` | [`no_object`] or [`no_std`] | [object map] |
|
||||
| `From<FnPtr>` | | [function pointer] |
|
||||
| `From<Instant>` | [`no_time`] or [`no_std`] | [timestamp] |
|
||||
| `From<Rc<RefCell<Dynamic>>>` | [`sync`] or [`no_closure`] | [`Dynamic`] |
|
||||
| `From<Arc<RwLock<Dynamic>>>` ([`sync`]) | non-[`sync`] or [`no_closure`] | [`Dynamic`] |
|
||||
| `FromIterator<X: IntoIterator<Item=T>>` | [`no_index`] | [array] |
|
255
rhai_engine/rhaibook/language/dynamic-tag.md
Normal file
255
rhai_engine/rhaibook/language/dynamic-tag.md
Normal file
@@ -0,0 +1,255 @@
|
||||
Dynamic Value Tag
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Each [`Dynamic`] value can contain a _tag_ that is `i32` and can contain any arbitrary data.
|
||||
|
||||
On 32-bit targets, however, the tag is only `i16`.
|
||||
|
||||
The tag defaults to zero.
|
||||
|
||||
```admonish bug.small "Value out of bounds"
|
||||
|
||||
It is an error to set a tag to a value beyond the bounds of `i32` (`i16` on 32-bit targets).
|
||||
```
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
|
||||
x.tag == 0; // tag defaults to zero
|
||||
|
||||
x.tag = 123; // set tag value
|
||||
|
||||
set_tag(x, 123); // 'set_tag' function also works
|
||||
|
||||
x.tag == 123; // get updated tag value
|
||||
|
||||
x.tag() == 123; // method also works
|
||||
|
||||
tag(x) == 123; // function call style also works
|
||||
|
||||
x.tag[3..5] = 2; // tag can be used as a bit-field
|
||||
|
||||
x.tag[3..5] == 2;
|
||||
|
||||
let y = x;
|
||||
|
||||
y.tag == 123; // the tag is copied across assignment
|
||||
|
||||
y.tag = 3000000000; // runtime error: 3000000000 is too large for 'i32'
|
||||
```
|
||||
|
||||
|
||||
Practical Applications
|
||||
----------------------
|
||||
|
||||
Attaching arbitrary information together with a value has a lot of practical uses.
|
||||
|
||||
### Identify code path
|
||||
|
||||
For example, it is easy to attach an ID number to a value to indicate how or why that value is
|
||||
originally set.
|
||||
|
||||
This is tremendously convenient for debugging purposes where it is necessary to figure out which
|
||||
code path a particular value went through.
|
||||
|
||||
After the script is verified, all tag assignment statements can simply be removed.
|
||||
|
||||
```js
|
||||
const ROUTE1 = 1;
|
||||
const ROUTE2 = 2;
|
||||
const ROUTE3 = 3;
|
||||
const ERROR_ROUTE = 9;
|
||||
|
||||
fn some_complex_calculation(x) {
|
||||
let result;
|
||||
|
||||
if some_complex_condition(x) {
|
||||
result = 42;
|
||||
result.tag = ROUTE1; // record route #1
|
||||
} else if some_other_very_complex_condition(x) == 1 {
|
||||
result = 123;
|
||||
result.tag = ROUTE2; // record route #2
|
||||
} else if some_non_understandable_calculation(x) > 0 {
|
||||
result = 0;
|
||||
result.tag = ROUTE3; // record route #3
|
||||
} else {
|
||||
result = -1;
|
||||
result.tag = ERROR_ROUTE; // record error
|
||||
}
|
||||
|
||||
result // this value now contains the tag
|
||||
}
|
||||
|
||||
let my_result = some_complex_calculation(key);
|
||||
|
||||
// The code path that 'my_result' went through is now in its tag.
|
||||
|
||||
// It is now easy to trace how 'my_result' gets its final value.
|
||||
|
||||
print(`Result = ${my_result} and reason = ${my_result.tag}`);
|
||||
```
|
||||
|
||||
### Identify data source
|
||||
|
||||
It is convenient to use the tag value to record the _source_ of a piece of data.
|
||||
|
||||
```js
|
||||
let x = [0, 1, 2, 3, 42, 99, 123];
|
||||
|
||||
// Store the index number of each value into its tag before
|
||||
// filtering out all even numbers, leaving only odd numbers
|
||||
let filtered = x.map(|v, i| { v.tag = i; v }).filter(|v| v.is_odd());
|
||||
|
||||
// The tag now contains the original index position
|
||||
|
||||
for (data, i) in filtered {
|
||||
print(`${i + 1}: Value ${data} from position #${data.tag + 1}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Identify code conditions
|
||||
|
||||
The tag value may also contain a _[bit-field]_ of up to 32 (16 under 32-bit targets) individual bits,
|
||||
recording up to 32 (or 16 under 32-bit targets) logic conditions that contributed to the value.
|
||||
|
||||
Again, after the script is verified, all tag assignment statements can simply be removed.
|
||||
|
||||
```js
|
||||
|
||||
fn some_complex_calculation(x) {
|
||||
let result = x;
|
||||
|
||||
// Check first condition
|
||||
if some_complex_condition() {
|
||||
result += 1;
|
||||
result.tag[0] = true; // Set first bit in bit-field
|
||||
}
|
||||
|
||||
// Check second condition
|
||||
if some_other_very_complex_condition(x) == 1 {
|
||||
result *= 10;
|
||||
result.tag[1] = true; // Set second bit in bit-field
|
||||
}
|
||||
|
||||
// Check third condition
|
||||
if some_non_understandable_calculation(x) > 0 {
|
||||
result -= 42;
|
||||
result.tag[2] = true; // Set third bit in bit-field
|
||||
}
|
||||
|
||||
// Check result
|
||||
if result > 100 {
|
||||
result = 0;
|
||||
result.tag[3] = true; // Set forth bit in bit-field
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
let my_result = some_complex_calculation(42);
|
||||
|
||||
// The tag of 'my_result' now contains a bit-field indicating
|
||||
// the result of each condition.
|
||||
|
||||
// It is now easy to trace how 'my_result' gets its final value.
|
||||
// Use indexing on the tag to get at individual bits.
|
||||
|
||||
print(`Result = ${my_result}`);
|
||||
print(`First condition = ${my_result.tag[0]}`);
|
||||
print(`Second condition = ${my_result.tag[1]}`);
|
||||
print(`Third condition = ${my_result.tag[2]}`);
|
||||
print(`Result check = ${my_result.tag[3]}`);
|
||||
```
|
||||
|
||||
### Return auxillary info
|
||||
|
||||
Sometimes it is useful to return auxillary info from a [function].
|
||||
|
||||
```rust
|
||||
// Verify Bell's Inequality by calculating a norm
|
||||
// and comparing it with a hypotenuse.
|
||||
// https://en.wikipedia.org/wiki/Bell%27s_theorem
|
||||
//
|
||||
// Returns the smaller of the norm or hypotenuse.
|
||||
// Tag is 1 if norm <= hypo, 0 if otherwise.
|
||||
fn bells_inequality(x, y, z) {
|
||||
let norm = sqrt(x ** 2 + y ** 2);
|
||||
let result;
|
||||
|
||||
if norm <= z {
|
||||
result = norm;
|
||||
result.tag = 1;
|
||||
} else {
|
||||
result = z;
|
||||
result.tag = 0;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
let dist = bells_inequality(x, y, z);
|
||||
|
||||
print(`Value = ${dist}`);
|
||||
|
||||
if dist.tag == 1 {
|
||||
print("Local realism maintained! Einstein rules!");
|
||||
} else {
|
||||
print("Spooky action at a distance detected! Einstein will hate this...");
|
||||
}
|
||||
```
|
||||
|
||||
### Poor-man's tuples
|
||||
|
||||
Rust has _tuples_ but Rhai does not (nor does JavaScript in this sense).
|
||||
|
||||
Similar to the JavaScript situation, practical alternatives using Rhai include returning an
|
||||
[object map] or an [array].
|
||||
|
||||
Both of these alternatives, however, incur overhead that may be wasteful when the amount of
|
||||
additional information is small – e.g. in many cases, a single `bool`, or a small number.
|
||||
|
||||
To return a number of _small_ values from [functions], the tag value as a [bit-field] is an ideal
|
||||
container without resorting to a full-blown [object map] or [array].
|
||||
|
||||
```rust
|
||||
// This function essentially returns a tuple of four numbers:
|
||||
// (result, a, b, c)
|
||||
fn complex_calc(x, y, z) {
|
||||
let a = x + y;
|
||||
let b = x - y + z;
|
||||
let c = (a + b) * z / y;
|
||||
let r = do_complex_calculation(a, b, c);
|
||||
|
||||
// Store 'a', 'b' and 'c' into tag if they are small
|
||||
r.tag[0..8] = a;
|
||||
r.tag[8..16] = b;
|
||||
r.tag[16..32] = c;
|
||||
|
||||
r
|
||||
}
|
||||
|
||||
// Deconstruct the tuple
|
||||
let result = complex_calc(x, y, z);
|
||||
let a = r.tag[0..8];
|
||||
let b = r.tag[8..16];
|
||||
let c = r.tag[16..32];
|
||||
```
|
||||
|
||||
|
||||
TL;DR
|
||||
-----
|
||||
|
||||
```admonish question "Tell me, really, what is the _point_?"
|
||||
|
||||
Due to byte alignment requirements on modern CPU's, there are unused spaces in a [`Dynamic`] type,
|
||||
of the order of 4 bytes on 64-bit targets (2 bytes on 32-bit).
|
||||
|
||||
It is empty space that can be put to good use and not wasted, especially when Rhai does not have
|
||||
built-in support of tuples in order to return multiple values from [functions].
|
||||
```
|
77
rhai_engine/rhaibook/language/dynamic.md
Normal file
77
rhai_engine/rhaibook/language/dynamic.md
Normal file
@@ -0,0 +1,77 @@
|
||||
Dynamic Values
|
||||
==============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
A `Dynamic` value can be _any_ type, as long as it implements `Clone`.
|
||||
|
||||
~~~admonish warning.small "`Send + Sync`"
|
||||
|
||||
Under the [`sync`] feature, all types must also be `Send + Sync`.
|
||||
~~~
|
||||
|
||||
```rust
|
||||
let x = 42; // value is an integer
|
||||
|
||||
x = 123.456; // value is now a floating-point number
|
||||
|
||||
x = "hello"; // value is now a string
|
||||
|
||||
x = x.len > 0; // value is now a boolean
|
||||
|
||||
x = [x]; // value is now an array
|
||||
|
||||
x = #{x: x}; // value is now an object map
|
||||
```
|
||||
|
||||
|
||||
Use `type_of()` to Get Value Type
|
||||
---------------------------------
|
||||
|
||||
Because [`type_of()`] a `Dynamic` value returns the type of the actual value,
|
||||
it is usually used to perform type-specific actions based on the actual value's type.
|
||||
|
||||
```js
|
||||
let mystery = get_some_dynamic_value();
|
||||
|
||||
switch type_of(mystery) {
|
||||
"()" => print("Hey, I got the unit () here!"),
|
||||
"i64" => print("Hey, I got an integer here!"),
|
||||
"f64" => print("Hey, I got a float here!"),
|
||||
"decimal" => print("Hey, I got a decimal here!"),
|
||||
"range" => print("Hey, I got an exclusive range here!"),
|
||||
"range=" => print("Hey, I got an inclusive range here!"),
|
||||
"string" => print("Hey, I got a string here!"),
|
||||
"bool" => print("Hey, I got a boolean here!"),
|
||||
"array" => print("Hey, I got an array here!"),
|
||||
"blob" => print("Hey, I got a BLOB here!"),
|
||||
"map" => print("Hey, I got an object map here!"),
|
||||
"Fn" => print("Hey, I got a function pointer here!"),
|
||||
"timestamp" => print("Hey, I got a time-stamp here!"),
|
||||
"TestStruct" => print("Hey, I got the TestStruct custom type here!"),
|
||||
_ => print(`I don't know what this is: ${type_of(mystery)}`)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Parse from JSON
|
||||
---------------
|
||||
|
||||
~~~admonish warning.side "Requires `metadata`"
|
||||
|
||||
`parse_json` is defined in the [`LanguageCorePackage`][built-in packages], which is excluded when using a [raw `Engine`].
|
||||
|
||||
It also requires the [`metadata`] feature; the [`no_index`] and [`no_object`] features must _not_ be set.
|
||||
~~~
|
||||
|
||||
Use `parse_json` to parse a JSON string into a [`Dynamic`] value.
|
||||
|
||||
| JSON type | Rhai type |
|
||||
| :---------------------------: | :----------: |
|
||||
| `number` (no decimal point) | `INT` |
|
||||
| `number` (with decimal point) | `FLOAT` |
|
||||
| `string` | [string] |
|
||||
| `boolean` | `bool` |
|
||||
| `Array` | [array] |
|
||||
| `Object` | [object map] |
|
||||
| `null` | [`()`] |
|
97
rhai_engine/rhaibook/language/eval.md
Normal file
97
rhai_engine/rhaibook/language/eval.md
Normal file
@@ -0,0 +1,97 @@
|
||||
`eval` Function
|
||||
===============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Or "How to Shoot Yourself in the Foot even Easier"
|
||||
--------------------------------------------------
|
||||
|
||||
Saving the best for last, there is the ever-dreaded... `eval` [function]!
|
||||
|
||||
```rust
|
||||
let x = 10;
|
||||
|
||||
fn foo(x) { x += 12; x }
|
||||
|
||||
let script =
|
||||
"
|
||||
let y = x;
|
||||
y += foo(y);
|
||||
x + y
|
||||
";
|
||||
|
||||
let result = eval(script); // <- look, JavaScript, we can also do this!
|
||||
|
||||
result == 42;
|
||||
|
||||
x == 10; // prints 10 - arguments are passed by value
|
||||
y == 32; // prints 32 - variables defined in 'eval' persist!
|
||||
|
||||
eval("{ let z = y }"); // to keep a variable local, use a statements block
|
||||
|
||||
print(z); // <- error: variable 'z' not found
|
||||
|
||||
"print(42)".eval(); // <- nope... method-call style doesn't work with 'eval'
|
||||
```
|
||||
|
||||
~~~admonish danger.small "`eval` executes inside the current scope!"
|
||||
|
||||
Script segments passed to `eval` execute inside the _current_ [`Scope`], so they can access and modify
|
||||
_everything_, including all [variables] that are visible at that position in code!
|
||||
|
||||
```rust
|
||||
let script = "x += 32";
|
||||
|
||||
let x = 10;
|
||||
eval(script); // variable 'x' is visible!
|
||||
print(x); // prints 42
|
||||
|
||||
// The above is equivalent to:
|
||||
let script = "x += 32";
|
||||
let x = 10;
|
||||
x += 32;
|
||||
print(x);
|
||||
```
|
||||
|
||||
`eval` can also be used to define new [variables] and do other things normally forbidden inside
|
||||
a [function] call.
|
||||
|
||||
```rust
|
||||
let script = "let x = 42";
|
||||
eval(script);
|
||||
print(x); // prints 42
|
||||
```
|
||||
|
||||
Treat it as if the script segments are physically pasted in at the position of the `eval` call.
|
||||
~~~
|
||||
|
||||
~~~admonish warning.small "Cannot define new functions"
|
||||
|
||||
New [functions] cannot be defined within an `eval` call, since [functions] can only be defined at
|
||||
the _global_ level!
|
||||
~~~
|
||||
|
||||
~~~admonish failure.small "`eval` is evil"
|
||||
|
||||
For those who subscribe to the (very sensible) motto of ["`eval` is evil"](http://linterrors.com/js/eval-is-evil),
|
||||
disable `eval` via [`Engine::disable_symbol`][disable keywords and operators].
|
||||
|
||||
```rust
|
||||
// Disable usage of 'eval'
|
||||
engine.disable_symbol("eval");
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
~~~admonish question.small "Do you regret implementing `eval` in Rhai?"
|
||||
|
||||
Or course we do.
|
||||
|
||||
Having the possibility of an `eval` call disrupts any predictability in the Rhai script,
|
||||
thus disabling a large number of optimizations.
|
||||
~~~
|
||||
|
||||
```admonish question.small "Why did it then???!!!"
|
||||
|
||||
Brendan Eich puts it well: "it is just too easy to implement." _(source wanted)_
|
||||
```
|
109
rhai_engine/rhaibook/language/fn-anon.md
Normal file
109
rhai_engine/rhaibook/language/fn-anon.md
Normal file
@@ -0,0 +1,109 @@
|
||||
Anonymous Functions
|
||||
===================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Many functions in the standard API expect [function pointer] as parameters.
|
||||
|
||||
For example:
|
||||
|
||||
```rust
|
||||
// Function 'double' defined here - used only once
|
||||
fn double(x) { 2 * x }
|
||||
|
||||
// Function 'square' defined here - again used only once
|
||||
fn square(x) { x * x }
|
||||
|
||||
let x = [1, 2, 3, 4, 5];
|
||||
|
||||
// Pass a function pointer to 'double'
|
||||
let y = x.map(double);
|
||||
|
||||
// Pass a function pointer to 'square' using Fn(...) notation
|
||||
let z = y.map(Fn("square"));
|
||||
```
|
||||
|
||||
Sometimes it gets tedious to define separate [functions] only to dispatch them via single [function pointers] –
|
||||
essentially, those [functions] are only ever called in one place.
|
||||
|
||||
This scenario is especially common when simulating object-oriented programming ([OOP]).
|
||||
|
||||
```rust
|
||||
// Define functions one-by-one
|
||||
fn obj_inc(x, y) { this.data += x * y; }
|
||||
fn obj_dec(x) { this.data -= x; }
|
||||
fn obj_print() { print(this.data); }
|
||||
|
||||
// Define object
|
||||
let obj = #{
|
||||
data: 42,
|
||||
increment: obj_inc, // use function pointers to
|
||||
decrement: obj_dec, // refer to method functions
|
||||
print: obj_print
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
Syntax
|
||||
------
|
||||
|
||||
Anonymous [functions] have a syntax similar to Rust's _closures_ (they are _not_ the same).
|
||||
|
||||
> `|`_param 1_`,` _param 2_`,` ... `,` _param n_`|` _statement_
|
||||
>
|
||||
> `|`_param 1_`,` _param 2_`,` ... `,` _param n_`| {` _statements_... `}`
|
||||
|
||||
No parameters:
|
||||
|
||||
> `||` _statement_
|
||||
>
|
||||
> `|| {` _statements_... `}`
|
||||
|
||||
Anonymous functions can be disabled via [`Engine::set_allow_anonymous_function`][options].
|
||||
|
||||
|
||||
Rewrite Using Anonymous Functions
|
||||
---------------------------------
|
||||
|
||||
The above can be rewritten using _anonymous [functions]_.
|
||||
|
||||
```rust
|
||||
let x = [1, 2, 3, 4, 5];
|
||||
|
||||
let y = x.map(|x| 2 * x);
|
||||
|
||||
let z = y.map(|x| x * x);
|
||||
|
||||
let obj = #{
|
||||
data: 42,
|
||||
increment: |x, y| this.data += x * y, // one statement
|
||||
decrement: |x| this.data -= x, // one statement
|
||||
print_obj: || {
|
||||
print(this.data); // full function body
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
This de-sugars to:
|
||||
|
||||
```rust
|
||||
// Automatically generated...
|
||||
fn anon_fn_0001(x) { 2 * x }
|
||||
fn anon_fn_0002(x) { x * x }
|
||||
fn anon_fn_0003(x, y) { this.data += x * y; }
|
||||
fn anon_fn_0004(x) { this.data -= x; }
|
||||
fn anon_fn_0005() { print(this.data); }
|
||||
|
||||
let x = [1, 2, 3, 4, 5];
|
||||
|
||||
let y = x.map(anon_fn_0001);
|
||||
|
||||
let z = y.map(anon_fn_0002);
|
||||
|
||||
let obj = #{
|
||||
data: 42,
|
||||
increment: anon_fn_0003,
|
||||
decrement: anon_fn_0004,
|
||||
print: anon_fn_0005
|
||||
};
|
||||
```
|
194
rhai_engine/rhaibook/language/fn-closure.md
Normal file
194
rhai_engine/rhaibook/language/fn-closure.md
Normal file
@@ -0,0 +1,194 @@
|
||||
Closures
|
||||
========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
~~~admonish tip.side "Tip: `is_shared`"
|
||||
|
||||
Use `Dynamic::is_shared` to check whether a particular [`Dynamic`] value is shared.
|
||||
~~~
|
||||
|
||||
Although [anonymous functions] de-sugar to standard function definitions, they differ from standard
|
||||
functions because they can _captures_ [variables] that are not defined within the current scope,
|
||||
but are instead defined in an external scope – i.e. where the [anonymous function] is created.
|
||||
|
||||
All [variables] that are accessible during the time the [anonymous function] is created are
|
||||
automatically captured when they are used, as long as they are not shadowed by local [variables]
|
||||
defined within the function's.
|
||||
|
||||
The captured [variables] are automatically converted into **reference-counted shared values**
|
||||
(`Rc<RefCell<Dynamic>>`, or `Arc<RwLock<Dynamic>>` under [`sync`]).
|
||||
|
||||
Therefore, similar to closures in many languages, these captured shared values persist through
|
||||
reference counting, and may be read or modified even after the [variables] that hold them go out of
|
||||
scope and no longer exist.
|
||||
|
||||
```admonish tip.small "Tip: Disable closures"
|
||||
|
||||
Capturing external [variables] can be turned off via the [`no_closure`] feature.
|
||||
```
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let x = 1; // a normal variable
|
||||
|
||||
x.is_shared() == false;
|
||||
|
||||
let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f'
|
||||
|
||||
x.is_shared() == true; // 'x' is now a shared value!
|
||||
|
||||
f.call(2) == 3; // 1 + 2 == 3
|
||||
|
||||
x = 40; // changing 'x'...
|
||||
|
||||
f.call(2) == 42; // the value of 'x' is 40 because 'x' is shared
|
||||
|
||||
// The above de-sugars into something like this:
|
||||
|
||||
fn anon_0001(x, y) { x + y } // parameter 'x' is inserted
|
||||
|
||||
make_shared(x); // convert variable 'x' into a shared value
|
||||
|
||||
let f = anon_0001.curry(x); // shared 'x' is curried
|
||||
```
|
||||
|
||||
|
||||
~~~admonish bug "Beware: Captured Variables are Truly Shared"
|
||||
|
||||
The example below is a typical tutorial sample for many languages to illustrate the traps
|
||||
that may accompany capturing external [variables] in closures.
|
||||
|
||||
It prints `9`, `9`, `9`, ... `9`, `9`, not `0`, `1`, `2`, ... `8`, `9`, because there is
|
||||
ever only _one_ captured [variable], and all ten closures capture the _same_ [variable].
|
||||
|
||||
```rust
|
||||
let list = [];
|
||||
|
||||
for i in 0..10 {
|
||||
list.push(|| print(i)); // the for loop variable 'i' is captured
|
||||
}
|
||||
|
||||
list.len() == 10; // 10 closures stored in the array
|
||||
|
||||
list[0].type_of() == "Fn"; // make sure these are closures
|
||||
|
||||
for f in list {
|
||||
f.call(); // all references to 'i' are the same variable!
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish danger "Be Careful to Prevent Data Races"
|
||||
|
||||
Rust does not have data races, but that doesn't mean Rhai doesn't.
|
||||
|
||||
Avoid performing a method call on a captured shared [variable] (which essentially takes a
|
||||
mutable reference to the shared object) while using that same [variable] as a parameter
|
||||
in the method call – this is a sure-fire way to generate a data race error.
|
||||
|
||||
If a shared value is used as the `this` pointer in a method call to a closure function,
|
||||
then the same shared value _must not_ be captured inside that function, or a data race
|
||||
will occur and the script will terminate with an error.
|
||||
|
||||
```rust
|
||||
let x = 20;
|
||||
|
||||
x.is_shared() == false; // 'x' is not shared, so no data race is possible
|
||||
|
||||
let f = |a| this += x + a; // 'x' is captured in this closure
|
||||
|
||||
x.is_shared() == true; // now 'x' is shared
|
||||
|
||||
x.call(f, 2); // <- error: data race detected on 'x'
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish danger "Data Races in `sync` Builds Can Become Deadlocks"
|
||||
|
||||
Under the [`sync`] feature, shared values are guarded with a `RwLock`, meaning that data race
|
||||
conditions no longer raise an error.
|
||||
|
||||
Instead, they wait endlessly for the `RwLock` to be freed, and thus can become deadlocks.
|
||||
|
||||
On the other hand, since the same thread (i.e. the [`Engine`] thread) that is holding the lock
|
||||
is attempting to read it again, this may also [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1)
|
||||
depending on the O/S.
|
||||
|
||||
```rust
|
||||
let x = 20;
|
||||
|
||||
let f = |a| this += x + a; // 'x' is captured in this closure
|
||||
|
||||
// Under `sync`, the following may wait forever, or may panic,
|
||||
// because 'x' is locked as the `this` pointer but also accessed
|
||||
// via a captured shared value.
|
||||
x.call(f, 2);
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
TL;DR
|
||||
-----
|
||||
|
||||
```admonish question "How is it actually implemented?"
|
||||
|
||||
The actual implementation of closures de-sugars to:
|
||||
|
||||
1. Keeping track of what [variables] are accessed inside the [anonymous function],
|
||||
|
||||
2. If a [variable] is not defined within the [anonymous function's][anonymous function] scope,
|
||||
it is looked up _outside_ the [function] and in the current execution scope –
|
||||
where the [anonymous function] is created.
|
||||
|
||||
3. The [variable] is added to the parameters list of the [anonymous function], at the front.
|
||||
|
||||
4. The [variable] is then converted into a **reference-counted shared value**.
|
||||
|
||||
An [anonymous function] which captures an external [variable] is the only way to create a
|
||||
reference-counted shared value in Rhai.
|
||||
|
||||
5. The shared value is then [curried][currying] into the [function pointer] itself,
|
||||
essentially carrying a reference to that shared value and inserting it into future calls of the [function].
|
||||
|
||||
This process is called _Automatic Currying_, and is the mechanism through which Rhai simulates closures.
|
||||
```
|
||||
|
||||
```admonish question "Why automatic currying?"
|
||||
|
||||
In concept, a closure _closes_ over captured variables from the outer scope – that's why
|
||||
they are called _closures_. When this happen, a typical language implementation hoists
|
||||
those variables that are captured away from the stack frame and into heap-allocated storage.
|
||||
This is because those variables may be needed after the stack frame goes away.
|
||||
|
||||
These heap-allocated captured variables only go away when all the closures that need them
|
||||
are finished with them. A garbage collector makes this trivial to implement – they are
|
||||
automatically collected as soon as all closures needing them are destroyed.
|
||||
|
||||
In Rust, this can be done by reference counting instead, with the potential pitfall of creating
|
||||
reference loops that will prevent those variables from being deallocated forever.
|
||||
Rhai avoids this by clone-copying most data values, so reference loops are hard to create.
|
||||
|
||||
Rhai does the hoisting of captured variables into the heap by converting those values
|
||||
into reference-counted locked values, also allocated on the heap. The process is identical.
|
||||
|
||||
Closures are usually implemented as a data structure containing two items:
|
||||
|
||||
1. A function pointer to the function body of the closure,
|
||||
2. A data structure containing references to the captured shared variables on the heap.
|
||||
|
||||
Usually a language implementation passes the structure containing references to captured
|
||||
shared variables into the function pointer, the function body taking this data structure
|
||||
as an additional parameter.
|
||||
|
||||
This is essentially what Rhai does, except that Rhai passes each variable individually
|
||||
as separate parameters to the function, instead of creating a structure and passing that
|
||||
structure as a single parameter. This is the only difference.
|
||||
|
||||
Therefore, in most languages, essentially all closures are implemented as automatic currying of
|
||||
shared variables hoisted into the heap, automatically passing those variables as parameters into
|
||||
the function. Rhai just brings this directly up to the front.
|
||||
```
|
31
rhai_engine/rhaibook/language/fn-curry.md
Normal file
31
rhai_engine/rhaibook/language/fn-curry.md
Normal file
@@ -0,0 +1,31 @@
|
||||
Function Pointer Currying
|
||||
=========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
It is possible to _curry_ a [function pointer] by providing partial (or all) arguments.
|
||||
|
||||
Currying is done via the `curry` keyword and produces a new [function pointer] which carries
|
||||
the curried arguments.
|
||||
|
||||
When the curried [function pointer] is called, the curried arguments are inserted starting from the _left_.
|
||||
|
||||
The actual call arguments should be reduced by the number of curried arguments.
|
||||
|
||||
```rust
|
||||
fn mul(x, y) { // function with two parameters
|
||||
x * y
|
||||
}
|
||||
|
||||
let func = mul; // <- de-sugars to 'Fn("mul")'
|
||||
|
||||
func.call(21, 2) == 42; // two arguments are required for 'mul'
|
||||
|
||||
let curried = func.curry(21); // currying produces a new function pointer which
|
||||
// carries 21 as the first argument
|
||||
|
||||
let curried = curry(func, 21); // function-call style also works
|
||||
|
||||
curried.call(2) == 42; // <- de-sugars to 'func.call(21, 2)'
|
||||
// only one argument is now required
|
||||
```
|
39
rhai_engine/rhaibook/language/fn-metadata.md
Normal file
39
rhai_engine/rhaibook/language/fn-metadata.md
Normal file
@@ -0,0 +1,39 @@
|
||||
Get Functions Metadata in Scripts
|
||||
=================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The built-in function `get_fn_metadata_list` returns an [array] of [object maps], each containing
|
||||
the metadata of one script-defined [function] in scope.
|
||||
|
||||
`get_fn_metadata_list` is defined in the [`LanguageCorePackage`][built-in packages], which is
|
||||
excluded when using a [raw `Engine`].
|
||||
|
||||
`get_fn_metadata_list` has a few versions taking different parameters:
|
||||
|
||||
| Signature | Description |
|
||||
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `get_fn_metadata_list()` | returns an [array] for _all_ script-defined [functions] |
|
||||
| `get_fn_metadata_list(name)` | returns an [array] containing all script-defined [functions] matching a specified name |
|
||||
| `get_fn_metadata_list(name, params)` | returns an [array] containing all script-defined [functions] matching a specified name and accepting the specified number of parameters |
|
||||
|
||||
[Functions] from the following sources are returned, in order:
|
||||
|
||||
1. Encapsulated script environment (e.g. when loading a [module] from a script file),
|
||||
2. Current script,
|
||||
3. [Modules] registered via `Engine::register_global_module` (latest registrations first)
|
||||
4. [Modules] imported via the [`import`] statement (latest imports first),
|
||||
5. [Modules] added via `Engine::register_static_module` (latest registrations first)
|
||||
|
||||
The return value is an [array] of [object maps] (so `get_fn_metadata_list` is also not available under
|
||||
[`no_index`] or [`no_object`]), containing the following fields.
|
||||
|
||||
| Field | Type | Optional? | Description |
|
||||
| -------------- | :------------------------: | :-------: | ----------------------------------------------------------------------------------- |
|
||||
| `namespace` | [string] | **yes** | the module _namespace_ if the [function] is defined within a [module] |
|
||||
| `access` | [string] | no | `"public"` if the function is public,<br/>`"private"` if it is [private][`private`] |
|
||||
| `name` | [string] | no | [function] name |
|
||||
| `params` | [array] of [strings] | no | parameter names |
|
||||
| `this_type` | [string](strings-chars.md) | **yes** | restrict the type of `this` if the [function] is a [method] |
|
||||
| `is_anonymous` | `bool` | no | is this [function] an [anonymous function]? |
|
||||
| `comments` | [array] of [strings] | **yes** | [doc-comments], if any, one per line |
|
197
rhai_engine/rhaibook/language/fn-method.md
Normal file
197
rhai_engine/rhaibook/language/fn-method.md
Normal file
@@ -0,0 +1,197 @@
|
||||
`this` – Simulating an Object Method
|
||||
==========================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish warning.side "Functions are pure"
|
||||
|
||||
The only way for a script-defined [function] to change an external value is via `this`.
|
||||
```
|
||||
|
||||
Arguments passed to script-defined [functions] are always by _value_ because [functions] are _pure_.
|
||||
|
||||
However, [functions] can also be called in _method-call_ style (not available under [`no_object`]):
|
||||
|
||||
> _object_ `.` _method_ `(` _parameters_ ... `)`
|
||||
|
||||
When a [function] is called this way, the keyword `this` binds to the object in the method call and
|
||||
can be changed.
|
||||
|
||||
```rust
|
||||
fn change() { // note that the method does not need a parameter
|
||||
this = 42; // 'this' binds to the object in method-call
|
||||
}
|
||||
|
||||
let x = 500;
|
||||
|
||||
x.change(); // call 'change' in method-call style, 'this' binds to 'x'
|
||||
|
||||
x == 42; // 'x' is changed!
|
||||
|
||||
change(); // <- error: 'this' is unbound
|
||||
```
|
||||
|
||||
|
||||
Elvis Operator
|
||||
--------------
|
||||
|
||||
The [_Elvis_ operator][elvis] can be used to short-circuit the method call when the object itself is [`()`].
|
||||
|
||||
> _object_ `?.` _method_ `(` _parameters_ ... `)`
|
||||
|
||||
In the above, the _method_ is never called if _object_ is [`()`].
|
||||
|
||||
|
||||
Restrict the Type of `this` in Function Definitions
|
||||
---------------------------------------------------
|
||||
|
||||
```admonish tip.side.wide "Tip: Automatically global"
|
||||
|
||||
Methods defined this way are automatically exposed to the [global namespace][function namespaces].
|
||||
```
|
||||
|
||||
In many cases it may be desirable to implement _methods_ for different [custom types] using
|
||||
script-defined [functions].
|
||||
|
||||
### The Problem
|
||||
|
||||
Doing so is brittle and requires a lot of type checking code because there can only be one
|
||||
[function] definition for the same name and arity:
|
||||
|
||||
```js
|
||||
// Really painful way to define a method called 'do_update' on various data types
|
||||
fn do_update(x) {
|
||||
switch type_of(this) {
|
||||
"i64" => this *= x,
|
||||
"string" => this.len += x,
|
||||
"bool" if this => this *= x,
|
||||
"bool" => this *= 42,
|
||||
"MyType" => this.update(x),
|
||||
"Strange-Type#Name::with_!@#symbols" => this.update(x),
|
||||
_ => throw `I don't know how to handle ${type_of(this)}`!`
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### The Solution
|
||||
|
||||
With a special syntax, it is possible to restrict a [function] to be callable only when the object
|
||||
pointed to by `this` is of a certain type:
|
||||
|
||||
> `fn` _type name_ `.` _method_ `(` _parameters_ ... `) {` ... `}`
|
||||
|
||||
or in quotes if the type name is not a valid identifier itself:
|
||||
|
||||
> `fn` `"`_type name string_`"` `.` _method_ `(` _parameters_ ... `) {` ... `}`
|
||||
|
||||
Needless to say, this _typed method_ definition style is not available under [`no_object`].
|
||||
|
||||
~~~admonish warning.small "Type name must be the same as `type_of`"
|
||||
|
||||
The _type name_ specified in front of the [function] name must match the output of [`type_of`]
|
||||
for the required type.
|
||||
~~~
|
||||
|
||||
~~~admonish tip.small "Tip: `int` and `float`"
|
||||
`int` can be used in place of the system integer type (usually `i64` or `i32`).
|
||||
|
||||
`float` can be used in place of the system floating-point type (usually `f64` or `f32`).
|
||||
|
||||
Using these make scripts more portable.
|
||||
~~~
|
||||
|
||||
### Examples
|
||||
|
||||
```js
|
||||
/// This 'do_update' can only be called on objects of type 'MyType' in method style
|
||||
fn MyType.do_update(x, y) {
|
||||
this.update(x * y);
|
||||
}
|
||||
|
||||
/// This 'do_update' can only be called on objects of type 'Strange-Type#Name::with_!@#symbols'
|
||||
/// (which can be specified via 'Engine::register_type_with_name') in method style
|
||||
fn "Strange-Type#Name::with_!@#symbols".do_update(x, y) {
|
||||
this.update(x * y);
|
||||
}
|
||||
|
||||
/// Define a blanket version
|
||||
fn do_update(x, y) {
|
||||
this = `${this}, ${x}, ${y}`;
|
||||
}
|
||||
|
||||
/// This 'do_update' can only be called on integers in method style
|
||||
fn int.do_update(x, y) {
|
||||
this += x * y
|
||||
}
|
||||
|
||||
let obj = create_my_type(); // 'x' is 'MyType'
|
||||
|
||||
obj.type_of() == "MyType";
|
||||
|
||||
obj.do_update(42, 123); // ok!
|
||||
|
||||
let x = 42; // 'x' is an integer
|
||||
|
||||
x.type_of() == "i64";
|
||||
|
||||
x.do_update(42, 123); // ok!
|
||||
|
||||
let x = true; // 'x' is a boolean
|
||||
|
||||
x.type_of() == "bool";
|
||||
|
||||
x.do_update(42, 123); // <- this works because there is a blanket version
|
||||
|
||||
// Use 'is_def_fn' with three parameters to test for typed methods
|
||||
|
||||
is_def_fn("MyType", "do_update", 2) == true;
|
||||
|
||||
is_def_fn("int", "do_update", 2) == true;
|
||||
```
|
||||
|
||||
|
||||
Bind to `this` for Module Functions
|
||||
-----------------------------------
|
||||
|
||||
### The Problem
|
||||
|
||||
The _method-call_ syntax is not possible for [functions] [imported][`import`] from [modules].
|
||||
|
||||
```js
|
||||
import "my_module" as foo;
|
||||
|
||||
let x = 42;
|
||||
|
||||
x.foo::change_value(1); // <- syntax error
|
||||
```
|
||||
|
||||
### The Solution
|
||||
|
||||
In order to call a [module] [function] as a method, it must be defined with a restriction on the
|
||||
type of object pointed to by `this`:
|
||||
|
||||
```js
|
||||
┌────────────────┐
|
||||
│ my_module.rhai │
|
||||
└────────────────┘
|
||||
|
||||
// This is a typed method function requiring 'this' to be an integer.
|
||||
// Typed methods are automatically marked global when importing this module.
|
||||
fn int.change_value(offset) {
|
||||
// 'this' is guaranteed to be an integer
|
||||
this += offset;
|
||||
}
|
||||
|
||||
|
||||
┌───────────┐
|
||||
│ main.rhai │
|
||||
└───────────┘
|
||||
|
||||
import "my_module";
|
||||
|
||||
let x = 42;
|
||||
|
||||
x.change_value(1); // ok!
|
||||
|
||||
x == 43;
|
||||
```
|
124
rhai_engine/rhaibook/language/fn-namespaces.md
Normal file
124
rhai_engine/rhaibook/language/fn-namespaces.md
Normal file
@@ -0,0 +1,124 @@
|
||||
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-matched with other completely unrelated scripts.
|
||||
|
||||
For example, the `AST::merge` and `AST::combine` methods (or the equivalent `+` and `+=` operators)
|
||||
allow combining all [functions] in one [`AST`] into another, forming a new, unified, group of [functions].
|
||||
|
||||
|
||||
Namespace Types
|
||||
---------------
|
||||
|
||||
In general, there are two main types of _namespaces_ where [functions] are looked up:
|
||||
|
||||
| Namespace | Quantity | Source | Lookup | Sub-modules? | Variables? |
|
||||
| --------- | :------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | :----------: | :--------: |
|
||||
| Global | one | <ol><li>[`AST`] being evaluated</li><li>`Engine::register_XXX` API</li><li>global registered [modules]</li><li>[functions] in [imported][`import`] [modules] marked _global_</li><li>[functions] in registered static [modules] marked _global_</li></ol> | simple name | ignored | ignored |
|
||||
| Module | many | <ol><li>[Module] registered via `Engine::register_static_module`</li><li>[Module] loaded via [`import`] statement</li></ol> | namespace-qualified name | yes | yes |
|
||||
|
||||
### Module Namespaces
|
||||
|
||||
There can be multiple [module] namespaces at any time during a script evaluation, usually loaded via
|
||||
the [`import`] statement.
|
||||
|
||||
_Static_ [module] namespaces can also be registered into an [`Engine`] via `Engine::register_static_module`.
|
||||
|
||||
[Functions] and [variables] in module namespaces are isolated and encapsulated within their own environments.
|
||||
|
||||
They must be called or accessed in a _namespace-qualified_ manner.
|
||||
|
||||
```js
|
||||
import "my_module" as m; // new module namespace 'm' created via 'import'
|
||||
|
||||
let x = m::calc_result(); // namespace-qualified function call
|
||||
|
||||
let y = m::MY_NUMBER; // namespace-qualified variable/constant access
|
||||
|
||||
let z = calc_result(); // <- error: function 'calc_result' not found
|
||||
// in global namespace!
|
||||
```
|
||||
|
||||
### Global Namespace
|
||||
|
||||
There is one _global_ namespace for every [`Engine`], which includes (in the following search order):
|
||||
|
||||
* all [functions] defined in the [`AST`] currently being evaluated,
|
||||
|
||||
* all native Rust functions and iterators registered via the `Engine::register_XXX` API,
|
||||
|
||||
* all functions and iterators defined in global [modules] that are registered into the [`Engine`]
|
||||
via `register_global_module`,
|
||||
|
||||
* functions defined in [modules] registered into the [`Engine`] via `register_static_module` that
|
||||
are specifically marked for exposure to the global namespace (e.g. via the `#[rhai(global)]`
|
||||
attribute in a [plugin module]).
|
||||
|
||||
* [functions] defined in [imported][`import`] [modules] that are specifically marked for exposure to
|
||||
the global namespace (e.g. via the `#[rhai(global)]` attribute in a [plugin module]).
|
||||
|
||||
Anywhere in a Rhai script, when a function call is made, the function is searched within the
|
||||
global namespace, in the above search order.
|
||||
|
||||
Therefore, function calls in Rhai are _late_ bound – meaning that the function called cannot be
|
||||
determined or guaranteed; 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 get_message() {
|
||||
"Hello!" // greeting message
|
||||
}
|
||||
|
||||
fn say_hello() {
|
||||
print(get_message()); // prints message
|
||||
}
|
||||
|
||||
say_hello();
|
||||
"#)?;
|
||||
|
||||
// Compile another script with an overriding function
|
||||
let ast2 = engine.compile(r#"fn get_message() { "Boo!" }"#)?;
|
||||
|
||||
// Combine the two AST's
|
||||
ast1 += ast2; // 'message' will be overwritten
|
||||
|
||||
engine.run_ast(&ast1)?; // prints 'Boo!'
|
||||
```
|
||||
|
||||
Therefore, care must be taken when _cross-calling_ [functions] to make sure that the correct
|
||||
[function] is 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:
|
||||
|
||||
```js
|
||||
┌──────────────┐
|
||||
│ message.rhai │
|
||||
└──────────────┘
|
||||
|
||||
fn get_message() { "Hello!" }
|
||||
|
||||
|
||||
┌─────────────┐
|
||||
│ script.rhai │
|
||||
└─────────────┘
|
||||
|
||||
import "message" as msg;
|
||||
|
||||
fn say_hello() {
|
||||
print(msg::get_message());
|
||||
}
|
||||
say_hello();
|
||||
```
|
96
rhai_engine/rhaibook/language/fn-parent-scope.md
Normal file
96
rhai_engine/rhaibook/language/fn-parent-scope.md
Normal file
@@ -0,0 +1,96 @@
|
||||
Call a Function Within the Caller's Scope
|
||||
=========================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Peeking Out of The Pure Box
|
||||
---------------------------
|
||||
|
||||
```admonish info.side "Only scripts"
|
||||
|
||||
This is only meaningful for _scripted_ [functions].
|
||||
|
||||
Native Rust functions can never access any [`Scope`].
|
||||
```
|
||||
|
||||
Rhai [functions] are _pure_, meaning that they depend on on their arguments and have no access to
|
||||
the calling environment.
|
||||
|
||||
When a [function] accesses a [variable] that is not defined within that [function]'s [`Scope`],
|
||||
it raises an evaluation error.
|
||||
|
||||
It is possible, through a special syntax, to actually run the [function] call within the [`Scope`]
|
||||
of the parent caller – i.e. the [`Scope`] that makes the [function] call – and
|
||||
access/mutate [variables] defined there.
|
||||
|
||||
|
||||
```rust
|
||||
fn foo(y) { // function accesses 'x' and 'y', but 'x' is not defined
|
||||
x += y; // 'x' is modified in this function
|
||||
let z = 0; // 'z' is defined in this function's scope
|
||||
x
|
||||
}
|
||||
|
||||
let x = 1; // 'x' is defined here in the parent scope
|
||||
|
||||
foo(41); // error: variable 'x' not found
|
||||
|
||||
// Calling a function with a '!' causes it to run within the caller's scope
|
||||
|
||||
foo!(41) == 42; // the function can access and mutate the value of 'x'!
|
||||
|
||||
x == 42; // 'x' is changed!
|
||||
|
||||
z == 0; // <- error: variable 'z' not found
|
||||
|
||||
x.method!(); // <- syntax error: not allowed in method-call style
|
||||
|
||||
// Also works for function pointers
|
||||
|
||||
let f = foo; // <- de-sugars to 'Fn("foo")'
|
||||
|
||||
call!(f, 42) == 84; // must use function-call style
|
||||
|
||||
x == 84; // 'x' is changed once again
|
||||
|
||||
f.call!(41); // <- syntax error: not allowed in method-call style
|
||||
|
||||
// But not allowed for module functions
|
||||
|
||||
import "hello" as h;
|
||||
|
||||
h::greet!(); // <- syntax error: not allowed in namespace-qualified calls
|
||||
```
|
||||
|
||||
```admonish danger.small "The caller's scope can be mutated"
|
||||
|
||||
Changes to [variables] in the calling [`Scope`] persist.
|
||||
|
||||
With this syntax, it is possible for a Rhai [function] to mutate its calling environment.
|
||||
```
|
||||
|
||||
```admonish warning.small "New variables are not retained"
|
||||
|
||||
[Variables] or [constants] defined within the [function] are _not_ retained.
|
||||
They remain local to the [function].
|
||||
|
||||
Although the syntax resembles a Rust _macro_ invocation, it is still a [function] call.
|
||||
```
|
||||
|
||||
|
||||
```admonish danger "Caveat emptor"
|
||||
|
||||
[Functions] relying on the calling [`Scope`] is often a _Very Bad Idea™_ because it makes code
|
||||
almost impossible to reason about and maintain, as their behaviors are volatile and unpredictable.
|
||||
|
||||
Rhai [functions] are normally _pure_, meaning that you can rely on the fact that they never mutate
|
||||
the outside environment. Using this syntax breaks this guarantee.
|
||||
|
||||
[Functions] called in this manner behave more like macros that are expanded inline than actual
|
||||
function calls, thus the syntax is also similar to Rust's macro invocations.
|
||||
|
||||
This usage should be at the last resort.
|
||||
|
||||
**YOU HAVE BEEN WARNED**.
|
||||
```
|
310
rhai_engine/rhaibook/language/fn-ptr.md
Normal file
310
rhai_engine/rhaibook/language/fn-ptr.md
Normal file
@@ -0,0 +1,310 @@
|
||||
Function Pointers
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish question.side "Trivia"
|
||||
|
||||
A function pointer simply stores the _name_ of the [function] as a [string].
|
||||
```
|
||||
|
||||
It is possible to store a _function pointer_ in a variable just like a normal value.
|
||||
|
||||
A function pointer is created via the `Fn` [function], which takes a [string] parameter.
|
||||
|
||||
Call a function pointer via the `call` method.
|
||||
|
||||
|
||||
Short-Hand Notation
|
||||
-------------------
|
||||
|
||||
```admonish warning.side "Not for native"
|
||||
|
||||
Native Rust functions cannot use this short-hand notation.
|
||||
```
|
||||
|
||||
Having to write `Fn("foo")` in order to create a function pointer to the [function] `foo` is a chore,
|
||||
so there is a short-hand available.
|
||||
|
||||
A function pointer to any _script-defined_ [function] _within the same script_ can be obtained simply
|
||||
by referring to the [function's][function] name.
|
||||
|
||||
```rust
|
||||
fn foo() { ... } // function definition
|
||||
|
||||
let f = foo; // function pointer to 'foo'
|
||||
|
||||
let f = Fn("foo"); // <- the above is equivalent to this
|
||||
|
||||
let g = bar; // error: variable 'bar' not found
|
||||
```
|
||||
|
||||
The short-hand notation is particularly useful when passing [functions] as [closure] arguments.
|
||||
|
||||
```rust
|
||||
fn is_even(n) { n % 2 == 0 }
|
||||
|
||||
let array = [1, 2, 3, 4, 5];
|
||||
|
||||
array.filter(is_even);
|
||||
|
||||
array.filter(Fn("is_even")); // <- the above is equivalent to this
|
||||
|
||||
array.filter(|n| n % 2 == 0); // <- ... or this
|
||||
```
|
||||
|
||||
Built-in Functions
|
||||
------------------
|
||||
|
||||
The following standard methods (mostly defined in the [`BasicFnPackage`][built-in packages] but
|
||||
excluded when using a [raw `Engine`]) operate on function pointers.
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ---------------------------------- | ------------ | ------------------------------------------------------------------------------------------------ |
|
||||
| `name` method and property | _none_ | returns the name of the [function] encapsulated by the function pointer |
|
||||
| `is_anonymous` method and property | _none_ | does the function pointer refer to an [anonymous function]? Not available under [`no_function`]. |
|
||||
| `call` | _arguments_ | calls the [function] matching the function pointer's name with the _arguments_ |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
fn foo(x) { 41 + x }
|
||||
|
||||
let func = Fn("foo"); // use the 'Fn' function to create a function pointer
|
||||
|
||||
let func = foo; // <- short-hand: equivalent to 'Fn("foo")'
|
||||
|
||||
print(func); // prints 'Fn(foo)'
|
||||
|
||||
let func = fn_name.Fn(); // <- error: 'Fn' cannot be called in method-call style
|
||||
|
||||
func.type_of() == "Fn"; // type_of() as function pointer is 'Fn'
|
||||
|
||||
func.name == "foo";
|
||||
|
||||
func.call(1) == 42; // call a function pointer with the 'call' method
|
||||
|
||||
foo(1) == 42; // <- the above de-sugars to this
|
||||
|
||||
call(func, 1); // normal function call style also works for 'call'
|
||||
|
||||
let len = Fn("len"); // 'Fn' also works with registered native Rust functions
|
||||
|
||||
len.call("hello") == 5;
|
||||
|
||||
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)'
|
||||
```
|
||||
|
||||
|
||||
```admonish warning "Not First-Class Functions"
|
||||
|
||||
Beware that function pointers are _not_ first-class functions.
|
||||
|
||||
They are _syntactic sugar_ only, capturing only the _name_ of a [function] to call.
|
||||
They do not hold the actual [functions].
|
||||
|
||||
The actual [function] must be defined in the appropriate [namespace][function namespace]
|
||||
for the call to succeed.
|
||||
```
|
||||
|
||||
~~~admonish warning "Global Namespace Only"
|
||||
|
||||
Because of their dynamic nature, function pointers cannot refer to functions in [`import`]-ed [modules].
|
||||
|
||||
They can only refer to [functions] within the global [namespace][function namespace].
|
||||
|
||||
```js
|
||||
import "foo" as f; // assume there is 'f::do_work()'
|
||||
|
||||
f::do_work(); // works!
|
||||
|
||||
let p = Fn("f::do_work"); // error: invalid function name
|
||||
|
||||
fn do_work_now() { // call it from a local function
|
||||
f::do_work();
|
||||
}
|
||||
|
||||
let p = Fn("do_work_now");
|
||||
|
||||
p.call(); // works!
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
Dynamic Dispatch
|
||||
----------------
|
||||
|
||||
The purpose of function pointers is to enable rudimentary _dynamic dispatch_, meaning to determine,
|
||||
at runtime, which function to call among a group.
|
||||
|
||||
Although it is possible to simulate dynamic dispatch via a number and a large
|
||||
[`if-then-else-if`][`if`] statement, using function pointers significantly simplifies the code.
|
||||
|
||||
```rust
|
||||
let x = some_calculation();
|
||||
|
||||
// These are the functions to call depending on the value of 'x'
|
||||
fn method1(x) { ... }
|
||||
fn method2(x) { ... }
|
||||
fn method3(x) { ... }
|
||||
|
||||
// Traditional - using decision variable
|
||||
let func = sign(x);
|
||||
|
||||
// Dispatch with if-statement
|
||||
if func == -1 {
|
||||
method1(42);
|
||||
} else if func == 0 {
|
||||
method2(42);
|
||||
} else if func == 1 {
|
||||
method3(42);
|
||||
}
|
||||
|
||||
// Using pure function pointer
|
||||
let func = if x < 0 {
|
||||
method1
|
||||
} else if x == 0 {
|
||||
method2
|
||||
} else if x > 0 {
|
||||
method3
|
||||
};
|
||||
|
||||
// Dynamic dispatch
|
||||
func.call(42);
|
||||
|
||||
// Using functions map
|
||||
let map = [ method1, method2, method3 ];
|
||||
|
||||
let func = sign(x) + 1;
|
||||
|
||||
// Dynamic dispatch
|
||||
map[func].call(42);
|
||||
```
|
||||
|
||||
|
||||
Bind the `this` Pointer
|
||||
-----------------------
|
||||
|
||||
When `call` is called as a _method_ but not on a function pointer, it is possible to dynamically dispatch
|
||||
to a function call while binding the object in the method call to the `this` pointer of the function.
|
||||
|
||||
To achieve this, pass the function pointer as the _first_ argument to `call`:
|
||||
|
||||
```rust
|
||||
fn add(x) { // define function which uses 'this'
|
||||
this += x;
|
||||
}
|
||||
|
||||
let func = add; // function pointer to 'add'
|
||||
|
||||
func.call(1); // error: 'this' pointer is not bound
|
||||
|
||||
let x = 41;
|
||||
|
||||
func.call(x, 1); // error: function 'add (i64, i64)' not found
|
||||
|
||||
call(func, x, 1); // error: function 'add (i64, i64)' not found
|
||||
|
||||
x.call(func, 1); // 'this' is bound to 'x', dispatched to 'func'
|
||||
|
||||
x == 42;
|
||||
```
|
||||
|
||||
Beware that this only works for [_method-call_](fn-method.md) style.
|
||||
Normal function-call style cannot bind the `this` pointer (for syntactic reasons).
|
||||
|
||||
Therefore, obviously, binding the `this` pointer is unsupported under [`no_object`].
|
||||
|
||||
|
||||
Call a Function Pointer within a Rust Function (as a Callback)
|
||||
--------------------------------------------------------------
|
||||
|
||||
It is completely normal to register a Rust function with an [`Engine`] that takes parameters
|
||||
whose types are function pointers. The Rust type in question is `rhai::FnPtr`.
|
||||
|
||||
A function pointer in Rhai is essentially syntactic sugar wrapping the _name_ of a function
|
||||
to call in script. Therefore, the script's _execution context_ (i.e. [`NativeCallContext`])
|
||||
is needed in order to call a function pointer.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, FnPtr, NativeCallContext};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// A function expecting a callback in form of a function pointer.
|
||||
fn super_call(context: NativeCallContext, callback: FnPtr, value: i64)
|
||||
-> Result<String, Box<EvalAltResult>>
|
||||
{
|
||||
// Use 'FnPtr::call_within_context' to call the function pointer using the call context.
|
||||
// 'FnPtr::call_within_context' automatically casts to the required result type.
|
||||
callback.call_within_context(&context, (value,))
|
||||
// ^^^^^^^^ arguments passed in tuple
|
||||
}
|
||||
|
||||
engine.register_fn("super_call", super_call);
|
||||
```
|
||||
|
||||
|
||||
Call a Function Pointer Directly
|
||||
--------------------------------
|
||||
|
||||
The `FnPtr::call` method allows the function pointer to be called directly on any [`Engine`] and
|
||||
[`AST`], making it possible to reuse the `FnPtr` data type in may different calls and scripting
|
||||
environments.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, FnPtr};
|
||||
|
||||
let engine = Engine::new();
|
||||
|
||||
// Compile script to AST
|
||||
let ast = engine.compile(
|
||||
r#"
|
||||
let test = "hello";
|
||||
|x| test + x // this creates a closure
|
||||
"#)?;
|
||||
|
||||
// Save the closure together with captured variables
|
||||
let fn_ptr = engine.eval_ast::<FnPtr>(&ast)?;
|
||||
|
||||
// 'f' captures: the Engine, the AST, and the closure
|
||||
let f = move |x: i64| -> Result<String, _> {
|
||||
fn_ptr.call(&engine, &ast, (x,))
|
||||
};
|
||||
|
||||
// 'f' can be called like a normal function
|
||||
let result = f(42)?;
|
||||
|
||||
result == "hello42";
|
||||
```
|
||||
|
||||
|
||||
Bind to a native Rust Function
|
||||
------------------------------
|
||||
|
||||
It is also possible to create a function pointer that binds to a native Rust function or a Rust closure.
|
||||
|
||||
The signature of the native Rust function takes the following form.
|
||||
|
||||
> ```rust
|
||||
> Fn(context: NativeCallContext, args: &mut [&mut Dynamic])
|
||||
> -> Result<Dynamic, Box<EvalAltResult>> + 'static
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :-------------------: | ----------------------------------------------- |
|
||||
| `context` | [`NativeCallContext`] | mutable reference to the current _call context_ |
|
||||
| `args` | `&mut [&mut Dynamic]` | mutable reference to list of arguments |
|
||||
|
||||
When such a function pointer is used in script, the native Rust function will be called
|
||||
with the arguments provided.
|
||||
|
||||
The Rust function should check whether the appropriate number of arguments have been passed.
|
84
rhai_engine/rhaibook/language/for.md
Normal file
84
rhai_engine/rhaibook/language/for.md
Normal file
@@ -0,0 +1,84 @@
|
||||
For Loop
|
||||
========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Iterating through a numeric [range] or an [array], or any type with a registered [type iterator],
|
||||
is provided by the `for` ... `in` loop.
|
||||
|
||||
There are two alternative syntaxes, one including a counter variable:
|
||||
|
||||
> `for` _variable_ `in` _expression_ `{` ... `}`
|
||||
>
|
||||
> `for (` _variable_ `,` _counter_ `)` `in` _expression_ `{` ... `}`
|
||||
|
||||
~~~admonish tip.small "Tip: Disable `for` loops"
|
||||
|
||||
`for` loops can be disabled via [`Engine::set_allow_looping`][options].
|
||||
~~~
|
||||
|
||||
Break or Continue
|
||||
-----------------
|
||||
|
||||
Like C, `continue` can be used to skip to the next iteration, by-passing all following statements.
|
||||
|
||||
`break` can be used to break out of the loop unconditionally.
|
||||
|
||||
|
||||
For Expression
|
||||
--------------
|
||||
|
||||
Unlike Rust, `for` statements can also be used as _expressions_.
|
||||
|
||||
The `break` statement takes an optional expression that provides the return value.
|
||||
|
||||
The default return value of a `for` expression is [`()`].
|
||||
|
||||
~~~admonish tip.small "Tip: Disable all loop expressions"
|
||||
|
||||
Loop expressions can be disabled via [`Engine::set_allow_loop_expressions`][options].
|
||||
~~~
|
||||
|
||||
```js
|
||||
let a = [42, 123, 999, 0, true, "hello", "world!", 987.6543];
|
||||
|
||||
// 'for' can be used just like an expression
|
||||
let index = for (item, count) in a {
|
||||
// if the 'for' loop breaks here, return a specific value
|
||||
switch item.type_of() {
|
||||
"i64" if item.is_even => break count,
|
||||
"f64" if item.to_int().is_even => break count,
|
||||
}
|
||||
|
||||
// ... if the 'for' loop exits here, the return value is ()
|
||||
};
|
||||
|
||||
if index == () {
|
||||
print("Magic number not found!");
|
||||
} else {
|
||||
print(`Magic number found at index ${index}!`);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Counter Variable
|
||||
----------------
|
||||
|
||||
The counter variable, if specified, starts from zero, incrementing upwards.
|
||||
|
||||
```js , no_run
|
||||
let a = [42, 123, 999, 0, true, "hello", "world!", 987.6543];
|
||||
|
||||
// Loop through the array
|
||||
for (item, count) in a {
|
||||
if x.type_of() == "string" {
|
||||
continue; // skip to the next iteration
|
||||
}
|
||||
|
||||
// 'item' contains a copy of each element during each iteration
|
||||
// 'count' increments (starting from zero) for each iteration
|
||||
print(`Item #${count + 1} = ${item}`);
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
```
|
222
rhai_engine/rhaibook/language/functions.md
Normal file
222
rhai_engine/rhaibook/language/functions.md
Normal file
@@ -0,0 +1,222 @@
|
||||
Functions
|
||||
=========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai supports defining functions in script via the `fn` keyword, with a syntax that is very similar to Rust without types.
|
||||
|
||||
Valid function names are the same as valid [variable] names.
|
||||
|
||||
```rust
|
||||
fn add(x, y) {
|
||||
x + y
|
||||
}
|
||||
|
||||
fn sub(x, y,) { // trailing comma in parameters list is OK
|
||||
x - y
|
||||
}
|
||||
|
||||
add(2, 3) == 5;
|
||||
|
||||
sub(2, 3,) == -1; // trailing comma in arguments list is OK
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Disable functions"
|
||||
|
||||
Defining functions can be disabled via the [`no_function`] feature.
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: `is_def_fn`"
|
||||
|
||||
Use `is_def_fn` (not available under [`no_function`]) to detect if a Rhai function is defined
|
||||
(and therefore callable) based on its name and the number of parameters (_arity_).
|
||||
|
||||
```rust
|
||||
fn foo(x) { x + 1 }
|
||||
|
||||
is_def_fn("foo", 1) == true;
|
||||
|
||||
is_def_fn("foo", 0) == false;
|
||||
|
||||
is_def_fn("foo", 2) == false;
|
||||
|
||||
is_def_fn("bar", 1) == false;
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
Implicit Return
|
||||
---------------
|
||||
|
||||
Just like in Rust, an implicit return can be used. In fact, the last statement of a block is
|
||||
_always_ the block's return value regardless of whether it is terminated with a semicolon `;`.
|
||||
This is different from Rust.
|
||||
|
||||
```rust
|
||||
fn add(x, y) { // implicit return:
|
||||
x + y; // value of the last statement (no need for ending semicolon)
|
||||
// is used as the return value
|
||||
}
|
||||
|
||||
fn add2(x) {
|
||||
return x + 2; // explicit return
|
||||
}
|
||||
|
||||
add(2, 3) == 5;
|
||||
|
||||
add2(42) == 44;
|
||||
```
|
||||
|
||||
|
||||
Global Definitions Only
|
||||
-----------------------
|
||||
|
||||
Functions can only be defined at the global level, never inside a block or another function.
|
||||
|
||||
Again, this is different from Rust.
|
||||
|
||||
```rust
|
||||
// Global level is OK
|
||||
fn add(x, y) {
|
||||
x + y
|
||||
}
|
||||
|
||||
// The following will not compile
|
||||
fn do_addition(x) {
|
||||
fn add_y(n) { // <- syntax error: cannot define inside another function
|
||||
n + y
|
||||
}
|
||||
|
||||
add_y(x)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
No Access to External Scope
|
||||
---------------------------
|
||||
|
||||
Functions are not _closures_. They do not capture the calling environment and can only access their
|
||||
own parameters.
|
||||
|
||||
They cannot access [variables] external to the function itself.
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
|
||||
fn foo() {
|
||||
x // <- error: variable 'x' not found
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
But Can Call Other Functions and Access Modules
|
||||
-----------------------------------------------
|
||||
|
||||
All functions in the same [`AST`] can call each other.
|
||||
|
||||
```rust
|
||||
fn foo(x) { // function defined in the global namespace
|
||||
x + 1
|
||||
}
|
||||
|
||||
fn bar(x) {
|
||||
foo(x) // ok! function 'foo' can be called
|
||||
}
|
||||
```
|
||||
|
||||
In addition, [modules] [imported][`import`] at global level can be accessed.
|
||||
|
||||
```js
|
||||
import "hello" as hey;
|
||||
import "world" as woo;
|
||||
|
||||
{
|
||||
import "x" as xyz; // <- this module is not at global level
|
||||
} // <- it goes away here
|
||||
|
||||
fn foo(x) {
|
||||
hey::process(x); // ok! imported module 'hey' can be accessed
|
||||
|
||||
print(woo::value); // ok! imported module 'woo' can be accessed
|
||||
|
||||
xyz::do_work(); // <- error: module 'xyz' not found
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Automatic Global Module
|
||||
-----------------------
|
||||
|
||||
When a [constant] is declared at global scope, it is added to a special [module] called [`global`].
|
||||
|
||||
Functions can access those [constants] via the special [`global`] [module].
|
||||
|
||||
Naturally, the automatic [`global`] [module] is not available under [`no_function`] nor [`no_module`].
|
||||
|
||||
```rust
|
||||
const CONSTANT = 42; // this constant is automatically added to 'global'
|
||||
|
||||
let hello = 1; // variables are not added to 'global'
|
||||
|
||||
{
|
||||
const INNER = 0; // this constant is not at global level
|
||||
} // <- it goes away here
|
||||
|
||||
fn foo(x) {
|
||||
x * global::hello // <- error: variable 'hello' not found in 'global'
|
||||
|
||||
x * global::CONSTANT // ok! 'CONSTANT' exists in 'global'
|
||||
|
||||
x * global::INNER // <- error: constant 'INNER' not found in 'global'
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Use Before Definition Allowed
|
||||
-----------------------------
|
||||
|
||||
Unlike C/C++, functions in Rhai can be defined _anywhere_ at global level.
|
||||
|
||||
A function does not need to be defined prior to being used in a script; a statement in the script
|
||||
can freely call a function defined afterwards.
|
||||
|
||||
This is similar to Rust and many other modern languages, such as JavaScript's `function` keyword.
|
||||
|
||||
```rust
|
||||
let x = foo(41); // <- I can do this!
|
||||
|
||||
fn foo(x) { // <- define 'foo' after use
|
||||
x + 1
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Arguments are Passed by Value
|
||||
-----------------------------
|
||||
|
||||
Functions defined in script always take [`Dynamic`] parameters (i.e. they can be of any types).
|
||||
Therefore, functions with the same name and same _number_ of parameters are equivalent.
|
||||
|
||||
All arguments are passed by _value_, so all Rhai script-defined functions are _pure_
|
||||
(i.e. they never modify their arguments).
|
||||
|
||||
Any update to an argument will **not** be reflected back to the caller.
|
||||
|
||||
|
||||
```rust
|
||||
fn change(s) { // 's' is passed by value
|
||||
s = 42; // only a COPY of 's' is changed
|
||||
}
|
||||
|
||||
let x = 500;
|
||||
|
||||
change(x);
|
||||
|
||||
x == 500; // 'x' is NOT changed!
|
||||
```
|
||||
|
||||
```admonish warning.small "Rhai functions are pure"
|
||||
|
||||
The only possibility for a Rhai script-defined function to modify an external variable is
|
||||
via the [`this`](fn-method.md) pointer.
|
||||
```
|
42
rhai_engine/rhaibook/language/global.md
Normal file
42
rhai_engine/rhaibook/language/global.md
Normal file
@@ -0,0 +1,42 @@
|
||||
Automatic Global Module
|
||||
=======================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
When a [constant] is declared at global scope, it is added to a special [module] called `global`.
|
||||
|
||||
[Functions] can access those [constants] via the special `global` [module].
|
||||
|
||||
Naturally, the automatic `global` [module] is not available under [`no_function`] nor [`no_module`].
|
||||
|
||||
```rust
|
||||
const CONSTANT = 42; // this constant is automatically added to 'global'
|
||||
|
||||
{
|
||||
const INNER = 0; // this constant is not at global level
|
||||
} // <- it goes away here
|
||||
|
||||
fn foo(x) {
|
||||
x *= global::CONSTANT; // ok! 'CONSTANT' exists in 'global'
|
||||
|
||||
x * global::INNER // <- error: constant 'INNER' not found in 'global'
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Override `global`
|
||||
-----------------
|
||||
|
||||
It is possible to _override_ the automatic global [module] by [importing][`import`] another [module]
|
||||
under the name `global`.
|
||||
|
||||
```rust
|
||||
import "foo" as global; // import a module as 'global'
|
||||
|
||||
const CONSTANT = 42; // this constant is NOT added to 'global'
|
||||
|
||||
fn foo(x) {
|
||||
global::CONSTANT // <- error: constant 'CONSTANT' not found in 'global'
|
||||
}
|
||||
```
|
92
rhai_engine/rhaibook/language/if.md
Normal file
92
rhai_engine/rhaibook/language/if.md
Normal file
@@ -0,0 +1,92 @@
|
||||
If Statement
|
||||
============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
`if` statements follow C syntax.
|
||||
|
||||
```rust
|
||||
if foo(x) {
|
||||
print("It's true!");
|
||||
} else if bar == baz {
|
||||
print("It's true again!");
|
||||
} else if baz.is_foo() {
|
||||
print("Yet again true.");
|
||||
} else if foo(bar - baz) {
|
||||
print("True again... this is getting boring.");
|
||||
} else {
|
||||
print("It's finally false!");
|
||||
}
|
||||
```
|
||||
|
||||
~~~admonish warning.small "Braces are mandatory"
|
||||
|
||||
Unlike C, the condition expression does _not_ need to be enclosed in parentheses `(`...`)`, but all
|
||||
branches of the `if` statement must be enclosed within braces `{`...`}`, even when there is only
|
||||
one statement inside the branch.
|
||||
|
||||
Like Rust, there is no ambiguity regarding which `if` clause a branch belongs to.
|
||||
|
||||
```rust
|
||||
// Rhai is not C!
|
||||
if (decision) print(42);
|
||||
// ^ syntax error, expecting '{'
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
If Expression
|
||||
-------------
|
||||
|
||||
Like Rust, `if` statements can also be used as _expressions_, replacing the `? :` conditional
|
||||
operators in other C-like languages.
|
||||
|
||||
~~~admonish tip.small "Tip: Disable `if` expressions"
|
||||
|
||||
`if` expressions can be disabled via [`Engine::set_allow_if_expression`][options].
|
||||
~~~
|
||||
|
||||
```rust
|
||||
// The following is equivalent to C: int x = 1 + (decision ? 42 : 123) / 2;
|
||||
let x = 1 + if decision { 42 } else { 123 } / 2;
|
||||
x == 22;
|
||||
|
||||
let x = if decision { 42 }; // no else branch defaults to '()'
|
||||
x == ();
|
||||
```
|
||||
|
||||
~~~admonish danger.small "Statement before expression"
|
||||
|
||||
Beware that, like Rust, `if` is parsed primarily as a statement where it makes sense.
|
||||
This is to avoid surprises.
|
||||
|
||||
```rust
|
||||
fn index_of(x) {
|
||||
// 'if' is parsed primarily as a statement
|
||||
if this.contains(x) {
|
||||
return this.find_index(x)
|
||||
}
|
||||
|
||||
-1
|
||||
}
|
||||
```
|
||||
|
||||
The above will not be parsed as a single expression:
|
||||
|
||||
```rust
|
||||
fn index_of(x) {
|
||||
if this.contains(x) { return this.find_index(x) } - 1
|
||||
// error due to '() - 1' ^
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
To force parsing as an expression, parentheses are required:
|
||||
|
||||
```rust
|
||||
fn calc_index(b, offset) {
|
||||
(if b { 1 } else { 0 }) + offset
|
||||
// ^---------------------^ parentheses
|
||||
}
|
||||
```
|
||||
~~~
|
138
rhai_engine/rhaibook/language/in.md
Normal file
138
rhai_engine/rhaibook/language/in.md
Normal file
@@ -0,0 +1,138 @@
|
||||
In Operator
|
||||
===========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish question.side.wide "Trivia"
|
||||
|
||||
The `in` operator is simply syntactic sugar for a call to the `contains` function.
|
||||
|
||||
Similarly, `!in` is a call to `!contains`.
|
||||
```
|
||||
|
||||
The `in` operator is used to check for _containment_ – i.e. whether a particular collection
|
||||
data type _contains_ a particular item.
|
||||
|
||||
Similarly, `!in` is used to check for non-existence – i.e. it is `true` if a particular
|
||||
collection data type does _not_ contain a particular item.
|
||||
|
||||
```rust
|
||||
42 in array;
|
||||
|
||||
array.contains(42); // <- the above is equivalent to this
|
||||
|
||||
123 !in array;
|
||||
|
||||
!array.contains(123); // <- the above is equivalent to this
|
||||
```
|
||||
|
||||
|
||||
Built-in Support for Standard Data Types
|
||||
----------------------------------------
|
||||
|
||||
| Data type | Check for |
|
||||
| :-------------: | :---------------------------------: |
|
||||
| Numeric [range] | integer number |
|
||||
| [Array] | contained item |
|
||||
| [Object map] | property name |
|
||||
| [String] | [sub-string][string] or [character] |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let array = [1, "abc", 42, ()];
|
||||
|
||||
42 in array == true; // check array for item
|
||||
|
||||
let map = #{
|
||||
foo: 42,
|
||||
bar: true,
|
||||
baz: "hello"
|
||||
};
|
||||
|
||||
"foo" in map == true; // check object map for property name
|
||||
|
||||
'w' in "hello, world!" == true; // check string for character
|
||||
|
||||
'w' !in "hello, world!" == false;
|
||||
|
||||
"wor" in "hello, world" == true; // check string for sub-string
|
||||
|
||||
42 in -100..100 == true; // check range for number
|
||||
```
|
||||
|
||||
|
||||
Array Items Comparison
|
||||
----------------------
|
||||
|
||||
The default implementation of the `in` operator for [arrays] uses the `==` operator (if defined)
|
||||
to compare items.
|
||||
|
||||
~~~admonish warning.small "`==` defaults to `false`"
|
||||
|
||||
For a [custom type], `==` defaults to `false` when comparing it with a value of of the same type.
|
||||
|
||||
See the section on [_Logic Operators_](logic.md) for more details.
|
||||
~~~
|
||||
|
||||
```rust
|
||||
let ts = new_ts(); // assume 'new_ts' returns a custom type
|
||||
|
||||
let array = [1, 2, 3, ts, 42, 999];
|
||||
// ^^ custom type
|
||||
|
||||
42 in array == true; // 42 cannot be compared with 'ts'
|
||||
// so it defaults to 'false'
|
||||
// because == operator is not defined
|
||||
```
|
||||
|
||||
|
||||
Custom Implementation of `contains`
|
||||
-----------------------------------
|
||||
|
||||
The `in` and `!in` operators map directly to a call to a function `contains` with the two operands switched.
|
||||
|
||||
```rust
|
||||
// This expression...
|
||||
item in container
|
||||
|
||||
// maps to this...
|
||||
contains(container, item)
|
||||
|
||||
// or...
|
||||
container.contains(item)
|
||||
```
|
||||
|
||||
Support for the `in` and `!in` operators can be easily extended to other types by registering a
|
||||
custom binary function named `contains` with the correct parameter types.
|
||||
|
||||
Since `!in` maps to `!(... in ...)`, `contains` is enough to support both operators.
|
||||
|
||||
```rust
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_type::<TestStruct>()
|
||||
.register_fn("new_ts", || TestStruct::new())
|
||||
.register_fn("contains", |ts: &mut TestStruct, item: i64| -> bool {
|
||||
// Remember the parameters are switched from the 'in' expression
|
||||
ts.contains(item)
|
||||
});
|
||||
|
||||
// Now the 'in' operator can be used for 'TestStruct' and integer
|
||||
|
||||
engine.run(
|
||||
r#"
|
||||
let ts = new_ts();
|
||||
|
||||
if 42 in ts { // this calls 'ts.contains(42)'
|
||||
print("I got 42!");
|
||||
} else if 123 !in ts { // this calls '!ts.contains(123)'
|
||||
print("I ain't got 123!");
|
||||
}
|
||||
|
||||
let err = "hello" in ts; // <- runtime error: 'contains' not found
|
||||
// for 'TestStruct' and string
|
||||
"#)?;
|
||||
```
|
183
rhai_engine/rhaibook/language/iter.md
Normal file
183
rhai_engine/rhaibook/language/iter.md
Normal file
@@ -0,0 +1,183 @@
|
||||
Standard Iterable Types
|
||||
========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Certain [standard types] are iterable via a [`for`] statement.
|
||||
|
||||
|
||||
Iterate Through Arrays
|
||||
----------------------
|
||||
|
||||
Iterating through an [array] yields cloned _copies_ of each element.
|
||||
|
||||
```rust
|
||||
let a = [1, 3, 5, 7, 9, 42];
|
||||
|
||||
// Loop through the array
|
||||
for x in a {
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
```
|
||||
|
||||
Iterate Through Strings
|
||||
-----------------------
|
||||
|
||||
Iterating through a [string] yields individual [characters].
|
||||
|
||||
The `chars` method also allow iterating through characters in a [string], optionally accepting the
|
||||
character position to start from (counting from the end if negative), as well as the number of
|
||||
characters to iterate (defaults to all).
|
||||
|
||||
`char` also accepts a [range] which can be created via the `..` (exclusive) and `..=` (inclusive) operators.
|
||||
|
||||
```rust
|
||||
let s = "hello, world!";
|
||||
|
||||
// Iterate through all the characters.
|
||||
for ch in s {
|
||||
print(ch);
|
||||
}
|
||||
|
||||
// Iterate starting from the 3rd character and stopping at the 7th.
|
||||
for ch in s.chars(2, 5) {
|
||||
if ch > 'z' { continue; } // skip to the next iteration
|
||||
|
||||
print(ch);
|
||||
|
||||
if x == '@' { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// Iterate starting from the 3rd character and stopping at the end.
|
||||
for ch in s.chars(2..s.len) {
|
||||
if ch > 'z' { continue; } // skip to the next iteration
|
||||
|
||||
print(ch);
|
||||
|
||||
if x == '@' { break; } // break out of for loop
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Iterate Through Numeric Ranges
|
||||
------------------------------
|
||||
|
||||
[Ranges] are created via the `..` (exclusive) and `..=` (inclusive) operators.
|
||||
|
||||
The `range` function similarly creates exclusive [ranges], plus allowing optional step values.
|
||||
|
||||
```rust
|
||||
// Iterate starting from 0 and stopping at 49
|
||||
// The step is assumed to be 1 when omitted for integers
|
||||
for x in 0..50 {
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// The 'range' function is just the same
|
||||
for x in range(0, 50) {
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// The 'range' function also takes a step
|
||||
for x in range(0, 50, 3) { // step by 3
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// The 'range' function can also step backwards
|
||||
for x in range(50..0, -3) { // step down by -3
|
||||
if x < 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// It works also for floating-point numbers
|
||||
for x in range(5.0, 0.0, -2.0) { // step down by -2.0
|
||||
if x < 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 4.2 { break; } // break out of for loop
|
||||
}
|
||||
```
|
||||
|
||||
Iterate Through Bit-Fields
|
||||
--------------------------
|
||||
|
||||
The `bits` function allows iterating through an integer as a [bit-field].
|
||||
|
||||
`bits` optionally accepts the bit number to start from (counting from the most-significant-bit if
|
||||
negative), as well as the number of bits to iterate (defaults all).
|
||||
|
||||
`bits` also accepts a [range] which can be created via the `..` (exclusive) and `..=` (inclusive) operators.
|
||||
|
||||
```js , no_run
|
||||
let x = 0b_1001110010_1101100010_1100010100;
|
||||
let num_on = 0;
|
||||
|
||||
// Iterate through all the bits
|
||||
for bit in x.bits() {
|
||||
if bit { num_on += 1; }
|
||||
}
|
||||
|
||||
print(`There are ${num_on} bits turned on!`);
|
||||
|
||||
const START = 3;
|
||||
|
||||
// Iterate through all the bits from 3 through 12
|
||||
for (bit, index) in x.bits(START, 10) {
|
||||
print(`Bit #${index} is ${if bit { "ON" } else { "OFF" }}!`);
|
||||
|
||||
if index >= 7 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// Iterate through all the bits from 3 through 12
|
||||
for (bit, index) in x.bits(3..=12) {
|
||||
print(`Bit #${index} is ${if bit { "ON" } else { "OFF" }}!`);
|
||||
|
||||
if index >= 7 { break; } // break out of for loop
|
||||
}
|
||||
```
|
||||
|
||||
Iterate Through Object Maps
|
||||
---------------------------
|
||||
|
||||
Two methods, `keys` and `values`, return [arrays] containing cloned _copies_
|
||||
of all property names and values of an [object map], respectively.
|
||||
|
||||
These [arrays] can be iterated.
|
||||
|
||||
```rust
|
||||
let map = #{a:1, b:3, c:5, d:7, e:9};
|
||||
|
||||
// Property names are returned in unsorted, random order
|
||||
for x in map.keys() {
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// Property values are returned in unsorted, random order
|
||||
for val in map.values() {
|
||||
print(val);
|
||||
}
|
||||
```
|
59
rhai_engine/rhaibook/language/iterator.md
Normal file
59
rhai_engine/rhaibook/language/iterator.md
Normal file
@@ -0,0 +1,59 @@
|
||||
Make a Custom Type Iterable
|
||||
===========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish info.side "Built-in type iterators"
|
||||
|
||||
Type iterators are already defined for built-in [standard types] such as [strings], [ranges],
|
||||
[bit-fields], [arrays] and [object maps].
|
||||
|
||||
That's why they can be used with the [`for`] loop.
|
||||
```
|
||||
|
||||
If a [custom type] is iterable, the [`for`] loop can be used to iterate through
|
||||
its items in sequence, as long as it has a _type iterator_ registered.
|
||||
|
||||
`Engine::register_iterator<T>` allows registration of a type iterator for any type
|
||||
that implements `IntoIterator`.
|
||||
|
||||
With a type iterator registered, the [custom type] can be iterated through.
|
||||
|
||||
```rust
|
||||
// Custom type
|
||||
#[derive(Debug, Clone)]
|
||||
struct TestStruct { fields: Vec<i64> }
|
||||
|
||||
// Implement 'IntoIterator' trait
|
||||
impl IntoIterator<Item = i64> for TestStruct {
|
||||
type Item = i64;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.fields.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register API and type iterator for 'TestStruct'
|
||||
engine.register_type_with_name::<TestStruct>("TestStruct")
|
||||
.register_fn("new_ts", || TestStruct { fields: vec![1, 2, 3, 42] })
|
||||
.register_iterator::<TestStruct>();
|
||||
|
||||
// 'TestStruct' is now iterable
|
||||
engine.run(
|
||||
"
|
||||
for value in new_ts() {
|
||||
...
|
||||
}
|
||||
")?;
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Fallible type iterators"
|
||||
|
||||
`Engine::register_iterator_result` allows registration of a _fallible_ type iterator –
|
||||
i.e. an iterator that returns `Result<T, Box<EvalAltResult>>`.
|
||||
|
||||
On in very rare situations will this be necessary though.
|
||||
```
|
108
rhai_engine/rhaibook/language/json.md
Normal file
108
rhai_engine/rhaibook/language/json.md
Normal file
@@ -0,0 +1,108 @@
|
||||
Parse an Object Map from JSON
|
||||
=============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Do It Without `serde`
|
||||
---------------------
|
||||
|
||||
```admonish info.side.wide "Object map vs. JSON"
|
||||
|
||||
A valid JSON object hash does not start with a hash character `#` while a Rhai [object map] does.
|
||||
That's the only difference!
|
||||
```
|
||||
|
||||
The syntax for an [object map] is extremely similar to the JSON representation of a object hash,
|
||||
with the exception of `null` values which can technically be mapped to [`()`].
|
||||
|
||||
Use the `Engine::parse_json` method to parse a piece of JSON into an [object map].
|
||||
|
||||
```rust
|
||||
// JSON string - notice that JSON property names are always quoted
|
||||
// notice also that comments are acceptable within the JSON string
|
||||
let json = r#"{
|
||||
"a": 1, // <- this is an integer number
|
||||
"b": true,
|
||||
"c": 123.0, // <- this is a floating-point number
|
||||
"$d e f!": "hello", // <- any text can be a property name
|
||||
"^^^!!!": [1,42,"999"], // <- value can be array or another hash
|
||||
"z": null // <- JSON 'null' value
|
||||
}"#;
|
||||
|
||||
// Parse the JSON expression as an object map
|
||||
// Set the second boolean parameter to true in order to map 'null' to '()'
|
||||
let map = engine.parse_json(json, true)?;
|
||||
|
||||
map.len() == 6; // 'map' contains all properties in the JSON string
|
||||
|
||||
// Put the object map into a 'Scope'
|
||||
let mut scope = Scope::new();
|
||||
scope.push("map", map);
|
||||
|
||||
let result = engine.eval_with_scope::<i64>(&mut scope, r#"map["^^^!!!"].len()"#)?;
|
||||
|
||||
result == 3; // the object map is successfully used in the script
|
||||
```
|
||||
|
||||
```admonish warning.small "Warning: Must be object hash"
|
||||
|
||||
The JSON text must represent a single object hash – i.e. must be wrapped within braces
|
||||
`{`...`}`.
|
||||
|
||||
It cannot be a primitive type (e.g. number, string etc.).
|
||||
Otherwise it cannot be converted into an [object map] and a type error is returned.
|
||||
```
|
||||
|
||||
```admonish note.small "Representation of numbers"
|
||||
|
||||
JSON numbers are all floating-point while Rhai supports integers (`INT`) and floating-point (`FLOAT`)
|
||||
(except under [`no_float`]).
|
||||
|
||||
Most common generators of JSON data distinguish between integer and floating-point values by always
|
||||
serializing a floating-point number with a decimal point (i.e. `123.0` instead of `123` which is
|
||||
assumed to be an integer).
|
||||
|
||||
This style can be used successfully with Rhai [object maps].
|
||||
```
|
||||
|
||||
Sub-objects are handled transparently by `Engine::parse_json`.
|
||||
|
||||
It is _not_ necessary to replace `{` with `#{` in order to fake a Rhai [object map] literal.
|
||||
|
||||
```rust
|
||||
// JSON with sub-object 'b'.
|
||||
let json = r#"{"a":1, "b":{"x":true, "y":false}}"#;
|
||||
|
||||
// 'parse_json' handles this just fine.
|
||||
let map = engine.parse_json(json, false)?;
|
||||
|
||||
// 'map' contains two properties: 'a' and 'b'
|
||||
map.len() == 2;
|
||||
```
|
||||
|
||||
```admonish question "TL;DR – How is it done?"
|
||||
|
||||
Internally, `Engine::parse_json` _cheats_ by treating the JSON text as a Rhai script.
|
||||
|
||||
That is why it even supports [comments] and arithmetic expressions in the JSON text,
|
||||
although it is not a good idea to rely on non-standard JSON formats.
|
||||
|
||||
A [token remap filter] is used to convert `{` into `#{` and `null` to [`()`].
|
||||
```
|
||||
|
||||
|
||||
Use `serde`
|
||||
-----------
|
||||
|
||||
```admonish info.side "See also"
|
||||
|
||||
See _[Serialization/ Deserialization of `Dynamic` with `serde`][`serde`]_ for more details.
|
||||
```
|
||||
|
||||
Remember, `Engine::parse_json` is nothing more than a _cheap_ alternative to true JSON parsing.
|
||||
|
||||
If strict correctness is needed, or for more configuration possibilities, turn on the
|
||||
[`serde`][features] feature to pull in [`serde`](https://crates.io/crates/serde) which enables
|
||||
serialization and deserialization to/from multiple formats, including JSON.
|
||||
|
||||
Beware, though... the [`serde`](https://crates.io/crates/serde) crate is quite heavy.
|
30
rhai_engine/rhaibook/language/keywords.md
Normal file
30
rhai_engine/rhaibook/language/keywords.md
Normal file
@@ -0,0 +1,30 @@
|
||||
Keywords
|
||||
========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The following are reserved keywords in Rhai.
|
||||
|
||||
| Active keywords | Reserved keywords | Usage | Inactive under feature |
|
||||
| -------------------------------------------------------------------------- | ---------------------------------------------------------- | ----------------------------------- | :----------------------------: |
|
||||
| `true`, `false` | | [constants] | |
|
||||
| [`let`][variable], [`const`][constant] | `var`, `static` | [variables] | |
|
||||
| `is_shared` | | _shared_ values | [`no_closure`] |
|
||||
| | `is` | type checking | |
|
||||
| [`if`], [`else`][`if`] | `goto` | control flow | |
|
||||
| [`switch`] | `match`, `case` | switching and matching | |
|
||||
| [`do`], [`while`], [`loop`], `until`, [`for`], [`in`], `continue`, `break` | | looping | |
|
||||
| [`fn`][function], [`private`], `is_def_fn`, `this` | `public`, `protected`, `new` | [functions] | [`no_function`] |
|
||||
| [`return`] | | return values | |
|
||||
| [`throw`], [`try`], [`catch`] | | [throw/catch][`catch`] [exceptions] | |
|
||||
| [`import`], [`export`], `as` | `use`, `with`, `module`, `package`, `super` | [modules] | [`no_module`] |
|
||||
| [`global`] | | automatic global [module] | [`no_function`], [`no_module`] |
|
||||
| [`Fn`][function pointer], `call`, [`curry`][currying] | | [function pointers] | |
|
||||
| | `spawn`, `thread`, `go`, `sync`, `async`, `await`, `yield` | threading/async | |
|
||||
| [`type_of`], [`print`], [`debug`], [`eval`], `is_def_var` | | special functions | |
|
||||
| | `default`, `void`, `null`, `nil` | special values | |
|
||||
|
||||
```admonish warning.small
|
||||
Keywords cannot become the name of a [function] or [variable], even when they are
|
||||
[disabled][disable keywords and operators].
|
||||
```
|
234
rhai_engine/rhaibook/language/logic.md
Normal file
234
rhai_engine/rhaibook/language/logic.md
Normal file
@@ -0,0 +1,234 @@
|
||||
Logic Operators
|
||||
===============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Comparison Operators
|
||||
--------------------
|
||||
|
||||
| Operator | Description<br/>(`x` _operator_ `y`) | `x`, `y` same type or are numeric | `x`, `y` different types |
|
||||
| :------: | ------------------------------------ | :-------------------------------: | :----------------------: |
|
||||
| `==` | `x` is equals to `y` | error if not defined | `false` if not defined |
|
||||
| `!=` | `x` is not equals to `y` | error if not defined | `true` if not defined |
|
||||
| `>` | `x` is greater than `y` | error if not defined | `false` if not defined |
|
||||
| `>=` | `x` is greater than or equals to `y` | error if not defined | `false` if not defined |
|
||||
| `<` | `x` is less than `y` | error if not defined | `false` if not defined |
|
||||
| `<=` | `x` is less than or equals to `y` | error if not defined | `false` if not defined |
|
||||
|
||||
Comparison operators between most values of the same type are built in for all [standard types].
|
||||
|
||||
Others are defined in the [`LogicPackage`][built-in packages] but excluded when using a [raw `Engine`].
|
||||
|
||||
|
||||
### Floating-point numbers interoperate with integers
|
||||
|
||||
Comparing a floating-point number (`FLOAT`) with an integer is also supported.
|
||||
|
||||
```rust
|
||||
42 == 42.0; // true
|
||||
|
||||
42.0 == 42; // true
|
||||
|
||||
42.0 > 42; // false
|
||||
|
||||
42 >= 42.0; // true
|
||||
|
||||
42.0 < 42; // false
|
||||
```
|
||||
|
||||
### Decimal numbers interoperate with integers
|
||||
|
||||
Comparing a [`Decimal`][rust_decimal] number with an integer is also supported.
|
||||
|
||||
```rust
|
||||
let d = parse_decimal("42");
|
||||
|
||||
42 == d; // true
|
||||
|
||||
d == 42; // true
|
||||
|
||||
d > 42; // false
|
||||
|
||||
42 >= d; // true
|
||||
|
||||
d < 42; // false
|
||||
```
|
||||
|
||||
### Strings interoperate with characters
|
||||
|
||||
Comparing a [string] with a [character] is also supported, with the character first turned into a
|
||||
[string] before performing the comparison.
|
||||
|
||||
```rust
|
||||
'x' == "x"; // true
|
||||
|
||||
"" < 'a'; // true
|
||||
|
||||
'x' > "hello"; // false
|
||||
```
|
||||
|
||||
### Comparing different types defaults to `false`
|
||||
|
||||
Comparing two values of _different_ data types defaults to `false` unless the appropriate operator
|
||||
functions have been registered.
|
||||
|
||||
The exception is `!=` (not equals) which defaults to `true`. This is in line with intuition.
|
||||
|
||||
```rust
|
||||
42 > "42"; // false: i64 cannot be compared with string
|
||||
|
||||
42 <= "42"; // false: i64 cannot be compared with string
|
||||
|
||||
let ts = new_ts(); // custom type
|
||||
|
||||
ts == 42; // false: different types cannot be compared
|
||||
|
||||
ts != 42; // true: different types cannot be compared
|
||||
|
||||
ts == ts; // error: '==' not defined for the custom type
|
||||
```
|
||||
|
||||
### Safety valve: Comparing different _numeric_ types has no default
|
||||
|
||||
Beware that the above default does _NOT_ apply to numeric values of different types
|
||||
(e.g. comparison between `i64` and `u16`, `i32` and `f64`) – when multiple numeric types are
|
||||
used it is too easy to mess up and for subtle errors to creep in.
|
||||
|
||||
```rust
|
||||
// Assume variable 'x' = 42_u16, 'y' = 42_u16 (both types of u16)
|
||||
|
||||
x == y; // true: '==' operator for u16 is built-in
|
||||
|
||||
x == "hello"; // false: different non-numeric operand types default to false
|
||||
|
||||
x == 42; // error: ==(u16, i64) not defined, no default for numeric types
|
||||
|
||||
42 == y; // error: ==(i64, u16) not defined, no default for numeric types
|
||||
```
|
||||
|
||||
### Caution: Beware operators for custom types
|
||||
|
||||
```admonish tip.side.wide "Tip: Always the full set"
|
||||
|
||||
It is strongly recommended that, when defining operators for [custom types], always define the
|
||||
**full set** of six operators together, or at least the `==` and `!=` pair.
|
||||
```
|
||||
|
||||
Operators are completely separate from each other. For example:
|
||||
|
||||
* `!=` does not equal `!(==)`
|
||||
|
||||
* `>` does not equal `!(<=)`
|
||||
|
||||
* `<=` does not equal `<` plus `==`
|
||||
|
||||
* `<=` does not imply `<`
|
||||
|
||||
Therefore, if a [custom type] misses an [operator] definition, it simply raises an error
|
||||
or returns the default.
|
||||
|
||||
This behavior can be counter-intuitive.
|
||||
|
||||
```rust
|
||||
let ts = new_ts(); // custom type with '<=' and '==' defined
|
||||
|
||||
ts <= ts; // true: '<=' defined
|
||||
|
||||
ts < ts; // error: '<' not defined, even though '<=' is
|
||||
|
||||
ts == ts; // true: '==' defined
|
||||
|
||||
ts != ts; // error: '!=' not defined, even though '==' is
|
||||
```
|
||||
|
||||
|
||||
Boolean Operators
|
||||
-----------------
|
||||
|
||||
```admonish note.side
|
||||
|
||||
All boolean operators are [built in][built-in operators] for the `bool` data type.
|
||||
```
|
||||
|
||||
| Operator | Description | Arity | Short-circuits? |
|
||||
| :---------------: | :---------: | :----: | :-------------: |
|
||||
| `!` _(prefix)_ | _NOT_ | unary | no |
|
||||
| `&&` | _AND_ | binary | **yes** |
|
||||
| `&` | _AND_ | binary | no |
|
||||
| <code>\|\|</code> | _OR_ | binary | **yes** |
|
||||
| <code>\|</code> | _OR_ | binary | no |
|
||||
|
||||
Double boolean operators `&&` and `||` _short-circuit_ – meaning that the second operand will not be evaluated
|
||||
if the first one already proves the condition wrong.
|
||||
|
||||
Single boolean operators `&` and `|` always evaluate both operands.
|
||||
|
||||
```rust
|
||||
a() || b(); // b() is not evaluated if a() is true
|
||||
|
||||
a() && b(); // b() is not evaluated if a() is false
|
||||
|
||||
a() | b(); // both a() and b() are evaluated
|
||||
|
||||
a() & b(); // both a() and b() are evaluated
|
||||
```
|
||||
|
||||
|
||||
Null-Coalescing Operator
|
||||
------------------------
|
||||
|
||||
| Operator | Description | Arity | Short-circuits? |
|
||||
| :------: | :-----------: | :----: | :-------------: |
|
||||
| `??` | Null-coalesce | binary | yes |
|
||||
|
||||
The null-coalescing operator (`??`) returns the first operand if it is not [`()`], or the second
|
||||
operand if the first operand is [`()`].
|
||||
|
||||
It _short-circuits_ – meaning that the second operand will not be evaluated if the first
|
||||
operand is not [`()`].
|
||||
|
||||
```rust
|
||||
a ?? b // returns 'a' if it is not (), otherwise 'b'
|
||||
|
||||
a() ?? b(); // b() is only evaluated if a() is ()
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: Default value for object map property"
|
||||
|
||||
Use the null-coalescing operator to implement default values for non-existent [object map] properties.
|
||||
|
||||
```rust
|
||||
let map = #{ foo: 42 };
|
||||
|
||||
// Regular property access
|
||||
let x = map.foo; // x == 42
|
||||
|
||||
// Non-existent property
|
||||
let x = map.bar; // x == ()
|
||||
|
||||
// Default value for property
|
||||
let x = map.bar ?? 42; // x == 42
|
||||
```
|
||||
~~~
|
||||
|
||||
### Short-circuit loops and early returns
|
||||
|
||||
The following statements are allowed to follow the null-coalescing operator:
|
||||
|
||||
* `break`
|
||||
* `continue`
|
||||
* [`return`]
|
||||
* [`throw`]
|
||||
|
||||
This means that you can use the null-coalescing operator to short-circuit loops and/or
|
||||
early-return from functions when the value tested is [`()`].
|
||||
|
||||
```rust
|
||||
let total = 0;
|
||||
|
||||
for value in list {
|
||||
// Whenever 'calculate' returns '()', the loop stops
|
||||
total += calculate(value) ?? break;
|
||||
}
|
||||
```
|
71
rhai_engine/rhaibook/language/loop.md
Normal file
71
rhai_engine/rhaibook/language/loop.md
Normal file
@@ -0,0 +1,71 @@
|
||||
Infinite Loop
|
||||
=============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Infinite loops follow Rust syntax.
|
||||
|
||||
Like Rust, `continue` can be used to skip to the next iteration, by-passing all following statements;
|
||||
`break` can be used to break out of the loop unconditionally.
|
||||
|
||||
~~~admonish tip.small "Tip: Disable `loop`"
|
||||
|
||||
`loop` can be disabled via [`Engine::set_allow_looping`][options].
|
||||
~~~
|
||||
|
||||
```rust
|
||||
let x = 10;
|
||||
|
||||
loop {
|
||||
x -= 1;
|
||||
|
||||
if x > 5 { continue; } // skip to the next iteration
|
||||
|
||||
print(x);
|
||||
|
||||
if x == 0 { break; } // break out of loop
|
||||
}
|
||||
```
|
||||
|
||||
~~~admonish danger.small "Remember the `break` statement"
|
||||
|
||||
A `loop` statement without a `break` statement inside its loop block is infinite.
|
||||
There is no way for the loop to stop iterating.
|
||||
~~~
|
||||
|
||||
|
||||
Loop Expression
|
||||
---------------
|
||||
|
||||
Like Rust, `loop` statements can also be used as _expressions_.
|
||||
|
||||
The `break` statement takes an optional expression that provides the return value.
|
||||
|
||||
The default return value of a `loop` expression is [`()`].
|
||||
|
||||
~~~admonish tip.small "Tip: Disable all loop expressions"
|
||||
|
||||
Loop expressions can be disabled via [`Engine::set_allow_loop_expressions`][options].
|
||||
~~~
|
||||
|
||||
```js
|
||||
let x = 0;
|
||||
|
||||
// 'loop' can be used just like an expression
|
||||
let result = loop {
|
||||
if is_magic_number(x) {
|
||||
// if the loop breaks here, return a specific value
|
||||
break get_magic_result(x);
|
||||
}
|
||||
|
||||
x += 1;
|
||||
|
||||
// ... if the loop exits here, the return value is ()
|
||||
};
|
||||
|
||||
if result == () {
|
||||
print("Magic number not found!");
|
||||
} else {
|
||||
print(`Magic result = ${result}!`);
|
||||
}
|
||||
```
|
116
rhai_engine/rhaibook/language/modules/export.md
Normal file
116
rhai_engine/rhaibook/language/modules/export.md
Normal file
@@ -0,0 +1,116 @@
|
||||
Export Variables, Functions and Sub-Modules From a Script
|
||||
=========================================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
```admonish info.side "See also"
|
||||
|
||||
See [_Create a Module from AST_]({{rootUrl}}/rust/modules/ast.md) for more details.
|
||||
```
|
||||
|
||||
The easiest way to expose a collection of [functions] as a self-contained [module] is to do it via a Rhai script itself.
|
||||
|
||||
The script text is evaluated.
|
||||
|
||||
[Variables] are then selectively exposed via the `export` statement.
|
||||
|
||||
[Functions] defined by the script are automatically exported, unless marked as `private`.
|
||||
|
||||
Modules loaded within this [module] at the global level become _sub-modules_ and are also automatically exported.
|
||||
|
||||
|
||||
Export Global Variables
|
||||
-----------------------
|
||||
|
||||
The `export` statement, which can only be at global level, exposes a selected [variable] as member of a [module].
|
||||
|
||||
[Variables] not exported are _private_ and hidden. They are merely used to initialize the [module],
|
||||
but cannot be accessed from outside.
|
||||
|
||||
Everything exported from a [module] is **[constant]** (i.e. read-only).
|
||||
|
||||
```js
|
||||
// This is a module script.
|
||||
|
||||
let hidden = 123; // variable not exported - default hidden
|
||||
let x = 42; // this will be exported below
|
||||
|
||||
export x; // the variable 'x' is exported under its own name
|
||||
|
||||
export const x = 42; // convenient short-hand to declare a constant and export it
|
||||
// under its own name
|
||||
|
||||
export let x = 123; // variables can be exported as well, though it'll still be constant
|
||||
|
||||
export x as answer; // the variable 'x' is exported under the alias 'answer'
|
||||
// another script can load this module and access 'x' as 'module::answer'
|
||||
|
||||
{
|
||||
let inner = 0; // local variable - it disappears when the statements block ends,
|
||||
// therefore it is not 'global' and cannot be exported
|
||||
|
||||
export inner; // <- syntax error: cannot export a local variable
|
||||
}
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Multiple exports"
|
||||
|
||||
[Variables] can be exported under multiple names.
|
||||
For example, the following exports three [variables]:
|
||||
* `x` as `x` and `hello`
|
||||
* `y` as `foo` and `bar`
|
||||
* `z` as `z`
|
||||
|
||||
~~~js
|
||||
export x;
|
||||
export x as hello;
|
||||
export y as foo;
|
||||
export x as world;
|
||||
export y as bar;
|
||||
export z;
|
||||
~~~
|
||||
```
|
||||
|
||||
|
||||
Export Functions
|
||||
----------------
|
||||
|
||||
```admonish info.side "Private functions"
|
||||
|
||||
`private` [functions] are commonly called within the [module] only.
|
||||
They cannot be accessed otherwise.
|
||||
```
|
||||
|
||||
All [functions] are automatically exported, _unless_ it is explicitly opt-out with the `private` prefix.
|
||||
|
||||
[Functions] declared `private` are hidden to the outside.
|
||||
|
||||
```rust
|
||||
// This is a module script.
|
||||
|
||||
fn inc(x) { x + 1 } // script-defined function - default public
|
||||
|
||||
private fn foo() {} // private function - hidden
|
||||
```
|
||||
|
||||
|
||||
Sub-Modules
|
||||
-----------
|
||||
|
||||
All loaded [modules] are automatically exported as sub-modules.
|
||||
|
||||
~~~admonish tip.small "Tip: Skip exporting a module"
|
||||
|
||||
To prevent a [module] from being exported, load it inside a block statement so that it goes away at the
|
||||
end of the block.
|
||||
|
||||
```js
|
||||
// This is a module script.
|
||||
|
||||
import "hello" as foo; // <- exported
|
||||
|
||||
{
|
||||
import "world" as bar; // <- not exported
|
||||
}
|
||||
```
|
||||
~~~
|
132
rhai_engine/rhaibook/language/modules/import.md
Normal file
132
rhai_engine/rhaibook/language/modules/import.md
Normal file
@@ -0,0 +1,132 @@
|
||||
Import a Module
|
||||
===============
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
```admonish info.side "See also"
|
||||
|
||||
See [_Module Resolvers_][module resolver] for more details.
|
||||
```
|
||||
|
||||
Before a [module] can be used (via an `import` statement) in a script, there must be a
|
||||
[module resolver] registered into the [`Engine`], the default being the `FileModuleResolver`.
|
||||
|
||||
|
||||
`import` Statement
|
||||
------------------
|
||||
|
||||
```admonish tip.side.wide "Tip"
|
||||
|
||||
A [module] that is only `import`-ed but not given any name is simply run.
|
||||
|
||||
This is a very simple way to run another script file from within a script.
|
||||
```
|
||||
|
||||
A [module] can be _imported_ via the `import` statement, and be given a name.
|
||||
|
||||
Its members can be accessed via `::` similar to C++.
|
||||
|
||||
```js
|
||||
import "crypto_banner"; // run the script file 'crypto_banner.rhai' without creating an imported module
|
||||
|
||||
import "crypto" as lock; // run the script file 'crypto.rhai' and import it as a module named 'lock'
|
||||
|
||||
const SECRET_NUMBER = 42;
|
||||
|
||||
let mod_file = `crypto_${SECRET_NUMBER}`;
|
||||
|
||||
import mod_file as my_mod; // load the script file "crypto_42.rhai" and import it as a module named 'my_mod'
|
||||
// notice that module path names can be dynamically constructed!
|
||||
// any expression that evaluates to a string is acceptable after the 'import' keyword
|
||||
|
||||
lock::encrypt(secret); // use functions defined under the module via '::'
|
||||
|
||||
lock::hash::sha256(key); // sub-modules are also supported
|
||||
|
||||
print(lock::status); // module variables are constants
|
||||
|
||||
lock::status = "off"; // <- runtime error: cannot modify a constant
|
||||
```
|
||||
|
||||
|
||||
```admonish info "Imports are _scoped_"
|
||||
|
||||
[Modules] imported via `import` statements are only accessible inside the relevant block scope.
|
||||
|
||||
~~~js
|
||||
import "hacker" as h; // import module - visible globally
|
||||
|
||||
if secured { // <- new block scope
|
||||
let mod = "crypt";
|
||||
|
||||
import mod + "o" as c; // import module (the path needs not be a constant string)
|
||||
|
||||
let x = c::encrypt(key); // use a function in the module
|
||||
|
||||
h::hack(x); // global module 'h' is visible here
|
||||
} // <- module 'c' disappears at the end of the block scope
|
||||
|
||||
h::hack(something); // this works as 'h' is visible
|
||||
|
||||
c::encrypt(something); // <- this causes a run-time error because
|
||||
// module 'c' is no longer available!
|
||||
|
||||
fn foo(something) {
|
||||
h::hack(something); // <- this also works as 'h' is visible
|
||||
}
|
||||
|
||||
for x in 0..1000 {
|
||||
import "crypto" as c; // <- importing a module inside a loop is a Very Bad Idea™
|
||||
|
||||
c.encrypt(something);
|
||||
}
|
||||
~~~
|
||||
```
|
||||
|
||||
~~~admonish note "Place `import` statements at the top"
|
||||
|
||||
`import` statements can appear anywhere a normal statement can be, but in the vast majority of cases they are
|
||||
usually grouped at the top (beginning) of a script for manageability and visibility.
|
||||
|
||||
It is not advised to deviate from this common practice unless there is a _Very Good Reason™_.
|
||||
|
||||
Especially, do not place an `import` statement within a loop; doing so will repeatedly re-load the
|
||||
same [module] during every iteration of the loop!
|
||||
~~~
|
||||
|
||||
~~~admonish danger "Recursive imports"
|
||||
|
||||
Beware of _import cycles_ – i.e. recursively loading the same [module]. This is a sure-fire way to
|
||||
cause a stack overflow in the [`Engine`], unless stopped by setting a limit for [maximum number of modules].
|
||||
|
||||
For instance, importing itself always causes an infinite recursion:
|
||||
|
||||
```js
|
||||
┌────────────┐
|
||||
│ hello.rhai │
|
||||
└────────────┘
|
||||
|
||||
import "hello" as foo; // import itself - infinite recursion!
|
||||
|
||||
foo::do_something();
|
||||
```
|
||||
|
||||
[Modules] cross-referencing also cause infinite recursion:
|
||||
|
||||
```js
|
||||
┌────────────┐
|
||||
│ hello.rhai │
|
||||
└────────────┘
|
||||
|
||||
import "world" as foo;
|
||||
foo::do_something();
|
||||
|
||||
|
||||
┌────────────┐
|
||||
│ world.rhai │
|
||||
└────────────┘
|
||||
|
||||
import "hello" as bar;
|
||||
bar::do_something_else();
|
||||
```
|
||||
~~~
|
14
rhai_engine/rhaibook/language/modules/index.md
Normal file
14
rhai_engine/rhaibook/language/modules/index.md
Normal file
@@ -0,0 +1,14 @@
|
||||
Modules
|
||||
=======
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_.
|
||||
Modules can be disabled via the [`no_module`] feature.
|
||||
|
||||
A module has the type `Module` and holds a collection of functions, variables, [type iterators] and sub-modules.
|
||||
It may be created entirely from Rust functions, or it may encapsulate a Rhai script together with the functions
|
||||
and variables defined by that script.
|
||||
|
||||
Other scripts can then load this module and use the functions and variables exported as if they were
|
||||
defined inside the same script.
|
122
rhai_engine/rhaibook/language/num-fn.md
Normal file
122
rhai_engine/rhaibook/language/num-fn.md
Normal file
@@ -0,0 +1,122 @@
|
||||
Numeric Functions
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Integer Functions
|
||||
-----------------
|
||||
|
||||
The following standard functions are defined.
|
||||
|
||||
| Function | Not available under | Package | Description |
|
||||
| ----------------------------- | :-----------------: | :--------------------------------------: | ---------------------------------------------------------------- |
|
||||
| `is_odd` method and property | | [`ArithmeticPackage`][built-in packages] | returns `true` if the value is an odd number, otherwise `false` |
|
||||
| `is_even` method and property | | [`ArithmeticPackage`][built-in packages] | returns `true` if the value is an even number, otherwise `false` |
|
||||
| `min` | | [`LogicPackage`][built-in packages] | returns the smaller of two numbers |
|
||||
| `max` | | [`LogicPackage`][built-in packages] | returns the larger of two numbers |
|
||||
| `to_float` | [`no_float`] | [`BasicMathPackage`][built-in packages] | convert the value into `f64` (`f32` under [`f32_float`]) |
|
||||
| `to_decimal` | non-[`decimal`] | [`BasicMathPackage`][built-in packages] | convert the value into [`Decimal`][rust_decimal] |
|
||||
|
||||
|
||||
Signed Numeric Functions
|
||||
------------------------
|
||||
|
||||
The following standard functions are defined in the [`ArithmeticPackage`][built-in packages]
|
||||
(excluded when using a [raw `Engine`]) and operate on `i8`, `i16`, `i32`, `i64`, `f32`, `f64` and
|
||||
[`Decimal`][rust_decimal] (requires [`decimal`]) only.
|
||||
|
||||
| Function | Description |
|
||||
| ----------------------------- | -------------------------------------------------------------- |
|
||||
| `abs` | absolute value |
|
||||
| `sign` | returns (`INT`) −1 if negative, +1 if positive, 0 if zero |
|
||||
| `is_zero` method and property | returns `true` if the value is zero, otherwise `false` |
|
||||
|
||||
|
||||
Floating-Point Functions
|
||||
------------------------
|
||||
|
||||
The following standard functions are defined in the [`BasicMathPackage`][built-in packages]
|
||||
(excluded when using a [raw `Engine`]) and operate on `f64` (`f32` under [`f32_float`]) and
|
||||
[`Decimal`][rust_decimal] (requires [`decimal`]) only.
|
||||
|
||||
| Category | Supports `Decimal` | Functions |
|
||||
| ---------------- | :----------------: | ---------------------------------------------------------------------------------------- |
|
||||
| Trigonometry | yes | `sin`, `cos`, `tan` |
|
||||
| Trigonometry | **no** | `sinh`, `cosh`, `tanh` in radians, `hypot(`_x_`,`_y_`)` |
|
||||
| Arc-trigonometry | **no** | `asin`, `acos`, `atan(`_v_`)`, `atan(`_x_`,`_y_`)`, `asinh`, `acosh`, `atanh` in radians |
|
||||
| Square root | yes | `sqrt` |
|
||||
| Exponential | yes | `exp` (base _e_) |
|
||||
| Logarithmic | yes | `ln` (base _e_) |
|
||||
| Logarithmic | yes | `log` (base 10) |
|
||||
| Logarithmic | **no** | `log(`_x_`,`_base_`)` |
|
||||
| Rounding | yes | `floor`, `ceiling`, `round`, `int`, `fraction` methods and properties |
|
||||
| Conversion | yes | [`to_int`], [`to_decimal`] (requires [`decimal`]), [`to_float`] (not under [`no_float`]) |
|
||||
| Conversion | **no** | `to_degrees`, `to_radians` |
|
||||
| Comparison | yes | `min`, `max` (also inter-operates with integers) |
|
||||
| Testing | **no** | `is_nan`, `is_finite`, `is_infinite` methods and properties |
|
||||
|
||||
|
||||
Decimal Rounding Functions
|
||||
--------------------------
|
||||
|
||||
The following rounding methods are defined in the [`BasicMathPackage`][built-in packages]
|
||||
(excluded when using a [raw `Engine`]) and operate on [`Decimal`][rust_decimal] only,
|
||||
which requires the [`decimal`] feature.
|
||||
|
||||
| Rounding type | Behavior | Methods |
|
||||
| ----------------- | ------------------------------------------- | ------------------------------------------------------------ |
|
||||
| None | | `floor`, `ceiling`, `int`, `fraction` methods and properties |
|
||||
| Banker's rounding | round to integer | `round` method and property |
|
||||
| Banker's rounding | round to specified number of decimal points | `round(`_decimal points_`)` |
|
||||
| Round up | away from zero | `round_up(`_decimal points_`)` |
|
||||
| Round down | towards zero | `round_down(`_decimal points_`)` |
|
||||
| Round half-up | mid-point away from zero | `round_half_up(`_decimal points_`)` |
|
||||
| Round half-down | mid-point towards zero | `round_half_down(`_decimal points_`)` |
|
||||
|
||||
|
||||
Parsing Functions
|
||||
-----------------
|
||||
|
||||
The following standard functions are defined in the [`BasicMathPackage`][built-in packages]
|
||||
(excluded when using a [raw `Engine`]) to parse numbers.
|
||||
|
||||
| Function | No available under | Description |
|
||||
| ----------------- | :------------------------------: | --------------------------------------------------------------------------------------------- |
|
||||
| [`parse_int`] | | converts a [string] to `INT` with an optional radix |
|
||||
| [`parse_float`] | [`no_float`] and non-[`decimal`] | converts a [string] to `FLOAT` ([`Decimal`][rust_decimal] under [`no_float`] and [`decimal`]) |
|
||||
| [`parse_decimal`] | non-[`decimal`] | converts a [string] to [`Decimal`][rust_decimal] |
|
||||
|
||||
|
||||
Formatting Functions
|
||||
--------------------
|
||||
|
||||
The following standard functions are defined in the [`BasicStringPackage`][built-in packages]
|
||||
(excluded when using a [raw `Engine`]) to convert integer numbers into a [string] of hex, octal
|
||||
or binary representations.
|
||||
|
||||
| Function | Description |
|
||||
| ------------- | ------------------------------------ |
|
||||
| [`to_binary`] | converts an integer number to binary |
|
||||
| [`to_octal`] | converts an integer number to octal |
|
||||
| [`to_hex`] | converts an integer number to hex |
|
||||
|
||||
These formatting functions are defined for all available integer numbers – i.e. `INT`, `u8`,
|
||||
`i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `u128` and `i128` unless disabled by feature flags.
|
||||
|
||||
|
||||
Floating-point Constants
|
||||
------------------------
|
||||
|
||||
The following functions return standard mathematical constants.
|
||||
|
||||
| Function | Description |
|
||||
| -------- | ------------------------- |
|
||||
| `PI` | returns the value of π |
|
||||
| `E` | returns the value of _e_ |
|
||||
|
||||
|
||||
Numerical Functions for Scientific Computing
|
||||
--------------------------------------------
|
||||
|
||||
Check out the [`rhai-sci`] crate for more numerical functions.
|
135
rhai_engine/rhaibook/language/num-op.md
Normal file
135
rhai_engine/rhaibook/language/num-op.md
Normal file
@@ -0,0 +1,135 @@
|
||||
Numeric Operators
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Numeric operators generally follow C styles.
|
||||
|
||||
Unary Operators
|
||||
---------------
|
||||
|
||||
| Operator | Description |
|
||||
| :------: | ----------- |
|
||||
| `+` | positive |
|
||||
| `-` | negative |
|
||||
|
||||
```rust
|
||||
let number = +42;
|
||||
|
||||
number = -5;
|
||||
|
||||
number = -5 - +5;
|
||||
|
||||
-(-42) == +42; // two '-' equals '+'
|
||||
// beware: '++' and '--' are reserved symbols
|
||||
```
|
||||
|
||||
Binary Operators
|
||||
----------------
|
||||
|
||||
| Operator | Description | Result type | `INT` | `FLOAT` | [`Decimal`][rust_decimal] |
|
||||
| :-------------------------------: | ---------------------------------------------------------------- | :---------: | :---: | :--------------------: | :-----------------------: |
|
||||
| `+`, `+=` | plus | numeric | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `-`, `-=` | minus | numeric | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `*`, `*=` | multiply | numeric | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `/`, `/=` | divide (integer division if acting on integer types) | numeric | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `%`, `%=` | modulo (remainder) | numeric | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `**`, `**=` | power/exponentiation | numeric | yes | yes, also `FLOAT**INT` | **no** |
|
||||
| `<<`, `<<=` | left bit-shift (if negative number of bits, shift right instead) | numeric | yes | **no** | **no** |
|
||||
| `>>`, `>>=` | right bit-shift (if negative number of bits, shift left instead) | numeric | yes | **no** | **no** |
|
||||
| `&`, `&=` | bit-wise _And_ | numeric | yes | **no** | **no** |
|
||||
| <code>\|</code>, <code>\|=</code> | bit-wise _Or_ | numeric | yes | **no** | **no** |
|
||||
| `^`, `^=` | bit-wise _Xor_ | numeric | yes | **no** | **no** |
|
||||
| `==` | equals to | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `!=` | not equals to | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `>` | greater than | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `>=` | greater than or equals to | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `<` | less than | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `<=` | less than or equals to | `bool` | yes | yes, also with `INT` | yes, also with `INT` |
|
||||
| `..` | exclusive range | [range] | yes | **no** | **no** |
|
||||
| `..=` | inclusive range | [range] | yes | **no** | **no** |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let x = (1 + 2) * (6 - 4) / 2; // arithmetic, with parentheses
|
||||
|
||||
let reminder = 42 % 10; // modulo
|
||||
|
||||
let power = 42 ** 2; // power
|
||||
|
||||
let left_shifted = 42 << 3; // left shift
|
||||
|
||||
let right_shifted = 42 >> 3; // right shift
|
||||
|
||||
let bit_op = 42 | 99; // bit masking
|
||||
```
|
||||
|
||||
|
||||
Floating-Point Interoperates with Integers
|
||||
------------------------------------------
|
||||
|
||||
When one of the operands to a binary arithmetic [operator] is floating-point, it works with `INT` for
|
||||
the other operand and the result is floating-point.
|
||||
|
||||
```rust
|
||||
let x = 41.0 + 1; // 'FLOAT' + 'INT'
|
||||
|
||||
type_of(x) == "f64"; // result is 'FLOAT'
|
||||
|
||||
let x = 21 * 2.0; // 'FLOAT' * 'INT'
|
||||
|
||||
type_of(x) == "f64";
|
||||
|
||||
(x == 42) == true; // 'FLOAT' == 'INT'
|
||||
|
||||
(10 < x) == true; // 'INT' < 'FLOAT'
|
||||
```
|
||||
|
||||
|
||||
Decimal Interoperates with Integers
|
||||
-----------------------------------
|
||||
|
||||
When one of the operands to a binary arithmetic [operator] is [`Decimal`][rust_decimal],
|
||||
it works with `INT` for the other operand and the result is [`Decimal`][rust_decimal].
|
||||
|
||||
```rust
|
||||
let d = parse_decimal("2");
|
||||
|
||||
let x = d + 1; // 'Decimal' + 'INT'
|
||||
|
||||
type_of(x) == "decimal"; // result is 'Decimal'
|
||||
|
||||
let x = 21 * d; // 'Decimal' * 'INT'
|
||||
|
||||
type_of(x) == "decimal";
|
||||
|
||||
(x == 42) == true; // 'Decimal' == 'INT'
|
||||
|
||||
(10 < x) == true; // 'INT' < 'Decimal'
|
||||
```
|
||||
|
||||
|
||||
Unary Before Binary
|
||||
-------------------
|
||||
|
||||
In Rhai, unary operators take [precedence] over binary operators. This is especially important to
|
||||
remember when handling operators such as `**` which in some languages bind tighter than the unary
|
||||
`-` operator.
|
||||
|
||||
```rust
|
||||
-2 + 2 == 0;
|
||||
|
||||
-2 - 2 == -4;
|
||||
|
||||
-2 * 2 == -4;
|
||||
|
||||
-2 / 2 == -1;
|
||||
|
||||
-2 % 2 == 0;
|
||||
|
||||
-2 ** 2 = 4; // means: (-2) ** 2
|
||||
// in some languages this means: -(2 ** 2)
|
||||
```
|
138
rhai_engine/rhaibook/language/numbers.md
Normal file
138
rhai_engine/rhaibook/language/numbers.md
Normal file
@@ -0,0 +1,138 @@
|
||||
Numbers
|
||||
=======
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Integers
|
||||
--------
|
||||
|
||||
```admonish tip.side "Tip: Bit-fields"
|
||||
|
||||
Integers can also be conveniently manipulated as [bit-fields].
|
||||
```
|
||||
|
||||
Integer numbers follow C-style format with support for decimal, binary (`0b`), octal (`0o`) and hex (`0x`) notations.
|
||||
|
||||
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
|
||||
----------------------
|
||||
|
||||
```admonish tip.side "Tip: Notations"
|
||||
|
||||
Both decimal and scientific notations can be used to represent floating-point numbers.
|
||||
```
|
||||
|
||||
Floating-point numbers are also supported if not disabled with [`no_float`].
|
||||
|
||||
The default system floating-point type is `f64` (also aliased to `FLOAT`).
|
||||
It can be turned into `f32` via the [`f32_float`] feature.
|
||||
|
||||
|
||||
`Decimal` Numbers
|
||||
-----------------
|
||||
|
||||
When rounding errors cannot be accepted, such as in financial calculations, the [`decimal`] feature
|
||||
turns on support for the [`Decimal`][rust_decimal] type, which is a fixed-precision floating-point
|
||||
number with no rounding errors.
|
||||
|
||||
|
||||
Number Literals
|
||||
---------------
|
||||
|
||||
`_` separators can be added freely and are ignored within a number – except at the very beginning or right after
|
||||
a decimal point (`.`).
|
||||
|
||||
| Sample | Format | Value type | [`no_float`] | [`no_float`] + [`decimal`] |
|
||||
| ------------------ | ------------------------- | :--------: | :------------: | :------------------------: |
|
||||
| `_123` | _improper separator_ | | | |
|
||||
| `123_345`, `-42` | decimal | `INT` | `INT` | `INT` |
|
||||
| `0o07_76` | octal | `INT` | `INT` | `INT` |
|
||||
| `0xab_cd_ef` | hex | `INT` | `INT` | `INT` |
|
||||
| `0b0101_1001` | binary | `INT` | `INT` | `INT` |
|
||||
| `123._456` | _improper separator_ | | | |
|
||||
| `123_456.78_9` | normal floating-point | `FLOAT` | _syntax error_ | [`Decimal`][rust_decimal] |
|
||||
| `-42.` | ending with decimal point | `FLOAT` | _syntax error_ | [`Decimal`][rust_decimal] |
|
||||
| `123_456_.789e-10` | scientific notation | `FLOAT` | _syntax error_ | [`Decimal`][rust_decimal] |
|
||||
| `.456` | _missing leading `0`_ | | | |
|
||||
| `123.456e_10` | _improper separator_ | | | |
|
||||
| `123.e-10` | _missing decimal `0`_ | | | |
|
||||
|
||||
|
||||
Warning – No Implicit Type Conversions
|
||||
--------------------------------------------
|
||||
|
||||
Unlike most C-like languages, Rhai does _not_ provide implicit type conversions between different
|
||||
numeric types.
|
||||
|
||||
For example, a `u8` is never implicitly converted to `i64` when used as a parameter in a function
|
||||
call or as a comparison operand. `f32` is never implicitly converted to `f64`.
|
||||
|
||||
This is exactly the same as Rust where all numeric types are distinct. Rhai is written in Rust afterall.
|
||||
|
||||
```admonish warning.small
|
||||
|
||||
Integer variables pushed inside a custom [`Scope`] must be the correct type.
|
||||
|
||||
It is extremely easy to mess up numeric types since the Rust default integer type is `i32` while for
|
||||
Rhai it is `i64` (unless under [`only_i32`]).
|
||||
```
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope, INT};
|
||||
|
||||
let engine = Engine::new();
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
scope.push("r", 42); // 'r' is i32 (Rust default integer type)
|
||||
scope.push("x", 42_u8); // 'x' is u8
|
||||
scope.push("y", 42_i64); // 'y' is i64
|
||||
scope.push("z", 42 as INT); // 'z' is i64 (or i32 under 'only_i32')
|
||||
scope.push("f", 42.0_f32); // 'f' is f32
|
||||
|
||||
// Rhai integers are i64 (i32 under 'only_i32')
|
||||
engine.eval::<String>("type_of(42)")? == "i64";
|
||||
|
||||
// false - i32 is never equal to i64
|
||||
engine.eval_with_scope::<bool>(&mut scope, "r == 42")?;
|
||||
|
||||
// false - u8 is never equal to i64
|
||||
engine.eval_with_scope::<bool>(&mut scope, "x == 42")?;
|
||||
|
||||
// true - i64 is equal to i64
|
||||
engine.eval_with_scope::<bool>(&mut scope, "y == 42")?;
|
||||
|
||||
// true - INT is i64
|
||||
engine.eval_with_scope::<bool>(&mut scope, "z == 42")?;
|
||||
|
||||
// false - f32 is never equal to f64
|
||||
engine.eval_with_scope::<bool>(&mut scope, "f == 42.0")?;
|
||||
```
|
||||
|
||||
|
||||
Floating-Point vs. Decimal
|
||||
--------------------------
|
||||
|
||||
~~~admonish tip.side.wide "Tip: `no_float` + `decimal`"
|
||||
|
||||
When both [`no_float`] and [`decimal`] features are turned on, [`Decimal`][rust_decimal] _replaces_
|
||||
the standard floating-point type.
|
||||
|
||||
Floating-point number literals in scripts parse to [`Decimal`][rust_decimal] values.
|
||||
~~~
|
||||
|
||||
[`Decimal`][rust_decimal] (enabled via the [`decimal`] feature) represents a fixed-precision
|
||||
floating-point number which is popular with financial calculations and other usage scenarios where
|
||||
round-off errors are not acceptable.
|
||||
|
||||
[`Decimal`][rust_decimal] takes up more space (16 bytes) than a standard `FLOAT` (4-8 bytes) and is
|
||||
much slower in calculations due to the lack of CPU hardware support. Use it only when necessary.
|
||||
|
||||
For most situations, the standard floating-point number type `FLOAT` (`f64` or `f32` with
|
||||
[`f32_float`]) is enough and is faster than [`Decimal`][rust_decimal].
|
||||
|
||||
It is possible to use both `FLOAT` and [`Decimal`][rust_decimal] together with just the [`decimal`] feature
|
||||
– use [`parse_decimal`] or [`to_decimal`] to create a [`Decimal`][rust_decimal] value.
|
78
rhai_engine/rhaibook/language/object-maps-missing-prop.md
Normal file
78
rhai_engine/rhaibook/language/object-maps-missing-prop.md
Normal file
@@ -0,0 +1,78 @@
|
||||
Non-Existent Property Handling for Object Maps
|
||||
==============================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
[`Engine::on_map_missing_property`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_map_missing_property
|
||||
[`Target`]: https://docs.rs/rhai/latest/rhai/enum.Target.html
|
||||
|
||||
|
||||
~~~admonish warning.small "Requires `internals`"
|
||||
|
||||
This is an advanced feature that requires the [`internals`] feature to be enabled.
|
||||
~~~
|
||||
|
||||
Normally, when a property is accessed from an [object map] that does not exist, [`()`] is returned.
|
||||
Via [`Engine:: set_fail_on_invalid_map_property`][options], it is possible to make this an error
|
||||
instead.
|
||||
|
||||
Other than that, it is possible to completely control this behavior via a special callback function
|
||||
registered into an [`Engine`] via `on_map_missing_property`.
|
||||
|
||||
Using this callback, for instance, it is simple to instruct Rhai to create a new property in the
|
||||
[object map] on the fly, possibly with a default value, when a non-existent property is accessed.
|
||||
|
||||
|
||||
Function Signature
|
||||
------------------
|
||||
|
||||
The function signature passed to [`Engine::on_map_missing_property`] takes the following form.
|
||||
|
||||
> ```rust
|
||||
> Fn(map: &mut Map, prop: &str, context: EvalContext) -> Result<Target, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :----------------------: | ----------------------------------- |
|
||||
| `map` | [`&mut Map`][object map] | the [object map] being accessed |
|
||||
| `prop` | `&str` | name of the property being accessed |
|
||||
| `context` | [`EvalContext`] | the current _evaluation context_ |
|
||||
|
||||
### Return value
|
||||
|
||||
The return value is `Result<Target, Box<EvalAltResult>>`.
|
||||
|
||||
[`Target`] is an advanced type, available only under the [`internals`] feature, that represents a
|
||||
_reference_ to a [`Dynamic`] value.
|
||||
|
||||
It can be used to point to a particular value within the [object map].
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
engine.on_map_missing_property(|map, prop, context| {
|
||||
match prop {
|
||||
"x" => {
|
||||
// The object-map can be modified in place
|
||||
map.insert("y".into(), (42_i64).into());
|
||||
|
||||
// Return a mutable reference to an element
|
||||
let value_ref = map.get_mut("y").unwrap();
|
||||
Ok(value_ref.into())
|
||||
}
|
||||
"z" => {
|
||||
// Return a temporary value (not a reference)
|
||||
let value = Dynamic::from(100_i64);
|
||||
Ok(value.into())
|
||||
}
|
||||
// Return the standard property-not-found error
|
||||
_ => Err(EvalAltResult::ErrorPropertyNotFound(
|
||||
prop.to_string(), Position::NONE
|
||||
).into()),
|
||||
}
|
||||
});
|
||||
```
|
43
rhai_engine/rhaibook/language/object-maps-oop.md
Normal file
43
rhai_engine/rhaibook/language/object-maps-oop.md
Normal file
@@ -0,0 +1,43 @@
|
||||
Special Support for OOP via Object Maps
|
||||
=======================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish info.side "See also"
|
||||
|
||||
See the pattern on [_Simulating Object-Oriented Programming_][OOP] for more details.
|
||||
```
|
||||
|
||||
[Object maps] can be used to simulate [object-oriented programming (OOP)][OOP] by storing data
|
||||
as properties and methods as properties holding [function pointers].
|
||||
|
||||
If an [object map]'s property holds a [function pointer], the property can simply be called like
|
||||
a normal method in method-call syntax.
|
||||
|
||||
This is a _short-hand_ to avoid the more verbose syntax of using the `call` function keyword.
|
||||
|
||||
When a property holding a [function pointer] or a [closure] is called like a method, it is replaced
|
||||
as a method call on the [object map] itself.
|
||||
|
||||
```rust
|
||||
let obj = #{
|
||||
data: 40,
|
||||
action: || this.data += x // 'action' holds a closure
|
||||
};
|
||||
|
||||
obj.action(2); // calls the function pointer with 'this' bound to 'obj'
|
||||
|
||||
obj.call(obj.action, 2); // <- the above de-sugars to this
|
||||
|
||||
obj.data == 42;
|
||||
|
||||
// To achieve the above with normal function pointer call will fail.
|
||||
|
||||
fn do_action(map, x) { map.data += x; } // 'map' is a copy
|
||||
|
||||
obj.action = do_action; // <- de-sugars to 'Fn("do_action")'
|
||||
|
||||
obj.action.call(obj, 2); // a copy of 'obj' is passed by value
|
||||
|
||||
obj.data == 42; // 'obj.data' is not changed
|
||||
```
|
309
rhai_engine/rhaibook/language/object-maps.md
Normal file
309
rhai_engine/rhaibook/language/object-maps.md
Normal file
@@ -0,0 +1,309 @@
|
||||
Object Maps
|
||||
===========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish tip.side "Safety"
|
||||
|
||||
Always limit the [maximum size of object maps].
|
||||
```
|
||||
|
||||
Object maps are hash dictionaries. Properties are all [`Dynamic`] and can be freely added and retrieved.
|
||||
|
||||
The Rust type of a Rhai object map is `rhai::Map`.
|
||||
Currently it is an alias to `BTreeMap<SmartString, Dynamic>`.
|
||||
|
||||
[`type_of()`] an object map returns `"map"`.
|
||||
|
||||
Object maps are disabled via the [`no_object`] feature.
|
||||
|
||||
~~~admonish tip "Tip: Object maps are _FAST_"
|
||||
|
||||
Normally, when [properties][getters/setters] are accessed, copies of the data values are made.
|
||||
This is normally slow.
|
||||
|
||||
Object maps have special treatment – properties are accessed via _references_, meaning that
|
||||
no copies of data values are made.
|
||||
|
||||
This makes object map access fast, especially when deep within a properties chain.
|
||||
|
||||
```rust
|
||||
// 'obj' is a normal custom type
|
||||
let x = obj.a.b.c.d;
|
||||
|
||||
// The above is equivalent to:
|
||||
let a_value = obj.a; // temp copy of 'a'
|
||||
let b_value = a_value.b; // temp copy of 'b'
|
||||
let c_value = b_value.c; // temp copy of 'c'
|
||||
let d_value = c_value.d; // temp copy of 'd'
|
||||
let x = d_value;
|
||||
|
||||
// 'map' is an object map
|
||||
let x = map.a.b.c.d; // direct access to 'd'
|
||||
// 'a', 'b' and 'c' are not copied
|
||||
|
||||
map.a.b.c.d = 42; // directly modifies 'd' in 'a', 'b' and 'c'
|
||||
// no copy of any property value is made
|
||||
|
||||
map.a.b.c.d.calc(); // directly calls 'calc' on 'd'
|
||||
// no copy of any property value is made
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish question.small "TL;DR: Why `SmartString`?"
|
||||
|
||||
[`SmartString`] is used because most object map properties are short (at least shorter than 23 characters)
|
||||
and ASCII-based, so they can usually be stored inline without incurring the cost of an allocation.
|
||||
~~~
|
||||
|
||||
~~~admonish question.small "TL;DR: Why `BTreeMap` and not `HashMap`?"
|
||||
|
||||
The vast majority of object maps contain just a few properties.
|
||||
|
||||
`BTreeMap` performs significantly better than `HashMap` when the number of entries is small.
|
||||
~~~
|
||||
|
||||
|
||||
Literal Syntax
|
||||
--------------
|
||||
|
||||
Object map literals are built within braces `#{` ... `}` with _name_`:`_value_ pairs separated by
|
||||
commas `,`:
|
||||
|
||||
> `#{` _property_ `:` _value_`,` ... `,` _property_ `:` _value_ `}`
|
||||
>
|
||||
> `#{` _property_ `:` _value_`,` ... `,` _property_ `:` _value_ `,` `}` `// trailing comma is OK`
|
||||
|
||||
The property _name_ can be a simple identifier following the same naming rules as [variables],
|
||||
or a [string literal][literals] without interpolation.
|
||||
|
||||
|
||||
Property Access Syntax
|
||||
----------------------
|
||||
|
||||
### Dot notation
|
||||
|
||||
The _dot notation_ allows only property names that follow the same naming rules as [variables].
|
||||
|
||||
> _object_ `.` _property_
|
||||
|
||||
### Elvis notation
|
||||
|
||||
The [_Elvis notation_][elvis] is similar to the _dot notation_ except that it returns [`()`] if the object
|
||||
itself is [`()`].
|
||||
|
||||
> `// returns () if object is ()`
|
||||
> _object_ `?.` _property_
|
||||
>
|
||||
> `// no action if object is ()`
|
||||
> _object_ `?.` _property_ `=` _value_ `;`
|
||||
|
||||
### Index notation
|
||||
|
||||
The _index notation_ allows setting/getting properties of arbitrary names (even the empty [string]).
|
||||
|
||||
> _object_ `[` _property_ `]`
|
||||
|
||||
|
||||
Handle Non-Existent Properties
|
||||
------------------------------
|
||||
|
||||
Trying to read a non-existent property returns [`()`] instead of causing an error.
|
||||
|
||||
This is similar to JavaScript where accessing a non-existent property returns `undefined`.
|
||||
|
||||
```rust
|
||||
let map = #{ foo: 42 };
|
||||
|
||||
// Regular property access
|
||||
let x = map.foo; // x == 42
|
||||
|
||||
// Non-existent property
|
||||
let x = map.bar; // x == ()
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Force error"
|
||||
|
||||
It is possible to force Rhai to return an `EvalAltResult:: ErrorPropertyNotFound` via
|
||||
[`Engine:: set_fail_on_invalid_map_property`][options].
|
||||
```
|
||||
|
||||
```admonish tip.small "Advanced tip: Override standard behavior"
|
||||
|
||||
For fine-tuned control on what happens when a non-existent property is accessed,
|
||||
see [_Non-Existent Property Handling for Object Maps_](object-maps-missing-prop.md).
|
||||
```
|
||||
|
||||
### Check for property existence
|
||||
|
||||
Use the [`in`] operator to check whether a property exists in an object-map.
|
||||
|
||||
```rust
|
||||
let map = #{ foo: 42 };
|
||||
|
||||
"foo" in map == true;
|
||||
|
||||
"bar" in map == false;
|
||||
```
|
||||
|
||||
### Short-circuit non-existent property access
|
||||
|
||||
Use the [_Elvis operator_][elvis] (`?.`) to short-circuit further processing if the object is [`()`].
|
||||
|
||||
```rust
|
||||
x.a.b.foo(); // <- error if 'x', 'x.a' or 'x.a.b' is ()
|
||||
|
||||
x.a.b = 42; // <- error if 'x' or 'x.a' is ()
|
||||
|
||||
x?.a?.b?.foo(); // <- ok! returns () if 'x', 'x.a' or 'x.a.b' is ()
|
||||
|
||||
x?.a?.b = 42; // <- ok even if 'x' or 'x.a' is ()
|
||||
```
|
||||
|
||||
### Default property value
|
||||
|
||||
Using the [null-coalescing operator](logic.md#null-coalescing-operator) to give non-existent
|
||||
properties default values.
|
||||
|
||||
```rust
|
||||
let map = #{ foo: 42 };
|
||||
|
||||
// Regular property access
|
||||
let x = map.foo; // x == 42
|
||||
|
||||
// Non-existent property
|
||||
let x = map.bar; // x == ()
|
||||
|
||||
// Default value for property
|
||||
let x = map.bar ?? 42; // x == 42
|
||||
```
|
||||
|
||||
|
||||
Built-in Functions
|
||||
------------------
|
||||
|
||||
The following methods (defined in the [`BasicMapPackage`][built-in packages] but excluded when using
|
||||
a [raw `Engine`]) operate on object maps.
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| --------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `get` | property name | gets a copy of the value of a certain property ([`()`] if the property does not exist); behavior is not affected by [`Engine::fail_on_invalid_map_property`][options] |
|
||||
| `set` | <ol><li>property name</li><li>new element</li></ol> | sets a certain property to a new value (property is added if not already exists) |
|
||||
| `len` | _none_ | returns the number of properties |
|
||||
| `is_empty` | _none_ | returns `true` if the object map is empty |
|
||||
| `clear` | _none_ | empties the object map |
|
||||
| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) |
|
||||
| `+=` operator, `mixin` | second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) |
|
||||
| `+` operator | <ol><li>first object map</li><li>second object map</li></ol> | merges the first object map with the second |
|
||||
| `==` operator | <ol><li>first object map</li><li>second object map</li></ol> | are the two object maps the same (elements compared with the `==` operator, if defined)? |
|
||||
| `!=` operator | <ol><li>first object map</li><li>second object map</li></ol> | are the two object maps different (elements compared with the `==` operator, if defined)? |
|
||||
| `fill_with` | second object map | adds in all properties of the second object map that do not exist in the object map |
|
||||
| `contains`, [`in`] operator | property name | does the object map contain a property of a particular name? |
|
||||
| `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] |
|
||||
| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |
|
||||
| `drain` | [function pointer] to predicate (usually a [closure]) | removes all elements (returning them) that return `true` when called with the predicate function taking the following parameters:<ol><li>key</li><li>_(optional)_ object map element (if omitted, the object map element is bound to `this`)</li></ol> |
|
||||
| `retain` | [function pointer] to predicate (usually a [closure]) | removes all elements (returning them) that do not return `true` when called with the predicate function taking the following parameters:<ol><li>key</li><li>_(optional)_ object map element (if omitted, the object map element is bound to `this`)</li></ol> |
|
||||
| `filter` | [function pointer] to predicate (usually a [closure]) | constructs a object map with all elements that return `true` when called with the predicate function taking the following parameters:<ol><li>key</li><li>_(optional)_ object map element (if omitted, the object map element is bound to `this`)</li></ol> |
|
||||
| `to_json` | _none_ | returns a JSON representation of the object map ([`()`] is mapped to `null`, all other data types must be supported by JSON) |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let y = #{ // object map literal with 3 properties
|
||||
a: 1,
|
||||
bar: "hello",
|
||||
"baz!$@": 123.456, // like JavaScript, you can use any string as property names...
|
||||
"": false, // even the empty string!
|
||||
|
||||
`hello`: 999, // literal strings are also OK
|
||||
|
||||
a: 42, // <- syntax error: duplicated property name
|
||||
|
||||
`a${2}`: 42, // <- syntax error: property name cannot have string interpolation
|
||||
};
|
||||
|
||||
y.a = 42; // access via dot notation
|
||||
y.a == 42;
|
||||
|
||||
y.baz!$@ = 42; // <- syntax error: only proper variable names allowed in dot notation
|
||||
y."baz!$@" = 42; // <- syntax error: strings not allowed in dot notation
|
||||
y["baz!$@"] = 42; // access via index notation is OK
|
||||
|
||||
"baz!$@" in y == true; // use 'in' to test if a property exists in the object map
|
||||
("z" in y) == false;
|
||||
|
||||
ts.obj = y; // object maps can be assigned completely (by value copy)
|
||||
let foo = ts.list.a;
|
||||
foo == 42;
|
||||
|
||||
let foo = #{ a:1, }; // trailing comma is OK
|
||||
|
||||
let foo = #{ a:1, b:2, c:3 }["a"];
|
||||
let foo = #{ a:1, b:2, c:3 }.a;
|
||||
foo == 1;
|
||||
|
||||
fn abc() {
|
||||
#{ a:1, b:2, c:3 } // a function returning an object map
|
||||
}
|
||||
|
||||
let foo = abc().b;
|
||||
foo == 2;
|
||||
|
||||
let foo = y["a"];
|
||||
foo == 42;
|
||||
|
||||
y.contains("a") == true;
|
||||
y.contains("xyz") == false;
|
||||
|
||||
y.xyz == (); // a non-existent property returns '()'
|
||||
y["xyz"] == ();
|
||||
|
||||
y.len == (); // an object map has no property getter function
|
||||
y.len() == 3; // method calls are OK
|
||||
|
||||
y.remove("a") == 1; // remove property
|
||||
|
||||
y.len() == 2;
|
||||
y.contains("a") == false;
|
||||
|
||||
for name in y.keys() { // get an array of all the property names via 'keys'
|
||||
print(name);
|
||||
}
|
||||
|
||||
for val in y.values() { // get an array of all the property values via 'values'
|
||||
print(val);
|
||||
}
|
||||
|
||||
y.clear(); // empty the object map
|
||||
|
||||
y.len() == 0;
|
||||
```
|
||||
|
||||
|
||||
No Support for Property Getters
|
||||
-------------------------------
|
||||
|
||||
In order not to affect the speed of accessing properties in an object map, new
|
||||
[property getters][getters/setters] cannot be registered because they conflict with the syntax of
|
||||
property access.
|
||||
|
||||
A [property getter][getters/setters] function registered via `Engine::register_get`, for example,
|
||||
for a `Map` will never be found – instead, the property will be looked up in the object map.
|
||||
|
||||
Properties should be registered as _methods_ instead:
|
||||
|
||||
```rust
|
||||
map.len // access property 'len', returns '()' if not found
|
||||
|
||||
map.len() // 'len' method - returns the number of properties
|
||||
|
||||
map.keys // access property 'keys', returns '()' if not found
|
||||
|
||||
map.keys() // 'keys' method - returns array of all property names
|
||||
|
||||
map.values // access property 'values', returns '()' if not found
|
||||
|
||||
map.values() // 'values' method - returns array of all property values
|
||||
```
|
36
rhai_engine/rhaibook/language/overload.md
Normal file
36
rhai_engine/rhaibook/language/overload.md
Normal file
@@ -0,0 +1,36 @@
|
||||
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_ 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.
|
||||
|
||||
```js
|
||||
fn foo(x, y, z) {
|
||||
print(`Three!!! ${x}, ${y}, ${z}`);
|
||||
}
|
||||
fn foo(x) {
|
||||
print(`One! ${x}`);
|
||||
}
|
||||
fn foo(x, y) {
|
||||
print(`Two! ${x}, ${y}`);
|
||||
}
|
||||
fn foo() {
|
||||
print("None.");
|
||||
}
|
||||
fn foo(x) { // <- overwrites previous definition
|
||||
print(`HA! NEW ONE! ${x}`);
|
||||
}
|
||||
|
||||
foo(1,2,3); // prints "Three!!! 1,2,3"
|
||||
|
||||
foo(42); // prints "HA! NEW ONE! 42"
|
||||
|
||||
foo(1,2); // prints "Two!! 1,2"
|
||||
|
||||
foo(); // prints "None."
|
||||
```
|
92
rhai_engine/rhaibook/language/print-debug.md
Normal file
92
rhai_engine/rhaibook/language/print-debug.md
Normal file
@@ -0,0 +1,92 @@
|
||||
`print` and `debug`
|
||||
===================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
[`Engine::on_print`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_print
|
||||
[`Engine::on_debug`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_debug
|
||||
|
||||
|
||||
The `print` and `debug` functions default to printing to `stdout`, with `debug` using standard debug formatting.
|
||||
|
||||
```js
|
||||
print("hello"); // prints "hello" to stdout
|
||||
|
||||
print(1 + 2 + 3); // prints "6" to stdout
|
||||
|
||||
let x = 42;
|
||||
|
||||
print(`hello${x}`); // prints "hello42" to stdout
|
||||
|
||||
debug("world!"); // prints "world!" to stdout using debug formatting
|
||||
```
|
||||
|
||||
|
||||
Override `print` and `debug` with Callback Functions
|
||||
----------------------------------------------------
|
||||
|
||||
When embedding Rhai into an application, it is usually necessary to trap `print` and `debug` output
|
||||
(for logging into a tracking log, for example) with the [`Engine::on_print`] and [`Engine::on_debug`] methods.
|
||||
|
||||
```rust
|
||||
// Any function or closure that takes an '&str' argument can be used to override 'print'.
|
||||
engine.on_print(|x| println!("hello: {x}"));
|
||||
|
||||
// Any function or closure that takes a '&str', an 'Option<&str>' and a 'Position' argument
|
||||
// can be used to override 'debug'.
|
||||
engine.on_debug(|x, src, pos| {
|
||||
let src = src.unwrap_or("unknown");
|
||||
println!("DEBUG of {src} at {pos:?}: {s}")
|
||||
});
|
||||
|
||||
// Example: quick-'n-dirty logging
|
||||
let logbook = Arc::new(RwLock::new(Vec::<String>::new()));
|
||||
|
||||
// Redirect print/debug output to 'log'
|
||||
let log = logbook.clone();
|
||||
engine.on_print(move |s| {
|
||||
let entry = format!("entry: {}", s);
|
||||
log.write().unwrap().push(entry);
|
||||
});
|
||||
|
||||
let log = logbook.clone();
|
||||
engine.on_debug(move |s, src, pos| {
|
||||
let src = src.unwrap_or("unknown");
|
||||
let entry = format!("DEBUG of {src} at {pos:?}: {s}");
|
||||
log.write().unwrap().push(entry);
|
||||
});
|
||||
|
||||
// Evaluate script
|
||||
engine.run(script)?;
|
||||
|
||||
// 'logbook' captures all the 'print' and 'debug' output
|
||||
for entry in logbook.read().unwrap().iter() {
|
||||
println!("{entry}");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
`on_debug` Callback Signature
|
||||
-----------------------------
|
||||
|
||||
The function signature passed to [`Engine::on_debug]` takes the following form.
|
||||
|
||||
> ```rust
|
||||
> Fn(text: &str, source: Option<&str>, pos: Position)
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :------------: | --------------------------------------------------------------- |
|
||||
| `text` | `&str` | text to display |
|
||||
| `source` | `Option<&str>` | source of the current evaluation, if any |
|
||||
| `pos` | `Position` | position (line number and character offset) of the `debug` call |
|
||||
|
||||
The _source_ of a script evaluation is any text string provided to an [`AST`] via `AST::set_source`.
|
||||
|
||||
```admonish tip.small
|
||||
|
||||
If a [module] is loaded via an [`import`] statement, then the _source_ of functions defined within
|
||||
the module will be the module's _path_.
|
||||
```
|
205
rhai_engine/rhaibook/language/ranges.md
Normal file
205
rhai_engine/rhaibook/language/ranges.md
Normal file
@@ -0,0 +1,205 @@
|
||||
Ranges
|
||||
======
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Syntax
|
||||
------
|
||||
|
||||
Numeric ranges can be constructed by the `..` (exclusive) or `..=` (inclusive) operators.
|
||||
|
||||
### Exclusive range
|
||||
|
||||
> _start_ `..` _end_
|
||||
|
||||
An _exclusive_ range does not include the last (i.e. "end") value.
|
||||
|
||||
The Rust type of an exclusive range is `std::ops::Range<INT>`.
|
||||
|
||||
[`type_of()`] an exclusive range returns `"range"`.
|
||||
|
||||
### Inclusive range
|
||||
|
||||
> _start_ `..=` _end_
|
||||
|
||||
An _inclusive_ range includes the last (i.e. "end") value.
|
||||
|
||||
The Rust type of an inclusive range is `std::ops::RangeInclusive<INT>`.
|
||||
|
||||
[`type_of()`] an inclusive range returns `"range="`.
|
||||
|
||||
|
||||
Usage Scenarios
|
||||
---------------
|
||||
|
||||
Ranges are commonly used in the following scenarios.
|
||||
|
||||
| Scenario | Example |
|
||||
| -------------------------- | --------------------------------------- |
|
||||
| [`for`] statements | `for n in 0..100 { ... }` |
|
||||
| [`in`] expressions | `if n in 0..100 { ... }` |
|
||||
| [`switch`] expressions | `switch n { 0..100 => ... }` |
|
||||
| [Bit-fields] access | `let x = n[2..6];` |
|
||||
| Bits iteration | `for bit in n.bits(2..=9) { ... }` |
|
||||
| [Array] range-based APIs | `array.extract(2..8)` |
|
||||
| [BLOB] range-based APIs | `blob.parse_le_int(4..8)` |
|
||||
| [String] range-based APIs | `string.sub_string(4..=12)` |
|
||||
| [Characters] iteration | `for ch in string.bits(4..=12) { ... }` |
|
||||
| [Custom types] | `my_obj.action(3..=15, "foo");` |
|
||||
|
||||
|
||||
Use as Parameter Type
|
||||
---------------------
|
||||
|
||||
Native Rust functions that take parameters of type `std::ops::Range<INT>` or
|
||||
`std::ops::RangeInclusive<INT>`, when registered into an [`Engine`], accept ranges as arguments.
|
||||
|
||||
```admonish warning.small "Different types"
|
||||
|
||||
`..` (exclusive range) and `..=` (inclusive range) are _different_ types to Rhai
|
||||
and they do not interoperate.
|
||||
|
||||
Two different versions of the same API must be registered to handle both range styles.
|
||||
```
|
||||
|
||||
```rust
|
||||
use std::ops::{Range, RangeInclusive};
|
||||
|
||||
/// The actual work function
|
||||
fn do_work(obj: &mut TestStruct, from: i64, to: i64, inclusive: bool) {
|
||||
...
|
||||
}
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine
|
||||
/// Version of API that accepts an exclusive range
|
||||
.register_fn("do_work", |obj: &mut TestStruct, range: Range<i64>|
|
||||
do_work(obj, range.start, range.end, false)
|
||||
)
|
||||
/// Version of API that accepts an inclusive range
|
||||
.register_fn("do_work", |obj: &mut TestStruct, range: RangeInclusive<i64>|
|
||||
do_work(obj, range.start(), range.end(), true)
|
||||
);
|
||||
|
||||
engine.run(
|
||||
"
|
||||
let obj = new_ts();
|
||||
|
||||
obj.do_work(0..12); // use exclusive range
|
||||
|
||||
obj.do_work(0..=11); // use inclusive range
|
||||
")?;
|
||||
```
|
||||
|
||||
### Indexers Using Ranges
|
||||
|
||||
[Indexers] commonly use ranges as parameters.
|
||||
|
||||
```rust
|
||||
use std::ops::{Range, RangeInclusive};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine
|
||||
/// Version of indexer that accepts an exclusive range
|
||||
.register_indexer_get_set(
|
||||
|obj: &mut TestStruct, range: Range<i64>| -> bool { ... },
|
||||
|obj: &mut TestStruct, range: Range<i64>, value: bool| { ... },
|
||||
)
|
||||
/// Version of indexer that accepts an inclusive range
|
||||
.register_indexer_get_set(
|
||||
|obj: &mut TestStruct, range: RangeInclusive<i64>| -> bool { ... },
|
||||
|obj: &mut TestStruct, range: RangeInclusive<i64>, value: bool| { ... },
|
||||
);
|
||||
|
||||
engine.run(
|
||||
"
|
||||
let obj = new_ts();
|
||||
|
||||
let x = obj[0..12]; // use exclusive range
|
||||
|
||||
obj[0..=11] = !x; // use inclusive range
|
||||
")?;
|
||||
```
|
||||
|
||||
Built-in Functions
|
||||
------------------
|
||||
|
||||
The following methods (mostly defined in the [`BasicIteratorPackage`][built-in packages] but
|
||||
excluded when using a [raw `Engine`]) operate on ranges.
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ---------------------------------- | :-------------: | --------------------------------------------- |
|
||||
| `start` method and property | | beginning of the range |
|
||||
| `end` method and property | | end of the range |
|
||||
| `contains`, [`in`] operator | number to check | does this range contain the specified number? |
|
||||
| `is_empty` method and property | | returns `true` if the range contains no items |
|
||||
| `is_inclusive` method and property | | is the range inclusive? |
|
||||
| `is_exclusive` method and property | | is the range exclusive? |
|
||||
|
||||
|
||||
TL;DR
|
||||
-----
|
||||
|
||||
```admonish question "What happened to the _open-ended_ ranges?"
|
||||
|
||||
Rust has _open-ended_ ranges, such as `start..`, `..end` and `..=end`. They are not available in Rhai.
|
||||
|
||||
They are not needed because Rhai can [overload][function overloading] functions.
|
||||
|
||||
Typically, an API accepting ranges as parameters would have equivalent versions that accept a
|
||||
starting position and a length (the standard `start + len` pair), as well as a versions that accept
|
||||
only the starting position (the length assuming to the end).
|
||||
|
||||
In fact, usually all versions redirect to a call to one single version.
|
||||
|
||||
For example, a naive implementation of the `extract` method for [arrays] (without any error handling)
|
||||
would look like:
|
||||
|
||||
~~~rust
|
||||
use std::ops::{Range, RangeInclusive};
|
||||
|
||||
// Version with exclusive range
|
||||
#[rhai_fn(name = "extract", pure)]
|
||||
pub fn extract_range(array: &mut Array, range: Range<i64>) -> Array {
|
||||
array[range].to_vec()
|
||||
}
|
||||
// Version with inclusive range
|
||||
#[rhai_fn(name = "extract", pure)]
|
||||
pub fn extract_range2(array: &mut Array, range: RangeInclusive<i64>) -> Array {
|
||||
extract_range(array, range.start()..range.end() + 1)
|
||||
}
|
||||
// Version with start
|
||||
#[rhai_fn(name = "extract", pure)]
|
||||
pub fn extract_to_end(array: &mut Array, start: i64) -> Array {
|
||||
extract_range(array, start..start + array.len())
|
||||
}
|
||||
// Version with start+len
|
||||
#[rhai_fn(name = "extract", pure)]
|
||||
pub fn extract(array: &mut Array, start: i64, len: i64) -> Array {
|
||||
extract_range(array, start..start + len)
|
||||
}
|
||||
~~~
|
||||
|
||||
Therefore, there should always be a function that can do what open-ended ranges are intended for.
|
||||
|
||||
The left-open form (i.e. `..end` and `..=end`) is trivially replaced by using zero as the starting
|
||||
position with a length that corresponds to the end position (for `..end`).
|
||||
|
||||
The right-open form (i.e. `start..`) is trivially replaced by the version taking a single starting position.
|
||||
|
||||
~~~rust
|
||||
let x = [1, 2, 3, 4, 5];
|
||||
|
||||
x.extract(0..3); // normal range argument
|
||||
// copies 'x' from positions 0-2
|
||||
|
||||
x.extract(2); // copies 'x' from position 2 onwards
|
||||
// equivalent to '2..'
|
||||
|
||||
x.extract(0, 2); // copies 'x' from beginning for 2 items
|
||||
// equivalent to '..2'
|
||||
~~~
|
||||
```
|
50
rhai_engine/rhaibook/language/return.md
Normal file
50
rhai_engine/rhaibook/language/return.md
Normal file
@@ -0,0 +1,50 @@
|
||||
Return Value
|
||||
============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
`return`
|
||||
--------
|
||||
|
||||
The `return` statement is used to immediately stop evaluation and exist the current context
|
||||
(typically a [function] call) yielding a _return value_.
|
||||
|
||||
```rust
|
||||
return; // equivalent to return ();
|
||||
|
||||
return 123 + 456; // returns 579
|
||||
```
|
||||
|
||||
A `return` statement at _global_ level stops the entire script evaluation,
|
||||
the return value is taken as the result of the script evaluation.
|
||||
|
||||
A `return` statement inside a [function call][function] exits with a return value to the caller.
|
||||
|
||||
|
||||
`exit`
|
||||
------
|
||||
|
||||
Similar to the `return` statement, the `exit` _function_ is used to immediately stop evaluation,
|
||||
but it does so regardless of where it is called from, even deep inside nested function calls.
|
||||
|
||||
```rust
|
||||
fn foo() {
|
||||
exit(42); // exit with result 42
|
||||
}
|
||||
fn bar() {
|
||||
foo();
|
||||
}
|
||||
fn baz() {
|
||||
bar();
|
||||
}
|
||||
|
||||
let x = baz(); // exits with result 42
|
||||
|
||||
print(x); // <- this is never run
|
||||
```
|
||||
|
||||
The `exit` function is defined in the [`LanguageCorePackage`][built-in packages] but excluded when using a [raw `Engine`].
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| -------- | ------------------------- | ------------------------------------------------------------------------ |
|
||||
| `exit` | result value _(optional)_ | immediately terminate script evaluation (default result value is [`()`]) |
|
52
rhai_engine/rhaibook/language/shadow.md
Normal file
52
rhai_engine/rhaibook/language/shadow.md
Normal file
@@ -0,0 +1,52 @@
|
||||
Variable Shadowing
|
||||
==================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
In Rhai, new [variables] automatically _shadow_ existing ones of the same name. There is no error.
|
||||
|
||||
This behavior is consistent with Rust.
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
let y = 123;
|
||||
|
||||
print(x); // prints 42
|
||||
|
||||
let x = 88; // <- 'x' is shadowed here
|
||||
|
||||
// At this point, it is no longer possible to access the
|
||||
// original 'x' on the first line...
|
||||
|
||||
print(x); // prints 88
|
||||
|
||||
let x = 0; // <- 'x' is shadowed again
|
||||
|
||||
// At this point, it is no longer possible to access both
|
||||
// previously-defined 'x'...
|
||||
|
||||
print(x); // prints 0
|
||||
|
||||
{
|
||||
let x = 999; // <- 'x' is shadowed in a block
|
||||
|
||||
print(x); // prints 999
|
||||
}
|
||||
|
||||
print(x); // prints 0 - shadowing within the block goes away
|
||||
|
||||
print(y); // prints 123 - 'y' is not shadowed
|
||||
```
|
||||
|
||||
|
||||
~~~admonish tip "Tip: Disable shadowing"
|
||||
|
||||
Set [`Engine::set_allow_shadowing`][options] to `false` to turn [variables] shadowing off.
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
|
||||
let x = 123; // <- syntax error: variable 'x' already defined
|
||||
// when variables shadowing is disallowed
|
||||
```
|
||||
~~~
|
58
rhai_engine/rhaibook/language/statement-expression.md
Normal file
58
rhai_engine/rhaibook/language/statement-expression.md
Normal file
@@ -0,0 +1,58 @@
|
||||
Statement Expression
|
||||
====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish warning.side "Differs from Rust"
|
||||
|
||||
This is different from Rust where, if the last statement is terminated by a semicolon, the block's
|
||||
return value defaults to `()`.
|
||||
```
|
||||
|
||||
Like Rust, a statement can be used anywhere where an _expression_ is expected.
|
||||
|
||||
These are called, for lack of a more creative name, "statement expressions."
|
||||
|
||||
The _last_ statement of a statements block is _always_ the block's return value when used as a statement,
|
||||
_regardless_ of whether it is terminated by a semicolon or not.
|
||||
|
||||
If the last statement has no return value (e.g. variable definitions, assignments) then it is
|
||||
assumed to be [`()`].
|
||||
|
||||
```rust
|
||||
let x = {
|
||||
let foo = calc_something();
|
||||
let bar = foo + baz;
|
||||
bar.further_processing(); // <- this is the return value
|
||||
}; // <- semicolon is needed here...
|
||||
|
||||
// The above is equivalent to:
|
||||
let result;
|
||||
{
|
||||
let foo = calc_something();
|
||||
let bar = foo + baz;
|
||||
result = bar.further_processing();
|
||||
}
|
||||
let x = result;
|
||||
|
||||
// Statement expressions can be inserted inside normal expressions
|
||||
// to avoid duplicated calculations
|
||||
let x = foo(bar) + { let v = calc(); process(v, v.len, v.abs) } + baz;
|
||||
|
||||
// The above is equivalent to:
|
||||
let foo_result = foo(bar);
|
||||
let calc_result;
|
||||
{
|
||||
let v = calc();
|
||||
result = process(v, v.len, v.abs); // <- avoid calculating 'v'
|
||||
}
|
||||
let x = foo_result + calc_result + baz;
|
||||
|
||||
// Statement expressions are also useful as function call arguments
|
||||
// when side effects are desired
|
||||
do_work(x, y, { let z = foo(x, y); print(z); z });
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
// statement expression
|
||||
```
|
||||
|
||||
Statement expressions can be disabled via [`Engine::set_allow_statement_expression`][options].
|
73
rhai_engine/rhaibook/language/statements.md
Normal file
73
rhai_engine/rhaibook/language/statements.md
Normal file
@@ -0,0 +1,73 @@
|
||||
Statements
|
||||
==========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Statements are terminated by semicolons `;` and they are mandatory,
|
||||
except for the _last_ statement in a _block_ (enclosed by `{` ... `}` pairs) where it can be omitted.
|
||||
|
||||
Semicolons can also be omitted for statement types that always end in a block – for example
|
||||
the [`if`], [`while`], [`for`], [`loop`] and [`switch`] statements.
|
||||
|
||||
```rust
|
||||
let a = 42; // normal assignment statement
|
||||
let a = foo(42); // normal function call statement
|
||||
foo < 42; // normal expression as statement
|
||||
|
||||
let a = { 40 + 2 }; // 'a' is set to the value of the statements block, which is the value of the last statement
|
||||
// ^ the last statement does not require a terminating semicolon (but also works with it)
|
||||
// ^ semicolon required here to terminate the 'let' statement
|
||||
// it is a syntax error without it, even though it ends with '}'
|
||||
// that is because the 'let' statement doesn't end in a block
|
||||
|
||||
if foo { a = 42 }
|
||||
// ^ no need to terminate an if-statement with a semicolon
|
||||
// that is because the 'if' statement ends in a block
|
||||
|
||||
4 * 10 + 2 // a statement which is just one expression - no ending semicolon is OK
|
||||
// because it is the last statement of the whole block
|
||||
```
|
||||
|
||||
|
||||
Statements Block
|
||||
----------------
|
||||
|
||||
### Syntax
|
||||
|
||||
Statements blocks in Rhai are formed by enclosing zero or more statements within braces `{`...`}`.
|
||||
|
||||
> `{` _statement_`;` _statement_`;` ... _statement_ `}`
|
||||
>
|
||||
> `{` _statement_`;` _statement_`;` ... _statement_`;` `}` `// trailing semi-colon is optional`
|
||||
|
||||
### Closed scope
|
||||
|
||||
A statements block forms a _closed_ scope.
|
||||
|
||||
Any [variable] and/or [constant] defined within the block are removed outside the block, so are
|
||||
[modules] [imported][`import`] within the block.
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
let y = 18;
|
||||
|
||||
{
|
||||
import "hello" as h;
|
||||
const HELLO = 99;
|
||||
let y = 0;
|
||||
|
||||
h::greet(); // ok
|
||||
|
||||
print(y + HELLO); // prints 99 (y is zero)
|
||||
|
||||
:
|
||||
:
|
||||
} // <- 'HELLO' and 'y' go away here...
|
||||
|
||||
print(x + y); // prints 60 (y is still 18)
|
||||
|
||||
print(HELLO); // <- error: 'HELLO' not found
|
||||
|
||||
h::greet(); // <- error: module 'h' not found
|
||||
```
|
147
rhai_engine/rhaibook/language/string-fn.md
Normal file
147
rhai_engine/rhaibook/language/string-fn.md
Normal file
@@ -0,0 +1,147 @@
|
||||
Standard String Functions
|
||||
=========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
The following standard methods (mostly defined in the [`MoreStringPackage`][built-in packages] but
|
||||
excluded when using a [raw `Engine`]) operate on [strings] (and possibly characters).
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `len` method and property | _none_ | returns the number of characters (**not** number of bytes) in the string |
|
||||
| `bytes` method and property | _none_ | returns the number of bytes making up the UTF-8 string; for strings containing only ASCII characters, this is much faster than `len` |
|
||||
| `is_empty` method and property | _none_ | returns `true` if the string is empty |
|
||||
| `to_blob`<br/>(not available under [`no_index`]) | _none_ | converts the string into an UTF-8 encoded byte-stream and returns it as a [BLOB]. |
|
||||
| `to_chars`<br/>(not available under [`no_index`]) | _none_ | splits the string by individual characters, returning them as an [array] |
|
||||
| `get` | position, counting from end if < 0 | gets the character at a certain position ([`()`] if the position is not valid) |
|
||||
| `set` | <ol><li>position, counting from end if < 0</li><li>new character</li></ol> | sets a certain position to a new character (no effect if the position is not valid) |
|
||||
| `pad` | <ol><li>target length</li><li>character/string to pad</li></ol> | pads the string with a character or a string to at least a specified length |
|
||||
| `append`, `+=` operator | item to append | adds the display text of an item to the end of the string |
|
||||
| `remove` | character/string to remove | removes a character or a string from the string |
|
||||
| `pop` | _(optional)_ number of characters to remove, none if ≤ 0, entire string if ≥ length | removes the last character (if no parameter) and returns it ([`()`] if empty); otherwise, removes the last number of characters and returns them as a string |
|
||||
| `clear` | _none_ | empties the string |
|
||||
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
|
||||
| `to_upper` | _none_ | converts the string/character into upper-case as a new string/character and returns it |
|
||||
| `to_lower` | _none_ | converts the string/character into lower-case as a new string/character and returns it |
|
||||
| `make_upper` | _none_ | converts the string/character into upper-case |
|
||||
| `make_lower` | _none_ | converts the string/character into lower-case |
|
||||
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
|
||||
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
|
||||
| `starts_with` | string | returns `true` if the string starts with a certain string |
|
||||
| `ends_with` | string | returns `true` if the string ends with a certain string |
|
||||
| `min` | <ol><li>first character/string</li><li>second character/string</li><ol> | returns the smaller of two characters/strings |
|
||||
| `max` | <ol><li>first character/string</li><li>second character/string</li><ol> | returns the larger of two characters/strings |
|
||||
| `index_of` | <ol><li>character/sub-string to search for</li><li>_(optional)_ start position, counting from end if < 0, end if ≥ length</li></ol> | returns the position that a certain character or sub-string occurs in the string, or −1 if not found |
|
||||
| `sub_string` | <ol><li>start position, counting from end if < 0</li><li>_(optional)_ number of characters to extract, none if ≤ 0, to end if omitted</li></ol> | extracts a sub-string |
|
||||
| `sub_string` | [range] of characters to extract, from beginning if ≤ 0, to end if ≥ length | extracts a sub-string |
|
||||
| `split`<br/>(not available under [`no_index`]) | _none_ | splits the string by whitespaces, returning an [array] of string segments |
|
||||
| `split`<br/>(not available under [`no_index`]) | position to split at (in number of characters), counting from end if < 0, end if ≥ length | splits the string into two segments at the specified character position, returning an [array] of two string segments |
|
||||
| `split`<br/>(not available under [`no_index`]) | <ol><li>delimiter character/string</li><li>_(optional)_ maximum number of segments, 1 if < 1</li></ol> | splits the string by the specified delimiter, returning an [array] of string segments |
|
||||
| `split_rev`<br/>(not available under [`no_index`]) | <ol><li>delimiter character/string</li><li>_(optional)_ maximum number of segments, 1 if < 1</li></ol> | splits the string by the specified delimiter in reverse order, returning an [array] of string segments |
|
||||
| `crop` | <ol><li>start position, counting from end if < 0</li><li>_(optional)_ number of characters to retain, none if ≤ 0, to end if omitted</li></ol> | retains only a portion of the string |
|
||||
| `crop` | [range] of characters to retain, from beginning if ≤ 0, to end if ≥ length | retains only a portion of the string |
|
||||
| `replace` | <ol><li>target character/sub-string</li><li>replacement character/string</li></ol> | replaces a sub-string with another |
|
||||
| `chars` method and property | <ol><li>_(optional)_ start position, counting from end if < 0</li><li>_(optional)_ number of characters to iterate, none if ≤ 0</li></ol> | allows iteration of the characters inside the string |
|
||||
|
||||
Beware that functions that involve indexing into a [string] to get at individual [characters],
|
||||
e.g. `sub_string`, require walking through the entire UTF-8 encoded bytes stream to extract
|
||||
individual Unicode characters and counting them, which can be slow for long [strings].
|
||||
|
||||
|
||||
Building Strings
|
||||
----------------
|
||||
|
||||
[Strings] can be built from segments via the `+` operator.
|
||||
|
||||
| Operator | Description |
|
||||
| ------------------ | ------------------------------------------------------------------------- |
|
||||
| [string] `+=` item | convert the item into a [string], then append it to the first [string] |
|
||||
| [string] `+` item | convert the item into a [string], then concatenate them as a new [string] |
|
||||
| item `+` [string] | convert the item into a [string], then concatenate them as a new [string] |
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
|
||||
// Build string with '+'
|
||||
let s = "The answer is: " + x + "!!!";
|
||||
|
||||
// Prints: "The answer is: 42!!!"
|
||||
print(s);
|
||||
```
|
||||
|
||||
### Standard Operators Between Strings and/or Characters
|
||||
|
||||
The following standard operators inter-operate between [strings] and/or [characters][strings].
|
||||
|
||||
When one (or both) of the operands is a [character], it is first converted into a one-character
|
||||
[string] before running the operator.
|
||||
|
||||
| Operator | Description |
|
||||
| --------- | --------------------------------------------- |
|
||||
| `+`, `+=` | [character]/[string] concatenation |
|
||||
| `-`, `-=` | remove [character]/sub-[string] from [string] |
|
||||
| `==` | equals to |
|
||||
| `!=` | not equals to |
|
||||
| `>` | greater than |
|
||||
| `>=` | greater than or equals to |
|
||||
| `<` | less than |
|
||||
| `<=` | less than or equals to |
|
||||
|
||||
### Interop with BLOB's
|
||||
|
||||
For convenience, when a [BLOB] is appended to a [string], or vice versa, it is treated as a UTF-8
|
||||
encoded byte stream and automatically first converted into the appropriate [string] value.
|
||||
|
||||
That is because it is rarely useful to append a [BLOB] into a string, but extremely useful to be
|
||||
able to directly manipulate UTF-8 encoded text.
|
||||
|
||||
| Operator | Description |
|
||||
| --------- | --------------------------------------------------------------------------- |
|
||||
| `+`, `+=` | append a [BLOB] (as a UTF-8 encoded byte stream) to the end of the [string] |
|
||||
| `+` | concatenate a [BLOB] (as a UTF-8 encoded byte stream) with a [string] |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let full_name == " Bob C. Davis ";
|
||||
full_name.len == 14;
|
||||
|
||||
full_name.trim();
|
||||
full_name.len == 12;
|
||||
full_name == "Bob C. Davis";
|
||||
|
||||
full_name.pad(15, '$');
|
||||
full_name.len == 15;
|
||||
full_name == "Bob C. Davis$$$";
|
||||
|
||||
let n = full_name.index_of('$');
|
||||
n == 12;
|
||||
|
||||
full_name.index_of("$$", n + 1) == 13;
|
||||
|
||||
full_name.sub_string(n, 3) == "$$$";
|
||||
full_name.sub_string(n..n+3) == "$$$";
|
||||
|
||||
full_name.truncate(6);
|
||||
full_name.len == 6;
|
||||
full_name == "Bob C.";
|
||||
|
||||
full_name.replace("Bob", "John");
|
||||
full_name.len == 7;
|
||||
full_name == "John C.";
|
||||
|
||||
full_name.contains('C') == true;
|
||||
full_name.contains("John") == true;
|
||||
|
||||
full_name.crop(5);
|
||||
full_name == "C.";
|
||||
|
||||
full_name.crop(0, 1);
|
||||
full_name == "C";
|
||||
|
||||
full_name.clear();
|
||||
full_name.len == 0;
|
||||
```
|
106
rhai_engine/rhaibook/language/string-interp.md
Normal file
106
rhai_engine/rhaibook/language/string-interp.md
Normal file
@@ -0,0 +1,106 @@
|
||||
Multi-Line Literal Strings
|
||||
==========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
A [string] wrapped by a pair of back-tick (`` ` ``) characters is interpreted _literally_.
|
||||
|
||||
This means that every single character that lies between the two back-ticks is taken verbatim.
|
||||
|
||||
This include new-lines, whitespaces, escape characters etc.
|
||||
|
||||
```js
|
||||
let x = `hello, world! "\t\x42"
|
||||
hello world again! 'x'
|
||||
this is the last time!!! `;
|
||||
|
||||
// The above is the same as:
|
||||
let x = "hello, world! \"\\t\\x42\"\n hello world again! 'x'\n this is the last time!!! ";
|
||||
```
|
||||
|
||||
If a back-tick (`` ` ``) appears at the _end_ of a line, then it is understood that the entire text
|
||||
block starts from the _next_ line; the starting new-line character is stripped.
|
||||
|
||||
```js
|
||||
let x = `
|
||||
hello, world! "\t\x42"
|
||||
hello world again! 'x'
|
||||
this is the last time!!!
|
||||
`;
|
||||
|
||||
// The above is the same as:
|
||||
let x = " hello, world! \"\\t\\x42\"\n hello world again! 'x'\n this is the last time!!!\n";
|
||||
```
|
||||
|
||||
To actually put a back-tick (`` ` ``) character inside a multi-line literal [string], use two
|
||||
back-ticks together (i.e. ` `` `).
|
||||
|
||||
```js
|
||||
let x = `I have a quote " as well as a back-tick `` here.`;
|
||||
|
||||
// The above is the same as:
|
||||
let x = "I have a quote \" as well as a back-tick ` here.";
|
||||
```
|
||||
|
||||
|
||||
String Interpolation
|
||||
====================
|
||||
|
||||
```admonish warning.side.wide "Only literal strings"
|
||||
|
||||
Interpolation is not supported for normal [string] or [character] literals.
|
||||
```
|
||||
|
||||
~~~admonish question.side.wide "What if I want `${` inside?"
|
||||
|
||||
🤦 Well, you just _have_ to ask for the impossible, don't you?
|
||||
|
||||
Currently there is no way to escape `${`. Build the [string] in three pieces:
|
||||
|
||||
```js
|
||||
`Interpolations start with "`
|
||||
+ "${"
|
||||
+ `" and end with }.`
|
||||
```
|
||||
~~~
|
||||
|
||||
Multi-line literal [strings] support _string interpolation_ wrapped in `${` ... `}`.
|
||||
|
||||
`${` ... `}` acts as a statements _block_ and can contain anything that is allowed within a
|
||||
statements block, including another interpolated [string]!
|
||||
The last result of the block is taken as the value for interpolation.
|
||||
|
||||
Rhai uses [`to_string`] to convert any value into a [string], then physically joins all the
|
||||
sub-strings together.
|
||||
|
||||
For convenience, if any interpolated value is a [BLOB], however, it is automatically treated as a
|
||||
UTF-8 encoded string. That is because it is rarely useful to interpolate a [BLOB] into a [string],
|
||||
but extremely useful to be able to directly manipulate UTF-8 encoded text.
|
||||
|
||||
```js
|
||||
let x = 42;
|
||||
let y = 123;
|
||||
|
||||
let s = `x = ${x} and y = ${y}.`; // <- interpolated string
|
||||
|
||||
let s = ("x = " + {x} + " and y = " + {y} + "."); // <- de-sugars to this
|
||||
|
||||
s == "x = 42 and y = 123.";
|
||||
|
||||
let s = `
|
||||
Undeniable logic:
|
||||
1) Hello, ${let w = `${x} world`; if x > 1 { w += "s" } w}!
|
||||
2) If ${y} > ${x} then it is ${y > x}!
|
||||
`;
|
||||
|
||||
s == "Undeniable logic:\n1) Hello, 42 worlds!\n2) If 123 > 42 then it is true!\n";
|
||||
|
||||
let blob = blob(3, 0x21);
|
||||
|
||||
print(blob); // prints [212121]
|
||||
|
||||
print(`Data: ${blob}`); // prints "Data: !!!"
|
||||
// BLOB is treated as UTF-8 encoded string
|
||||
|
||||
print(`Data: ${blob.to_string()}`); // prints "Data: [212121]"
|
||||
```
|
259
rhai_engine/rhaibook/language/strings-chars.md
Normal file
259
rhai_engine/rhaibook/language/strings-chars.md
Normal file
@@ -0,0 +1,259 @@
|
||||
Strings and Characters
|
||||
======================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish tip.side "Safety"
|
||||
|
||||
Always limit the [maximum length of strings].
|
||||
```
|
||||
|
||||
String in Rhai contain any text sequence of valid Unicode characters.
|
||||
|
||||
Internally strings are stored in UTF-8 encoding.
|
||||
|
||||
[`type_of()`] a string returns `"string"`.
|
||||
|
||||
|
||||
String and Character Literals
|
||||
-----------------------------
|
||||
|
||||
String and character literals follow JavaScript-style syntax.
|
||||
|
||||
| Type | Quotes | Escapes? | Continuation? | Interpolation? |
|
||||
| ------------------------- | :-------------: | :------: | :-----------: | :------------: |
|
||||
| Normal string | `"..."` | yes | with `\` | **no** |
|
||||
| Raw string | `#..#"..."#..#` | **no** | **no** | **no** |
|
||||
| Multi-line literal string | `` `...` `` | **no** | **no** | with `${...}` |
|
||||
| Character | `'...'` | yes | **no** | **no** |
|
||||
|
||||
```admonish tip.small "Tip: Building strings"
|
||||
|
||||
Strings can be built up from other strings and types via the `+` operator
|
||||
(provided by the [`MoreStringPackage`][built-in packages] but excluded when using a [raw `Engine`]).
|
||||
|
||||
This is particularly useful when printing output.
|
||||
```
|
||||
|
||||
|
||||
Standard Escape Sequences
|
||||
-------------------------
|
||||
|
||||
~~~admonish tip.side "Tip: Character `to_int()`"
|
||||
|
||||
Use the `to_int` method to convert a Unicode character into its 32-bit Unicode encoding.
|
||||
~~~
|
||||
|
||||
There is built-in support for Unicode (`\u`_xxxx_ or `\U`_xxxxxxxx_) and hex (`\x`_xx_) escape
|
||||
sequences for normal strings and characters.
|
||||
|
||||
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.
|
||||
|
||||
Escape sequences are not supported for multi-line literal strings wrapped by back-ticks (`` ` ``).
|
||||
|
||||
| Escape sequence | Meaning |
|
||||
| --------------- | -------------------------------- |
|
||||
| `\\` | back-slash (`\`) |
|
||||
| `\t` | tab |
|
||||
| `\r` | carriage-return (`CR`) |
|
||||
| `\n` | line-feed (`LF`) |
|
||||
| `\"` or `""` | double-quote (`"`) |
|
||||
| `\'` | single-quote (`'`) |
|
||||
| `\x`_xx_ | ASCII character in 2-digit hex |
|
||||
| `\u`_xxxx_ | Unicode character in 4-digit hex |
|
||||
| `\U`_xxxxxxxx_ | Unicode character in 8-digit hex |
|
||||
|
||||
|
||||
Line Continuation
|
||||
-----------------
|
||||
|
||||
For a normal string wrapped by double-quotes (`"`), a back-slash (`\`) character at the end of a
|
||||
line indicates that the string continues onto the next line _without any line-break_.
|
||||
|
||||
Whitespace up to the indentation of the opening double-quote is ignored in order to enable lining up
|
||||
blocks of text.
|
||||
|
||||
Spaces are _not_ added, so to separate one line with the next with a space, put a space before the
|
||||
ending back-slash (`\`) character.
|
||||
|
||||
```rust
|
||||
let x = "hello, world!\
|
||||
hello world again! \
|
||||
this is the ""last"" time!!!";
|
||||
// ^^^^^^ these whitespaces are ignored
|
||||
|
||||
// The above is the same as:
|
||||
let x = "hello, world!hello world again! this is the \"last\" time!!!";
|
||||
```
|
||||
|
||||
A string with continuation does not open up a new line. To do so, a new-line character must be
|
||||
manually inserted at the appropriate position.
|
||||
|
||||
```rust
|
||||
let x = "hello, world!\n\
|
||||
hello world again!\n\
|
||||
this is the last time!!!";
|
||||
|
||||
// The above is the same as:
|
||||
let x = "hello, world!\nhello world again!\nthis is the last time!!!";
|
||||
```
|
||||
|
||||
~~~admonish warning.small "No ending quote before the line ends is a syntax error"
|
||||
|
||||
If the ending double-quote is omitted, it is a syntax error.
|
||||
|
||||
```rust
|
||||
let x = "hello
|
||||
# ";
|
||||
// ^ syntax error: unterminated string literal
|
||||
```
|
||||
~~~
|
||||
|
||||
```admonish question.small "Why not go multi-line?"
|
||||
|
||||
Technically speaking, there is no difficulty in allowing strings to run for multiple lines
|
||||
_without_ the continuation back-slash.
|
||||
|
||||
Rhai forces you to manually mark a continuation with a back-slash because the ending quote is easy to omit.
|
||||
Once it happens, the entire remainder of the script would become one giant, multi-line string.
|
||||
|
||||
This behavior is different from Rust, where string literals can run for multiple lines.
|
||||
```
|
||||
|
||||
|
||||
Raw Strings
|
||||
-----------
|
||||
|
||||
A _raw string_ is any text enclosed by a pair of double-quotes (`"`), wrapped by hash (`#`) characters.
|
||||
|
||||
The number of hash (`#`) on each side must be the same.
|
||||
|
||||
Any text inside the double-quotes, as long as it is not a double-quote (`"`) followed by the same
|
||||
number of hash (`#`) characters, is simply copied verbatim, _including control codes and/or
|
||||
line-breaks_.
|
||||
|
||||
Raw strings are very useful for embedded regular expressions, file paths, and program code etc.
|
||||
|
||||
```rust
|
||||
let x = #"Hello, I am a raw string! which means that I can contain
|
||||
line-breaks, \ slashes (not escapes), "quotes" and even # characters!"#
|
||||
|
||||
// Use more than one '#' if you happen to have '"###...' inside the string...
|
||||
|
||||
let x = ###"In Rhai, you can write ##"hello"## as a raw string."###;
|
||||
// ^^^ this is not the end of the raw string
|
||||
```
|
||||
|
||||
|
||||
Indexing
|
||||
--------
|
||||
|
||||
Strings can be _indexed_ into to get access to any individual character.
|
||||
This is similar to many modern languages but different from Rust.
|
||||
|
||||
### From beginning
|
||||
|
||||
Individual characters within a string can be accessed with zero-based, non-negative integer indices:
|
||||
|
||||
> _string_ `[` _index from 0 to (total number of characters − 1)_ `]`
|
||||
|
||||
### From end
|
||||
|
||||
A _negative_ index accesses a character in the string counting from the _end_, with −1 being the
|
||||
_last_ character.
|
||||
|
||||
> _string_ `[` _index from −1 to −(total number of characters)_ `]`
|
||||
|
||||
```admonish warning.small "Character indexing can be SLOOOOOOOOW"
|
||||
|
||||
Internally, a Rhai string is still stored compactly as a Rust UTF-8 string in order to save memory.
|
||||
|
||||
Therefore, getting the character at a particular index involves walking through the entire UTF-8
|
||||
encoded bytes stream to extract individual Unicode characters, counting them on the way.
|
||||
|
||||
Because of this, indexing can be a _slow_ procedure, especially for long strings.
|
||||
Along the same lines, getting the _length_ of a string (which returns the number of characters, not
|
||||
bytes) can also be slow.
|
||||
```
|
||||
|
||||
|
||||
Sub-Strings
|
||||
-----------
|
||||
|
||||
Sub-strings, or _slices_ in some programming languages, are parts of strings.
|
||||
|
||||
In Rhai, a sub-string can be specified by indexing with a [range] of characters:
|
||||
|
||||
> _string_ `[` _first character (starting from zero)_ `..` _last character (exclusive)_ `]`
|
||||
>
|
||||
> _string_ `[` _first character (starting from zero)_ `..=` _last character (inclusive)_ `]`
|
||||
|
||||
Sub-string [ranges] always start from zero counting towards the end of the string.
|
||||
Negative [ranges] are not supported.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```js
|
||||
let name = "Bob";
|
||||
let middle_initial = 'C';
|
||||
let last = "Davis";
|
||||
|
||||
let full_name = `${name} ${middle_initial}. ${last}`;
|
||||
full_name == "Bob C. Davis";
|
||||
|
||||
// String building with different types
|
||||
let age = 42;
|
||||
let record = `${full_name}: age ${age}`;
|
||||
record == "Bob C. Davis: age 42";
|
||||
|
||||
// Unlike Rust, Rhai strings can be indexed to get a character
|
||||
// (disabled with 'no_index')
|
||||
let c = record[4];
|
||||
c == 'C'; // single character
|
||||
|
||||
let slice = record[4..8]; // sub-string slice
|
||||
slice == " C. D";
|
||||
|
||||
ts.s = record; // custom type properties can take strings
|
||||
|
||||
let c = ts.s[4];
|
||||
c == 'C';
|
||||
|
||||
let c = ts.s[-4]; // negative index counts from the end
|
||||
c == 'e';
|
||||
|
||||
let c = "foo"[0]; // indexing also works on string literals...
|
||||
c == 'f';
|
||||
|
||||
let c = ("foo" + "bar")[5]; // ... and expressions returning strings
|
||||
c == 'r';
|
||||
|
||||
let text = "hello, world!";
|
||||
text[0] = 'H'; // modify a single character
|
||||
text == "Hello, world!";
|
||||
|
||||
text[7..=11] = "Earth"; // modify a sub-string slice
|
||||
text == "Hello, Earth!";
|
||||
|
||||
// Escape sequences in strings
|
||||
record += " \u2764\n"; // escape sequence of '❤' in Unicode
|
||||
record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line
|
||||
|
||||
// Unlike Rust, Rhai strings can be directly modified character-by-character
|
||||
// (disabled with 'no_index')
|
||||
record[4] = '\x58'; // 0x58 = 'X'
|
||||
record == "Bob X. Davis: age 42 ❤\n";
|
||||
|
||||
// Use 'in' to test if a substring (or character) exists in a string
|
||||
"Davis" in record == true;
|
||||
'X' in record == true;
|
||||
'C' in record == false;
|
||||
|
||||
// Strings can be iterated with a 'for' statement, yielding characters
|
||||
for ch in record {
|
||||
print(ch);
|
||||
}
|
||||
```
|
39
rhai_engine/rhaibook/language/switch-expression.md
Normal file
39
rhai_engine/rhaibook/language/switch-expression.md
Normal file
@@ -0,0 +1,39 @@
|
||||
Switch Expression
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Like [`if`], [`switch`] also works as an _expression_.
|
||||
|
||||
```admonish tip.small "Tip"
|
||||
|
||||
This means that a [`switch`] expression can appear anywhere a regular expression can,
|
||||
e.g. as [function] call arguments.
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: Disable `switch` expressions"
|
||||
|
||||
[`switch`] expressions can be disabled via [`Engine::set_allow_switch_expression`][options].
|
||||
~~~
|
||||
|
||||
```js
|
||||
let x = switch foo { 1 => true, _ => false };
|
||||
|
||||
func(switch foo {
|
||||
"hello" => 42,
|
||||
"world" => 123,
|
||||
_ => 0
|
||||
});
|
||||
|
||||
// The above is somewhat equivalent to:
|
||||
|
||||
let x = if foo == 1 { true } else { false };
|
||||
|
||||
if foo == "hello" {
|
||||
func(42);
|
||||
} else if foo == "world" {
|
||||
func(123);
|
||||
} else {
|
||||
func(0);
|
||||
}
|
||||
```
|
215
rhai_engine/rhaibook/language/switch.md
Normal file
215
rhai_engine/rhaibook/language/switch.md
Normal file
@@ -0,0 +1,215 @@
|
||||
Switch Statement
|
||||
================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The `switch` statement allows matching on [literal] values, and it mostly follows Rust's `match` syntax.
|
||||
|
||||
```js
|
||||
switch calc_secret_value(x) {
|
||||
1 => print("It's one!"),
|
||||
2 => {
|
||||
// A statements block instead of a one-line statement
|
||||
print("It's two!");
|
||||
print("Again!");
|
||||
}
|
||||
3 => print("Go!"),
|
||||
// A list of alternatives
|
||||
4 | 5 | 6 => print("Some small number!"),
|
||||
// _ is the default when no case matches. It must be the last case.
|
||||
_ => print(`Oops! Something's wrong: ${x}`)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Default Case
|
||||
------------
|
||||
|
||||
A _default_ case (i.e. when no other cases match) can be specified with `_`.
|
||||
|
||||
```admonish warning.small "Must be last"
|
||||
|
||||
The default case must be the _last_ case in the `switch` statement.
|
||||
```
|
||||
|
||||
```js
|
||||
switch wrong_default {
|
||||
1 => 2,
|
||||
_ => 9, // <- syntax error: default case not the last
|
||||
2 => 3,
|
||||
3 => 4, // <- ending with extra comma is OK
|
||||
}
|
||||
|
||||
switch wrong_default {
|
||||
1 => 2,
|
||||
2 => 3,
|
||||
3 => 4,
|
||||
_ => 8, // <- syntax error: default case not the last
|
||||
_ => 9
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Array and Object Map Literals Also Work
|
||||
---------------------------------------
|
||||
|
||||
The `switch` expression can match against any _[literal]_, including [array] and [object map] [literals].
|
||||
|
||||
```js
|
||||
// Match on arrays
|
||||
switch [foo, bar, baz] {
|
||||
["hello", 42, true] => ...,
|
||||
["hello", 123, false] => ...,
|
||||
["world", 1, true] => ...,
|
||||
_ => ...
|
||||
}
|
||||
|
||||
// Match on object maps
|
||||
switch map {
|
||||
#{ a: 1, b: 2, c: true } => ...,
|
||||
#{ a: 42, d: "hello" } => ...,
|
||||
_ => ...
|
||||
}
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Working with enums"
|
||||
|
||||
Switching on [arrays] is very useful when working with Rust enums
|
||||
(see [this section]({{rootUrl}}/patterns/enums.md) for more details).
|
||||
```
|
||||
|
||||
|
||||
Case Conditions
|
||||
---------------
|
||||
|
||||
Similar to Rust, each case (except the default case at the end) can provide an optional condition
|
||||
that must evaluate to `true` in order for the case to match.
|
||||
|
||||
All cases are checked in order, so an earlier case that matches will override all later cases.
|
||||
|
||||
```js
|
||||
let result = switch calc_secret_value(x) {
|
||||
1 if some_external_condition(x, y, z) => 100,
|
||||
|
||||
1 | 2 | 3 if x < foo => 200, // <- all alternatives share the same condition
|
||||
|
||||
2 if bar() => 999,
|
||||
|
||||
2 => "two", // <- fallback value for 2
|
||||
|
||||
2 => "dead code", // <- this case is a duplicate and will never match
|
||||
// because the previous case matches first
|
||||
|
||||
5 if CONDITION => 123, // <- value for 5 matching condition
|
||||
|
||||
5 => "five", // <- fallback value for 5
|
||||
|
||||
_ if CONDITION => 8888 // <- syntax error: default case cannot have condition
|
||||
};
|
||||
```
|
||||
|
||||
~~~admonish tip "Tip: Use with `type_of()`"
|
||||
|
||||
Case conditions, together with [`type_of()`], makes it extremely easy to work with
|
||||
values which may be of several different types (like properties in a JSON object).
|
||||
|
||||
```js
|
||||
switch value.type_of() {
|
||||
// if 'value' is a string...
|
||||
"string" if value.len() < 5 => ...,
|
||||
"string" => ...,
|
||||
|
||||
// if 'value' is an array...
|
||||
"array" => ...,
|
||||
|
||||
// if 'value' is an object map...
|
||||
"map" if value.prop == 42 => ...,
|
||||
"map" => ...,
|
||||
|
||||
// if 'value' is a number...
|
||||
"i64" if value > 0 => ...,
|
||||
"i64" => ...,
|
||||
|
||||
// anything else: probably an error...
|
||||
_ => ...
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
Range Cases
|
||||
-----------
|
||||
|
||||
Because of their popularity, [literal] integer [ranges] can also be used as `switch` cases.
|
||||
|
||||
Numeric [ranges] are only searched when the `switch` value is itself a number (including
|
||||
floating-point and [`Decimal`][rust_decimal]). They never match any other data types.
|
||||
|
||||
```admonish warning.small "Must come after numeric cases"
|
||||
|
||||
Range cases must come _after_ all numeric cases.
|
||||
```
|
||||
|
||||
```js
|
||||
let x = 42;
|
||||
|
||||
switch x {
|
||||
'x' => ..., // no match: wrong data type
|
||||
|
||||
1 => ..., // <- specific numeric cases are checked first
|
||||
2 => ..., // <- but these do not match
|
||||
|
||||
0..50 if x > 45 => ..., // no match: condition is 'false'
|
||||
|
||||
-10..20 => ..., // no match: not in range
|
||||
|
||||
0..50 => ..., // <- MATCH!!!
|
||||
|
||||
30..100 => ..., // no match: even though it is within range,
|
||||
// the previous case matches first
|
||||
|
||||
42 => ..., // <- syntax error: numeric cases cannot follow range cases
|
||||
}
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Ranges can overlap"
|
||||
|
||||
When more then one [range] contain the `switch` value, the _first_ one with a fulfilled condition
|
||||
(if any) is evaluated.
|
||||
|
||||
Numeric [range] cases are tried in the order that they appear in the original script.
|
||||
```
|
||||
|
||||
|
||||
Difference From `if`-`else if` Chain
|
||||
------------------------------------
|
||||
|
||||
Although a `switch` expression looks _almost_ the same as an [`if`-`else if`][`if`] chain, there are
|
||||
subtle differences between the two.
|
||||
|
||||
### Look-up Table vs `x == y`
|
||||
|
||||
A `switch` expression matches through _hashing_ via a look-up table. Therefore, matching is very
|
||||
fast. Walking down an [`if`-`else if`][`if`] chain is _much_ slower.
|
||||
|
||||
On the other hand, operators can be [overloaded][operator overloading] in Rhai, meaning that it is
|
||||
possible to override the `==` operator for integers such that `x == y` returns a different result
|
||||
from the built-in default.
|
||||
|
||||
`switch` expressions do _not_ use the `==` operator for comparison; instead, they _hash_ the data
|
||||
values and jump directly to the correct statements via a pre-compiled look-up table. This makes
|
||||
matching extremely efficient, but it also means that [overloading][operator overloading] the `==`
|
||||
operator will have no effect.
|
||||
|
||||
Therefore, in environments where it is desirable to [overload][operator overloading] the `==`
|
||||
operator for [standard types] – though it is difficult to think of valid scenarios where you'd
|
||||
want `1 == 1` to return something other than `true` – avoid using the `switch` expression.
|
||||
|
||||
### Efficiency
|
||||
|
||||
Because the `switch` expression works through a look-up table, it is very efficient even for _large_
|
||||
number of cases; in fact, switching is an O(1) operation regardless of the size of the data and
|
||||
number of cases to match.
|
||||
|
||||
A long [`if`-`else if`][`if`] chain becomes increasingly slower with each additional case because
|
||||
essentially an O(n) _linear scan_ is performed.
|
56
rhai_engine/rhaibook/language/throw.md
Normal file
56
rhai_engine/rhaibook/language/throw.md
Normal file
@@ -0,0 +1,56 @@
|
||||
Throw Exception on Error
|
||||
========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
All [`Engine`] evaluation API methods return `Result<T, Box<rhai::EvalAltResult>>`
|
||||
with `EvalAltResult` holding error information.
|
||||
|
||||
To deliberately return an error, use the `throw` keyword.
|
||||
|
||||
```js
|
||||
if some_bad_condition_has_happened {
|
||||
throw error; // 'throw' any value as the exception
|
||||
}
|
||||
|
||||
throw; // defaults to '()'
|
||||
```
|
||||
|
||||
Exceptions thrown via `throw` in the script can be captured in Rust by matching
|
||||
`EvalAltResult::ErrorRuntime(value, position)` with the exception value captured by `value`.
|
||||
|
||||
```rust
|
||||
let result = engine.eval::<i64>(
|
||||
"
|
||||
let x = 42;
|
||||
|
||||
if x > 0 {
|
||||
throw x;
|
||||
}
|
||||
").expect_err();
|
||||
|
||||
println!("{result}"); // prints "Runtime error: 42 (line 5, position 15)"
|
||||
```
|
||||
|
||||
|
||||
Catch a Thrown Exception
|
||||
------------------------
|
||||
|
||||
It is possible to _catch_ an exception instead of having it abort the evaluation
|
||||
of the entire script via the [`try` ... `catch`][`try`]
|
||||
statement common to many C-like languages.
|
||||
|
||||
```js
|
||||
fn code_that_throws() {
|
||||
throw 42;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
code_that_throws();
|
||||
}
|
||||
catch (err) // 'err' captures the thrown exception value
|
||||
{
|
||||
print(err); // prints 42
|
||||
}
|
||||
```
|
50
rhai_engine/rhaibook/language/timestamps.md
Normal file
50
rhai_engine/rhaibook/language/timestamps.md
Normal file
@@ -0,0 +1,50 @@
|
||||
Timestamps
|
||||
==========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Timestamps are provided by the [`BasicTimePackage`][built-in packages] (excluded when using a [raw `Engine`])
|
||||
via the `timestamp` function.
|
||||
|
||||
Timestamps are not available under [`no_time`] or [`no_std`].
|
||||
|
||||
The Rust type of a timestamp is `std::time::Instant` ([`instant::Instant`] in [WASM] builds).
|
||||
|
||||
[`type_of()`] a timestamp returns `"timestamp"`.
|
||||
|
||||
|
||||
Built-in Functions
|
||||
------------------
|
||||
|
||||
The following methods (defined in the [`BasicTimePackage`][built-in packages] but excluded when
|
||||
using a [raw `Engine`]) operate on timestamps.
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ----------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| `elapsed` method and property | _none_ | returns the number of seconds since the timestamp |
|
||||
| `+` operator | number of seconds to add | returns a new timestamp with a specified number of seconds added |
|
||||
| `+=` operator | number of seconds to add | adds a specified number of seconds to the timestamp |
|
||||
| `-` operator | number of seconds to subtract | returns a new timestamp with a specified number of seconds subtracted |
|
||||
| `-=` operator | number of seconds to subtract | subtracts a specified number of seconds from the timestamp |
|
||||
| `-` operator | <ol><li>later timestamp</li><li>earlier timestamp</li></ol> | returns the number of seconds between the two timestamps |
|
||||
|
||||
The following methods are defined in the [`LanguageCorePackage`][built-in packages] but excluded
|
||||
when using a [raw `Engine`].
|
||||
|
||||
| Function | Not available under | Parameter(s) | Description |
|
||||
| -------- | :-----------------: | -------------------------- | ----------------------------------------------------------- |
|
||||
| `sleep` | [`no_std`] | number of seconds to sleep | blocks the current thread for a specified number of seconds |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let now = timestamp();
|
||||
|
||||
// Do some lengthy operation...
|
||||
|
||||
if now.elapsed > 30.0 {
|
||||
print("takes too long (over 30 seconds)!")
|
||||
}
|
||||
```
|
113
rhai_engine/rhaibook/language/try-catch.md
Normal file
113
rhai_engine/rhaibook/language/try-catch.md
Normal file
@@ -0,0 +1,113 @@
|
||||
Catch Exceptions
|
||||
================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
When an [exception] is thrown via a [`throw`] statement, evaluation of the script halts and the
|
||||
[`Engine`] returns with `EvalAltResult::ErrorRuntime` containing the exception value thrown.
|
||||
|
||||
It is possible, via the `try` ... `catch` statement, to _catch_ exceptions, optionally with an
|
||||
_error variable_.
|
||||
|
||||
> `try` `{` ... `}` `catch` `{` ... `}`
|
||||
>
|
||||
> `try` `{` ... `}` `catch` `(` _error variable_ `)` `{` ... `}`
|
||||
|
||||
```js
|
||||
// Catch an exception and capturing its value
|
||||
try
|
||||
{
|
||||
throw 42;
|
||||
}
|
||||
catch (err) // 'err' captures the thrown exception value
|
||||
{
|
||||
print(err); // prints 42
|
||||
}
|
||||
|
||||
// Catch an exception without capturing its value
|
||||
try
|
||||
{
|
||||
print(42/0); // deliberate divide-by-zero exception
|
||||
}
|
||||
catch // no error variable - exception value is discarded
|
||||
{
|
||||
print("Ouch!");
|
||||
}
|
||||
|
||||
// Exception in the 'catch' block
|
||||
try
|
||||
{
|
||||
print(42/0); // throw divide-by-zero exception
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("You seem to be dividing by zero here...");
|
||||
|
||||
throw "die"; // a 'throw' statement inside a 'catch' block
|
||||
// throws a new exception
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
~~~admonish tip "Tip: Re-throw exception"
|
||||
|
||||
Like the `try` ... `catch` syntax in most languages, it is possible to _re-throw_ an exception
|
||||
within the `catch` block simply by another [`throw`] statement without a value.
|
||||
|
||||
```js
|
||||
try
|
||||
{
|
||||
// Call something that will throw an exception...
|
||||
do_something_bad_that_throws();
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Oooh! You've done something real bad!");
|
||||
|
||||
throw; // 'throw' without a value within a 'catch' block
|
||||
// re-throws the original exception
|
||||
}
|
||||
|
||||
```
|
||||
~~~
|
||||
|
||||
```admonish success "Catchable exceptions"
|
||||
|
||||
Many script-oriented exceptions can be caught via `try` ... `catch`.
|
||||
|
||||
| Error type | Error value |
|
||||
| ----------------------------------------------------- | :--------------------------: |
|
||||
| Runtime error thrown by a [`throw`] statement | value in [`throw`] statement |
|
||||
| Arithmetic error | [object map] |
|
||||
| [Variable] not found | [object map] |
|
||||
| [Function] not found | [object map] |
|
||||
| [Module] not found | [object map] |
|
||||
| Unbound `this` | [object map] |
|
||||
| Data type mismatch | [object map] |
|
||||
| Assignment to a calculated/[constant] value | [object map] |
|
||||
| [Array]/[string]/[bit-field] indexing out-of-bounds | [object map] |
|
||||
| Indexing with an inappropriate data type | [object map] |
|
||||
| Error in property access | [object map] |
|
||||
| [`for`] statement on a type without a [type iterator] | [object map] |
|
||||
| Data race detected | [object map] |
|
||||
| Other runtime error | [object map] |
|
||||
|
||||
The error value in the `catch` clause is an [object map] containing information on the particular error,
|
||||
including its type, line and character position (if any), and source etc.
|
||||
|
||||
When the [`no_object`] feature is turned on, however, the error value is a simple [string] description.
|
||||
```
|
||||
|
||||
```admonish failure "Non-catchable exceptions"
|
||||
|
||||
Some exceptions _cannot_ be caught.
|
||||
|
||||
| Error type | Notes |
|
||||
| ----------------------------------------------------------------------- | --------------------------------- |
|
||||
| System error – e.g. script file not found | system errors are not recoverable |
|
||||
| Syntax error during parsing | invalid script |
|
||||
| [Custom syntax] mismatch error | incompatible [`Engine`] instance |
|
||||
| Script evaluation metrics exceeding [limits][safety] | [safety] protection |
|
||||
| Script evaluation manually [terminated]({{rootUrl}}/safety/progress.md) | [safety] protection |
|
||||
```
|
60
rhai_engine/rhaibook/language/type-of.md
Normal file
60
rhai_engine/rhaibook/language/type-of.md
Normal file
@@ -0,0 +1,60 @@
|
||||
`type_of()`
|
||||
===========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The `type_of` function detects the actual type of a value.
|
||||
|
||||
This is useful because all [variables] are [`Dynamic`] in nature.
|
||||
|
||||
```js
|
||||
// Use 'type_of()' to get the actual types of values
|
||||
type_of('c') == "char";
|
||||
type_of(42) == "i64";
|
||||
|
||||
let x = 123;
|
||||
x.type_of() == "i64"; // method-call style is also OK
|
||||
type_of(x) == "i64";
|
||||
|
||||
x = 99.999;
|
||||
type_of(x) == "f64";
|
||||
|
||||
x = "hello";
|
||||
if type_of(x) == "string" {
|
||||
do_something_first_with_string(x);
|
||||
}
|
||||
|
||||
switch type_of(x) {
|
||||
"string" => do_something_with_string(x),
|
||||
"char" => do_something_with_char(x),
|
||||
"i64" => do_something_with_int(x),
|
||||
"f64" => do_something_with_float(x),
|
||||
"bool" => do_something_with_bool(x),
|
||||
_ => throw `I cannot work with ${type_of(x)}!!!`
|
||||
}
|
||||
```
|
||||
|
||||
```admonish info.small "Standard types"
|
||||
|
||||
See [here][standard types] for the `type_of` output of standard types.
|
||||
```
|
||||
|
||||
```admonish info.small "Custom types"
|
||||
|
||||
`type_of()` a [custom type] returns:
|
||||
|
||||
* the friendly name, if registered via `Engine::register_type_with_name`
|
||||
|
||||
* the full Rust type path, if registered via `Engine::register_type`
|
||||
|
||||
~~~rust
|
||||
struct TestStruct1;
|
||||
struct TestStruct2;
|
||||
|
||||
engine
|
||||
// type_of(struct1) == "path::to::module::TestStruct1"
|
||||
.register_type::<TestStruct1>()
|
||||
// type_of(struct2) == "MyStruct"
|
||||
.register_type_with_name::<TestStruct2>("MyStruct");
|
||||
~~~
|
||||
```
|
83
rhai_engine/rhaibook/language/values-and-types.md
Normal file
83
rhai_engine/rhaibook/language/values-and-types.md
Normal file
@@ -0,0 +1,83 @@
|
||||
Values and Types
|
||||
================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The following primitive types are supported natively.
|
||||
|
||||
| Category | Equivalent Rust types | [`type_of()`](type-of.md) | `to_string()` |
|
||||
| ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------- |
|
||||
| **System integer** | `rhai::INT` (default `i64`, `i32` under [`only_i32`]) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
|
||||
| **Other integer number** | `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64` | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. |
|
||||
| **Integer numeric [range]** | `std::ops::Range<rhai::INT>`, `std::ops::RangeInclusive<rhai::INT>` | `"range"`, `"range="` | `"2..7"`, `"0..=15"` etc. |
|
||||
| **Floating-point number** (disabled with [`no_float`]) | `rhai::FLOAT` (default `f64`, `f32` under [`f32_float`]) | `"f32"` or `"f64"` | `"123.4567"` etc. |
|
||||
| **Fixed precision decimal number** (requires [`decimal`]) | [`rust_decimal::Decimal`][rust_decimal] | `"decimal"` | `"42"`, `"123.4567"` etc. |
|
||||
| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
|
||||
| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
|
||||
| **Immutable Unicode [string]** | [`rhai::ImmutableString`][`ImmutableString`] (`Rc<SmartString>`, `Arc<SmartString>` under [`sync`]) | `"string"` | `"hello"` etc. |
|
||||
| **[`Array`]** (disabled with [`no_index`]) | [`rhai::Array`][array] (`Vec<Dynamic>`) | `"array"` | `"[ 1, 2, 3 ]"` etc. |
|
||||
| **Byte array – [`BLOB`]** (disabled with [`no_index`]) | [`rhai::Blob`][BLOB] (`Vec<u8>`) | `"blob"` | `"[01020304abcd]"` etc. |
|
||||
| **[Object map]** (disabled with [`no_object`]) | [`rhai::Map`][object map] (`BTreeMap<SmartString, Dynamic>`) | `"map"` | `"#{ "a": 1, "b": true }"` etc. |
|
||||
| **[Timestamp]** (disabled with [`no_time`] or [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if [WASM] build) | `"timestamp"` | `"<timestamp>"` |
|
||||
| **[Function pointer]** | [`rhai::FnPtr`][function pointer] | `"Fn"` | `"Fn(foo)"` etc. |
|
||||
| **Dynamic value** (i.e. can be anything) | [`rhai::Dynamic`][`Dynamic`] | _the actual type_ | _actual value_ |
|
||||
| **Shared value** (a reference-counted, shared [`Dynamic`] value, created via [closures], disabled with [`no_closure`]) | | _the actual type_ | _actual value_ |
|
||||
| **Nothing/void/nil/null/Unit** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ |
|
||||
|
||||
|
||||
```admonish warning.small "No automatic type conversion for integers"
|
||||
|
||||
The various integer types are treated strictly _distinct_ by Rhai, meaning that
|
||||
`i32` and `i64` and `u32` and `u8` are completely different.
|
||||
|
||||
They cannot even be added together or compared with each other.
|
||||
|
||||
Nor can a smaller integer type be up-casted to a larger integer type.
|
||||
|
||||
This is very similar to Rust.
|
||||
```
|
||||
|
||||
|
||||
Default Types
|
||||
-------------
|
||||
|
||||
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 targets where using 64-bit integers incur a performance penalty.
|
||||
|
||||
~~~admonish danger.small "Warning: Default integer is `i64`"
|
||||
|
||||
Rhai's default integer type is `i64`, which is _DIFFERENT_ from Rust's `i32`.
|
||||
|
||||
It is very easy to unsuspectingly set an `i32` into Rhai, which _still works_ but will incur a significant
|
||||
runtime performance hit since the [`Engine`] will treat `i32` as an opaque [custom type] (unless using the
|
||||
[`only_i32`] feature).
|
||||
|
||||
`i64` is the default even on 32-bit systems. To use `i32` on 32-bit systems requires the [`only_i32`] feature.
|
||||
~~~
|
||||
|
||||
```admonish tip.small "Tip: Floating-point numbers"
|
||||
|
||||
If no floating-point is needed or supported, use the [`no_float`] feature to remove it.
|
||||
|
||||
Some applications require fixed-precision decimal numbers, which can be enabled via the [`decimal`] feature.
|
||||
```
|
||||
|
||||
```admonish info.small "Immutable strings"
|
||||
|
||||
[Strings] in Rhai are _immutable_, meaning that they can be shared but not modified.
|
||||
|
||||
Internally, the [`ImmutableString`] type is a wrapper over `Rc<String>` or `Arc<String>` (depending on [`sync`]).
|
||||
|
||||
Any modification done to a Rhai string causes the [string] to be cloned and the modifications made to the copy.
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Convert to string"
|
||||
|
||||
The [`to_string`] function converts a standard type into a [string] for display purposes.
|
||||
|
||||
The [`to_debug`] function converts a standard type into a [string] in debug format.
|
||||
```
|
183
rhai_engine/rhaibook/language/variables.md
Normal file
183
rhai_engine/rhaibook/language/variables.md
Normal file
@@ -0,0 +1,183 @@
|
||||
Variables
|
||||
=========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Valid Names
|
||||
-----------
|
||||
|
||||
```admonish tip.side.wide "Tip: Unicode Standard Annex #31 identifiers"
|
||||
|
||||
The [`unicode-xid-ident`] feature expands the allowed characters for variable names to the set defined by
|
||||
[Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/).
|
||||
```
|
||||
|
||||
Variables in Rhai follow normal C naming rules – must contain only ASCII letters, digits and underscores `_`.
|
||||
|
||||
| Character set | Description |
|
||||
| :-----------: | ------------------------ |
|
||||
| `A` ... `Z` | Upper-case ASCII letters |
|
||||
| `a` ... `z` | Lower-case ASCII letters |
|
||||
| `0` ... `9` | Digit characters |
|
||||
| `_` | Underscore character |
|
||||
|
||||
However, unlike Rust, a variable name must also contain at least one ASCII letter, and an ASCII
|
||||
letter must come _before_ any digits. In other words, the first character that is not an underscore `_`
|
||||
must be an ASCII letter and not a digit.
|
||||
|
||||
```admonish question.side.wide "Why this restriction?"
|
||||
|
||||
To reduce confusion (and subtle bugs) because, for instance, `_1` can easily be misread (or mistyped)
|
||||
as `-1`.
|
||||
|
||||
Rhai is dynamic without type checking, so there is no compiler to catch these typos.
|
||||
```
|
||||
|
||||
Therefore, some names acceptable to Rust, like `_`, `_42foo`, `_1` etc., are not valid in Rhai.
|
||||
|
||||
For example: `c3po` and `_r2d2_` are valid variable names, but `3abc` and `____49steps` are not.
|
||||
|
||||
Variable names are case _sensitive_.
|
||||
|
||||
Variable names also cannot be the same as a [keyword] (active or reserved).
|
||||
|
||||
```admonish warning.small "Avoid names longer than 11 letters on 32-Bit"
|
||||
|
||||
Rhai uses [`SmartString`] which avoids allocations unless a string is over its internal limit
|
||||
(23 ASCII characters on 64-bit, but only 11 ASCII characters on 32-bit).
|
||||
|
||||
On 64-bit systems, _most_ variable names are shorter than 23 letters, so this is unlikely to become
|
||||
an issue.
|
||||
|
||||
However, on 32-bit systems, take care to limit, where possible, variable names to within 11 letters.
|
||||
This is particularly true for local variables inside a hot loop, where they are created and
|
||||
destroyed in rapid succession.
|
||||
|
||||
~~~js
|
||||
// The following is SLOW on 32-bit
|
||||
for my_super_loop_variable in array {
|
||||
print(`Super! ${my_super_loop_variable}`);
|
||||
}
|
||||
|
||||
// Suggested revision:
|
||||
for loop_var in array {
|
||||
print(`Super! ${loop_var}`);
|
||||
}
|
||||
~~~
|
||||
```
|
||||
|
||||
|
||||
Declare a Variable
|
||||
------------------
|
||||
|
||||
Variables are declared using the `let` keyword.
|
||||
|
||||
```admonish tip.small "Tip: No initial value"
|
||||
|
||||
Variables do not have to be given an initial value.
|
||||
If none is provided, it defaults to [`()`].
|
||||
```
|
||||
|
||||
```admonish warning.small "Variables are local"
|
||||
|
||||
A variable defined within a [statements block](statements.md) is _local_ to that block.
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: `is_def_var`"
|
||||
|
||||
Use `is_def_var` to detect if a variable is defined.
|
||||
~~~
|
||||
|
||||
```rust
|
||||
let x; // ok - value is '()'
|
||||
let x = 3; // ok
|
||||
let _x = 42; // ok
|
||||
let x_ = 42; // also ok
|
||||
let _x_ = 42; // still ok
|
||||
|
||||
let _ = 123; // <- syntax error: illegal variable name
|
||||
let _9 = 9; // <- syntax error: illegal variable name
|
||||
|
||||
let x = 42; // variable is 'x', lower case
|
||||
let X = 123; // variable is 'X', upper case
|
||||
|
||||
print(x); // prints 42
|
||||
print(X); // prints 123
|
||||
|
||||
{
|
||||
let x = 999; // local variable 'x' shadows the 'x' in parent block
|
||||
|
||||
print(x); // prints 999
|
||||
}
|
||||
|
||||
print(x); // prints 42 - the parent block's 'x' is not changed
|
||||
|
||||
let x = 0; // new variable 'x' shadows the old 'x'
|
||||
|
||||
print(x); // prints 0
|
||||
|
||||
is_def_var("x") == true;
|
||||
|
||||
is_def_var("_x") == true;
|
||||
|
||||
is_def_var("y") == false;
|
||||
```
|
||||
|
||||
|
||||
Use Before Definition
|
||||
---------------------
|
||||
|
||||
By default, variables do not need to be defined before they are used.
|
||||
|
||||
If a variable accessed by a script is not defined previously within the same script, it is assumed
|
||||
to be provided via an external custom [`Scope`] passed to the [`Engine`] via the
|
||||
`Engine::XXX_with_scope` API.
|
||||
|
||||
```rust
|
||||
let engine = Engine::new();
|
||||
|
||||
engine.run("print(answer)")?; // <- error: variable 'answer' not found
|
||||
|
||||
// Create custom scope
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Add variable to custom scope
|
||||
scope.push("answer", 42_i64);
|
||||
|
||||
// Run with custom scope
|
||||
engine.run_with_scope(&mut scope,
|
||||
"print(answer)" // <- prints 42
|
||||
)?;
|
||||
```
|
||||
|
||||
~~~admonish bug.small "No `Scope`"
|
||||
|
||||
If no [`Scope`] is used to evaluate the script (e.g. when using `Engine::run` instead of
|
||||
`Engine::run_with_scope`), an undefined variable causes a runtime error when accessed.
|
||||
~~~
|
||||
|
||||
|
||||
Strict Variables Mode
|
||||
---------------------
|
||||
|
||||
With [`Engine::set_strict_variables`][options], it is possible to turn on
|
||||
[_Strict Variables_][strict variables] mode.
|
||||
|
||||
When [strict variables] mode is active, accessing a variable not previously defined within
|
||||
the same script directly causes a parse error when compiling the script.
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
|
||||
print(x); // prints 42
|
||||
|
||||
print(foo); // <- parse error under strict variables mode:
|
||||
// variable 'foo' is undefined
|
||||
```
|
||||
|
||||
```admonish tip.small
|
||||
|
||||
Turn on [strict variables] mode if no [`Scope`] is to be provided for script evaluation runs.
|
||||
This way, variable access errors are caught during compile time instead of runtime.
|
||||
```
|
62
rhai_engine/rhaibook/language/while.md
Normal file
62
rhai_engine/rhaibook/language/while.md
Normal file
@@ -0,0 +1,62 @@
|
||||
While Loop
|
||||
==========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
`while` loops follow C syntax.
|
||||
|
||||
Like C, `continue` can be used to skip to the next iteration, by-passing all following statements;
|
||||
`break` can be used to break out of the loop unconditionally.
|
||||
|
||||
~~~admonish tip.small "Tip: Disable `while` loops"
|
||||
|
||||
`while` loops can be disabled via [`Engine::set_allow_looping`][options].
|
||||
~~~
|
||||
|
||||
```rust
|
||||
let x = 10;
|
||||
|
||||
while x > 0 {
|
||||
x -= 1;
|
||||
if x < 6 { continue; } // skip to the next iteration
|
||||
print(x);
|
||||
if x == 5 { break; } // break out of while loop
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
While Expression
|
||||
----------------
|
||||
|
||||
Like Rust, `while` statements can also be used as _expressions_.
|
||||
|
||||
The `break` statement takes an optional expression that provides the return value.
|
||||
|
||||
The default return value of a `while` expression is [`()`].
|
||||
|
||||
~~~admonish tip.small "Tip: Disable all loop expressions"
|
||||
|
||||
Loop expressions can be disabled via [`Engine::set_allow_loop_expressions`][options].
|
||||
~~~
|
||||
|
||||
```js
|
||||
let x = 0;
|
||||
|
||||
// 'while' can be used just like an expression
|
||||
let result = while x < 100 {
|
||||
if is_magic_number(x) {
|
||||
// if the 'while' loop breaks here, return a specific value
|
||||
break get_magic_result(x);
|
||||
}
|
||||
|
||||
x += 1;
|
||||
|
||||
// ... if the 'while' loop exits here, the return value is ()
|
||||
};
|
||||
|
||||
if result == () {
|
||||
print("Magic number not found!");
|
||||
} else {
|
||||
print(`Magic result = ${result}!`);
|
||||
}
|
||||
```
|
Reference in New Issue
Block a user