Edit documentation.

This commit is contained in:
Stephen Chung
2020-06-22 00:03:45 +08:00
parent 7cc1a3f5dc
commit d728ac6758
37 changed files with 386 additions and 147 deletions

View File

@@ -13,7 +13,8 @@ print(x * 2); // prints 84
x = 123; // <- syntax error: cannot assign to constant
```
Constants must be assigned a _value_, not an expression.
Unlike variables which need not have initial values (default to [`()`]),
constants must be assigned one, and it must be a constant _value_, not an expression.
```rust
const x = 40 + 2; // <- syntax error: cannot assign expression to constant

View File

@@ -6,29 +6,29 @@
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!
Saving the best for last, there is the ever-dreaded... `eval` function!
```rust
let x = 10;
fn foo(x) { x += 12; x }
let script = "let y = x;"; // build a script
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!
let result = eval(script); // <- look, JS, we can also do this!
print("Answer: " + result); // prints 42
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!
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
eval("{ let z = y }"); // to keep a variable local, use a statement block
print("z = " + z); // <- error: variable 'z' not found
print("z = " + z); // <- error: variable 'z' not found
"print(42)".eval(); // <- nope... method-call style doesn't work
"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_,
@@ -45,8 +45,8 @@ 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
eval(script); // variable 'x' in the current scope is visible!
print(x); // prints 42
// The above is equivalent to:
let script = "x += 32";
@@ -65,7 +65,7 @@ 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"
let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run 40 + 2"
```
Or overload it from Rust:
@@ -86,11 +86,11 @@ There is even a package named [`EvalPackage`]({{rootUrl}}/rust/packages.md) whic
```rust
use rhai::Engine;
use rhai::packages::Package // load the 'Package' trait to use packages
use rhai::packages::EvalPackage; // the 'eval' package disables 'eval'
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
let package = EvalPackage::new(); // create the package
engine.load_package(package.get()); // load the package
engine.load_package(package.get()); // load the package
```

View File

@@ -5,50 +5,53 @@
Iterating through a range or an [array] is provided by the `for` ... `in` loop.
Like C, `continue` can be used to skip to the next iteration, by-passing all following statements;
`break` can be used to break out of the loop unconditionally.
```rust
// Iterate through string, yielding characters
let s = "hello, world!";
for ch in s {
if ch > 'z' { continue; } // skip to the next iteration
if ch > 'z' { continue; } // skip to the next iteration
print(ch);
if x == '@' { break; } // break out of for loop
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
if x > 10 { continue; } // skip to the next iteration
print(x);
if x == 42 { break; } // break out of for loop
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
if x > 10 { continue; } // skip to the next iteration
print(x);
if x == 42 { break; } // break out of for loop
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
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
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
// Property names are returned in unsorted, random order
for x in keys(map) {
if x > 10 { continue; } // skip to the next iteration
if x > 10 { continue; } // skip to the next iteration
print(x);
if x == 42 { break; } // break out of for loop
if x == 42 { break; } // break out of for loop
}
// Property values are returned in random order
// Property values are returned in unsorted, random order
for val in values(map) {
print(val);
}

View File

@@ -18,6 +18,7 @@ print(add(2, 3)); // prints 5
print(sub(2, 3,)); // prints -1 - trailing comma in arguments list is OK
```
Implicit Return
---------------
@@ -38,6 +39,7 @@ print(add(2, 3)); // prints 5
print(add2(42)); // prints 44
```
No Access to External Scope
--------------------------
@@ -50,13 +52,15 @@ let x = 42;
fn foo() { x } // <- syntax error: variable 'x' doesn't exist
```
Passing Arguments by Value
-------------------------
Arguments Passed 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).
Therefore, functions with the same name and same _number_ of parameters are equivalent.
It is important to remember that 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.
This can introduce subtle bugs, if not careful, especially when using the _method-call_ style.
@@ -71,6 +75,7 @@ x.change(); // de-sugars to 'change(x)'
x == 500; // 'x' is NOT changed!
```
Global Definitions Only
----------------------
@@ -92,6 +97,12 @@ fn do_addition(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.
Use Before Definition
--------------------
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 JS's `function` keyword.

View File

@@ -3,24 +3,30 @@
{{#include ../links.md}}
`if` statements follow C syntax:
```rust
if foo(x) {
print("It's true!");
} else if bar == baz {
print("It's true again!");
} else if ... {
:
} else if ... {
:
} 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!");
}
```
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.
Unlike C, the condition expression does _not_ need to be enclosed in parentheses '`(`' .. '`)`', but
all branches of the `if` statement must be enclosed within braces '`{`' .. '`}`',
even when there is only one statement inside the branch.
Like Rust, there is no ambiguity regarding which `if` clause a branch belongs to.
```rust
// Rhai is not C!
if (decision) print("I've decided!");
// ^ syntax error, expecting '{' in statement block
```

View File

@@ -3,6 +3,11 @@ Infinite `loop`
{{#include ../links.md}}
Infinite loops follow C syntax.
Like C, `continue` can be used to skip to the next iteration, by-passing all following statements;
`break` can be used to break out of the loop unconditionally.
```rust
let x = 10;
@@ -13,3 +18,6 @@ loop {
if x == 0 { break; } // break out of loop
}
```
Beware: a `loop` statement without a `break` statement inside its loop block is infinite -
there is no way for the loop to stop iterating.

View File

@@ -3,11 +3,16 @@ 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.
Property getters/setters and methods in a Rust custom type registered with the [`Engine`] can be called
just like a regular function. In fact, like Rust, property getters/setters and object methods
are registered as regular functions in Rhai that take a first `&mut` parameter.
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).
However, sometimes it is not as straight-forward, and methods called in function-call style may end up
not muting the object - see the example below. Therefore, it is best to always use method-call style.
Custom types, properties and methods can be disabled via the [`no_object`] feature.
```rust
@@ -21,8 +26,8 @@ update(a); // <- this de-sugars to 'a.update()' thus if 'a' is a simple
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
// 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'
array[0].update(); // <- call in method-call style will update 'a'
```

View File

@@ -1,18 +1 @@
Modules
=======
{{#include ../links.md}}
Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_.
Modules can be disabled via the [`no_module`] feature.
A module is of the type `Module` and encapsulates a Rhai script together with the functions defined
by that script.
The script text is run, variables are then selectively exposed via the [`export`] statement.
Functions defined by the script are automatically exported.
Modules loaded within this module at the global level become _sub-modules_ and are also automatically exported.
Other scripts can then load this module and use the variables and functions exported
as if they were defined inside the same script.
# Modules

View File

@@ -7,8 +7,8 @@ For many applications in which Rhai is embedded, it is necessary to customize th
are resolved. For instance, modules may need to be loaded from script texts stored in a database,
not in the file system.
A module resolver must implement the trait `rhai::ModuleResolver`, which contains only one function:
`resolve`.
A module resolver must implement the trait [`rhai::ModuleResolver`]({{rootUrl}}/rust/traits.md),
which contains only one function: `resolve`.
When Rhai prepares to load a module, `ModuleResolver::resolve` is called with the name
of the _module path_ (i.e. the path specified in the [`import`] statement). Upon success, it should

View File

@@ -3,6 +3,9 @@ Import a Module
{{#include ../../links.md}}
`import` Statement
-----------------
A module can be _imported_ via the `import` statement, and its members are accessed via '`::`' similar to C++.
```rust
@@ -17,10 +20,14 @@ print(lock::status); // module variables are constants
lock::status = "off"; // <- runtime error - cannot modify a constant
```
Scoped Imports
--------------
`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
group at the beginning of a script. 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
@@ -44,3 +51,32 @@ for x in range(0, 1000) {
c.encrypt(something);
}
```
Recursive Imports
----------------
Beware of _import cycles_ - i.e. recursively loading the same module. This is a sure-fire way to
cause a stack overflow in the [`Engine`], unless stopped by setting a limit for [maximum number of modules].
For instance, importing itself always causes an infinite recursion:
```rust
// This file is 'hello.rhai'
import "hello" as foo; // import itself - infinite recursion!
foo::do_something();
```
Modules cross-referencing also cause infinite recursion:
```rust
// This file is 'hello.rhai' - references 'world.rhai'
import "world" as foo;
foo::do_something();
// This file is 'world.rhai' - references 'hello.rhai'
import "hello" as bar;
bar::do_something_else();
```

View File

@@ -0,0 +1,18 @@
Modules
=======
{{#include ../../links.md}}
Rhai allows organizing code (functions, both Rust-based or script-based, and variables) into _modules_.
Modules can be disabled via the [`no_module`] feature.
A module is of the type `Module` and encapsulates a Rhai script together with the functions defined
by that script.
The script text is run, variables are then selectively exposed via the [`export`] statement.
Functions defined by the script are automatically exported.
Modules loaded within this module at the global level become _sub-modules_ and are also automatically exported.
Other scripts can then load this module and use the variables and functions exported
as if they were defined inside the same script.

View File

@@ -3,8 +3,14 @@ Return Values
{{#include ../links.md}}
The `return` statement is used to immediately stop evaluation and exist the current context
(typically a function call) yielding a _return value_.
```rust
return; // equivalent to return ();
return 123 + 456; // returns 579
```
A `return` statement at _global_ level stop the entire script evaluation,
the return value is taken as the result of the script evaluation.

View File

@@ -3,13 +3,14 @@ Statements
{{#include ../links.md}}
Terminated by '`;`'
------------------
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 [`()`].
Semicolons can also be omitted if the statement contains a block itself
(e.g. the `if`, `while`, `for` and `loop` statements).
```rust
let a = 42; // normal assignment statement
@@ -20,6 +21,20 @@ let a = { 40 + 2 }; // 'a' is set to the value of the statement block, which
// ^ 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
if foo { a = 42 }
// ^ there is no need to terminate an if-statement with a semicolon
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
```
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 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 [`()`].

View File

@@ -3,6 +3,9 @@ Variables
{{#include ../links.md}}
Valid Names
-----------
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,
@@ -11,9 +14,21 @@ 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.
Variable names cannot be the same as a [keyword].
Declare a Variable
------------------
Variables are declared using the `let` keyword.
Variables do not have to be given an initial value.
If none is provided, then it defaults to [`()`].
A variable defined within a statement block is _local_ to that block.
```rust
let x; // ok - value is '()'
let x = 3; // ok
let _x = 42; // ok
let x_ = 42; // also ok

View File

@@ -3,6 +3,11 @@
{{#include ../links.md}}
`while` loops follow C syntax.
Like C, `continue` can be used to skip to the next iteration, by-passing all following statements;
`break` can be used to break out of the loop unconditionally.
```rust
let x = 10;