Add Rhai book.
This commit is contained in:
124
doc/src/language/arrays.md
Normal file
124
doc/src/language/arrays.md
Normal file
@@ -0,0 +1,124 @@
|
||||
Arrays
|
||||
======
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices.
|
||||
|
||||
Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`'.
|
||||
|
||||
All elements stored in an array are [`Dynamic`], and the array can freely grow or shrink with elements added or removed.
|
||||
|
||||
The Rust type of a Rhai array is `rhai::Array`.
|
||||
|
||||
[`type_of()`] an array returns `"array"`.
|
||||
|
||||
Arrays are disabled via the [`no_index`] feature.
|
||||
|
||||
The maximum allowed size of an array can be controlled via `Engine::set_max_array_size`
|
||||
(see [maximum size of arrays].
|
||||
|
||||
|
||||
Built-in Functions
|
||||
-----------------
|
||||
|
||||
The following methods (mostly defined in the [`BasicArrayPackage`](/rust/packages.md) but excluded if using a [raw `Engine`]) operate on arrays:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `push` | element to insert | inserts an element at the end |
|
||||
| `+=` operator, `append` | array to append | concatenates the second array to the end of the first |
|
||||
| `+` operator | first array, second array | concatenates the first array with the second |
|
||||
| `insert` | element to insert, position<br/>(beginning if <= 0, end if >= length) | insert an element at a certain index |
|
||||
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
|
||||
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
|
||||
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
|
||||
| `len` method and property | _none_ | returns the number of elements |
|
||||
| `pad` | element to pad, target length | 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) |
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let y = [2, 3]; // array literal with 2 elements
|
||||
|
||||
let y = [2, 3,]; // trailing comma is OK
|
||||
|
||||
y.insert(0, 1); // insert element at the beginning
|
||||
y.insert(999, 4); // insert element at the end
|
||||
|
||||
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 item exists in the array
|
||||
(42 in y) == false; // 'in' uses the '==' operator (which users can override)
|
||||
// to check if the target item exists in the array
|
||||
|
||||
y[1] = 42; // array elements can be reassigned
|
||||
|
||||
(42 in y) == true;
|
||||
|
||||
y.remove(2) == 3; // remove element
|
||||
|
||||
y.len == 3;
|
||||
|
||||
y[2] == 4; // elements after the removed element are shifted
|
||||
|
||||
ts.list = y; // arrays can be assigned completely (by value copy)
|
||||
let foo = ts.list[1];
|
||||
foo == 42;
|
||||
|
||||
let foo = [1, 2, 3][0];
|
||||
foo == 1;
|
||||
|
||||
fn abc() {
|
||||
[42, 43, 44] // a function returning an array
|
||||
}
|
||||
|
||||
let foo = abc()[0];
|
||||
foo == 42;
|
||||
|
||||
let foo = y[0];
|
||||
foo == 1;
|
||||
|
||||
y.push(4); // 4 elements
|
||||
y.push(5); // 5 elements
|
||||
|
||||
y.len == 5;
|
||||
|
||||
let first = y.shift(); // remove the first element, 4 elements remaining
|
||||
first == 1;
|
||||
|
||||
let last = y.pop(); // remove the last element, 3 elements remaining
|
||||
last == 5;
|
||||
|
||||
y.len == 3;
|
||||
|
||||
for item in y { // arrays can be iterated with a 'for' statement
|
||||
print(item);
|
||||
}
|
||||
|
||||
y.pad(10, "hello"); // pad the array up to 10 elements
|
||||
|
||||
y.len == 10;
|
||||
|
||||
y.truncate(5); // truncate the array to 5 elements
|
||||
|
||||
y.len == 5;
|
||||
|
||||
y.clear(); // empty the array
|
||||
|
||||
y.len == 0;
|
||||
```
|
||||
|
||||
`push` and `pad` are only defined for standard built-in types. For custom types, type-specific versions must be registered:
|
||||
|
||||
```rust
|
||||
engine.register_fn("push", |list: &mut Array, item: MyType| list.push(Box::new(item)) );
|
||||
```
|
22
doc/src/language/comments.md
Normal file
22
doc/src/language/comments.md
Normal file
@@ -0,0 +1,22 @@
|
||||
Comments
|
||||
========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Comments are C-style, including '`/*` ... `*/`' pairs and '`//`' for comments to the end of the line.
|
||||
Comments can be nested.
|
||||
|
||||
```rust
|
||||
let /* intruder comment */ name = "Bob";
|
||||
|
||||
// This is a very important 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:
|
||||
/*/*/*/*/**/*/*/*/*/
|
||||
*/
|
||||
```
|
20
doc/src/language/constants.md
Normal file
20
doc/src/language/constants.md
Normal file
@@ -0,0 +1,20 @@
|
||||
Constants
|
||||
=========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Constants can be defined using the `const` keyword and are immutable.
|
||||
|
||||
Constants follow the same naming rules as [variables].
|
||||
|
||||
```rust
|
||||
const x = 42;
|
||||
print(x * 2); // prints 84
|
||||
x = 123; // <- syntax error: cannot assign to constant
|
||||
```
|
||||
|
||||
Constants must be assigned a _value_, not an expression.
|
||||
|
||||
```rust
|
||||
const x = 40 + 2; // <- syntax error: cannot assign expression to constant
|
||||
```
|
20
doc/src/language/convert.md
Normal file
20
doc/src/language/convert.md
Normal file
@@ -0,0 +1,20 @@
|
||||
Value Conversions
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The `to_float` function converts a supported number to `FLOAT` (defaults to `f64`).
|
||||
|
||||
The `to_int` function converts a supported number to `INT` (`i32` or `i64` depending on [`only_i32`]).
|
||||
|
||||
That's it; for other conversions, register custom conversion functions.
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
let y = x * 100.0; // <- error: cannot multiply i64 with f64
|
||||
let y = x.to_float() * 100.0; // works
|
||||
let z = y.to_int() + x; // works
|
||||
|
||||
let c = 'X'; // character
|
||||
print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88"
|
||||
```
|
101
doc/src/language/dynamic.md
Normal file
101
doc/src/language/dynamic.md
Normal file
@@ -0,0 +1,101 @@
|
||||
Dynamic Values
|
||||
==============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
A `Dynamic` value can be _any_ type. However, under [`sync`], all types must be `Send + Sync`.
|
||||
|
||||
|
||||
Use [`type_of()`] to Get Value Type
|
||||
----------------------------------
|
||||
|
||||
Because [`type_of()`] a `Dynamic` value returns the type of the actual value,
|
||||
it is usually used to perform type-specific actions based on the actual value's type.
|
||||
|
||||
```rust
|
||||
let mystery = get_some_dynamic_value();
|
||||
|
||||
if type_of(mystery) == "i64" {
|
||||
print("Hey, I got an integer here!");
|
||||
} else if type_of(mystery) == "f64" {
|
||||
print("Hey, I got a float here!");
|
||||
} else if type_of(mystery) == "string" {
|
||||
print("Hey, I got a string here!");
|
||||
} else if type_of(mystery) == "bool" {
|
||||
print("Hey, I got a boolean here!");
|
||||
} else if type_of(mystery) == "array" {
|
||||
print("Hey, I got an array here!");
|
||||
} else if type_of(mystery) == "map" {
|
||||
print("Hey, I got an object map here!");
|
||||
} else if type_of(mystery) == "TestStruct" {
|
||||
print("Hey, I got the TestStruct custom type here!");
|
||||
} else {
|
||||
print("I don't know what this is: " + type_of(mystery));
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Functions Returning `Dynamic`
|
||||
----------------------------
|
||||
|
||||
In Rust, sometimes a `Dynamic` forms part of a returned value - a good example is an [array]
|
||||
which contains `Dynamic` elements, or an [object map] which contains `Dynamic` property values.
|
||||
|
||||
To get the _real_ values, the actual value types _must_ be known in advance.
|
||||
There is no easy way for Rust to decide, at run-time, what type the `Dynamic` value is
|
||||
(short of using the `type_name` function and match against the name).
|
||||
|
||||
|
||||
Type Checking and Casting
|
||||
------------------------
|
||||
|
||||
A `Dynamic` value's actual type can be checked via the `is` method.
|
||||
|
||||
The `cast` method then converts the value into a specific, known type.
|
||||
|
||||
Alternatively, use the `try_cast` method which does not panic but returns `None` when the cast fails.
|
||||
|
||||
```rust
|
||||
let list: Array = engine.eval("...")?; // return type is 'Array'
|
||||
let item = list[0]; // an element in an 'Array' is 'Dynamic'
|
||||
|
||||
item.is::<i64>() == true; // 'is' returns whether a 'Dynamic' value is of a particular type
|
||||
|
||||
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
|
||||
let value: i64 = item.cast(); // type can also be inferred
|
||||
|
||||
let value = item.try_cast::<i64>().unwrap(); // 'try_cast' does not panic when the cast fails, but returns 'None'
|
||||
```
|
||||
|
||||
Type Name
|
||||
---------
|
||||
|
||||
The `type_name` method gets the name of the actual type as a static string slice,
|
||||
which can be `match`-ed against.
|
||||
|
||||
```rust
|
||||
let list: Array = engine.eval("...")?; // return type is 'Array'
|
||||
let item = list[0]; // an element in an 'Array' is 'Dynamic'
|
||||
|
||||
match item.type_name() { // 'type_name' returns the name of the actual Rust type
|
||||
"i64" => ...
|
||||
"alloc::string::String" => ...
|
||||
"bool" => ...
|
||||
"path::to::module::TestStruct" => ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Conversion Traits
|
||||
----------------
|
||||
|
||||
The following conversion traits are implemented for `Dynamic`:
|
||||
|
||||
* `From<i64>` (`i32` if [`only_i32`])
|
||||
* `From<f64>` (if not [`no_float`])
|
||||
* `From<bool>`
|
||||
* `From<rhai::ImmutableString>`
|
||||
* `From<String>`
|
||||
* `From<char>`
|
||||
* `From<Vec<T>>` (into an [array])
|
||||
* `From<HashMap<String, T>>` (into an [object map]).
|
96
doc/src/language/eval.md
Normal file
96
doc/src/language/eval.md
Normal file
@@ -0,0 +1,96 @@
|
||||
`eval` Statement
|
||||
===============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Or "How to Shoot Yourself in the Foot even Easier"
|
||||
------------------------------------------------
|
||||
|
||||
Saving the best for last: in addition to script optimizations, there is the ever-dreaded... `eval` function!
|
||||
|
||||
```rust
|
||||
let x = 10;
|
||||
|
||||
fn foo(x) { x += 12; x }
|
||||
|
||||
let script = "let y = x;"; // build a script
|
||||
script += "y += foo(y);";
|
||||
script += "x + y";
|
||||
|
||||
let result = eval(script); // <- look, JS, we can also do this!
|
||||
|
||||
print("Answer: " + result); // prints 42
|
||||
|
||||
print("x = " + x); // prints 10: functions call arguments are passed by value
|
||||
print("y = " + y); // prints 32: variables defined in 'eval' persist!
|
||||
|
||||
eval("{ let z = y }"); // to keep a variable local, use a statement block
|
||||
|
||||
print("z = " + z); // <- error: variable 'z' not found
|
||||
|
||||
"print(42)".eval(); // <- nope... method-call style doesn't work
|
||||
```
|
||||
|
||||
Script segments passed to `eval` execute inside the current [`Scope`], so they can access and modify _everything_,
|
||||
including all variables that are visible at that position in code! It is almost as if the script segments were
|
||||
physically pasted in at the position of the `eval` call.
|
||||
|
||||
|
||||
Cannot Define New Functions
|
||||
--------------------------
|
||||
|
||||
New functions cannot be defined within an `eval` call, since functions can only be defined at the _global_ level,
|
||||
not inside another function call!
|
||||
|
||||
```rust
|
||||
let script = "x += 32";
|
||||
let x = 10;
|
||||
eval(script); // variable 'x' in the current scope is visible!
|
||||
print(x); // prints 42
|
||||
|
||||
// The above is equivalent to:
|
||||
let script = "x += 32";
|
||||
let x = 10;
|
||||
x += 32;
|
||||
print(x);
|
||||
```
|
||||
|
||||
|
||||
`eval` is Evil
|
||||
--------------
|
||||
|
||||
For those who subscribe to the (very sensible) motto of ["`eval` is evil"](http://linterrors.com/js/eval-is-evil),
|
||||
disable `eval` by overloading it, probably with something that throws.
|
||||
|
||||
```rust
|
||||
fn eval(script) { throw "eval is evil! I refuse to run " + script }
|
||||
|
||||
let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run 40 + 2"
|
||||
```
|
||||
|
||||
Or overload it from Rust:
|
||||
|
||||
```rust
|
||||
fn alt_eval(script: String) -> Result<(), Box<EvalAltResult>> {
|
||||
Err(format!("eval is evil! I refuse to run {}", script).into())
|
||||
}
|
||||
|
||||
engine.register_result_fn("eval", alt_eval);
|
||||
```
|
||||
|
||||
|
||||
`EvalPackage`
|
||||
-------------
|
||||
|
||||
There is even a package named [`EvalPackage`](/rust/packages.md) which implements the disabling override:
|
||||
|
||||
```rust
|
||||
use rhai::Engine;
|
||||
use rhai::packages::Package // load the 'Package' trait to use packages
|
||||
use rhai::packages::EvalPackage; // the 'eval' package disables 'eval'
|
||||
|
||||
let mut engine = Engine::new();
|
||||
let package = EvalPackage::new(); // create the package
|
||||
|
||||
engine.load_package(package.get()); // load the package
|
||||
```
|
55
doc/src/language/for.md
Normal file
55
doc/src/language/for.md
Normal file
@@ -0,0 +1,55 @@
|
||||
`for` Loop
|
||||
==========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Iterating through a range or an [array] is provided by the `for` ... `in` loop.
|
||||
|
||||
```rust
|
||||
// Iterate through string, yielding characters
|
||||
let s = "hello, world!";
|
||||
|
||||
for ch in s {
|
||||
if ch > 'z' { continue; } // skip to the next iteration
|
||||
print(ch);
|
||||
if x == '@' { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// Iterate through array
|
||||
let array = [1, 3, 5, 7, 9, 42];
|
||||
|
||||
for x in array {
|
||||
if x > 10 { continue; } // skip to the next iteration
|
||||
print(x);
|
||||
if x == 42 { break; } // break out of for loop
|
||||
}
|
||||
|
||||
// The 'range' function allows iterating from first to last-1
|
||||
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
|
||||
}
|
||||
|
||||
// Iterate through object map
|
||||
let map = #{a:1, b:3, c:5, d:7, e:9};
|
||||
|
||||
// Property names are returned in random order
|
||||
for x in keys(map) {
|
||||
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 random order
|
||||
for val in values(map) {
|
||||
print(val);
|
||||
}
|
||||
```
|
97
doc/src/language/functions.md
Normal file
97
doc/src/language/functions.md
Normal file
@@ -0,0 +1,97 @@
|
||||
Functions
|
||||
=========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai supports defining functions in script (unless disabled with [`no_function`]):
|
||||
|
||||
```rust
|
||||
fn add(x, y) {
|
||||
return x + y;
|
||||
}
|
||||
|
||||
fn sub(x, y,) { // trailing comma in parameters list is OK
|
||||
return x - y;
|
||||
}
|
||||
|
||||
print(add(2, 3)); // prints 5
|
||||
print(sub(2, 3,)); // prints -1 - trailing comma in arguments list is OK
|
||||
```
|
||||
|
||||
Implicit Return
|
||||
---------------
|
||||
|
||||
Just like in Rust, an implicit return can be used. In fact, the last statement of a block is _always_ the block's return value
|
||||
regardless of whether it is terminated with a semicolon `';'`. This is different from Rust.
|
||||
|
||||
```rust
|
||||
fn add(x, y) { // implicit return:
|
||||
x + y; // value of the last statement (no need for ending semicolon)
|
||||
// is used as the return value
|
||||
}
|
||||
|
||||
fn add2(x) {
|
||||
return x + 2; // explicit return
|
||||
}
|
||||
|
||||
print(add(2, 3)); // prints 5
|
||||
print(add2(42)); // prints 44
|
||||
```
|
||||
|
||||
No Access to External Scope
|
||||
--------------------------
|
||||
|
||||
Functions are not _closures_. They do not capture the calling environment and can only access their own parameters.
|
||||
They cannot access variables external to the function itself.
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
|
||||
fn foo() { x } // <- syntax error: variable 'x' doesn't exist
|
||||
```
|
||||
|
||||
Passing Arguments by Value
|
||||
-------------------------
|
||||
|
||||
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
|
||||
It is important to remember that all arguments are passed by _value_, so all functions are _pure_
|
||||
(i.e. they never modify their arguments).
|
||||
|
||||
Any update to an argument will **not** be reflected back to the caller.
|
||||
|
||||
This can introduce subtle bugs, if not careful, especially when using the _method-call_ style.
|
||||
|
||||
```rust
|
||||
fn change(s) { // 's' is passed by value
|
||||
s = 42; // only a COPY of 's' is changed
|
||||
}
|
||||
|
||||
let x = 500;
|
||||
x.change(); // de-sugars to 'change(x)'
|
||||
x == 500; // 'x' is NOT changed!
|
||||
```
|
||||
|
||||
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: functions cannot be defined inside another function
|
||||
n + y
|
||||
}
|
||||
|
||||
add_y(x)
|
||||
}
|
||||
```
|
||||
|
||||
Unlike C/C++, functions can be defined _anywhere_ within the 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.
|
42
doc/src/language/if.md
Normal file
42
doc/src/language/if.md
Normal file
@@ -0,0 +1,42 @@
|
||||
`if` Statement
|
||||
==============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```rust
|
||||
if foo(x) {
|
||||
print("It's true!");
|
||||
} else if bar == baz {
|
||||
print("It's true again!");
|
||||
} else if ... {
|
||||
:
|
||||
} else if ... {
|
||||
:
|
||||
} else {
|
||||
print("It's finally false!");
|
||||
}
|
||||
```
|
||||
|
||||
All branches of an `if` statement must be enclosed within braces '`{`' .. '`}`', even when there is only one statement.
|
||||
Like Rust, there is no ambiguity regarding which `if` clause a statement belongs to.
|
||||
|
||||
```rust
|
||||
if (decision) print("I've decided!");
|
||||
// ^ syntax error, expecting '{' in statement block
|
||||
```
|
||||
|
||||
|
||||
`if`-Expressions
|
||||
---------------
|
||||
|
||||
Like Rust, `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 == ();
|
||||
```
|
47
doc/src/language/json.md
Normal file
47
doc/src/language/json.md
Normal file
@@ -0,0 +1,47 @@
|
||||
Parse an Object Map from JSON
|
||||
============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The syntax for an [object map] is extremely similar to JSON, with the exception of `null` values which can
|
||||
technically be mapped to [`()`]. A valid JSON string does not start with a hash character `#` while a
|
||||
Rhai [object map] does - that's the major difference!
|
||||
|
||||
Use the `Engine::parse_json` method to parse a piece of JSON into an object map:
|
||||
|
||||
```rust
|
||||
// JSON string - notice that JSON property names are always quoted
|
||||
// notice also that comments are acceptable within the JSON string
|
||||
let json = r#"{
|
||||
"a": 1, // <- this is an integer number
|
||||
"b": true,
|
||||
"c": 123.0, // <- this is a floating-point number
|
||||
"$d e f!": "hello", // <- any text can be a property name
|
||||
"^^^!!!": [1,42,"999"], // <- value can be array or another hash
|
||||
"z": null // <- JSON 'null' value
|
||||
}
|
||||
"#;
|
||||
|
||||
// Parse the JSON expression as an object map
|
||||
// Set the second boolean parameter to true in order to map 'null' to '()'
|
||||
let map = engine.parse_json(json, true)?;
|
||||
|
||||
map.len() == 6; // 'map' contains all properties in the JSON string
|
||||
|
||||
// Put the object map into a 'Scope'
|
||||
let mut scope = Scope::new();
|
||||
scope.push("map", map);
|
||||
|
||||
let result = engine.eval_with_scope::<INT>(r#"map["^^^!!!"].len()"#)?;
|
||||
|
||||
result == 3; // the object map is successfully used in the script
|
||||
```
|
||||
|
||||
Representation of Numbers
|
||||
------------------------
|
||||
|
||||
JSON numbers are all floating-point while Rhai supports integers (`INT`) and floating-point (`FLOAT`) if
|
||||
the [`no_float`] feature is not used. Most common generators of JSON data distinguish between
|
||||
integer and floating-point values by always serializing a floating-point number with a decimal point
|
||||
(i.e. `123.0` instead of `123` which is assumed to be an integer). This style can be used successfully
|
||||
with Rhai [object maps].
|
20
doc/src/language/keywords.md
Normal file
20
doc/src/language/keywords.md
Normal file
@@ -0,0 +1,20 @@
|
||||
Keywords
|
||||
========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The following are reserved keywords in Rhai:
|
||||
|
||||
| Keywords | Usage | Not available under feature |
|
||||
| ------------------------------------------------- | --------------------- | :-------------------------: |
|
||||
| `true`, `false` | Boolean constants | |
|
||||
| `let`, `const` | Variable declarations | |
|
||||
| `if`, `else` | Control flow | |
|
||||
| `while`, `loop`, `for`, `in`, `continue`, `break` | Looping | |
|
||||
| `fn`, `private` | Functions | [`no_function`] |
|
||||
| `return` | Return values | |
|
||||
| `throw` | Return errors | |
|
||||
| `import`, `export`, `as` | Modules | [`no_module`] |
|
||||
|
||||
Keywords cannot be the name of a [function] or [variable], unless the relevant exclusive feature is enabled.
|
||||
For example, `fn` is a valid variable name under [`no_function`].
|
82
doc/src/language/logic.md
Normal file
82
doc/src/language/logic.md
Normal file
@@ -0,0 +1,82 @@
|
||||
Logic Operators
|
||||
==============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Comparison Operators
|
||||
-------------------
|
||||
|
||||
Comparing most values of the same data type work out-of-the-box for all [standard types] supported by the system.
|
||||
|
||||
However, if using a [raw `Engine`] without loading any [packages], comparisons can only be made between a limited
|
||||
set of types (see [built-in operators]).
|
||||
|
||||
```rust
|
||||
42 == 42; // true
|
||||
42 > 42; // false
|
||||
"hello" > "foo"; // true
|
||||
"42" == 42; // false
|
||||
```
|
||||
|
||||
Comparing two values of _different_ data types, or of unknown data types, always results in `false`,
|
||||
except for '`!=`' (not equals) which results in `true`. This is in line with intuition.
|
||||
|
||||
```rust
|
||||
42 == 42.0; // false - i64 cannot be compared with f64
|
||||
42 != 42.0; // true - i64 cannot be compared with f64
|
||||
|
||||
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 - types cannot be compared
|
||||
ts != 42; // true - types cannot be compared
|
||||
```
|
||||
|
||||
Boolean operators
|
||||
-----------------
|
||||
|
||||
| Operator | Description |
|
||||
| -------- | ------------------------------------- |
|
||||
| `!` | Boolean _Not_ |
|
||||
| `&&` | Boolean _And_ (short-circuits) |
|
||||
| `\|\|` | Boolean _Or_ (short-circuits) |
|
||||
| `&` | Boolean _And_ (doesn't short-circuit) |
|
||||
| `\|` | Boolean _Or_ (doesn't short-circuit) |
|
||||
|
||||
Double boolean operators `&&` and `||` _short-circuit_, meaning that the second operand will not be evaluated
|
||||
if the first one already proves the condition wrong.
|
||||
|
||||
Single boolean operators `&` and `|` always evaluate both operands.
|
||||
|
||||
```rust
|
||||
this() || that(); // that() is not evaluated if this() is true
|
||||
this() && that(); // that() is not evaluated if this() is false
|
||||
|
||||
this() | that(); // both this() and that() are evaluated
|
||||
this() & that(); // both this() and that() are evaluated
|
||||
```
|
||||
|
||||
Compound Assignment Operators
|
||||
----------------------------
|
||||
|
||||
```rust
|
||||
let number = 5;
|
||||
number += 4; // number = number + 4
|
||||
number -= 3; // number = number - 3
|
||||
number *= 2; // number = number * 2
|
||||
number /= 1; // number = number / 1
|
||||
number %= 3; // number = number % 3
|
||||
number <<= 2; // number = number << 2
|
||||
number >>= 1; // number = number >> 1
|
||||
```
|
||||
|
||||
The `+=` operator can also be used to build [strings]:
|
||||
|
||||
```rust
|
||||
let my_str = "abc";
|
||||
my_str += "ABC";
|
||||
my_str += 12345;
|
||||
|
||||
my_str == "abcABC12345"
|
||||
```
|
15
doc/src/language/loop.md
Normal file
15
doc/src/language/loop.md
Normal file
@@ -0,0 +1,15 @@
|
||||
Infinite `loop`
|
||||
===============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```rust
|
||||
let x = 10;
|
||||
|
||||
loop {
|
||||
x = x - 1;
|
||||
if x > 5 { continue; } // skip to the next iteration
|
||||
print(x);
|
||||
if x == 0 { break; } // break out of loop
|
||||
}
|
||||
```
|
28
doc/src/language/method.md
Normal file
28
doc/src/language/method.md
Normal file
@@ -0,0 +1,28 @@
|
||||
Call Method as Function
|
||||
======================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like a regular function in Rust.
|
||||
|
||||
Unlike functions defined in script (for which all arguments are passed by _value_),
|
||||
native Rust functions may mutate the object (or the first argument if called in normal function call style).
|
||||
|
||||
Custom types, properties and methods can be disabled via the [`no_object`] feature.
|
||||
|
||||
```rust
|
||||
let a = new_ts(); // constructor function
|
||||
a.field = 500; // property setter
|
||||
a.update(); // method call, 'a' can be modified
|
||||
|
||||
update(a); // <- this de-sugars to 'a.update()' thus if 'a' is a simple variable
|
||||
// unlike scripted functions, 'a' can be modified and is not a copy
|
||||
|
||||
let array = [ a ];
|
||||
|
||||
update(array[0]); // <- 'array[0]' is an expression returning a calculated value,
|
||||
// a transient (i.e. a copy) so this statement has no effect
|
||||
// except waste a lot of time cloning
|
||||
|
||||
array[0].update(); // <- call this method-call style will update 'a'
|
||||
```
|
7
doc/src/language/modules.md
Normal file
7
doc/src/language/modules.md
Normal file
@@ -0,0 +1,7 @@
|
||||
Modules
|
||||
=======
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_.
|
||||
Modules can be disabled via the [`no_module`] feature.
|
53
doc/src/language/modules/ast.md
Normal file
53
doc/src/language/modules/ast.md
Normal file
@@ -0,0 +1,53 @@
|
||||
Create a Module from an AST
|
||||
==========================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
It is easy to convert a pre-compiled `AST` into a module: just use `Module::eval_ast_as_new`.
|
||||
|
||||
Don't forget the [`export`] statement, otherwise there will be no variables exposed by the module
|
||||
other than non-[`private`] functions (unless that's intentional).
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Module};
|
||||
|
||||
let engine = Engine::new();
|
||||
|
||||
// Compile a script into an 'AST'
|
||||
let ast = engine.compile(r#"
|
||||
// Functions become module functions
|
||||
fn calc(x) {
|
||||
x + 1
|
||||
}
|
||||
fn add_len(x, y) {
|
||||
x + y.len
|
||||
}
|
||||
|
||||
// Imported modules can become sub-modules
|
||||
import "another module" as extra;
|
||||
|
||||
// Variables defined at global level can become module variables
|
||||
const x = 123;
|
||||
let foo = 41;
|
||||
let hello;
|
||||
|
||||
// Variable values become constant module variable values
|
||||
foo = calc(foo);
|
||||
hello = "hello, " + foo + " worlds!";
|
||||
|
||||
// Finally, export the variables and modules
|
||||
export
|
||||
x as abc, // aliased variable name
|
||||
foo,
|
||||
hello,
|
||||
extra as foobar; // export sub-module
|
||||
"#)?;
|
||||
|
||||
// Convert the 'AST' into a module, using the 'Engine' to evaluate it first
|
||||
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
||||
|
||||
// 'module' now can be loaded into a custom 'Scope' for future use. It contains:
|
||||
// - sub-module: 'foobar' (renamed from 'extra')
|
||||
// - functions: 'calc', 'add_len'
|
||||
// - variables: 'abc' (renamed from 'x'), 'foo', 'hello'
|
||||
```
|
32
doc/src/language/modules/export.md
Normal file
32
doc/src/language/modules/export.md
Normal file
@@ -0,0 +1,32 @@
|
||||
Export Variables and Functions from Modules
|
||||
==========================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
A _module_ is a single script (or pre-compiled `AST`) containing global variables and functions.
|
||||
|
||||
The `export` statement, which can only be at global level, exposes selected variables as members of a module.
|
||||
|
||||
Variables not exported are _private_ and invisible to the outside.
|
||||
|
||||
On the other hand, all functions are automatically exported, _unless_ it is explicitly opt-out with the [`private`] prefix.
|
||||
|
||||
Functions declared [`private`] are invisible to the outside.
|
||||
|
||||
Everything exported from a module is **constant** (**read-only**).
|
||||
|
||||
```rust
|
||||
// This is a module script.
|
||||
|
||||
fn inc(x) { x + 1 } // script-defined function - default public
|
||||
|
||||
private fn foo() {} // private function - invisible to outside
|
||||
|
||||
let private = 123; // variable not exported - default invisible to outside
|
||||
let x = 42; // this will be exported below
|
||||
|
||||
export x; // the variable 'x' is exported under its own name
|
||||
|
||||
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'
|
||||
```
|
45
doc/src/language/modules/import.md
Normal file
45
doc/src/language/modules/import.md
Normal file
@@ -0,0 +1,45 @@
|
||||
Import a Module
|
||||
===============
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
A module can be _imported_ via the `import` statement, and its members are accessed via '`::`' similar to C++.
|
||||
|
||||
```rust
|
||||
import "crypto" as lock; // import the script file 'crypto.rhai' as a module named 'lock'
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
`import` statements are _scoped_, meaning that they are only accessible inside the scope that they're imported.
|
||||
They can appear anywhere a normal statement can be, but in the vast majority of cases `import` statements are
|
||||
group at the beginning of a script.
|
||||
|
||||
It is, however, not advised to deviate from this common practice unless there is a _Very Good Reason™_.
|
||||
Especially, do not place an `import` statement within a loop; doing so will repeatedly re-load the same module
|
||||
during every iteration of the loop!
|
||||
|
||||
```rust
|
||||
let mod = "crypto";
|
||||
|
||||
if secured { // new block scope
|
||||
import mod as c; // import module (the path needs not be a constant string)
|
||||
|
||||
c::encrypt(key); // use a function in the module
|
||||
} // the module disappears at the end of the block scope
|
||||
|
||||
crypto::encrypt(others); // <- this causes a run-time error because the 'crypto' module
|
||||
// is no longer available!
|
||||
|
||||
for x in range(0, 1000) {
|
||||
import "crypto" as c; // <- importing a module inside a loop is a Very Bad Idea™
|
||||
|
||||
c.encrypt(something);
|
||||
}
|
||||
```
|
29
doc/src/language/modules/resolvers.md
Normal file
29
doc/src/language/modules/resolvers.md
Normal file
@@ -0,0 +1,29 @@
|
||||
Module Resolvers
|
||||
================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
When encountering an [`import`] statement, Rhai attempts to _resolve_ the module based on the path string.
|
||||
|
||||
_Module Resolvers_ are service types that implement the [`ModuleResolver`](/rust/traits.md) trait.
|
||||
|
||||
There are a number of standard resolvers built into Rhai, the default being the `FileModuleResolver`
|
||||
which simply loads a script file based on the path (with `.rhai` extension attached) and execute it to form a module.
|
||||
|
||||
Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
|
||||
|
||||
| Module Resolver | Description |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. |
|
||||
| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. |
|
||||
|
||||
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
|
||||
|
||||
```rust
|
||||
// Use the 'StaticModuleResolver'
|
||||
let resolver = rhai::module_resolvers::StaticModuleResolver::new();
|
||||
engine.set_module_resolver(Some(resolver));
|
||||
|
||||
// Effectively disable 'import' statements by setting module resolver to 'None'
|
||||
engine.set_module_resolver(None);
|
||||
```
|
30
doc/src/language/modules/rust.md
Normal file
30
doc/src/language/modules/rust.md
Normal file
@@ -0,0 +1,30 @@
|
||||
Create a Module from Rust
|
||||
========================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
To load a custom module (written in Rust) into an [`Engine`], first create a [`Module`] type,
|
||||
add variables/functions into it, then finally push it into a custom [`Scope`].
|
||||
|
||||
This has the equivalent effect of putting an [`import`] statement at the beginning of any script run.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope, Module, i64};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
let mut scope = Scope::new();
|
||||
|
||||
let mut module = Module::new(); // new module
|
||||
module.set_var("answer", 41_i64); // variable 'answer' under module
|
||||
module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions
|
||||
|
||||
// Push the module into the custom scope under the name 'question'
|
||||
// This is equivalent to 'import "..." as question;'
|
||||
scope.push_module("question", module);
|
||||
|
||||
// Use module-qualified variables
|
||||
engine.eval_expression_with_scope::<i64>(&scope, "question::answer + 1")? == 42;
|
||||
|
||||
// Call module-qualified functions
|
||||
engine.eval_expression_with_scope::<i64>(&scope, "question::inc(question::answer)")? == 42;
|
||||
```
|
32
doc/src/language/num-fn.md
Normal file
32
doc/src/language/num-fn.md
Normal file
@@ -0,0 +1,32 @@
|
||||
Numeric Functions
|
||||
================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Integer Functions
|
||||
----------------
|
||||
|
||||
The following standard functions (defined in the [`BasicMathPackage`](/rust/packages.md) but excluded if using a [raw `Engine`])
|
||||
operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only:
|
||||
|
||||
| Function | Description |
|
||||
| ------------ | --------------------------------- |
|
||||
| `abs` | absolute value |
|
||||
| [`to_float`] | converts an integer type to `f64` |
|
||||
|
||||
Floating-Point Functions
|
||||
-----------------------
|
||||
|
||||
The following standard functions (defined in the [`BasicMathPackage`](/rust/packages.md) but excluded if using a [raw `Engine`])
|
||||
operate on `f64` only:
|
||||
|
||||
| Category | Functions |
|
||||
| ---------------- | --------------------------------------------------------------------- |
|
||||
| Trigonometry | `sin`, `cos`, `tan`, `sinh`, `cosh`, `tanh` in degrees |
|
||||
| Arc-trigonometry | `asin`, `acos`, `atan`, `asinh`, `acosh`, `atanh` in degrees |
|
||||
| Square root | `sqrt` |
|
||||
| Exponential | `exp` (base _e_) |
|
||||
| Logarithmic | `ln` (base _e_), `log10` (base 10), `log` (any base) |
|
||||
| Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` methods and properties |
|
||||
| Conversion | [`to_int`] |
|
||||
| Testing | `is_nan`, `is_finite`, `is_infinite` methods and properties |
|
51
doc/src/language/num-op.md
Normal file
51
doc/src/language/num-op.md
Normal file
@@ -0,0 +1,51 @@
|
||||
Numeric Operators
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Numeric operators generally follow C styles.
|
||||
|
||||
Unary Operators
|
||||
---------------
|
||||
|
||||
| Operator | Description |
|
||||
| -------- | ----------- |
|
||||
| `+` | Plus |
|
||||
| `-` | Negative |
|
||||
|
||||
```rust
|
||||
let number = -5;
|
||||
|
||||
number = -5 - +5;
|
||||
```
|
||||
|
||||
Binary Operators
|
||||
----------------
|
||||
|
||||
| Operator | Description | Integers only |
|
||||
| -------- | ---------------------------------------------------- | :-----------: |
|
||||
| `+` | Plus | |
|
||||
| `-` | Minus | |
|
||||
| `*` | Multiply | |
|
||||
| `/` | Divide (integer division if acting on integer types) | |
|
||||
| `%` | Modulo (remainder) | |
|
||||
| `~` | Power | |
|
||||
| `&` | Binary _And_ bit-mask | Yes |
|
||||
| `\|` | Binary _Or_ bit-mask | Yes |
|
||||
| `^` | Binary _Xor_ bit-mask | Yes |
|
||||
| `<<` | Left bit-shift | Yes |
|
||||
| `>>` | Right bit-shift | Yes |
|
||||
|
||||
```rust
|
||||
let x = (1 + 2) * (6 - 4) / 2; // arithmetic, with parentheses
|
||||
|
||||
let reminder = 42 % 10; // modulo
|
||||
|
||||
let power = 42 ~ 2; // power (i64 and f64 only)
|
||||
|
||||
let left_shifted = 42 << 3; // left shift
|
||||
|
||||
let right_shifted = 42 >> 3; // right shift
|
||||
|
||||
let bit_op = 42 | 99; // bit masking
|
||||
```
|
21
doc/src/language/numbers.md
Normal file
21
doc/src/language/numbers.md
Normal file
@@ -0,0 +1,21 @@
|
||||
Numbers
|
||||
=======
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Integer numbers follow C-style format with support for decimal, binary ('`0b`'), octal ('`0o`') and hex ('`0x`') notations.
|
||||
|
||||
The default system integer type (also aliased to `INT`) is `i64`. It can be turned into `i32` via the [`only_i32`] feature.
|
||||
|
||||
Floating-point numbers are also supported if not disabled with [`no_float`]. The default system floating-point type is `i64`
|
||||
(also aliased to `FLOAT`).
|
||||
|
||||
'`_`' separators can be added freely and are ignored within a number.
|
||||
|
||||
| Format | Type |
|
||||
| ---------------- | ---------------- |
|
||||
| `123_345`, `-42` | `i64` in decimal |
|
||||
| `0o07_76` | `i64` in octal |
|
||||
| `0xabcd_ef` | `i64` in hex |
|
||||
| `0b0101_1001` | `i64` in binary |
|
||||
| `123_456.789` | `f64` |
|
124
doc/src/language/object-maps.md
Normal file
124
doc/src/language/object-maps.md
Normal file
@@ -0,0 +1,124 @@
|
||||
Object Maps
|
||||
===========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Object maps are hash dictionaries. Properties are all [`Dynamic`] and can be freely added and retrieved.
|
||||
|
||||
The Rust type of a Rhai object map is `rhai::Map`.
|
||||
|
||||
[`type_of()`] an object map returns `"map"`.
|
||||
|
||||
Object maps are disabled via the [`no_object`] feature.
|
||||
|
||||
The maximum allowed size of an object map can be controlled via `Engine::set_max_map_size`
|
||||
(see [maximum size of object maps]).
|
||||
|
||||
|
||||
Object Map Literals
|
||||
------------------
|
||||
|
||||
Object map literals are built within braces '`#{`' ... '`}`' (_name_ `:` _value_ syntax similar to Rust)
|
||||
and separated by commas '`,`'. The property _name_ can be a simple variable name following the same
|
||||
naming rules as [variables], or an arbitrary [string] literal.
|
||||
|
||||
|
||||
Access Properties
|
||||
----------------
|
||||
|
||||
Property values can be accessed via the _dot_ notation (_object_ `.` _property_)
|
||||
or _index_ notation (_object_ `[` _property_ `]`).
|
||||
|
||||
The dot notation allows only property names that follow the same naming rules as [variables].
|
||||
|
||||
The index notation allows setting/getting properties of arbitrary names (even the empty [string]).
|
||||
|
||||
**Important:** Trying to read a non-existent property returns [`()`] instead of causing an error.
|
||||
|
||||
|
||||
Built-in Functions
|
||||
-----------------
|
||||
|
||||
The following methods (defined in the [`BasicMapPackage`](/rust/packages.md) but excluded if using a [raw `Engine`])
|
||||
operate on object maps:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `has` | property name | does the object map contain a property of a particular name? |
|
||||
| `len` | _none_ | returns the number of properties |
|
||||
| `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 | first object map, second object map | merges the first object map with the second |
|
||||
| `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] |
|
||||
| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let y = #{ // object map literal with 3 properties
|
||||
a: 1,
|
||||
bar: "hello",
|
||||
"baz!$@": 123.456, // like JS, you can use any string as property names...
|
||||
"": false, // even the empty string!
|
||||
|
||||
a: 42 // <- syntax error: duplicated property name
|
||||
};
|
||||
|
||||
y.a = 42; // access via dot notation
|
||||
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.a == 42;
|
||||
|
||||
y["baz!$@"] == 123.456; // access via index notation
|
||||
|
||||
"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"];
|
||||
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.has("a") == true;
|
||||
y.has("xyz") == false;
|
||||
|
||||
y.xyz == (); // a non-existing property returns '()'
|
||||
y["xyz"] == ();
|
||||
|
||||
y.len() == 3;
|
||||
|
||||
y.remove("a") == 1; // remove property
|
||||
|
||||
y.len() == 2;
|
||||
y.has("a") == false;
|
||||
|
||||
for name in keys(y) { // get an array of all the property names via the 'keys' function
|
||||
print(name);
|
||||
}
|
||||
|
||||
for val in values(y) { // get an array of all the property values via the 'values' function
|
||||
print(val);
|
||||
}
|
||||
|
||||
y.clear(); // empty the object map
|
||||
|
||||
y.len() == 0;
|
||||
```
|
22
doc/src/language/overload.md
Normal file
22
doc/src/language/overload.md
Normal file
@@ -0,0 +1,22 @@
|
||||
Function Overloading
|
||||
===================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Functions defined in script can be _overloaded_ by _arity_ (i.e. they are resolved purely upon the function's _name_
|
||||
and _number_ of parameters, but not parameter _types_ since all parameters are the same type - [`Dynamic`]).
|
||||
|
||||
New definitions _overwrite_ previous definitions of the same name and number of parameters.
|
||||
|
||||
```rust
|
||||
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) { print("HA! NEW ONE! " + x) } // overwrites previous definition
|
||||
|
||||
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."
|
||||
```
|
44
doc/src/language/print-debug.md
Normal file
44
doc/src/language/print-debug.md
Normal file
@@ -0,0 +1,44 @@
|
||||
`print` and `debug`
|
||||
===================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The `print` and `debug` functions default to printing to `stdout`, with `debug` using standard debug formatting.
|
||||
|
||||
```rust
|
||||
print("hello"); // prints hello to stdout
|
||||
print(1 + 2 + 3); // prints 6 to stdout
|
||||
print("hello" + 42); // prints hello42 to stdout
|
||||
debug("world!"); // prints "world!" to stdout using debug formatting
|
||||
```
|
||||
|
||||
Override `print` and `debug` with Callback Functions
|
||||
--------------------------------------------------
|
||||
|
||||
When embedding Rhai into an application, it is usually necessary to trap `print` and `debug` output
|
||||
(for logging into a tracking log, for example) with the `Engine::on_print` and `Engine::on_debug` methods:
|
||||
|
||||
```rust
|
||||
// Any function or closure that takes an '&str' argument can be used to override
|
||||
// 'print' and 'debug'
|
||||
engine.on_print(|x| println!("hello: {}", x));
|
||||
engine.on_debug(|x| println!("DEBUG: {}", x));
|
||||
|
||||
// Example: quick-'n-dirty logging
|
||||
let logbook = Arc::new(RwLock::new(Vec::<String>::new()));
|
||||
|
||||
// Redirect print/debug output to 'log'
|
||||
let log = logbook.clone();
|
||||
engine.on_print(move |s| log.write().unwrap().push(format!("entry: {}", s)));
|
||||
|
||||
let log = logbook.clone();
|
||||
engine.on_debug(move |s| log.write().unwrap().push(format!("DEBUG: {}", s)));
|
||||
|
||||
// Evaluate script
|
||||
engine.eval::<()>(script)?;
|
||||
|
||||
// 'logbook' captures all the 'print' and 'debug' output
|
||||
for entry in logbook.read().unwrap().iter() {
|
||||
println!("{}", entry);
|
||||
}
|
||||
```
|
10
doc/src/language/return.md
Normal file
10
doc/src/language/return.md
Normal file
@@ -0,0 +1,10 @@
|
||||
Return Values
|
||||
=============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```rust
|
||||
return; // equivalent to return ();
|
||||
|
||||
return 123 + 456; // returns 579
|
||||
```
|
25
doc/src/language/statements.md
Normal file
25
doc/src/language/statements.md
Normal file
@@ -0,0 +1,25 @@
|
||||
Statements
|
||||
==========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Statements are terminated by semicolons '`;`' and they are mandatory,
|
||||
except for the _last_ statement in a _block_ (enclosed by '`{`' .. '`}`' pairs) where it can be omitted.
|
||||
|
||||
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 statement block is _always_ the block's
|
||||
return value when used as a statement.
|
||||
If the last statement has no return value (e.g. variable definitions, assignments) then it is assumed to be [`()`].
|
||||
|
||||
```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 statement block, which is the value of the last statement
|
||||
// ^ the last statement does not require a terminating semicolon (although it also works with it)
|
||||
// ^ semicolon required here to terminate the assignment statement; it is a syntax error without it
|
||||
|
||||
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
|
||||
```
|
64
doc/src/language/string-fn.md
Normal file
64
doc/src/language/string-fn.md
Normal file
@@ -0,0 +1,64 @@
|
||||
Built-in String Functions
|
||||
========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The following standard methods (mostly defined in the [`MoreStringPackage`](/rust/packages.md) but excluded if
|
||||
using a [raw `Engine`]) operate on [strings]:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
|
||||
| `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string |
|
||||
| `pad` | character to pad, target length | pads the string with an character to at least a specified length |
|
||||
| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string |
|
||||
| `clear` | _none_ | empties the string |
|
||||
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
|
||||
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
|
||||
| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
|
||||
| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
|
||||
| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
|
||||
| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another |
|
||||
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
|
||||
|
||||
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.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;
|
||||
```
|
123
doc/src/language/strings-chars.md
Normal file
123
doc/src/language/strings-chars.md
Normal file
@@ -0,0 +1,123 @@
|
||||
Strings and Characters
|
||||
=====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
String in Rhai contain any text sequence of valid Unicode characters.
|
||||
Internally strings are stored in UTF-8 encoding.
|
||||
|
||||
Strings can be built up from other strings and types via the `+` operator (provided by the [`MoreStringPackage`](/rust/packages.md)
|
||||
but excluded if using a [raw `Engine`]). This is particularly useful when printing output.
|
||||
|
||||
[`type_of()`] a string returns `"string"`.
|
||||
|
||||
The maximum allowed length of a string can be controlled via `Engine::set_max_string_size`
|
||||
(see [maximum length of strings]).
|
||||
|
||||
The `ImmutableString` Type
|
||||
-------------------------
|
||||
|
||||
All strings in Rhai are implemented as `ImmutableString` (see [standard types]).
|
||||
|
||||
`ImmutableString` should be used in place of the standard Rust type `String` when registering functions.
|
||||
|
||||
|
||||
String and Character Literals
|
||||
----------------------------
|
||||
|
||||
String and character literals follow C-style formatting, with support for Unicode ('`\u`_xxxx_' or '`\U`_xxxxxxxx_')
|
||||
and hex ('`\x`_xx_') escape sequences.
|
||||
|
||||
Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full,
|
||||
32-bit extended Unicode code points.
|
||||
|
||||
Standard escape sequences:
|
||||
|
||||
| Escape sequence | Meaning |
|
||||
| --------------- | ------------------------------ |
|
||||
| `\\` | back-slash `\` |
|
||||
| `\t` | tab |
|
||||
| `\r` | carriage-return `CR` |
|
||||
| `\n` | line-feed `LF` |
|
||||
| `\"` | double-quote `"` in strings |
|
||||
| `\'` | single-quote `'` in characters |
|
||||
| `\x`_xx_ | Unicode in 2-digit hex |
|
||||
| `\u`_xxxx_ | Unicode in 4-digit hex |
|
||||
| `\U`_xxxxxxxx_ | Unicode in 8-digit hex |
|
||||
|
||||
|
||||
Differences from Rust Strings
|
||||
----------------------------
|
||||
|
||||
Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`'s!),
|
||||
but nevertheless there are major differences.
|
||||
|
||||
In Rhai a string is the same as an array of Unicode characters and can be directly indexed (unlike Rust).
|
||||
|
||||
This is similar to most other languages where strings are internally represented not as UTF-8 but as arrays of multi-byte
|
||||
Unicode characters.
|
||||
|
||||
Individual characters within a Rhai string can also be replaced just as if the string is an array of Unicode characters.
|
||||
|
||||
In Rhai, there is also no separate concepts of `String` and `&str` as in Rust.
|
||||
|
||||
|
||||
Immutable Strings
|
||||
----------------
|
||||
|
||||
Rhai strings are _immutable_ and can be shared.
|
||||
|
||||
Modifying a Rhai string actually causes it first to be cloned, and then the modification made to the copy.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
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';
|
||||
|
||||
ts.s = record; // custom type properties can take strings
|
||||
|
||||
let c = ts.s[4];
|
||||
c == 'C';
|
||||
|
||||
let c = "foo"[0]; // indexing also works on string literals...
|
||||
c == 'f';
|
||||
|
||||
let c = ("foo" + "bar")[5]; // ... and expressions returning strings
|
||||
c == 'r';
|
||||
|
||||
// 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);
|
||||
}
|
||||
```
|
32
doc/src/language/throw.md
Normal file
32
doc/src/language/throw.md
Normal file
@@ -0,0 +1,32 @@
|
||||
Throw Exception on Error
|
||||
=======================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
All of [`Engine`]'s evaluation/consuming methods return `Result<T, Box<rhai::EvalAltResult>>`
|
||||
with `EvalAltResult` holding error information.
|
||||
|
||||
To deliberately return an error during an evaluation, use the `throw` keyword.
|
||||
|
||||
```rust
|
||||
if some_bad_condition_has_happened {
|
||||
throw error; // 'throw' takes a string as the exception text
|
||||
}
|
||||
|
||||
throw; // defaults to empty exception text: ""
|
||||
```
|
||||
|
||||
Exceptions thrown via `throw` in the script can be captured by matching `Err(Box<EvalAltResult::ErrorRuntime(` _reason_ `,` _position_ `)>)`
|
||||
with the exception text captured by the first parameter.
|
||||
|
||||
```rust
|
||||
let result = engine.eval::<i64>(r#"
|
||||
let x = 42;
|
||||
|
||||
if x > 0 {
|
||||
throw x + " is too large!";
|
||||
}
|
||||
"#);
|
||||
|
||||
println!(result); // prints "Runtime error: 42 is too large! (line 5, position 15)"
|
||||
```
|
38
doc/src/language/timestamps.md
Normal file
38
doc/src/language/timestamps.md
Normal file
@@ -0,0 +1,38 @@
|
||||
`timestamp`'s
|
||||
=============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Timestamps are provided by the [`BasicTimePackage`](/rust/packages.md) (excluded if using a [raw `Engine`])
|
||||
via the `timestamp` function.
|
||||
|
||||
Timestamps are not available under [`no_std`].
|
||||
|
||||
The Rust type of a timestamp is `std::time::Instant` ([`instant::Instant`](https://crates.io/crates/instant) in [WASM] builds).
|
||||
|
||||
[`type_of()`] a timestamp returns `"timestamp"`.
|
||||
|
||||
|
||||
Built-in Functions
|
||||
-----------------
|
||||
|
||||
The following methods (defined in the [`BasicTimePackage`](/rust/packages.md) but excluded if using a [raw `Engine`]) operate on timestamps:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ----------------------------- | ---------------------------------- | -------------------------------------------------------- |
|
||||
| `elapsed` method and property | _none_ | returns the number of seconds since the timestamp |
|
||||
| `-` operator | later timestamp, earlier timestamp | returns the number of seconds between the two timestamps |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let now = timestamp();
|
||||
|
||||
// Do some lengthy operation...
|
||||
|
||||
if now.elapsed > 30.0 {
|
||||
print("takes too long (over 30 seconds)!")
|
||||
}
|
||||
```
|
26
doc/src/language/type-of.md
Normal file
26
doc/src/language/type-of.md
Normal file
@@ -0,0 +1,26 @@
|
||||
`type_of`
|
||||
=========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The `type_of` function detects the actual type of a value.
|
||||
|
||||
This is useful because all variables are [`Dynamic`] in nature.
|
||||
|
||||
```rust
|
||||
// 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_with_string(x);
|
||||
}
|
||||
```
|
38
doc/src/language/values-and-types.md
Normal file
38
doc/src/language/values-and-types.md
Normal file
@@ -0,0 +1,38 @@
|
||||
Values and Types
|
||||
===============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The following primitive types are supported natively:
|
||||
|
||||
| Category | Equivalent Rust types | [`type_of()`] | `to_string()` |
|
||||
| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------- | --------------------- |
|
||||
| **Integer number** | `u8`, `i8`, `u16`, `i16`, <br/>`u32`, `i32` (default for [`only_i32`]),<br/>`u64`, `i64` _(default)_ | `"i32"`, `"u64"` etc. | `"42"`, `"123"` etc. |
|
||||
| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. |
|
||||
| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
|
||||
| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
|
||||
| **Immutable Unicode string** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`) | `"string"` | `"hello"` etc. |
|
||||
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
|
||||
| **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` |
|
||||
| **Timestamp** (implemented in the [`BasicTimePackage`](/rust/packages.md)) | `std::time::Instant` | `"timestamp"` | _not supported_ |
|
||||
| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
|
||||
| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
|
||||
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
|
||||
| **Nothing/void/nil/null** (or whatever it is called) | `()` | `"()"` | `""` _(empty string)_ |
|
||||
|
||||
All types are treated strictly separate by Rhai, meaning that `i32` and `i64` and `u32` are completely different -
|
||||
they even cannot be added together. This is very similar to Rust.
|
||||
|
||||
The default integer type is `i64`. If other integer types are not needed, it is possible to exclude them and make a
|
||||
smaller build with the [`only_i64`] feature.
|
||||
|
||||
If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`.
|
||||
This is useful on some 32-bit targets where using 64-bit integers incur a performance penalty.
|
||||
|
||||
If no floating-point is needed or supported, use the [`no_float`] feature to remove it.
|
||||
|
||||
[Strings] in Rhai are _immutable_, meaning that they can be shared but not modified. In actual, the `ImmutableString` type
|
||||
is an alias to `Rc<String>` or `Arc<String>` (depending on the [`sync`] feature).
|
||||
Any modification done to a Rhai string will cause the string to be cloned and the modifications made to the copy.
|
||||
|
||||
The `to_string` function converts a standard type into a [string] for display purposes.
|
35
doc/src/language/variables.md
Normal file
35
doc/src/language/variables.md
Normal file
@@ -0,0 +1,35 @@
|
||||
Variables
|
||||
=========
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Variables in Rhai follow normal C naming rules (i.e. must contain only ASCII letters, digits and underscores '`_`').
|
||||
|
||||
Variable names must start with an ASCII letter or an underscore '`_`', must contain at least one ASCII letter,
|
||||
and must start with an ASCII letter before a digit.
|
||||
|
||||
Therefore, names like '`_`', '`_42`', '`3a`' etc. are not legal variable names, but '`_c3po`' and '`r2d2`' are.
|
||||
Variable names are also case _sensitive_.
|
||||
|
||||
Variables are defined using the `let` keyword. A variable defined within a statement block is _local_ to that block.
|
||||
|
||||
```rust
|
||||
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
|
||||
x == 42;
|
||||
X == 123;
|
||||
|
||||
{
|
||||
let x = 999; // local variable 'x' shadows the 'x' in parent block
|
||||
x == 999; // access to local 'x'
|
||||
}
|
||||
x == 42; // the parent block's 'x' is not changed
|
||||
```
|
15
doc/src/language/while.md
Normal file
15
doc/src/language/while.md
Normal file
@@ -0,0 +1,15 @@
|
||||
`while` Loop
|
||||
============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```rust
|
||||
let x = 10;
|
||||
|
||||
while x > 0 {
|
||||
x = x - 1;
|
||||
if x < 6 { continue; } // skip to the next iteration
|
||||
print(x);
|
||||
if x == 5 { break; } // break out of while loop
|
||||
}
|
||||
```
|
Reference in New Issue
Block a user