reorganize module
This commit is contained in:
@@ -1,258 +0,0 @@
|
||||
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.
|
||||
|
||||
[`type_of()`](type-of.md) an array returns `"array"`.
|
||||
|
||||
|
||||
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_ `]`
|
||||
|
||||
|
||||
Built-in Functions
|
||||
------------------
|
||||
|
||||
The following methods 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](ranges.md) 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](fn-ptr.md) 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](fn-ptr.md) to predicate (usually a [closure](fn-closure.md)) | 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](ranges.md) 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](fn-ptr.md) to predicate (usually a [closure](fn-closure.md)) | 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](ranges.md) 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](ranges.md) 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](fn-ptr.md) to predicate (usually a [closure](fn-closure.md)) | 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 is used for comparison |
|
||||
| `index_of` | <ol><li>element to find (not a [function pointer](fn-ptr.md))</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](fn-ptr.md) to predicate (usually a [closure](fn-closure.md))</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>`this`: array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `find` | <ol><li>[function pointer](fn-ptr.md) to predicate (usually a [closure](fn-closure.md))</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](fn-ptr.md) to predicate (usually a [closure](fn-closure.md))</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](fn-ptr.md) to predicate (usually a [closure](fn-closure.md)); 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](fn-ptr.md) to conversion function (usually a [closure](fn-closure.md)) | 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](fn-ptr.md) to accumulator function (usually a [closure](fn-closure.md))</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>array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `reduce_rev` | <ol><li>[function pointer](fn-ptr.md) to accumulator function (usually a [closure](fn-closure.md))</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>array element</li><li>_(optional)_ index position</li></ol> |
|
||||
| `some` | [function pointer](fn-ptr.md) to predicate (usually a [closure](fn-closure.md)) | 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](fn-ptr.md) to predicate (usually a [closure](fn-closure.md)) | 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](fn-ptr.md) to a comparison function (usually a [closure](fn-closure.md)) | 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 (integers, floating-point, decimal, [string](strings-chars.md), [character](strings-chars.md), `bool`, `()`) |
|
||||
|
||||
|
||||
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.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 == []
|
||||
```
|
@@ -1,86 +0,0 @@
|
||||
Compound Assignments
|
||||
====================
|
||||
|
||||
Compound assignments are [assignments](assignments.md) with a [binary operator](operators.md) 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](overload.md) 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;
|
||||
```
|
@@ -1,121 +0,0 @@
|
||||
Assignments
|
||||
===========
|
||||
|
||||
Value assignments to [variables](variables.md) 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](variables.md), 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](arrays.md)).
|
||||
|
||||
```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 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](functions.md).
|
||||
|
||||
```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](object-maps.md) 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; }
|
||||
```
|
@@ -1,123 +0,0 @@
|
||||
Integer as Bit-Fields
|
||||
=====================
|
||||
|
||||
```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](ranges.md) is used, the bits within the [range](ranges.md) 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.
|
||||
|
||||
|
||||
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 one less than the number of bits for the
|
||||
system integer type (usually 63).
|
||||
|
||||
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](ranges.md) 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 negative the number of bits for the
|
||||
system integer type (usually −64).
|
||||
```
|
||||
|
||||
|
||||
Bit-Field Functions
|
||||
-------------------
|
||||
|
||||
The following standard functions operate on bit-fields.
|
||||
|
||||
| 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](ranges.md) 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](ranges.md) 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](ranges.md) 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}`)
|
||||
}
|
||||
```
|
@@ -1,192 +0,0 @@
|
||||
BLOB's
|
||||
======
|
||||
|
||||
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.
|
||||
|
||||
[`type_of()`](type-of.md) a BLOB returns `"blob"`.
|
||||
|
||||
|
||||
Element Access Syntax
|
||||
---------------------
|
||||
|
||||
### From beginning
|
||||
|
||||
Like [arrays](arrays.md), 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 integer.
|
||||
|
||||
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 32-bit) 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](strings-chars.md) as a
|
||||
byte stream.
|
||||
|
||||
Use the `write_ascii` method to write ASCII [strings](strings-chars.md) into any specific
|
||||
[range](ranges.md) 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 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](arrays.md) of integers |
|
||||
| `as_string` | _none_ | converts the BLOB into a [string](strings-chars.md) (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](strings-chars.md) to append</li></ol> | concatenates a [string/character](strings-chars.md) (as UTF-8 encoded byte-stream) to the end of the BLOB |
|
||||
| `+` operator | <ol><li>first BLOB</li><li>[string](strings-chars.md) to append</li></ol> | creates a new [string](strings-chars.md) by concatenating the BLOB (as UTF-8 encoded byte-stream) with the the [string](strings-chars.md) |
|
||||
| `+` operator | <ol><li>[string](strings-chars.md)</li><li>BLOB to append</li></ol> | creates a new [string](strings-chars.md) by concatenating the BLOB (as UTF-8 encoded byte-stream) to the end of the [string](strings-chars.md) |
|
||||
| `+` 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](ranges.md) 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](ranges.md) 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](ranges.md) 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](ranges.md) 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 32-bit), 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](ranges.md) of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under 32-bit) | 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 32-bit), 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](ranges.md) of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under 32-bit) | 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` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to parse, 8 if > 8 (4 under 32-bit), 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` | [range](ranges.md) of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under 32-bit) | 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` | <ol><li>start position, counting from end if < 0, end if ≥ length</li><li>number of bytes to parse, 8 if > 8 (4 under 32-bit), 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` | [range](ranges.md) of bytes to parse, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under 32-bit) | 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 32-bit), 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](ranges.md) of bytes to write, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under 32-bit)</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 32-bit), 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](ranges.md) of bytes to write, from beginning if ≤ 0, to end if ≥ length (up to 8 bytes, 4 under 32-bit)</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](strings-chars.md) to write</li></ol> | writes a [string](strings-chars.md) to the particular offset in UTF-8 encoding |
|
||||
| `write_utf8` | <ol><li>[range](ranges.md) of bytes to write, from beginning if ≤ 0, to end if ≥ length, to end if ≥ length</li><li>[string](strings-chars.md) to write</li></ol> | writes a [string](strings-chars.md) 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](strings-chars.md) to write, none if ≤ 0, to end if ≥ length</li><li>[string](strings-chars.md) to write</li></ol> | writes a [string](strings-chars.md) to the particular offset in 7-bit ASCII encoding (non-ASCII [characters](strings-chars.md) are skipped) |
|
||||
| `write_ascii` | <ol><li>[range](ranges.md) of bytes to write, from beginning if ≤ 0, to end if ≥ length, to end if ≥ length</li><li>[string](strings-chars.md) to write</li></ol> | writes a [string](strings-chars.md) to the particular offset in 7-bit ASCII encoding (non-ASCII [characters](strings-chars.md) are skipped) |
|
@@ -1,113 +0,0 @@
|
||||
Comments
|
||||
========
|
||||
|
||||
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:
|
||||
/*/*/*/*/**/*/*/*/*/
|
||||
*/
|
||||
```
|
||||
|
||||
|
||||
Doc-Comments
|
||||
============
|
||||
|
||||
Comments starting with `///` (three slashes) or `/**` (two asterisks) are _doc-comments_.
|
||||
|
||||
Doc-comments can only appear in front of [function](functions.md) definitions, not any other elements.
|
||||
|
||||
```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. *
|
||||
* *
|
||||
****************************************/
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
Module Documentation
|
||||
====================
|
||||
|
||||
Comment lines starting with `//!` make up the _module documentation_.
|
||||
|
||||
They are used to document the containing [module](modules/index.md) –
|
||||
or for a Rhai script file, to document the file itself.
|
||||
|
||||
```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.
|
||||
```
|
@@ -1,44 +0,0 @@
|
||||
Constants
|
||||
=========
|
||||
|
||||
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](variables.md),
|
||||
but as a convention are often named with all-capital letters.
|
||||
```
|
||||
|
||||
|
||||
Automatic Global Module
|
||||
-----------------------
|
||||
|
||||
When a [constant](constants.md) is declared at global scope, it is added to a special
|
||||
[module](modules/index.md) called `global`.
|
||||
|
||||
[Functions](functions.md) can access those [constants](constants.md) via the special `global`
|
||||
[module](modules/index.md).
|
||||
|
||||
```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'
|
||||
}
|
||||
```
|
@@ -1,95 +0,0 @@
|
||||
Value Conversions
|
||||
=================
|
||||
|
||||
|
||||
Convert Between Integer and Floating-Point
|
||||
------------------------------------------
|
||||
|
||||
| Function | From type | To type |
|
||||
| ------------ | :---------------------: | :------------: |
|
||||
| `to_int` | floating-point, decimal | integer |
|
||||
| `to_float` | integer, decimal | floating-point |
|
||||
| `to_decimal` | integer, floating-point | 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](strings-chars.md) | integer |
|
||||
| `parse_int` with radix 2-36 | [string](strings-chars.md) | integer (specified radix) |
|
||||
| `parse_float` | [string](strings-chars.md) | floating-point |
|
||||
| `parse_decimal` | [string](strings-chars.md) | 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` | integer | [string](strings-chars.md) | binary (i.e. only `1` and `0`) |
|
||||
| `to_octal` | integer | [string](strings-chars.md) | octal (i.e. `0` ... `7`) |
|
||||
| `to_hex` | integer | [string](strings-chars.md) | 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";
|
||||
```
|
@@ -1,58 +0,0 @@
|
||||
Do Loop
|
||||
=======
|
||||
|
||||
`do` loops have two opposite variants: `do` ... `while` and `do` ... `until`.
|
||||
|
||||
`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.
|
||||
|
||||
```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
|
||||
-------------
|
||||
|
||||
`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 `()`.
|
||||
|
||||
```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}!`);
|
||||
}
|
||||
```
|
@@ -1,242 +0,0 @@
|
||||
Dynamic Value Tag
|
||||
=================
|
||||
|
||||
Each dynamic value can contain a _tag_ that is `i32` and can contain any arbitrary 32-bit signed data.
|
||||
|
||||
On 32-bit targets, however, the tag is only `i16` (16-bit signed).
|
||||
|
||||
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](bit-fields.md)_ 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](functions.md).
|
||||
|
||||
```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
|
||||
|
||||
Rhai does not have _tuples_ (nor does JavaScript in this sense).
|
||||
|
||||
Similar to the JavaScript situation, practical alternatives using Rhai include returning an
|
||||
[object map](object-maps.md) or an [array](arrays.md).
|
||||
|
||||
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](functions.md), the tag value as a
|
||||
[bit-field](bit-fields.md) is an ideal container without resorting to a full-blown
|
||||
[object map](object-maps.md) or [array](arrays.md).
|
||||
|
||||
```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];
|
||||
```
|
@@ -1,47 +0,0 @@
|
||||
Dynamic Values
|
||||
==============
|
||||
|
||||
A dynamic value can be _any_ type.
|
||||
|
||||
```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()`](type-of.md) 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)}`)
|
||||
}
|
||||
```
|
@@ -1,70 +0,0 @@
|
||||
`eval` Function
|
||||
===============
|
||||
|
||||
Or "How to Shoot Yourself in the Foot even Easier"
|
||||
--------------------------------------------------
|
||||
|
||||
Saving the best for last, there is the ever-dreaded... `eval` [function](functions.md)!
|
||||
|
||||
```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](variables.md) 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](variables.md) and do other things normally forbidden inside
|
||||
a [function](functions.md) 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](functions.md) cannot be defined within an `eval` call, since [functions](functions.md)
|
||||
can only be defined at the _global_ level!
|
||||
~~~
|
@@ -1,203 +0,0 @@
|
||||
Closures
|
||||
========
|
||||
|
||||
Many functions in the standard API expect [function pointer](fn-ptr.md) 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](functions.md) only to dispatch them via
|
||||
single [function pointers](fn-ptr.md) – essentially, those [functions](functions.md) 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
|
||||
------
|
||||
|
||||
Closures 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_... `}`
|
||||
|
||||
|
||||
Rewrite Using Closures
|
||||
----------------------
|
||||
|
||||
The above can be rewritten using closures.
|
||||
|
||||
```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
|
||||
};
|
||||
```
|
||||
|
||||
Capture External Variables
|
||||
--------------------------
|
||||
|
||||
~~~admonish tip.side "Tip: `is_shared`"
|
||||
|
||||
Use `is_shared` to check whether a particular dynamic value is shared.
|
||||
~~~
|
||||
|
||||
Closures differ from standard functions because they can _captures_ [variables](variables.md) that
|
||||
are not defined within the current scope, but are instead defined in an external scope – i.e.
|
||||
where the it is created.
|
||||
|
||||
All [variables](variables.md) that are accessible during the time the closure is created are
|
||||
automatically captured when they are used, as long as they are not shadowed by local
|
||||
[variables](variables.md) defined within the function's.
|
||||
|
||||
The captured [variables](variables.md) are automatically converted into **reference-counted shared values**.
|
||||
|
||||
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](variables.md) that hold
|
||||
them go out of scope and no longer exist.
|
||||
|
||||
```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](variables.md) in closures.
|
||||
|
||||
It prints `9`, `9`, `9`, ... `9`, `9`, not `0`, `1`, `2`, ... `8`, `9`, because there is ever only
|
||||
_one_ captured [variable](variables.md), and all ten closures capture the _same_
|
||||
[variable](variables.md).
|
||||
|
||||
```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' point to the same variable!
|
||||
}
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish danger "Prevent data races"
|
||||
|
||||
Data races are possible in Rhai scripts.
|
||||
|
||||
Avoid performing a method call on a captured shared [variable](variables.md) (which essentially
|
||||
takes a mutable reference to the shared object) while using that same [variable](variables.md) 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' not shared, so no data races
|
||||
|
||||
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'
|
||||
```
|
||||
~~~
|
@@ -1,50 +0,0 @@
|
||||
Functions Metadata
|
||||
==================
|
||||
|
||||
{{#title Functions Metadata}}
|
||||
|
||||
The _metadata_ of a [function](functions.md) means all relevant information related to a
|
||||
[function's](functions.md) definition including:
|
||||
|
||||
1. Its callable name
|
||||
|
||||
2. Its access mode (public or [private](modules/export.md))
|
||||
|
||||
3. Its parameter names (if any)
|
||||
|
||||
4. Its purpose, in the form of [doc-comments](comments.md)
|
||||
|
||||
5. Usage notes, warnings, examples etc., in the form of [doc-comments](comments.md)
|
||||
|
||||
A [function's](functions.md) _signature_ encapsulates the first three pieces of information in a
|
||||
single concise line of definition:
|
||||
|
||||
> `[private]` _name_ `(`_param 1_`,` _param 2_`,` ... `,` _param n_ `)`
|
||||
|
||||
|
||||
Get Functions Metadata
|
||||
======================
|
||||
|
||||
The built-in [function](functions.md) `get_fn_metadata_list` returns an [array](arrays) of [object
|
||||
maps](object-maps.md), each containing the metadata of one script-defined [function](functions.md)
|
||||
in scope.
|
||||
|
||||
`get_fn_metadata_list` has a few versions taking different parameters:
|
||||
|
||||
| Signature | Description |
|
||||
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `get_fn_metadata_list()` | returns an [array](arrays.md) for _all_ script-defined [functions](functions.md) |
|
||||
| `get_fn_metadata_list(name)` | returns an [array](arrays.md) containing all script-defined [functions](functions.md) matching a specified name |
|
||||
| `get_fn_metadata_list(name, params)` | returns an [array](arrays.md) containing all script-defined [functions](functions.md) matching a specified name and accepting the specified number of parameters |
|
||||
|
||||
The return value is an [array](arrays.md) of [object maps](object-maps.md) containing the following fields.
|
||||
|
||||
| Field | Type | Optional? | Description |
|
||||
| -------------- | :-----------------------------------------------: | :-------: | ----------------------------------------------------------------------------------------------------- |
|
||||
| `namespace` | [string](strings-chars.md) | **yes** | the module _namespace_ if the [function](functions.md) is defined within a [module](modules/index.md) |
|
||||
| `access` | [string](strings-chars.md) | no | `"public"` if the function is public,<br/>`"private"` if it is [private](modules/export.md) |
|
||||
| `name` | [string](strings-chars.md) | no | [function](functions.md) name |
|
||||
| `params` | [array](arrays.md) of [strings](strings-chars.md) | no | parameter names |
|
||||
| `this_type` | [string](strings-chars.md) | **yes** | restrict the type of `this` if the [function](functions.md) is a [method](fn_methods.md) |
|
||||
| `is_anonymous` | `bool` | no | is this [function](functions.md) an [anonymous function](fn-anon.md)? |
|
||||
| `comments` | [array](arrays.md) of [strings](strings-chars.md) | **yes** | [doc-comments](comments.md), if any, one per line |
|
@@ -1,195 +0,0 @@
|
||||
`this` – Simulating an Object Method
|
||||
==========================================
|
||||
|
||||
```admonish warning.side "Functions are pure"
|
||||
|
||||
The only way for a script-defined [function](functions.md) to change an external value is via `this`.
|
||||
```
|
||||
|
||||
Arguments passed to script-defined [functions](functions.md) are always by _value_ because
|
||||
[functions](functions.md) are _pure_.
|
||||
|
||||
However, [functions](functions.md) can also be called in _method-call_ style:
|
||||
|
||||
> _object_ `.` _method_ `(` _parameters_ ... `)`
|
||||
|
||||
When a [function](functions.md) 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](https://en.wikipedia.org/wiki/Elvis_operator) 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.
|
||||
```
|
||||
|
||||
In many cases it may be desirable to implement _methods_ for different custom types using
|
||||
script-defined [functions](functions.md).
|
||||
|
||||
### The Problem
|
||||
|
||||
Doing so is brittle and requires a lot of type checking code because there can only be one
|
||||
[function](functions.md) 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](functions.md) 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_ ... `) {` ... `}`
|
||||
|
||||
~~~admonish warning.small "Type name must be the same as `type_of`"
|
||||
|
||||
The _type name_ specified in front of the [function](functions.md) name must match the output of
|
||||
[`type_of`](type-of.md) 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](functions.md) [imported](modules/import.md)
|
||||
from [modules](modules/index.md).
|
||||
|
||||
```js
|
||||
import "my_module" as foo;
|
||||
|
||||
let x = 42;
|
||||
|
||||
x.foo::change_value(1); // <- syntax error
|
||||
```
|
||||
|
||||
### The Solution
|
||||
|
||||
In order to call a [module](modules/index.md) [function](functions.md) 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;
|
||||
```
|
@@ -1,247 +0,0 @@
|
||||
Function Pointers
|
||||
=================
|
||||
|
||||
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](strings-chars.md) 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](functions.md)
|
||||
`foo` is a chore, so there is a short-hand available.
|
||||
|
||||
A function pointer to any _script-defined_ [function](functions.md) _within the same script_ can be
|
||||
obtained simply by referring to the [function's](functions.md) 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](functions.md) as
|
||||
[closure](fn-closure.md) 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 operate on function pointers.
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ---------------------------------- | ------------ | -------------------------------------------------------------------------------------------- |
|
||||
| `name` method and property | _none_ | returns the name of the [function](functions.md) encapsulated by the function pointer |
|
||||
| `is_anonymous` method and property | _none_ | does the function pointer refer to an [anonymous function](fn-anon.md)? |
|
||||
| `call` | _arguments_ | calls the [function](functions.md) 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](functions.md) to call.
|
||||
They do not hold the actual [functions](functions.md).
|
||||
|
||||
The actual [function](functions.md) must be defined in the appropriate namespace for the call to
|
||||
succeed.
|
||||
```
|
||||
|
||||
~~~admonish warning "Global Namespace Only"
|
||||
|
||||
Because of their dynamic nature, function pointers cannot refer to functions in
|
||||
[`import`](modules/import.md)-ed [modules](modules/index.md).
|
||||
|
||||
They can only refer to [functions](functions.md) defined globally within the script
|
||||
or a built-in function.
|
||||
|
||||
```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.md) 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).
|
||||
|
||||
|
||||
Currying
|
||||
--------
|
||||
|
||||
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
|
||||
```
|
@@ -1,253 +0,0 @@
|
||||
For Loop
|
||||
========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Iterating through a numeric [range](ranges.md) or an [array](arrays.md), or any iterable type,
|
||||
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_ `{` ... `}`
|
||||
|
||||
|
||||
Break or Continue
|
||||
-----------------
|
||||
|
||||
`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
|
||||
--------------
|
||||
|
||||
`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 `()`.
|
||||
|
||||
```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
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Iterate Through Arrays
|
||||
----------------------
|
||||
|
||||
Iterating through an [array](arrays.md) 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](strings-chars.md) yields individual [characters](strings-chars.md).
|
||||
|
||||
The `chars` method also allow iterating through characters in a [string](strings-chars.md),
|
||||
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](ranges.md) 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](ranges.md) are created via the `..` (exclusive) and `..=` (inclusive) operators.
|
||||
|
||||
The `range` function similarly creates exclusive [ranges](ranges.md), 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](bit-fields.md).
|
||||
|
||||
`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](ranges.md) 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](arrays.md) containing cloned _copies_
|
||||
of all property names and values of an [object map](object-maps.md), respectively.
|
||||
|
||||
These [arrays](arrays.md) 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);
|
||||
}
|
||||
```
|
@@ -1,210 +0,0 @@
|
||||
Functions
|
||||
=========
|
||||
|
||||
Rhai supports defining functions in script via the `fn` keyword.
|
||||
|
||||
Valid function names are the same as valid [variable](variables.md) 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: `is_def_fn`"
|
||||
|
||||
Use `is_def_fn` 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
|
||||
---------------
|
||||
|
||||
The last statement of a block is _always_ the block's return value regardless of whether it is
|
||||
terminated with a semicolon `;`.
|
||||
|
||||
```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.
|
||||
|
||||
```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](variables.md) 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 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](modules/index.md) [imported](modules/import.md) 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](constants.md) is declared at global scope, it is added to a special
|
||||
[module](modules/index.md) called [`global`](global.md).
|
||||
|
||||
Functions can access those [constants](constants.md) via the special [`global`](global.md)
|
||||
[module](modules/index.md).
|
||||
|
||||
```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 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.
|
||||
```
|
@@ -1,23 +0,0 @@
|
||||
Properties
|
||||
==========
|
||||
|
||||
Data types typically expose properties, which can be accessed in a Rust-like syntax:
|
||||
|
||||
> _object_ `.` _property_
|
||||
>
|
||||
> _object_ `.` _property_ `=` _value_ `;`
|
||||
|
||||
A runtime error is raised if the property does not exist for the object's data type.
|
||||
|
||||
|
||||
Elvis Operator
|
||||
--------------
|
||||
|
||||
The [_Elvis operator_](https://en.wikipedia.org/wiki/Elvis_operator) can be used to short-circuit
|
||||
processing if the object itself is `()`.
|
||||
|
||||
> `// returns () if object is ()`
|
||||
> _object_ `?.` _property_
|
||||
>
|
||||
> `// no action if object is ()`
|
||||
> _object_ `?.` _property_ `=` _value_ `;`
|
@@ -1,85 +0,0 @@
|
||||
If Statement
|
||||
============
|
||||
|
||||
`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.
|
||||
|
||||
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
|
||||
=============
|
||||
|
||||
`if` statements can also be used as _expressions_, replacing the `? :` conditional operators in
|
||||
other C-like languages.
|
||||
|
||||
```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
|
||||
}
|
||||
```
|
||||
~~~
|
@@ -1,8 +0,0 @@
|
||||
Rhai Language Reference
|
||||
=======================
|
||||
|
||||
{{#title Rhai Language Reference}}
|
||||
|
||||

|
||||
|
||||
This is a stand-alone reference for the Rhai scripting language.
|
@@ -1,30 +0,0 @@
|
||||
Indexing
|
||||
========
|
||||
|
||||
```admonish tip.side "Tip: Non-integer index"
|
||||
|
||||
Some data types take an index that is not an integer.
|
||||
For example, [object map](object-maps.md) indices are [strings](strings-chars.md).
|
||||
```
|
||||
|
||||
Some data types, such as [arrays](arrays.md), can be _indexed_ via a Rust-like syntax:
|
||||
|
||||
> _object_ `[` _index_ `]`
|
||||
>
|
||||
> _object_ `[` _index_ `]` `=` _value_ `;`
|
||||
|
||||
Usually, a runtime error is raised if the index value is out of bounds or does not exist for the
|
||||
object's data type.
|
||||
|
||||
|
||||
Elvis Notation
|
||||
--------------
|
||||
|
||||
The [_Elvis notation_](https://en.wikipedia.org/wiki/Elvis_operator) is similar except that it
|
||||
returns `()` if the object itself is `()`.
|
||||
|
||||
> `// returns () if object is ()`
|
||||
> _object_ `?[` _index_ `]`
|
||||
>
|
||||
> `// no action if object is ()`
|
||||
> _object_ `?[` _index_ `]` `=` _value_ `;`
|
@@ -1,27 +0,0 @@
|
||||
Keywords
|
||||
========
|
||||
|
||||
The following are reserved keywords in Rhai.
|
||||
|
||||
| Active keywords | Reserved keywords | Usage |
|
||||
| -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------- |
|
||||
| `true`, `false` | | constants |
|
||||
| [`let`](variable.md), [`const`](constant.md) | `var`, `static` | variables |
|
||||
| `is_shared` | | _shared_ values |
|
||||
| | `is` | type checking |
|
||||
| [`if`](if.md), [`else`](if.md) | `goto` | control flow |
|
||||
| [`switch`](switch.md) | `match`, `case` | switching and matching |
|
||||
| [`do`](do.md), [`while`](while.md), [`loop`](loop.md), `until`, [`for`](for.md), [`in`](operators.md), `continue`, `break` | | looping |
|
||||
| [`fn`](functions.md), [`private`](modules/export.md), `is_def_fn`, `this` | `public`, `protected`, `new` | [functions](functions.md) |
|
||||
| [`return`](return.md) | | return values |
|
||||
| [`throw`](throw.md), [`try`](try-catch.md), [`catch`](try-catch.md) | | [throw/catch](try-catch.md) exceptions |
|
||||
| [`import`](modules/import.md), [`export`](modules/export.md), `as` | `use`, `with`, `module`, `package`, `super` | [modules](modules/index.md) |
|
||||
| [`global`](global.md) | | automatic global [module](modules/index.md) |
|
||||
| [`Fn`](fn-ptr.md), `call`, [`curry`](fn-curry.md) | | [function pointers](fn-ptr.md) |
|
||||
| | `spawn`, `thread`, `go`, `sync`, `async`, `await`, `yield` | threading/async |
|
||||
| [`type_of`](type-of.md), [`print`](print-debug.md), [`debug`](print-debug.md), [`eval`](eval.md), `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.
|
||||
```
|
@@ -1,59 +0,0 @@
|
||||
Infinite Loop
|
||||
=============
|
||||
|
||||
Infinite loops follow Rust syntax.
|
||||
|
||||
`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.
|
||||
|
||||
```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
|
||||
---------------
|
||||
|
||||
`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 `()`.
|
||||
|
||||
```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}!`);
|
||||
}
|
||||
```
|
@@ -1,18 +0,0 @@
|
||||
Methods
|
||||
=======
|
||||
|
||||
Data types may have _methods_ that can be called:
|
||||
|
||||
> _object_ `.` _method_ `(` _parameters_ ... `)`
|
||||
|
||||
A runtime error is raised if the appropriate method does not exist for the object's data type.
|
||||
|
||||
|
||||
Elvis Operator
|
||||
--------------
|
||||
|
||||
The [_Elvis_ operator](https://en.wikipedia.org/wiki/Elvis_operator) can be used to short-circuit
|
||||
the method call when the object itself is `()`.
|
||||
|
||||
> `// method is not called if object is ()`
|
||||
> _object_ `?.` _method_ `(` _parameters_ ... `)`
|
@@ -1,113 +0,0 @@
|
||||
Export Variables, Functions and Sub-Modules From a Script
|
||||
=========================================================
|
||||
|
||||
The easiest way to expose a collection of [functions](../functions.md) as a self-contained [module](index.md)
|
||||
is to do it via a Rhai script itself.
|
||||
|
||||
The script text is evaluated.
|
||||
|
||||
[Variables](../variables.md) are then selectively exposed via the `export` statement.
|
||||
|
||||
[Functions](../functions.md) defined by the script are automatically exported, unless marked as `private`.
|
||||
|
||||
Modules loaded within this [module](index.md) at the global level become _sub-modules_ and are also
|
||||
automatically exported.
|
||||
|
||||
|
||||
Export Global Constants
|
||||
-----------------------
|
||||
|
||||
The `export` statement, which can only be at global level, exposes a selected
|
||||
[variable](../variables.md) as member of a [module](index.md).
|
||||
|
||||
[Variables](../variables.md) not exported are _private_ and hidden. They are merely used to
|
||||
initialize the [module](index.md), but cannot be accessed from outside.
|
||||
|
||||
Everything exported from a [module](index.md) is **[constant](../constants.md)** (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.wide "Private functions"
|
||||
|
||||
`private` [functions](../functions.md) are commonly called within the [module](index.md) only.
|
||||
They cannot be accessed otherwise.
|
||||
```
|
||||
|
||||
All [functions](../functions.md) are automatically exported, _unless_ it is explicitly opt-out with
|
||||
the `private` prefix.
|
||||
|
||||
[Functions](../functions.md) 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](index.md) are automatically exported as sub-modules.
|
||||
|
||||
~~~admonish tip.small "Tip: Skip exporting a module"
|
||||
|
||||
To prevent a [module](index.md) 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
|
||||
}
|
||||
```
|
||||
~~~
|
@@ -1,121 +0,0 @@
|
||||
Import a Module
|
||||
===============
|
||||
|
||||
`import` Statement
|
||||
------------------
|
||||
|
||||
```admonish tip.side.wide "Tip"
|
||||
|
||||
A [module](index.md) 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](index.md) 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](index.md) 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](index.md) during every iteration of the loop!
|
||||
~~~
|
||||
|
||||
~~~admonish danger "Recursive imports"
|
||||
|
||||
Beware of _import cycles_ – i.e. recursively loading the same [module](index.md).
|
||||
This is a sure-fire way to cause a stack overflow error.
|
||||
|
||||
For instance, importing itself always causes an infinite recursion:
|
||||
|
||||
```js
|
||||
┌────────────┐
|
||||
│ hello.rhai │
|
||||
└────────────┘
|
||||
|
||||
import "hello" as foo; // import itself - infinite recursion!
|
||||
|
||||
foo::do_something();
|
||||
```
|
||||
|
||||
[Modules](index.md) 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();
|
||||
```
|
||||
~~~
|
@@ -1,12 +0,0 @@
|
||||
Modules
|
||||
=======
|
||||
|
||||
Rhai allows organizing code into _modules_.
|
||||
|
||||
A module holds a collection of [functions](../functions.md), [constants](../constants.md) and sub-modules.
|
||||
|
||||
It may encapsulates a Rhai script together with the [functions](../functions.md) and
|
||||
[constants](../constants.md) defined by that script.
|
||||
|
||||
Other scripts can then load this module and use the [functions](../functions.md) and
|
||||
[constants](../constants.md) exported as if they were defined inside the same script.
|
@@ -1,103 +0,0 @@
|
||||
Numeric Functions
|
||||
=================
|
||||
|
||||
{{#title Numeric Functions}}
|
||||
|
||||
Integer Functions
|
||||
-----------------
|
||||
|
||||
The following standard functions operate on integers only.
|
||||
|
||||
| Function | Description |
|
||||
| ----------------------------- | ---------------------------------------------------------------- |
|
||||
| `is_odd` method and property | returns `true` if the value is an odd number, otherwise `false` |
|
||||
| `is_even` method and property | returns `true` if the value is an even number, otherwise `false` |
|
||||
| `min` | returns the smaller of two numbers, the first number if equal |
|
||||
| `max` | returns the larger of two numbers, the first number if equal |
|
||||
| `to_float` | convert the value into `f64` (`f32` under 32-bit) |
|
||||
| `to_decimal` | convert the value into decimal |
|
||||
|
||||
|
||||
Signed Numeric Functions
|
||||
------------------------
|
||||
|
||||
The following standard functions operate on signed numbers (including floating-point and decimal) only.
|
||||
|
||||
| Function | Description |
|
||||
| ----------------------------- | ------------------------------------------------------ |
|
||||
| `abs` | absolute value |
|
||||
| `sign` | returns −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 operate on floating-point and decimal numbers only.
|
||||
|
||||
| Category | 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_), `log` (base 10) |
|
||||
| Logarithmic | **no** | `log(`_x_`,`_base_`)` |
|
||||
| Rounding | yes | `floor`, `ceiling`, `round`, `int`, `fraction` methods and properties |
|
||||
| Conversion | yes | [`to_int`](convert.md), [`to_decimal`](convert.md), [`to_float`](convert.md) |
|
||||
| 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 operate on decimal numbers only.
|
||||
|
||||
| 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 parse numbers.
|
||||
|
||||
| Function | Description |
|
||||
| ----------------------------- | ----------------------------------------------------------------------- |
|
||||
| [`parse_int`](convert.md) | converts a [string](strings-chars.md) to integer with an optional radix |
|
||||
| [`parse_float`](convert.md) | converts a [string](strings-chars.md) to floating-point |
|
||||
| [`parse_decimal`](convert.md) | converts a [string](strings-chars.md) to decimal |
|
||||
|
||||
|
||||
Formatting Functions
|
||||
--------------------
|
||||
|
||||
The following standard functions convert integer numbers into a [string](strings-chars.md) of hex,
|
||||
octal or binary representations.
|
||||
|
||||
| Function | Description |
|
||||
| ------------------------- | ------------------------------------ |
|
||||
| [`to_binary`](convert.md) | converts an integer number to binary |
|
||||
| [`to_octal`](convert.md) | converts an integer number to octal |
|
||||
| [`to_hex`](convert.md) | converts an integer number to hex |
|
||||
|
||||
|
||||
Floating-point Constants
|
||||
------------------------
|
||||
|
||||
The following functions return standard mathematical constants.
|
||||
|
||||
| Function | Description |
|
||||
| -------- | ------------------------- |
|
||||
| `PI` | returns the value of π |
|
||||
| `E` | returns the value of _e_ |
|
@@ -1,136 +0,0 @@
|
||||
Numeric Operators
|
||||
=================
|
||||
|
||||
{{#title Numeric Operators}}
|
||||
|
||||
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 | Integer | Floating-point | Decimal |
|
||||
| :-------------------------------: | ---------------------------------------------------------------- | :----------------: | :-----: | :--------------------: | :---------------: |
|
||||
| `+`, `+=` | plus | numeric | yes | yes, also integer | yes, also integer |
|
||||
| `-`, `-=` | minus | numeric | yes | yes, also integer | yes, also integer |
|
||||
| `*`, `*=` | multiply | numeric | yes | yes, also integer | yes, also integer |
|
||||
| `/`, `/=` | divide (integer division if acting on integer types) | numeric | yes | yes, also integer | yes, also integer |
|
||||
| `%`, `%=` | modulo (remainder) | numeric | yes | yes, also integer | yes, also integer |
|
||||
| `**`, `**=` | 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 integer | yes, also integer |
|
||||
| `!=` | not equals to | `bool` | yes | yes, also integer | yes, also integer |
|
||||
| `>` | greater than | `bool` | yes | yes, also integer | yes, also integer |
|
||||
| `>=` | greater than or equals to | `bool` | yes | yes, also integer | yes, also integer |
|
||||
| `<` | less than | `bool` | yes | yes, also integer | yes, also integer |
|
||||
| `<=` | less than or equals to | `bool` | yes | yes, also integer | yes, also integer |
|
||||
| `..` | exclusive range | [range](ranges.md) | yes | **no** | **no** |
|
||||
| `..=` | inclusive range | [range](ranges.md) | 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 integer
|
||||
for the other operand and the result is floating-point.
|
||||
|
||||
```rust
|
||||
let x = 41.0 + 1; // float + integer
|
||||
|
||||
type_of(x) == "f64"; // result is float
|
||||
|
||||
let x = 21 * 2.0; // float * integer
|
||||
|
||||
type_of(x) == "f64";
|
||||
|
||||
(x == 42) == true; // float == integer
|
||||
|
||||
(10 < x) == true; // integer < float
|
||||
```
|
||||
|
||||
|
||||
Decimal Interoperates with Integers
|
||||
-----------------------------------
|
||||
|
||||
When one of the operands to a binary arithmetic operator is decimal,
|
||||
it works with integer for the other operand and the result is decimal.
|
||||
|
||||
```rust
|
||||
let d = parse_decimal("2");
|
||||
|
||||
let x = d + 1; // decimal + integer
|
||||
|
||||
type_of(x) == "decimal"; // result is decimal
|
||||
|
||||
let x = 21 * d; // decimal * integer
|
||||
|
||||
type_of(x) == "decimal";
|
||||
|
||||
(x == 42) == true; // decimal == integer
|
||||
|
||||
(10 < x) == true; // integer < 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)
|
||||
```
|
@@ -1,56 +0,0 @@
|
||||
Numbers
|
||||
=======
|
||||
|
||||
|
||||
Integers
|
||||
--------
|
||||
|
||||
Integer numbers follow C-style format with support for decimal, binary (`0b`), octal (`0o`) and hex (`0x`) notations.
|
||||
|
||||
Integers can also be conveniently manipulated as [bit-fields](bit-fields.md).
|
||||
|
||||
|
||||
Floating-Point Numbers
|
||||
----------------------
|
||||
|
||||
Both decimal and scientific notations can be used to represent floating-point numbers.
|
||||
|
||||
|
||||
Decimal Numbers
|
||||
---------------
|
||||
|
||||
When rounding errors cannot be accepted, such as in financial calculations, use the 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 |
|
||||
| ------------------ | ------------------------- |
|
||||
| `_123` | _improper separator_ |
|
||||
| `123_345`, `-42` | decimal |
|
||||
| `0o07_76` | octal |
|
||||
| `0xab_cd_ef` | hex |
|
||||
| `0b0101_1001` | binary |
|
||||
| `123._456` | _improper separator_ |
|
||||
| `123_456.78_9` | normal floating-point |
|
||||
| `-42.` | ending with decimal point |
|
||||
| `123_456_.789e-10` | scientific notation |
|
||||
| `.456` | _missing leading `0`_ |
|
||||
| `123.456e_10` | _improper separator_ |
|
||||
| `123.e-10` | _missing decimal `0`_ |
|
||||
|
||||
|
||||
Floating-Point vs. Decimal
|
||||
--------------------------
|
||||
|
||||
Decimal numbers 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 numbers take up more space (16 bytes each) than a standard floating-point number (4-8 bytes)
|
||||
and is much slower in calculations due to the lack of CPU hardware support. Use it only when
|
||||
necessary.
|
@@ -1,285 +0,0 @@
|
||||
Object Maps
|
||||
===========
|
||||
|
||||
Object maps are hash dictionaries. Properties are all dynamic values and can be freely added and retrieved.
|
||||
|
||||
[`type_of()`](type-of.md) an object map returns `"map"`.
|
||||
|
||||
~~~admonish tip "Tip: Object maps are _FAST_"
|
||||
|
||||
Normally, when [properties](getters-setters.md) 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
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
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](variables.md), or a [string literal](../appendix/literals.md) without interpolation.
|
||||
|
||||
|
||||
Property Access Syntax
|
||||
----------------------
|
||||
|
||||
### Dot notation
|
||||
|
||||
The _dot notation_ allows only property names that follow the same naming rules as
|
||||
[variables](variables.md).
|
||||
|
||||
> _object_ `.` _property_
|
||||
|
||||
### Elvis notation
|
||||
|
||||
The [_Elvis notation_](https://en.wikipedia.org/wiki/Elvis_operator) 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](strings-chars.md)).
|
||||
|
||||
> _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 == ()
|
||||
```
|
||||
|
||||
### Check for property existence
|
||||
|
||||
Use the [`in`](operators.md#in-operator) 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_](https://en.wikipedia.org/wiki/Elvis_operator) (`?.`) 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](operators.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 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) |
|
||||
| `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? |
|
||||
| `drain` | [function pointer](fn-ptr.md) to predicate (usually a [closure](fn-closure.md)) | 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](fn-ptr.md) to predicate (usually a [closure](fn-closure.md)) | 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](fn-ptr.md) to predicate (usually a [closure](fn-closure.md)) | 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> |
|
||||
| `keys` | _none_ | returns an [array](arrays.md) of all the property names (in random order) |
|
||||
| `values` | _none_ | returns an [array](arrays.md) of all the property values (in random order) |
|
||||
| `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;
|
||||
```
|
||||
|
||||
|
||||
Special Support for OOP
|
||||
------------------------
|
||||
|
||||
Object maps can be used to simulate object-oriented programming (OOP) by storing data as properties
|
||||
and methods as properties holding [function pointers](fn-ptr.md).
|
||||
|
||||
If an object map's property holds a [function pointer](fn-ptr.md), 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](fn-ptr.md) or a [closure](fn-closure.md) 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
|
||||
```
|
@@ -1,253 +0,0 @@
|
||||
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](values-and-types.md).
|
||||
|
||||
|
||||
### Floating-point numbers interoperate with integers
|
||||
|
||||
Comparing a floating-point number 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 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](strings-chars.md) with a [character](strings-chars.md) is also supported, with
|
||||
the character first turned into a [string](strings-chars.md) 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
|
||||
```
|
||||
|
||||
|
||||
Boolean Operators
|
||||
=================
|
||||
|
||||
```admonish note.side
|
||||
|
||||
All boolean operators are [built in](../engine/builtin.md) 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 `()`.
|
||||
|
||||
This operator _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](object-maps.md) 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`](return.md)
|
||||
* [`throw`](throw.md)
|
||||
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
In Operator
|
||||
===========
|
||||
|
||||
```admonish question.side "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](ranges.md) | integer number |
|
||||
| [Array](arrays.md) | contained item |
|
||||
| [Object map](object-maps.md) | property name |
|
||||
| [String](strings-chars.md) | [sub-string](strings-chars.md) or [character](strings-chars.md) |
|
||||
|
||||
### 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
|
||||
```
|
@@ -1,36 +0,0 @@
|
||||
Function Overloading
|
||||
====================
|
||||
|
||||
{{#title Function Overloading}}
|
||||
|
||||
[Functions](functions.md) 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 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."
|
||||
```
|
@@ -1,16 +0,0 @@
|
||||
`print` and `debug`
|
||||
===================
|
||||
|
||||
The `print` and `debug` functions can be used to output values.
|
||||
|
||||
```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
|
||||
```
|
@@ -1,94 +0,0 @@
|
||||
Ranges
|
||||
======
|
||||
|
||||
|
||||
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.
|
||||
|
||||
[`type_of()`](type-of.md) an exclusive range returns `"range"`.
|
||||
|
||||
### Inclusive range
|
||||
|
||||
> _start_ `..=` _end_
|
||||
|
||||
An _inclusive_ range includes the last (i.e. "end") value.
|
||||
|
||||
[`type_of()`](type-of.md) an inclusive range returns `"range="`.
|
||||
|
||||
|
||||
Usage Scenarios
|
||||
---------------
|
||||
|
||||
Ranges are commonly used in the following scenarios.
|
||||
|
||||
| Scenario | Example |
|
||||
| -------------------------------------------- | --------------------------------------- |
|
||||
| [`for`](for.md) statements | `for n in 0..100 { ... }` |
|
||||
| [`in`](operators.md) expressions | `if n in 0..100 { ... }` |
|
||||
| [`switch`](switch.md) expressions | `switch n { 0..100 => ... }` |
|
||||
| [Bit-fields](bit-fields.md) access | `let x = n[2..6];` |
|
||||
| Bits iteration | `for bit in n.bits(2..=9) { ... }` |
|
||||
| [Array](arrays.md) range-based APIs | `array.extract(2..8)` |
|
||||
| [BLOB](blobs.md) range-based APIs | `blob.parse_le_int(4..8)` |
|
||||
| [String](strings-chars.md) range-based APIs | `string.sub_string(4..=12)` |
|
||||
| [Characters](strings-chars.md) iteration | `for ch in string.bits(4..=12) { ... }` |
|
||||
|
||||
|
||||
Built-in Functions
|
||||
------------------
|
||||
|
||||
The following methods 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 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.
|
||||
|
||||
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'
|
||||
~~~
|
||||
```
|
@@ -1,43 +0,0 @@
|
||||
Return Value
|
||||
============
|
||||
|
||||
`return`
|
||||
--------
|
||||
|
||||
The `return` statement is used to immediately stop evaluation and exist the current context
|
||||
(typically a [function](functions.md) call) yielding a _return value_.
|
||||
|
||||
```rust
|
||||
return; // equivalent to return ();
|
||||
|
||||
return 123 + 456; // returns 579
|
||||
```
|
||||
|
||||
A `return` statement at _global_ level exits the script with the return value as the result.
|
||||
|
||||
A `return` statement inside a [function call](functions.md) 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.
|
||||
|
||||
The result value of `exit`, when omitted, defaults to `()`.
|
||||
|
||||
```rust
|
||||
fn foo() {
|
||||
exit(42); // exit with result value 42
|
||||
}
|
||||
fn bar() {
|
||||
foo();
|
||||
}
|
||||
fn baz() {
|
||||
bar();
|
||||
}
|
||||
|
||||
let x = baz(); // exits with result value 42
|
||||
|
||||
print(x); // <- this is never run
|
||||
```
|
@@ -1,121 +0,0 @@
|
||||
Statements
|
||||
==========
|
||||
|
||||
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`](if.md), [`while`](while.md), [`for`](for.md), [`loop`](loop.md) and
|
||||
[`switch`](switch.md) 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](variable.md) and/or [constant](constant.md) defined within the block are removed
|
||||
outside the block, so are [modules](modules/index.md) [imported](modules/import.md) 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
|
||||
```
|
||||
|
||||
|
||||
Statement Expression
|
||||
====================
|
||||
|
||||
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
|
||||
```
|
@@ -1,149 +0,0 @@
|
||||
Standard String Functions
|
||||
=========================
|
||||
|
||||
{{#title Standard String Functions and Operators}}
|
||||
|
||||
|
||||
The following standard methods operate on [strings](strings-chars.md) (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` | _none_ | converts the string into an UTF-8 encoded byte-stream and returns it as a [BLOB](blobs.md). |
|
||||
| `to_chars` | _none_ | splits the string by individual characters, returning them as an [array](arrays.md) |
|
||||
| `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](ranges.md) of characters to extract, from beginning if ≤ 0, to end if ≥ length | extracts a sub-string |
|
||||
| `split` | _none_ | splits the string by whitespaces, returning an [array](arrays.md) of string segments |
|
||||
| `split` | 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](arrays) of two string segments |
|
||||
| `split` | <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](arrays) of string segments |
|
||||
| `split_rev` | <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](arrays) 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](ranges.md) 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](strings-chars.md) to get at individual
|
||||
[characters](strings-chars.md), 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](strings-chars.md).
|
||||
|
||||
|
||||
Building Strings
|
||||
----------------
|
||||
|
||||
[Strings](strings-chars.md) can be built from segments via the `+` operator.
|
||||
|
||||
| Operator | Description |
|
||||
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
|
||||
| [string](strings-chars.md) `+=` item | convert the item into a [string](strings-chars.md), then append it to the first [string](strings-chars.md) |
|
||||
| [string](strings-chars.md) `+` item | convert the item into a [string](strings-chars.md), then concatenate them as a new [string](strings-chars.md) |
|
||||
| item `+` [string](strings-chars.md) | convert the item into a [string](strings-chars.md), then concatenate them as a new [string](strings-chars.md) |
|
||||
|
||||
```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](strings-chars.md) and/or
|
||||
[characters](strings-chars.md).
|
||||
|
||||
When one (or both) of the operands is a [character](strings-chars.md), it is first converted into a
|
||||
one-character [string](strings-chars.md) before running the operator.
|
||||
|
||||
| Operator | Description |
|
||||
| --------- | -------------------------------------------------------------------------------------------------- |
|
||||
| `+`, `+=` | [character](strings-chars.md)/[string](strings-chars.md) concatenation |
|
||||
| `-`, `-=` | remove [character](strings-chars.md/[sub-string](strings-chars.md) from [string](strings-chars.md) |
|
||||
| `==` | 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](blobs.md) is appended to a [string](strings-chars.md), or vice
|
||||
versa, it is treated as a UTF-8 encoded byte stream and automatically first converted into the appropriate
|
||||
[string](strings-chars.md) value.
|
||||
|
||||
That is because it is rarely useful to append a [BLOB](blobs.md) into a string, but extremely useful
|
||||
to be able to directly manipulate UTF-8 encoded text.
|
||||
|
||||
| Operator | Description |
|
||||
| --------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| `+`, `+=` | append a [BLOB](blobs.md) (as a UTF-8 encoded byte stream) to the end of the [string](strings-chars.md) |
|
||||
| `+` | concatenate a [BLOB](blobs.md) (as a UTF-8 encoded byte stream) with a [string](strings-chars.md) |
|
||||
|
||||
|
||||
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;
|
||||
```
|
@@ -1,348 +0,0 @@
|
||||
Strings and Characters
|
||||
======================
|
||||
|
||||
String in Rhai contain any text sequence of valid Unicode characters.
|
||||
|
||||
[`type_of()`](type-of.md) 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 `+` or `+=` operators.
|
||||
```
|
||||
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
Multi-Line Literal Strings
|
||||
--------------------------
|
||||
|
||||
A string wrapped by a pair of back-tick (`` ` ``) characters is interpreted _literally_,
|
||||
meaning 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 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 `${` ... `}`.
|
||||
|
||||
Interpolation is not supported for normal string or character literals.
|
||||
|
||||
`${` ... `}` 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`](convert.md) to convert any value into a string, then physically joins all
|
||||
the sub-strings together.
|
||||
|
||||
For convenience, if any interpolated value is a [BLOB](blobs.md), however, it is automatically treated as a
|
||||
UTF-8 encoded string. That is because it is rarely useful to interpolate a [BLOB](blobs.md) 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]"
|
||||
```
|
||||
|
||||
|
||||
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](ranges.md) of characters:
|
||||
|
||||
> _string_ `[` _first character (starting from zero)_ `..` _last character (exclusive)_ `]`
|
||||
>
|
||||
> _string_ `[` _first character (starting from zero)_ `..=` _last character (inclusive)_ `]`
|
||||
|
||||
Sub-string [ranges](ranges.md) always start from zero counting towards the end of the string.
|
||||
Negative [ranges](ranges.md) 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);
|
||||
}
|
||||
```
|
@@ -1,244 +0,0 @@
|
||||
Switch Statement
|
||||
================
|
||||
|
||||
The `switch` statement allows matching on [literal](../appendix/literals.md) values.
|
||||
|
||||
```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](../appendix/literals.md)_, including
|
||||
[array](arrays.md) and [object map](object-maps.md) [literals](../appendix/literals.md).
|
||||
|
||||
```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" } => ...,
|
||||
_ => ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
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()`](type-of.md), 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](../appendix/literals.md) integer [ranges](ranges.md) can also
|
||||
be used as `switch` cases.
|
||||
|
||||
Numeric [ranges](ranges.md) are only searched when the `switch` value is itself a number (including
|
||||
floating-point and 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!!! duplicated range cases are OK
|
||||
|
||||
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](ranges.md) contain the `switch` value, the _first_ one with a fulfilled condition
|
||||
(if any) is evaluated.
|
||||
|
||||
Numeric [range](ranges.md) cases are tried in the order that they appear in the original script.
|
||||
```
|
||||
|
||||
|
||||
Switch Expression
|
||||
=================
|
||||
|
||||
Like [`if`](if.md), `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](functions.md) call arguments.
|
||||
```
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Difference From `if`-`else if` Chain
|
||||
------------------------------------
|
||||
|
||||
Although a `switch` expression looks _almost_ the same as an [`if`-`else if`](if.md) 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.md) chain is _much_ slower.
|
||||
|
||||
On the other hand, operators can be [overloaded](overload.md) 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](overload.md) the `==` operator
|
||||
will have no effect.
|
||||
|
||||
Therefore, in environments where it is desirable to [overload](overload.md) the `==` operator for
|
||||
[standard types](values-and-types.md) – 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.md) chain becomes increasingly slower with each additional case because
|
||||
essentially an O(n) _linear scan_ is performed.
|
@@ -1,34 +0,0 @@
|
||||
Throw Exception on Error
|
||||
========================
|
||||
|
||||
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 '()'
|
||||
```
|
||||
|
||||
|
||||
Catch a Thrown Exception
|
||||
------------------------
|
||||
|
||||
It is possible to _catch_ an exception, instead of having it abort the script run, via the
|
||||
[`try` ... `catch`](try-catch.md) 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
|
||||
}
|
||||
```
|
@@ -1,41 +0,0 @@
|
||||
Timestamps
|
||||
==========
|
||||
|
||||
Timestamps are provided by the via the `timestamp` function.
|
||||
|
||||
[`type_of()`](type-of.md) a timestamp returns `"timestamp"`.
|
||||
|
||||
|
||||
Built-in Functions
|
||||
------------------
|
||||
|
||||
The following methods 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 time-related functions are also available.
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| -------- | -------------------------- | ----------------------------------------------------------- |
|
||||
| `sleep` | 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)!")
|
||||
}
|
||||
```
|
@@ -1,108 +0,0 @@
|
||||
Catch Exceptions
|
||||
================
|
||||
|
||||
When an [exception](throw.md) is thrown via a [`throw`](throw.md) statement, the script halts with
|
||||
the exception value.
|
||||
|
||||
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`](throw.md) 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`](throw.md) statement | value in [`throw`](throw.md) statement |
|
||||
| Arithmetic error | [object map](object-maps.md) |
|
||||
| [Variable](variables.md) not found | [object map](object-maps.md) |
|
||||
| [Function](functions.md) not found | [object map](object-maps.md) |
|
||||
| [Module](modules/index.md) not found | [object map](object-maps.md) |
|
||||
| Unbound `this` | [object map](object-maps.md) |
|
||||
| Data type mismatch | [object map](object-maps.md) |
|
||||
| Assignment to a calculated/[constant](constants.md) value | [object map](object-maps.md) |
|
||||
| [Array](arrays.md)/[string](strings-chars.md)/[bit-field](bit-fields.md) indexing out-of-bounds | [object map](object-maps.md) |
|
||||
| Indexing with an inappropriate data type | [object map](object-maps.md) |
|
||||
| Error in property access | [object map](object-maps.md) |
|
||||
| [`for`](for.md) statement on a type that is not iterable | [object map](object-maps.md) |
|
||||
| Data race detected | [object map](object-maps.md) |
|
||||
| Other runtime error | [object map](object-maps.md) |
|
||||
|
||||
The error value in the `catch` clause is an [object map](object-maps.md) containing information on
|
||||
the particular error, including its type, line and character position (if any), and source etc.
|
||||
```
|
||||
|
||||
```admonish failure "Non-catchable exceptions"
|
||||
|
||||
Some system 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 |
|
||||
```
|
@@ -1,38 +0,0 @@
|
||||
`type_of()`
|
||||
===========
|
||||
|
||||
The `type_of` function detects the actual type of a value.
|
||||
|
||||
This is useful because all [variables](variables.md) 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](values-and-types.md) for the `type_of` output of standard types.
|
||||
```
|
@@ -1,47 +0,0 @@
|
||||
Value Types
|
||||
===========
|
||||
|
||||
The following primitive value types are supported natively.
|
||||
|
||||
| Category | [`type_of()`](type-of.md) | `to_string()` |
|
||||
| -------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------- |
|
||||
| **System integer** | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
|
||||
| **Other integer number** | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. |
|
||||
| **Integer numeric [range](ranges.md)** | `"range"`, `"range="` | `"2..7"`, `"0..=15"` etc. |
|
||||
| **Floating-point number** | `"f32"` or `"f64"` | `"123.4567"` etc. |
|
||||
| **Fixed precision decimal number** | `"decimal"` | `"42"`, `"123.4567"` etc. |
|
||||
| **Boolean value** | `"bool"` | `"true"` or `"false"` |
|
||||
| **Unicode character** | `"char"` | `"A"`, `"x"` etc. |
|
||||
| **Immutable Unicode [string](strings-chars.md)** | `"string"` | `"hello"` etc. |
|
||||
| **[`Array`](arrays.md)** | `"array"` | `"[ 1, 2, 3 ]"` etc. |
|
||||
| **Byte array – [`BLOB`](blobs.md)** | `"blob"` | `"[01020304abcd]"` etc. |
|
||||
| **[Object map](object-maps.md)** | `"map"` | `"#{ "a": 1, "b": true }"` etc. |
|
||||
| **[Timestamp](timestamps.md)** | `"timestamp"` | `"<timestamp>"` |
|
||||
| **[Function pointer](fn-ptr.md)** | `"Fn"` | `"Fn(foo)"` etc. |
|
||||
| **Dynamic value** (i.e. can be anything) | _the actual type_ | _actual value_ |
|
||||
| **Shared value** (a reference-counted, shared dynamic value, created via [closures](fn-closure.md) | _the actual type_ | _actual value_ |
|
||||
| **Nothing/void/nil/null/Unit** (or whatever it is called) | `"()"` | `""` _(empty string)_ |
|
||||
|
||||
|
||||
```admonish warning.small "All types are distinct"
|
||||
|
||||
All types are treated strictly distinct by Rhai, meaning that `i32` and `i64` and `u32` are
|
||||
completely different. They cannot even be added together.
|
||||
|
||||
This is very similar to Rust.
|
||||
```
|
||||
|
||||
```admonish info.small "Strings"
|
||||
|
||||
[Strings](strings-chars.md) in Rhai are _immutable_, meaning that they can be shared but not modified.
|
||||
|
||||
Any modification done to a Rhai string causes the [string](strings-chars.md) 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](strings-chars.md) for display purposes.
|
||||
|
||||
The `to_debug` function converts a standard type into a [string](strings-chars.md) in debug format.
|
||||
```
|
@@ -1,154 +0,0 @@
|
||||
Variables
|
||||
=========
|
||||
|
||||
|
||||
Valid Names
|
||||
-----------
|
||||
|
||||
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, 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, e.g. `_`, `_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](keywords.md) (active or reserved).
|
||||
|
||||
```admonish warning "Avoid names longer than 11 letters on 32-Bit"
|
||||
|
||||
Rhai _inlines_ a string, which avoids allocations unless it 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;
|
||||
```
|
||||
|
||||
|
||||
Shadowing
|
||||
---------
|
||||
|
||||
New variables automatically _shadow_ existing ones of the same name. There is no error.
|
||||
|
||||
```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
|
||||
```
|
@@ -1,50 +0,0 @@
|
||||
While Loop
|
||||
==========
|
||||
|
||||
`while` loops follow C syntax.
|
||||
|
||||
`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.
|
||||
|
||||
```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
|
||||
----------------
|
||||
|
||||
`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 `()`.
|
||||
|
||||
```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