diff --git a/README.md b/README.md index 7d55bdbe..952d9bf3 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,12 @@ Features one single source file, all with names starting with `"unsafe_"`). * Re-entrant scripting engine can be made `Send + Sync` (via the [`sync`] feature). * Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`). -* Rugged - protection against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts. +* Rugged - protected against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts. * Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run. * [Function overloading](https://schungx.github.io/rhai/language/overload.html). * [Operator overloading](https://schungx.github.io/rhai/rust/operators.html). +* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html). +* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html). * Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html). * Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations. * Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html). @@ -43,3 +45,9 @@ Documentation ------------- See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai scripting engine and language. + +Playground +---------- + +An [Online Playground](https://alvinhochun.github.io/rhai-demo/) is available with syntax-highlighting editor. +Scripts can be evaluated directly from the editor. diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 6ea2fc76..f9491dd3 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -1,30 +1,31 @@ The Rhai Scripting Language ========================== -1. [What is Rhai](about.md) +1. [What is Rhai](about/index.md) 1. [Features](about/features.md) 2. [Supported Targets and Builds](about/targets.md) 3. [What Rhai Isn't](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/index.md) +3. [Getting Started](start/index.md) + 1. [Online Playground](start/playground.md) + 2. [Install the Rhai Crate](start/install.md) + 3. [Optional Features](start/features.md) + 4. [Special Builds](start/builds/index.md) 1. [Performance](start/builds/performance.md) 2. [Minimal](start/builds/minimal.md) 3. [no-std](start/builds/no-std.md) 4. [WebAssembly (WASM)](start/builds/wasm.md) - 4. [Examples](start/examples/index.md) + 5. [Examples](start/examples/index.md) 1. [Rust](start/examples/rust.md) 2. [Scripts](start/examples/scripts.md) -3. [Using the `Engine`](engine.md) +4. [Using the `Engine`](engine/index.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) +5. [Extend Rhai with Rust](rust/index.md) 1. [Traits](rust/traits.md) 2. [Register a Rust Function](rust/functions.md) 1. [String Parameters in Rust Functions](rust/strings.md) @@ -42,7 +43,7 @@ The Rhai Scripting Language 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) +6. [Rhai Language Reference](language/index.md) 1. [Comments](language/comments.md) 2. [Values and Types](language/values-and-types.md) 1. [Dynamic Values](language/dynamic.md) @@ -70,9 +71,10 @@ The Rhai Scripting Language 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) - 3. [Function Pointers](language/fn-ptr.md) + 1. [Call Method as Function](language/method.md) + 2. [Overloading](language/overload.md) + 3. [Namespaces](language/fn-namespaces.md) + 4. [Function Pointers](language/fn-ptr.md) 15. [Print and Debug](language/print-debug.md) 16. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) @@ -81,7 +83,7 @@ The Rhai Scripting Language 4. [Create from AST](language/modules/ast.md) 5. [Module Resolvers](language/modules/resolvers.md) 1. [Implement a Custom Module Resolver](language/modules/imp-resolver.md) -6. [Safety and Protection](safety/index.md) +7. [Safety and Protection](safety/index.md) 1. [Checked Arithmetic](safety/checked.md) 2. [Sand-Boxing](safety/sandbox.md) 3. [Maximum Length of Strings](safety/max-string-size.md) @@ -92,7 +94,7 @@ The Rhai Scripting Language 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) +8. [Advanced Topics](advanced.md) 1. [Object-Oriented Programming (OOP)](language/oop.md) 2. [Script Optimization](engine/optimize/index.md) 1. [Optimization Levels](engine/optimize/optimize-levels.md) @@ -102,7 +104,7 @@ The Rhai Scripting Language 5. [Volatility Considerations](engine/optimize/volatility.md) 6. [Subtle Semantic Changes](engine/optimize/semantics.md) 3. [Eval Statement](language/eval.md) -8. [Appendix](appendix/index.md) +9. [Appendix](appendix/index.md) 1. [Keywords](appendix/keywords.md) 2. [Operators](appendix/operators.md) 3. [Literals](appendix/literals.md) diff --git a/doc/src/about/features.md b/doc/src/about/features.md index 75df3def..c012353a 100644 --- a/doc/src/about/features.md +++ b/doc/src/about/features.md @@ -8,7 +8,7 @@ Easy * Easy-to-use language similar to JavaScript+Rust with dynamic typing. -* Tight integration with native Rust [functions] and [types][custom types], including [getters/setters]({{rootUrl}}/rust/getters-setters.md), [methods][custom type] and [indexers]({{rootUrl}}/rust/indexers.md). +* Tight integration with native Rust [functions] and [types][custom types], including [getters/setters], [methods][custom type] and [indexers]. * Freely pass Rust variables/constants into a script via an external [`Scope`]. diff --git a/doc/src/about.md b/doc/src/about/index.md similarity index 86% rename from doc/src/about.md rename to doc/src/about/index.md index 8a15d31f..85c55824 100644 --- a/doc/src/about.md +++ b/doc/src/about/index.md @@ -1,7 +1,7 @@ What is Rhai ============ -{{#include links.md}} +{{#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/related.md b/doc/src/about/related.md index f91fdb64..defc29e4 100644 --- a/doc/src/about/related.md +++ b/doc/src/about/related.md @@ -11,6 +11,8 @@ Other online documentation resources for Rhai: * [`LIB.RS`](https://lib.rs/crates/rhai) - Rhai library info +* [Online Playground][playground] - Run scripts directly from editor + 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. diff --git a/doc/src/engine/compile.md b/doc/src/engine/compile.md index 32e16bce..f4ee0e65 100644 --- a/doc/src/engine/compile.md +++ b/doc/src/engine/compile.md @@ -3,7 +3,7 @@ Compile a Script (to AST) {{#include ../links.md}} -To repeatedly evaluate a script, _compile_ it first into an AST (abstract syntax tree) form: +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 diff --git a/doc/src/engine.md b/doc/src/engine/index.md similarity index 88% rename from doc/src/engine.md rename to doc/src/engine/index.md index 5ed3389a..8bf58504 100644 --- a/doc/src/engine.md +++ b/doc/src/engine/index.md @@ -1,7 +1,7 @@ Using the Engine ================ -{{#include links.md}} +{{#include ../links.md}} Rhai's interpreter resides in the [`Engine`] type under the master `rhai` namespace. diff --git a/doc/src/engine/optimize/reoptimize.md b/doc/src/engine/optimize/reoptimize.md index bd173be8..e8292e4c 100644 --- a/doc/src/engine/optimize/reoptimize.md +++ b/doc/src/engine/optimize/reoptimize.md @@ -4,13 +4,13 @@ Re-Optimize an AST {{#include ../../links.md}} Sometimes it is more efficient to store one single, large script with delimited code blocks guarded by -constant variables. This script is compiled once to an `AST`. +constant variables. This script is compiled once to an [`AST`]. -Then, depending on the execution environment, constants are passed into the [`Engine`] and the `AST` +Then, depending on the execution environment, constants are passed into the [`Engine`] and the [`AST`] is _re_-optimized based on those constants via the `Engine::optimize_ast` method, effectively pruning out unused code sections. -The final, optimized `AST` is then used for evaluations. +The final, optimized [`AST`] is then used for evaluations. ```rust // Compile master script to AST diff --git a/doc/src/language/fn-namespaces.md b/doc/src/language/fn-namespaces.md new file mode 100644 index 00000000..5bd8e16f --- /dev/null +++ b/doc/src/language/fn-namespaces.md @@ -0,0 +1,141 @@ +Function Namespaces +================== + +{{#include ../links.md}} + +Each Function is a Separate Compilation Unit +------------------------------------------- + +[Functions] in Rhai are _pure_ and they form individual _compilation units_. +This means that individual functions can be separated, exported, re-grouped, imported, +and generally mix-'n-match-ed with other completely unrelated scripts. + +For example, the `AST::merge` method allows merging all functions in one [`AST`] into another, +forming a new, combined, group of functions. + +In general, there are two types of _namespaces_ where functions are looked up: + +| Namespace | Source | Lookup method | How Many | +| --------- | ---------------------------------------------------------------------- | --------------------------------- | :----------------------: | +| Global | `Engine::register_XXX` API, [`AST`] being evaluated, [packages] loaded | Simple function name | One | +| Module | [`Module`] | Namespace-qualified function name | As many as [`import`]-ed | + + +Global Namespace +---------------- + +There is one _global_ namespace for every [`Engine`], which includes: + +* All the native Rust functions registered via the `Engine::register_XXX` API. + +* All the Rust functions defined in [packages] that are loaded into the [`Engine`]. + +In addition, during evaluation of an [`AST`], all script-defined functions bundled together within +the [`AST`] are added to the global namespace and override any existing registered functions of +the same names and number of parameters. + +Anywhere in a Rhai script, when a function call is made, it is searched within the global namespace. +Therefore, function calls in Rhai are _late_ bound - meaning that the function called cannot be +determined or guaranteed and there is no way to _lock down_ the function being called. +This aspect is very similar to JavaScript before ES6 modules. + +```rust +// Compile a script into AST +let ast1 = engine.compile( + r#" + fn message() { "Hello!" } // greeting message + + fn say_hello() { + print(message()); // prints message + } + + say_hello(); + "# +)?; + +// Compile another script with an overriding function +let ast2 = engine.compile(r#"fn message() { "Boo!" }"#)?; + +// Merge the two AST's +let ast = ast1.merge(ast2); // 'message' will be overwritten + +engine.consume_ast(&ast)?; // prints 'Boo!' +``` + +Therefore, care must be taken when _cross-calling_ functions to make sure that the correct +functions are called. + +The only practical way to ensure that a function is a correct one is to use [modules] - +i.e. define the function in a separate module and then [`import`] it: + +```rust +message.rhai: + + fn message() { "Hello!" } + +script.rhai: + + fn say_hello() { + import "message" as msg; + print(msg::message()); + } + say_hello(); +``` + + +Module Namespaces +----------------- + +[Modules] can be dynamically loaded into a Rhai script using the [`import`] keyword. +When that happens, functions defined within the [module] can be called with a _qualified_ name. + +There is a catch, though, if functions in a module script refer to global functions +defined _within the script_. When called later, those functions will be searched in the +current global namespace and may not be found. + +```rust +greeting.rhai: + + fn message() { "Hello!" }; + + fn say_hello() { print(message()); } + + say_hello(); // 'message' is looked up in the global namespace + +script.rhai: + + import "greeting" as g; + g::say_hello(); // <- error: function not found - 'message' +``` + +In the example above, although the module `greeting.rhai` loads fine (`"Hello!"` is printed), +the subsequent call using the _namespace-qualified_ function name fails to find the same function +'`message`' which now essentially becomes `g::message`. The call fails as there is no more +function named '`message`' in the global namespace. + +Therefore, when writing functions for a [module], make sure that those functions are as _pure_ +as possible and avoid cross-calling them from each other. A [function pointer] is a valid technique +to call another function within a module-defined function: + +```rust +greeting.rhai: + + fn message() { "Hello!" }; + + fn say_hello(msg_func) { // 'msg_func' is a function pointer + print(msg_func.call()); // call via the function pointer + } + + say_hello(); // 'message' is looked up in the global namespace + +script.rhai: + + import "greeting" as g; + + fn my_msg() { + import "greeting" as g; // <- must import again here... + g::message() // <- ... otherwise will not find module 'g' + } + + g::say_hello(Fn("my_msg")); // prints 'Hello!' +``` diff --git a/doc/src/language/fn-ptr.md b/doc/src/language/fn-ptr.md index a9f2e64a..e4d9bc5d 100644 --- a/doc/src/language/fn-ptr.md +++ b/doc/src/language/fn-ptr.md @@ -54,7 +54,34 @@ let fn_name = "hello"; // the function name does not have to exist yet let hello = Fn(fn_name + "_world"); -hello.call(0); // error: function not found - "hello_world (i64)" +hello.call(0); // error: function not found - 'hello_world (i64)' +``` + + +Global Namespace Only +-------------------- + +Because of their dynamic nature, function pointers cannot refer to functions in a _module_ [namespace][function namespace] +(i.e. functions in [`import`]-ed modules). They can only refer to functions within the global [namespace][function namespace]. +See [function namespaces] for more details. + +```rust +import "foo" as f; // assume there is 'f::do_something()' + +f::do_something(); // works! + +let p = Fn("f::do_something"); + +p.call(); // error: function not found - 'f::do_something' + +fn do_something_now() { // call it from a local function + import "foo" as f; + f::do_something(); +} + +let p = Fn("do_something_now"); + +p.call(); // works! ``` diff --git a/doc/src/language.md b/doc/src/language/index.md similarity index 78% rename from doc/src/language.md rename to doc/src/language/index.md index b3526ebd..ac4e4fb6 100644 --- a/doc/src/language.md +++ b/doc/src/language/index.md @@ -1,7 +1,7 @@ Rhai Language Reference ====================== -{{#include links.md}} +{{#include ../links.md}} This section outlines the Rhai language. diff --git a/doc/src/language/method.md b/doc/src/language/method.md index a9cdc1c7..bdce0ed6 100644 --- a/doc/src/language/method.md +++ b/doc/src/language/method.md @@ -3,9 +3,9 @@ Call Method as Function {{#include ../links.md}} -Property getters/setters and methods in a Rust custom type registered with the [`Engine`] can be called +Property [getters/setters] and [methods][custom types] in a Rust custom type registered with the [`Engine`] can be called just like a regular function. In fact, like Rust, property getters/setters and object methods -are registered as regular functions in Rhai that take a first `&mut` parameter. +are registered as regular [functions] in Rhai that take a first `&mut` parameter. Unlike functions defined in script (for which all arguments are passed by _value_), native Rust functions may mutate the object (or the first argument if called in normal function call style). diff --git a/doc/src/language/modules/ast.md b/doc/src/language/modules/ast.md index 96607881..4bf39ace 100644 --- a/doc/src/language/modules/ast.md +++ b/doc/src/language/modules/ast.md @@ -3,7 +3,7 @@ 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`. +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). diff --git a/doc/src/language/modules/export.md b/doc/src/language/modules/export.md index 9ab2b9e3..dbb4ddca 100644 --- a/doc/src/language/modules/export.md +++ b/doc/src/language/modules/export.md @@ -3,9 +3,9 @@ Export Variables, Functions and Sub-Modules in Module {{#include ../../links.md}} -A _module_ is a single script (or pre-compiled `AST`) containing global variables, functions and sub-modules. +A _module_ is a single script (or pre-compiled [`AST`]) containing global variables, functions and sub-modules. -A module can be created from a script via the `Module::eval_ast_as_new` method. When given an `AST`, +A module can be created from a script via the `Module::eval_ast_as_new` method. When given an [`AST`], it is first evaluated, then the following items are exposed as members of the new module: * Global variables - essentially all variables that remain in the [`Scope`] at the end of a script run - that are exported. Variables not exported (via the `export` statement) remain hidden. diff --git a/doc/src/language/overload.md b/doc/src/language/overload.md index b841d00a..bb291ca3 100644 --- a/doc/src/language/overload.md +++ b/doc/src/language/overload.md @@ -3,7 +3,7 @@ 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_ +[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. diff --git a/doc/src/links.md b/doc/src/links.md index ae6ef01d..b7147a50 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -15,11 +15,13 @@ [minimal builds]: {{rootUrl}}/start/builds/minimal.md [WASM]: {{rootUrl}}/start/builds/wasm.md +[playground]: https://alvinhochun.github.io/rhai-demo [`Engine`]: {{rootUrl}}/engine/hello-world.md [traits]: {{rootUrl}}/rust/traits.md [`private`]: {{rootUrl}}/engine/call-fn.md [`Func`]: {{rootUrl}}/engine/func.md +[`AST`]: {{rootUrl}}/engine/compile.md [`eval_expression`]: {{rootUrl}}/engine/expressions.md [`eval_expression_with_scope`]: {{rootUrl}}/engine/expressions.md [raw `Engine`]: {{rootUrl}}/engine/raw.md @@ -38,6 +40,8 @@ [custom type]: {{rootUrl}}/rust/custom.md [custom types]: {{rootUrl}}/rust/custom.md +[getters/setters]: {{rootUrl}}/rust/getters-setters.md +[indexers]: {{rootUrl}}/rust/indexers.md [`instant::Instant`]: https://crates.io/crates/instant @@ -70,6 +74,8 @@ [functions]: {{rootUrl}}/language/functions.md [function pointer]: {{rootUrl}}/language/fn-ptr.md [function pointers]: {{rootUrl}}/language/fn-ptr.md +[function namespace]: {{rootUrl}}/language/fn-namespaces.md +[function namespaces]: {{rootUrl}}/language/fn-namespaces.md [`Module`]: {{rootUrl}}/language/modules/index.md [module]: {{rootUrl}}/language/modules/index.md @@ -89,7 +95,7 @@ [maximum length of strings]: {{rootUrl}}/safety/max-string-size.md [maximum size of arrays]: {{rootUrl}}/safety/max-array-size.md [maximum size of object maps]: {{rootUrl}}/safety/max-map-size.md -[progress]:/safety/progress.md +[progress]: {{rootUrl}}/safety/progress.md [script optimization]: {{rootUrl}}/engine/optimize/index.md [`OptimizationLevel::Full`]: {{rootUrl}}/engine/optimize/optimize-levels.md diff --git a/doc/src/rust.md b/doc/src/rust/index.md similarity index 91% rename from doc/src/rust.md rename to doc/src/rust/index.md index bad92288..6a0ca08d 100644 --- a/doc/src/rust.md +++ b/doc/src/rust/index.md @@ -1,7 +1,7 @@ Extend Rhai with Rust ==================== -{{#include links.md}} +{{#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. diff --git a/doc/src/safety.md b/doc/src/safety.md deleted file mode 100644 index 1140c921..00000000 --- a/doc/src/safety.md +++ /dev/null @@ -1 +0,0 @@ -# Safety and Protection diff --git a/doc/src/safety/max-stmt-depth.md b/doc/src/safety/max-stmt-depth.md index 007e7c82..e8b5ee84 100644 --- a/doc/src/safety/max-stmt-depth.md +++ b/doc/src/safety/max-stmt-depth.md @@ -25,7 +25,7 @@ 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. +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). diff --git a/doc/src/start/features.md b/doc/src/start/features.md index c7cbd095..b4a415a9 100644 --- a/doc/src/start/features.md +++ b/doc/src/start/features.md @@ -14,7 +14,7 @@ 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`. | +| `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 [script optimization]. | | `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`. | @@ -24,7 +24,7 @@ more control over what a script can (or cannot) do. | `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. | -| `internals` | Expose internal data structures (e.g. `AST` nodes). Beware that Rhai internals are volatile and may change from version to version. | +| `internals` | Expose internal data structures (e.g. [`AST`] nodes). Beware that Rhai internals are volatile and may change from version to version. | Example diff --git a/doc/src/start.md b/doc/src/start/index.md similarity index 81% rename from doc/src/start.md rename to doc/src/start/index.md index 33de6526..f89dfbd0 100644 --- a/doc/src/start.md +++ b/doc/src/start/index.md @@ -1,6 +1,6 @@ Getting Started =============== -{{#include links.md}} +{{#include ../links.md}} This section shows how to install the Rhai crate into a Rust application. diff --git a/doc/src/start/install.md b/doc/src/start/install.md index b8ee54dc..a7dc944f 100644 --- a/doc/src/start/install.md +++ b/doc/src/start/install.md @@ -3,8 +3,10 @@ Install the Rhai Crate {{#include ../links.md}} -Install the Rhai crate from [`crates.io`](https:/crates.io/crates/rhai/), start by looking up the -latest version and adding this line under `dependencies` in `Cargo.toml`: +In order to use Rhai in a project, the Rhai crate must first be made a dependency. + +The easiest way is to install the Rhai crate from [`crates.io`](https:/crates.io/crates/rhai/), +starting by looking up the latest version and adding this line under `dependencies` in the project's `Cargo.toml`: ```toml [dependencies] diff --git a/doc/src/start/playground.md b/doc/src/start/playground.md new file mode 100644 index 00000000..08809805 --- /dev/null +++ b/doc/src/start/playground.md @@ -0,0 +1,10 @@ +Online Playground +================= + +{{#include ../links.md}} + +Rhai provides an [online playground][playground] to try out its language and engine features +without having to install anything. + +The playground provides a syntax-highlighting script editor with example snippets. +Scripts can be evaluated directly from the editor. diff --git a/rhai_logo.png b/rhai_logo.png new file mode 100644 index 00000000..af45aa7f Binary files /dev/null and b/rhai_logo.png differ diff --git a/src/any.rs b/src/any.rs index d99712e1..bc148956 100644 --- a/src/any.rs +++ b/src/any.rs @@ -545,6 +545,7 @@ impl Dynamic { pub fn as_str(&self) -> Result<&str, &'static str> { match &self.0 { Union::Str(s) => Ok(s), + Union::FnPtr(f) => Ok(f.fn_name()), _ => Err(self.type_name()), } } @@ -561,15 +562,7 @@ impl Dynamic { pub(crate) fn take_immutable_string(self) -> Result { match self.0 { Union::Str(s) => Ok(s), - _ => Err(self.type_name()), - } - } - - /// Cast the `Dynamic` as a `FnPtr` and return the function name. - /// Returns the name of the actual type if the cast fails. - pub(crate) fn as_fn_name(&self) -> Result<&str, &'static str> { - match &self.0 { - Union::FnPtr(f) => Ok(f.fn_name()), + Union::FnPtr(f) => Ok(f.take_fn_name()), _ => Err(self.type_name()), } } diff --git a/src/api.rs b/src/api.rs index 66050214..2f0e3e19 100644 --- a/src/api.rs +++ b/src/api.rs @@ -422,7 +422,7 @@ impl Engine { self.register_indexer_set(setter); } - /// Compile a string into an `AST`, which can be used later for evaluation. + /// Compile a string into an [`AST`], which can be used later for evaluation. /// /// # Example /// @@ -445,7 +445,7 @@ impl Engine { self.compile_with_scope(&Scope::new(), script) } - /// Compile a string into an `AST` using own scope, which can be used later for evaluation. + /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation. /// /// The scope is useful for passing constants into the script for optimization /// when using `OptimizationLevel::Full`. @@ -488,7 +488,7 @@ impl Engine { } /// When passed a list of strings, first join the strings into one large script, - /// and then compile them into an `AST` using own scope, which can be used later for evaluation. + /// and then compile them into an [`AST`] using own scope, which can be used later for evaluation. /// /// The scope is useful for passing constants into the script for optimization /// when using `OptimizationLevel::Full`. @@ -541,7 +541,7 @@ impl Engine { self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level) } - /// Join a list of strings and compile into an `AST` using own scope at a specific optimization level. + /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level. pub(crate) fn compile_with_scope_and_optimization_level( &self, scope: &Scope, @@ -577,7 +577,7 @@ impl Engine { Ok(contents) } - /// Compile a script file into an `AST`, which can be used later for evaluation. + /// Compile a script file into an [`AST`], which can be used later for evaluation. /// /// # Example /// @@ -603,7 +603,7 @@ impl Engine { self.compile_file_with_scope(&Scope::new(), path) } - /// Compile a script file into an `AST` using own scope, which can be used later for evaluation. + /// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation. /// /// The scope is useful for passing constants into the script for optimization /// when using `OptimizationLevel::Full`. @@ -685,7 +685,7 @@ impl Engine { self.eval_ast_with_scope(&mut scope, &ast) } - /// Compile a string containing an expression into an `AST`, + /// Compile a string containing an expression into an [`AST`], /// which can be used later for evaluation. /// /// # Example @@ -709,7 +709,7 @@ impl Engine { self.compile_expression_with_scope(&Scope::new(), script) } - /// Compile a string containing an expression into an `AST` using own scope, + /// Compile a string containing an expression into an [`AST`] using own scope, /// which can be used later for evaluation. /// /// The scope is useful for passing constants into the script for optimization @@ -917,7 +917,7 @@ impl Engine { self.eval_ast_with_scope(scope, &ast) } - /// Evaluate an `AST`. + /// Evaluate an [`AST`]. /// /// # Example /// @@ -939,7 +939,7 @@ impl Engine { self.eval_ast_with_scope(&mut Scope::new(), ast) } - /// Evaluate an `AST` with own scope. + /// Evaluate an [`AST`] with own scope. /// /// # Example /// @@ -986,7 +986,7 @@ impl Engine { }); } - /// Evaluate an `AST` with own scope. + /// Evaluate an [`AST`] with own scope. pub(crate) fn eval_ast_with_scope_raw<'a>( &self, scope: &mut Scope, @@ -1052,7 +1052,7 @@ impl Engine { self.consume_ast_with_scope(&mut Scope::new(), ast) } - /// Evaluate an `AST` with own scope, but throw away the result and only return error (if any). + /// Evaluate an [`AST`] with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. pub fn consume_ast_with_scope( &self, @@ -1076,7 +1076,7 @@ impl Engine { ) } - /// Call a script function defined in an `AST` with multiple arguments. + /// Call a script function defined in an [`AST`] with multiple arguments. /// Arguments are passed as a tuple. /// /// # Example @@ -1133,7 +1133,7 @@ impl Engine { }); } - /// Call a script function defined in an `AST` with multiple `Dynamic` arguments. + /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments. /// /// # Example /// @@ -1179,7 +1179,7 @@ impl Engine { self.call_fn_dynamic_raw(scope, ast, name, arg_values.as_mut()) } - /// Call a script function defined in an `AST` with multiple `Dynamic` arguments. + /// Call a script function defined in an [`AST`] with multiple `Dynamic` arguments. /// /// ## WARNING /// @@ -1220,15 +1220,15 @@ impl Engine { ) } - /// Optimize the `AST` with constants defined in an external Scope. - /// An optimized copy of the `AST` is returned while the original `AST` is consumed. + /// Optimize the [`AST`] with constants defined in an external Scope. + /// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed. /// /// Although optimization is performed by default during compilation, sometimes it is necessary to /// _re_-optimize an AST. For example, when working with constants that are passed in via an - /// external scope, it will be more efficient to optimize the `AST` once again to take advantage + /// external scope, it will be more efficient to optimize the [`AST`] once again to take advantage /// of the new constants. /// - /// With this method, it is no longer necessary to recompile a large script. The script `AST` can be + /// With this method, it is no longer necessary to recompile a large script. The script [`AST`] can be /// compiled just once. Before evaluation, constants are passed into the `Engine` via an external scope /// (i.e. with `scope.push_constant(...)`). Then, the `AST is cloned and the copy re-optimized before running. #[cfg(not(feature = "no_optimize"))] diff --git a/src/engine.rs b/src/engine.rs index 516f34fb..65c91735 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -8,10 +8,10 @@ use crate::module::{resolvers, Module, ModuleRef, ModuleResolver}; use crate::optimize::OptimizationLevel; use crate::packages::{Package, PackageLibrary, PackagesCollection, StandardPackage}; use crate::parser::{Expr, FnAccess, ImmutableString, ReturnType, ScriptFnDef, Stmt, AST, INT}; -use crate::r#unsafe::{unsafe_cast_var_name_to_lifetime, unsafe_mut_cast_to_lifetime}; +use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; -use crate::token::Position; +use crate::token::{is_valid_identifier, Position}; use crate::utils::StaticVec; #[cfg(not(feature = "no_float"))] @@ -75,6 +75,7 @@ pub const KEYWORD_PRINT: &str = "print"; pub const KEYWORD_DEBUG: &str = "debug"; pub const KEYWORD_TYPE_OF: &str = "type_of"; pub const KEYWORD_EVAL: &str = "eval"; +pub const KEYWORD_FN_PTR: &str = "Fn"; pub const KEYWORD_FN_PTR_CALL: &str = "call"; pub const KEYWORD_THIS: &str = "this"; pub const FN_TO_STRING: &str = "to_string"; @@ -82,7 +83,6 @@ pub const FN_GET: &str = "get$"; pub const FN_SET: &str = "set$"; pub const FN_IDX_GET: &str = "$index$get$"; pub const FN_IDX_SET: &str = "$index$set$"; -pub const FN_FN_PTR: &str = "Fn"; /// A type specifying the method of chaining. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] @@ -729,10 +729,17 @@ impl Engine { // Replace the first reference with a reference to the clone, force-casting the lifetime. // Keep the original reference. Must remember to restore it later with `restore_first_arg_of_method_call`. - let this_pointer = mem::replace( - args.get_mut(0).unwrap(), - unsafe_mut_cast_to_lifetime(this_copy), - ); + // + // # Safety + // + // Blindly casting a a reference to another lifetime saves on allocations and string cloning, + // but must be used with the utmost care. + // + // We can do this here because, at the end of this scope, we'd restore the original reference + // with `restore_first_arg_of_method_call`. Therefore this shorter lifetime does not get "out". + let this_pointer = mem::replace(args.get_mut(0).unwrap(), unsafe { + mem::transmute(this_copy) + }); *old_this_ptr = Some(this_pointer); } @@ -1020,7 +1027,7 @@ impl Engine { )), // Fn - FN_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes) => { + KEYWORD_FN_PTR if args.len() == 1 && !self.has_override(lib, hashes) => { Err(Box::new(EvalAltResult::ErrorRuntime( "'Fn' should not be called in method style. Try Fn(...);".into(), Position::none(), @@ -1133,7 +1140,7 @@ impl Engine { state, lib, this_ptr, obj_ptr, expr, idx_values, next_chain, level, new_val, ) - .map_err(|err| EvalAltResult::new_position(err, *pos)) + .map_err(|err| err.new_position(*pos)) } // xxx[rhs] = new_val _ if new_val.is_some() => { @@ -1162,9 +1169,9 @@ impl Engine { } // Indexed value is a reference - update directly Ok(ref mut obj_ptr) => { - obj_ptr.set_value(new_val.unwrap()).map_err(|err| { - EvalAltResult::new_position(err, rhs.position()) - })?; + obj_ptr + .set_value(new_val.unwrap()) + .map_err(|err| err.new_position(rhs.position()))?; } Err(err) => match *err { // No index getter - try to call an index setter @@ -1205,11 +1212,12 @@ impl Engine { let (result, updated) = { let obj = target.as_mut(); let idx = idx_val.downcast_mut::>().unwrap(); + let mut fn_name = name.as_ref(); // Check if it is a FnPtr call - if name == KEYWORD_FN_PTR_CALL && obj.is::() { + if fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { // Redirect function name - let fn_name = obj.as_fn_name().unwrap(); + fn_name = obj.as_str().unwrap(); // Recalculate hash let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty()); // Arguments are passed as-is @@ -1222,25 +1230,20 @@ impl Engine { def_val, level, ) } else { - let mut fn_name = name.clone(); - let mut redirected = None; + let redirected: Option; let mut hash = *hash; // Check if it is a map method call in OOP style if let Some(map) = obj.downcast_ref::() { - if let Some(val) = map.get(name.as_ref()) { + if let Some(val) = map.get(fn_name) { if let Some(f) = val.downcast_ref::() { // Remap the function name redirected = Some(f.get_fn_name().clone()); - fn_name = redirected.as_ref().unwrap().as_str().into(); + fn_name = redirected.as_ref().unwrap(); // Recalculate the hash based on the new function name - hash = calc_fn_hash( - empty(), - fn_name.as_ref(), - idx.len(), - empty(), - ); + hash = + calc_fn_hash(empty(), fn_name, idx.len(), empty()); } } }; @@ -1249,14 +1252,13 @@ impl Engine { let mut arg_values = once(obj).chain(idx.iter_mut()).collect::>(); let args = arg_values.as_mut(); - let fn_name = fn_name.as_ref(); self.exec_fn_call( state, lib, fn_name, *native, hash, args, is_ref, true, def_val, level, ) } - .map_err(|err| EvalAltResult::new_position(err, *pos))? + .map_err(|err| err.new_position(*pos))? }; // Feed the changed temp value back @@ -1277,7 +1279,7 @@ impl Engine { self.get_indexed_mut(state, lib, target, index, *pos, true, level)?; val.set_value(new_val.unwrap()) - .map_err(|err| EvalAltResult::new_position(err, rhs.position()))?; + .map_err(|err| err.new_position(rhs.position()))?; Ok((Default::default(), true)) } // {xxx:map}.id @@ -1297,7 +1299,7 @@ impl Engine { state, lib, setter, true, 0, &mut args, is_ref, true, None, level, ) .map(|(v, _)| (v, true)) - .map_err(|err| EvalAltResult::new_position(err, *pos)) + .map_err(|err| err.new_position(*pos)) } // xxx.id Expr::Property(x) => { @@ -1307,7 +1309,7 @@ impl Engine { state, lib, getter, true, 0, &mut args, is_ref, true, None, level, ) .map(|(v, _)| (v, false)) - .map_err(|err| EvalAltResult::new_position(err, *pos)) + .map_err(|err| err.new_position(*pos)) } // {xxx:map}.prop[expr] | {xxx:map}.prop.expr Expr::Index(x) | Expr::Dot(x) if target.is::() => { @@ -1325,7 +1327,7 @@ impl Engine { state, lib, this_ptr, &mut val, expr, idx_values, next_chain, level, new_val, ) - .map_err(|err| EvalAltResult::new_position(err, *pos)) + .map_err(|err| err.new_position(*pos)) } // xxx.prop[expr] | xxx.prop.expr Expr::Index(x) | Expr::Dot(x) => { @@ -1338,7 +1340,7 @@ impl Engine { self.exec_fn_call( state, lib, getter, true, 0, args, is_ref, true, None, level, ) - .map_err(|err| EvalAltResult::new_position(err, *pos))? + .map_err(|err| err.new_position(*pos))? } else { unreachable!(); }; @@ -1350,7 +1352,7 @@ impl Engine { state, lib, this_ptr, target, expr, idx_values, next_chain, level, new_val, ) - .map_err(|err| EvalAltResult::new_position(err, *pos))?; + .map_err(|err| err.new_position(*pos))?; // Feed the value back via a setter just in case it has been updated if updated || may_be_changed { @@ -1364,7 +1366,7 @@ impl Engine { .or_else(|err| match *err { // If there is no setter, no need to feed it back because the property is read-only EvalAltResult::ErrorDotExpr(_, _) => Ok(Default::default()), - err => Err(EvalAltResult::new_position(Box::new(err), *pos)), + _ => Err(err.new_position(*pos)), })?; } } @@ -1413,7 +1415,7 @@ impl Engine { let (var_name, var_pos) = &x.0; self.inc_operations(state) - .map_err(|err| EvalAltResult::new_position(err, *var_pos))?; + .map_err(|err| err.new_position(*var_pos))?; let (target, _, typ, pos) = search_scope(scope, mods, state, this_ptr, dot_lhs)?; @@ -1433,7 +1435,7 @@ impl Engine { state, lib, &mut None, obj_ptr, dot_rhs, idx_values, chain_type, level, new_val, ) .map(|(v, _)| v) - .map_err(|err| EvalAltResult::new_position(err, *op_pos)) + .map_err(|err| err.new_position(*op_pos)) } // {expr}.??? = ??? or {expr}[???] = ??? expr if new_val.is_some() => { @@ -1449,7 +1451,7 @@ impl Engine { state, lib, this_ptr, obj_ptr, dot_rhs, idx_values, chain_type, level, new_val, ) .map(|(v, _)| v) - .map_err(|err| EvalAltResult::new_position(err, *op_pos)) + .map_err(|err| err.new_position(*op_pos)) } } } @@ -1472,7 +1474,7 @@ impl Engine { level: usize, ) -> Result<(), Box> { self.inc_operations(state) - .map_err(|err| EvalAltResult::new_position(err, expr.position()))?; + .map_err(|err| err.new_position(expr.position()))?; match expr { Expr::FnCall(x) if x.1.is_none() => { @@ -1627,7 +1629,7 @@ impl Engine { level: usize, ) -> Result> { self.inc_operations(state) - .map_err(|err| EvalAltResult::new_position(err, rhs.position()))?; + .map_err(|err| err.new_position(rhs.position()))?; let lhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?; let rhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?; @@ -1655,7 +1657,7 @@ impl Engine { &mut scope, mods, state, lib, op, hashes, args, false, false, def_value, level, ) - .map_err(|err| EvalAltResult::new_position(err, rhs.position()))?; + .map_err(|err| err.new_position(rhs.position()))?; if r.as_bool().unwrap_or(false) { return Ok(true.into()); } @@ -1694,7 +1696,7 @@ impl Engine { level: usize, ) -> Result> { self.inc_operations(state) - .map_err(|err| EvalAltResult::new_position(err, expr.position()))?; + .map_err(|err| err.new_position(expr.position()))?; let result = match expr { Expr::Expr(x) => self.eval_expr(scope, mods, state, lib, this_ptr, x.as_ref(), level), @@ -1728,7 +1730,7 @@ impl Engine { let (lhs_ptr, name, typ, pos) = search_scope(scope, mods, state, this_ptr, lhs_expr)?; self.inc_operations(state) - .map_err(|err| EvalAltResult::new_position(err, pos))?; + .map_err(|err| err.new_position(pos))?; match typ { // Assignment to constant variable @@ -1769,7 +1771,7 @@ impl Engine { state, lib, op, true, hash, args, false, false, None, level, ) .map(|(v, _)| v) - .map_err(|err| EvalAltResult::new_position(err, *op_pos))?; + .map_err(|err| err.new_position(*op_pos))?; } Ok(Default::default()) } @@ -1795,7 +1797,7 @@ impl Engine { ]; self.exec_fn_call(state, lib, op, true, hash, args, false, false, None, level) .map(|(v, _)| v) - .map_err(|err| EvalAltResult::new_position(err, *op_pos))? + .map_err(|err| err.new_position(*op_pos))? }); match lhs_expr { @@ -1859,8 +1861,8 @@ impl Engine { let ((name, native, pos), _, hash, args_expr, def_val) = x.as_ref(); let def_val = def_val.as_ref(); - // Handle Fn - if name == FN_FN_PTR && args_expr.len() == 1 { + // Handle Fn() + if name == KEYWORD_FN_PTR && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); @@ -1871,17 +1873,27 @@ impl Engine { self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; return arg_value .take_immutable_string() - .map(|s| FnPtr::from(s).into()) .map_err(|type_name| { Box::new(EvalAltResult::ErrorMismatchOutputType( type_name.into(), - Position::none(), + expr.position(), )) - }); + }) + .and_then(|s| { + if is_valid_identifier(s.chars()) { + Ok(s) + } else { + Err(Box::new(EvalAltResult::ErrorFunctionNotFound( + s.to_string(), + expr.position(), + ))) + } + }) + .map(|s| FnPtr::from(s).into()); } } - // Handle eval + // Handle eval() if name == KEYWORD_EVAL && args_expr.len() == 1 { let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::())); @@ -1894,7 +1906,7 @@ impl Engine { self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let result = self .eval_script_expr(scope, mods, state, lib, &script) - .map_err(|err| EvalAltResult::new_position(err, expr.position())); + .map_err(|err| err.new_position(expr.position())); if scope.len() != prev_len { // IMPORTANT! If the eval defines new variables in the current scope, @@ -1932,7 +1944,7 @@ impl Engine { search_scope(scope, mods, state, this_ptr, lhs)?; self.inc_operations(state) - .map_err(|err| EvalAltResult::new_position(err, pos))?; + .map_err(|err| err.new_position(pos))?; args = once(target).chain(arg_values.iter_mut()).collect(); @@ -1957,7 +1969,7 @@ impl Engine { state, lib, name, *native, *hash, args, is_ref, false, def_val, level, ) .map(|(v, _)| v) - .map_err(|err| EvalAltResult::new_position(err, *pos)) + .map_err(|err| err.new_position(*pos)) } // Module-qualified function call @@ -1979,7 +1991,7 @@ impl Engine { Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => { // Then search in Rust functions self.inc_operations(state) - .map_err(|err| EvalAltResult::new_position(err, *pos))?; + .map_err(|err| err.new_position(*pos))?; // Qualified Rust functions are indexed in two steps: // 1) Calculate a hash in a similar manner to script-defined functions, @@ -2005,7 +2017,7 @@ impl Engine { self.call_script_fn( &mut scope, &mut mods, state, lib, &mut None, name, fn_def, args, level, ) - .map_err(|err| EvalAltResult::new_position(err, *pos)) + .map_err(|err| err.new_position(*pos)) } Ok(f) => { f.get_native_fn()(self, args.as_mut()).map_err(|err| err.new_position(*pos)) @@ -2071,6 +2083,7 @@ impl Engine { }; self.check_data_size(result) + .map_err(|err| err.new_position(expr.position())) } /// Evaluate a statement @@ -2085,7 +2098,7 @@ impl Engine { level: usize, ) -> Result> { self.inc_operations(state) - .map_err(|err| EvalAltResult::new_position(err, stmt.position()))?; + .map_err(|err| err.new_position(stmt.position()))?; let result = match stmt { // No-op @@ -2200,7 +2213,7 @@ impl Engine { for loop_var in func(iter_type) { *scope.get_mut(index).0 = loop_var; self.inc_operations(state) - .map_err(|err| EvalAltResult::new_position(err, stmt.position()))?; + .map_err(|err| err.new_position(stmt.position()))?; match self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) { Ok(_) => (), @@ -2368,9 +2381,11 @@ impl Engine { }; self.check_data_size(result) + .map_err(|err| err.new_position(stmt.position())) } /// Check a result to ensure that the data size is within allowable limit. + /// Position in `EvalAltResult` may be None and should be set afterwards. fn check_data_size( &self, result: Result>, diff --git a/src/fn_func.rs b/src/fn_func.rs index bbbab4aa..ccb861a6 100644 --- a/src/fn_func.rs +++ b/src/fn_func.rs @@ -15,8 +15,8 @@ use crate::stdlib::{boxed::Box, string::ToString}; pub trait Func { type Output; - /// Create a Rust anonymous function from an `AST`. - /// The `Engine` and `AST` are consumed and basically embedded into the closure. + /// Create a Rust anonymous function from an [`AST`]. + /// The `Engine` and [`AST`] are consumed and basically embedded into the closure. /// /// # Examples /// diff --git a/src/fn_native.rs b/src/fn_native.rs index 040ff1f4..e41ae7db 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -67,6 +67,10 @@ impl FnPtr { pub(crate) fn get_fn_name(&self) -> &ImmutableString { &self.0 } + /// Get the name of the function. + pub(crate) fn take_fn_name(self) -> ImmutableString { + self.0 + } } impl fmt::Display for FnPtr { diff --git a/src/lib.rs b/src/lib.rs index af902d9b..4c37149c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,7 +62,7 @@ //! | `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_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | -//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and `AST` are all `Send + Sync`. | +//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and [`AST`] are all `Send + Sync`. | //! | `internals` | Expose internal data structures (beware they may be volatile from version to version). | //! //! See [The Rhai Book](https://schungx.github.io/rhai) for details on the Rhai script engine and language. diff --git a/src/module.rs b/src/module.rs index 7eeb4c8e..043d14dc 100644 --- a/src/module.rs +++ b/src/module.rs @@ -901,7 +901,7 @@ impl Module { .map(|f| f.get_shared_fn_def()) } - /// Create a new `Module` by evaluating an `AST`. + /// Create a new `Module` by evaluating an [`AST`]. /// /// # Examples /// diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index d492f52f..2b9da1f5 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -42,12 +42,10 @@ fn pad(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncRe } if len >= 0 { - let item = args[2].downcast_ref::().unwrap().clone(); + let item = args[2].clone(); let list = args[0].downcast_mut::().unwrap(); - while list.len() < len as usize { - push(list, item.clone())?; - } + list.resize(len as usize, item); } Ok(()) } diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index a472c2dd..2037c215 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -245,16 +245,21 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str let ch = *args[2].downcast_ref::< char>().unwrap(); let s = args[0].downcast_mut::().unwrap(); - let copy = s.make_mut(); - for _ in 0..copy.chars().count() - len as usize { - copy.push(ch); + let orig_len = s.chars().count(); + + if orig_len < len as usize { + let p = s.make_mut(); + + for _ in 0..(len as usize - orig_len) { + p.push(ch); + } } - if engine.max_string_size > 0 && copy.len() > engine.max_string_size { + if engine.max_string_size > 0 && s.len() > engine.max_string_size { Err(Box::new(EvalAltResult::ErrorDataTooLarge( "Length of string".to_string(), engine.max_string_size, - copy.len(), + s.len(), Position::none(), ))) } else { diff --git a/src/parser.rs b/src/parser.rs index 14c9d038..69289de0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -49,7 +49,7 @@ pub use crate::utils::ImmutableString; /// Compiled AST (abstract syntax tree) of a Rhai script. /// -/// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. +/// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. #[derive(Debug, Clone, Default)] pub struct AST( /// Global statements. @@ -59,7 +59,7 @@ pub struct AST( ); impl AST { - /// Create a new `AST`. + /// Create a new [`AST`]. pub fn new(statements: Vec, lib: Module) -> Self { Self(statements, lib) } @@ -95,16 +95,16 @@ impl AST { &self.1 } - /// Merge two `AST` into one. Both `AST`'s are untouched and a new, merged, version + /// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version /// is returned. /// - /// The second `AST` is simply appended to the end of the first _without any processing_. - /// Thus, the return value of the first `AST` (if using expression-statement syntax) is buried. - /// Of course, if the first `AST` uses a `return` statement at the end, then - /// the second `AST` will essentially be dead code. + /// The second [`AST`] is simply appended to the end of the first _without any processing_. + /// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried. + /// Of course, if the first [`AST`] uses a `return` statement at the end, then + /// the second [`AST`] will essentially be dead code. /// - /// All script-defined functions in the second `AST` overwrite similarly-named functions - /// in the first `AST` with the same number of parameters. + /// All script-defined functions in the second [`AST`] overwrite similarly-named functions + /// in the first [`AST`] with the same number of parameters. /// /// # Example /// @@ -157,13 +157,13 @@ impl AST { Self::new(ast, functions) } - /// Clear all function definitions in the `AST`. + /// Clear all function definitions in the [`AST`]. #[cfg(not(feature = "no_function"))] pub fn clear_functions(&mut self) { self.1 = Default::default(); } - /// Clear all statements in the `AST`, leaving only function definitions. + /// Clear all statements in the [`AST`], leaving only function definitions. #[cfg(not(feature = "no_function"))] pub fn retain_functions(&mut self) { self.0 = vec![]; @@ -802,22 +802,22 @@ fn parse_call_expr( mut modules: Option>, settings: ParseSettings, ) -> Result { - let (token, _) = input.peek().unwrap(); + let (token, token_pos) = input.peek().unwrap(); settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut args = StaticVec::new(); match token { - // id + // id( Token::EOF => { return Err(PERR::MissingToken( Token::RightParen.into(), format!("to close the arguments list of this function call '{}'", id), ) - .into_err(settings.pos)) + .into_err(*token_pos)) } - // id - Token::LexError(err) => return Err(err.into_err(settings.pos)), + // id( + Token::LexError(err) => return Err(err.into_err(*token_pos)), // id() Token::RightParen => { eat_token(input, Token::RightParen); @@ -1259,8 +1259,8 @@ fn parse_primary( state: &mut ParseState, mut settings: ParseSettings, ) -> Result { - let (token, pos1) = input.peek().unwrap(); - settings.pos = *pos1; + let (token, token_pos) = input.peek().unwrap(); + settings.pos = *token_pos; settings.ensure_level_within_max_limit(state.max_expr_depth)?; let (token, _) = match token { diff --git a/src/token.rs b/src/token.rs index 5cdc1771..4b19ab49 100644 --- a/src/token.rs +++ b/src/token.rs @@ -453,6 +453,22 @@ pub trait InputStream { fn peek_next(&mut self) -> Option; } +pub fn is_valid_identifier(name: impl Iterator) -> bool { + let mut first_alphabetic = false; + + for ch in name { + match ch { + '_' => (), + _ if char::is_ascii_alphabetic(&ch) => first_alphabetic = true, + _ if !first_alphabetic => return false, + _ if char::is_ascii_alphanumeric(&ch) => (), + _ => return false, + } + } + + first_alphabetic +} + /// Parse a string literal wrapped by `enclosing_char`. pub fn parse_string_literal( stream: &mut impl InputStream, @@ -783,13 +799,9 @@ fn get_next_token_inner( } } - let is_valid_identifier = result - .iter() - .find(|&ch| char::is_ascii_alphanumeric(ch)) // first alpha-numeric character - .map(char::is_ascii_alphabetic) // is a letter - .unwrap_or(false); // if no alpha-numeric at all - syntax error + let is_valid_identifier = is_valid_identifier(result.iter().cloned()); - let identifier: String = result.iter().collect(); + let identifier: String = result.into_iter().collect(); if !is_valid_identifier { return Some(( diff --git a/src/unsafe.rs b/src/unsafe.rs index 1d1ac545..fa5b271f 100644 --- a/src/unsafe.rs +++ b/src/unsafe.rs @@ -42,16 +42,6 @@ pub fn unsafe_cast_box(item: Box) -> Result, B } } -/// # DANGEROUS!!! -/// -/// A dangerous function that blindly casts a reference from one lifetime to another lifetime. -/// -/// Force-casting a a reference to another lifetime saves on allocations and string cloning, -/// but must be used with the utmost care. -pub fn unsafe_mut_cast_to_lifetime<'a, T>(value: &mut T) -> &'a mut T { - unsafe { mem::transmute::<_, &'a mut T>(value) } -} - /// # DANGEROUS!!! /// /// A dangerous function that blindly casts a `&str` from one lifetime to a `Cow` of diff --git a/tests/modules.rs b/tests/modules.rs index d7f6ad7c..b110fa8e 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -235,7 +235,7 @@ fn test_module_from_ast() -> Result<(), Box> { *engine .eval_with_scope::<()>(&mut scope, r#"import "testing" as ttt; ttt::hidden()"#) .expect_err("should error"), - EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "hidden" + EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "ttt::hidden" )); Ok(())