diff --git a/.gitignore b/.gitignore index 90e6863e..140811c2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target/ Cargo.lock .vscode/ .cargo/ +doc/book/ \ No newline at end of file diff --git a/doc/book.toml b/doc/book.toml new file mode 100644 index 00000000..5d3eae3a --- /dev/null +++ b/doc/book.toml @@ -0,0 +1,12 @@ +[book] +title = "Rhai - Embedded Scripting for Rust" +authors = ["Jonathan Turner", "Stephen Chung"] +description = "Tutorial and reference on the Rhai scripting engine and language." +language = "en" + +[output.html] +no-section-label = true + +[output.html.fold] +enable = true +level = 4 \ No newline at end of file diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md new file mode 100644 index 00000000..756f642a --- /dev/null +++ b/doc/src/SUMMARY.md @@ -0,0 +1,98 @@ +The Rhai Scripting Language +========================== + +1. [What is Rhai](about.md) + 1. [Features](about/features.md) + 2. [Supported Targets and Builds](about/targets.md) + 3. [What Rhai Doesn't Do](about/non-design.md) + 4. [Related Resources](about/related.md) +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) + 4. [WebAssembly (WASM)](start/builds/wasm.md) + 4. [Examples](start/examples.md) + 1. [Rust](start/examples/rust.md) + 2. [Scripts](start/examples/scripts.md) +3. [Using the `Engine`](engine.md) + 1. [Hello World in Rhai - Evaluate a Script](engine/hello-world.md) + 2. [Compile a Script to AST for Repeated Evaluations](engine/compile.md) + 3. [Call a Rhai Function from Rust](engine/call-fn.md) + 4. [Create a Rust Anonymous Function from a Rhai Function](engine/func.md) + 5. [Evaluate Expressions Only](engine/expressions.md) + 6. [Raw `Engine`](engine/raw.md) +4. [Extend Rhai with Rust](rust.md) + 1. [Traits](rust/traits.md) + 2. [Register a Rust Function](rust/functions.md) + 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) + 6. [Override a Built-in Function](rust/override.md) + 7. [Operator Overloading](rust/operators.md) + 8. [Register a Custom Type and its Methods](rust/custom.md) + 1. [Getters and Setters](rust/getters-setters.md) + 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) + 10. [Engine Configuration Options](rust/options.md) +5. [Rhai Language Reference](language.md) + 1. [Comments](language/comments.md) + 2. [Values and Types](language/values-and-types.md) + 1. [`Dynamic` Values](language/dynamic.md) + 2. [`type-of`](language/type-of.md) + 3. [Numbers](language/numbers.md) + 1. [Operators](language/num-op.md) + 2. [Functions](language/num-fn.md) + 3. [Value Conversions](language/convert.md) + 4. [Strings and Characters](language/strings-chars.md) + 1. [Built-in Functions](language/string-fn.md) + 5. [Arrays](language/arrays.md) + 6. [Object Maps](language/object-maps.md) + 1. [Parse from JSON](language/json.md) + 7. [Time-Stamps](language/timestamps.md) + 3. [Keywords](language/keywords.md) + 4. [Statements](language/statements.md) + 5. [Variables](language/variables.md) + 6. [Constants](language/constants.md) + 7. [Logic Operators](language/logic.md) + 8. [If Statement](language/if.md) + 9. [While Loop](language/while.md) + 10. [Loop Statement](language/loop.md) + 11. [For Loop](language/for.md) + 12. [Return Values](language/return.md) + 13. [Throw Exception on Error](language/throw.md) + 14. [Functions](language/functions.md) + 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) + 1. [Export Variables and Functions](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) +6. [Safety and Protection](safety.md) + 1. [Checked Arithmetic](safety/checked.md) + 2. [Sand-Boxing](safety/sandbox.md) + 3. [Maximum Length of Strings](safety/max-string-size.md) + 4. [Maximum Size of Arrays](safety/max-array-size.md) + 5. [Maximum Size of Object Maps](safety/max-map-size.md) + 6. [Maximum Number of Operations](safety/max-operations.md) + 1. [Tracking Progress and Force-Termination](safety/progress.md) + 7. [Maximum Number of Modules](safety/max-modules.md) + 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. [Optimization Levels](engine/optimize/optimize-levels.md) + 2. [Re-Optimize an AST](engine/optimize/reoptimize.md) + 3. [Eager Function Evaluation](engine/optimize/eager.md) + 4. [Side-Effect Considerations](engine/optimize/side-effects.md) + 5. [Volatility Considerations](engine/optimize/volatility.md) + 6. [Subtle Semantic Changes](engine/optimize/semantics.md) + 2. [Eval Statement](language/eval.md) diff --git a/doc/src/about.md b/doc/src/about.md new file mode 100644 index 00000000..8a15d31f --- /dev/null +++ b/doc/src/about.md @@ -0,0 +1,7 @@ +What is Rhai +============ + +{{#include links.md}} + +Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way +to add scripting to any application. diff --git a/doc/src/about/features.md b/doc/src/about/features.md new file mode 100644 index 00000000..1636ffe8 --- /dev/null +++ b/doc/src/about/features.md @@ -0,0 +1,60 @@ +Features +======== + +{{#include ../links.md}} + +Easy +---- + +* Easy-to-use language similar to JS+Rust with dynamic typing. + +* Tight integration with native Rust [functions](/rust/functions.md) and [types](/rust/custom.md), including [getters/setters](/rust/getters-setters.md), [methods](/rust/custom.md) and [indexers](/rust/indexers.md). + +* Freely pass Rust variables/constants into a script via an external [`Scope`]. + +* Easily [call a script-defined function](/engine/call-fn.md) from Rust. + +* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) to do checked arithmetic operations); + for [`no-std`] builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. + +Fast +---- + +* Fairly low compile-time overhead. + +* Fairly efficient evaluation (1 million iterations in 0.25 sec on a single core, 2.3 GHz Linux VM). + +* Scripts are [optimized](/engine/optimize.md) (useful for template-based machine-generated scripts) for repeated evaluations. + +Dynamic +------- + +* [Function overloading](/language/overload.md). + +* [Operator overloading](/rust/operators.md). + +* Organize code base with dynamically-loadable [modules]. + +Safe +---- + +* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to + one single source file, all with names starting with `"unsafe_"`). + +Rugged +------ + +* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`). + +* Protected against malicious attacks (such as [stack-overflow](/safety/max-call-stack.md), [over-sized data](/safety/max-string-size.md), and [runaway scripts](/safety/max-operations.md) etc.) that may come from untrusted third-party user-land scripts. + +* Track script evaluation [progress] and manually terminate a script run. + +Flexible +-------- + +* Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature). + +* Support for [minimal builds] by excluding unneeded language [features]. + +* Supports [most build targets](targets.md) including `no-std` and [WASM]. diff --git a/doc/src/about/non-design.md b/doc/src/about/non-design.md new file mode 100644 index 00000000..66726803 --- /dev/null +++ b/doc/src/about/non-design.md @@ -0,0 +1 @@ +# What Rhai Doesn't Do diff --git a/doc/src/about/non_design.md b/doc/src/about/non_design.md new file mode 100644 index 00000000..cd1e073c --- /dev/null +++ b/doc/src/about/non_design.md @@ -0,0 +1,33 @@ +What Rhai Doesn't Do +==================== + +{{#include ../links.md}} + +Rhai's purpose is to provide a dynamic layer over Rust code, in the same spirit of _zero cost abstractions_. +It doesn't attempt to be a new language. For example: + +* No classes. Well, Rust doesn't either. On the other hand... + +* No traits... so it is also not Rust. Do your Rusty stuff in Rust. + +* No structures/records - define your types in Rust instead; Rhai can seamlessly work with _any Rust type_. + There is, however, a built-in [object map] type which is adequate for most uses. + +* No first-class functions - Code your functions in Rust instead, and register them with Rhai. + +* No garbage collection - this should be expected, so... + +* No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure](/engine/call-fn.md). + +* No byte-codes/JIT - Rhai has an AST-walking interpreter which will not win any speed races. The purpose of Rhai is not + to be extremely _fast_, but to make it as easy as possible to integrate with native Rust programs. + +Due to this intended usage, Rhai deliberately keeps the language simple and small by omitting advanced language features +such as classes, inheritance, first-class functions, closures, concurrency, byte-codes, JIT etc. + +Avoid the temptation to write full-fledge program logic entirely in Rhai - that use case is best fulfilled by +more complete languages such as JS or Lua. + +Therefore, in actual practice, it is usually best to expose a Rust API into Rhai for scripts to call. +All your core functionalities should be in Rust. +This is similar to some dynamic languages where most of the core functionalities reside in a C/C++ standard library. diff --git a/doc/src/about/related.md b/doc/src/about/related.md new file mode 100644 index 00000000..b6d8d8df --- /dev/null +++ b/doc/src/about/related.md @@ -0,0 +1,15 @@ +Related Resources +================= + +{{#include ../links.md}} + +Other online documentation resources for Rhai: + +* [`DOCS.RS`](https://docs.rs/rhai) + + +Other cool projects to check out: + +* [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being led by my cousin. + +* Check out the list of [scripting languages for Rust](https://github.com/rust-unofficial/awesome-rust#scripting) on [awesome-rust](https://github.com/rust-unofficial/awesome-rust) diff --git a/doc/src/about/targets.md b/doc/src/about/targets.md new file mode 100644 index 00000000..68395396 --- /dev/null +++ b/doc/src/about/targets.md @@ -0,0 +1,12 @@ +Supported Targets and Builds +=========================== + +{{#include ../links.md}} + +The following targets and builds are support by Rhai: + +* All common CPU targets for Windows, Linux and MacOS. + +* [WASM] + +* [`no-std`] diff --git a/doc/src/advanced.md b/doc/src/advanced.md new file mode 100644 index 00000000..d1982c2c --- /dev/null +++ b/doc/src/advanced.md @@ -0,0 +1,10 @@ +Advanced Topics +=============== + +{{#include links.md}} + +This section covers advanced features such as: + +* [Script optimization] + +* The dreaded (or beloved depending on your taste) [`eval`] statement diff --git a/doc/src/engine.md b/doc/src/engine.md new file mode 100644 index 00000000..5ed3389a --- /dev/null +++ b/doc/src/engine.md @@ -0,0 +1,8 @@ +Using the Engine +================ + +{{#include links.md}} + +Rhai's interpreter resides in the [`Engine`] type under the master `rhai` namespace. + +This section shows how to set up, configure and use this scripting engine. diff --git a/doc/src/engine/call-fn.md b/doc/src/engine/call-fn.md new file mode 100644 index 00000000..d881d7bf --- /dev/null +++ b/doc/src/engine/call-fn.md @@ -0,0 +1,64 @@ +Calling Rhai Functions from Rust +=============================== + +{{#include ../links.md}} + +Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function +from Rust via `Engine::call_fn`. + +Functions declared with `private` are hidden and cannot be called from Rust (see also [modules]). + +```rust +// Define functions in a script. +let ast = engine.compile(true, + r#" + // a function with two parameters: string and i64 + fn hello(x, y) { + x.len + y + } + + // functions can be overloaded: this one takes only one parameter + fn hello(x) { + x * 2 + } + + // this one takes no parameters + fn hello() { + 42 + } + + // this one is private and cannot be called by 'call_fn' + private hidden() { + throw "you shouldn't see me!"; + } + "#)?; + +// A custom scope can also contain any variables/constants available to the functions +let mut scope = Scope::new(); + +// Evaluate a function defined in the script, passing arguments into the script as a tuple. +// Beware, arguments must be of the correct types because Rhai does not have built-in type conversions. +// If arguments of the wrong types are passed, the Engine will not find the function. + +let result: i64 = engine.call_fn(&mut scope, &ast, "hello", ( String::from("abc"), 123_i64 ) )?; +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// put arguments in a tuple + +let result: i64 = engine.call_fn(&mut scope, &ast, "hello", (123_i64,) )?; +// ^^^^^^^^^^ tuple of one + +let result: i64 = engine.call_fn(&mut scope, &ast, "hello", () )?; +// ^^ unit = tuple of zero + +// The following call will return a function-not-found error because +// 'hidden' is declared with 'private'. +let result: () = engine.call_fn(&mut scope, &ast, "hidden", ())?; +``` + +For more control, construct all arguments as `Dynamic` values and use `Engine::call_fn_dynamic`, passing it +anything that implements `IntoIterator` (such as a simple `Vec`): + +```rust +let result: Dynamic = engine.call_fn_dynamic(&mut scope, &ast, "hello", + vec![ String::from("abc").into(), 123_i64.into() ])?; +``` diff --git a/doc/src/engine/compile.md b/doc/src/engine/compile.md new file mode 100644 index 00000000..32e16bce --- /dev/null +++ b/doc/src/engine/compile.md @@ -0,0 +1,23 @@ +Compile a Script (to AST) +======================== + +{{#include ../links.md}} + +To repeatedly evaluate a script, _compile_ it first into an AST (abstract syntax tree) form: + +```rust +// Compile to an AST and store it for later evaluations +let ast = engine.compile("40 + 2")?; + +for _ in 0..42 { + let result: i64 = engine.eval_ast(&ast)?; + + println!("Answer #{}: {}", i, result); // prints 42 +} +``` + +Compiling a script file is also supported (not available under [`no_std`] or in [WASM] builds): + +```rust +let ast = engine.compile_file("hello_world.rhai".into())?; +``` diff --git a/doc/src/engine/expressions.md b/doc/src/engine/expressions.md new file mode 100644 index 00000000..e9e890bf --- /dev/null +++ b/doc/src/engine/expressions.md @@ -0,0 +1,25 @@ +Evaluate Expressions Only +======================== + +{{#include ../links.md}} + +Sometimes a use case does not require a full-blown scripting _language_, but only needs to evaluate _expressions_. + +In these cases, use the `Engine::compile_expression` and `Engine::eval_expression` methods or their `_with_scope` variants. + +```rust +let result = engine.eval_expression::("2 + (10 + 10) * 2")?; +``` + +When evaluating _expressions_, no full-blown statement (e.g. `if`, `while`, `for`) - not even variable assignment - +is supported and will be considered parse errors when encountered. + +```rust +// The following are all syntax errors because the script is not an expression. + +engine.eval_expression::<()>("x = 42")?; + +let ast = engine.compile_expression("let x = 42")?; + +let result = engine.eval_expression_with_scope::(&mut scope, "if x { 42 } else { 123 }")?; +``` diff --git a/doc/src/engine/func.md b/doc/src/engine/func.md new file mode 100644 index 00000000..551c4e48 --- /dev/null +++ b/doc/src/engine/func.md @@ -0,0 +1,43 @@ +Create a Rust Anonymous Function from a Rhai Function +=================================================== + +{{#include ../links.md}} + +It is possible to further encapsulate a script in Rust such that it becomes a normal Rust function. + +Such an _anonymous function_ is basically a boxed closure, very useful as call-back functions. + +Creating them is accomplished via the `Func` trait which contains `create_from_script` +(as well as its companion method `create_from_ast`): + +```rust +use rhai::{Engine, Func}; // use 'Func' for 'create_from_script' + +let engine = Engine::new(); // create a new 'Engine' just for this + +let script = "fn calc(x, y) { x + y.len < 42 }"; + +// Func takes two type parameters: +// 1) a tuple made up of the types of the script function's parameters +// 2) the return type of the script function +// +// 'func' will have type Box Result>> and is callable! +let func = Func::<(i64, String), bool>::create_from_script( +// ^^^^^^^^^^^^^ function parameter types in tuple + + engine, // the 'Engine' is consumed into the closure + script, // the script, notice number of parameters must match + "calc" // the entry-point function name +)?; + +func(123, "hello".to_string())? == false; // call the anonymous function + +schedule_callback(func); // pass it as a callback to another function + +// Although there is nothing you can't do by manually writing out the closure yourself... +let engine = Engine::new(); +let ast = engine.compile(script)?; +schedule_callback(Box::new(move |x: i64, y: String| -> Result> { + engine.call_fn(&mut Scope::new(), &ast, "calc", (x, y)) +})); +``` diff --git a/doc/src/engine/hello-world.md b/doc/src/engine/hello-world.md new file mode 100644 index 00000000..c4e868af --- /dev/null +++ b/doc/src/engine/hello-world.md @@ -0,0 +1,54 @@ +Hello World in Rhai +=================== + +{{#include ../links.md}} + +To get going with Rhai is as simple as creating an instance of the scripting engine `rhai::Engine` via +`Engine::new`, then calling the `eval` method: + +```rust +use rhai::{Engine, EvalAltResult}; + +fn main() -> Result<(), Box> +{ + let engine = Engine::new(); + + let result = engine.eval::("40 + 2")?; + // ^^^^^^^ cast the result to an 'i64', this is required + + println!("Answer: {}", result); // prints 42 + + Ok(()) +} +``` + +`rhai::EvalAltResult` is a Rust `enum` containing all errors encountered during the parsing or evaluation process. + + +Evaluate a Script +---------------- + +The type parameter is used to specify the type of the return value, which _must_ match the actual type or an error is returned. +Rhai is very strict here. + +Use [`Dynamic`] for uncertain return types. + +There are two ways to specify the return type - _turbofish_ notation, or type inference. + +```rust +let result = engine.eval::("40 + 2")?; // return type is i64, specified using 'turbofish' notation + +let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64 + +result.is::() == true; + +let result: Dynamic = engine.eval("boo()")?; // use 'Dynamic' if you're not sure what type it'll be! + +let result = engine.eval::("40 + 2")?; // returns an error because the actual return type is i64, not String +``` + +Evaluate a script file directly: + +```rust +let result = engine.eval_file::("hello_world.rhai".into())?; // 'eval_file' takes a 'PathBuf' +``` diff --git a/doc/src/engine/optimize.md b/doc/src/engine/optimize.md new file mode 100644 index 00000000..10ee4f21 --- /dev/null +++ b/doc/src/engine/optimize.md @@ -0,0 +1,109 @@ +Script Optimization +=================== + +{{#include ../links.md}} + +Rhai includes an _optimizer_ that tries to optimize a script after parsing. +This can reduce resource utilization and increase execution speed. + +Script optimization can be turned off via the [`no_optimize`] feature. + + +Dead Code Removal +---------------- + +For example, in the following: + +```rust +{ + let x = 999; // NOT eliminated: variable may be used later on (perhaps even an 'eval') + 123; // eliminated: no effect + "hello"; // eliminated: no effect + [1, 2, x, x*2, 5]; // eliminated: no effect + foo(42); // NOT eliminated: the function 'foo' may have side-effects + 666 // NOT eliminated: this is the return value of the block, + // and the block is the last one so this is the return value of the whole script +} +``` + +Rhai attempts to eliminate _dead code_ (i.e. code that does nothing, for example an expression by itself as a statement, +which is allowed in Rhai). + +The above script optimizes to: + +```rust +{ + let x = 999; + foo(42); + 666 +} +``` + + +Constants Propagation +-------------------- + +Constants propagation is used to remove dead code: + +```rust +const ABC = true; +if ABC || some_work() { print("done!"); } // 'ABC' is constant so it is replaced by 'true'... +if true || some_work() { print("done!"); } // since '||' short-circuits, 'some_work' is never called +if true { print("done!"); } // <- the line above is equivalent to this +print("done!"); // <- the line above is further simplified to this + // because the condition is always true +``` + +These are quite effective for template-based machine-generated scripts where certain constant values +are spliced into the script text in order to turn on/off certain sections. + +For fixed script texts, the constant values can be provided in a user-defined [`Scope`] object +to the [`Engine`] for use in compilation and evaluation. + + +Watch Out for Function Calls +--------------------------- + +Beware, however, that most operators are actually function calls, and those functions can be overridden, +so they are not optimized away: + +```rust +const DECISION = 1; + +if DECISION == 1 { // NOT optimized away because you can define + : // your own '==' function to override the built-in default! + : +} else if DECISION == 2 { // same here, NOT optimized away + : +} else if DECISION == 3 { // same here, NOT optimized away + : +} else { + : +} +``` + +because no operator functions will be run (in order not to trigger side-effects) during the optimization process +(unless the optimization level is set to [`OptimizationLevel::Full`]). + +So, instead, do this: + +```rust +const DECISION_1 = true; +const DECISION_2 = false; +const DECISION_3 = false; + +if DECISION_1 { + : // this branch is kept and promoted to the parent level +} else if DECISION_2 { + : // this branch is eliminated +} else if DECISION_3 { + : // this branch is eliminated +} else { + : // this branch is eliminated +} +``` + +In general, boolean constants are most effective for the optimizer to automatically prune +large `if`-`else` branches because they do not depend on operators. + +Alternatively, turn the optimizer to [`OptimizationLevel::Full`]. diff --git a/doc/src/engine/optimize/disable.md b/doc/src/engine/optimize/disable.md new file mode 100644 index 00000000..8c02726c --- /dev/null +++ b/doc/src/engine/optimize/disable.md @@ -0,0 +1,24 @@ +Turn Off Script Optimizations +============================ + +{{#include ../../links.md}} + +When scripts: + +* are known to be run only _once_, + +* are known to contain no dead code, + +* do not use constants in calculations + +the optimization pass may be a waste of time and resources. In that case, turn optimization off +by setting the optimization level to [`OptimizationLevel::None`]. + +Alternatively, turn off optimizations via the [`no_optimize`] feature. + +```rust +let engine = rhai::Engine::new(); + +// Turn off the optimizer +engine.set_optimization_level(rhai::OptimizationLevel::None); +``` diff --git a/doc/src/engine/optimize/eager.md b/doc/src/engine/optimize/eager.md new file mode 100644 index 00000000..b6bb26c8 --- /dev/null +++ b/doc/src/engine/optimize/eager.md @@ -0,0 +1,36 @@ +Eager Function Evaluation When Using Full Optimization Level +========================================================== + +{{#include ../../links.md}} + +When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to be _pure_ and will _eagerly_ +evaluated all function calls with constant arguments, using the result to replace the call. + +This also applies to all operators (which are implemented as functions). + +For instance, the same example above: + +```rust +// When compiling the following with OptimizationLevel::Full... + +const DECISION = 1; + // this condition is now eliminated because 'DECISION == 1' +if DECISION == 1 { // is a function call to the '==' function, and it returns 'true' + print("hello!"); // this block is promoted to the parent level +} else { + print("boo!"); // this block is eliminated because it is never reached +} + +print("hello!"); // <- the above is equivalent to this + // ('print' and 'debug' are handled specially) +``` + +Because of the eager evaluation of functions, many constant expressions will be evaluated and replaced by the result. +This does not happen with [`OptimizationLevel::Simple`] which doesn't assume all functions to be _pure_. + +```rust +// When compiling the following with OptimizationLevel::Full... + +let x = (1+2)*3-4/5%6; // <- will be replaced by 'let x = 9' +let y = (1>2) || (3<=4); // <- will be replaced by 'let y = true' +``` diff --git a/doc/src/engine/optimize/optimize-levels.md b/doc/src/engine/optimize/optimize-levels.md new file mode 100644 index 00000000..11511099 --- /dev/null +++ b/doc/src/engine/optimize/optimize-levels.md @@ -0,0 +1,24 @@ +Optimization Levels +================== + +{{#include ../../links.md}} + +Set Optimization Level +--------------------- + +There are actually three levels of optimizations: `None`, `Simple` and `Full`. + +* `None` is obvious - no optimization on the AST is performed. + +* `Simple` (default) performs only relatively _safe_ optimizations without causing side-effects + (i.e. it only relies on static analysis and will not actually perform any function calls). + +* `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. + +An [`Engine`]'s optimization level is set via a call to `Engine::set_optimization_level`: + +```rust +// Turn on aggressive optimizations +engine.set_optimization_level(rhai::OptimizationLevel::Full); +``` diff --git a/doc/src/engine/optimize/reoptimize.md b/doc/src/engine/optimize/reoptimize.md new file mode 100644 index 00000000..d3ac0f26 --- /dev/null +++ b/doc/src/engine/optimize/reoptimize.md @@ -0,0 +1,17 @@ +Re-Optimize an AST +================== + +{{#include ../../links.md}} + +If it is ever needed to _re_-optimize an `AST`, use the `optimize_ast` method: + +```rust +// Compile script to AST +let ast = engine.compile("40 + 2")?; + +// Create a new 'Scope' - put constants in it to aid optimization if using 'OptimizationLevel::Full' +let scope = Scope::new(); + +// Re-optimize the AST +let ast = engine.optimize_ast(&scope, &ast, OptimizationLevel::Full); +``` diff --git a/doc/src/engine/optimize/semantics.md b/doc/src/engine/optimize/semantics.md new file mode 100644 index 00000000..021173ef --- /dev/null +++ b/doc/src/engine/optimize/semantics.md @@ -0,0 +1,43 @@ +Subtle Semantic Changes After Optimization +========================================= + +{{#include ../../links.md}} + +Some optimizations can alter subtle semantics of the script. + +For example: + +```rust +if true { // condition always true + 123.456; // eliminated + hello; // eliminated, EVEN THOUGH the variable doesn't exist! + foo(42) // promoted up-level +} + +foo(42) // <- the above optimizes to this +``` + +If the original script were evaluated instead, it would have been an error - the variable `hello` does not exist, +so the script would have been terminated at that point with an error return. + +In fact, any errors inside a statement that has been eliminated will silently _disappear_: + +```rust +print("start!"); +if my_decision { /* do nothing... */ } // eliminated due to no effect +print("end!"); + +// The above optimizes to: + +print("start!"); +print("end!"); +``` + +In the script above, if `my_decision` holds anything other than a boolean value, +the script should have been terminated due to a type error. + +However, after optimization, the entire `if` statement is removed (because an access to `my_decision` produces +no side-effects), thus the script silently runs to completion without errors. + +It is usually a _Very Bad Idea™_ to depend on a script failing or such kind of subtleties, but if it turns out to be necessary +(why? I would never guess), turn script optimization off by setting the optimization level to [`OptimizationLevel::None`]. diff --git a/doc/src/engine/optimize/side-effects.md b/doc/src/engine/optimize/side-effects.md new file mode 100644 index 00000000..76009c55 --- /dev/null +++ b/doc/src/engine/optimize/side-effects.md @@ -0,0 +1,19 @@ +Side-Effect Considerations for Full Optimization Level +==================================================== + +{{#include ../../links.md}} + +All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_ (i.e. they do not mutate state +nor cause any side-effects, with the exception of `print` and `debug` which are handled specially) so using +[`OptimizationLevel::Full`] is usually quite safe _unless_ custom types and functions are registered. + +If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie within a pruned code block). + +If custom functions are registered to overload built-in operators, they will also be called when the operators are used +(in an `if` statement, for example) causing side-effects. + +Therefore, the rule-of-thumb is: + +* _Always_ register custom types and functions _after_ compiling scripts if [`OptimizationLevel::Full`] is used. + +* _DO NOT_ depend on knowledge that the functions have no side-effects, because those functions can change later on and, when that happens, existing scripts may break in subtle ways. diff --git a/doc/src/engine/optimize/volatility.md b/doc/src/engine/optimize/volatility.md new file mode 100644 index 00000000..26287aaf --- /dev/null +++ b/doc/src/engine/optimize/volatility.md @@ -0,0 +1,16 @@ +Volatility Considerations for Full Optimization Level +=================================================== + +{{#include ../../links.md}} + +Even if a custom function does not mutate state nor cause side-effects, it may still be _volatile_, +i.e. it _depends_ on the external environment and is not _pure_. + +A perfect example is a function that gets the current time - obviously each run will return a different value! + +The optimizer, when using [`OptimizationLevel::Full`], will _merrily assume_ that all functions are _pure_, +so when it finds constant arguments (or none) it eagerly executes the function call and replaces it with the result. + +This causes the script to behave differently from the intended semantics. + +Therefore, **avoid using [`OptimizationLevel::Full`]** if non-_pure_ custom types and/or functions are involved. diff --git a/doc/src/engine/raw.md b/doc/src/engine/raw.md new file mode 100644 index 00000000..fa054abe --- /dev/null +++ b/doc/src/engine/raw.md @@ -0,0 +1,24 @@ + +Raw `Engine` +=========== + +{{#include ../links.md}} + +`Engine::new` creates a scripting [`Engine`] with common functionalities (e.g. printing to the console via `print`). +In many controlled embedded environments, however, these are not needed. + +Use `Engine::new_raw` to create a _raw_ `Engine`, in which only a minimal set of basic arithmetic and logical operators +are supported. + +Built-in Operators +------------------ + +| Operators | Assignment operators | Supported for type (see [standard types]) | +| ------------------------ | ---------------------------- | ----------------------------------------------------------------------------- | +| `+`, | `+=` | `INT`, `FLOAT` (if not [`no_float`]), `ImmutableString` | +| `-`, `*`, `/`, `%`, `~`, | `-=`, `*=`, `/=`, `%=`, `~=` | `INT`, `FLOAT` (if not [`no_float`]) | +| `<<`, `>>`, `^`, | `<<=`, `>>=`, `^=` | `INT` | +| `&`, `\|`, | `&=`, `\|=` | `INT`, `bool` | +| `&&`, `\|\|` | | `bool` | +| `==`, `!=` | | `INT`, `FLOAT` (if not [`no_float`]), `bool`, `char`, `()`, `ImmutableString` | +| `>`, `>=`, `<`, `<=` | | `INT`, `FLOAT` (if not [`no_float`]), `char`, `()`, `ImmutableString` | diff --git a/doc/src/language.md b/doc/src/language.md new file mode 100644 index 00000000..b3526ebd --- /dev/null +++ b/doc/src/language.md @@ -0,0 +1,7 @@ +Rhai Language Reference +====================== + +{{#include links.md}} + +This section outlines the Rhai language. + diff --git a/doc/src/language/arrays.md b/doc/src/language/arrays.md new file mode 100644 index 00000000..984eea16 --- /dev/null +++ b/doc/src/language/arrays.md @@ -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
(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)) ); +``` diff --git a/doc/src/language/comments.md b/doc/src/language/comments.md new file mode 100644 index 00000000..4f01f680 --- /dev/null +++ b/doc/src/language/comments.md @@ -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: + /*/*/*/*/**/*/*/*/*/ +*/ +``` diff --git a/doc/src/language/constants.md b/doc/src/language/constants.md new file mode 100644 index 00000000..82811166 --- /dev/null +++ b/doc/src/language/constants.md @@ -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 +``` diff --git a/doc/src/language/convert.md b/doc/src/language/convert.md new file mode 100644 index 00000000..a0cd7a83 --- /dev/null +++ b/doc/src/language/convert.md @@ -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" +``` diff --git a/doc/src/language/dynamic.md b/doc/src/language/dynamic.md new file mode 100644 index 00000000..e8c8a21b --- /dev/null +++ b/doc/src/language/dynamic.md @@ -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::() == true; // 'is' returns whether a 'Dynamic' value is of a particular type + +let value = item.cast::(); // 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::().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` (`i32` if [`only_i32`]) +* `From` (if not [`no_float`]) +* `From` +* `From` +* `From` +* `From` +* `From>` (into an [array]) +* `From>` (into an [object map]). diff --git a/doc/src/language/eval.md b/doc/src/language/eval.md new file mode 100644 index 00000000..0d64ce1b --- /dev/null +++ b/doc/src/language/eval.md @@ -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> { + 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 +``` diff --git a/doc/src/language/for.md b/doc/src/language/for.md new file mode 100644 index 00000000..eb2406fc --- /dev/null +++ b/doc/src/language/for.md @@ -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); +} +``` diff --git a/doc/src/language/functions.md b/doc/src/language/functions.md new file mode 100644 index 00000000..d2cb48d5 --- /dev/null +++ b/doc/src/language/functions.md @@ -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. diff --git a/doc/src/language/if.md b/doc/src/language/if.md new file mode 100644 index 00000000..a71cbe9a --- /dev/null +++ b/doc/src/language/if.md @@ -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 == (); +``` diff --git a/doc/src/language/json.md b/doc/src/language/json.md new file mode 100644 index 00000000..a5aaba2a --- /dev/null +++ b/doc/src/language/json.md @@ -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::(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]. diff --git a/doc/src/language/keywords.md b/doc/src/language/keywords.md new file mode 100644 index 00000000..48c32015 --- /dev/null +++ b/doc/src/language/keywords.md @@ -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`]. diff --git a/doc/src/language/logic.md b/doc/src/language/logic.md new file mode 100644 index 00000000..3be2f9f4 --- /dev/null +++ b/doc/src/language/logic.md @@ -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" +``` diff --git a/doc/src/language/loop.md b/doc/src/language/loop.md new file mode 100644 index 00000000..8b1cba8e --- /dev/null +++ b/doc/src/language/loop.md @@ -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 +} +``` diff --git a/doc/src/language/method.md b/doc/src/language/method.md new file mode 100644 index 00000000..23822e05 --- /dev/null +++ b/doc/src/language/method.md @@ -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' +``` diff --git a/doc/src/language/modules.md b/doc/src/language/modules.md new file mode 100644 index 00000000..002765f6 --- /dev/null +++ b/doc/src/language/modules.md @@ -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. diff --git a/doc/src/language/modules/ast.md b/doc/src/language/modules/ast.md new file mode 100644 index 00000000..96607881 --- /dev/null +++ b/doc/src/language/modules/ast.md @@ -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' +``` diff --git a/doc/src/language/modules/export.md b/doc/src/language/modules/export.md new file mode 100644 index 00000000..0861bc0f --- /dev/null +++ b/doc/src/language/modules/export.md @@ -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' +``` diff --git a/doc/src/language/modules/import.md b/doc/src/language/modules/import.md new file mode 100644 index 00000000..0269fdfd --- /dev/null +++ b/doc/src/language/modules/import.md @@ -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); +} +``` diff --git a/doc/src/language/modules/resolvers.md b/doc/src/language/modules/resolvers.md new file mode 100644 index 00000000..1125cab8 --- /dev/null +++ b/doc/src/language/modules/resolvers.md @@ -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.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.
`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); +``` diff --git a/doc/src/language/modules/rust.md b/doc/src/language/modules/rust.md new file mode 100644 index 00000000..74f90896 --- /dev/null +++ b/doc/src/language/modules/rust.md @@ -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::(&scope, "question::answer + 1")? == 42; + +// Call module-qualified functions +engine.eval_expression_with_scope::(&scope, "question::inc(question::answer)")? == 42; +``` diff --git a/doc/src/language/num-fn.md b/doc/src/language/num-fn.md new file mode 100644 index 00000000..8c729f10 --- /dev/null +++ b/doc/src/language/num-fn.md @@ -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 | diff --git a/doc/src/language/num-op.md b/doc/src/language/num-op.md new file mode 100644 index 00000000..290c4aa7 --- /dev/null +++ b/doc/src/language/num-op.md @@ -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 +``` diff --git a/doc/src/language/numbers.md b/doc/src/language/numbers.md new file mode 100644 index 00000000..388d3e77 --- /dev/null +++ b/doc/src/language/numbers.md @@ -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` | diff --git a/doc/src/language/object-maps.md b/doc/src/language/object-maps.md new file mode 100644 index 00000000..4ef3c05a --- /dev/null +++ b/doc/src/language/object-maps.md @@ -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; +``` diff --git a/doc/src/language/overload.md b/doc/src/language/overload.md new file mode 100644 index 00000000..b83a8028 --- /dev/null +++ b/doc/src/language/overload.md @@ -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." +``` diff --git a/doc/src/language/print-debug.md b/doc/src/language/print-debug.md new file mode 100644 index 00000000..bd031b4a --- /dev/null +++ b/doc/src/language/print-debug.md @@ -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::::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); +} +``` diff --git a/doc/src/language/return.md b/doc/src/language/return.md new file mode 100644 index 00000000..5e696161 --- /dev/null +++ b/doc/src/language/return.md @@ -0,0 +1,10 @@ +Return Values +============= + +{{#include ../links.md}} + +```rust +return; // equivalent to return (); + +return 123 + 456; // returns 579 +``` diff --git a/doc/src/language/statements.md b/doc/src/language/statements.md new file mode 100644 index 00000000..c964d952 --- /dev/null +++ b/doc/src/language/statements.md @@ -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 +``` diff --git a/doc/src/language/string-fn.md b/doc/src/language/string-fn.md new file mode 100644 index 00000000..66f5500e --- /dev/null +++ b/doc/src/language/string-fn.md @@ -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; +``` diff --git a/doc/src/language/strings-chars.md b/doc/src/language/strings-chars.md new file mode 100644 index 00000000..db56e7f7 --- /dev/null +++ b/doc/src/language/strings-chars.md @@ -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); +} +``` diff --git a/doc/src/language/throw.md b/doc/src/language/throw.md new file mode 100644 index 00000000..97b74182 --- /dev/null +++ b/doc/src/language/throw.md @@ -0,0 +1,32 @@ +Throw Exception on Error +======================= + +{{#include ../links.md}} + +All of [`Engine`]'s evaluation/consuming methods return `Result>` +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)` +with the exception text captured by the first parameter. + +```rust +let result = engine.eval::(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)" +``` diff --git a/doc/src/language/timestamps.md b/doc/src/language/timestamps.md new file mode 100644 index 00000000..ad799deb --- /dev/null +++ b/doc/src/language/timestamps.md @@ -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)!") +} +``` diff --git a/doc/src/language/type-of.md b/doc/src/language/type-of.md new file mode 100644 index 00000000..a2869539 --- /dev/null +++ b/doc/src/language/type-of.md @@ -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); +} +``` diff --git a/doc/src/language/values-and-types.md b/doc/src/language/values-and-types.md new file mode 100644 index 00000000..c4b32533 --- /dev/null +++ b/doc/src/language/values-and-types.md @@ -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`,
`u32`, `i32` (default for [`only_i32`]),
`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` or `Arc`) | `"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` or `Arc` (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. diff --git a/doc/src/language/variables.md b/doc/src/language/variables.md new file mode 100644 index 00000000..94f0500b --- /dev/null +++ b/doc/src/language/variables.md @@ -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 +``` diff --git a/doc/src/language/while.md b/doc/src/language/while.md new file mode 100644 index 00000000..f0f419fb --- /dev/null +++ b/doc/src/language/while.md @@ -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 +} +``` diff --git a/doc/src/links.md b/doc/src/links.md new file mode 100644 index 00000000..0f0715c7 --- /dev/null +++ b/doc/src/links.md @@ -0,0 +1,87 @@ +[features]: /start/features.md +[`unchecked`]: /start/features.md +[`sync`]: /start/features.md +[`no_optimize`]: /start/features.md +[`no_float`]: /start/features.md +[`only_i32`]: /start/features.md +[`only_i64`]: /start/features.md +[`no_index`]: /start/features.md +[`no_object`]: /start/features.md +[`no_function`]: /start/features.md +[`no_module`]: /start/features.md +[`no_std`]: /start/features.md + +[`no-std`]: /start/features.md + +[minimal builds]: /start/builds/minimal.md +[WASM]: /start/builds/wasm.md + +[`Engine`]: /engine/hello-world.md +[`private`]: /engine/call_fn.md +[`Func`]: /engine/func.md +[`eval_expression`]: /engine/expressions.md +[`eval_expression_with_scope`]: /engine/expressions.md +[raw `Engine`]: /engine/raw.md +[built-in operators]: /engine/raw.md#built-in-operators +[package]: /rust/packages.md +[packages]: /rust/packages.md +[`Scope`]: /rust/scope.md + +[`type_of()`]: /language/type_of.md +[`to_string()`]: /language/values-and-types.md +[`()`]: /language/values-and-types.md +[standard types]: /language/values-and-types.md +[`Dynamic`]: /language/dynamic.md +[`to_int`]: /language/convert.md +[`to_float`]: /language/convert.md + +[custom type]: /language/custom.md +[custom types]: /language/custom.md + +[`print`]: /language/print-debug.md +[`debug`]: /language/print-debug.md + +[variable]: /language/variables.md +[variables]: /language/variables.md + +[string]: /language/strings-chars.md +[strings]: /language/strings-chars.md +[char]: /language/strings-chars.md + +[array]: /language/arrays.md +[arrays]: /language/arrays.md +[`Array`]: /language/arrays.md + +[`Map`]: /language/object-maps.md +[object map]: /language/object-maps.md +[object maps]: /language/object-maps.md + +[`timestamp`]: /language/timestamps.md +[timestamp]: /language/timestamps.md +[timestamps]: /language/timestamps.md + +[function]: /language/functions.md +[functions]: /language/functions.md + +[`Module`]: /language/modules.md +[module]: /language/modules.md +[modules]: /language/modules.md +[`export`]: /language/modules/export.md +[`import`]: /language/modules/import.md + +[`eval`]: /language/eval.md + +[maximum statement depth]: /safety/max-stmt-depth.md +[maximum call stack depth]: /safety/max-call-stack.md +[maximum number of operations]: /safety/max-operations.md +[maximum number of modules]: /safety/max-modules.md +[maximum length of strings]: /safety/max-string-size.md +[maximum size of arrays]: /safety/max-array-size.md +[maximum size of object maps]: /safety/max-map-size.md +[progress]:/safety/progress.md + +[script optimization]: /engine/optimize.md +[`OptimizationLevel::Full`]: /engine/optimize/optimize-levels.md +[`OptimizationLevel::Simple`]: /engine/optimize/optimize-levels.md +[`OptimizationLevel::None`]: /engine/optimize/optimize-levels.md + diff --git a/doc/src/rust.md b/doc/src/rust.md new file mode 100644 index 00000000..bad92288 --- /dev/null +++ b/doc/src/rust.md @@ -0,0 +1,9 @@ +Extend Rhai with Rust +==================== + +{{#include links.md}} + +Most features and functionalities required by a Rhai script should actually be coded in Rust, +which leverages the superior native run-time speed. + +This section discusses how to extend Rhai with functionalities written in Rust. diff --git a/doc/src/rust/custom.md b/doc/src/rust/custom.md new file mode 100644 index 00000000..cd3998b6 --- /dev/null +++ b/doc/src/rust/custom.md @@ -0,0 +1,148 @@ +Register a Custom Type and its Methods +===================================== + +{{#include ../links.md}} + +Rhai works seamlessly with _any_ complex Rust type. The type can be registered with the `Engine`, as below. + +Support for custom types can be turned off via the [`no_object`] feature. + +```rust +use rhai::{Engine, EvalAltResult}; +use rhai::RegisterFn; + +#[derive(Clone)] +struct TestStruct { + field: i64 +} + +impl TestStruct { + fn update(&mut self) { + self.field += 41; + } + + fn new() -> Self { + TestStruct { field: 1 } + } +} + +fn main() -> Result<(), Box> +{ + let engine = Engine::new(); + + engine.register_type::(); + + engine.register_fn("update", TestStruct::update); + engine.register_fn("new_ts", TestStruct::new); + + let result = engine.eval::("let x = new_ts(); x.update(); x")?; + + println!("result: {}", result.field); // prints 42 + + Ok(()) +} +``` + +Register a Custom Type +--------------------- + +A custom type must implement `Clone` as this allows the [`Engine`] to pass by value. + +Notice that the custom type needs to be _registered_ using `Engine::register_type`. + +```rust +#[derive(Clone)] +struct TestStruct { + field: i64 +} + +impl TestStruct { + fn update(&mut self) { // methods take &mut as first parameter + self.field += 41; + } + + fn new() -> Self { + TestStruct { field: 1 } + } +} + +let engine = Engine::new(); + +engine.register_type::(); +``` + +Methods on Custom Type +--------------------- + +To use native custom types, methods and functions in Rhai scripts, simply register them +using one of the `Engine::register_XXX` API. + +Below, the `update` and `new` methods are registered using `Engine::register_fn`. + +```rust +engine.register_fn("update", TestStruct::update); // registers 'update(&mut TestStruct)' +engine.register_fn("new_ts", TestStruct::new); // registers 'new()' +``` + +***Note**: Rhai follows the convention that methods of custom types take a `&mut` first parameter +so that invoking methods can update the types. All other parameters in Rhai are passed by value (i.e. clones).* + +Use the Custom Type in Scripts +----------------------------- + +The custom type is then ready for use in scripts. Scripts can see the functions and methods registered earlier. +Get the evaluation result back out just as before, this time casting to the custom type: + +```rust +let result = engine.eval::("let x = new_ts(); x.update(); x")?; + +println!("result: {}", result.field); // prints 42 +``` + +Method-Call Style vs. Function-Call Style +---------------------------------------- + +In fact, any function with a first argument that is a `&mut` reference can be used as method calls because +internally they are the same thing: methods on a type is implemented as a functions taking a `&mut` first argument. + +```rust +fn foo(ts: &mut TestStruct) -> i64 { + ts.field +} + +engine.register_fn("foo", foo); // register ad hoc function with correct signature + +let result = engine.eval::( + "let x = new_ts(); x.foo()" // 'foo' can be called like a method on 'x' +)?; + +println!("result: {}", result); // prints 1 +``` + +Under [`no_object`], however, the _method_ style of function calls (i.e. calling a function as an object-method) +is no longer supported. + +```rust +// Below is a syntax error under 'no_object' because 'clear' cannot be called in method style. +let result = engine.eval::<()>("let x = [1, 2, 3]; x.clear()")?; +``` + +[`type_of()`] +------------- + +[`type_of()`] works fine with custom types and returns the name of the type. + +If `Engine::register_type_with_name` is used to register the custom type +with a special "pretty-print" name, [`type_of()`] will return that name instead. + +```rust +engine.register_type::(); +engine.register_fn("new_ts", TestStruct::new); +let x = new_ts(); +print(x.type_of()); // prints "path::to::module::TestStruct" + +engine.register_type_with_name::("Hello"); +engine.register_fn("new_ts", TestStruct::new); +let x = new_ts(); +print(x.type_of()); // prints "Hello" +``` diff --git a/doc/src/rust/disable-custom.md b/doc/src/rust/disable-custom.md new file mode 100644 index 00000000..a2c69c95 --- /dev/null +++ b/doc/src/rust/disable-custom.md @@ -0,0 +1,10 @@ +Disable Custom Types +==================== + +{{#include ../links.md}} + +The custom types API `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set`, +`register_indexer_get`, `register_indexer_set` and `register_indexer_get_set` are not available under [`no_object`]. + +The indexers API `register_indexer_get`, `register_indexer_set` and `register_indexer_get_set` are also +not available under [`no_index`]. diff --git a/doc/src/rust/fallible.md b/doc/src/rust/fallible.md new file mode 100644 index 00000000..6c20bf94 --- /dev/null +++ b/doc/src/rust/fallible.md @@ -0,0 +1,41 @@ +Register a Fallible Rust Function +================================ + +{{#include ../links.md}} + +If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be registered with `register_result_fn` +(using the `RegisterResultFn` trait). + +The function must return `Result>`. + +`Box` implements `From<&str>` and `From` etc. +and the error text gets converted into `Box`. + +The error values are `Box`-ed in order to reduce memory footprint of the error path, which should be hit rarely. + +```rust +use rhai::{Engine, EvalAltResult, Position}; +use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn' + +// Function that may fail - the result type must be 'Dynamic' +fn safe_divide(x: i64, y: i64) -> Result> { + if y == 0 { + // Return an error if y is zero + Err("Division by zero!".into()) // short-cut to create Box + } else { + Ok((x / y).into()) // convert result into 'Dynamic' + } +} + +fn main() +{ + let engine = Engine::new(); + + // Fallible functions that return Result values must use register_result_fn() + engine.register_result_fn("divide", safe_divide); + + if let Err(error) = engine.eval::("divide(40, 0)") { + println!("Error: {:?}", *error); // prints ErrorRuntime("Division by zero detected!", (1, 1)") + } +} +``` diff --git a/doc/src/rust/functions.md b/doc/src/rust/functions.md new file mode 100644 index 00000000..b543896c --- /dev/null +++ b/doc/src/rust/functions.md @@ -0,0 +1,73 @@ +Register a Rust Function +======================== + +{{#include ../links.md}} + +Rhai's scripting engine is very lightweight. It gets most of its abilities from functions. + +To call these functions, they need to be _registered_ with the [`Engine`] using `Engine::register_fn` +(in the `RegisterFn` trait) and `Engine::register_result_fn` (in the `RegisterResultFn` trait, +see [fallible functions](/rust/fallible.md)). + +```rust +use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString}; +use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn' +use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn' + +// Normal function that returns a standard type +// Remember to use 'ImmutableString' and not 'String' +fn add_len(x: i64, s: ImmutableString) -> i64 { + x + s.len() +} +// Alternatively, '&str' maps directly to 'ImmutableString' +fn add_len_str(x: i64, s: &str) -> i64 { + x + s.len() +} + +// Function that returns a 'Dynamic' value - must return a 'Result' +fn get_any_value() -> Result> { + Ok((42_i64).into()) // standard types can use 'into()' +} + +fn main() -> Result<(), Box> +{ + let engine = Engine::new(); + + engine.register_fn("add", add_len); + engine.register_fn("add_str", add_len_str); + + let result = engine.eval::(r#"add(40, "xx")"#)?; + + println!("Answer: {}", result); // prints 42 + + let result = engine.eval::(r#"add_str(40, "xx")"#)?; + + println!("Answer: {}", result); // prints 42 + + // Functions that return Dynamic values must use register_result_fn() + engine.register_result_fn("get_any_value", get_any_value); + + let result = engine.eval::("get_any_value()")?; + + println!("Answer: {}", result); // prints 42 + + Ok(()) +} +``` + +To create a [`Dynamic`] value, use the `Dynamic::from` method. +[Standard types] in Rhai can also use `into()`. + +```rust +use rhai::Dynamic; + +let x = (42_i64).into(); // 'into()' works for standard types + +let y = Dynamic::from("hello!".to_string()); // remember &str is not supported by Rhai +``` + +Functions registered with the [`Engine`] can be _overloaded_ as long as the _signature_ is unique, +i.e. different functions can have the same name as long as their parameters are of different types +and/or different number. + +New definitions _overwrite_ previous definitions of the same name and same number/types of parameters. diff --git a/doc/src/rust/generic.md b/doc/src/rust/generic.md new file mode 100644 index 00000000..84527951 --- /dev/null +++ b/doc/src/rust/generic.md @@ -0,0 +1,32 @@ +Register a Generic Rust Function +=============================== + +{{#include ../links.md}} + +Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately. + +This essentially _overloads_ the function with different parameter types as Rhai does not natively support generics +but Rhai does support _function overloading_. + +```rust +use std::fmt::Display; + +use rhai::{Engine, RegisterFn}; + +fn show_it(x: &mut T) { + println!("put up a good show: {}!", x) +} + +fn main() +{ + let engine = Engine::new(); + + engine.register_fn("print", show_it::); + engine.register_fn("print", show_it::); + engine.register_fn("print", show_it::); +} +``` + +The above example shows how to register multiple functions +(or, in this case, multiple overloaded versions of the same function) +under the same name. diff --git a/doc/src/rust/getters-setters.md b/doc/src/rust/getters-setters.md new file mode 100644 index 00000000..18cf868e --- /dev/null +++ b/doc/src/rust/getters-setters.md @@ -0,0 +1,42 @@ +Custom Type Getters and Setters +============================== + +{{#include ../links.md}} + +A custom type can also expose members by registering `get` and/or `set` functions. + +```rust +#[derive(Clone)] +struct TestStruct { + field: String +} + +impl TestStruct { + // Returning a 'String' is OK - Rhai converts it into 'ImmutableString' + fn get_field(&mut self) -> String { + self.field.clone() + } + + // Remember Rhai uses 'ImmutableString' or '&str' instead of 'String' + fn set_field(&mut self, new_val: ImmutableString) { + // Get a 'String' from an 'ImmutableString' + self.field = (*new_val).clone(); + } + + fn new() -> Self { + TestStruct { field: "hello" } + } +} + +let engine = Engine::new(); + +engine.register_type::(); + +engine.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field); +engine.register_fn("new_ts", TestStruct::new); + +// Return result can be 'String' - Rhai will automatically convert it from 'ImmutableString' +let result = engine.eval::(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?; + +println!("Answer: {}", result); // prints 42 +``` diff --git a/doc/src/rust/indexers.md b/doc/src/rust/indexers.md new file mode 100644 index 00000000..9c85e9a5 --- /dev/null +++ b/doc/src/rust/indexers.md @@ -0,0 +1,47 @@ +Custom Type Indexers +=================== + +{{#include ../links.md}} + +A custom type can also expose an _indexer_ by registering an indexer function. + +A custom type with an indexer function defined can use the bracket '`[]`' notation to get a property value. + +Indexers are disabled when the [`no_index`] feature is used. + +```rust +#[derive(Clone)] +struct TestStruct { + fields: Vec +} + +impl TestStruct { + fn get_field(&mut self, index: i64) -> i64 { + self.fields[index as usize] + } + fn set_field(&mut self, index: i64, value: i64) { + self.fields[index as usize] = value + } + + fn new() -> Self { + TestStruct { fields: vec![1, 2, 3, 4, 5] } + } +} + +let engine = Engine::new(); + +engine.register_type::(); + +engine.register_fn("new_ts", TestStruct::new); + +// Shorthand: engine.register_indexer_get_set(TestStruct::get_field, TestStruct::set_field); +engine.register_indexer_get(TestStruct::get_field); +engine.register_indexer_set(TestStruct::set_field); + +let result = engine.eval::("let a = new_ts(); a[2] = 42; a[2]")?; + +println!("Answer: {}", result); // prints 42 +``` + +For efficiency reasons, indexers **cannot** be used to overload (i.e. override) built-in indexing operations for +[arrays] and [object maps]. diff --git a/doc/src/rust/operators.md b/doc/src/rust/operators.md new file mode 100644 index 00000000..6a48ed84 --- /dev/null +++ b/doc/src/rust/operators.md @@ -0,0 +1,57 @@ +Operator Overloading +=================== + +{{#include ../links.md}} + +In Rhai, a lot of functionalities are actually implemented as functions, including basic operations +such as arithmetic calculations. + +For example, in the expression "`a + b`", the `+` operator is _not_ built in, but calls a function named "`+`" instead! + +```rust +let x = a + b; +let x = +(a, b); // <- the above is equivalent to this function call +``` + +Similarly, comparison operators including `==`, `!=` etc. are all implemented as functions, +with the stark exception of `&&` and `||`. Because they [_short-circuit_](/language/logic.md#boolean-operators), +`&&` and `||` are handled specially and _not_ via a function; as a result, overriding them has no effect at all. + +Operator functions cannot be defined as a script function (because operators syntax are not valid function names). + +However, operator functions _can_ be registered to the [`Engine`] via the methods +`Engine::register_fn`, `Engine::register_result_fn` etc. + +When a custom operator function is registered with the same name as an operator, it _overrides_ the built-in version. + +```rust +use rhai::{Engine, EvalAltResult, RegisterFn}; + +let mut engine = Engine::new(); + +fn strange_add(a: i64, b: i64) -> i64 { (a + b) * 42 } + +engine.register_fn("+", strange_add); // overload '+' operator for two integers! + +let result: i64 = engine.eval("1 + 0"); // the overloading version is used + +println!("result: {}", result); // prints 42 + +let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded + +println!("result: {}", result); // prints 1.0 + +fn mixed_add(a: i64, b: f64) -> f64 { (a as f64) + b } + +engine.register_fn("+", mixed_add); // register '+' operator for an integer and a float + +let result: i64 = engine.eval("1 + 1.0"); // prints 2.0 (normally an error) +``` + +Normally, use operator overloading for [custom types] only. + +Be very careful when overriding built-in operators because script authors expect standard operators to behave in a +consistent and predictable manner, and will be annoyed if a calculation for '`+`' turns into a subtraction, for example. + +Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`]. +See the [script-optimization] for more details. diff --git a/doc/src/rust/options.md b/doc/src/rust/options.md new file mode 100644 index 00000000..f0a14b97 --- /dev/null +++ b/doc/src/rust/options.md @@ -0,0 +1,17 @@ +Engine Configuration Options +=========================== + +{{#include ../links.md}} + +A number of other configuration options are available from the `Engine` to fine-tune behavior and safeguards. + +| Method | Not available under | Description | +| ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `set_optimization_level` | [`no_optimize`] | Set the amount of script _optimizations_ performed. See [script optimization]. | +| `set_max_expr_depths` | [`unchecked`] | Set the maximum nesting levels of an expression/statement. See [maximum statement depth]. | +| `set_max_call_levels` | [`unchecked`] | Set the maximum number of function call levels (default 50) to avoid infinite recursion. See [maximum call stack depth]. | +| `set_max_operations` | [`unchecked`] | Set the maximum number of _operations_ that a script is allowed to consume. See [maximum number of operations]. | +| `set_max_modules` | [`unchecked`] | Set the maximum number of [modules] that a script is allowed to load. See [maximum number of modules]. | +| `set_max_string_size` | [`unchecked`] | Set the maximum length (in UTF-8 bytes) for [strings]. See [maximum length of strings]. | +| `set_max_array_size` | [`unchecked`], [`no_index`] | Set the maximum size for [arrays]. See [maximum size of arrays]. | +| `set_max_map_size` | [`unchecked`], [`no_object`] | Set the maximum number of properties for [object maps]. See [maximum size of object maps]. | diff --git a/doc/src/rust/override.md b/doc/src/rust/override.md new file mode 100644 index 00000000..576b5ad8 --- /dev/null +++ b/doc/src/rust/override.md @@ -0,0 +1,19 @@ +Override a Built-in Function +=========================== + +{{#include ../links.md}} + +Any similarly-named function defined in a script overrides any built-in or registered +native Rust function of the same name and number of parameters. + +```rust +// Override the built-in function 'to_int' +fn to_int(num) { + print("Ha! Gotcha! " + num); +} + +print(to_int(123)); // what happens? +``` + +A registered native Rust function, in turn, overrides any built-in function of the +same name, number and types of parameters. diff --git a/doc/src/rust/packages.md b/doc/src/rust/packages.md new file mode 100644 index 00000000..df58b56a --- /dev/null +++ b/doc/src/rust/packages.md @@ -0,0 +1,52 @@ +Packages +======== + +{{#include ../links.md}} + +Standard built-in Rhai features are provided in various _packages_ that can be loaded via a call to `Engine::load_package`. + +Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package` must be loaded in order for +packages to be used. + +```rust +use rhai::Engine; +use rhai::packages::Package // load the 'Package' trait to use packages +use rhai::packages::CorePackage; // the 'core' package contains basic functionalities (e.g. arithmetic) + +let mut engine = Engine::new_raw(); // create a 'raw' Engine +let package = CorePackage::new(); // create a package - can be shared among multiple `Engine` instances + +engine.load_package(package.get()); // load the package manually. 'get' returns a reference to the shared package +``` + +The follow packages are available: + +| Package | Description | In `Core` | In `Standard` | +| ---------------------- | ------------------------------------------------------------------------------------------------------ | :-------: | :-----------: | +| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) for numeric types that are not built in (e.g. `u16`) | Yes | Yes | +| `BasicIteratorPackage` | Numeric ranges (e.g. `range(1, 10)`) | Yes | Yes | +| `LogicPackage` | Logical and comparison operators (e.g. `==`, `>`) for numeric types that are not built in (e.g. `u16`) | Yes | Yes | +| `BasicStringPackage` | Basic string functions (e.g. `print`, `debug`, `len`) that are not built in | Yes | Yes | +| `BasicTimePackage` | Basic time functions (e.g. [timestamps]) | Yes | Yes | +| `MoreStringPackage` | Additional string functions, including converting common types to string | No | Yes | +| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes | +| `BasicArrayPackage` | Basic [array] functions (not available under `no_index`) | No | Yes | +| `BasicMapPackage` | Basic [object map] functions (not available under `no_object`) | No | Yes | +| `EvalPackage` | Disable [`eval`] | No | No | +| `CorePackage` | Basic essentials | Yes | Yes | +| `StandardPackage` | Standard library (default for `Engine::new`) | No | Yes | + +Packages typically contain Rust functions that are callable within a Rhai script. +All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers). + +Once a package is created (e.g. via `new`), it can be _shared_ (via `get`) among multiple instances of [`Engine`], +even across threads (under [`sync`]). Therefore, a package only has to be created _once_. + +Packages are actually implemented as [modules], so they share a lot of behavior and characteristics. +The main difference is that a package loads under the _global_ namespace, while a module loads under its own +namespace alias specified in an [`import`] statement (see also [modules]). + +A package is _static_ (i.e. pre-loaded into an [`Engine`]), while a module is _dynamic_ (i.e. loaded with +the `import` statement). + +Custom packages can also be created. See the macro [`def_package!`](https://docs.rs/rhai/0.13.0/rhai/macro.def_package.html). diff --git a/doc/src/rust/print-custom.md b/doc/src/rust/print-custom.md new file mode 100644 index 00000000..87b13c4b --- /dev/null +++ b/doc/src/rust/print-custom.md @@ -0,0 +1,16 @@ +Printing for Custom Types +======================== + +{{#include ../links.md}} + +To use custom types for [`print`] and [`debug`], or convert its value into a [string], it is necessary that the following +functions be registered (assuming the custom type is `T : Display + Debug`): + +| Function | Signature | Typical implementation | Usage | +| ----------- | ------------------------------------------------ | ------------------------------------- | --------------------------------------------------------------------------------------- | +| `to_string` | `|s: &mut T| -> ImmutableString` | `s.to_string().into()` | Converts the custom type into a [string] | +| `print` | `|s: &mut T| -> ImmutableString` | `s.to_string().into()` | Converts the custom type into a [string] for the [`print`](#print-and-debug) statement | +| `debug` | `|s: &mut T| -> ImmutableString` | `format!("{:?}", s).into()` | Converts the custom type into a [string] for the [`debug`](#print-and-debug) statement | +| `+` | `|s1: ImmutableString, s: T| -> ImmutableString` | `s1 + s` | Append the custom type to another [string], for `print("Answer: " + type);` usage | +| `+` | `|s: T, s2: ImmutableString| -> ImmutableString` | `s.to_string().push_str(&s2).into();` | Append another [string] to the custom type, for `print(type + " is the answer");` usage | +| `+=` | `|s1: &mut ImmutableString, s: T|` | `s1 += s.to_string()` | Append the custom type to an existing [string], for `s += type;` usage | diff --git a/doc/src/rust/scope.md b/doc/src/rust/scope.md new file mode 100644 index 00000000..d5ad8fa5 --- /dev/null +++ b/doc/src/rust/scope.md @@ -0,0 +1,59 @@ +`Scope` - Initializing and Maintaining State +=========================================== + +{{#include ../links.md}} + +By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined +but no global state. This gives each evaluation a clean starting slate. + +In order to continue using the same global state from one invocation to the next, +such a state must be manually created and passed in. + +All `Scope` variables are [`Dynamic`], meaning they can store values of any type. + +Under [`sync`], however, only types that are `Send + Sync` are supported, and the entire `Scope` itself +will also be `Send + Sync`. This is extremely useful in multi-threaded applications. + +In this example, a global state object (a `Scope`) is created with a few initialized variables, +then the same state is threaded through multiple invocations: + +```rust +use rhai::{Engine, Scope, EvalAltResult}; + +fn main() -> Result<(), Box> +{ + let engine = Engine::new(); + + // First create the state + let mut scope = Scope::new(); + + // Then push (i.e. add) some initialized variables into the state. + // Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64. + // Better stick to them or it gets hard working with the script. + scope.push("y", 42_i64); + scope.push("z", 999_i64); + + // 'set_value' adds a variable when one doesn't exist + scope.set_value("s", "hello, world!".to_string()); // remember to use 'String', not '&str' + + // First invocation + engine.eval_with_scope::<()>(&mut scope, r" + let x = 4 + 5 - y + z + s.len; + y = 1; + ")?; + + // Second invocation using the same state + let result = engine.eval_with_scope::(&mut scope, "x")?; + + println!("result: {}", result); // prints 979 + + // Variable y is changed in the script - read it with 'get_value' + assert_eq!(scope.get_value::("y").expect("variable y should exist"), 1); + + // We can modify scope variables directly with 'set_value' + scope.set_value("y", 42_i64); + assert_eq!(scope.get_value::("y").expect("variable y should exist"), 42); + + Ok(()) +} +``` diff --git a/doc/src/rust/strings.md b/doc/src/rust/strings.md new file mode 100644 index 00000000..ac03c874 --- /dev/null +++ b/doc/src/rust/strings.md @@ -0,0 +1,21 @@ +`String` Parameters in Rust Functions +==================================== + +{{#include ../links.md}} + +Rust functions accepting parameters of `String` should use `&str` instead because it maps directly to [`ImmutableString`] +which is the type that Rhai uses to represent [strings] internally. + +```rust +fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Rhai will not find this function +fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- Rhai finds this function fine +fn get_len3(s: ImmutableString) -> i64 { s.len() as i64 } // <- the above is equivalent to this + +engine.register_fn("len1", get_len1); +engine.register_fn("len2", get_len2); +engine.register_fn("len3", get_len3); + +let len = engine.eval::("x.len1()")?; // error: function 'len1 (&str | ImmutableString)' not found +let len = engine.eval::("x.len2()")?; // works fine +let len = engine.eval::("x.len3()")?; // works fine +``` diff --git a/doc/src/rust/traits.md b/doc/src/rust/traits.md new file mode 100644 index 00000000..12ea514e --- /dev/null +++ b/doc/src/rust/traits.md @@ -0,0 +1,13 @@ +Traits +====== + +{{#include ../links.md}} + +A number of traits, under the `rhai::` module namespace, provide additional functionalities. + +| Trait | Description | Methods | +| ------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------- | +| `RegisterFn` | Trait for registering functions | `register_fn` | +| `RegisterResultFn` | Trait for registering fallible functions returning `Result>` | `register_result_fn` | +| `Func` | Trait for creating anonymous functions from script | `create_from_ast`, `create_from_script` | +| `ModuleResolver` | Trait implemented by module resolution services | `resolve` | diff --git a/doc/src/safety.md b/doc/src/safety.md new file mode 100644 index 00000000..d207fe9d --- /dev/null +++ b/doc/src/safety.md @@ -0,0 +1,31 @@ +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. diff --git a/doc/src/safety/checked.md b/doc/src/safety/checked.md new file mode 100644 index 00000000..e16164b0 --- /dev/null +++ b/doc/src/safety/checked.md @@ -0,0 +1,11 @@ +Checked Arithmetic +================= + +{{#include ../links.md}} + +By default, all arithmetic calculations in Rhai are _checked_, meaning that the script terminates +with an error whenever it detects a numeric over-flow/under-flow condition or an invalid +floating-point operation, instead of crashing the entire system. + +This checking can be turned off via the [`unchecked`] feature for higher performance +(but higher risks as well). diff --git a/doc/src/safety/max-array-size.md b/doc/src/safety/max-array-size.md new file mode 100644 index 00000000..af5ba6f0 --- /dev/null +++ b/doc/src/safety/max-array-size.md @@ -0,0 +1,40 @@ +Maximum Size of Arrays +===================== + +{{#include ../links.md}} + +Limiting How Large Arrays Can Grow +--------------------------------- + +Rhai by default does not limit how large an [array] can be. + +This can be changed via the `Engine::set_max_array_size` method, with zero being unlimited (the default). + +A script attempting to create an array literal larger than the maximum will terminate with a parse error. + +Any script operation that produces an array larger than the maximum also terminates the script with an error result. + +This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). + +```rust +let mut engine = Engine::new(); + +engine.set_max_array_size(500); // allow arrays only up to 500 items + +engine.set_max_array_size(0); // allow unlimited arrays +``` + + +Setting Maximum Size +------------------- + +Be conservative when setting a maximum limit and always consider the fact that a registered function may grow +an array's size without Rhai noticing until the very end. + +For instance, the built-in '`+`' operator for arrays concatenates two arrays together to form one larger array; +if both arrays are _slightly_ below the maximum size limit, the resultant array may be almost _twice_ the maximum size. + +As a malicious script may create a deeply-nested array which consumes huge amounts of memory while each individual +array still stays under the maximum size limit, Rhai also recursively adds up the sizes of all [strings], [arrays] +and [object maps] contained within each array to make sure that the _aggregate_ sizes of none of these data structures +exceed their respective maximum size limits (if any). diff --git a/doc/src/safety/max-call-stack.md b/doc/src/safety/max-call-stack.md new file mode 100644 index 00000000..f10a8fbe --- /dev/null +++ b/doc/src/safety/max-call-stack.md @@ -0,0 +1,31 @@ +Maximum Call Stack Depth +======================= + +{{#include ../links.md}} + +Limiting How Stack Usage by Scripts +---------------------------------- + +Rhai by default limits function calls to a maximum depth of 128 levels (16 levels in debug build). + +This limit may be changed via the `Engine::set_max_call_levels` method. + +A script exceeding the maximum call stack depth will terminate with an error result. + +This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). + +```rust +let mut engine = Engine::new(); + +engine.set_max_call_levels(10); // allow only up to 10 levels of function calls + +engine.set_max_call_levels(0); // allow no function calls at all (max depth = zero) +``` + + +Setting Maximum Stack Depth +-------------------------- + +When setting this limit, care must be also taken to the evaluation depth of each _statement_ +within a function. It is entirely possible for a malicious script to embed a recursive call deep +inside a nested expression or statement block (see [maximum statement depth]). diff --git a/doc/src/safety/max-map-size.md b/doc/src/safety/max-map-size.md new file mode 100644 index 00000000..2fcd3273 --- /dev/null +++ b/doc/src/safety/max-map-size.md @@ -0,0 +1,40 @@ +Maximum Size of Object Maps +========================== + +{{#include ../links.md}} + +Limiting How Large Object Maps Can Grow +-------------------------------------- + +Rhai by default does not limit how large (i.e. the number of properties) an [object map] can be. + +This can be changed via the `Engine::set_max_map_size` method, with zero being unlimited (the default). + +A script attempting to create an object map literal with more properties than the maximum will terminate with a parse error. + +Any script operation that produces an object map with more properties than the maximum also terminates the script with an error result. + +This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). + +```rust +let mut engine = Engine::new(); + +engine.set_max_map_size(500); // allow object maps with only up to 500 properties + +engine.set_max_map_size(0); // allow unlimited object maps +``` + + +Setting Maximum Size +------------------- + +Be conservative when setting a maximum limit and always consider the fact that a registered function may grow +an object map's size without Rhai noticing until the very end. + +For instance, the built-in '`+`' operator for object maps concatenates two object maps together to form one larger object map; +if both object maps are _slightly_ below the maximum size limit, the resultant object map may be almost _twice_ the maximum size. + +As a malicious script may create a deeply-nested object map which consumes huge amounts of memory while each individual +object map still stays under the maximum size limit, Rhai also recursively adds up the sizes of all [strings], [arrays] +and [object maps] contained within each object map to make sure that the _aggregate_ sizes of none of these data structures +exceed their respective maximum size limits (if any). diff --git a/doc/src/safety/max-modules.md b/doc/src/safety/max-modules.md new file mode 100644 index 00000000..d6f2f8da --- /dev/null +++ b/doc/src/safety/max-modules.md @@ -0,0 +1,24 @@ +Maximum Number of Modules +======================== + +{{#include ../links.md}} + +Rhai by default does not limit how many [modules] can be loaded via [`import`] statements. + +This can be changed via the `Engine::set_max_modules` method. Notice that setting the maximum number +of modules to zero does _not_ indicate unlimited modules, but disallows loading any module altogether. + +A script attempting to load more than the maximum number of modules will terminate with an error result. + +This check can be disabled via the [`unchecked`] feature for higher performance +(but higher risks as well). + +```rust +let mut engine = Engine::new(); + +engine.set_max_modules(5); // allow loading only up to 5 modules + +engine.set_max_modules(0); // disallow loading any module (maximum = zero) + +engine.set_max_modules(1000); // set to a large number for effectively unlimited modules +``` diff --git a/doc/src/safety/max-operations.md b/doc/src/safety/max-operations.md new file mode 100644 index 00000000..6dd5d7d9 --- /dev/null +++ b/doc/src/safety/max-operations.md @@ -0,0 +1,43 @@ +Maximum Number of Operations +=========================== + +{{#include ../links.md}} + +Limiting How Long a Script Can Run +--------------------------------- + +Rhai by default does not limit how much time or CPU a script consumes. + +This can be changed via the `Engine::set_max_operations` method, with zero being unlimited (the default). + +The _operations count_ is intended to be a very course-grained measurement of the amount of CPU that a script +has consumed, allowing the system to impose a hard upper limit on computing resources. + +A script exceeding the maximum operations count terminates with an error result. +This can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). + +```rust +let mut engine = Engine::new(); + +engine.set_max_operations(500); // allow only up to 500 operations for this script + +engine.set_max_operations(0); // allow unlimited operations +``` + + +What Does One _Operation_ Mean +----------------------------- + +The concept of one single _operation_ in Rhai is volatile - it roughly equals one expression node, +loading one variable/constant, one operator call, one iteration of a loop, or one function call etc. +with sub-expressions, statements and function calls executed inside these contexts accumulated on top. + +A good rule-of-thumb is that one simple non-trivial expression consumes on average 5-10 operations. + +One _operation_ can take an unspecified amount of time and real CPU cycles, depending on the particulars. +For example, loading a constant consumes very few CPU cycles, while calling an external Rust function, +though also counted as only one operation, may consume much more computing resources. + +To help visualize, think of an _operation_ as roughly equals to one _instruction_ of a hypothetical CPU +which includes _specialized_ instructions, such as _function call_, _load module_ etc., each taking up +one CPU cycle to execute. diff --git a/doc/src/safety/max-stmt-depth.md b/doc/src/safety/max-stmt-depth.md new file mode 100644 index 00000000..007e7c82 --- /dev/null +++ b/doc/src/safety/max-stmt-depth.md @@ -0,0 +1,56 @@ +Maximum Statement Depth +====================== + +{{#include ../links.md}} + +Limiting How Deeply-Nested a Statement Can Be +-------------------------------------------- + +Rhai by default limits statements and expressions nesting to a maximum depth of 128 +(which should be plenty) when they are at _global_ level, but only a depth of 32 +when they are within function bodies. + +For debug builds, these limits are set further downwards to 32 and 16 respectively. + +That is because it is possible to overflow the [`Engine`]'s stack when it tries to +recursively parse an extremely deeply-nested code stream. + +```rust +// The following, if long enough, can easily cause stack overflow during parsing. +let a = (1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(...)+1))))))))))); +``` + +This limit may be changed via the `Engine::set_max_expr_depths` method. + +There are two limits to set, one for the maximum depth at global level, and the other for function bodies. + +A script exceeding the maximum nesting depths will terminate with a parsing error. +The malicious `AST` will not be able to get past parsing in the first place. + +This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). + +```rust +let mut engine = Engine::new(); + +engine.set_max_expr_depths(50, 5); // allow nesting up to 50 layers of expressions/statements + // at global level, but only 5 inside functions +``` + +Beware that there may be multiple layers for a simple language construct, even though it may correspond +to only one AST node. That is because the Rhai _parser_ internally runs a recursive chain of function calls +and it is important that a malicious script does not panic the parser in the first place. + + +Beware of Recursion +------------------- + +_Functions_ are placed under stricter limits because of the multiplicative effect of _recursion_. + +A script can effectively call itself while deep inside an expression chain within the function body, +thereby overflowing the stack even when the level of recursion is within limit. + +In general, make sure that `C x ( 5 + F ) + S` layered calls do not cause a stack overflow, where: + +* `C` = maximum call stack depth, +* `F` = maximum statement depth for functions, +* `S` = maximum statement depth at global level. diff --git a/doc/src/safety/max-string-size.md b/doc/src/safety/max-string-size.md new file mode 100644 index 00000000..4b279d68 --- /dev/null +++ b/doc/src/safety/max-string-size.md @@ -0,0 +1,36 @@ +Maximum Length of Strings +======================== + +{{#include ../links.md}} + +Limiting How Long Strings Can Grow +--------------------------------- + +Rhai by default does not limit how long a [string] can be. + +This can be changed via the `Engine::set_max_string_size` method, with zero being unlimited (the default). + +A script attempting to create a string literal longer than the maximum length will terminate with a parse error. + +Any script operation that produces a string longer than the maximum also terminates the script with an error result. + +This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). + +```rust +let mut engine = Engine::new(); + +engine.set_max_string_size(500); // allow strings only up to 500 bytes long (in UTF-8 format) + +engine.set_max_string_size(0); // allow unlimited string length +``` + + +Setting Maximum Length +--------------------- + +Be conservative when setting a maximum limit and always consider the fact that a registered function may grow +a string's length without Rhai noticing until the very end. + +For instance, the built-in '`+`' operator for strings concatenates two strings together to form one longer string; +if both strings are _slightly_ below the maximum length limit, the resultant string may be almost _twice_ the maximum length. + diff --git a/doc/src/safety/progress.md b/doc/src/safety/progress.md new file mode 100644 index 00000000..00c2ed0e --- /dev/null +++ b/doc/src/safety/progress.md @@ -0,0 +1,34 @@ +Tracking Progress and Force-Termination +====================================== + +{{#include ../links.md}} + +It is impossible to know when, or even whether, a script run will end +(a.k.a. the [Halting Problem](http://en.wikipedia.org/wiki/Halting_problem)). + +When dealing with third-party untrusted scripts that may be malicious, to track evaluation progress and +to force-terminate a script prematurely (for any reason), provide a closure to the `Engine::on_progress` method: + +```rust +let mut engine = Engine::new(); + +engine.on_progress(|&count| { // parameter is '&u64' - number of operations already performed + if count % 1000 == 0 { + println!("{}", count); // print out a progress log every 1,000 operations + } + true // return 'true' to continue running the script + // return 'false' to immediately terminate the script +}); +``` + +The closure passed to `Engine::on_progress` will be called once for every operation. +Return `false` to terminate the script immediately. + + +Operations Count vs. Progress Percentage +--------------------------------------- + +Notice that the _operations count_ value passed into the closure does not indicate the _percentage_ of work +already done by the script (and thus it is not real _progress_ tracking), because it is impossible to determine +how long a script may run. It is possible, however, to calculate this percentage based on an estimated +total number of operations for a typical run. diff --git a/doc/src/safety/sandbox.md b/doc/src/safety/sandbox.md new file mode 100644 index 00000000..61d8469d --- /dev/null +++ b/doc/src/safety/sandbox.md @@ -0,0 +1,17 @@ +Sand-Boxing - Block Access to External Data +========================================== + +{{#include ../links.md}} + +Rhai is _sand-boxed_ so a script can never read from outside its own environment. + +Furthermore, an [`Engine`] created non-`mut` cannot mutate any state outside of itself; +so it is highly recommended that [`Engine`]'s are created immutable as much as possible. + +```rust +let mut engine = Engine::new(); // create mutable 'Engine' + +engine.register_get("add", add); // configure 'engine' + +let engine = engine; // shadow the variable so that 'engine' is now immutable +``` diff --git a/doc/src/start.md b/doc/src/start.md new file mode 100644 index 00000000..33de6526 --- /dev/null +++ b/doc/src/start.md @@ -0,0 +1,6 @@ +Getting Started +=============== + +{{#include links.md}} + +This section shows how to install the Rhai crate into a Rust application. diff --git a/doc/src/start/builds.md b/doc/src/start/builds.md new file mode 100644 index 00000000..ac2023d5 --- /dev/null +++ b/doc/src/start/builds.md @@ -0,0 +1,7 @@ +Special Builds +============== + +{{#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/builds/minimal.md b/doc/src/start/builds/minimal.md new file mode 100644 index 00000000..57ad59c7 --- /dev/null +++ b/doc/src/start/builds/minimal.md @@ -0,0 +1,40 @@ +Minimal Build +============= + +{{#include ../../links.md}} + +Configuration +------------- + +In order to compile a _minimal_ build - i.e. a build optimized for size - perhaps for `no-std` embedded targets or for +compiling to [WASM], it is essential that the correct linker flags are used in `cargo.toml`: + +```toml +[profile.release] +lto = "fat" # turn on Link-Time Optimizations +codegen-units = 1 # trade compile time with maximum optimization +opt-level = "z" # optimize for size +``` + + +Opt-Out of Features +------------------ + +Opt out of as many features as possible, if they are not needed, to reduce code size because, remember, by default +all code is compiled in as what a script requires cannot be predicted. If a language feature is not needed, +omitting them via special features is a prudent strategy to optimize the build for size. + +Omitting arrays ([`no_index`]) yields the most code-size savings, followed by floating-point support +([`no_float`]), checked arithmetic/script resource limits ([`unchecked`]) and finally object maps and custom types ([`no_object`]). + +Where the usage scenario does not call for loading externally-defined modules, use [`no_module`] to save some bytes. +Disable script-defined functions ([`no_function`]) only when the feature is not needed because code size savings is minimal. + + +Use a Raw [`Engine`] +------------------- + +[`Engine::new_raw`](#raw-engine) creates a _raw_ engine. +A _raw_ engine supports, out of the box, only a very [restricted set](#built-in-operators) of basic arithmetic and logical operators. +Selectively include other necessary functionalities by loading specific [packages] to minimize the footprint. +Packages are sharable (even across threads via the [`sync`] feature), so they only have to be created once. diff --git a/doc/src/start/builds/no-std.md b/doc/src/start/builds/no-std.md new file mode 100644 index 00000000..22b9b025 --- /dev/null +++ b/doc/src/start/builds/no-std.md @@ -0,0 +1,9 @@ +`no-std` Build +============= + +{{#include ../../links.md}} + +The feature [`no_std`] automatically converts the scripting engine into a `no-std` build. + +Usually, a `no-std` build goes hand-in-hand with [minimal builds] because typical embedded +hardware (the primary target for `no-std`) has limited storage. diff --git a/doc/src/start/builds/performance.md b/doc/src/start/builds/performance.md new file mode 100644 index 00000000..65099707 --- /dev/null +++ b/doc/src/start/builds/performance.md @@ -0,0 +1,27 @@ +Performance Build +================= + +{{#include ../../links.md}} + +Use Only One Integer Type +------------------------ + +Some features are for performance. For example, using [`only_i32`] or [`only_i64`] disables all other integer types (such as `u16`). +If only a single integer type is needed in scripts - most of the time this is the case - it is best to avoid registering +lots of functions related to other integer types that will never be used. As a result, performance should improve. + + +Use Only 32-Bit Numbers +---------------------- + +If only 32-bit integers are needed - again, most of the time this is the case - using [`only_i32`] disables also `i64`. +On 64-bit targets this may not gain much, but on some 32-bit targets this improves performance due to 64-bit arithmetic +requiring more CPU cycles to complete. + + +Minimize Size of [`Dynamic`] +--------------------------- + +Turning on [`no_float`], and [`only_i32`] makes the key [`Dynamic`] data type only 8 bytes small on 32-bit targets +while normally it can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`. +Making [`Dynamic`] small helps performance due to better cache efficiency. diff --git a/doc/src/start/builds/wasm.md b/doc/src/start/builds/wasm.md new file mode 100644 index 00000000..c0e7d96b --- /dev/null +++ b/doc/src/start/builds/wasm.md @@ -0,0 +1,18 @@ +Building to WebAssembly (WASM) +============================= + +{{#include ../../links.md}} + +It is possible to use Rhai when compiling to WebAssembly (WASM). This yields a scripting engine (and language) +that can be run in a standard web browser. Why you would want to is another matter... as there is already +a nice, fast, complete scripting language for the the common WASM environment (i.e. a browser) - and it is called JavaScript. +But anyhow, do it because you _can_! + +When building for WASM, certain features will not be available, such as the script file API's and loading modules +from external script files. + +Also look into [minimal builds] to reduce generated WASM size. As of this version, a typical, full-featured +Rhai scripting engine compiles to a single WASM file less than 200KB gzipped. When excluding features that are +marginal in WASM environment, the gzipped payload can be further shrunk to 160KB. + +In benchmark tests, a WASM build runs scripts roughly 1.7-2.2x slower than a native optimized release build. diff --git a/doc/src/start/examples.md b/doc/src/start/examples.md new file mode 100644 index 00000000..b7f9ae1d --- /dev/null +++ b/doc/src/start/examples.md @@ -0,0 +1,7 @@ +Examples +======== + +{{#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/examples/rust.md b/doc/src/start/examples/rust.md new file mode 100644 index 00000000..d7a28271 --- /dev/null +++ b/doc/src/start/examples/rust.md @@ -0,0 +1,31 @@ +Rust Examples +============ + +{{#include ../../links.md}} + +A number of examples can be found in the `examples` folder: + +| Example | Description | +| ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| [`arrays_and_structs`](https://github.com/jonathandturner/rhai/tree/master/examples/arrays_and_structs.rs) | shows how to register a custom Rust type and using [arrays] on it | +| [`custom_types_and_methods`](https://github.com/jonathandturner/rhai/tree/master/examples/custom_types_and_methods.rs) | shows how to register a custom Rust type and methods for it | +| [`hello`](https://github.com/jonathandturner/rhai/tree/master/examples/hello.rs) | simple example that evaluates an expression and prints the result | +| [`no_std`](https://github.com/jonathandturner/rhai/tree/master/examples/no_std.rs) | example to test out `no-std` builds | +| [`reuse_scope`](https://github.com/jonathandturner/rhai/tree/master/examples/reuse_scope.rs) | evaluates two pieces of code in separate runs, but using a common [`Scope`] | +| [`rhai_runner`](https://github.com/jonathandturner/rhai/tree/master/examples/rhai_runner.rs) | runs each filename passed to it as a Rhai script | +| [`simple_fn`](https://github.com/jonathandturner/rhai/tree/master/examples/simple_fn.rs) | shows how to register a simple function | +| [`strings`](https://github.com/jonathandturner/rhai/tree/master/examples/strings.rs) | shows different ways to register functions taking string arguments | +| [`repl`](https://github.com/jonathandturner/rhai/tree/master/examples/repl.rs) | a simple REPL, interactively evaluate statements from stdin | + +The `repl` example is a particularly good one as it allows one to interactively try out Rhai's +language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop). + + +Running Examples +---------------- + +Examples can be run with the following command: + +```bash +cargo run --example {example_name} +``` diff --git a/doc/src/start/examples/scripts.md b/doc/src/start/examples/scripts.md new file mode 100644 index 00000000..50cfba4e --- /dev/null +++ b/doc/src/start/examples/scripts.md @@ -0,0 +1,51 @@ +Example Scripts +============== + +{{#include ../../links.md}} + +Language Feature Scripts +----------------------- + +There are also a number of examples scripts that showcase Rhai's features, all in the `scripts` folder: + +| Script | Description | +| -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | +| [`array.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/array.rhai) | [arrays] in Rhai | +| [`assignment.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/assignment.rhai) | variable declarations | +| [`comments.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/comments.rhai) | just comments | +| [`for1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for1.rhai) | [`for`](#for-loop) loops | +| [`for2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/for2.rhai) | [`for`](#for-loop) loops on [arrays] | +| [`function_decl1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl1.rhai) | a [function] without parameters | +| [`function_decl2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl2.rhai) | a [function] with two parameters | +| [`function_decl3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/function_decl3.rhai) | a [function] with many parameters | +| [`if1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/if1.rhai) | [`if`](#if-statement) example | +| [`loop.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/loop.rhai) | count-down [`loop`](#infinite-loop) in Rhai, emulating a `do` .. `while` loop | +| [`op1.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op1.rhai) | just simple addition | +| [`op2.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op2.rhai) | simple addition and multiplication | +| [`op3.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/op3.rhai) | change evaluation order with parenthesis | +| [`string.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/string.rhai) | [string] operations | +| [`strings_map.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/strings_map.rhai) | [string] and [object map] operations | +| [`while.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/while.rhai) | [`while`](#while-loop) loop | + + +Benchmark Scripts +---------------- + +The following scripts are for benchmarking the speed of Rhai: + +| Scripts | Description | +| ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | +| [`speed_test.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/speed_test.rhai) | a simple program to measure the speed of Rhai's interpreter (1 million iterations) | +| [`primes.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/primes.rhai) | use Sieve of Eratosthenes to find all primes smaller than a limit | +| [`fibonacci.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/fibonacci.rhai) | calculate the n-th Fibonacci number using a really dumb algorithm | +| [`mat_mul.rhai`](https://github.com/jonathandturner/rhai/tree/master/scripts/mat_mul.rhai) | matrix multiplication test to measure the speed of multi-dimensional array access | + + +Running Example Scripts +---------------------- + +To run the scripts, either make a tiny program or use of the `rhai_runner` example: + +```bash +cargo run --example rhai_runner scripts/any_script.rhai +``` diff --git a/doc/src/start/features.md b/doc/src/start/features.md new file mode 100644 index 00000000..fca919a5 --- /dev/null +++ b/doc/src/start/features.md @@ -0,0 +1,48 @@ +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. + +| Feature | Description | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `unchecked` | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit. Beware that a bad script may panic the entire system! | +| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and `AST`, are all `Send + Sync`. | +| `no_optimize` | Disable the script optimizer. | +| `no_float` | Disable floating-point numbers and math. | +| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | +| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | +| `no_index` | Disable [arrays] and indexing features. | +| `no_object` | Disable support for custom types and [object maps]. | +| `no_function` | Disable script-defined functions. | +| `no_module` | Disable loading external modules. | +| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | + + +Example +------- + +The `Cargo.toml` configuration below turns on these six features: + +* `sync` (everything `Send + Sync`) +* `unchecked` (no checked arithmetic - should not be used with untrusted user scripts) +* `only_i32` (only 32-bit signed integers) +* `no_float` (no floating point numbers) +* `no_module` (no loading external modules) +* `no_function` (no defining functions) + +```toml +[dependencies] +rhai = { version = "0.15.2", 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`), +no floating-point, is `Send + Sync` (so it can be safely used across threads), does not support defining functions +nor loading external modules. + +This configuration is perfect for an expression parser in a 32-bit embedded system without floating-point hardware. diff --git a/doc/src/start/install.md b/doc/src/start/install.md new file mode 100644 index 00000000..c3553298 --- /dev/null +++ b/doc/src/start/install.md @@ -0,0 +1,27 @@ +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`: + +```toml +[dependencies] +rhai = "0.15.2" +``` + +Use the latest released crate version on [`crates.io`](https:/crates.io/crates/rhai/): + +```toml +[dependencies] +rhai = "*" +``` + +Crate versions are released on [`crates.io`](https:/crates.io/crates/rhai/) infrequently, +so to track the latest features, enhancements and bug fixes, pull directly from GitHub: + +```toml +[dependencies] +rhai = { git = "https://github.com/jonathandturner/rhai" } +```