Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-05-28 14:09:27 +08:00
commit a9a95b3c2d
36 changed files with 2095 additions and 1474 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rhai" name = "rhai"
version = "0.14.2" version = "0.15.0"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"
@ -20,27 +20,23 @@ categories = [ "no-std", "embedded", "parser-implementations" ]
num-traits = { version = "0.2.11", default-features = false } num-traits = { version = "0.2.11", default-features = false }
[features] [features]
#default = ["no_stdlib", "no_function", "no_index", "no_object", "no_module", "no_float", "only_i32", "unchecked", "no_optimize", "sync"] #default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"]
default = [] default = []
plugins = [] plugins = []
unchecked = [] # unchecked arithmetic unchecked = [] # unchecked arithmetic
no_index = [] # no arrays and indexing sync = [] # restrict to only types that implement Send + Sync
no_float = [] # no floating-point
no_function = [] # no script-defined functions
no_object = [] # no custom objects
no_optimize = [] # no script optimizer no_optimize = [] # no script optimizer
no_module = [] # no modules no_float = [] # no floating-point
only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
sync = [] # restrict to only types that implement Send + Sync no_index = [] # no arrays and indexing
no_object = [] # no custom objects
no_function = [] # no script-defined functions
no_module = [] # no modules
# compiling for no-std # compiling for no-std
no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] no_std = [ "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ]
# other developer features
no_stdlib = [] # do not register the standard library
optimize_full = [] # set optimization level to Full (default is Simple) - this is a feature used only to simplify testing
[profile.release] [profile.release]
lto = "fat" lto = "fat"
codegen-units = 1 codegen-units = 1

400
README.md
View File

@ -11,7 +11,8 @@ Rhai - Embedded Scripting for Rust
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way
to add scripting to any application. to add scripting to any application.
Rhai's current features set: Features
--------
* Easy-to-use language similar to JS+Rust with dynamic typing but _no_ garbage collector. * Easy-to-use language similar to JS+Rust with dynamic typing but _no_ garbage collector.
* Tight integration with native Rust [functions](#working-with-functions) and [types](#custom-types-and-methods), * Tight integration with native Rust [functions](#working-with-functions) and [types](#custom-types-and-methods),
@ -23,20 +24,33 @@ Rhai's current features set:
* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to * 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_"`). one single source file, all with names starting with `"unsafe_"`).
* Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature). * 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 without explicit permission. * Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
* Rugged (protection against [stack-overflow](#maximum-call-stack-depth) and [runaway scripts](#maximum-number-of-operations) etc.). * Rugged (protection against [stack-overflow](#maximum-call-stack-depth) and [runaway scripts](#maximum-number-of-operations) etc.).
* Track script evaluation [progress](#tracking-progress) and manually terminate a script run. * Track script evaluation [progress](#tracking-progress) and manually terminate a script run.
* [`no-std`](#optional-features) support. * [`no-std`](#optional-features) support.
* [Function overloading](#function-overloading). * [Function overloading](#function-overloading).
* [Operator overloading](#operator-overloading). * [Operator overloading](#operator-overloading).
* Organize code base with dynamically-loadable [Modules]. * Organize code base with dynamically-loadable [Modules].
* Compiled script is [optimized](#script-optimization) for repeated evaluations. * Scripts are [optimized](#script-optimization) (useful for template-based machine-generated scripts) for repeated evaluations.
* Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features). * Support for [minimal builds](#minimal-builds) by excluding unneeded language [features](#optional-features).
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are
pulled in to provide for functionalities that used to be in `std`. pulled in to provide for functionalities that used to be in `std`.
**Note:** Currently, the version is 0.14.2, so the language and API's may change before they stabilize. **Note:** Currently, the version is 0.15.0, so the language and API's may change before they stabilize.
What Rhai doesn't do
--------------------
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 - definte your types in Rust instead; Rhai can seamless work with _any Rust type_.
* No first-class functions - Code your functions in Rust instead, and register them with Rhai.
* No closures - do your closure magic in Rust instead; [turn a Rhai scripted function into a Rust closure](#calling-rhai-functions-from-rust).
* It is best to expose an API in Rhai for scripts to call. All your core functionalities should be in Rust.
Installation Installation
------------ ------------
@ -45,7 +59,7 @@ Install the Rhai crate by adding this line to `dependencies`:
```toml ```toml
[dependencies] [dependencies]
rhai = "0.14.2" rhai = "0.15.0"
``` ```
Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/): Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/):
@ -55,7 +69,7 @@ Use the latest released crate version on [`crates.io`](https::/crates.io/crates/
rhai = "*" rhai = "*"
``` ```
Crate versions are released on [`crates.io`](https::/crates.io/crates/rhai/) infrequently, so if you want to track the 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: latest features, enhancements and bug fixes, pull directly from GitHub:
```toml ```toml
@ -68,19 +82,19 @@ Beware that in order to use pre-releases (e.g. alpha and beta), the exact versio
Optional features Optional features
----------------- -----------------
| Feature | Description | | Feature | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `unchecked` | Exclude arithmetic checking (such as over-flows and division by zero), stack depth limit and operations count limit. Beware that a bad script may panic the entire system! | | `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! |
| `no_function` | Disable script-defined functions. | | `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_index` | Disable [arrays] and indexing features. | | `no_optimize` | Disable the script optimizer. |
| `no_object` | Disable support for custom types and object maps. | | `no_float` | Disable floating-point numbers and math. |
| `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`. |
| `no_optimize` | Disable the script optimizer. | | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
| `no_module` | Disable modules. | | `no_index` | Disable [arrays] and indexing features. |
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | | `no_object` | Disable support for custom types and [object maps]. |
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | | `no_function` | Disable script-defined functions. |
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | | `no_module` | Disable loading modules. |
| `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_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
By default, Rhai includes all the standard functionalities in a small, tight package. 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. Most features are here to opt-**out** of certain functionalities that are not needed.
@ -88,16 +102,16 @@ Excluding unneeded functionalities can result in smaller, faster builds
as well as more control over what a script can (or cannot) do. as well as more control over what a script can (or cannot) do.
[`unchecked`]: #optional-features [`unchecked`]: #optional-features
[`no_index`]: #optional-features [`sync`]: #optional-features
[`no_float`]: #optional-features
[`no_function`]: #optional-features
[`no_object`]: #optional-features
[`no_optimize`]: #optional-features [`no_optimize`]: #optional-features
[`no_module`]: #optional-features [`no_float`]: #optional-features
[`only_i32`]: #optional-features [`only_i32`]: #optional-features
[`only_i64`]: #optional-features [`only_i64`]: #optional-features
[`no_index`]: #optional-features
[`no_object`]: #optional-features
[`no_function`]: #optional-features
[`no_module`]: #optional-features
[`no_std`]: #optional-features [`no_std`]: #optional-features
[`sync`]: #optional-features
### Performance builds ### Performance builds
@ -133,9 +147,9 @@ Omitting arrays (`no_index`) yields the most code-size savings, followed by floa
(`no_float`), checked arithmetic (`unchecked`) and finally object maps and custom types (`no_object`). (`no_float`), checked arithmetic (`unchecked`) and finally object maps and custom types (`no_object`).
Disable script-defined functions (`no_function`) only when the feature is not needed because code size savings is minimal. Disable script-defined functions (`no_function`) only when the feature is not needed because code size savings is minimal.
[`Engine::new_raw`](#raw-engine) creates a _raw_ engine which does not register _any_ utility functions. [`Engine::new_raw`](#raw-engine) creates a _raw_ engine.
This makes the scripting language quite useless as even basic arithmetic operators are not supported. A _raw_ engine supports, out of the box, only a very [restricted set](#built-in-operators) of basic arithmetic and logical operators.
Selectively include the necessary functionalities by loading specific [packages] to minimize the footprint. 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. Packages are sharable (even across threads via the [`sync`] feature), so they only have to be created once.
Related Related
@ -168,7 +182,7 @@ Examples can be run with the following command:
cargo run --example name cargo run --example name
``` ```
The `repl` example is a particularly good one as it allows you to interactively try out Rhai's 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). language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop).
Example Scripts Example Scripts
@ -376,7 +390,20 @@ Raw `Engine`
`Engine::new` creates a scripting [`Engine`] with common functionalities (e.g. printing to the console via `print`). `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. In many controlled embedded environments, however, these are not needed.
Use `Engine::new_raw` to create a _raw_ `Engine`, in which _nothing_ is added, not even basic arithmetic and logic operators! 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` |
### Packages ### Packages
@ -400,20 +427,20 @@ engine.load_package(package.get()); // load the package manually. 'g
The follow packages are available: The follow packages are available:
| Package | Description | In `CorePackage` | In `StandardPackage` | | Package | Description | In `CorePackage` | In `StandardPackage` |
| ---------------------- | ----------------------------------------------- | :--------------: | :------------------: | | ---------------------- | ------------------------------------------------------------------------------------------------------ | :--------------: | :------------------: |
| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) | Yes | Yes | | `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 | | `BasicIteratorPackage` | Numeric ranges (e.g. `range(1, 10)`) | Yes | Yes |
| `LogicPackage` | Logic and comparison operators (e.g. `==`, `>`) | 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 | 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 | | `BasicTimePackage` | Basic time functions (e.g. [timestamps]) | Yes | Yes |
| `MoreStringPackage` | Additional string functions | No | Yes | | `MoreStringPackage` | Additional string functions, including converting common types to string | No | Yes |
| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes | | `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes |
| `BasicArrayPackage` | Basic [array] functions | No | Yes | | `BasicArrayPackage` | Basic [array] functions (not available under `no_index`) | No | Yes |
| `BasicMapPackage` | Basic [object map] functions | No | Yes | | `BasicMapPackage` | Basic [object map] functions (not available under `no_object`) | No | Yes |
| `EvalPackage` | Disable [`eval`] | No | No | | `EvalPackage` | Disable [`eval`] | No | No |
| `CorePackage` | Basic essentials | | | | `CorePackage` | Basic essentials | Yes | Yes |
| `StandardPackage` | Standard library | | | | `StandardPackage` | Standard library | No | Yes |
Packages typically contain Rust functions that are callable within a Rhai script. 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). All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers).
@ -458,6 +485,7 @@ Values and types
[`type_of()`]: #values-and-types [`type_of()`]: #values-and-types
[`to_string()`]: #values-and-types [`to_string()`]: #values-and-types
[`()`]: #values-and-types [`()`]: #values-and-types
[standard types]: #values-and-types
The following primitive types are supported natively: The following primitive types are supported natively:
@ -467,14 +495,14 @@ The following primitive types are supported natively:
| **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. | | **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. |
| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` | | **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
| **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. | | **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. |
| **Unicode string** | `String` (_not_ `&str`) | `"string"` | `"hello"` etc. | | **Immutable Unicode string** | `rhai::ImmutableString` (implemented as `Rc<String>` or `Arc<String>`, _not_ `&str`) | `"string"` | `"hello"` etc. |
| **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | | **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
| **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` | | **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` |
| **Timestamp** (implemented in the [`BasicTimePackage`](#packages)) | `std::time::Instant` | `"timestamp"` | _not supported_ | | **Timestamp** (implemented in the [`BasicTimePackage`](#packages)) | `std::time::Instant` | `"timestamp"` | _not supported_ |
| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | | **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 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. | | **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 you want to call it) | `()` | `"()"` | `""` _(empty string)_ | | **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 - 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. they even cannot be added together. This is very similar to Rust.
@ -487,6 +515,10 @@ This is useful on some 32-bit targets where using 64-bit integers incur a perfor
If no floating-point is needed or supported, use the [`no_float`] feature to remove it. If no floating-point is needed or supported, use the [`no_float`] feature to remove it.
[Strings] in Rhai are _immutable_, meaning that they can be shared but not modified. In actual, the `ImmutableString` type
is an alias to `Rc<String>` or `Arc<String>` (depending on the [`sync`] feature).
Any modification done to a Rhai string will cause the string to be cloned and the modifications made to the copy.
The `to_string` function converts a standard type into a [string] for display purposes. The `to_string` function converts a standard type into a [string] for display purposes.
The `type_of` function detects the actual type of a value. This is useful because all variables are [`Dynamic`] in nature. The `type_of` function detects the actual type of a value. This is useful because all variables are [`Dynamic`] in nature.
@ -562,7 +594,7 @@ let value: i64 = item.cast(); // type can also be inferred
let value = item.try_cast::<i64>().unwrap(); // 'try_cast' does not panic when the cast fails, but returns 'None' let value = item.try_cast::<i64>().unwrap(); // 'try_cast' does not panic when the cast fails, but returns 'None'
``` ```
The `type_name` method gets the name of the actual type as a static string slice, which you may match against. The `type_name` method gets the name of the actual type as a static string slice, which can be `match`-ed against.
```rust ```rust
let list: Array = engine.eval("...")?; // return type is 'Array' let list: Array = engine.eval("...")?; // return type is 'Array'
@ -581,6 +613,7 @@ The following conversion traits are implemented for `Dynamic`:
* `From<i64>` (`i32` if [`only_i32`]) * `From<i64>` (`i32` if [`only_i32`])
* `From<f64>` (if not [`no_float`]) * `From<f64>` (if not [`no_float`])
* `From<bool>` * `From<bool>`
* `From<rhai::ImmutableString>`
* `From<String>` * `From<String>`
* `From<char>` * `From<char>`
* `From<Vec<T>>` (into an [array]) * `From<Vec<T>>` (into an [array])
@ -611,13 +644,12 @@ Traits
A number of traits, under the `rhai::` module namespace, provide additional functionalities. A number of traits, under the `rhai::` module namespace, provide additional functionalities.
| Trait | Description | Methods | | Trait | Description | Methods |
| ------------------- | -------------------------------------------------------------------------------------- | --------------------------------------- | | ------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------- |
| `RegisterFn` | Trait for registering functions | `register_fn` | | `RegisterFn` | Trait for registering functions | `register_fn` |
| `RegisterDynamicFn` | Trait for registering functions returning [`Dynamic`] | `register_dynamic_fn` | | `RegisterResultFn` | Trait for registering fallible functions returning `Result<Dynamic, Box<EvalAltResult>>` | `register_result_fn` |
| `RegisterResultFn` | Trait for registering fallible functions returning `Result<`_T_`, Box<EvalAltResult>>` | `register_result_fn` | | `Func` | Trait for creating anonymous functions from script | `create_from_ast`, `create_from_script` |
| `Func` | Trait for creating anonymous functions from script | `create_from_ast`, `create_from_script` | | `ModuleResolver` | Trait implemented by module resolution services | `resolve` |
| `ModuleResolver` | Trait implemented by module resolution services | `resolve` |
Working with functions Working with functions
---------------------- ----------------------
@ -628,16 +660,16 @@ To call these functions, they need to be registered with the [`Engine`].
```rust ```rust
use rhai::{Dynamic, Engine, EvalAltResult}; use rhai::{Dynamic, Engine, EvalAltResult};
use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn' use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn'
use rhai::{Dynamic, RegisterDynamicFn}; // use 'RegisterDynamicFn' trait for 'register_dynamic_fn' use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn'
// Normal function // Normal function that returns any value type
fn add(x: i64, y: i64) -> i64 { fn add(x: i64, y: i64) -> i64 {
x + y x + y
} }
// Function that returns a Dynamic value // Function that returns a 'Dynamic' value - must return a 'Result'
fn get_an_any() -> Dynamic { fn get_any_value() -> Result<Dynamic, Box<EvalAltResult>> {
Dynamic::from(42_i64) Ok((42_i64).into()) // standard types can use 'into()'
} }
fn main() -> Result<(), Box<EvalAltResult>> fn main() -> Result<(), Box<EvalAltResult>>
@ -650,10 +682,10 @@ fn main() -> Result<(), Box<EvalAltResult>>
println!("Answer: {}", result); // prints 42 println!("Answer: {}", result); // prints 42
// Functions that return Dynamic values must use register_dynamic_fn() // Functions that return Dynamic values must use register_result_fn()
engine.register_dynamic_fn("get_an_any", get_an_any); engine.register_result_fn("get_any_value", get_any_value);
let result = engine.eval::<i64>("get_an_any()")?; let result = engine.eval::<i64>("get_any_value()")?;
println!("Answer: {}", result); // prints 42 println!("Answer: {}", result); // prints 42
@ -661,18 +693,15 @@ fn main() -> Result<(), Box<EvalAltResult>>
} }
``` ```
To return a [`Dynamic`] value from a Rust function, use the `Dynamic::from` method. To create a [`Dynamic`] value, use the `Dynamic::from` method.
[Standard types] in Rhai can also use `into()`.
```rust ```rust
use rhai::Dynamic; use rhai::Dynamic;
fn decide(yes_no: bool) -> Dynamic { let x = (42_i64).into(); // 'into()' works for standard types
if yes_no {
Dynamic::from(42_i64) let y = Dynamic::from(String::from("hello!")); // remember &str is not supported by Rhai
} else {
Dynamic::from(String::from("hello!")) // remember &str is not supported by Rhai
}
}
``` ```
Generic functions Generic functions
@ -709,7 +738,7 @@ Fallible functions
If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be registered with `register_result_fn` If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be registered with `register_result_fn`
(using the `RegisterResultFn` trait). (using the `RegisterResultFn` trait).
The function must return `Result<_, Box<EvalAltResult>>`. `Box<EvalAltResult>` implements `From<&str>` and `From<String>` etc. The function must return `Result<Dynamic, Box<EvalAltResult>>`. `Box<EvalAltResult>` implements `From<&str>` and `From<String>` etc.
and the error text gets converted into `Box<EvalAltResult::ErrorRuntime>`. and the error text gets converted into `Box<EvalAltResult::ErrorRuntime>`.
The error values are `Box`-ed in order to reduce memory footprint of the error path, which should be hit rarely. The error values are `Box`-ed in order to reduce memory footprint of the error path, which should be hit rarely.
@ -718,13 +747,13 @@ The error values are `Box`-ed in order to reduce memory footprint of the error p
use rhai::{Engine, EvalAltResult, Position}; use rhai::{Engine, EvalAltResult, Position};
use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn' use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn'
// Function that may fail // Function that may fail - the result type must be 'Dynamic'
fn safe_divide(x: i64, y: i64) -> Result<i64, Box<EvalAltResult>> { fn safe_divide(x: i64, y: i64) -> Result<Dynamic, Box<EvalAltResult>> {
if y == 0 { if y == 0 {
// Return an error if y is zero // Return an error if y is zero
Err("Division by zero!".into()) // short-cut to create Box<EvalAltResult::ErrorRuntime> Err("Division by zero!".into()) // short-cut to create Box<EvalAltResult::ErrorRuntime>
} else { } else {
Ok(x / y) Ok((x / y).into()) // convert result into 'Dynamic'
} }
} }
@ -799,7 +828,7 @@ let result: i64 = engine.eval("1 + 1.0"); // prints 2.0 (normally an e
``` ```
Use operator overloading for custom types (described below) only. Use operator overloading for custom types (described below) only.
Be very careful when overloading built-in operators because script writers expect standard operators to behave in a Be very careful when overloading 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. 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`]. Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`].
@ -808,7 +837,7 @@ See the [relevant section](#script-optimization) for more details.
Custom types and methods Custom types and methods
----------------------- -----------------------
Here's an more complete example of working with Rust. First the example, then we'll break it into parts: A more complete example of working with Rust:
```rust ```rust
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult};
@ -846,8 +875,8 @@ fn main() -> Result<(), Box<EvalAltResult>>
} }
``` ```
All custom types must implement `Clone`. This allows the [`Engine`] to pass by value. All custom types must implement `Clone` as this allows the [`Engine`] to pass by value.
You can turn off support for custom types via the [`no_object`] feature. Support for custom types can be turned off via the [`no_object`] feature.
```rust ```rust
#[derive(Clone)] #[derive(Clone)]
@ -856,11 +885,12 @@ struct TestStruct {
} }
``` ```
Next, we create a few methods that we'll later use in our scripts. Notice that we register our custom type with the [`Engine`]. Next, create a few methods for later use in scripts.
Notice that the custom type needs to be _registered_ with the [`Engine`].
```rust ```rust
impl TestStruct { impl TestStruct {
fn update(&mut self) { fn update(&mut self) { // methods take &mut as first parameter
self.field += 41; self.field += 41;
} }
@ -874,19 +904,19 @@ let engine = Engine::new();
engine.register_type::<TestStruct>(); engine.register_type::<TestStruct>();
``` ```
To use native types, methods and functions with the [`Engine`], we need to register them. To use native types, methods and functions with the [`Engine`], simply register them using one of the `Engine::register_XXX` API.
There are some convenience functions to help with these. Below, the `update` and `new` methods are registered with the [`Engine`]. Below, the `update` and `new` methods are registered using `Engine::register_fn`.
*Note: [`Engine`] follows the convention that methods use a `&mut` first parameter so that invoking methods ***Note**: Rhai follows the convention that methods of custom types take a `&mut` first parameter so that invoking methods
can update the value in memory.* can update the custom types. All other parameters in Rhai are passed by value (i.e. clones).*
```rust ```rust
engine.register_fn("update", TestStruct::update); // registers 'update(&mut TestStruct)' engine.register_fn("update", TestStruct::update); // registers 'update(&mut TestStruct)'
engine.register_fn("new_ts", TestStruct::new); // registers 'new()' engine.register_fn("new_ts", TestStruct::new); // registers 'new()'
``` ```
Finally, we call our script. The script can see the function and method we registered earlier. The custom type is then ready for us in scripts. Scripts can see the functions and methods registered earlier.
We need to get the result back out from script land just as before, this time casting to our custom struct type. Get the evaluation result back out from script-land just as before, this time casting to the custom type:
```rust ```rust
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?; let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
@ -894,18 +924,19 @@ let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
println!("result: {}", result.field); // prints 42 println!("result: {}", result.field); // prints 42
``` ```
In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method call In fact, any function with a first argument that is a `&mut` reference can be used as method calls because
on that type because internally they are the same thing: internally they are the same thing: methods on a type is implemented as a functions taking a `&mut` first argument.
methods on a type is implemented as a functions taking a `&mut` first argument.
```rust ```rust
fn foo(ts: &mut TestStruct) -> i64 { fn foo(ts: &mut TestStruct) -> i64 {
ts.field ts.field
} }
engine.register_fn("foo", foo); engine.register_fn("foo", foo); // register ad hoc function with correct signature
let result = engine.eval::<i64>("let x = new_ts(); x.foo()")?; let result = engine.eval::<i64>(
"let x = new_ts(); x.foo()" // 'foo' can be called like a method on 'x'
)?;
println!("result: {}", result); // prints 1 println!("result: {}", result); // prints 1
``` ```
@ -919,7 +950,7 @@ let result = engine.eval::<i64>("let x = [1, 2, 3]; x.len()")?;
``` ```
[`type_of()`] works fine with custom types and returns the name of the type. [`type_of()`] works fine with custom types and returns the name of the type.
If `register_type_with_name` is used to register the custom 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. with a special "pretty-print" name, [`type_of()`] will return that name instead.
```rust ```rust
@ -942,20 +973,23 @@ Similarly, custom types can expose members by registering a `get` and/or `set` f
```rust ```rust
#[derive(Clone)] #[derive(Clone)]
struct TestStruct { struct TestStruct {
field: i64 field: String
} }
// Remember Rhai uses 'ImmutableString' instead of 'String'
impl TestStruct { impl TestStruct {
fn get_field(&mut self) -> i64 { fn get_field(&mut self) -> ImmutableString {
self.field // Make an 'ImmutableString' from a 'String'
self.field.into(0)
} }
fn set_field(&mut self, new_val: i64) { fn set_field(&mut self, new_val: ImmutableString) {
self.field = new_val; // Get a 'String' from an 'ImmutableString'
self.field = (*new_val).clone();
} }
fn new() -> Self { fn new() -> Self {
TestStruct { field: 1 } TestStruct { field: "hello" }
} }
} }
@ -966,7 +1000,8 @@ engine.register_type::<TestStruct>();
engine.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field); engine.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field);
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
let result = engine.eval::<i64>("let a = new_ts(); a.xyz = 42; a.xyz")?; // Return result can be 'String' - Rhai will automatically convert it from 'ImmutableString'
let result = engine.eval::<String>(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?;
println!("Answer: {}", result); // prints 42 println!("Answer: {}", result); // prints 42
``` ```
@ -1008,7 +1043,7 @@ println!("Answer: {}", result); // prints 42
Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set` Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set`
and `register_indexer` are not available when the [`no_object`] feature is turned on. and `register_indexer` are not available when the [`no_object`] feature is turned on.
`register_indexer` is also not available when the [`no_index`] feature is turned on. `register_indexer` is also not available when the [`no_index`] feature is turned on.
`Scope` - Initializing and maintaining state `Scope` - Initializing and maintaining state
------------------------------------------- -------------------------------------------
@ -1241,7 +1276,7 @@ number = -5 - +5;
Numeric functions Numeric functions
----------------- -----------------
The following standard functions (defined in the [`BasicMathPackage`] but excluded if using a [raw `Engine`]) operate on The following standard functions (defined in the [`BasicMathPackage`](#packages) but excluded if using a [raw `Engine`]) operate on
`i8`, `i16`, `i32`, `i64`, `f32` and `f64` only: `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only:
| Function | Description | | Function | Description |
@ -1299,6 +1334,9 @@ Unicode characters.
Individual characters within a Rhai string can also be replaced just as if the string is an array of 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. In Rhai, there is also no separate concepts of `String` and `&str` as in Rust.
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.
Strings can be built up from other strings and types via the `+` operator (provided by the [`MoreStringPackage`](#packages) Strings can be built up from other strings and types via the `+` operator (provided by the [`MoreStringPackage`](#packages)
but excluded if using a [raw `Engine`]). This is particularly useful when printing output. but excluded if using a [raw `Engine`]). This is particularly useful when printing output.
@ -1352,19 +1390,19 @@ record == "Bob X. Davis: age 42 ❤\n";
The following standard methods (defined in the [`MoreStringPackage`](#packages) but excluded if using a [raw `Engine`]) operate on strings: The following standard methods (defined in the [`MoreStringPackage`](#packages) but excluded if using a [raw `Engine`]) operate on strings:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | | ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `len` | _none_ | returns the number of characters (not number of bytes) in the string | | `len` | _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 | | `pad` | character to pad, target length | pads the string with an character to at least a specified length |
| `append` | character/string to append | Adds a character or a string to the end of another string | | `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string |
| `clear` | _none_ | empties the string | | `clear` | _none_ | empties the string |
| `truncate` | target length | cuts off the string at exactly a specified number of characters | | `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 | | `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 | | `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) | | `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) | | `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 | | `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 | | `trim` | _none_ | trims the string of whitespace at the beginning and end |
### Examples ### Examples
@ -1427,19 +1465,19 @@ Arrays are disabled via the [`no_index`] feature.
The following methods (defined in the [`BasicArrayPackage`](#packages) but excluded if using a [raw `Engine`]) operate on arrays: The following methods (defined in the [`BasicArrayPackage`](#packages) but excluded if using a [raw `Engine`]) operate on arrays:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | ----------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `push` | element to insert | inserts an element at the end | | `push` | element to insert | inserts an element at the end |
| `append` | array to append | concatenates the second array to the end of the first | | `+=` 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 | | `+` operator | first array, second array | concatenates the first array with the second |
| `insert` | element to insert, position<br/>(beginning if <= 0, end if >= length) | insert an element at a certain index | | `insert` | element to insert, position<br/>(beginning if <= 0, end if >= length) | insert an element at a certain index |
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | | `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
| `shift` | _none_ | removes the first 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 | | `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
| `len` | _none_ | returns the number of elements | | `len` | _none_ | returns the number of elements |
| `pad` | element to pad, target length | pads the array with an element to at least a specified length | | `pad` | element to pad, target length | pads the array with an element to at least a specified length |
| `clear` | _none_ | empties the array | | `clear` | _none_ | empties the array |
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | | `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
### Examples ### Examples
@ -1549,16 +1587,16 @@ Object maps are disabled via the [`no_object`] feature.
The following methods (defined in the [`BasicMapPackage`](#packages) but excluded if using a [raw `Engine`]) operate on object maps: The following methods (defined in the [`BasicMapPackage`](#packages) but excluded if using a [raw `Engine`]) operate on object maps:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ------------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `has` | property name | does the object map contain a property of a particular name? | | `has` | property name | does the object map contain a property of a particular name? |
| `len` | _none_ | returns the number of properties | | `len` | _none_ | returns the number of properties |
| `clear` | _none_ | empties the object map | | `clear` | _none_ | empties the object map |
| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) | | `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) |
| `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, `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 | | `+` 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`] | | `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`] | | `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |
### Examples ### Examples
@ -1704,10 +1742,10 @@ if now.elapsed() > 30.0 {
Comparison operators Comparison operators
-------------------- --------------------
Comparing most values of the same data type work out-of-the-box for standard types supported by the system. 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`], comparisons can only be made between restricted system types - However, if using a [raw `Engine`] without loading any [packages], comparisons can only be made between a limited
`INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), [string], [array], `bool`, `char`. set of types (see [built-in operators](#built-in-operators)).
```rust ```rust
42 == 42; // true 42 == 42; // true
@ -2272,7 +2310,7 @@ so that it does not consume more resources that it is allowed to.
The most important resources to watch out for are: The most important resources to watch out for are:
* **Memory**: A malignant script may continuously grow an [array] or [object map] until all memory is consumed. * **Memory**: A malignant script may continuously grow an [array] or [object map] until all memory is consumed.
It may also create a large [array] or [objecct map] literal that exhausts all memory during parsing. It may also create a large [array] or [object map] literal that exhausts all memory during parsing.
* **CPU**: A malignant script may run an infinite tight loop that consumes all CPU cycles. * **CPU**: A malignant script may run an infinite tight loop that consumes all CPU cycles.
* **Time**: A malignant script may run indefinitely, thereby blocking the calling system which is waiting for a result. * **Time**: A malignant script may run indefinitely, thereby blocking the calling system which is waiting for a result.
* **Stack**: A malignant script may attempt an infinite recursive call that exhausts the call stack. * **Stack**: A malignant script may attempt an infinite recursive call that exhausts the call stack.
@ -2349,6 +2387,10 @@ engine.set_max_modules(5); // allow loading only up to 5 module
engine.set_max_modules(0); // allow unlimited modules engine.set_max_modules(0); // allow unlimited modules
``` ```
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).
### Maximum call stack depth ### Maximum call stack depth
Rhai by default limits function calls to a maximum depth of 128 levels (16 levels in debug build). Rhai by default limits function calls to a maximum depth of 128 levels (16 levels in debug build).
@ -2370,6 +2412,8 @@ engine.set_max_call_levels(0); // allow no function calls at all (m
``` ```
A script exceeding the maximum call stack depth will terminate with an error result. 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).
### Maximum statement depth ### Maximum statement depth
@ -2413,15 +2457,17 @@ Make sure that `C x ( 5 + F ) + S` layered calls do not cause a stack overflow,
A script exceeding the maximum nesting depths will terminate with a parsing error. A script exceeding the maximum nesting depths will terminate with a parsing error.
The malignant `AST` will not be able to get past parsing in the first place. The malignant `AST` will not be able to get past parsing in the first place.
The limits can be disabled via the [`unchecked`] feature for higher performance This check can be disabled via the [`unchecked`] feature for higher performance
(but higher risks as well). (but higher risks as well).
### Checked arithmetic ### Checked arithmetic
By default, all arithmetic calculations in Rhai are _checked_, meaning that the script terminates 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 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 floating-point operation, instead of crashing the entire system.
via the [`unchecked`] feature for higher performance (but higher risks as well).
This checking can be turned off via the [`unchecked`] feature for higher performance
(but higher risks as well).
### Blocking access to external data ### Blocking access to external data
@ -2437,7 +2483,6 @@ engine.register_get("add", add); // configure 'engine'
let engine = engine; // shadow the variable so that 'engine' is now immutable let engine = engine; // shadow the variable so that 'engine' is now immutable
``` ```
Script optimization Script optimization
=================== ===================
@ -2455,7 +2500,7 @@ For example, in the following:
123; // eliminated: no effect 123; // eliminated: no effect
"hello"; // eliminated: no effect "hello"; // eliminated: no effect
[1, 2, x, x*2, 5]; // eliminated: no effect [1, 2, x, x*2, 5]; // eliminated: no effect
foo(42); // NOT eliminated: the function 'foo' may have side effects foo(42); // NOT eliminated: the function 'foo' may have side-effects
666 // NOT eliminated: this is the return value of the block, 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 // and the block is the last one so this is the return value of the whole script
} }
@ -2506,7 +2551,7 @@ if DECISION == 1 { // NOT optimized away because you can define
} }
``` ```
because no operator functions will be run (in order not to trigger side effects) during the optimization process 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: (unless the optimization level is set to [`OptimizationLevel::Full`]). So, instead, do this:
```rust ```rust
@ -2528,7 +2573,7 @@ if DECISION_1 {
In general, boolean constants are most effective for the optimizer to automatically prune In general, boolean constants are most effective for the optimizer to automatically prune
large `if`-`else` branches because they do not depend on operators. large `if`-`else` branches because they do not depend on operators.
Alternatively, turn the optimizer to [`OptimizationLevel::Full`] Alternatively, turn the optimizer to [`OptimizationLevel::Full`].
Here be dragons! Here be dragons!
================ ================
@ -2544,7 +2589,7 @@ There are actually three levels of optimizations: `None`, `Simple` and `Full`.
* `None` is obvious - no optimization on the AST is performed. * `None` is obvious - no optimization on the AST is performed.
* `Simple` (default) performs relatively _safe_ optimizations without causing side effects * `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). (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. * `Full` is _much_ more aggressive, _including_ running functions on constant arguments to determine their result.
@ -2599,28 +2644,32 @@ 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' let y = (1>2) || (3<=4); // <- will be replaced by 'let y = true'
``` ```
Function side effect considerations Side-effect considerations
---------------------------------- --------------------------
All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_ (i.e. they do not mutate state 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 side any effects, with the exception of `print` and `debug` which are handled specially) so using 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_ you register your own types and functions. [`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, they _may_ be called (or maybe not, if the calls happen to lie within a pruned code block).
If custom functions are registered to replace built-in operators, they will also be called when the operators are used 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) and cause side-effects. (in an `if` statement, for example) causing side-effects.
Function volatility considerations 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.
Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ Volatility considerations
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`], _assumes_ that
all functions are _pure_, so when it finds constant arguments it will eagerly execute the function call.
This causes the script to behave differently from the intended semantics because essentially the result of the function call
will always be the same value.
Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions. 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.
Subtle semantic changes Subtle semantic changes
----------------------- -----------------------
@ -2655,7 +2704,7 @@ print("end!");
In the script above, if `my_decision` holds anything other than a boolean value, the script should have been terminated due to 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 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. no side-effects), thus the script silently runs to completion without errors.
Turning off optimizations Turning off optimizations
------------------------- -------------------------
@ -2670,6 +2719,8 @@ let engine = rhai::Engine::new();
engine.set_optimization_level(rhai::OptimizationLevel::None); engine.set_optimization_level(rhai::OptimizationLevel::None);
``` ```
Alternatively, turn off optimizations via the [`no_optimize`] feature.
`eval` - or "How to Shoot Yourself in the Foot even Easier" `eval` - or "How to Shoot Yourself in the Foot even Easier"
--------------------------------------------------------- ---------------------------------------------------------
@ -2718,8 +2769,8 @@ x += 32;
print(x); print(x);
``` ```
For those who subscribe to the (very sensible) motto of ["`eval` is **evil**"](http://linterrors.com/js/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 overriding it, probably with something that throws. disable `eval` by overloading it, probably with something that throws.
```rust ```rust
fn eval(script) { throw "eval is evil! I refuse to run " + script } fn eval(script) { throw "eval is evil! I refuse to run " + script }
@ -2727,7 +2778,7 @@ fn eval(script) { throw "eval is evil! I refuse to run " + script }
let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run 40 + 2" let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run 40 + 2"
``` ```
Or override it from Rust: Or overload it from Rust:
```rust ```rust
fn alt_eval(script: String) -> Result<(), Box<EvalAltResult>> { fn alt_eval(script: String) -> Result<(), Box<EvalAltResult>> {
@ -2737,4 +2788,15 @@ fn alt_eval(script: String) -> Result<(), Box<EvalAltResult>> {
engine.register_result_fn("eval", alt_eval); engine.register_result_fn("eval", alt_eval);
``` ```
There is even a [package] named `EvalPackage` which implements the disabling override. There is even a package named [`EvalPackage`](#packages) 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
```

View File

@ -4,15 +4,45 @@ Rhai Release Notes
Version 0.14.2 Version 0.14.2
============== ==============
Regression Regression fix
---------- --------------
* Do not optimize script with `eval_expression` - it is assumed to be one-off and short. * Do not optimize script with `eval_expression` - it is assumed to be one-off and short.
Breaking changes
----------------
* `Engine::compile_XXX` functions now return `ParseError` instead of `Box<ParseError>`.
* The `RegisterDynamicFn` trait is merged into the `RegisterResutlFn` trait which now always returns
`Result<Dynamic, Box<EvalAltResult>>`.
* Default maximum limit on levels of nested function calls is fine-tuned and set to a different value.
* Some operator functions are now built in (see _Speed enhancements_ below), so they are available even
under `Engine::new_raw`.
* Strings are now immutable. The type `rhai::ImmutableString` is used instead of `std::string::String`.
This is to avoid excessive cloning of strings. All native-Rust functions taking string parameters
should switch to `rhai::ImmutableString` (which is either `Rc<String>` or `Arc<String>` depending on
whether the `sync` feature is used).
New features New features
------------ ------------
* Set limits on maximum level of nesting expressions and statements to avoid panics during parsing. * Set limit on maximum level of nesting expressions and statements to avoid panics during parsing.
* New `EvalPackage` to disable `eval`.
* More benchmarks.
Speed enhancements
------------------
* Common operators (e.g. `+`, `>`, `==`) now call into highly efficient built-in implementations for standard types
(i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function.
This yields a 5-10% speed benefit depending on script operator usage. Scripts running tight loops will see
significant speed-up.
* Common assignment operators (e.g. `+=`, `%=`) now call into highly efficient built-in implementations for
standard types (i.e. `INT`, `FLOAT`, `bool`, `char`, `()` and `ImmutableString`) if not overridden by a registered function.
* Implementations of common operators for standard types are removed from the `ArithmeticPackage` and `LogicPackage`
(and therefore the `CorePackage`) because they are now always available, even under `Engine::new_raw`.
* Operator-assignment statements (e.g. `+=`) are now handled directly and much faster.
* Strings are now _immutable_ and use the `rhai::ImmutableString` type, eliminating large amounts of cloning.
Version 0.14.1 Version 0.14.1

View File

@ -29,7 +29,7 @@ fn bench_engine_new_raw_core(bench: &mut Bencher) {
#[bench] #[bench]
fn bench_engine_register_fn(bench: &mut Bencher) { fn bench_engine_register_fn(bench: &mut Bencher) {
fn hello(a: INT, b: Array, c: Map) -> bool { fn hello(_a: INT, _b: Array, _c: Map) -> bool {
true true
} }

View File

@ -86,7 +86,7 @@ fn bench_eval_call_expression(bench: &mut Bencher) {
modifierTest + 1000 / 2 > (80 * 100 % 2) modifierTest + 1000 / 2 > (80 * 100 % 2)
"#; "#;
let mut engine = Engine::new(); let engine = Engine::new();
bench.iter(|| engine.eval_expression::<bool>(script).unwrap()); bench.iter(|| engine.eval_expression::<bool>(script).unwrap());
} }
@ -101,7 +101,58 @@ fn bench_eval_call(bench: &mut Bencher) {
modifierTest + 1000 / 2 > (80 * 100 % 2) modifierTest + 1000 / 2 > (80 * 100 % 2)
"#; "#;
let mut engine = Engine::new(); let engine = Engine::new();
bench.iter(|| engine.eval::<bool>(script).unwrap()); bench.iter(|| engine.eval::<bool>(script).unwrap());
} }
#[bench]
fn bench_eval_loop_number(bench: &mut Bencher) {
let script = r#"
let s = 0;
for x in range(0, 10000) {
s += 1;
}
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_loop_strings_build(bench: &mut Bencher) {
let script = r#"
let s = 0;
for x in range(0, 10000) {
s += "x";
}
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_loop_strings_no_build(bench: &mut Bencher) {
let script = r#"
let s = "hello";
for x in range(0, 10000) {
s += "";
}
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}

View File

@ -12,7 +12,7 @@ fn bench_iterations_1000(bench: &mut Bencher) {
let x = 1_000; let x = 1_000;
while x > 0 { while x > 0 {
x = x - 1; x -= 1;
} }
"#; "#;

View File

@ -6,7 +6,7 @@ let x = 10;
loop { loop {
print(x); print(x);
x = x - 1; x -= 1;
if x <= 0 { break; } if x <= 0 { break; }
} }

View File

@ -7,7 +7,7 @@ let x = 1_000_000;
print("Ready... Go!"); print("Ready... Go!");
while x > 0 { while x > 0 {
x = x - 1; x -= 1;
} }
print("Finished. Run time = " + now.elapsed() + " seconds."); print("Finished. Run time = " + now.elapsed() + " seconds.");

View File

@ -4,5 +4,5 @@ let x = 10;
while x > 0 { while x > 0 {
print(x); print(x);
x = x - 1; x -= 1;
} }

View File

@ -1,6 +1,6 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling. //! Helper module which defines the `Any` trait to to allow dynamic value handling.
use crate::parser::INT; use crate::parser::{ImmutableString, INT};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -151,7 +151,7 @@ pub struct Dynamic(pub(crate) Union);
pub enum Union { pub enum Union {
Unit(()), Unit(()),
Bool(bool), Bool(bool),
Str(Box<String>), Str(ImmutableString),
Char(char), Char(char),
Int(INT), Int(INT),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -178,6 +178,10 @@ impl Dynamic {
/// Is the value held by this `Dynamic` a particular type? /// Is the value held by this `Dynamic` a particular type?
pub fn is<T: Variant + Clone>(&self) -> bool { pub fn is<T: Variant + Clone>(&self) -> bool {
self.type_id() == TypeId::of::<T>() self.type_id() == TypeId::of::<T>()
|| match self.0 {
Union::Str(_) => TypeId::of::<String>() == TypeId::of::<T>(),
_ => false,
}
} }
/// Get the TypeId of the value held by this `Dynamic`. /// Get the TypeId of the value held by this `Dynamic`.
@ -185,7 +189,7 @@ impl Dynamic {
match &self.0 { match &self.0 {
Union::Unit(_) => TypeId::of::<()>(), Union::Unit(_) => TypeId::of::<()>(),
Union::Bool(_) => TypeId::of::<bool>(), Union::Bool(_) => TypeId::of::<bool>(),
Union::Str(_) => TypeId::of::<String>(), Union::Str(_) => TypeId::of::<ImmutableString>(),
Union::Char(_) => TypeId::of::<char>(), Union::Char(_) => TypeId::of::<char>(),
Union::Int(_) => TypeId::of::<INT>(), Union::Int(_) => TypeId::of::<INT>(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -342,6 +346,12 @@ impl Dynamic {
return Self(result); return Self(result);
} else if let Some(result) = dyn_value.downcast_ref::<char>().cloned().map(Union::Char) { } else if let Some(result) = dyn_value.downcast_ref::<char>().cloned().map(Union::Char) {
return Self(result); return Self(result);
} else if let Some(result) = dyn_value
.downcast_ref::<ImmutableString>()
.cloned()
.map(Union::Str)
{
return Self(result);
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -358,7 +368,7 @@ impl Dynamic {
Err(var) => var, Err(var) => var,
}; };
var = match unsafe_cast_box::<_, String>(var) { var = match unsafe_cast_box::<_, String>(var) {
Ok(s) => return Self(Union::Str(s)), Ok(s) => return Self(Union::Str(s.into())),
Err(var) => var, Err(var) => var,
}; };
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -395,14 +405,19 @@ impl Dynamic {
/// assert_eq!(x.try_cast::<u32>().unwrap(), 42); /// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
/// ``` /// ```
pub fn try_cast<T: Variant>(self) -> Option<T> { pub fn try_cast<T: Variant>(self) -> Option<T> {
if TypeId::of::<T>() == TypeId::of::<Dynamic>() { let type_id = TypeId::of::<T>();
if type_id == TypeId::of::<Dynamic>() {
return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v);
} }
match self.0 { match self.0 {
Union::Unit(value) => unsafe_try_cast(value), Union::Unit(value) => unsafe_try_cast(value),
Union::Bool(value) => unsafe_try_cast(value), Union::Bool(value) => unsafe_try_cast(value),
Union::Str(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), Union::Str(value) if type_id == TypeId::of::<ImmutableString>() => {
unsafe_try_cast(value)
}
Union::Str(value) => unsafe_try_cast(value.into_owned()),
Union::Char(value) => unsafe_try_cast(value), Union::Char(value) => unsafe_try_cast(value),
Union::Int(value) => unsafe_try_cast(value), Union::Int(value) => unsafe_try_cast(value),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -434,16 +449,19 @@ impl Dynamic {
/// assert_eq!(x.cast::<u32>(), 42); /// assert_eq!(x.cast::<u32>(), 42);
/// ``` /// ```
pub fn cast<T: Variant + Clone>(self) -> T { pub fn cast<T: Variant + Clone>(self) -> T {
//self.try_cast::<T>().unwrap() let type_id = TypeId::of::<T>();
if TypeId::of::<T>() == TypeId::of::<Dynamic>() { if type_id == TypeId::of::<Dynamic>() {
return *unsafe_cast_box::<_, T>(Box::new(self)).unwrap(); return *unsafe_cast_box::<_, T>(Box::new(self)).unwrap();
} }
match self.0 { match self.0 {
Union::Unit(value) => unsafe_try_cast(value).unwrap(), Union::Unit(value) => unsafe_try_cast(value).unwrap(),
Union::Bool(value) => unsafe_try_cast(value).unwrap(), Union::Bool(value) => unsafe_try_cast(value).unwrap(),
Union::Str(value) => *unsafe_cast_box::<_, T>(value).unwrap(), Union::Str(value) if type_id == TypeId::of::<ImmutableString>() => {
unsafe_try_cast(value).unwrap()
}
Union::Str(value) => unsafe_try_cast(value.into_owned()).unwrap(),
Union::Char(value) => unsafe_try_cast(value).unwrap(), Union::Char(value) => unsafe_try_cast(value).unwrap(),
Union::Int(value) => unsafe_try_cast(value).unwrap(), Union::Int(value) => unsafe_try_cast(value).unwrap(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -469,7 +487,9 @@ impl Dynamic {
match &self.0 { match &self.0 {
Union::Unit(value) => (value as &dyn Any).downcast_ref::<T>(), Union::Unit(value) => (value as &dyn Any).downcast_ref::<T>(),
Union::Bool(value) => (value as &dyn Any).downcast_ref::<T>(), Union::Bool(value) => (value as &dyn Any).downcast_ref::<T>(),
Union::Str(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(), Union::Str(value) => (value as &dyn Any)
.downcast_ref::<T>()
.or_else(|| (value.as_ref() as &dyn Any).downcast_ref::<T>()),
Union::Char(value) => (value as &dyn Any).downcast_ref::<T>(), Union::Char(value) => (value as &dyn Any).downcast_ref::<T>(),
Union::Int(value) => (value as &dyn Any).downcast_ref::<T>(), Union::Int(value) => (value as &dyn Any).downcast_ref::<T>(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -495,7 +515,7 @@ impl Dynamic {
match &mut self.0 { match &mut self.0 {
Union::Unit(value) => (value as &mut dyn Any).downcast_mut::<T>(), Union::Unit(value) => (value as &mut dyn Any).downcast_mut::<T>(),
Union::Bool(value) => (value as &mut dyn Any).downcast_mut::<T>(), Union::Bool(value) => (value as &mut dyn Any).downcast_mut::<T>(),
Union::Str(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(), Union::Str(value) => (value as &mut dyn Any).downcast_mut::<T>(),
Union::Char(value) => (value as &mut dyn Any).downcast_mut::<T>(), Union::Char(value) => (value as &mut dyn Any).downcast_mut::<T>(),
Union::Int(value) => (value as &mut dyn Any).downcast_mut::<T>(), Union::Int(value) => (value as &mut dyn Any).downcast_mut::<T>(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -519,6 +539,16 @@ impl Dynamic {
} }
} }
/// Cast the `Dynamic` as the system floating-point type `FLOAT` and return it.
/// Returns the name of the actual type if the cast fails.
#[cfg(not(feature = "no_float"))]
pub fn as_float(&self) -> Result<FLOAT, &'static str> {
match self.0 {
Union::Float(n) => Ok(n),
_ => Err(self.type_name()),
}
}
/// Cast the `Dynamic` as a `bool` and return it. /// Cast the `Dynamic` as a `bool` and return it.
/// Returns the name of the actual type if the cast fails. /// Returns the name of the actual type if the cast fails.
pub fn as_bool(&self) -> Result<bool, &'static str> { pub fn as_bool(&self) -> Result<bool, &'static str> {
@ -550,7 +580,7 @@ impl Dynamic {
/// Returns the name of the actual type if the cast fails. /// Returns the name of the actual type if the cast fails.
pub fn take_string(self) -> Result<String, &'static str> { pub fn take_string(self) -> Result<String, &'static str> {
match self.0 { match self.0 {
Union::Str(s) => Ok(*s), Union::Str(s) => Ok(s.into_owned()),
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
@ -584,7 +614,12 @@ impl From<char> for Dynamic {
} }
impl From<String> for Dynamic { impl From<String> for Dynamic {
fn from(value: String) -> Self { fn from(value: String) -> Self {
Self(Union::Str(Box::new(value))) Self(Union::Str(value.into()))
}
}
impl From<ImmutableString> for Dynamic {
fn from(value: ImmutableString) -> Self {
Self(Union::Str(value))
} }
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]

View File

@ -4,7 +4,7 @@ use crate::any::{Dynamic, Variant};
use crate::engine::{make_getter, make_setter, Engine, State, FUNC_INDEXER}; use crate::engine::{make_getter, make_setter, Engine, State, FUNC_INDEXER};
use crate::error::ParseError; use crate::error::ParseError;
use crate::fn_call::FuncArgs; use crate::fn_call::FuncArgs;
use crate::fn_native::{IteratorFn, ObjectGetCallback, ObjectIndexerCallback, ObjectSetCallback}; use crate::fn_native::{IteratorFn, SendSync};
use crate::fn_register::RegisterFn; use crate::fn_register::RegisterFn;
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::parser::{parse, parse_global_expr, AST}; use crate::parser::{parse, parse_global_expr, AST};
@ -162,11 +162,13 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn register_get<T, U, F>(&mut self, name: &str, callback: F) pub fn register_get<T, U>(
where &mut self,
name: &str,
callback: impl Fn(&mut T) -> U + SendSync + 'static,
) where
T: Variant + Clone, T: Variant + Clone,
U: Variant + Clone, U: Variant + Clone,
F: ObjectGetCallback<T, U>,
{ {
self.register_fn(&make_getter(name), callback); self.register_fn(&make_getter(name), callback);
} }
@ -208,11 +210,13 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn register_set<T, U, F>(&mut self, name: &str, callback: F) pub fn register_set<T, U>(
where &mut self,
name: &str,
callback: impl Fn(&mut T, U) + SendSync + 'static,
) where
T: Variant + Clone, T: Variant + Clone,
U: Variant + Clone, U: Variant + Clone,
F: ObjectSetCallback<T, U>,
{ {
self.register_fn(&make_setter(name), callback); self.register_fn(&make_setter(name), callback);
} }
@ -256,12 +260,14 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn register_get_set<T, U, G, S>(&mut self, name: &str, get_fn: G, set_fn: S) pub fn register_get_set<T, U>(
where &mut self,
name: &str,
get_fn: impl Fn(&mut T) -> U + SendSync + 'static,
set_fn: impl Fn(&mut T, U) + SendSync + 'static,
) where
T: Variant + Clone, T: Variant + Clone,
U: Variant + Clone, U: Variant + Clone,
G: ObjectGetCallback<T, U>,
S: ObjectSetCallback<T, U>,
{ {
self.register_get(name, get_fn); self.register_get(name, get_fn);
self.register_set(name, set_fn); self.register_set(name, set_fn);
@ -305,12 +311,13 @@ impl Engine {
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub fn register_indexer<T, X, U, F>(&mut self, callback: F) pub fn register_indexer<T, X, U>(
where &mut self,
callback: impl Fn(&mut T, X) -> U + SendSync + 'static,
) where
T: Variant + Clone, T: Variant + Clone,
U: Variant + Clone, U: Variant + Clone,
X: Variant + Clone, X: Variant + Clone,
F: ObjectIndexerCallback<T, X, U>,
{ {
self.register_fn(FUNC_INDEXER, callback); self.register_fn(FUNC_INDEXER, callback);
} }
@ -334,7 +341,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn compile(&self, script: &str) -> Result<AST, Box<ParseError>> { pub fn compile(&self, script: &str) -> Result<AST, ParseError> {
self.compile_with_scope(&Scope::new(), script) self.compile_with_scope(&Scope::new(), script)
} }
@ -376,7 +383,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result<AST, Box<ParseError>> { pub fn compile_with_scope(&self, scope: &Scope, script: &str) -> Result<AST, ParseError> {
self.compile_scripts_with_scope(scope, &[script]) self.compile_scripts_with_scope(scope, &[script])
} }
@ -430,7 +437,7 @@ impl Engine {
&self, &self,
scope: &Scope, scope: &Scope,
scripts: &[&str], scripts: &[&str],
) -> Result<AST, Box<ParseError>> { ) -> Result<AST, ParseError> {
self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level) self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level)
} }
@ -440,7 +447,7 @@ impl Engine {
scope: &Scope, scope: &Scope,
scripts: &[&str], scripts: &[&str],
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Result<AST, Box<ParseError>> { ) -> Result<AST, ParseError> {
let stream = lex(scripts); let stream = lex(scripts);
parse( parse(
@ -607,7 +614,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn compile_expression(&self, script: &str) -> Result<AST, Box<ParseError>> { pub fn compile_expression(&self, script: &str) -> Result<AST, ParseError> {
self.compile_expression_with_scope(&Scope::new(), script) self.compile_expression_with_scope(&Scope::new(), script)
} }
@ -654,7 +661,7 @@ impl Engine {
&self, &self,
scope: &Scope, scope: &Scope,
script: &str, script: &str,
) -> Result<AST, Box<ParseError>> { ) -> Result<AST, ParseError> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts); let stream = lex(&scripts);
@ -902,12 +909,12 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<(Dynamic, u64), Box<EvalAltResult>> { ) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
let mut state = State::new(ast.fn_lib()); let mut state = State::new();
ast.statements() ast.statements()
.iter() .iter()
.try_fold(().into(), |_, stmt| { .try_fold(().into(), |_, stmt| {
self.eval_stmt(scope, &mut state, stmt, 0) self.eval_stmt(scope, &mut state, ast.lib(), stmt, 0)
}) })
.or_else(|err| match *err { .or_else(|err| match *err {
EvalAltResult::Return(out, _) => Ok(out), EvalAltResult::Return(out, _) => Ok(out),
@ -973,12 +980,12 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let mut state = State::new(ast.fn_lib()); let mut state = State::new();
ast.statements() ast.statements()
.iter() .iter()
.try_fold(().into(), |_, stmt| { .try_fold(().into(), |_, stmt| {
self.eval_stmt(scope, &mut state, stmt, 0) self.eval_stmt(scope, &mut state, ast.lib(), stmt, 0)
}) })
.map_or_else( .map_or_else(
|err| match *err { |err| match *err {
@ -1034,17 +1041,18 @@ impl Engine {
) -> Result<T, Box<EvalAltResult>> { ) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = args.into_vec(); let mut arg_values = args.into_vec();
let mut args: StaticVec<_> = arg_values.iter_mut().collect(); let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let fn_lib = ast.fn_lib(); let lib = ast.lib();
let pos = Position::none(); let pos = Position::none();
let fn_def = fn_lib let fn_def = lib
.get_function_by_signature(name, args.len(), true) .get_function_by_signature(name, args.len(), true)
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.into(), pos)))?; .ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.into(), pos)))?;
let state = State::new(fn_lib); let mut state = State::new();
let args = args.as_mut(); let args = args.as_mut();
let (result, _) = self.call_script_fn(Some(scope), state, name, fn_def, args, pos, 0)?; let result =
self.call_script_fn(Some(scope), &mut state, &lib, name, fn_def, args, pos, 0)?;
let return_type = self.map_type_name(result.type_name()); let return_type = self.map_type_name(result.type_name());
@ -1074,14 +1082,14 @@ impl Engine {
mut ast: AST, mut ast: AST,
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> AST { ) -> AST {
let fn_lib = ast let lib = ast
.fn_lib() .lib()
.iter() .iter()
.map(|(_, fn_def)| fn_def.as_ref().clone()) .map(|(_, fn_def)| fn_def.as_ref().clone())
.collect(); .collect();
let stmt = mem::take(ast.statements_mut()); let stmt = mem::take(ast.statements_mut());
optimize_into_ast(self, scope, stmt, fn_lib, optimization_level) optimize_into_ast(self, scope, stmt, lib, optimization_level)
} }
/// Register a callback for script evaluation progress. /// Register a callback for script evaluation progress.
@ -1118,47 +1126,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(feature = "sync")] pub fn on_progress(&mut self, callback: impl Fn(u64) -> bool + SendSync + 'static) {
pub fn on_progress(&mut self, callback: impl Fn(u64) -> bool + Send + Sync + 'static) {
self.progress = Some(Box::new(callback));
}
/// Register a callback for script evaluation progress.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # use std::cell::Cell;
/// # use std::rc::Rc;
/// use rhai::Engine;
///
/// let result = Rc::new(Cell::new(0_u64));
/// let logger = result.clone();
///
/// let mut engine = Engine::new();
///
/// engine.on_progress(move |ops| {
/// if ops > 10000 {
/// false
/// } else if ops % 800 == 0 {
/// logger.set(ops);
/// true
/// } else {
/// true
/// }
/// });
///
/// engine.consume("for x in range(0, 50000) {}")
/// .expect_err("should error");
///
/// assert_eq!(result.get(), 9600);
///
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "sync"))]
pub fn on_progress(&mut self, callback: impl Fn(u64) -> bool + 'static) {
self.progress = Some(Box::new(callback)); self.progress = Some(Box::new(callback));
} }
@ -1186,36 +1154,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(feature = "sync")] pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) {
pub fn on_print(&mut self, callback: impl Fn(&str) + Send + Sync + 'static) {
self.print = Box::new(callback);
}
/// Override default action of `print` (print to stdout using `println!`)
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # use std::cell::RefCell;
/// # use std::rc::Rc;
/// use rhai::Engine;
///
/// let result = Rc::new(RefCell::new(String::from("")));
///
/// let mut engine = Engine::new();
///
/// // Override action of 'print' function
/// let logger = result.clone();
/// engine.on_print(move |s| logger.borrow_mut().push_str(s));
///
/// engine.consume("print(40 + 2);")?;
///
/// assert_eq!(*result.borrow(), "42");
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "sync"))]
pub fn on_print(&mut self, callback: impl Fn(&str) + 'static) {
self.print = Box::new(callback); self.print = Box::new(callback);
} }
@ -1243,36 +1182,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(feature = "sync")] pub fn on_debug(&mut self, callback: impl Fn(&str) + SendSync + 'static) {
pub fn on_debug(&mut self, callback: impl Fn(&str) + Send + Sync + 'static) {
self.debug = Box::new(callback);
}
/// Override default action of `debug` (print to stdout using `println!`)
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # use std::cell::RefCell;
/// # use std::rc::Rc;
/// use rhai::Engine;
///
/// let result = Rc::new(RefCell::new(String::from("")));
///
/// let mut engine = Engine::new();
///
/// // Override action of 'print' function
/// let logger = result.clone();
/// engine.on_debug(move |s| logger.borrow_mut().push_str(s));
///
/// engine.consume(r#"debug("hello");"#)?;
///
/// assert_eq!(*result.borrow(), r#""hello""#);
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "sync"))]
pub fn on_debug(&mut self, callback: impl Fn(&str) + 'static) {
self.debug = Box::new(callback); self.debug = Box::new(callback);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -120,14 +120,14 @@ pub enum ParseErrorType {
impl ParseErrorType { impl ParseErrorType {
/// Make a `ParseError` using the current type and position. /// Make a `ParseError` using the current type and position.
pub(crate) fn into_err(self, pos: Position) -> Box<ParseError> { pub(crate) fn into_err(self, pos: Position) -> ParseError {
Box::new(ParseError(self, pos)) ParseError(Box::new(self), pos)
} }
} }
/// Error when parsing a script. /// Error when parsing a script.
#[derive(Debug, Eq, PartialEq, Clone, Hash)] #[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct ParseError(pub(crate) ParseErrorType, pub(crate) Position); pub struct ParseError(pub(crate) Box<ParseErrorType>, pub(crate) Position);
impl ParseError { impl ParseError {
/// Get the parse error. /// Get the parse error.
@ -141,7 +141,7 @@ impl ParseError {
} }
pub(crate) fn desc(&self) -> &str { pub(crate) fn desc(&self) -> &str {
match &self.0 { match self.0.as_ref() {
ParseErrorType::BadInput(p) => p, ParseErrorType::BadInput(p) => p,
ParseErrorType::UnexpectedEOF => "Script is incomplete", ParseErrorType::UnexpectedEOF => "Script is incomplete",
ParseErrorType::UnknownOperator(_) => "Unknown operator", ParseErrorType::UnknownOperator(_) => "Unknown operator",
@ -173,7 +173,7 @@ impl Error for ParseError {}
impl fmt::Display for ParseError { impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 { match self.0.as_ref() {
ParseErrorType::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => { ParseErrorType::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => {
write!(f, "{}", if s.is_empty() { self.desc() } else { s })? write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
} }

View File

@ -80,7 +80,7 @@ pub trait Func<ARGS, RET> {
self, self,
script: &str, script: &str,
entry_point: &str, entry_point: &str,
) -> Result<Self::Output, Box<ParseError>>; ) -> Result<Self::Output, ParseError>;
} }
macro_rules! def_anonymous_fn { macro_rules! def_anonymous_fn {
@ -103,7 +103,7 @@ macro_rules! def_anonymous_fn {
}) })
} }
fn create_from_script(self, script: &str, entry_point: &str) -> Result<Self::Output, Box<ParseError>> { fn create_from_script(self, script: &str, entry_point: &str) -> Result<Self::Output, ParseError> {
let ast = self.compile(script)?; let ast = self.compile(script)?;
Ok(Func::<($($par,)*), RET>::create_from_ast(self, ast, entry_point)) Ok(Func::<($($par,)*), RET>::create_from_ast(self, ast, entry_point))
} }

View File

@ -4,78 +4,71 @@ use crate::result::EvalAltResult;
use crate::stdlib::{boxed::Box, rc::Rc, sync::Arc}; use crate::stdlib::{boxed::Box, rc::Rc, sync::Arc};
#[cfg(feature = "sync")]
pub trait SendSync: Send + Sync {}
#[cfg(feature = "sync")]
impl<T: Send + Sync> SendSync for T {}
#[cfg(not(feature = "sync"))]
pub trait SendSync {}
#[cfg(not(feature = "sync"))]
impl<T> SendSync for T {}
#[cfg(not(feature = "sync"))]
pub type Shared<T> = Rc<T>;
#[cfg(feature = "sync")]
pub type Shared<T> = Arc<T>;
/// Consume a `Shared` resource and return a mutable reference to the wrapped value.
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
#[cfg(not(feature = "sync"))]
{
Rc::make_mut(value)
}
#[cfg(feature = "sync")]
{
Arc::make_mut(value)
}
}
/// Consume a `Shared` resource, assuming that it is unique (i.e. not shared).
///
/// # Panics
///
/// Panics if the resource is shared (i.e. has other outstanding references).
pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
#[cfg(not(feature = "sync"))]
{
Rc::try_unwrap(value).map_err(|_| ()).unwrap()
}
#[cfg(feature = "sync")]
{
Arc::try_unwrap(value).map_err(|_| ()).unwrap()
}
}
pub type FnCallArgs<'a> = [&'a mut Dynamic]; pub type FnCallArgs<'a> = [&'a mut Dynamic];
#[cfg(feature = "sync")]
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>; pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
#[cfg(feature = "sync")]
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
pub type IteratorFn = fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>; pub type IteratorFn = fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
#[cfg(feature = "sync")]
pub type PrintCallback = dyn Fn(&str) + Send + Sync + 'static;
#[cfg(not(feature = "sync"))]
pub type PrintCallback = dyn Fn(&str) + 'static;
#[cfg(feature = "sync")]
pub type ProgressCallback = dyn Fn(u64) -> bool + Send + Sync + 'static;
#[cfg(not(feature = "sync"))]
pub type ProgressCallback = dyn Fn(u64) -> bool + 'static;
// Define callback function types
#[cfg(feature = "sync")]
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + Send + Sync + 'static {}
#[cfg(feature = "sync")]
impl<F: Fn(&mut T) -> U + Send + Sync + 'static, T, U> ObjectGetCallback<T, U> for F {}
#[cfg(not(feature = "sync"))]
pub trait ObjectGetCallback<T, U>: Fn(&mut T) -> U + 'static {}
#[cfg(not(feature = "sync"))]
impl<F: Fn(&mut T) -> U + 'static, T, U> ObjectGetCallback<T, U> for F {}
#[cfg(feature = "sync")]
pub trait ObjectSetCallback<T, U>: Fn(&mut T, U) + Send + Sync + 'static {}
#[cfg(feature = "sync")]
impl<F: Fn(&mut T, U) + Send + Sync + 'static, T, U> ObjectSetCallback<T, U> for F {}
#[cfg(not(feature = "sync"))]
pub trait ObjectSetCallback<T, U>: Fn(&mut T, U) + 'static {}
#[cfg(not(feature = "sync"))]
impl<F: Fn(&mut T, U) + 'static, T, U> ObjectSetCallback<T, U> for F {}
#[cfg(feature = "sync")]
pub trait ObjectIndexerCallback<T, X, U>: Fn(&mut T, X) -> U + Send + Sync + 'static {}
#[cfg(feature = "sync")]
impl<F: Fn(&mut T, X) -> U + Send + Sync + 'static, T, X, U> ObjectIndexerCallback<T, X, U> for F {}
#[cfg(not(feature = "sync"))]
pub trait ObjectIndexerCallback<T, X, U>: Fn(&mut T, X) -> U + 'static {}
#[cfg(not(feature = "sync"))]
impl<F: Fn(&mut T, X) -> U + 'static, T, X, U> ObjectIndexerCallback<T, X, U> for F {}
#[cfg(not(feature = "sync"))]
pub type SharedNativeFunction = Rc<FnAny>;
#[cfg(feature = "sync")]
pub type SharedNativeFunction = Arc<FnAny>;
#[cfg(feature = "sync")]
pub type SharedFnDef = Arc<FnDef>;
#[cfg(not(feature = "sync"))]
pub type SharedFnDef = Rc<FnDef>;
/// A type encapsulating a function callable by Rhai. /// A type encapsulating a function callable by Rhai.
#[derive(Clone)] #[derive(Clone)]
pub enum CallableFunction { pub enum CallableFunction {
/// A pure native Rust function with all arguments passed by value. /// A pure native Rust function with all arguments passed by value.
Pure(SharedNativeFunction), Pure(Shared<FnAny>),
/// A native Rust object method with the first argument passed by reference, /// A native Rust object method with the first argument passed by reference,
/// and the rest passed by value. /// and the rest passed by value.
Method(SharedNativeFunction), Method(Shared<FnAny>),
/// An iterator function. /// An iterator function.
Iterator(IteratorFn), Iterator(IteratorFn),
/// A script-defined function. /// A script-defined function.
Script(SharedFnDef), Script(Shared<FnDef>),
} }
impl CallableFunction { impl CallableFunction {

View File

@ -8,7 +8,7 @@ use crate::fn_native::{CallableFunction, FnAny, FnCallArgs};
use crate::parser::FnAccess; use crate::parser::FnAccess;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString}; use crate::stdlib::{any::TypeId, boxed::Box, mem};
/// A trait to register custom plugins with the `Engine`. /// A trait to register custom plugins with the `Engine`.
/// ///
@ -85,49 +85,22 @@ pub trait RegisterFn<FN, ARGS, RET> {
fn register_fn(&mut self, name: &str, f: FN); fn register_fn(&mut self, name: &str, f: FN);
} }
/// Trait to register custom functions that return `Dynamic` values with the `Engine`. /// Trait to register fallible custom functions returning `Result<Dynamic, Box<EvalAltResult>>` with the `Engine`.
pub trait RegisterDynamicFn<FN, ARGS> { pub trait RegisterResultFn<FN, ARGS> {
/// Register a custom function returning `Dynamic` values with the `Engine`.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::{Engine, Dynamic, RegisterDynamicFn};
///
/// // Function that returns a Dynamic value
/// fn return_the_same_as_dynamic(x: i64) -> Dynamic {
/// Dynamic::from(x)
/// }
///
/// let mut engine = Engine::new();
///
/// // You must use the trait rhai::RegisterDynamicFn to get this method.
/// engine.register_dynamic_fn("get_any_number", return_the_same_as_dynamic);
///
/// assert_eq!(engine.eval::<i64>("get_any_number(42)")?, 42);
/// # Ok(())
/// # }
/// ```
fn register_dynamic_fn(&mut self, name: &str, f: FN);
}
/// Trait to register fallible custom functions returning `Result<_, Box<EvalAltResult>>` with the `Engine`.
pub trait RegisterResultFn<FN, ARGS, RET> {
/// Register a custom fallible function with the `Engine`. /// Register a custom fallible function with the `Engine`.
/// ///
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::{Engine, RegisterResultFn, EvalAltResult}; /// use rhai::{Engine, Dynamic, RegisterResultFn, EvalAltResult};
/// ///
/// // Normal function /// // Normal function
/// fn div(x: i64, y: i64) -> Result<i64, Box<EvalAltResult>> { /// fn div(x: i64, y: i64) -> Result<Dynamic, Box<EvalAltResult>> {
/// if y == 0 { /// if y == 0 {
/// // '.into()' automatically converts to 'Box<EvalAltResult::ErrorRuntime>' /// // '.into()' automatically converts to 'Box<EvalAltResult::ErrorRuntime>'
/// Err("division by zero!".into()) /// Err("division by zero!".into())
/// } else { /// } else {
/// Ok(x / y) /// Ok((x / y).into())
/// } /// }
/// } /// }
/// ///
@ -226,16 +199,10 @@ pub fn map_dynamic<T: Variant + Clone>(data: T) -> Result<Dynamic, Box<EvalAltRe
/// To Dynamic mapping function. /// To Dynamic mapping function.
#[inline(always)] #[inline(always)]
pub fn map_identity(data: Dynamic) -> Result<Dynamic, Box<EvalAltResult>> { pub fn map_result(
Ok(data) data: Result<Dynamic, Box<EvalAltResult>>,
}
/// To `Result<Dynamic, Box<EvalAltResult>>` mapping function.
#[inline(always)]
pub fn map_result<T: Variant + Clone>(
data: Result<T, Box<EvalAltResult>>,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
data.map(|v| v.into_dynamic()) data
} }
macro_rules! def_register { macro_rules! def_register {
@ -244,10 +211,10 @@ macro_rules! def_register {
}; };
(imp $abi:ident : $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => { (imp $abi:ident : $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => {
// ^ function ABI type // ^ function ABI type
// ^ function parameter generic type name (A, B, C etc.) // ^ function parameter generic type name (A, B, C etc.)
// ^ function parameter marker type (T, Ref<T> or Mut<T>) // ^ function parameter marker type (T, Ref<T> or Mut<T>)
// ^ function parameter actual type (T, &T or &mut T) // ^ function parameter actual type (T, &T or &mut T)
// ^ dereferencing function // ^ dereferencing function
impl< impl<
$($par: Variant + Clone,)* $($par: Variant + Clone,)*
@ -261,7 +228,7 @@ macro_rules! def_register {
> RegisterFn<FN, ($($mark,)*), RET> for Engine > RegisterFn<FN, ($($mark,)*), RET> for Engine
{ {
fn register_fn(&mut self, name: &str, f: FN) { fn register_fn(&mut self, name: &str, f: FN) {
self.global_module.set_fn(name.to_string(), FnAccess::Public, self.global_module.set_fn(name, FnAccess::Public,
&[$(TypeId::of::<$par>()),*], &[$(TypeId::of::<$par>()),*],
CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $clone),*)) CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $clone),*))
); );
@ -272,33 +239,13 @@ macro_rules! def_register {
$($par: Variant + Clone,)* $($par: Variant + Clone,)*
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
FN: Fn($($param),*) -> Dynamic + Send + Sync + 'static, FN: Fn($($param),*) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync + 'static,
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
FN: Fn($($param),*) -> Dynamic + 'static, FN: Fn($($param),*) -> Result<Dynamic, Box<EvalAltResult>> + 'static,
> RegisterDynamicFn<FN, ($($mark,)*)> for Engine > RegisterResultFn<FN, ($($mark,)*)> for Engine
{
fn register_dynamic_fn(&mut self, name: &str, f: FN) {
self.global_module.set_fn(name.to_string(), FnAccess::Public,
&[$(TypeId::of::<$par>()),*],
CallableFunction::$abi(make_func!(f : map_identity ; $($par => $clone),*))
);
}
}
impl<
$($par: Variant + Clone,)*
#[cfg(feature = "sync")]
FN: Fn($($param),*) -> Result<RET, Box<EvalAltResult>> + Send + Sync + 'static,
#[cfg(not(feature = "sync"))]
FN: Fn($($param),*) -> Result<RET, Box<EvalAltResult>> + 'static,
RET: Variant + Clone
> RegisterResultFn<FN, ($($mark,)*), RET> for Engine
{ {
fn register_result_fn(&mut self, name: &str, f: FN) { fn register_result_fn(&mut self, name: &str, f: FN) {
self.global_module.set_fn(name.to_string(), FnAccess::Public, self.global_module.set_fn(name, FnAccess::Public,
&[$(TypeId::of::<$par>()),*], &[$(TypeId::of::<$par>()),*],
CallableFunction::$abi(make_func!(f : map_result ; $($par => $clone),*)) CallableFunction::$abi(make_func!(f : map_result ; $($par => $clone),*))
); );

View File

@ -49,18 +49,18 @@
//! //!
//! ## Optional features //! ## Optional features
//! //!
//! | Feature | Description | //! | Feature | Description |
//! | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | //! | ------------- | ----------------------------------------------------------------------------------------------------------------------------------|
//! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | //! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! |
//! | `no_function` | Disable script-defined functions if not needed. | //! | `no_function` | Disable script-defined functions if not needed. |
//! | `no_index` | Disable arrays and indexing features if not needed. | //! | `no_index` | Disable arrays and indexing features if not needed. |
//! | `no_object` | Disable support for custom types and objects. | //! | `no_object` | Disable support for custom types and objects. |
//! | `no_float` | Disable floating-point numbers and math if not needed. | //! | `no_float` | Disable floating-point numbers and math if not needed. |
//! | `no_optimize` | Disable the script optimizer. | //! | `no_optimize` | Disable the script optimizer. |
//! | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | //! | `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`. | //! | `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. | //! | `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`. |
//! //!
//! [Check out the README on GitHub for details on the Rhai language!](https://github.com/jonathandturner/rhai) //! [Check out the README on GitHub for details on the Rhai language!](https://github.com/jonathandturner/rhai)
@ -93,9 +93,9 @@ pub use engine::Engine;
pub use error::{ParseError, ParseErrorType}; pub use error::{ParseError, ParseErrorType};
#[cfg(feature = "plugins")] #[cfg(feature = "plugins")]
pub use fn_register::{Plugin, RegisterPlugin}; pub use fn_register::{Plugin, RegisterPlugin};
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; pub use fn_register::{RegisterFn, RegisterResultFn};
pub use module::Module; pub use module::Module;
pub use parser::{AST, INT}; pub use parser::{ImmutableString, AST, INT};
pub use result::EvalAltResult; pub use result::EvalAltResult;
pub use scope::Scope; pub use scope::Scope;
pub use token::Position; pub use token::Position;

View File

@ -3,7 +3,7 @@
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{Engine, FunctionsLib}; use crate::engine::{Engine, FunctionsLib};
use crate::fn_native::{CallableFunction as CF, FnCallArgs, IteratorFn}; use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn};
use crate::parser::{ use crate::parser::{
FnAccess, FnAccess,
FnAccess::{Private, Public}, FnAccess::{Private, Public},
@ -47,17 +47,17 @@ pub struct Module {
all_variables: HashMap<u64, Dynamic>, all_variables: HashMap<u64, Dynamic>,
/// External Rust functions. /// External Rust functions.
functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, CF)>, functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, CallableFunction)>,
/// Script-defined functions. /// Script-defined functions.
fn_lib: FunctionsLib, lib: FunctionsLib,
/// Iterator functions, keyed by the type producing the iterator. /// Iterator functions, keyed by the type producing the iterator.
type_iterators: HashMap<TypeId, IteratorFn>, type_iterators: HashMap<TypeId, IteratorFn>,
/// Flattened collection of all external Rust functions, native or scripted, /// Flattened collection of all external Rust functions, native or scripted,
/// including those in sub-modules. /// including those in sub-modules.
all_functions: HashMap<u64, CF>, all_functions: HashMap<u64, CallableFunction>,
} }
impl fmt::Debug for Module { impl fmt::Debug for Module {
@ -67,7 +67,7 @@ impl fmt::Debug for Module {
"<module {:?}, functions={}, lib={}>", "<module {:?}, functions={}, lib={}>",
self.variables, self.variables,
self.functions.len(), self.functions.len(),
self.fn_lib.len() self.lib.len()
) )
} }
} }
@ -164,7 +164,7 @@ impl Module {
/// module.set_var("answer", 42_i64); /// module.set_var("answer", 42_i64);
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42); /// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
/// ``` /// ```
pub fn set_var<K: Into<String>, T: Variant + Clone>(&mut self, name: K, value: T) { pub fn set_var(&mut self, name: impl Into<String>, value: impl Variant + Clone) {
self.variables.insert(name.into(), Dynamic::from(value)); self.variables.insert(name.into(), Dynamic::from(value));
} }
@ -244,7 +244,7 @@ impl Module {
/// module.set_sub_module("question", sub_module); /// module.set_sub_module("question", sub_module);
/// assert!(module.get_sub_module("question").is_some()); /// assert!(module.get_sub_module("question").is_some());
/// ``` /// ```
pub fn set_sub_module<K: Into<String>>(&mut self, name: K, sub_module: Module) { pub fn set_sub_module(&mut self, name: impl Into<String>, sub_module: Module) {
self.modules.insert(name.into(), sub_module.into()); self.modules.insert(name.into(), sub_module.into());
} }
@ -269,7 +269,15 @@ impl Module {
/// Set a Rust function into the module, returning a hash key. /// Set a Rust function into the module, returning a hash key.
/// ///
/// If there is an existing Rust function of the same hash, it is replaced. /// If there is an existing Rust function of the same hash, it is replaced.
pub fn set_fn(&mut self, name: String, access: FnAccess, params: &[TypeId], func: CF) -> u64 { pub fn set_fn(
&mut self,
name: impl Into<String>,
access: FnAccess,
params: &[TypeId],
func: CallableFunction,
) -> u64 {
let name = name.into();
let hash_fn = calc_fn_hash(empty(), &name, params.len(), params.iter().cloned()); let hash_fn = calc_fn_hash(empty(), &name, params.len(), params.iter().cloned());
let params = params.into_iter().cloned().collect(); let params = params.into_iter().cloned().collect();
@ -293,15 +301,20 @@ impl Module {
/// let hash = module.set_fn_0("calc", || Ok(42_i64)); /// let hash = module.set_fn_0("calc", || Ok(42_i64));
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn set_fn_0<K: Into<String>, T: Variant + Clone>( pub fn set_fn_0<T: Variant + Clone>(
&mut self, &mut self,
name: K, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn<T> + 'static, #[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn() -> FuncReturn<T> + Send + Sync + 'static, #[cfg(feature = "sync")] func: impl Fn() -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = move |_: &mut FnCallArgs| func().map(Dynamic::from); let f = move |_: &mut FnCallArgs| func().map(Dynamic::from);
let args = []; let args = [];
self.set_fn(name.into(), Public, &args, CF::from_pure(Box::new(f))) self.set_fn(
name,
Public,
&args,
CallableFunction::from_pure(Box::new(f)),
)
} }
/// Set a Rust function taking one parameter into the module, returning a hash key. /// Set a Rust function taking one parameter into the module, returning a hash key.
@ -317,16 +330,21 @@ impl Module {
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn set_fn_1<K: Into<String>, A: Variant + Clone, T: Variant + Clone>( pub fn set_fn_1<A: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
name: K, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn<T> + 'static, #[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn<T> + Send + Sync + 'static, #[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
let f = let f =
move |args: &mut FnCallArgs| func(mem::take(args[0]).cast::<A>()).map(Dynamic::from); move |args: &mut FnCallArgs| func(mem::take(args[0]).cast::<A>()).map(Dynamic::from);
let args = [TypeId::of::<A>()]; let args = [TypeId::of::<A>()];
self.set_fn(name.into(), Public, &args, CF::from_pure(Box::new(f))) self.set_fn(
name,
Public,
&args,
CallableFunction::from_pure(Box::new(f)),
)
} }
/// Set a Rust function taking one mutable parameter into the module, returning a hash key. /// Set a Rust function taking one mutable parameter into the module, returning a hash key.
@ -342,9 +360,9 @@ impl Module {
/// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) }); /// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) });
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn set_fn_1_mut<K: Into<String>, A: Variant + Clone, T: Variant + Clone>( pub fn set_fn_1_mut<A: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
name: K, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn<T> + 'static, #[cfg(not(feature = "sync"))] func: impl Fn(&mut A) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn<T> + Send + Sync + 'static, #[cfg(feature = "sync")] func: impl Fn(&mut A) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
@ -352,7 +370,12 @@ impl Module {
func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from) func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from)
}; };
let args = [TypeId::of::<A>()]; let args = [TypeId::of::<A>()];
self.set_fn(name.into(), Public, &args, CF::from_method(Box::new(f))) self.set_fn(
name,
Public,
&args,
CallableFunction::from_method(Box::new(f)),
)
} }
/// Set a Rust function taking two parameters into the module, returning a hash key. /// Set a Rust function taking two parameters into the module, returning a hash key.
@ -370,9 +393,9 @@ impl Module {
/// }); /// });
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn set_fn_2<K: Into<String>, A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>( pub fn set_fn_2<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self, &mut self,
name: K, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> FuncReturn<T> + 'static, #[cfg(not(feature = "sync"))] func: impl Fn(A, B) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(A, B) -> FuncReturn<T> + Send + Sync + 'static, #[cfg(feature = "sync")] func: impl Fn(A, B) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
@ -383,7 +406,12 @@ impl Module {
func(a, b).map(Dynamic::from) func(a, b).map(Dynamic::from)
}; };
let args = [TypeId::of::<A>(), TypeId::of::<B>()]; let args = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(name.into(), Public, &args, CF::from_pure(Box::new(f))) self.set_fn(
name,
Public,
&args,
CallableFunction::from_pure(Box::new(f)),
)
} }
/// Set a Rust function taking two parameters (the first one mutable) into the module, /// Set a Rust function taking two parameters (the first one mutable) into the module,
@ -400,14 +428,9 @@ impl Module {
/// }); /// });
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn set_fn_2_mut< pub fn set_fn_2_mut<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
K: Into<String>,
A: Variant + Clone,
B: Variant + Clone,
T: Variant + Clone,
>(
&mut self, &mut self,
name: K, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<T> + 'static, #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<T> + Send + Sync + 'static, #[cfg(feature = "sync")] func: impl Fn(&mut A, B) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
@ -418,7 +441,12 @@ impl Module {
func(a, b).map(Dynamic::from) func(a, b).map(Dynamic::from)
}; };
let args = [TypeId::of::<A>(), TypeId::of::<B>()]; let args = [TypeId::of::<A>(), TypeId::of::<B>()];
self.set_fn(name.into(), Public, &args, CF::from_method(Box::new(f))) self.set_fn(
name,
Public,
&args,
CallableFunction::from_method(Box::new(f)),
)
} }
/// Set a Rust function taking three parameters into the module, returning a hash key. /// Set a Rust function taking three parameters into the module, returning a hash key.
@ -437,14 +465,13 @@ impl Module {
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn set_fn_3< pub fn set_fn_3<
K: Into<String>,
A: Variant + Clone, A: Variant + Clone,
B: Variant + Clone, B: Variant + Clone,
C: Variant + Clone, C: Variant + Clone,
T: Variant + Clone, T: Variant + Clone,
>( >(
&mut self, &mut self,
name: K, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> FuncReturn<T> + 'static, #[cfg(not(feature = "sync"))] func: impl Fn(A, B, C) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(A, B, C) -> FuncReturn<T> + Send + Sync + 'static, #[cfg(feature = "sync")] func: impl Fn(A, B, C) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
@ -456,7 +483,12 @@ impl Module {
func(a, b, c).map(Dynamic::from) func(a, b, c).map(Dynamic::from)
}; };
let args = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]; let args = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(name.into(), Public, &args, CF::from_pure(Box::new(f))) self.set_fn(
name,
Public,
&args,
CallableFunction::from_pure(Box::new(f)),
)
} }
/// Set a Rust function taking three parameters (the first one mutable) into the module, /// Set a Rust function taking three parameters (the first one mutable) into the module,
@ -476,14 +508,13 @@ impl Module {
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn set_fn_3_mut< pub fn set_fn_3_mut<
K: Into<String>,
A: Variant + Clone, A: Variant + Clone,
B: Variant + Clone, B: Variant + Clone,
C: Variant + Clone, C: Variant + Clone,
T: Variant + Clone, T: Variant + Clone,
>( >(
&mut self, &mut self,
name: K, name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + 'static, #[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + Send + Sync + 'static, #[cfg(feature = "sync")] func: impl Fn(&mut A, B, C) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 { ) -> u64 {
@ -495,7 +526,111 @@ impl Module {
func(a, b, c).map(Dynamic::from) func(a, b, c).map(Dynamic::from)
}; };
let args = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()]; let args = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
self.set_fn(name.into(), Public, &args, CF::from_method(Box::new(f))) self.set_fn(
name,
Public,
&args,
CallableFunction::from_method(Box::new(f)),
)
}
/// Set a Rust function taking four parameters into the module, returning a hash key.
///
/// If there is a similar existing Rust function, it is replaced.
///
/// # Examples
///
/// ```
/// use rhai::Module;
///
/// let mut module = Module::new();
/// let hash = module.set_fn_3("calc", |x: i64, y: String, z: i64, _w: ()| {
/// Ok(x + y.len() as i64 + z)
/// });
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_4<
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
D: Variant + Clone,
T: Variant + Clone,
>(
&mut self,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(A, B, C, D) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(A, B, C, D) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f = move |args: &mut FnCallArgs| {
let a = mem::take(args[0]).cast::<A>();
let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>();
let d = mem::take(args[3]).cast::<D>();
func(a, b, c, d).map(Dynamic::from)
};
let args = [
TypeId::of::<A>(),
TypeId::of::<B>(),
TypeId::of::<C>(),
TypeId::of::<D>(),
];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_pure(Box::new(f)),
)
}
/// Set a Rust function taking three parameters (the first one mutable) into the module,
/// returning a hash key.
///
/// If there is a similar existing Rust function, it is replaced.
///
/// # Examples
///
/// ```
/// use rhai::Module;
///
/// let mut module = Module::new();
/// let hash = module.set_fn_3_mut("calc", |x: &mut i64, y: String, z: i64, _w: ()| {
/// *x += y.len() as i64 + z; Ok(*x)
/// });
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_4_mut<
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
D: Variant + Clone,
T: Variant + Clone,
>(
&mut self,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(&mut A, B, C, D) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(&mut A, B, C, D) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f = move |args: &mut FnCallArgs| {
let b = mem::take(args[1]).cast::<B>();
let c = mem::take(args[2]).cast::<C>();
let d = mem::take(args[3]).cast::<D>();
let a = args[0].downcast_mut::<A>().unwrap();
func(a, b, c, d).map(Dynamic::from)
};
let args = [
TypeId::of::<A>(),
TypeId::of::<B>(),
TypeId::of::<C>(),
TypeId::of::<C>(),
];
self.set_fn(
name,
Public,
&args,
CallableFunction::from_method(Box::new(f)),
)
} }
/// Get a Rust function. /// Get a Rust function.
@ -512,19 +647,19 @@ impl Module {
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1)); /// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
/// assert!(module.get_fn(hash).is_some()); /// assert!(module.get_fn(hash).is_some());
/// ``` /// ```
pub fn get_fn(&self, hash_fn: u64) -> Option<&CF> { pub fn get_fn(&self, hash_fn: u64) -> Option<&CallableFunction> {
self.functions.get(&hash_fn).map(|(_, _, _, v)| v) self.functions.get(&hash_fn).map(|(_, _, _, v)| v)
} }
/// Get a modules-qualified function. /// Get a modules-qualified function.
/// ///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`. /// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match
/// It is also returned by the `set_fn_XXX` calls. /// the hash calculated by `index_all_sub_modules`.
pub(crate) fn get_qualified_fn( pub(crate) fn get_qualified_fn(
&mut self, &mut self,
name: &str, name: &str,
hash_fn_native: u64, hash_fn_native: u64,
) -> Result<&CF, Box<EvalAltResult>> { ) -> Result<&CallableFunction, Box<EvalAltResult>> {
self.all_functions.get(&hash_fn_native).ok_or_else(|| { self.all_functions.get(&hash_fn_native).ok_or_else(|| {
Box::new(EvalAltResult::ErrorFunctionNotFound( Box::new(EvalAltResult::ErrorFunctionNotFound(
name.to_string(), name.to_string(),
@ -578,7 +713,7 @@ impl Module {
}, },
); );
module.fn_lib = module.fn_lib.merge(ast.fn_lib()); module.lib = module.lib.merge(ast.lib());
Ok(module) Ok(module)
} }
@ -591,7 +726,7 @@ impl Module {
module: &'a Module, module: &'a Module,
qualifiers: &mut Vec<&'a str>, qualifiers: &mut Vec<&'a str>,
variables: &mut Vec<(u64, Dynamic)>, variables: &mut Vec<(u64, Dynamic)>,
functions: &mut Vec<(u64, CF)>, functions: &mut Vec<(u64, CallableFunction)>,
) { ) {
for (name, m) in &module.modules { for (name, m) in &module.modules {
// Index all the sub-modules first. // Index all the sub-modules first.
@ -627,7 +762,7 @@ impl Module {
functions.push((hash_fn_native, func.clone())); functions.push((hash_fn_native, func.clone()));
} }
// Index all script-defined functions // Index all script-defined functions
for fn_def in module.fn_lib.values() { for fn_def in module.lib.values() {
match fn_def.access { match fn_def.access {
// Private functions are not exported // Private functions are not exported
Private => continue, Private => continue,
@ -640,7 +775,7 @@ impl Module {
fn_def.params.len(), fn_def.params.len(),
empty(), empty(),
); );
functions.push((hash_fn_def, CF::Script(fn_def.clone()).into())); functions.push((hash_fn_def, CallableFunction::Script(fn_def.clone()).into()));
} }
} }

View File

@ -1,11 +1,9 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{ use crate::engine::{
Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF, Engine, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT,
KEYWORD_TYPE_OF,
}; };
use crate::fn_native::FnCallArgs;
use crate::module::Module;
use crate::packages::PackagesCollection;
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
@ -54,23 +52,19 @@ struct State<'a> {
/// An `Engine` instance for eager function evaluation. /// An `Engine` instance for eager function evaluation.
engine: &'a Engine, engine: &'a Engine,
/// Library of script-defined functions. /// Library of script-defined functions.
fn_lib: &'a [(&'a str, usize)], lib: &'a FunctionsLib,
/// Optimization level. /// Optimization level.
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
} }
impl<'a> State<'a> { impl<'a> State<'a> {
/// Create a new State. /// Create a new State.
pub fn new( pub fn new(engine: &'a Engine, lib: &'a FunctionsLib, level: OptimizationLevel) -> Self {
engine: &'a Engine,
fn_lib: &'a [(&'a str, usize)],
level: OptimizationLevel,
) -> Self {
Self { Self {
changed: false, changed: false,
constants: vec![], constants: vec![],
engine, engine,
fn_lib, lib,
optimization_level: level, optimization_level: level,
} }
} }
@ -111,27 +105,36 @@ impl<'a> State<'a> {
} }
/// Call a registered function /// Call a registered function
fn call_fn( fn call_fn_with_constant_arguments(
packages: &PackagesCollection, state: &State,
global_module: &Module,
fn_name: &str, fn_name: &str,
args: &mut FnCallArgs, arg_values: &mut [Dynamic],
pos: Position, pos: Position,
) -> Result<Option<Dynamic>, Box<EvalAltResult>> { ) -> Result<Option<Dynamic>, Box<EvalAltResult>> {
// Search built-in's and external functions // Search built-in's and external functions
let hash_fn = calc_fn_hash( let hash_fn = calc_fn_hash(
empty(), empty(),
fn_name, fn_name,
args.len(), arg_values.len(),
args.iter().map(|a| a.type_id()), arg_values.iter().map(|a| a.type_id()),
); );
global_module state
.get_fn(hash_fn) .engine
.or_else(|| packages.get_fn(hash_fn)) .call_fn_raw(
.map(|func| func.get_native_fn()(args)) None,
.transpose() &mut Default::default(),
.map_err(|err| err.new_position(pos)) state.lib,
fn_name,
(hash_fn, 0),
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
false,
None,
pos,
0,
)
.map(|(v, _)| Some(v))
.or_else(|_| Ok(None))
} }
/// Optimize a statement. /// Optimize a statement.
@ -377,26 +380,26 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
stmt => Expr::Stmt(Box::new((stmt, x.1))), stmt => Expr::Stmt(Box::new((stmt, x.1))),
}, },
// id = expr // id = expr
Expr::Assignment(x) => match x.1 { Expr::Assignment(x) => match x.2 {
//id = id2 = expr2 //id = id2 op= expr2
Expr::Assignment(x2) => match (x.0, x2.0) { Expr::Assignment(x2) if x.1 == "=" => match (x.0, x2.0) {
// var = var = expr2 -> var = expr2 // var = var op= expr2 -> var op= expr2
(Expr::Variable(a), Expr::Variable(b)) (Expr::Variable(a), Expr::Variable(b))
if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 => if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 =>
{ {
// Assignment to the same variable - fold // Assignment to the same variable - fold
state.set_dirty(); state.set_dirty();
Expr::Assignment(Box::new((Expr::Variable(a), optimize_expr(x2.1, state), x.2))) Expr::Assignment(Box::new((Expr::Variable(a), x2.1, optimize_expr(x2.2, state), x.3)))
} }
// id1 = id2 = expr2 // id1 = id2 op= expr2
(id1, id2) => { (id1, id2) => {
Expr::Assignment(Box::new(( Expr::Assignment(Box::new((
id1, Expr::Assignment(Box::new((id2, optimize_expr(x2.1, state), x2.2))), x.2, id1, x.1, Expr::Assignment(Box::new((id2, x2.1, optimize_expr(x2.2, state), x2.3))), x.3,
))) )))
} }
}, },
// id = expr // id op= expr
expr => Expr::Assignment(Box::new((x.0, optimize_expr(expr, state), x.2))), expr => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(expr, state), x.3))),
}, },
// lhs.rhs // lhs.rhs
@ -435,7 +438,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// All other items can be thrown away. // All other items can be thrown away.
state.set_dirty(); state.set_dirty();
let pos = m.1; let pos = m.1;
m.0.into_iter().find(|((name, _), _)| name == &s.0) m.0.into_iter().find(|((name, _), _)| name == s.0.as_ref())
.map(|(_, expr)| expr.set_position(pos)) .map(|(_, expr)| expr.set_position(pos))
.unwrap_or_else(|| Expr::Unit(pos)) .unwrap_or_else(|| Expr::Unit(pos))
} }
@ -463,7 +466,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// "xxx" in "xxxxx" // "xxx" in "xxxxx"
(Expr::StringConstant(a), Expr::StringConstant(b)) => { (Expr::StringConstant(a), Expr::StringConstant(b)) => {
state.set_dirty(); state.set_dirty();
if b.0.contains(&a.0) { Expr::True(a.1) } else { Expr::False(a.1) } if b.0.contains(a.0.as_ref()) { Expr::True(a.1) } else { Expr::False(a.1) }
} }
// 'x' in "xxxxx" // 'x' in "xxxxx"
(Expr::CharConstant(a), Expr::StringConstant(b)) => { (Expr::CharConstant(a), Expr::StringConstant(b)) => {
@ -473,7 +476,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// "xxx" in #{...} // "xxx" in #{...}
(Expr::StringConstant(a), Expr::Map(b)) => { (Expr::StringConstant(a), Expr::Map(b)) => {
state.set_dirty(); state.set_dirty();
if b.0.iter().find(|((name, _), _)| name == &a.0).is_some() { if b.0.iter().find(|((name, _), _)| name == a.0.as_ref()).is_some() {
Expr::True(a.1) Expr::True(a.1)
} else { } else {
Expr::False(a.1) Expr::False(a.1)
@ -546,27 +549,30 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
&& state.optimization_level == OptimizationLevel::Full // full optimizations && state.optimization_level == OptimizationLevel::Full // full optimizations
&& x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants && x.3.iter().all(|expr| expr.is_constant()) // all arguments are constants
=> { => {
let ((name, pos), _, _, args, def_value) = x.as_mut(); let ((name, native_only, pos), _, _, args, def_value) = x.as_mut();
// First search in script-defined functions (can override built-in) // First search in script-defined functions (can override built-in)
if state.fn_lib.iter().find(|(id, len)| *id == name && *len == args.len()).is_some() { // Cater for both normal function call style and method call style (one additional arguments)
if !*native_only && state.lib.values().find(|f|
&f.name == name
&& (args.len()..=args.len() + 1).contains(&f.params.len())
).is_some() {
// A script-defined function overrides the built-in function - do not make the call // A script-defined function overrides the built-in function - do not make the call
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect(); x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
return Expr::FnCall(x); return Expr::FnCall(x);
} }
let mut arg_values: StaticVec<_> = args.iter().map(Expr::get_constant_value).collect(); let mut arg_values: StaticVec<_> = args.iter().map(Expr::get_constant_value).collect();
let mut call_args: StaticVec<_> = arg_values.iter_mut().collect();
// Save the typename of the first argument if it is `type_of()` // Save the typename of the first argument if it is `type_of()`
// This is to avoid `call_args` being passed into the closure // This is to avoid `call_args` being passed into the closure
let arg_for_type_of = if name == KEYWORD_TYPE_OF && call_args.len() == 1 { let arg_for_type_of = if name == KEYWORD_TYPE_OF && arg_values.len() == 1 {
state.engine.map_type_name(call_args[0].type_name()) state.engine.map_type_name(arg_values[0].type_name())
} else { } else {
"" ""
}; };
call_fn(&state.engine.packages, &state.engine.global_module, name, call_args.as_mut(), *pos).ok() call_fn_with_constant_arguments(&state, name, arg_values.as_mut(), *pos).ok()
.and_then(|result| .and_then(|result|
result.or_else(|| { result.or_else(|| {
if !arg_for_type_of.is_empty() { if !arg_for_type_of.is_empty() {
@ -608,11 +614,11 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
} }
} }
fn optimize<'a>( fn optimize(
statements: Vec<Stmt>, statements: Vec<Stmt>,
engine: &Engine, engine: &Engine,
scope: &Scope, scope: &Scope,
fn_lib: &'a [(&'a str, usize)], lib: &FunctionsLib,
level: OptimizationLevel, level: OptimizationLevel,
) -> Vec<Stmt> { ) -> Vec<Stmt> {
// If optimization level is None then skip optimizing // If optimization level is None then skip optimizing
@ -621,7 +627,7 @@ fn optimize<'a>(
} }
// Set up the state // Set up the state
let mut state = State::new(engine, fn_lib, level); let mut state = State::new(engine, lib, level);
// Add constants from the scope into the state // Add constants from the scope into the state
scope scope
@ -706,41 +712,35 @@ pub fn optimize_into_ast(
const level: OptimizationLevel = OptimizationLevel::None; const level: OptimizationLevel = OptimizationLevel::None;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
let fn_lib_values: StaticVec<_> = functions let lib = {
.iter()
.map(|fn_def| (fn_def.name.as_str(), fn_def.params.len()))
.collect();
#[cfg(not(feature = "no_function"))]
let fn_lib = fn_lib_values.as_ref();
#[cfg(feature = "no_function")]
const fn_lib: &[(&str, usize)] = &[];
#[cfg(not(feature = "no_function"))]
let lib = FunctionsLib::from_iter(functions.iter().cloned().map(|mut fn_def| {
if !level.is_none() { if !level.is_none() {
let pos = fn_def.body.position(); let lib = FunctionsLib::from_iter(functions.iter().cloned());
// Optimize the function body FunctionsLib::from_iter(functions.into_iter().map(|mut fn_def| {
let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), fn_lib, level); let pos = fn_def.body.position();
// {} -> Noop // Optimize the function body
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &lib, level);
// { return val; } -> val
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => { // {} -> Noop
Stmt::Expr(Box::new(x.1.unwrap())) fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
} // { return val; } -> val
// { return; } -> () Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
Stmt::ReturnWithVal(x) if x.1.is_none() && (x.0).0 == ReturnType::Return => { Stmt::Expr(Box::new(x.1.unwrap()))
Stmt::Expr(Box::new(Expr::Unit((x.0).1))) }
} // { return; } -> ()
// All others Stmt::ReturnWithVal(x) if x.1.is_none() && (x.0).0 == ReturnType::Return => {
stmt => stmt, Stmt::Expr(Box::new(Expr::Unit((x.0).1)))
}; }
// All others
stmt => stmt,
};
fn_def
}))
} else {
FunctionsLib::from_iter(functions.into_iter())
} }
fn_def };
}));
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
let lib: FunctionsLib = Default::default(); let lib: FunctionsLib = Default::default();
@ -749,7 +749,7 @@ pub fn optimize_into_ast(
match level { match level {
OptimizationLevel::None => statements, OptimizationLevel::None => statements,
OptimizationLevel::Simple | OptimizationLevel::Full => { OptimizationLevel::Simple | OptimizationLevel::Full => {
optimize(statements, engine, &scope, fn_lib, level) optimize(statements, engine, &scope, &lib, level)
} }
}, },
lib, lib,

View File

@ -20,7 +20,7 @@ use crate::stdlib::{
}; };
// Checked add // Checked add
fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> { pub(crate) fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
x.checked_add(&y).ok_or_else(|| { x.checked_add(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Addition overflow: {} + {}", x, y), format!("Addition overflow: {} + {}", x, y),
@ -29,7 +29,7 @@ fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
}) })
} }
// Checked subtract // Checked subtract
fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> { pub(crate) fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
x.checked_sub(&y).ok_or_else(|| { x.checked_sub(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Subtraction underflow: {} - {}", x, y), format!("Subtraction underflow: {} - {}", x, y),
@ -38,7 +38,7 @@ fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
}) })
} }
// Checked multiply // Checked multiply
fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> { pub(crate) fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
x.checked_mul(&y).ok_or_else(|| { x.checked_mul(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Multiplication overflow: {} * {}", x, y), format!("Multiplication overflow: {} * {}", x, y),
@ -47,7 +47,7 @@ fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
}) })
} }
// Checked divide // Checked divide
fn div<T>(x: T, y: T) -> FuncReturn<T> pub(crate) fn div<T>(x: T, y: T) -> FuncReturn<T>
where where
T: Display + CheckedDiv + PartialEq + Zero, T: Display + CheckedDiv + PartialEq + Zero,
{ {
@ -67,7 +67,7 @@ where
}) })
} }
// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX // Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> { pub(crate) fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
x.checked_neg().ok_or_else(|| { x.checked_neg().ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Negation overflow: -{}", x), format!("Negation overflow: -{}", x),
@ -76,7 +76,7 @@ fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
}) })
} }
// Checked absolute // Checked absolute
fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> FuncReturn<T> { pub(crate) fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> FuncReturn<T> {
// FIX - We don't use Signed::abs() here because, contrary to documentation, it panics // FIX - We don't use Signed::abs() here because, contrary to documentation, it panics
// when the number is ::MIN instead of returning ::MIN itself. // when the number is ::MIN instead of returning ::MIN itself.
if x >= <T as Zero>::zero() { if x >= <T as Zero>::zero() {
@ -133,7 +133,7 @@ fn binary_xor<T: BitXor>(x: T, y: T) -> FuncReturn<<T as BitXor>::Output> {
Ok(x ^ y) Ok(x ^ y)
} }
// Checked left-shift // Checked left-shift
fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> { pub(crate) fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
// Cannot shift by a negative number of bits // Cannot shift by a negative number of bits
if y < 0 { if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -150,7 +150,7 @@ fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
}) })
} }
// Checked right-shift // Checked right-shift
fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> { pub(crate) fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
// Cannot shift by a negative number of bits // Cannot shift by a negative number of bits
if y < 0 { if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -167,15 +167,15 @@ fn shr<T: Display + CheckedShr>(x: T, y: INT) -> FuncReturn<T> {
}) })
} }
// Unchecked left-shift - may panic if shifting by a negative number of bits // Unchecked left-shift - may panic if shifting by a negative number of bits
fn shl_u<T: Shl<T>>(x: T, y: T) -> FuncReturn<<T as Shl<T>>::Output> { pub(crate) fn shl_u<T: Shl<T>>(x: T, y: T) -> FuncReturn<<T as Shl<T>>::Output> {
Ok(x.shl(y)) Ok(x.shl(y))
} }
// Unchecked right-shift - may panic if shifting by a negative number of bits // Unchecked right-shift - may panic if shifting by a negative number of bits
fn shr_u<T: Shr<T>>(x: T, y: T) -> FuncReturn<<T as Shr<T>>::Output> { pub(crate) fn shr_u<T: Shr<T>>(x: T, y: T) -> FuncReturn<<T as Shr<T>>::Output> {
Ok(x.shr(y)) Ok(x.shr(y))
} }
// Checked modulo // Checked modulo
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> { pub(crate) fn modulo<T: Display + CheckedRem>(x: T, y: T) -> FuncReturn<T> {
x.checked_rem(&y).ok_or_else(|| { x.checked_rem(&y).ok_or_else(|| {
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
format!("Modulo division by zero or overflow: {} % {}", x, y), format!("Modulo division by zero or overflow: {} % {}", x, y),
@ -188,7 +188,7 @@ fn modulo_u<T: Rem>(x: T, y: T) -> FuncReturn<<T as Rem>::Output> {
Ok(x % y) Ok(x % y)
} }
// Checked power // Checked power
fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> { pub(crate) fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
{ {
if y > (u32::MAX as INT) { if y > (u32::MAX as INT) {
@ -229,17 +229,17 @@ fn pow_i_i(x: INT, y: INT) -> FuncReturn<INT> {
} }
} }
// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX) // Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> { pub(crate) fn pow_i_i_u(x: INT, y: INT) -> FuncReturn<INT> {
Ok(x.pow(y as u32)) Ok(x.pow(y as u32))
} }
// Floating-point power - always well-defined // Floating-point power - always well-defined
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn<FLOAT> { pub(crate) fn pow_f_f(x: FLOAT, y: FLOAT) -> FuncReturn<FLOAT> {
Ok(x.powf(y)) Ok(x.powf(y))
} }
// Checked power // Checked power
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> { pub(crate) fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
// Raise to power that is larger than an i32 // Raise to power that is larger than an i32
if y > (i32::MAX as INT) { if y > (i32::MAX as INT) {
return Err(Box::new(EvalAltResult::ErrorArithmetic( return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -253,7 +253,7 @@ fn pow_f_i(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
// Unchecked power - may be incorrect if the power index is too high (> i32::MAX) // Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn<FLOAT> { pub(crate) fn pow_f_i_u(x: FLOAT, y: INT) -> FuncReturn<FLOAT> {
Ok(x.powi(y as i32)) Ok(x.powi(y as i32))
} }
@ -269,119 +269,69 @@ macro_rules! reg_op {
} }
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
// Checked basic arithmetic #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op!(lib, "+", add, INT); #[cfg(not(feature = "unchecked"))]
reg_op!(lib, "-", sub, INT);
reg_op!(lib, "*", mul, INT);
reg_op!(lib, "/", div, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{ {
reg_op!(lib, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); // Checked basic arithmetic
reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "/", div, i8, u8, i16, u16, i32, u32, u64, i128, u128);
// Checked bit shifts
reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64, i128, u128);
} }
}
// Unchecked basic arithmetic #[cfg(feature = "unchecked")]
#[cfg(feature = "unchecked")]
{
reg_op!(lib, "+", add_u, INT);
reg_op!(lib, "-", sub_u, INT);
reg_op!(lib, "*", mul_u, INT);
reg_op!(lib, "/", div_u, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{ {
reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); // Unchecked basic arithmetic
reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
// Unchecked bit shifts
reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
} }
} }
// Basic arithmetic for floating-point - no need to check // Basic arithmetic for floating-point - no need to check
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
reg_op!(lib, "+", add_u, f32, f64); reg_op!(lib, "+", add_u, f32);
reg_op!(lib, "-", sub_u, f32, f64); reg_op!(lib, "-", sub_u, f32);
reg_op!(lib, "*", mul_u, f32, f64); reg_op!(lib, "*", mul_u, f32);
reg_op!(lib, "/", div_u, f32, f64); reg_op!(lib, "/", div_u, f32);
} }
// Bit operations
reg_op!(lib, "|", binary_or, INT);
reg_op!(lib, "&", binary_and, INT);
reg_op!(lib, "^", binary_xor, INT);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64, i128, u128);
} }
// Checked bit shifts
#[cfg(not(feature = "unchecked"))]
{
reg_op!(lib, "<<", shl, INT);
reg_op!(lib, ">>", shr, INT);
reg_op!(lib, "%", modulo, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
}
}
// Unchecked bit shifts
#[cfg(feature = "unchecked")]
{
reg_op!(lib, "<<", shl_u, INT, INT);
reg_op!(lib, ">>", shr_u, INT, INT);
reg_op!(lib, "%", modulo_u, INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
}
}
// Checked power
#[cfg(not(feature = "unchecked"))]
{
lib.set_fn_2("~", pow_i_i);
#[cfg(not(feature = "no_float"))]
lib.set_fn_2("~", pow_f_i);
}
// Unchecked power
#[cfg(feature = "unchecked")]
{
lib.set_fn_2("~", pow_i_i_u);
#[cfg(not(feature = "no_float"))]
lib.set_fn_2("~", pow_f_i_u);
}
// Floating-point modulo and power
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
reg_op!(lib, "%", modulo_u, f32, f64); // Checked power
lib.set_fn_2("~", pow_f_f); #[cfg(not(feature = "unchecked"))]
lib.set_fn_2("~", pow_f_i);
// Unchecked power
#[cfg(feature = "unchecked")]
lib.set_fn_2("~", pow_f_i_u);
// Floating-point modulo and power
reg_op!(lib, "%", modulo_u, f32);
// Floating-point unary
reg_unary!(lib, "-", neg_u, f32, f64);
reg_unary!(lib, "abs", abs_u, f32, f64);
} }
// Checked unary // Checked unary
@ -411,11 +361,4 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64, i128); reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64, i128);
} }
} }
// Floating-point unary
#[cfg(not(feature = "no_float"))]
{
reg_unary!(lib, "-", neg_u, f32, f64);
reg_unary!(lib, "abs", abs_u, f32, f64);
}
}); });

View File

@ -4,9 +4,9 @@ use crate::any::{Dynamic, Variant};
use crate::def_package; use crate::def_package;
use crate::engine::Array; use crate::engine::Array;
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::parser::INT; use crate::parser::{ImmutableString, INT};
use crate::stdlib::{any::TypeId, boxed::Box, string::String}; use crate::stdlib::{any::TypeId, boxed::Box};
// Register array utility functions // Register array utility functions
fn push<T: Variant + Clone>(list: &mut Array, item: T) -> FuncReturn<()> { fn push<T: Variant + Clone>(list: &mut Array, item: T) -> FuncReturn<()> {
@ -45,14 +45,18 @@ macro_rules! reg_tri {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
reg_op!(lib, "push", push, INT, bool, char, String, Array, ()); reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ());
reg_tri!(lib, "pad", pad, INT, bool, char, String, Array, ()); reg_tri!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ());
reg_tri!(lib, "insert", ins, INT, bool, char, String, Array, ()); reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, Array, ());
lib.set_fn_2_mut("append", |x: &mut Array, y: Array| { lib.set_fn_2_mut("append", |x: &mut Array, y: Array| {
x.extend(y); x.extend(y);
Ok(()) Ok(())
}); });
lib.set_fn_2_mut("+=", |x: &mut Array, y: Array| {
x.extend(y);
Ok(())
});
lib.set_fn_2( lib.set_fn_2(
"+", "+",
|mut x: Array, y: Array| { |mut x: Array, y: Array| {

View File

@ -1,11 +1,11 @@
use crate::def_package; use crate::def_package;
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::stdlib::string::String; use crate::parser::ImmutableString;
def_package!(crate:EvalPackage:"Disable 'eval'.", lib, { def_package!(crate:EvalPackage:"Disable 'eval'.", lib, {
lib.set_fn_1_mut( lib.set_fn_1(
"eval", "eval",
|_: &mut String| -> FuncReturn<()> { |_: ImmutableString| -> FuncReturn<()> {
Err("eval is evil!".into()) Err("eval is evil!".into())
}, },
); );

View File

@ -1,8 +1,5 @@
use crate::def_package; use crate::def_package;
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::parser::INT;
use crate::stdlib::string::String;
// Comparison operators // Comparison operators
pub fn lt<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> { pub fn lt<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
@ -25,12 +22,6 @@ pub fn ne<T: PartialEq>(x: T, y: T) -> FuncReturn<bool> {
} }
// Logic operators // Logic operators
fn and(x: bool, y: bool) -> FuncReturn<bool> {
Ok(x && y)
}
fn or(x: bool, y: bool) -> FuncReturn<bool> {
Ok(x || y)
}
fn not(x: bool) -> FuncReturn<bool> { fn not(x: bool) -> FuncReturn<bool> {
Ok(!x) Ok(!x)
} }
@ -42,48 +33,26 @@ macro_rules! reg_op {
} }
def_package!(crate:LogicPackage:"Logical operators.", lib, { def_package!(crate:LogicPackage:"Logical operators.", lib, {
reg_op!(lib, "<", lt, INT, char);
reg_op!(lib, "<=", lte, INT, char);
reg_op!(lib, ">", gt, INT, char);
reg_op!(lib, ">=", gte, INT, char);
reg_op!(lib, "==", eq, INT, char, bool, ());
reg_op!(lib, "!=", ne, INT, char, bool, ());
// Special versions for strings - at least avoid copying the first string
lib.set_fn_2_mut("<", |x: &mut String, y: String| Ok(*x < y));
lib.set_fn_2_mut("<=", |x: &mut String, y: String| Ok(*x <= y));
lib.set_fn_2_mut(">", |x: &mut String, y: String| Ok(*x > y));
lib.set_fn_2_mut(">=", |x: &mut String, y: String| Ok(*x >= y));
lib.set_fn_2_mut("==", |x: &mut String, y: String| Ok(*x == y));
lib.set_fn_2_mut("!=", |x: &mut String, y: String| Ok(*x != y));
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, u32, u64, i128, u128);
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
reg_op!(lib, "<", lt, f32, f64); reg_op!(lib, "<", lt, f32);
reg_op!(lib, "<=", lte, f32, f64); reg_op!(lib, "<=", lte, f32);
reg_op!(lib, ">", gt, f32, f64); reg_op!(lib, ">", gt, f32);
reg_op!(lib, ">=", gte, f32, f64); reg_op!(lib, ">=", gte, f32);
reg_op!(lib, "==", eq, f32, f64); reg_op!(lib, "==", eq, f32);
reg_op!(lib, "!=", ne, f32, f64); reg_op!(lib, "!=", ne, f32);
} }
// `&&` and `||` are treated specially as they short-circuit.
// They are implemented as special `Expr` instances, not function calls.
//reg_op!(lib, "||", or, bool);
//reg_op!(lib, "&&", and, bool);
lib.set_fn_2("|", or);
lib.set_fn_2("&", and);
lib.set_fn_1("!", not); lib.set_fn_1("!", not);
}); });

View File

@ -4,12 +4,9 @@ use crate::any::Dynamic;
use crate::def_package; use crate::def_package;
use crate::engine::Map; use crate::engine::Map;
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::parser::INT; use crate::parser::{ImmutableString, INT};
use crate::stdlib::{ use crate::stdlib::{string::ToString, vec::Vec};
string::{String, ToString},
vec::Vec,
};
fn map_get_keys(map: &mut Map) -> FuncReturn<Vec<Dynamic>> { fn map_get_keys(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
Ok(map.iter().map(|(k, _)| k.to_string().into()).collect()) Ok(map.iter().map(|(k, _)| k.to_string().into()).collect())
@ -22,7 +19,7 @@ fn map_get_values(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
lib.set_fn_2_mut( lib.set_fn_2_mut(
"has", "has",
|map: &mut Map, prop: String| Ok(map.contains_key(&prop)), |map: &mut Map, prop: ImmutableString| Ok(map.contains_key(prop.as_str())),
); );
lib.set_fn_1_mut("len", |map: &mut Map| Ok(map.len() as INT)); lib.set_fn_1_mut("len", |map: &mut Map| Ok(map.len() as INT));
lib.set_fn_1_mut("clear", |map: &mut Map| { lib.set_fn_1_mut("clear", |map: &mut Map| {
@ -31,7 +28,7 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
}); });
lib.set_fn_2_mut( lib.set_fn_2_mut(
"remove", "remove",
|x: &mut Map, name: String| Ok(x.remove(&name).unwrap_or_else(|| ().into())), |x: &mut Map, name: ImmutableString| Ok(x.remove(name.as_str()).unwrap_or_else(|| ().into())),
); );
lib.set_fn_2_mut( lib.set_fn_2_mut(
"mixin", "mixin",
@ -42,6 +39,15 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
Ok(()) Ok(())
}, },
); );
lib.set_fn_2_mut(
"+=",
|map1: &mut Map, map2: Map| {
map2.into_iter().for_each(|(key, value)| {
map1.insert(key, value);
});
Ok(())
},
);
lib.set_fn_2( lib.set_fn_2(
"+", "+",
|mut map1: Map, map2: Map| { |mut map1: Map, map2: Map| {

View File

@ -1,12 +1,12 @@
//! Module containing all built-in _packages_ available to Rhai, plus facilities to define custom packages. //! Module containing all built-in _packages_ available to Rhai, plus facilities to define custom packages.
use crate::fn_native::{CallableFunction, IteratorFn}; use crate::fn_native::{CallableFunction, IteratorFn, Shared};
use crate::module::Module; use crate::module::Module;
use crate::utils::StaticVec; use crate::utils::StaticVec;
use crate::stdlib::{any::TypeId, boxed::Box, collections::HashMap, rc::Rc, sync::Arc, vec::Vec}; use crate::stdlib::any::TypeId;
mod arithmetic; pub(crate) mod arithmetic;
mod array_basic; mod array_basic;
mod eval; mod eval;
mod iter_basic; mod iter_basic;
@ -44,35 +44,27 @@ pub trait Package {
fn get(&self) -> PackageLibrary; fn get(&self) -> PackageLibrary;
} }
/// Type which `Rc`-wraps a `Module` to facilitate sharing library instances. /// A sharable `Module` to facilitate sharing library instances.
#[cfg(not(feature = "sync"))] pub type PackageLibrary = Shared<Module>;
pub type PackageLibrary = Rc<Module>;
/// Type which `Arc`-wraps a `Module` to facilitate sharing library instances.
#[cfg(feature = "sync")]
pub type PackageLibrary = Arc<Module>;
/// Type containing a collection of `PackageLibrary` instances. /// Type containing a collection of `PackageLibrary` instances.
/// All function and type iterator keys in the loaded packages are indexed for fast access. /// All function and type iterator keys in the loaded packages are indexed for fast access.
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub(crate) struct PackagesCollection { pub(crate) struct PackagesCollection(StaticVec<PackageLibrary>);
/// Collection of `PackageLibrary` instances.
packages: StaticVec<PackageLibrary>,
}
impl PackagesCollection { impl PackagesCollection {
/// Add a `PackageLibrary` into the `PackagesCollection`. /// Add a `PackageLibrary` into the `PackagesCollection`.
pub fn push(&mut self, package: PackageLibrary) { pub fn push(&mut self, package: PackageLibrary) {
// Later packages override previous ones. // Later packages override previous ones.
self.packages.insert(0, package); self.0.insert(0, package);
} }
/// Does the specified function hash key exist in the `PackagesCollection`? /// Does the specified function hash key exist in the `PackagesCollection`?
pub fn contains_fn(&self, hash: u64) -> bool { pub fn contains_fn(&self, hash: u64) -> bool {
self.packages.iter().any(|p| p.contains_fn(hash)) self.0.iter().any(|p| p.contains_fn(hash))
} }
/// Get specified function via its hash key. /// Get specified function via its hash key.
pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> { pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
self.packages self.0
.iter() .iter()
.map(|p| p.get_fn(hash)) .map(|p| p.get_fn(hash))
.find(|f| f.is_some()) .find(|f| f.is_some())
@ -80,11 +72,11 @@ impl PackagesCollection {
} }
/// Does the specified TypeId iterator exist in the `PackagesCollection`? /// Does the specified TypeId iterator exist in the `PackagesCollection`?
pub fn contains_iter(&self, id: TypeId) -> bool { pub fn contains_iter(&self, id: TypeId) -> bool {
self.packages.iter().any(|p| p.contains_iter(id)) self.0.iter().any(|p| p.contains_iter(id))
} }
/// Get the specified TypeId iterator. /// Get the specified TypeId iterator.
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> { pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
self.packages self.0
.iter() .iter()
.map(|p| p.get_iter(id)) .map(|p| p.get_iter(id))
.find(|f| f.is_some()) .find(|f| f.is_some())

View File

@ -1,7 +1,7 @@
use crate::def_package; use crate::def_package;
use crate::engine::{FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; use crate::engine::{FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT};
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::parser::INT; use crate::parser::{ImmutableString, INT};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::engine::Array;
@ -12,19 +12,19 @@ use crate::engine::Map;
use crate::stdlib::{ use crate::stdlib::{
fmt::{Debug, Display}, fmt::{Debug, Display},
format, format,
string::{String, ToString}, string::ToString,
}; };
// Register print and debug // Register print and debug
fn to_debug<T: Debug>(x: &mut T) -> FuncReturn<String> { fn to_debug<T: Debug>(x: &mut T) -> FuncReturn<ImmutableString> {
Ok(format!("{:?}", x)) Ok(format!("{:?}", x).into())
} }
fn to_string<T: Display>(x: &mut T) -> FuncReturn<String> { fn to_string<T: Display>(x: &mut T) -> FuncReturn<ImmutableString> {
Ok(format!("{}", x)) Ok(format!("{}", x).into())
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
fn format_map(x: &mut Map) -> FuncReturn<String> { fn format_map(x: &mut Map) -> FuncReturn<ImmutableString> {
Ok(format!("#{:?}", x)) Ok(format!("#{:?}", x).into())
} }
macro_rules! reg_op { macro_rules! reg_op {
@ -41,10 +41,10 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
lib.set_fn_1(KEYWORD_PRINT, |_: ()| Ok("".to_string())); lib.set_fn_1(KEYWORD_PRINT, |_: ()| Ok("".to_string()));
lib.set_fn_1(FUNC_TO_STRING, |_: ()| Ok("".to_string())); lib.set_fn_1(FUNC_TO_STRING, |_: ()| Ok("".to_string()));
lib.set_fn_1_mut(KEYWORD_PRINT, |s: &mut String| Ok(s.clone())); lib.set_fn_1(KEYWORD_PRINT, |s: ImmutableString| Ok(s));
lib.set_fn_1_mut(FUNC_TO_STRING, |s: &mut String| Ok(s.clone())); lib.set_fn_1(FUNC_TO_STRING, |s: ImmutableString| Ok(s));
reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, String); reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, ImmutableString);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
@ -78,29 +78,8 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
lib.set_fn_1_mut(KEYWORD_DEBUG, format_map); lib.set_fn_1_mut(KEYWORD_DEBUG, format_map);
} }
lib.set_fn_2( lib.set_fn_2("+", |s: ImmutableString, ch: char| Ok(s + ch));
"+", lib.set_fn_2_mut("+=", |s: &mut ImmutableString, ch: char| { *s += ch; Ok(()) });
|mut s: String, ch: char| { lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { *s += ch; Ok(()) });
s.push(ch); lib.set_fn_2_mut("append", |s: &mut ImmutableString, s2: ImmutableString| { *s += &s2; Ok(()) });
Ok(s)
},
);
lib.set_fn_2(
"+",
|mut s: String, s2: String| {
s.push_str(&s2);
Ok(s)
},
);
lib.set_fn_2_mut("append", |s: &mut String, ch: char| {
s.push(ch);
Ok(())
});
lib.set_fn_2_mut(
"append",
|s: &mut String, s2: String| {
s.push_str(&s2);
Ok(())
}
);
}); });

View File

@ -1,6 +1,6 @@
use crate::def_package; use crate::def_package;
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::parser::INT; use crate::parser::{ImmutableString, INT};
use crate::utils::StaticVec; use crate::utils::StaticVec;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -10,22 +10,21 @@ use crate::stdlib::{
fmt::Display, fmt::Display,
format, format,
string::{String, ToString}, string::{String, ToString},
vec::Vec,
}; };
fn prepend<T: Display>(x: T, y: String) -> FuncReturn<String> { fn prepend<T: Display>(x: T, y: ImmutableString) -> FuncReturn<ImmutableString> {
Ok(format!("{}{}", x, y)) Ok(format!("{}{}", x, y).into())
} }
fn append<T: Display>(x: String, y: T) -> FuncReturn<String> { fn append<T: Display>(x: ImmutableString, y: T) -> FuncReturn<ImmutableString> {
Ok(format!("{}{}", x, y)) Ok(format!("{}{}", x, y).into())
} }
fn sub_string(s: &mut String, start: INT, len: INT) -> FuncReturn<String> { fn sub_string(s: ImmutableString, start: INT, len: INT) -> FuncReturn<ImmutableString> {
let offset = if s.is_empty() || len <= 0 { let offset = if s.is_empty() || len <= 0 {
return Ok("".to_string()); return Ok("".to_string().into());
} else if start < 0 { } else if start < 0 {
0 0
} else if (start as usize) >= s.chars().count() { } else if (start as usize) >= s.chars().count() {
return Ok("".to_string()); return Ok("".to_string().into());
} else { } else {
start as usize start as usize
}; };
@ -38,36 +37,38 @@ fn sub_string(s: &mut String, start: INT, len: INT) -> FuncReturn<String> {
len as usize len as usize
}; };
Ok(chars.iter().skip(offset).take(len).cloned().collect()) Ok(chars
}
fn crop_string(s: &mut String, start: INT, len: INT) -> FuncReturn<()> {
let offset = if s.is_empty() || len <= 0 {
s.clear();
return Ok(());
} else if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
s.clear();
return Ok(());
} else {
start as usize
};
let chars: StaticVec<_> = s.chars().collect();
let len = if offset + (len as usize) > chars.len() {
chars.len() - offset
} else {
len as usize
};
s.clear();
chars
.iter() .iter()
.skip(offset) .skip(offset)
.take(len) .take(len)
.for_each(|&ch| s.push(ch)); .cloned()
.collect::<String>()
.into())
}
fn crop_string(s: &mut ImmutableString, start: INT, len: INT) -> FuncReturn<()> {
let offset = if s.is_empty() || len <= 0 {
s.make_mut().clear();
return Ok(());
} else if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
s.make_mut().clear();
return Ok(());
} else {
start as usize
};
let chars: StaticVec<_> = s.chars().collect();
let len = if offset + (len as usize) > chars.len() {
chars.len() - offset
} else {
len as usize
};
let copy = s.make_mut();
copy.clear();
copy.extend(chars.iter().skip(offset).take(len));
Ok(()) Ok(())
} }
@ -80,10 +81,10 @@ macro_rules! reg_op {
def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, {
reg_op!(lib, "+", append, INT, bool, char); reg_op!(lib, "+", append, INT, bool, char);
lib.set_fn_2_mut( "+", |x: &mut String, _: ()| Ok(x.clone())); lib.set_fn_2( "+", |x: ImmutableString, _: ()| Ok(x));
reg_op!(lib, "+", prepend, INT, bool, char); reg_op!(lib, "+", prepend, INT, bool, char);
lib.set_fn_2("+", |_: (), y: String| Ok(y)); lib.set_fn_2("+", |_: (), y: ImmutableString| Ok(y));
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
@ -100,22 +101,22 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
{ {
lib.set_fn_2("+", |x: String, y: Array| Ok(format!("{}{:?}", x, y))); lib.set_fn_2("+", |x: ImmutableString, y: Array| Ok(format!("{}{:?}", x, y)));
lib.set_fn_2("+", |x: Array, y: String| Ok(format!("{:?}{}", x, y))); lib.set_fn_2_mut("+", |x: &mut Array, y: ImmutableString| Ok(format!("{:?}{}", x, y)));
} }
lib.set_fn_1_mut("len", |s: &mut String| Ok(s.chars().count() as INT)); lib.set_fn_1("len", |s: ImmutableString| Ok(s.chars().count() as INT));
lib.set_fn_2_mut( lib.set_fn_2(
"contains", "contains",
|s: &mut String, ch: char| Ok(s.contains(ch)), |s: ImmutableString, ch: char| Ok(s.contains(ch)),
); );
lib.set_fn_2_mut( lib.set_fn_2(
"contains", "contains",
|s: &mut String, find: String| Ok(s.contains(&find)), |s: ImmutableString, find: ImmutableString| Ok(s.contains(find.as_str())),
); );
lib.set_fn_3_mut( lib.set_fn_3(
"index_of", "index_of",
|s: &mut String, ch: char, start: INT| { |s: ImmutableString, ch: char, start: INT| {
let start = if start < 0 { let start = if start < 0 {
0 0
} else if (start as usize) >= s.chars().count() { } else if (start as usize) >= s.chars().count() {
@ -130,17 +131,17 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
.unwrap_or(-1 as INT)) .unwrap_or(-1 as INT))
}, },
); );
lib.set_fn_2_mut( lib.set_fn_2(
"index_of", "index_of",
|s: &mut String, ch: char| { |s: ImmutableString, ch: char| {
Ok(s.find(ch) Ok(s.find(ch)
.map(|index| s[0..index].chars().count() as INT) .map(|index| s[0..index].chars().count() as INT)
.unwrap_or(-1 as INT)) .unwrap_or(-1 as INT))
}, },
); );
lib.set_fn_3_mut( lib.set_fn_3(
"index_of", "index_of",
|s: &mut String, find: String, start: INT| { |s: ImmutableString, find: ImmutableString, start: INT| {
let start = if start < 0 { let start = if start < 0 {
0 0
} else if (start as usize) >= s.chars().count() { } else if (start as usize) >= s.chars().count() {
@ -150,109 +151,106 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
}; };
Ok(s[start..] Ok(s[start..]
.find(&find) .find(find.as_str())
.map(|index| s[0..start + index].chars().count() as INT) .map(|index| s[0..start + index].chars().count() as INT)
.unwrap_or(-1 as INT)) .unwrap_or(-1 as INT))
}, },
); );
lib.set_fn_2_mut( lib.set_fn_2(
"index_of", "index_of",
|s: &mut String, find: String| { |s: ImmutableString, find: ImmutableString| {
Ok(s.find(&find) Ok(s.find(find.as_str())
.map(|index| s[0..index].chars().count() as INT) .map(|index| s[0..index].chars().count() as INT)
.unwrap_or(-1 as INT)) .unwrap_or(-1 as INT))
}, },
); );
lib.set_fn_1_mut("clear", |s: &mut String| { lib.set_fn_1_mut("clear", |s: &mut ImmutableString| {
s.clear(); s.make_mut().clear();
Ok(()) Ok(())
}); });
lib.set_fn_2_mut( "append", |s: &mut String, ch: char| { lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| {
s.push(ch); s.make_mut().push(ch);
Ok(()) Ok(())
}); });
lib.set_fn_2_mut( lib.set_fn_2_mut(
"append", "append",
|s: &mut String, add: String| { |s: &mut ImmutableString, add: ImmutableString| {
s.push_str(&add); s.make_mut().push_str(add.as_str());
Ok(()) Ok(())
} }
); );
lib.set_fn_3_mut( "sub_string", sub_string); lib.set_fn_3("sub_string", sub_string);
lib.set_fn_2_mut( lib.set_fn_2(
"sub_string", "sub_string",
|s: &mut String, start: INT| sub_string(s, start, s.len() as INT), |s: ImmutableString, start: INT| {
let len = s.len() as INT;
sub_string(s, start, len)
},
); );
lib.set_fn_3_mut( "crop", crop_string); lib.set_fn_3_mut("crop", crop_string);
lib.set_fn_2_mut( lib.set_fn_2_mut(
"crop", "crop",
|s: &mut String, start: INT| crop_string(s, start, s.len() as INT), |s: &mut ImmutableString, start: INT| crop_string(s, start, s.len() as INT),
); );
lib.set_fn_2_mut( lib.set_fn_2_mut(
"truncate", "truncate",
|s: &mut String, len: INT| { |s: &mut ImmutableString, len: INT| {
if len >= 0 { if len > 0 {
let chars: StaticVec<_> = s.chars().take(len as usize).collect(); let chars: StaticVec<_> = s.chars().collect();
s.clear(); let copy = s.make_mut();
chars.iter().for_each(|&ch| s.push(ch)); copy.clear();
copy.extend(chars.into_iter().take(len as usize));
} else { } else {
s.clear(); s.make_mut().clear();
} }
Ok(()) Ok(())
}, },
); );
lib.set_fn_3_mut( lib.set_fn_3_mut(
"pad", "pad",
|s: &mut String, len: INT, ch: char| { |s: &mut ImmutableString, len: INT, ch: char| {
for _ in 0..s.chars().count() - len as usize { let copy = s.make_mut();
s.push(ch); for _ in 0..copy.chars().count() - len as usize {
copy.push(ch);
} }
Ok(()) Ok(())
}, },
); );
lib.set_fn_3_mut( lib.set_fn_3_mut(
"replace", "replace",
|s: &mut String, find: String, sub: String| { |s: &mut ImmutableString, find: ImmutableString, sub: ImmutableString| {
let new_str = s.replace(&find, &sub); *s = s.replace(find.as_str(), sub.as_str()).into();
s.clear();
s.push_str(&new_str);
Ok(()) Ok(())
}, },
); );
lib.set_fn_3_mut( lib.set_fn_3_mut(
"replace", "replace",
|s: &mut String, find: String, sub: char| { |s: &mut ImmutableString, find: ImmutableString, sub: char| {
let new_str = s.replace(&find, &sub.to_string()); *s = s.replace(find.as_str(), &sub.to_string()).into();
s.clear();
s.push_str(&new_str);
Ok(()) Ok(())
}, },
); );
lib.set_fn_3_mut( lib.set_fn_3_mut(
"replace", "replace",
|s: &mut String, find: char, sub: String| { |s: &mut ImmutableString, find: char, sub: ImmutableString| {
let new_str = s.replace(&find.to_string(), &sub); *s = s.replace(&find.to_string(), sub.as_str()).into();
s.clear();
s.push_str(&new_str);
Ok(()) Ok(())
}, },
); );
lib.set_fn_3_mut( lib.set_fn_3_mut(
"replace", "replace",
|s: &mut String, find: char, sub: char| { |s: &mut ImmutableString, find: char, sub: char| {
let new_str = s.replace(&find.to_string(), &sub.to_string()); *s = s.replace(&find.to_string(), &sub.to_string()).into();
s.clear();
s.push_str(&new_str);
Ok(()) Ok(())
}, },
); );
lib.set_fn_1_mut( lib.set_fn_1_mut(
"trim", "trim",
|s: &mut String| { |s: &mut ImmutableString| {
let trimmed = s.trim(); let trimmed = s.trim();
if trimmed.len() < s.len() { if trimmed.len() < s.len() {
*s = trimmed.to_string(); *s = trimmed.to_string().into();
} }
Ok(()) Ok(())
}, },

View File

@ -50,6 +50,8 @@ pub type FLOAT = f64;
type PERR = ParseErrorType; type PERR = ParseErrorType;
pub use crate::utils::ImmutableString;
/// Compiled AST (abstract syntax tree) of a Rhai script. /// 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`.
@ -63,8 +65,8 @@ pub struct AST(
impl AST { impl AST {
/// Create a new `AST`. /// Create a new `AST`.
pub fn new(statements: Vec<Stmt>, fn_lib: FunctionsLib) -> Self { pub fn new(statements: Vec<Stmt>, lib: FunctionsLib) -> Self {
Self(statements, fn_lib) Self(statements, lib)
} }
/// Get the statements. /// Get the statements.
@ -78,7 +80,7 @@ impl AST {
} }
/// Get the script-defined functions. /// Get the script-defined functions.
pub(crate) fn fn_lib(&self) -> &FunctionsLib { pub(crate) fn lib(&self) -> &FunctionsLib {
&self.1 &self.1
} }
@ -361,11 +363,6 @@ impl Stmt {
} }
} }
#[cfg(not(feature = "no_module"))]
type MRef = Option<Box<ModuleRef>>;
#[cfg(feature = "no_module")]
type MRef = Option<ModuleRef>;
/// An expression. /// An expression.
/// ///
/// Each variant is at most one pointer in size (for speed), /// Each variant is at most one pointer in size (for speed),
@ -380,27 +377,34 @@ pub enum Expr {
/// Character constant. /// Character constant.
CharConstant(Box<(char, Position)>), CharConstant(Box<(char, Position)>),
/// String constant. /// String constant.
StringConstant(Box<(String, Position)>), StringConstant(Box<(ImmutableString, Position)>),
/// Variable access - ((variable name, position), optional modules, hash, optional index) /// Variable access - ((variable name, position), optional modules, hash, optional index)
Variable(Box<((String, Position), MRef, u64, Option<NonZeroUsize>)>), Variable(
Box<(
(String, Position),
Option<Box<ModuleRef>>,
u64,
Option<NonZeroUsize>,
)>,
),
/// Property access. /// Property access.
Property(Box<((String, String, String), Position)>), Property(Box<((String, String, String), Position)>),
/// { stmt } /// { stmt }
Stmt(Box<(Stmt, Position)>), Stmt(Box<(Stmt, Position)>),
/// func(expr, ... ) - ((function name, position), optional modules, hash, arguments, optional default value) /// func(expr, ... ) - ((function name, native_only, position), optional modules, hash, arguments, optional default value)
/// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls
/// and the function names are predictable, so no need to allocate a new `String`. /// and the function names are predictable, so no need to allocate a new `String`.
FnCall( FnCall(
Box<( Box<(
(Cow<'static, str>, Position), (Cow<'static, str>, bool, Position),
MRef, Option<Box<ModuleRef>>,
u64, u64,
StaticVec<Expr>, StaticVec<Expr>,
Option<Dynamic>, Option<Dynamic>,
)>, )>,
), ),
/// expr = expr /// expr op= expr
Assignment(Box<(Expr, Expr, Position)>), Assignment(Box<(Expr, Cow<'static, str>, Expr, Position)>),
/// lhs.rhs /// lhs.rhs
Dot(Box<(Expr, Expr, Position)>), Dot(Box<(Expr, Expr, Position)>),
/// expr[expr] /// expr[expr]
@ -501,13 +505,14 @@ impl Expr {
Self::Property(x) => x.1, Self::Property(x) => x.1,
Self::Stmt(x) => x.1, Self::Stmt(x) => x.1,
Self::Variable(x) => (x.0).1, Self::Variable(x) => (x.0).1,
Self::FnCall(x) => (x.0).1, Self::FnCall(x) => (x.0).2,
Self::Assignment(x) => x.0.position(),
Self::And(x) | Self::Or(x) | Self::In(x) => x.2, Self::And(x) | Self::Or(x) | Self::In(x) => x.2,
Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos, Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos,
Self::Assignment(x) | Self::Dot(x) | Self::Index(x) => x.0.position(), Self::Dot(x) | Self::Index(x) => x.0.position(),
} }
} }
@ -525,14 +530,14 @@ impl Expr {
Self::Variable(x) => (x.0).1 = new_pos, Self::Variable(x) => (x.0).1 = new_pos,
Self::Property(x) => x.1 = new_pos, Self::Property(x) => x.1 = new_pos,
Self::Stmt(x) => x.1 = new_pos, Self::Stmt(x) => x.1 = new_pos,
Self::FnCall(x) => (x.0).1 = new_pos, Self::FnCall(x) => (x.0).2 = new_pos,
Self::And(x) => x.2 = new_pos, Self::And(x) => x.2 = new_pos,
Self::Or(x) => x.2 = new_pos, Self::Or(x) => x.2 = new_pos,
Self::In(x) => x.2 = new_pos, Self::In(x) => x.2 = new_pos,
Self::True(pos) => *pos = new_pos, Self::True(pos) => *pos = new_pos,
Self::False(pos) => *pos = new_pos, Self::False(pos) => *pos = new_pos,
Self::Unit(pos) => *pos = new_pos, Self::Unit(pos) => *pos = new_pos,
Self::Assignment(x) => x.2 = new_pos, Self::Assignment(x) => x.3 = new_pos,
Self::Dot(x) => x.2 = new_pos, Self::Dot(x) => x.2 = new_pos,
Self::Index(x) => x.2 = new_pos, Self::Index(x) => x.2 = new_pos,
} }
@ -661,7 +666,7 @@ fn eat_token(input: &mut Peekable<TokenIterator>, token: Token) -> Position {
} }
/// Match a particular token, consuming it if matched. /// Match a particular token, consuming it if matched.
fn match_token(input: &mut Peekable<TokenIterator>, token: Token) -> Result<bool, Box<ParseError>> { fn match_token(input: &mut Peekable<TokenIterator>, token: Token) -> Result<bool, ParseError> {
let (t, _) = input.peek().unwrap(); let (t, _) = input.peek().unwrap();
if *t == token { if *t == token {
eat_token(input, token); eat_token(input, token);
@ -678,7 +683,7 @@ fn parse_paren_expr<'a>(
pos: Position, pos: Position,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, ParseError> {
if level > state.max_expr_depth { if level > state.max_expr_depth {
return Err(PERR::ExprTooDeep.into_err(pos)); return Err(PERR::ExprTooDeep.into_err(pos));
} }
@ -708,12 +713,11 @@ fn parse_call_expr<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
state: &mut ParseState, state: &mut ParseState,
id: String, id: String,
#[cfg(not(feature = "no_module"))] mut modules: Option<Box<ModuleRef>>, mut modules: Option<Box<ModuleRef>>,
#[cfg(feature = "no_module")] modules: Option<ModuleRef>,
begin: Position, begin: Position,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, ParseError> {
let (token, pos) = input.peek().unwrap(); let (token, pos) = input.peek().unwrap();
if level > state.max_expr_depth { if level > state.max_expr_depth {
@ -751,15 +755,16 @@ fn parse_call_expr<'a>(
let qualifiers = modules.iter().map(|(m, _)| m.as_str()); let qualifiers = modules.iter().map(|(m, _)| m.as_str());
calc_fn_hash(qualifiers, &id, 0, empty()) calc_fn_hash(qualifiers, &id, 0, empty())
} else { } else {
// Qualifiers (none) + function name + no parameters.
calc_fn_hash(empty(), &id, 0, empty()) calc_fn_hash(empty(), &id, 0, empty())
} }
}; };
// Qualifiers (none) + function name + no parameters. // Qualifiers (none) + function name + no parameters.
#[cfg(feature = "no_module")] #[cfg(feature = "no_module")]
let hash_fn_def = calc_fn_hash(empty(), &id, empty()); let hash_fn_def = calc_fn_hash(empty(), &id, 0, empty());
return Ok(Expr::FnCall(Box::new(( return Ok(Expr::FnCall(Box::new((
(id.into(), begin), (id.into(), false, begin),
modules, modules,
hash_fn_def, hash_fn_def,
args, args,
@ -792,15 +797,16 @@ fn parse_call_expr<'a>(
let qualifiers = modules.iter().map(|(m, _)| m.as_str()); let qualifiers = modules.iter().map(|(m, _)| m.as_str());
calc_fn_hash(qualifiers, &id, args.len(), empty()) calc_fn_hash(qualifiers, &id, args.len(), empty())
} else { } else {
// Qualifiers (none) + function name + number of arguments.
calc_fn_hash(empty(), &id, args.len(), empty()) calc_fn_hash(empty(), &id, args.len(), empty())
} }
}; };
// Qualifiers (none) + function name + dummy parameter types (one for each parameter). // Qualifiers (none) + function name + number of arguments.
#[cfg(feature = "no_module")] #[cfg(feature = "no_module")]
let hash_fn_def = calc_fn_hash(empty(), &id, args_iter); let hash_fn_def = calc_fn_hash(empty(), &id, args.len(), empty());
return Ok(Expr::FnCall(Box::new(( return Ok(Expr::FnCall(Box::new((
(id.into(), begin), (id.into(), false, begin),
modules, modules,
hash_fn_def, hash_fn_def,
args, args,
@ -844,7 +850,7 @@ fn parse_index_chain<'a>(
pos: Position, pos: Position,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, ParseError> {
if level > state.max_expr_depth { if level > state.max_expr_depth {
return Err(PERR::ExprTooDeep.into_err(pos)); return Err(PERR::ExprTooDeep.into_err(pos));
} }
@ -872,30 +878,25 @@ fn parse_index_chain<'a>(
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(x) => { Expr::FloatConstant(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(), "Only arrays, object maps and strings can be indexed".into(),
) )
.into_err(x.1)) .into_err(lhs.position()))
} }
Expr::CharConstant(x) => { Expr::CharConstant(_)
| Expr::Assignment(_)
| Expr::And(_)
| Expr::Or(_)
| Expr::In(_)
| Expr::True(_)
| Expr::False(_)
| Expr::Unit(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(), "Only arrays, object maps and strings can be indexed".into(),
) )
.into_err(x.1)) .into_err(lhs.position()))
}
Expr::Assignment(x) | Expr::And(x) | Expr::Or(x) | Expr::In(x) => {
return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(),
)
.into_err(x.2))
}
Expr::True(pos) | Expr::False(pos) | Expr::Unit(pos) => {
return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(),
)
.into_err(pos))
} }
_ => (), _ => (),
@ -913,32 +914,25 @@ fn parse_index_chain<'a>(
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(x) => { Expr::FloatConstant(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(), "Only arrays, object maps and strings can be indexed".into(),
) )
.into_err(x.1)) .into_err(lhs.position()))
} }
Expr::CharConstant(x) => { Expr::CharConstant(_)
| Expr::Assignment(_)
| Expr::And(_)
| Expr::Or(_)
| Expr::In(_)
| Expr::True(_)
| Expr::False(_)
| Expr::Unit(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(), "Only arrays, object maps and strings can be indexed".into(),
) )
.into_err(x.1)) .into_err(lhs.position()))
}
Expr::Assignment(x) | Expr::And(x) | Expr::Or(x) | Expr::In(x) => {
return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(),
)
.into_err(x.2))
}
Expr::True(pos) | Expr::False(pos) | Expr::Unit(pos) => {
return Err(PERR::MalformedIndexExpr(
"Only arrays, object maps and strings can be indexed".into(),
)
.into_err(pos))
} }
_ => (), _ => (),
@ -946,46 +940,46 @@ fn parse_index_chain<'a>(
// lhs[float] // lhs[float]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(x) => { x @ Expr::FloatConstant(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a float".into(), "Array access expects integer index, not a float".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// lhs[char] // lhs[char]
Expr::CharConstant(x) => { x @ Expr::CharConstant(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a character".into(), "Array access expects integer index, not a character".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// lhs[??? = ??? ] // lhs[??? = ??? ]
Expr::Assignment(x) => { x @ Expr::Assignment(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not ()".into(), "Array access expects integer index, not an assignment".into(),
) )
.into_err(x.2)) .into_err(x.position()))
} }
// lhs[()] // lhs[()]
Expr::Unit(pos) => { x @ Expr::Unit(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not ()".into(), "Array access expects integer index, not ()".into(),
) )
.into_err(*pos)) .into_err(x.position()))
} }
// lhs[??? && ???], lhs[??? || ???], lhs[??? in ???] // lhs[??? && ???], lhs[??? || ???], lhs[??? in ???]
Expr::And(x) | Expr::Or(x) | Expr::In(x) => { x @ Expr::And(_) | x @ Expr::Or(_) | x @ Expr::In(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a boolean".into(), "Array access expects integer index, not a boolean".into(),
) )
.into_err(x.2)) .into_err(x.position()))
} }
// lhs[true], lhs[false] // lhs[true], lhs[false]
Expr::True(pos) | Expr::False(pos) => { x @ Expr::True(_) | x @ Expr::False(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a boolean".into(), "Array access expects integer index, not a boolean".into(),
) )
.into_err(*pos)) .into_err(x.position()))
} }
// All other expressions // All other expressions
_ => (), _ => (),
@ -1033,7 +1027,7 @@ fn parse_array_literal<'a>(
pos: Position, pos: Position,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, ParseError> {
if level > state.max_expr_depth { if level > state.max_expr_depth {
return Err(PERR::ExprTooDeep.into_err(pos)); return Err(PERR::ExprTooDeep.into_err(pos));
} }
@ -1081,7 +1075,7 @@ fn parse_map_literal<'a>(
pos: Position, pos: Position,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, ParseError> {
if level > state.max_expr_depth { if level > state.max_expr_depth {
return Err(PERR::ExprTooDeep.into_err(pos)); return Err(PERR::ExprTooDeep.into_err(pos));
} }
@ -1182,7 +1176,7 @@ fn parse_primary<'a>(
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, ParseError> {
let (token, pos) = input.peek().unwrap(); let (token, pos) = input.peek().unwrap();
let pos = *pos; let pos = *pos;
@ -1205,7 +1199,7 @@ fn parse_primary<'a>(
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, pos))), Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, pos))),
Token::CharConstant(c) => Expr::CharConstant(Box::new((c, pos))), Token::CharConstant(c) => Expr::CharConstant(Box::new((c, pos))),
Token::StringConst(s) => Expr::StringConstant(Box::new((s, pos))), Token::StringConst(s) => Expr::StringConstant(Box::new((s.into(), pos))),
Token::Identifier(s) => { Token::Identifier(s) => {
let index = state.find(&s); let index = state.find(&s);
Expr::Variable(Box::new(((s, pos), None, 0, index))) Expr::Variable(Box::new(((s, pos), None, 0, index)))
@ -1290,7 +1284,7 @@ fn parse_unary<'a>(
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, ParseError> {
let (token, pos) = input.peek().unwrap(); let (token, pos) = input.peek().unwrap();
let pos = *pos; let pos = *pos;
@ -1345,7 +1339,7 @@ fn parse_unary<'a>(
args.push(expr); args.push(expr);
Ok(Expr::FnCall(Box::new(( Ok(Expr::FnCall(Box::new((
(op.into(), pos), (op.into(), true, pos),
None, None,
hash, hash,
args, args,
@ -1369,7 +1363,7 @@ fn parse_unary<'a>(
let hash = calc_fn_hash(empty(), op, 2, empty()); let hash = calc_fn_hash(empty(), op, 2, empty());
Ok(Expr::FnCall(Box::new(( Ok(Expr::FnCall(Box::new((
(op.into(), pos), (op.into(), true, pos),
None, None,
hash, hash,
args, args,
@ -1384,17 +1378,22 @@ fn parse_unary<'a>(
} }
fn make_assignment_stmt<'a>( fn make_assignment_stmt<'a>(
fn_name: Cow<'static, str>,
state: &mut ParseState, state: &mut ParseState,
lhs: Expr, lhs: Expr,
rhs: Expr, rhs: Expr,
pos: Position, pos: Position,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, ParseError> {
match &lhs { match &lhs {
Expr::Variable(x) if x.3.is_none() => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))), Expr::Variable(x) if x.3.is_none() => {
Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos))))
}
Expr::Variable(x) => { Expr::Variable(x) => {
let ((name, name_pos), _, _, index) = x.as_ref(); let ((name, name_pos), _, _, index) = x.as_ref();
match state.stack[(state.len() - index.unwrap().get())].1 { match state.stack[(state.len() - index.unwrap().get())].1 {
ScopeEntryType::Normal => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))), ScopeEntryType::Normal => {
Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos))))
}
// Constant values cannot be assigned to // Constant values cannot be assigned to
ScopeEntryType::Constant => { ScopeEntryType::Constant => {
Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos)) Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos))
@ -1403,11 +1402,15 @@ fn make_assignment_stmt<'a>(
} }
} }
Expr::Index(x) | Expr::Dot(x) => match &x.0 { Expr::Index(x) | Expr::Dot(x) => match &x.0 {
Expr::Variable(x) if x.3.is_none() => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))), Expr::Variable(x) if x.3.is_none() => {
Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos))))
}
Expr::Variable(x) => { Expr::Variable(x) => {
let ((name, name_pos), _, _, index) = x.as_ref(); let ((name, name_pos), _, _, index) = x.as_ref();
match state.stack[(state.len() - index.unwrap().get())].1 { match state.stack[(state.len() - index.unwrap().get())].1 {
ScopeEntryType::Normal => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))), ScopeEntryType::Normal => {
Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos))))
}
// Constant values cannot be assigned to // Constant values cannot be assigned to
ScopeEntryType::Constant => { ScopeEntryType::Constant => {
Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos)) Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos))
@ -1431,7 +1434,7 @@ fn parse_op_assignment_stmt<'a>(
lhs: Expr, lhs: Expr,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, ParseError> {
let (token, pos) = input.peek().unwrap(); let (token, pos) = input.peek().unwrap();
let pos = *pos; let pos = *pos;
@ -1440,40 +1443,26 @@ fn parse_op_assignment_stmt<'a>(
} }
let op = match token { let op = match token {
Token::Equals => { Token::Equals => "".into(),
let pos = eat_token(input, Token::Equals);
let rhs = parse_expr(input, state, level + 1, allow_stmt_expr)?; Token::PlusAssign
return make_assignment_stmt(state, lhs, rhs, pos); | Token::MinusAssign
} | Token::MultiplyAssign
Token::PlusAssign => Token::Plus.syntax(), | Token::DivideAssign
Token::MinusAssign => Token::Minus.syntax(), | Token::LeftShiftAssign
Token::MultiplyAssign => Token::Multiply.syntax(), | Token::RightShiftAssign
Token::DivideAssign => Token::Divide.syntax(), | Token::ModuloAssign
Token::LeftShiftAssign => Token::LeftShift.syntax(), | Token::PowerOfAssign
Token::RightShiftAssign => Token::RightShift.syntax(), | Token::AndAssign
Token::ModuloAssign => Token::Modulo.syntax(), | Token::OrAssign
Token::PowerOfAssign => Token::PowerOf.syntax(), | Token::XOrAssign => token.syntax(),
Token::AndAssign => Token::Ampersand.syntax(),
Token::OrAssign => Token::Pipe.syntax(),
Token::XOrAssign => Token::XOr.syntax(),
_ => return Ok(lhs), _ => return Ok(lhs),
}; };
input.next(); let (_, pos) = input.next().unwrap();
let lhs_copy = lhs.clone();
let rhs = parse_expr(input, state, level + 1, allow_stmt_expr)?; let rhs = parse_expr(input, state, level + 1, allow_stmt_expr)?;
make_assignment_stmt(op, state, lhs, rhs, pos)
// lhs op= rhs -> lhs = op(lhs, rhs)
let mut args = StaticVec::new();
args.push(lhs_copy);
args.push(rhs);
let hash = calc_fn_hash(empty(), &op, args.len(), empty());
let rhs_expr = Expr::FnCall(Box::new(((op, pos), None, hash, args, None)));
make_assignment_stmt(state, lhs, rhs_expr, pos)
} }
/// Make a dot expression. /// Make a dot expression.
@ -1482,7 +1471,7 @@ fn make_dot_expr(
rhs: Expr, rhs: Expr,
op_pos: Position, op_pos: Position,
is_index: bool, is_index: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, ParseError> {
Ok(match (lhs, rhs) { Ok(match (lhs, rhs) {
// idx_lhs[idx_rhs].rhs // idx_lhs[idx_rhs].rhs
// Attach dot chain to the bottom level of indexing chain // Attach dot chain to the bottom level of indexing chain
@ -1544,35 +1533,28 @@ fn make_dot_expr(
} }
/// Make an 'in' expression. /// Make an 'in' expression.
fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, Box<ParseError>> { fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseError> {
match (&lhs, &rhs) { match (&lhs, &rhs) {
(_, Expr::IntegerConstant(x)) => { (_, x @ Expr::IntegerConstant(_))
| (_, x @ Expr::And(_))
| (_, x @ Expr::Or(_))
| (_, x @ Expr::In(_))
| (_, x @ Expr::Assignment(_))
| (_, x @ Expr::True(_))
| (_, x @ Expr::False(_))
| (_, x @ Expr::Unit(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression expects a string, array or object map".into(), "'in' expression expects a string, array or object map".into(),
) )
.into_err(x.1)) .into_err(x.position()))
}
(_, Expr::And(x)) | (_, Expr::Or(x)) | (_, Expr::In(x)) | (_, Expr::Assignment(x)) => {
return Err(PERR::MalformedInExpr(
"'in' expression expects a string, array or object map".into(),
)
.into_err(x.2))
}
(_, Expr::True(pos)) | (_, Expr::False(pos)) | (_, Expr::Unit(pos)) => {
return Err(PERR::MalformedInExpr(
"'in' expression expects a string, array or object map".into(),
)
.into_err(*pos))
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
(_, Expr::FloatConstant(x)) => { (_, x @ Expr::FloatConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression expects a string, array or object map".into(), "'in' expression expects a string, array or object map".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// "xxx" in "xxxx", 'x' in "xxxx" - OK! // "xxx" in "xxxx", 'x' in "xxxx" - OK!
@ -1581,63 +1563,58 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, Box<Pars
// 123.456 in "xxxx" // 123.456 in "xxxx"
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
(Expr::FloatConstant(x), Expr::StringConstant(_)) => { (x @ Expr::FloatConstant(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not a float".into(), "'in' expression for a string expects a string, not a float".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// 123 in "xxxx" // 123 in "xxxx"
(Expr::IntegerConstant(x), Expr::StringConstant(_)) => { (x @ Expr::IntegerConstant(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not a number".into(), "'in' expression for a string expects a string, not a number".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// (??? && ???) in "xxxx", (??? || ???) in "xxxx", (??? in ???) in "xxxx", // (??? && ???) in "xxxx", (??? || ???) in "xxxx", (??? in ???) in "xxxx",
(Expr::And(x), Expr::StringConstant(_))
| (Expr::Or(x), Expr::StringConstant(_))
| (Expr::In(x), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not a boolean".into(),
)
.into_err(x.2))
}
// true in "xxxx", false in "xxxx" // true in "xxxx", false in "xxxx"
(Expr::True(pos), Expr::StringConstant(_)) (x @ Expr::And(_), Expr::StringConstant(_))
| (Expr::False(pos), Expr::StringConstant(_)) => { | (x @ Expr::Or(_), Expr::StringConstant(_))
| (x @ Expr::In(_), Expr::StringConstant(_))
| (x @ Expr::True(_), Expr::StringConstant(_))
| (x @ Expr::False(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not a boolean".into(), "'in' expression for a string expects a string, not a boolean".into(),
) )
.into_err(*pos)) .into_err(x.position()))
} }
// [???, ???, ???] in "xxxx" // [???, ???, ???] in "xxxx"
(Expr::Array(x), Expr::StringConstant(_)) => { (x @ Expr::Array(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not an array".into(), "'in' expression for a string expects a string, not an array".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// #{...} in "xxxx" // #{...} in "xxxx"
(Expr::Map(x), Expr::StringConstant(_)) => { (x @ Expr::Map(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not an object map".into(), "'in' expression for a string expects a string, not an object map".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// (??? = ???) in "xxxx" // (??? = ???) in "xxxx"
(Expr::Assignment(x), Expr::StringConstant(_)) => { (x @ Expr::Assignment(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not ()".into(), "'in' expression for a string expects a string, not an assignment".into(),
) )
.into_err(x.2)) .into_err(x.position()))
} }
// () in "xxxx" // () in "xxxx"
(Expr::Unit(pos), Expr::StringConstant(_)) => { (x @ Expr::Unit(_), Expr::StringConstant(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for a string expects a string, not ()".into(), "'in' expression for a string expects a string, not ()".into(),
) )
.into_err(*pos)) .into_err(x.position()))
} }
// "xxx" in #{...}, 'x' in #{...} - OK! // "xxx" in #{...}, 'x' in #{...} - OK!
@ -1645,62 +1622,58 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, Box<Pars
// 123.456 in #{...} // 123.456 in #{...}
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
(Expr::FloatConstant(x), Expr::Map(_)) => { (x @ Expr::FloatConstant(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not a float".into(), "'in' expression for an object map expects a string, not a float".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// 123 in #{...} // 123 in #{...}
(Expr::IntegerConstant(x), Expr::Map(_)) => { (x @ Expr::IntegerConstant(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not a number".into(), "'in' expression for an object map expects a string, not a number".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// (??? && ???) in #{...}, (??? || ???) in #{...}, (??? in ???) in #{...}, // (??? && ???) in #{...}, (??? || ???) in #{...}, (??? in ???) in #{...},
(Expr::And(x), Expr::Map(_))
| (Expr::Or(x), Expr::Map(_))
| (Expr::In(x), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not a boolean".into(),
)
.into_err(x.2))
}
// true in #{...}, false in #{...} // true in #{...}, false in #{...}
(Expr::True(pos), Expr::Map(_)) | (Expr::False(pos), Expr::Map(_)) => { (x @ Expr::And(_), Expr::Map(_))
| (x @ Expr::Or(_), Expr::Map(_))
| (x @ Expr::In(_), Expr::Map(_))
| (x @ Expr::True(_), Expr::Map(_))
| (x @ Expr::False(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not a boolean".into(), "'in' expression for an object map expects a string, not a boolean".into(),
) )
.into_err(*pos)) .into_err(x.position()))
} }
// [???, ???, ???] in #{..} // [???, ???, ???] in #{..}
(Expr::Array(x), Expr::Map(_)) => { (x @ Expr::Array(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not an array".into(), "'in' expression for an object map expects a string, not an array".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// #{...} in #{..} // #{...} in #{..}
(Expr::Map(x), Expr::Map(_)) => { (x @ Expr::Map(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not an object map".into(), "'in' expression for an object map expects a string, not an object map".into(),
) )
.into_err(x.1)) .into_err(x.position()))
} }
// (??? = ???) in #{...} // (??? = ???) in #{...}
(Expr::Assignment(x), Expr::Map(_)) => { (x @ Expr::Assignment(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not ()".into(), "'in' expression for an object map expects a string, not an assignment".into(),
) )
.into_err(x.2)) .into_err(x.position()))
} }
// () in #{...} // () in #{...}
(Expr::Unit(pos), Expr::Map(_)) => { (x @ Expr::Unit(_), Expr::Map(_)) => {
return Err(PERR::MalformedInExpr( return Err(PERR::MalformedInExpr(
"'in' expression for an object map expects a string, not ()".into(), "'in' expression for an object map expects a string, not ()".into(),
) )
.into_err(*pos)) .into_err(x.position()))
} }
_ => (), _ => (),
@ -1717,7 +1690,7 @@ fn parse_binary_op<'a>(
lhs: Expr, lhs: Expr,
mut level: usize, mut level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, ParseError> {
if level > state.max_expr_depth { if level > state.max_expr_depth {
return Err(PERR::ExprTooDeep.into_err(lhs.position())); return Err(PERR::ExprTooDeep.into_err(lhs.position()));
} }
@ -1765,26 +1738,30 @@ fn parse_binary_op<'a>(
args.push(rhs); args.push(rhs);
root = match op_token { root = match op_token {
Token::Plus => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), Token::Plus => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::Minus => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), Token::Minus => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::Multiply => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), Token::Multiply => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::Divide => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), Token::Divide => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::LeftShift => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), Token::LeftShift => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::RightShift => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), Token::RightShift => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::Modulo => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), Token::Modulo => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::PowerOf => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), Token::PowerOf => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
// Comparison operators default to false when passed invalid operands // Comparison operators default to false when passed invalid operands
Token::EqualsTo => Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))), Token::EqualsTo => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def))),
Token::NotEqualsTo => Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))), Token::NotEqualsTo => {
Token::LessThan => Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))), Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def)))
Token::LessThanEqualsTo => { }
Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))) Token::LessThan => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def))),
Token::LessThanEqualsTo => {
Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def)))
}
Token::GreaterThan => {
Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def)))
} }
Token::GreaterThan => Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))),
Token::GreaterThanEqualsTo => { Token::GreaterThanEqualsTo => {
Expr::FnCall(Box::new(((op, pos), None, hash, args, cmp_def))) Expr::FnCall(Box::new(((op, true, pos), None, hash, args, cmp_def)))
} }
Token::Or => { Token::Or => {
@ -1797,9 +1774,9 @@ fn parse_binary_op<'a>(
let current_lhs = args.pop(); let current_lhs = args.pop();
Expr::And(Box::new((current_lhs, rhs, pos))) Expr::And(Box::new((current_lhs, rhs, pos)))
} }
Token::Ampersand => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), Token::Ampersand => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::Pipe => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), Token::Pipe => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::XOr => Expr::FnCall(Box::new(((op, pos), None, hash, args, None))), Token::XOr => Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))),
Token::In => { Token::In => {
let rhs = args.pop(); let rhs = args.pop();
@ -1815,7 +1792,7 @@ fn parse_binary_op<'a>(
match &mut rhs { match &mut rhs {
// current_lhs.rhs(...) - method call // current_lhs.rhs(...) - method call
Expr::FnCall(x) => { Expr::FnCall(x) => {
let ((id, _), _, hash, args, _) = x.as_mut(); let ((id, _, _), _, hash, args, _) = x.as_mut();
// Recalculate function call hash because there is an additional argument // Recalculate function call hash because there is an additional argument
*hash = calc_fn_hash(empty(), id, args.len() + 1, empty()); *hash = calc_fn_hash(empty(), id, args.len() + 1, empty());
} }
@ -1836,7 +1813,7 @@ fn parse_expr<'a>(
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, ParseError> {
let (_, pos) = input.peek().unwrap(); let (_, pos) = input.peek().unwrap();
if level > state.max_expr_depth { if level > state.max_expr_depth {
@ -1851,7 +1828,7 @@ fn parse_expr<'a>(
fn ensure_not_statement_expr<'a>( fn ensure_not_statement_expr<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
type_name: &str, type_name: &str,
) -> Result<(), Box<ParseError>> { ) -> Result<(), ParseError> {
match input.peek().unwrap() { match input.peek().unwrap() {
// Disallow statement expressions // Disallow statement expressions
(Token::LeftBrace, pos) | (Token::EOF, pos) => { (Token::LeftBrace, pos) | (Token::EOF, pos) => {
@ -1863,9 +1840,7 @@ fn ensure_not_statement_expr<'a>(
} }
/// Make sure that the expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`). /// Make sure that the expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`).
fn ensure_not_assignment<'a>( fn ensure_not_assignment<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<(), ParseError> {
input: &mut Peekable<TokenIterator<'a>>,
) -> Result<(), Box<ParseError>> {
match input.peek().unwrap() { match input.peek().unwrap() {
(Token::Equals, pos) => { (Token::Equals, pos) => {
return Err(PERR::BadInput("Possibly a typo of '=='?".to_string()).into_err(*pos)) return Err(PERR::BadInput("Possibly a typo of '=='?".to_string()).into_err(*pos))
@ -1898,7 +1873,7 @@ fn parse_if<'a>(
breakable: bool, breakable: bool,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, ParseError> {
// if ... // if ...
let pos = eat_token(input, Token::If); let pos = eat_token(input, Token::If);
@ -1934,7 +1909,7 @@ fn parse_while<'a>(
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, ParseError> {
// while ... // while ...
let pos = eat_token(input, Token::While); let pos = eat_token(input, Token::While);
@ -1957,7 +1932,7 @@ fn parse_loop<'a>(
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, ParseError> {
// loop ... // loop ...
let pos = eat_token(input, Token::Loop); let pos = eat_token(input, Token::Loop);
@ -1977,7 +1952,7 @@ fn parse_for<'a>(
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, ParseError> {
// for ... // for ...
let pos = eat_token(input, Token::For); let pos = eat_token(input, Token::For);
@ -2030,7 +2005,7 @@ fn parse_let<'a>(
var_type: ScopeEntryType, var_type: ScopeEntryType,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, ParseError> {
// let/const... (specified in `var_type`) // let/const... (specified in `var_type`)
let (_, pos) = input.next().unwrap(); let (_, pos) = input.next().unwrap();
@ -2091,7 +2066,7 @@ fn parse_import<'a>(
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, ParseError> {
// import ... // import ...
let pos = eat_token(input, Token::Import); let pos = eat_token(input, Token::Import);
@ -2129,7 +2104,7 @@ fn parse_export<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, ParseError> {
let pos = eat_token(input, Token::Export); let pos = eat_token(input, Token::Export);
if level > state.max_expr_depth { if level > state.max_expr_depth {
@ -2196,7 +2171,7 @@ fn parse_block<'a>(
breakable: bool, breakable: bool,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, ParseError> {
// Must start with { // Must start with {
let pos = match input.next().unwrap() { let pos = match input.next().unwrap() {
(Token::LeftBrace, pos) => pos, (Token::LeftBrace, pos) => pos,
@ -2267,7 +2242,7 @@ fn parse_expr_stmt<'a>(
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, ParseError> {
let (_, pos) = input.peek().unwrap(); let (_, pos) = input.peek().unwrap();
if level > state.max_expr_depth { if level > state.max_expr_depth {
@ -2287,7 +2262,7 @@ fn parse_stmt<'a>(
is_global: bool, is_global: bool,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, ParseError> {
use ScopeEntryType::{Constant, Normal}; use ScopeEntryType::{Constant, Normal};
let (token, pos) = match input.peek().unwrap() { let (token, pos) = match input.peek().unwrap() {
@ -2376,7 +2351,7 @@ fn parse_fn<'a>(
access: FnAccess, access: FnAccess,
level: usize, level: usize,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<FnDef, Box<ParseError>> { ) -> Result<FnDef, ParseError> {
let pos = eat_token(input, Token::Fn); let pos = eat_token(input, Token::Fn);
if level > state.max_expr_depth { if level > state.max_expr_depth {
@ -2467,7 +2442,7 @@ pub fn parse_global_expr<'a>(
scope: &Scope, scope: &Scope,
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
max_expr_depth: usize, max_expr_depth: usize,
) -> Result<AST, Box<ParseError>> { ) -> Result<AST, ParseError> {
let mut state = ParseState::new(max_expr_depth); let mut state = ParseState::new(max_expr_depth);
let expr = parse_expr(input, &mut state, 0, false)?; let expr = parse_expr(input, &mut state, 0, false)?;
@ -2495,7 +2470,7 @@ pub fn parse_global_expr<'a>(
fn parse_global_level<'a>( fn parse_global_level<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
max_expr_depth: (usize, usize), max_expr_depth: (usize, usize),
) -> Result<(Vec<Stmt>, HashMap<u64, FnDef>), Box<ParseError>> { ) -> Result<(Vec<Stmt>, HashMap<u64, FnDef>), ParseError> {
let mut statements = Vec::<Stmt>::new(); let mut statements = Vec::<Stmt>::new();
let mut functions = HashMap::<u64, FnDef>::new(); let mut functions = HashMap::<u64, FnDef>::new();
let mut state = ParseState::new(max_expr_depth.0); let mut state = ParseState::new(max_expr_depth.0);
@ -2577,13 +2552,13 @@ pub fn parse<'a>(
scope: &Scope, scope: &Scope,
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
max_expr_depth: (usize, usize), max_expr_depth: (usize, usize),
) -> Result<AST, Box<ParseError>> { ) -> Result<AST, ParseError> {
let (statements, functions) = parse_global_level(input, max_expr_depth)?; let (statements, functions) = parse_global_level(input, max_expr_depth)?;
let fn_lib = functions.into_iter().map(|(_, v)| v).collect(); let lib = functions.into_iter().map(|(_, v)| v).collect();
Ok( Ok(
// Optimize AST // Optimize AST
optimize_into_ast(engine, scope, statements, fn_lib, optimization_level), optimize_into_ast(engine, scope, statements, lib, optimization_level),
) )
} }
@ -2598,7 +2573,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
Union::Unit(_) => Some(Expr::Unit(pos)), Union::Unit(_) => Some(Expr::Unit(pos)),
Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))), Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))),
Union::Char(value) => Some(Expr::CharConstant(Box::new((value, pos)))), Union::Char(value) => Some(Expr::CharConstant(Box::new((value, pos)))),
Union::Str(value) => Some(Expr::StringConstant(Box::new(((*value).clone(), pos)))), Union::Str(value) => Some(Expr::StringConstant(Box::new((value.clone(), pos)))),
Union::Bool(true) => Some(Expr::True(pos)), Union::Bool(true) => Some(Expr::True(pos)),
Union::Bool(false) => Some(Expr::False(pos)), Union::Bool(false) => Some(Expr::False(pos)),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]

View File

@ -23,7 +23,7 @@ use crate::stdlib::path::PathBuf;
#[derive(Debug)] #[derive(Debug)]
pub enum EvalAltResult { pub enum EvalAltResult {
/// Syntax error. /// Syntax error.
ErrorParsing(Box<ParseError>), ErrorParsing(ParseError),
/// Error reading from a script file. Wrapped value is the path of the script file. /// Error reading from a script file. Wrapped value is the path of the script file.
/// ///
@ -241,11 +241,6 @@ impl fmt::Display for EvalAltResult {
impl From<ParseError> for Box<EvalAltResult> { impl From<ParseError> for Box<EvalAltResult> {
fn from(err: ParseError) -> Self { fn from(err: ParseError) -> Self {
Box::new(EvalAltResult::ErrorParsing(Box::new(err)))
}
}
impl From<Box<ParseError>> for Box<EvalAltResult> {
fn from(err: Box<ParseError>) -> Self {
Box::new(EvalAltResult::ErrorParsing(err)) Box::new(EvalAltResult::ErrorParsing(err))
} }
} }

View File

@ -21,8 +21,8 @@ type LERR = LexError;
/// A location (line number + character position) in the input script. /// A location (line number + character position) in the input script.
/// ///
/// In order to keep footprint small, both line number and character position have 16-bit resolution, /// In order to keep footprint small, both line number and character position have 16-bit unsigned resolution,
/// meaning they go up to a maximum of 65,535 lines/characters per line. /// meaning they go up to a maximum of 65,535 lines and characters per line.
/// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect. /// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position { pub struct Position {

View File

@ -42,6 +42,18 @@ pub fn unsafe_cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, 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<str>` of /// A dangerous function that blindly casts a `&str` from one lifetime to a `Cow<str>` of
/// another lifetime. This is mainly used to let us push a block-local variable into the /// another lifetime. This is mainly used to let us push a block-local variable into the
/// current `Scope` without cloning the variable name. Doing this is safe because all local /// current `Scope` without cloning the variable name. Doing this is safe because all local

View File

@ -4,14 +4,20 @@
//! //!
//! The `StaticVec` type has some `unsafe` blocks to handle conversions between `MaybeUninit` and regular types. //! The `StaticVec` type has some `unsafe` blocks to handle conversions between `MaybeUninit` and regular types.
use crate::fn_native::{shared_make_mut, shared_take, Shared};
use crate::stdlib::{ use crate::stdlib::{
any::TypeId, any::TypeId,
borrow::Borrow,
boxed::Box,
fmt, fmt,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter::FromIterator, iter::FromIterator,
mem, mem,
mem::MaybeUninit, mem::MaybeUninit,
ops::{Drop, Index, IndexMut}, ops::{Add, AddAssign, Deref, Drop, Index, IndexMut},
str::FromStr,
string::{String, ToString},
vec::Vec, vec::Vec,
}; };
@ -560,3 +566,273 @@ impl<T> From<Vec<T>> for StaticVec<T> {
arr arr
} }
} }
/// The system immutable string type.
///
/// An `ImmutableString` wraps an `Rc<String>` (or `Arc<String>` under the `sync` feature)
/// so that it can be simply shared and not cloned.
///
/// # Examples
///
/// ```
/// use rhai::ImmutableString;
///
/// let s1: ImmutableString = "hello".into();
///
/// // No actual cloning of the string is involved below.
/// let s2 = s1.clone();
/// let s3 = s2.clone();
///
/// assert_eq!(s1, s2);
///
/// // Clones the underlying string (because it is already shared) and extracts it.
/// let mut s: String = s1.into_owned();
///
/// // Changing the clone has no impact on the previously shared version.
/// s.push_str(", world!");
///
/// // The old version still exists.
/// assert_eq!(s2, s3);
/// assert_eq!(s2.as_str(), "hello");
///
/// // Not equals!
/// assert_ne!(s2.as_str(), s.as_str());
/// assert_eq!(s, "hello, world!");
/// ```
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct ImmutableString(Shared<String>);
impl Deref for ImmutableString {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<String> for ImmutableString {
fn as_ref(&self) -> &String {
&self.0
}
}
impl Borrow<str> for ImmutableString {
fn borrow(&self) -> &str {
self.0.as_str()
}
}
impl From<&str> for ImmutableString {
fn from(value: &str) -> Self {
Self(value.to_string().into())
}
}
impl From<String> for ImmutableString {
fn from(value: String) -> Self {
Self(value.into())
}
}
impl From<Box<String>> for ImmutableString {
fn from(value: Box<String>) -> Self {
Self(value.into())
}
}
impl From<ImmutableString> for String {
fn from(value: ImmutableString) -> Self {
value.into_owned()
}
}
impl FromStr for ImmutableString {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_string().into()))
}
}
impl FromIterator<char> for ImmutableString {
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl<'a> FromIterator<&'a char> for ImmutableString {
fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self {
Self(iter.into_iter().cloned().collect::<String>().into())
}
}
impl<'a> FromIterator<&'a str> for ImmutableString {
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl<'a> FromIterator<String> for ImmutableString {
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl fmt::Display for ImmutableString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.0.as_str(), f)
}
}
impl fmt::Debug for ImmutableString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.0.as_str(), f)
}
}
impl Add for ImmutableString {
type Output = Self;
fn add(mut self, rhs: Self) -> Self::Output {
if rhs.is_empty() {
self
} else if self.is_empty() {
rhs
} else {
self.make_mut().push_str(rhs.0.as_str());
self
}
}
}
impl Add for &ImmutableString {
type Output = ImmutableString;
fn add(self, rhs: Self) -> Self::Output {
if rhs.is_empty() {
self.clone()
} else if self.is_empty() {
rhs.clone()
} else {
let mut s = self.clone();
s.make_mut().push_str(rhs.0.as_str());
s
}
}
}
impl AddAssign<&ImmutableString> for ImmutableString {
fn add_assign(&mut self, rhs: &ImmutableString) {
if !rhs.is_empty() {
if self.is_empty() {
self.0 = rhs.0.clone();
} else {
self.make_mut().push_str(rhs.0.as_str());
}
}
}
}
impl Add<&str> for ImmutableString {
type Output = Self;
fn add(mut self, rhs: &str) -> Self::Output {
if rhs.is_empty() {
self
} else {
self.make_mut().push_str(rhs);
self
}
}
}
impl Add<&str> for &ImmutableString {
type Output = ImmutableString;
fn add(self, rhs: &str) -> Self::Output {
if rhs.is_empty() {
self.clone()
} else {
let mut s = self.clone();
s.make_mut().push_str(rhs);
s
}
}
}
impl AddAssign<&str> for ImmutableString {
fn add_assign(&mut self, rhs: &str) {
if !rhs.is_empty() {
self.make_mut().push_str(rhs);
}
}
}
impl Add<String> for ImmutableString {
type Output = Self;
fn add(mut self, rhs: String) -> Self::Output {
if rhs.is_empty() {
self
} else if self.is_empty() {
rhs.into()
} else {
self.make_mut().push_str(&rhs);
self
}
}
}
impl Add<String> for &ImmutableString {
type Output = ImmutableString;
fn add(self, rhs: String) -> Self::Output {
if rhs.is_empty() {
self.clone()
} else if self.is_empty() {
rhs.into()
} else {
let mut s = self.clone();
s.make_mut().push_str(&rhs);
s
}
}
}
impl Add<char> for ImmutableString {
type Output = Self;
fn add(mut self, rhs: char) -> Self::Output {
self.make_mut().push(rhs);
self
}
}
impl Add<char> for &ImmutableString {
type Output = ImmutableString;
fn add(self, rhs: char) -> Self::Output {
let mut s = self.clone();
s.make_mut().push(rhs);
s
}
}
impl AddAssign<char> for ImmutableString {
fn add_assign(&mut self, rhs: char) {
self.make_mut().push(rhs);
}
}
impl ImmutableString {
/// Consume the `ImmutableString` and convert it into a `String`.
/// If there are other references to the same string, a cloned copy is returned.
pub fn into_owned(mut self) -> String {
self.make_mut(); // Make sure it is unique reference
shared_take(self.0) // Should succeed
}
/// Make sure that the `ImmutableString` is unique (i.e. no other outstanding references).
/// Then return a mutable reference to the `String`.
pub fn make_mut(&mut self) -> &mut String {
shared_make_mut(&mut self.0)
}
}

View File

@ -22,7 +22,7 @@ fn test_float() -> Result<(), Box<EvalAltResult>> {
#[test] #[test]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
fn struct_with_float() -> Result<(), Box<EvalAltResult>> { fn test_struct_with_float() -> Result<(), Box<EvalAltResult>> {
#[derive(Clone)] #[derive(Clone)]
struct TestStruct { struct TestStruct {
x: f64, x: f64,
@ -64,3 +64,16 @@ fn struct_with_float() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_float_func() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.register_fn("sum", |x: FLOAT, y: FLOAT, z: FLOAT, w: FLOAT| {
x + y + z + w
});
assert_eq!(engine.eval::<FLOAT>("sum(1.0, 2.0, 3.0, 4.0)")?, 10.0);
Ok(())
}

View File

@ -1,6 +1,6 @@
#![cfg(not(feature = "no_object"))] #![cfg(not(feature = "no_object"))]
use rhai::{Engine, EvalAltResult, RegisterFn, INT}; use rhai::{Engine, EvalAltResult, ImmutableString, RegisterFn, INT};
#[test] #[test]
fn test_get_set() -> Result<(), Box<EvalAltResult>> { fn test_get_set() -> Result<(), Box<EvalAltResult>> {
@ -43,7 +43,9 @@ fn test_get_set() -> Result<(), Box<EvalAltResult>> {
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
engine.register_indexer(|value: &mut TestStruct, index: String| value.array[index.len()]); engine.register_indexer(|value: &mut TestStruct, index: ImmutableString| {
value.array[index.len()]
});
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x = 500; a.x")?, 500); assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x = 500; a.x")?, 500);
assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42); assert_eq!(engine.eval::<INT>("let a = new_ts(); a.x.add(); a.x")?, 42);

View File

@ -63,6 +63,9 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
let mut module = Module::new(); let mut module = Module::new();
module.set_var("answer", 42 as INT); module.set_var("answer", 42 as INT);
module.set_fn_4("sum".to_string(), |x: INT, y: INT, z: INT, w: INT| {
Ok(x + y + z + w)
});
resolver.insert("hello".to_string(), module); resolver.insert("hello".to_string(), module);
@ -74,69 +77,72 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
r#" r#"
import "hello" as h1; import "hello" as h1;
import "hello" as h2; import "hello" as h2;
h2::answer h1::sum(h2::answer, -10, 3, 7)
"# "#
)?, )?,
42 42
); );
engine.set_max_modules(5); #[cfg(not(feature = "unchecked"))]
{
engine.set_max_modules(5);
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<INT>( .eval::<INT>(
r#" r#"
let sum = 0; let sum = 0;
for x in range(0, 10) { for x in range(0, 10) {
import "hello" as h; import "hello" as h;
sum += h::answer; sum += h::answer;
} }
sum sum
"# "#
) )
.expect_err("should error"), .expect_err("should error"),
EvalAltResult::ErrorTooManyModules(_) EvalAltResult::ErrorTooManyModules(_)
)); ));
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<INT>( .eval::<INT>(
r#" r#"
let sum = 0; let sum = 0;
fn foo() { fn foo() {
import "hello" as h; import "hello" as h;
sum += h::answer; sum += h::answer;
} }
for x in range(0, 10) { for x in range(0, 10) {
foo(); foo();
} }
sum sum
"# "#
) )
.expect_err("should error"), .expect_err("should error"),
EvalAltResult::ErrorInFunctionCall(fn_name, _, _) if fn_name == "foo" EvalAltResult::ErrorInFunctionCall(fn_name, _, _) if fn_name == "foo"
)); ));
engine.set_max_modules(0); engine.set_max_modules(0);
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
engine.eval::<()>( engine.eval::<()>(
r#" r#"
fn foo() { fn foo() {
import "hello" as h; import "hello" as h;
} }
for x in range(0, 10) { for x in range(0, 10) {
foo(); foo();
} }
"#, "#,
)?; )?;
}
Ok(()) Ok(())
} }

File diff suppressed because one or more lines are too long