diff --git a/README.md b/README.md index b4f0853d..ac9a79a1 100644 --- a/README.md +++ b/README.md @@ -42,4 +42,4 @@ Features Documentation ------------- -See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language. +See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai scripting engine and language. diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index da62423b..2ba5e9e7 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -9,12 +9,12 @@ The Rhai Scripting Language 2. [Getting Started](start.md) 1. [Install the Rhai Crate](start/install.md) 2. [Optional Features](start/features.md) - 3. [Special Builds](start/builds.md) - 1. [Performance Build](start/builds/performance.md) - 2. [Minimal Build](start/builds/minimal.md) - 3. [no-std Build](start/builds/no-std.md) + 3. [Special Builds](start/builds/index.md) + 1. [Performance](start/builds/performance.md) + 2. [Minimal](start/builds/minimal.md) + 3. [no-std](start/builds/no-std.md) 4. [WebAssembly (WASM)](start/builds/wasm.md) - 4. [Examples](start/examples.md) + 4. [Examples](start/examples/index.md) 1. [Rust](start/examples/rust.md) 2. [Scripts](start/examples/scripts.md) 3. [Using the `Engine`](engine.md) @@ -30,7 +30,7 @@ The Rhai Scripting Language 1. [String Parameters in Rust Functions](rust/strings.md) 3. [Register a Generic Rust Function](rust/generic.md) 4. [Register a Fallible Rust Function](rust/fallible.md) - 5. [Packages](rust/packages.md) + 5. [Packages](rust/packages/index.md) 1. [Built-in Packages](rust/packages/builtin.md) 2. [Create a Custom Package](rust/packages/create.md) 6. [Override a Built-in Function](rust/override.md) @@ -40,7 +40,7 @@ The Rhai Scripting Language 2. [Indexers](rust/indexers.md) 3. [Disable Custom Types](rust/disable-custom.md) 4. [Printing Custom Types](rust/print-custom.md) - 9. [Scope - Initializing and Maintaining State](rust/scope.md) + 9. [Scope - Initializing and Maintaining State](rust/scope.md) 10. [Engine Configuration Options](rust/options.md) 5. [Rhai Language Reference](language.md) 1. [Comments](language/comments.md) @@ -72,14 +72,14 @@ The Rhai Scripting Language 1. [Function Overloading](language/overload.md) 2. [Call Method as Function](language/method.md) 15. [Print and Debug](language/print-debug.md) - 16. [Modules](language/modules.md) + 16. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 2. [Import Modules](language/modules/import.md) 3. [Create from Rust](language/modules/rust.md) 4. [Create from AST](language/modules/ast.md) 5. [Module Resolvers](language/modules/resolvers.md) 1. [Implement a Custom Module Resolver](language/modules/imp-resolver.md) -6. [Safety and Protection](safety.md) +6. [Safety and Protection](safety/index.md) 1. [Checked Arithmetic](safety/checked.md) 2. [Sand-Boxing](safety/sandbox.md) 3. [Maximum Length of Strings](safety/max-string-size.md) @@ -91,7 +91,7 @@ The Rhai Scripting Language 8. [Maximum Call Stack Depth](safety/max-call-stack.md) 9. [Maximum Statement Depth](safety/max-stmt-depth.md) 7. [Advanced Topics](advanced.md) - 1. [Script Optimization](engine/optimize.md) + 1. [Script Optimization](engine/optimize/index.md) 1. [Optimization Levels](engine/optimize/optimize-levels.md) 2. [Re-Optimize an AST](engine/optimize/reoptimize.md) 3. [Eager Function Evaluation](engine/optimize/eager.md) @@ -99,3 +99,7 @@ The Rhai Scripting Language 5. [Volatility Considerations](engine/optimize/volatility.md) 6. [Subtle Semantic Changes](engine/optimize/semantics.md) 2. [Eval Statement](language/eval.md) +8. [Appendix](appendix/index.md) + 1. [Keywords](appendix/keywords.md) + 2. [Operators](appendix/operators.md) + 3. [Literals](appendix/literals.md) diff --git a/doc/src/about/related.md b/doc/src/about/related.md index b6d8d8df..f91fdb64 100644 --- a/doc/src/about/related.md +++ b/doc/src/about/related.md @@ -5,8 +5,11 @@ Related Resources Other online documentation resources for Rhai: -* [`DOCS.RS`](https://docs.rs/rhai) +* [`crates.io`](https://crates.io/crates/rhai/) - Rhai crate +* [`DOCS.RS`](https://docs.rs/rhai) - Rhai API documentation + +* [`LIB.RS`](https://lib.rs/crates/rhai) - Rhai library info Other cool projects to check out: diff --git a/doc/src/advanced.md b/doc/src/advanced.md index d1982c2c..3cb28cb5 100644 --- a/doc/src/advanced.md +++ b/doc/src/advanced.md @@ -7,4 +7,4 @@ This section covers advanced features such as: * [Script optimization] -* The dreaded (or beloved depending on your taste) [`eval`] statement +* The dreaded (or beloved for those with twisted tastes) [`eval`] statement diff --git a/doc/src/appendix/index.md b/doc/src/appendix/index.md new file mode 100644 index 00000000..bdb6cae9 --- /dev/null +++ b/doc/src/appendix/index.md @@ -0,0 +1,6 @@ +Appendix +======== + +{{#include ../links.md}} + +This section contains miscellaneous reference materials. diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md new file mode 100644 index 00000000..f6cdd67e --- /dev/null +++ b/doc/src/appendix/keywords.md @@ -0,0 +1,30 @@ +Keywords List +============= + +{{#include ../links.md}} + +| Keyword | Description | +| :--------: | ------------------------------------- | +| `true` | Boolean true literal | +| `false` | Boolean false literal | +| `let` | Variable declaration | +| `const` | Constant declaration | +| `if` | If statement | +| `else` | else block of if statement | +| `while` | While loop | +| `loop` | Infinite loop | +| `for` | For loop | +| `in` | Containment test, part of for loop | +| `continue` | Continue a loop at the next iteration | +| `break` | Loop breaking | +| `return` | Return value | +| `throw` | Throw exception | +| `private` | Mark function private | +| `import` | Import module | +| `export` | Export variable | +| `as` | Alias for variable export | +| `fn` | Function definition | +| `type_of` | Get type name of value | +| `print` | Print value | +| `debug` | Print value in debug format | +| `eval` | Evaluate script | diff --git a/doc/src/appendix/literals.md b/doc/src/appendix/literals.md new file mode 100644 index 00000000..1f14b9fb --- /dev/null +++ b/doc/src/appendix/literals.md @@ -0,0 +1,16 @@ +Literals Syntax +=============== + +{{#include ../links.md}} + +| Type | Literal syntax | +| :--------------------------------: | :---------------------------------------: | +| `INT` | `42`, `-123`, `0` | +| `FLOAT` | `42.0`, `-123.456`, `0.0` | +| [String] | `"... \x?? \u???? \U???????? ..."` | +| Character | `"... \x?? \u???? \U???????? ..."` | +| [`Array`] | `[ ???, ???, ??? ]` | +| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` | +| Boolean true | `true` | +| Boolean false | `false` | +| `Nothing`/`null`/`nil`/`void`/Unit | `()` | diff --git a/doc/src/appendix/operators.md b/doc/src/appendix/operators.md new file mode 100644 index 00000000..4a1f4a2f --- /dev/null +++ b/doc/src/appendix/operators.md @@ -0,0 +1,30 @@ +Operators +========= + +{{#include ../links.md}} + +| Operator | Description | Binary? | +| :---------------: | ---------------------------- | :-----: | +| `+` | Add | Yes | +| `-` | Subtract, Minus | Yes/No | +| `*` | Multiply | Yes | +| `/` | Divide | Yes | +| `%` | Modulo | Yes | +| `~` | Power | Yes | +| `>>` | Right bit-shift | Yes | +| `<<` | Left bit-shift | Yes | +| `&` | Bit-wise AND, Boolean AND | Yes | +| \| | Bit-wise OR, Boolean OR | Yes | +| `^` | Bit-wise XOR | Yes | +| `==` | Equals to | Yes | +| `~=` | Not equals to | Yes | +| `>` | Greater than | Yes | +| `>=` | Greater than or equals to | Yes | +| `<` | Less than | Yes | +| `<=` | Less than or equals to | Yes | +| `>=` | Greater than or equals to | Yes | +| `&&` | Boolean AND (short-circuits) | Yes | +| \|\| | Boolean OR (short-circuits) | Yes | +| `~` | Boolean NOT | No | +| `[` .. `]` | Indexing | Yes | +| `.` | Property access, Method call | Yes | diff --git a/doc/src/context.json b/doc/src/context.json index f898a0c5..187955d4 100644 --- a/doc/src/context.json +++ b/doc/src/context.json @@ -1,4 +1,5 @@ { + "version": "0.15.1", "rootUrl": "", "rootUrlX": "/rhai" } \ No newline at end of file diff --git a/doc/src/engine/optimize.md b/doc/src/engine/optimize/index.md similarity index 99% rename from doc/src/engine/optimize.md rename to doc/src/engine/optimize/index.md index 10ee4f21..e28aa748 100644 --- a/doc/src/engine/optimize.md +++ b/doc/src/engine/optimize/index.md @@ -1,7 +1,7 @@ Script Optimization =================== -{{#include ../links.md}} +{{#include ../../links.md}} Rhai includes an _optimizer_ that tries to optimize a script after parsing. This can reduce resource utilization and increase execution speed. diff --git a/doc/src/engine/optimize/optimize-levels.md b/doc/src/engine/optimize/optimize-levels.md index 11511099..97dc27ea 100644 --- a/doc/src/engine/optimize/optimize-levels.md +++ b/doc/src/engine/optimize/optimize-levels.md @@ -3,10 +3,7 @@ Optimization Levels {{#include ../../links.md}} -Set Optimization Level ---------------------- - -There are actually three levels of optimizations: `None`, `Simple` and `Full`. +There are three levels of optimization: `None`, `Simple` and `Full`. * `None` is obvious - no optimization on the AST is performed. @@ -16,6 +13,10 @@ There are actually three levels of optimizations: `None`, `Simple` and `Full`. * `Full` is _much_ more aggressive, _including_ running functions on constant arguments to determine their result. One benefit to this is that many more optimization opportunities arise, especially with regards to comparison operators. + +Set Optimization Level +--------------------- + An [`Engine`]'s optimization level is set via a call to `Engine::set_optimization_level`: ```rust diff --git a/doc/src/engine/optimize/reoptimize.md b/doc/src/engine/optimize/reoptimize.md index d3ac0f26..bd173be8 100644 --- a/doc/src/engine/optimize/reoptimize.md +++ b/doc/src/engine/optimize/reoptimize.md @@ -3,15 +3,38 @@ Re-Optimize an AST {{#include ../../links.md}} -If it is ever needed to _re_-optimize an `AST`, use the `optimize_ast` method: +Sometimes it is more efficient to store one single, large script with delimited code blocks guarded by +constant variables. This script is compiled once to an `AST`. + +Then, depending on the execution environment, constants are passed into the [`Engine`] and the `AST` +is _re_-optimized based on those constants via the `Engine::optimize_ast` method, +effectively pruning out unused code sections. + +The final, optimized `AST` is then used for evaluations. ```rust -// Compile script to AST -let ast = engine.compile("40 + 2")?; +// Compile master script to AST +let master_ast = engine.compile( +r" + if SCENARIO_1 { + do_work(); + } else if SCENARIO_2 { + do_something(); + } else if SCENARIO_3 { + do_something_else(); + } else { + do_nothing(); + } +")?; -// Create a new 'Scope' - put constants in it to aid optimization if using 'OptimizationLevel::Full' -let scope = Scope::new(); +// Create a new 'Scope' - put constants in it to aid optimization +let mut scope = Scope::new(); +scope.push_constant("SCENARIO_1", true); +scope.push_constant("SCENARIO_2", false); +scope.push_constant("SCENARIO_3", false); // Re-optimize the AST -let ast = engine.optimize_ast(&scope, &ast, OptimizationLevel::Full); +let new_ast = engine.optimize_ast(&scope, master_ast.clone(), OptimizationLevel::Simple); + +// 'new_ast' is essentially: 'do_work()' ``` diff --git a/doc/src/language/constants.md b/doc/src/language/constants.md index 82811166..b4ced7df 100644 --- a/doc/src/language/constants.md +++ b/doc/src/language/constants.md @@ -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 diff --git a/doc/src/language/eval.md b/doc/src/language/eval.md index f79810e5..f7f9965b 100644 --- a/doc/src/language/eval.md +++ b/doc/src/language/eval.md @@ -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 ``` diff --git a/doc/src/language/for.md b/doc/src/language/for.md index eb2406fc..ff784d12 100644 --- a/doc/src/language/for.md +++ b/doc/src/language/for.md @@ -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); } diff --git a/doc/src/language/functions.md b/doc/src/language/functions.md index d2cb48d5..30604a53 100644 --- a/doc/src/language/functions.md +++ b/doc/src/language/functions.md @@ -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. diff --git a/doc/src/language/if.md b/doc/src/language/if.md index a71cbe9a..3958a2e3 100644 --- a/doc/src/language/if.md +++ b/doc/src/language/if.md @@ -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 ``` diff --git a/doc/src/language/loop.md b/doc/src/language/loop.md index 8b1cba8e..299ca3ff 100644 --- a/doc/src/language/loop.md +++ b/doc/src/language/loop.md @@ -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. diff --git a/doc/src/language/method.md b/doc/src/language/method.md index 23822e05..a9cdc1c7 100644 --- a/doc/src/language/method.md +++ b/doc/src/language/method.md @@ -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' ``` diff --git a/doc/src/language/modules.md b/doc/src/language/modules.md index 6b616103..a55ecc05 100644 --- a/doc/src/language/modules.md +++ b/doc/src/language/modules.md @@ -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 diff --git a/doc/src/language/modules/imp-resolver.md b/doc/src/language/modules/imp-resolver.md index 15de7253..a36f930b 100644 --- a/doc/src/language/modules/imp-resolver.md +++ b/doc/src/language/modules/imp-resolver.md @@ -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 diff --git a/doc/src/language/modules/import.md b/doc/src/language/modules/import.md index 536e0745..4ede1f52 100644 --- a/doc/src/language/modules/import.md +++ b/doc/src/language/modules/import.md @@ -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(); +``` diff --git a/doc/src/language/modules/index.md b/doc/src/language/modules/index.md new file mode 100644 index 00000000..cac54f3d --- /dev/null +++ b/doc/src/language/modules/index.md @@ -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. diff --git a/doc/src/language/return.md b/doc/src/language/return.md index 5e696161..41c063f9 100644 --- a/doc/src/language/return.md +++ b/doc/src/language/return.md @@ -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. diff --git a/doc/src/language/statements.md b/doc/src/language/statements.md index c964d952..450011c2 100644 --- a/doc/src/language/statements.md +++ b/doc/src/language/statements.md @@ -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 [`()`]. diff --git a/doc/src/language/variables.md b/doc/src/language/variables.md index 94f0500b..581aa9c4 100644 --- a/doc/src/language/variables.md +++ b/doc/src/language/variables.md @@ -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 diff --git a/doc/src/language/while.md b/doc/src/language/while.md index f0f419fb..e912175e 100644 --- a/doc/src/language/while.md +++ b/doc/src/language/while.md @@ -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; diff --git a/doc/src/links.md b/doc/src/links.md index cc4dd9f0..ba4e57af 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -23,8 +23,8 @@ [`eval_expression_with_scope`]: {{rootUrl}}/engine/expressions.md [raw `Engine`]: {{rootUrl}}/engine/raw.md [built-in operators]: {{rootUrl}}/engine/raw.md#built-in-operators -[package]: {{rootUrl}}/rust/packages.md -[packages]: {{rootUrl}}/rust/packages.md +[package]: {{rootUrl}}/rust/packages/index.md +[packages]: {{rootUrl}}/rust/packages/index.md [`Scope`]: {{rootUrl}}/rust/scope.md [`type_of()`]: {{rootUrl}}/language/type-of.md @@ -41,6 +41,9 @@ [`print`]: {{rootUrl}}/language/print-debug.md [`debug`]: {{rootUrl}}/language/print-debug.md +[keywords]: {{rootUrl}}/appendix/keywords.md +[keyword]: {{rootUrl}}/appendix/keywords.md + [variable]: {{rootUrl}}/language/variables.md [variables]: {{rootUrl}}/language/variables.md @@ -63,9 +66,9 @@ [function]: {{rootUrl}}/language/functions.md [functions]: {{rootUrl}}/language/functions.md -[`Module`]: {{rootUrl}}/language/modules.md -[module]: {{rootUrl}}/language/modules.md -[modules]: {{rootUrl}}/language/modules.md +[`Module`]: {{rootUrl}}/language/modules/index.md +[module]: {{rootUrl}}/language/modules/index.md +[modules]: {{rootUrl}}/language/modules/index.md [`export`]: {{rootUrl}}/language/modules/export.md [`import`]: {{rootUrl}}/language/modules/import.md @@ -80,7 +83,7 @@ [maximum size of object maps]: {{rootUrl}}/safety/max-map-size.md [progress]:/safety/progress.md -[script optimization]: {{rootUrl}}/engine/optimize.md +[script optimization]: {{rootUrl}}/engine/optimize/index.md [`OptimizationLevel::Full`]: {{rootUrl}}/engine/optimize/optimize-levels.md [`OptimizationLevel::Simple`]: {{rootUrl}}/engine/optimize/optimize-levels.md [`OptimizationLevel::None`]: {{rootUrl}}/engine/optimize/optimize-levels.md diff --git a/doc/src/rust/packages.md b/doc/src/rust/packages/index.md similarity index 98% rename from doc/src/rust/packages.md rename to doc/src/rust/packages/index.md index 6f8c5b6c..0b41656f 100644 --- a/doc/src/rust/packages.md +++ b/doc/src/rust/packages/index.md @@ -1,7 +1,7 @@ Packages ======== -{{#include ../links.md}} +{{#include ../../links.md}} Standard built-in Rhai features are provided in various _packages_ that can be loaded via a call to `Engine::load_package`. diff --git a/doc/src/safety.md b/doc/src/safety.md index d207fe9d..1140c921 100644 --- a/doc/src/safety.md +++ b/doc/src/safety.md @@ -1,31 +1 @@ -Safety and Protection Against DoS Attacks -======================================== - -{{#include links.md}} - -For scripting systems open to untrusted user-land scripts, it is always best to limit the amount of -resources used by a script so that it does not consume more resources that it is allowed to. - -The most important resources to watch out for are: - -* **Memory**: A malicous script may continuously grow a [string], an [array] or [object map] until all memory is consumed. - It may also create a large [array] or [object map] literal that exhausts all memory during parsing. - -* **CPU**: A malicous script may run an infinite tight loop that consumes all CPU cycles. - -* **Time**: A malicous script may run indefinitely, thereby blocking the calling system which is waiting for a result. - -* **Stack**: A malicous script may attempt an infinite recursive call that exhausts the call stack. - Alternatively, it may create a degenerated deep expression with so many levels that the parser exhausts the call stack - when parsing the expression; or even deeply-nested statement blocks, if nested deep enough. - -* **Overflows**: A malicous script may deliberately cause numeric over-flows and/or under-flows, divide by zero, and/or - create bad floating-point representations, in order to crash the system. - -* **Files**: A malicous script may continuously [`import`] an external module within an infinite loop, - thereby putting heavy load on the file-system (or even the network if the file is not local). - Furthermore, the module script may simply [`import`] itself in an infinite recursion. - Even when modules are not created from files, they still typically consume a lot of resources to load. - -* **Data**: A malicous script may attempt to read from and/or write to data that it does not own. If this happens, - it is a severe security breach and may put the entire system at risk. +# Safety and Protection diff --git a/doc/src/safety/index.md b/doc/src/safety/index.md new file mode 100644 index 00000000..d49768e4 --- /dev/null +++ b/doc/src/safety/index.md @@ -0,0 +1,35 @@ +Safety and Protection Against DoS Attacks +======================================== + +{{#include ../links.md}} + +For scripting systems open to untrusted user-land scripts, it is always best to limit the amount of +resources used by a script so that it does not consume more resources that it is allowed to. + +The most important resources to watch out for are: + +* **Memory**: A malicous script may continuously grow a [string], an [array] or [object map] until all memory is consumed. + + It may also create a large [array] or [object map] literal that exhausts all memory during parsing. + +* **CPU**: A malicous script may run an infinite tight loop that consumes all CPU cycles. + +* **Time**: A malicous script may run indefinitely, thereby blocking the calling system which is waiting for a result. + +* **Stack**: A malicous script may attempt an infinite recursive call that exhausts the call stack. + + Alternatively, it may create a degenerated deep expression with so many levels that the parser exhausts the call stack + when parsing the expression; or even deeply-nested statement blocks, if nested deep enough. + + Another way to cause a stack overflow is to load a [self-referencing module]({{rootUrl}}/language/modules/import.md). + +* **Overflows**: A malicous script may deliberately cause numeric over-flows and/or under-flows, divide by zero, and/or + create bad floating-point representations, in order to crash the system. + +* **Files**: A malicous script may continuously [`import`] an external module within an infinite loop, + thereby putting heavy load on the file-system (or even the network if the file is not local). + + Even when modules are not created from files, they still typically consume a lot of resources to load. + +* **Data**: A malicous script may attempt to read from and/or write to data that it does not own. If this happens, + it is a severe security breach and may put the entire system at risk. diff --git a/doc/src/safety/max-modules.md b/doc/src/safety/max-modules.md index d6f2f8da..d707ee64 100644 --- a/doc/src/safety/max-modules.md +++ b/doc/src/safety/max-modules.md @@ -10,6 +10,9 @@ of modules to zero does _not_ indicate unlimited modules, but disallows loading A script attempting to load more than the maximum number of modules will terminate with an error result. +This limit can also be used to stop [`import`-loops]({{rootUrl}}/language/modules/import.md) +(i.e. cycles of modules referring to each other). + This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). diff --git a/doc/src/start/builds.md b/doc/src/start/builds/index.md similarity index 86% rename from doc/src/start/builds.md rename to doc/src/start/builds/index.md index ac2023d5..a3cdfbf3 100644 --- a/doc/src/start/builds.md +++ b/doc/src/start/builds/index.md @@ -1,7 +1,7 @@ Special Builds ============== -{{#include ../links.md}} +{{#include ../../links.md}} It is possible to mix-and-match various [features] of the Rhai crate to make specialized builds with specific characteristics and behaviors. diff --git a/doc/src/start/examples.md b/doc/src/start/examples/index.md similarity index 88% rename from doc/src/start/examples.md rename to doc/src/start/examples/index.md index b7f9ae1d..6af1495b 100644 --- a/doc/src/start/examples.md +++ b/doc/src/start/examples/index.md @@ -1,7 +1,7 @@ Examples ======== -{{#include ../links.md}} +{{#include ../../links.md}} Rhai comes with a number of examples showing how to integrate the scripting [`Engine`] within a Rust application, as well as a number of sample scripts that showcase different Rhai language features. diff --git a/doc/src/start/features.md b/doc/src/start/features.md index fca919a5..1f67fb28 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -4,10 +4,12 @@ Optional Features {{#include ../links.md}} By default, Rhai includes all the standard functionalities in a small, tight package. -Most features are here to opt-**out** of certain functionalities that are not needed. -Excluding unneeded functionalities can result in smaller, faster builds -as well as more control over what a script can (or cannot) do. +Most features are here to opt-**out** of certain functionalities that are not needed. +Notice that this deviates from Rust norm where features are _additive_. + +Excluding unneeded functionalities can result in smaller, faster builds as well as +more control over what a script can (or cannot) do. | Feature | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -38,7 +40,7 @@ The `Cargo.toml` configuration below turns on these six features: ```toml [dependencies] -rhai = { version = "0.15.2", features = [ "sync", "unchecked", "only_i32", "no_float", "no_module", "no_function" ] } +rhai = { version = "{{version}}", features = [ "sync", "unchecked", "only_i32", "no_float", "no_module", "no_function" ] } ``` The resulting scripting engine supports only the `i32` integer numeral type (and no others like `u32` or `i16`), diff --git a/doc/src/start/install.md b/doc/src/start/install.md index c3553298..b8ee54dc 100644 --- a/doc/src/start/install.md +++ b/doc/src/start/install.md @@ -3,15 +3,15 @@ Install the Rhai Crate {{#include ../links.md}} -Install the Rhai crate from [`crates.io`](https:/crates.io/crates/rhai/) by adding this line -under `dependencies` in `Cargo.toml`: +Install the Rhai crate from [`crates.io`](https:/crates.io/crates/rhai/), start by looking up the +latest version and adding this line under `dependencies` in `Cargo.toml`: ```toml [dependencies] -rhai = "0.15.2" +rhai = "{{version}}" # assuming {{version}} is the latest version ``` -Use the latest released crate version on [`crates.io`](https:/crates.io/crates/rhai/): +Or to automatically use the latest released crate version on [`crates.io`](https:/crates.io/crates/rhai/): ```toml [dependencies] diff --git a/tests/side_effects.rs b/tests/side_effects.rs index fb364747..a5725b08 100644 --- a/tests/side_effects.rs +++ b/tests/side_effects.rs @@ -44,13 +44,13 @@ fn test_side_effects_command() -> Result<(), Box> { let mut engine = Engine::new(); let mut scope = Scope::new(); - // Create the command object with initial state, handled by an `Rc`. + // Create the command object with initial state, handled by an `Arc`. let command = Arc::new(Mutex::new(Command { state: 12 })); assert_eq!(command.lock().unwrap().get(), 12); // Create the wrapper. let wrapper = CommandWrapper { - command: command.clone(), // Notice this clones the `Rc` only + command: command.clone(), // Notice this clones the `Arc` only }; // Make the wrapper a singleton in the script environment.