reorganize module

This commit is contained in:
Timur Gordon
2025-04-04 08:28:07 +02:00
parent 1ea37e2e7f
commit 939b6b4e57
375 changed files with 7580 additions and 191 deletions

View File

@@ -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 length1_ `]`
### 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 == []
```

View File

@@ -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;
```

View File

@@ -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; }
```

View File

@@ -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}`)
}
```

View File

@@ -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 length1_ `]`
### 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) |

View File

@@ -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) &ndash;
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.
```

View File

@@ -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'
}
```

View File

@@ -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";
```

View File

@@ -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}!`);
}
```

View File

@@ -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 &ndash; 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];
```

View File

@@ -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)}`)
}
```

View File

@@ -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!
~~~

View File

@@ -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) &ndash; 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 &ndash; 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 &ndash; 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'
```
~~~

View File

@@ -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 |

View File

@@ -1,195 +0,0 @@
`this` &ndash; 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;
```

View File

@@ -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
```

View File

@@ -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);
}
```

View File

@@ -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.
```

View File

@@ -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_ `;`

View File

@@ -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
}
```
~~~

View File

@@ -1,8 +0,0 @@
Rhai Language Reference
=======================
{{#title Rhai Language Reference}}
![Rhai Logo]({{rootUrl}}/images/logo/rhai-banner-transparent-colour.svg)
This is a stand-alone reference for the Rhai scripting language.

View File

@@ -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_ `;`

View File

@@ -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.
```

View File

@@ -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}!`);
}
```

View File

@@ -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_ ... `)`

View File

@@ -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
}
```
~~~

View File

@@ -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_ &ndash; 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();
```
~~~

View File

@@ -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.

View File

@@ -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, &plus;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 &pi; |
| `E` | returns the value of _e_ |

View File

@@ -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)
```

View File

@@ -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 &ndash; 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.

View File

@@ -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 &ndash; 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
```

View File

@@ -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`) &ndash; 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_ &ndash; 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_ &ndash; 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_ &ndash; i.e. whether a particular collection
data type _contains_ a particular item.
Similarly, `!in` is used to check for non-existence &ndash; 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
```

View File

@@ -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."
```

View File

@@ -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
```

View File

@@ -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'
~~~
```

View File

@@ -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
```

View File

@@ -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 &ndash; 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
```

View File

@@ -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;
```

View File

@@ -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);
}
```

View File

@@ -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) &ndash; though it is difficult to think of valid scenarios
where you'd want `1 == 1` to return something other than `true` &ndash; 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.

View File

@@ -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
}
```

View File

@@ -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)!")
}
```

View File

@@ -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 &ndash; 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 |
```

View File

@@ -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.
```

View File

@@ -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 &ndash; [`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.
```

View File

@@ -1,154 +0,0 @@
Variables
=========
Valid Names
-----------
Variables in Rhai follow normal C naming rules &ndash; 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
```

View File

@@ -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}!`);
}
```