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]
name = "rhai"
version = "0.14.2"
version = "0.15.0"
edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust"
@ -20,27 +20,23 @@ categories = [ "no-std", "embedded", "parser-implementations" ]
num-traits = { version = "0.2.11", default-features = false }
[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 = []
plugins = []
unchecked = [] # unchecked arithmetic
no_index = [] # no arrays and indexing
no_float = [] # no floating-point
no_function = [] # no script-defined functions
no_object = [] # no custom objects
sync = [] # restrict to only types that implement Send + Sync
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_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
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]
lto = "fat"
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
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.
* 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
one single source file, all with names starting with `"unsafe_"`).
* Re-entrant scripting [`Engine`] can be made `Send + Sync` (via the [`sync`] feature).
* Sand-boxed - the scripting [`Engine`], if declared immutable, cannot mutate the containing environment 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.).
* Track script evaluation [progress](#tracking-progress) and manually terminate a script run.
* [`no-std`](#optional-features) support.
* [Function overloading](#function-overloading).
* [Operator overloading](#operator-overloading).
* 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).
* 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
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
------------
@ -45,7 +59,7 @@ Install the Rhai crate by adding this line to `dependencies`:
```toml
[dependencies]
rhai = "0.14.2"
rhai = "0.15.0"
```
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 = "*"
```
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:
```toml
@ -68,19 +82,19 @@ Beware that in order to use pre-releases (e.g. alpha and beta), the exact versio
Optional features
-----------------
| 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! |
| `no_function` | Disable script-defined functions. |
| `no_index` | Disable [arrays] and indexing features. |
| `no_object` | Disable support for custom types and object maps. |
| `no_float` | Disable floating-point numbers and math. |
| `no_optimize` | Disable the script optimizer. |
| `no_module` | Disable modules. |
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
| `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and `AST`, are all `Send + Sync`. |
| Feature | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `unchecked` | Disable arithmetic checking (such as over-flows and division by zero), call stack depth limit, operations count limit and modules loading limit. Beware that a bad script may panic the entire system! |
| `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, all Rhai types, including [`Engine`], [`Scope`] and `AST`, are all `Send + Sync`. |
| `no_optimize` | Disable the script optimizer. |
| `no_float` | Disable floating-point numbers and math. |
| `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. |
| `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
| `no_index` | Disable [arrays] and indexing features. |
| `no_object` | Disable support for custom types and [object maps]. |
| `no_function` | Disable script-defined functions. |
| `no_module` | Disable loading modules. |
| `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.
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.
[`unchecked`]: #optional-features
[`no_index`]: #optional-features
[`no_float`]: #optional-features
[`no_function`]: #optional-features
[`no_object`]: #optional-features
[`sync`]: #optional-features
[`no_optimize`]: #optional-features
[`no_module`]: #optional-features
[`no_float`]: #optional-features
[`only_i32`]: #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
[`sync`]: #optional-features
### 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`).
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.
This makes the scripting language quite useless as even basic arithmetic operators are not supported.
Selectively include the necessary functionalities by loading specific [packages] to minimize the footprint.
[`Engine::new_raw`](#raw-engine) creates a _raw_ engine.
A _raw_ engine supports, out of the box, only a very [restricted set](#built-in-operators) of basic arithmetic and logical operators.
Selectively include other necessary functionalities by loading specific [packages] to minimize the footprint.
Packages are sharable (even across threads via the [`sync`] feature), so they only have to be created once.
Related
@ -168,7 +182,7 @@ Examples can be run with the following command:
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).
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`).
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
@ -400,20 +427,20 @@ engine.load_package(package.get()); // load the package manually. 'g
The follow packages are available:
| Package | Description | In `CorePackage` | In `StandardPackage` |
| ---------------------- | ----------------------------------------------- | :--------------: | :------------------: |
| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) | Yes | Yes |
| `BasicIteratorPackage` | Numeric ranges (e.g. `range(1, 10)`) | Yes | Yes |
| `LogicPackage` | Logic and comparison operators (e.g. `==`, `>`) | Yes | Yes |
| `BasicStringPackage` | Basic string functions | Yes | Yes |
| `BasicTimePackage` | Basic time functions (e.g. [timestamps]) | Yes | Yes |
| `MoreStringPackage` | Additional string functions | No | Yes |
| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes |
| `BasicArrayPackage` | Basic [array] functions | No | Yes |
| `BasicMapPackage` | Basic [object map] functions | No | Yes |
| `EvalPackage` | Disable [`eval`] | No | No |
| `CorePackage` | Basic essentials | | |
| `StandardPackage` | Standard library | | |
| Package | Description | In `CorePackage` | In `StandardPackage` |
| ---------------------- | ------------------------------------------------------------------------------------------------------ | :--------------: | :------------------: |
| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) for numeric types that are not built in (e.g. `u16`) | Yes | Yes |
| `BasicIteratorPackage` | Numeric ranges (e.g. `range(1, 10)`) | Yes | Yes |
| `LogicPackage` | Logical and comparison operators (e.g. `==`, `>`) for numeric types that are not built in (e.g. `u16`) | Yes | Yes |
| `BasicStringPackage` | Basic string functions (e.g. `print`, `debug`, `len`) that are not built in | Yes | Yes |
| `BasicTimePackage` | Basic time functions (e.g. [timestamps]) | Yes | Yes |
| `MoreStringPackage` | Additional string functions, including converting common types to string | No | Yes |
| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes |
| `BasicArrayPackage` | Basic [array] functions (not available under `no_index`) | No | Yes |
| `BasicMapPackage` | Basic [object map] functions (not available under `no_object`) | No | Yes |
| `EvalPackage` | Disable [`eval`] | No | No |
| `CorePackage` | Basic essentials | Yes | Yes |
| `StandardPackage` | Standard library | No | Yes |
Packages typically contain Rust functions that are callable within a Rhai script.
All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers).
@ -458,6 +485,7 @@ Values and types
[`type_of()`]: #values-and-types
[`to_string()`]: #values-and-types
[`()`]: #values-and-types
[standard types]: #values-and-types
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. |
| **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` |
| **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"` | `"[ ?, ?, ? ]"` |
| **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_ |
| **Dynamic value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ |
| **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. |
| **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |
| **Nothing/void/nil/null** (or whatever 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 -
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.
[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 `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'
```
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
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<f64>` (if not [`no_float`])
* `From<bool>`
* `From<rhai::ImmutableString>`
* `From<String>`
* `From<char>`
* `From<Vec<T>>` (into an [array])
@ -611,13 +644,12 @@ Traits
A number of traits, under the `rhai::` module namespace, provide additional functionalities.
| Trait | Description | Methods |
| ------------------- | -------------------------------------------------------------------------------------- | --------------------------------------- |
| `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<`_T_`, Box<EvalAltResult>>` | `register_result_fn` |
| `Func` | Trait for creating anonymous functions from script | `create_from_ast`, `create_from_script` |
| `ModuleResolver` | Trait implemented by module resolution services | `resolve` |
| Trait | Description | Methods |
| ------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------- |
| `RegisterFn` | Trait for registering functions | `register_fn` |
| `RegisterResultFn` | Trait for registering fallible functions returning `Result<Dynamic, Box<EvalAltResult>>` | `register_result_fn` |
| `Func` | Trait for creating anonymous functions from script | `create_from_ast`, `create_from_script` |
| `ModuleResolver` | Trait implemented by module resolution services | `resolve` |
Working with functions
----------------------
@ -628,16 +660,16 @@ To call these functions, they need to be registered with the [`Engine`].
```rust
use rhai::{Dynamic, Engine, EvalAltResult};
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 {
x + y
}
// Function that returns a Dynamic value
fn get_an_any() -> Dynamic {
Dynamic::from(42_i64)
// Function that returns a 'Dynamic' value - must return a 'Result'
fn get_any_value() -> Result<Dynamic, Box<EvalAltResult>> {
Ok((42_i64).into()) // standard types can use 'into()'
}
fn main() -> Result<(), Box<EvalAltResult>>
@ -650,10 +682,10 @@ fn main() -> Result<(), Box<EvalAltResult>>
println!("Answer: {}", result); // prints 42
// Functions that return Dynamic values must use register_dynamic_fn()
engine.register_dynamic_fn("get_an_any", get_an_any);
// Functions that return Dynamic values must use register_result_fn()
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
@ -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
use rhai::Dynamic;
fn decide(yes_no: bool) -> Dynamic {
if yes_no {
Dynamic::from(42_i64)
} else {
Dynamic::from(String::from("hello!")) // remember &str is not supported by Rhai
}
}
let x = (42_i64).into(); // 'into()' works for standard types
let y = Dynamic::from(String::from("hello!")); // remember &str is not supported by Rhai
```
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`
(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>`.
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::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn'
// Function that may fail
fn safe_divide(x: i64, y: i64) -> Result<i64, Box<EvalAltResult>> {
// Function that may fail - the result type must be 'Dynamic'
fn safe_divide(x: i64, y: i64) -> Result<Dynamic, Box<EvalAltResult>> {
if y == 0 {
// Return an error if y is zero
Err("Division by zero!".into()) // short-cut to create Box<EvalAltResult::ErrorRuntime>
} 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.
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.
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
-----------------------
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
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.
You can turn off support for custom types via the [`no_object`] feature.
All custom types must implement `Clone` as this allows the [`Engine`] to pass by value.
Support for custom types can be turned off via the [`no_object`] feature.
```rust
#[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
impl TestStruct {
fn update(&mut self) {
fn update(&mut self) { // methods take &mut as first parameter
self.field += 41;
}
@ -874,19 +904,19 @@ let engine = Engine::new();
engine.register_type::<TestStruct>();
```
To use native types, methods and functions with the [`Engine`], we need to register them.
There are some convenience functions to help with these. Below, the `update` and `new` methods are registered with the [`Engine`].
To use native types, methods and functions with the [`Engine`], simply register them using one of the `Engine::register_XXX` API.
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
can update the value in memory.*
***Note**: Rhai follows the convention that methods of custom types take a `&mut` first parameter so that invoking methods
can update the custom types. All other parameters in Rhai are passed by value (i.e. clones).*
```rust
engine.register_fn("update", TestStruct::update); // registers 'update(&mut TestStruct)'
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.
We need to get the result back out from script land just as before, this time casting to our custom struct type.
The custom type is then ready for us in scripts. Scripts can see the functions and methods registered earlier.
Get the evaluation result back out from script-land just as before, this time casting to the custom type:
```rust
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
```
In fact, any function with a first argument (either by copy or via a `&mut` reference) can be used as a method call
on that type because internally they are the same thing:
methods on a type is implemented as a functions taking a `&mut` first argument.
In fact, any function with a first argument that is a `&mut` reference can be used as method calls because
internally they are the same thing: methods on a type is implemented as a functions taking a `&mut` first argument.
```rust
fn foo(ts: &mut TestStruct) -> i64 {
ts.field
}
engine.register_fn("foo", foo);
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
```
@ -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.
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.
```rust
@ -942,20 +973,23 @@ Similarly, custom types can expose members by registering a `get` and/or `set` f
```rust
#[derive(Clone)]
struct TestStruct {
field: i64
field: String
}
// Remember Rhai uses 'ImmutableString' instead of 'String'
impl TestStruct {
fn get_field(&mut self) -> i64 {
self.field
fn get_field(&mut self) -> ImmutableString {
// Make an 'ImmutableString' from a 'String'
self.field.into(0)
}
fn set_field(&mut self, new_val: i64) {
self.field = new_val;
fn set_field(&mut self, new_val: ImmutableString) {
// Get a 'String' from an 'ImmutableString'
self.field = (*new_val).clone();
}
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_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
```
@ -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`
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
-------------------------------------------
@ -1241,7 +1276,7 @@ number = -5 - +5;
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:
| 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.
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)
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:
| Function | Parameter(s) | Description |
| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `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 |
| `append` | character/string to append | Adds a character or a string to the end of another string |
| `clear` | _none_ | empties the string |
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another |
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
| Function | Parameter(s) | Description |
| ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `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 |
| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string |
| `clear` | _none_ | empties the string |
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another |
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
### Examples
@ -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:
| Function | Parameter(s) | Description |
| ------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `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 | 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 |
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
| `len` | _none_ | returns the number of elements |
| `pad` | element to pad, target length | pads the array with an element to at least a specified length |
| `clear` | _none_ | empties the array |
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
| Function | Parameter(s) | Description |
| ----------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `push` | element to insert | inserts an element at the end |
| `+=` operator, `append` | array to append | concatenates the second array to the end of the first |
| `+` operator | first array, second array | concatenates the first array with the second |
| `insert` | element to insert, position<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) |
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
| `len` | _none_ | returns the number of elements |
| `pad` | element to pad, target length | pads the array with an element to at least a specified length |
| `clear` | _none_ | empties the array |
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
### Examples
@ -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:
| Function | Parameter(s) | Description |
| ------------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `has` | property name | does the object map contain a property of a particular name? |
| `len` | _none_ | returns the number of properties |
| `clear` | _none_ | empties the object map |
| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) |
| `mixin` | second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) |
| `+` operator | first object map, second object map | merges the first object map with the second |
| `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] |
| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |
| Function | Parameter(s) | Description |
| ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `has` | property name | does the object map contain a property of a particular name? |
| `len` | _none_ | returns the number of properties |
| `clear` | _none_ | empties the object map |
| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) |
| `+=` operator, `mixin` | second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) |
| `+` operator | first object map, second object map | merges the first object map with the second |
| `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] |
| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |
### Examples
@ -1704,10 +1742,10 @@ if now.elapsed() > 30.0 {
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 -
`INT` (`i64` or `i32` depending on [`only_i32`] and [`only_i64`]), `f64` (if not [`no_float`]), [string], [array], `bool`, `char`.
However, if using a [raw `Engine`] without loading any [packages], comparisons can only be made between a limited
set of types (see [built-in operators](#built-in-operators)).
```rust
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:
* **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.
* **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.
@ -2349,6 +2387,10 @@ engine.set_max_modules(5); // allow loading only up to 5 module
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
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.
This check can be disabled via the [`unchecked`] feature for higher performance
(but higher risks as well).
### 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.
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).
### Checked arithmetic
By default, all arithmetic calculations in Rhai are _checked_, meaning that the script terminates
with an error whenever it detects a numeric over-flow/under-flow condition or an invalid
floating-point operation, instead of crashing the entire system. This checking can be turned off
via the [`unchecked`] feature for higher performance (but higher risks as well).
floating-point operation, instead of crashing the entire system.
This checking can be turned off via the [`unchecked`] feature for higher performance
(but higher risks as well).
### 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
```
Script optimization
===================
@ -2455,7 +2500,7 @@ For example, in the following:
123; // eliminated: no effect
"hello"; // eliminated: no effect
[1, 2, x, x*2, 5]; // eliminated: no effect
foo(42); // NOT eliminated: the function 'foo' may have side effects
foo(42); // NOT eliminated: the function 'foo' may have side-effects
666 // NOT eliminated: this is the return value of the block,
// and the block is the last one so this is the return value of the whole script
}
@ -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:
```rust
@ -2528,7 +2573,7 @@ if DECISION_1 {
In general, boolean constants are most effective for the optimizer to automatically prune
large `if`-`else` branches because they do not depend on operators.
Alternatively, turn the optimizer to [`OptimizationLevel::Full`]
Alternatively, turn the optimizer to [`OptimizationLevel::Full`].
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.
* `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).
* `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'
```
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
nor cause side any 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.
nor cause any side-effects, with the exception of `print` and `debug` which are handled specially) so using
[`OptimizationLevel::Full`] is usually quite safe _unless_ custom types and functions are registered.
If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie within a pruned code block).
If custom functions are registered to replace built-in operators, they will also be called when the operators are used
(in an `if` statement, for example) and cause side-effects.
If custom functions are registered to overload built-in operators, they will also be called when the operators are used
(in an `if` statement, for example) causing side-effects.
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_
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.
Volatility considerations
-------------------------
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
-----------------------
@ -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
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
-------------------------
@ -2670,6 +2719,8 @@ let engine = rhai::Engine::new();
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"
---------------------------------------------------------
@ -2718,8 +2769,8 @@ x += 32;
print(x);
```
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.
For those who subscribe to the (very sensible) motto of ["`eval` is evil"](http://linterrors.com/js/eval-is-evil),
disable `eval` by overloading it, probably with something that throws.
```rust
fn eval(script) { throw "eval is evil! I refuse to run " + script }
@ -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"
```
Or override it from Rust:
Or overload it from Rust:
```rust
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);
```
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
==============
Regression
----------
Regression fix
--------------
* 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
------------
* 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

View File

@ -29,7 +29,7 @@ fn bench_engine_new_raw_core(bench: &mut Bencher) {
#[bench]
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
}

View File

@ -86,7 +86,7 @@ fn bench_eval_call_expression(bench: &mut Bencher) {
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
let mut engine = Engine::new();
let engine = Engine::new();
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)
"#;
let mut engine = Engine::new();
let engine = Engine::new();
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;
while x > 0 {
x = x - 1;
x -= 1;
}
"#;

View File

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

View File

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

View File

@ -4,5 +4,5 @@ let x = 10;
while x > 0 {
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.
use crate::parser::INT;
use crate::parser::{ImmutableString, INT};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
#[cfg(not(feature = "no_module"))]
@ -151,7 +151,7 @@ pub struct Dynamic(pub(crate) Union);
pub enum Union {
Unit(()),
Bool(bool),
Str(Box<String>),
Str(ImmutableString),
Char(char),
Int(INT),
#[cfg(not(feature = "no_float"))]
@ -178,6 +178,10 @@ impl Dynamic {
/// Is the value held by this `Dynamic` a particular type?
pub fn is<T: Variant + Clone>(&self) -> bool {
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`.
@ -185,7 +189,7 @@ impl Dynamic {
match &self.0 {
Union::Unit(_) => TypeId::of::<()>(),
Union::Bool(_) => TypeId::of::<bool>(),
Union::Str(_) => TypeId::of::<String>(),
Union::Str(_) => TypeId::of::<ImmutableString>(),
Union::Char(_) => TypeId::of::<char>(),
Union::Int(_) => TypeId::of::<INT>(),
#[cfg(not(feature = "no_float"))]
@ -342,6 +346,12 @@ impl Dynamic {
return Self(result);
} else if let Some(result) = dyn_value.downcast_ref::<char>().cloned().map(Union::Char) {
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"))]
@ -358,7 +368,7 @@ impl Dynamic {
Err(var) => 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,
};
#[cfg(not(feature = "no_index"))]
@ -395,14 +405,19 @@ impl Dynamic {
/// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
/// ```
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);
}
match self.0 {
Union::Unit(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::Int(value) => unsafe_try_cast(value),
#[cfg(not(feature = "no_float"))]
@ -434,16 +449,19 @@ impl Dynamic {
/// assert_eq!(x.cast::<u32>(), 42);
/// ```
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();
}
match self.0 {
Union::Unit(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::Int(value) => unsafe_try_cast(value).unwrap(),
#[cfg(not(feature = "no_float"))]
@ -469,7 +487,9 @@ impl Dynamic {
match &self.0 {
Union::Unit(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::Int(value) => (value as &dyn Any).downcast_ref::<T>(),
#[cfg(not(feature = "no_float"))]
@ -495,7 +515,7 @@ impl Dynamic {
match &mut self.0 {
Union::Unit(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::Int(value) => (value as &mut dyn Any).downcast_mut::<T>(),
#[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.
/// Returns the name of the actual type if the cast fails.
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.
pub fn take_string(self) -> Result<String, &'static str> {
match self.0 {
Union::Str(s) => Ok(*s),
Union::Str(s) => Ok(s.into_owned()),
_ => Err(self.type_name()),
}
}
@ -584,7 +614,12 @@ impl From<char> for Dynamic {
}
impl From<String> for Dynamic {
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"))]

View File

@ -4,7 +4,7 @@ use crate::any::{Dynamic, Variant};
use crate::engine::{make_getter, make_setter, Engine, State, FUNC_INDEXER};
use crate::error::ParseError;
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::optimize::{optimize_into_ast, OptimizationLevel};
use crate::parser::{parse, parse_global_expr, AST};
@ -162,11 +162,13 @@ impl Engine {
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
pub fn register_get<T, U, F>(&mut self, name: &str, callback: F)
where
pub fn register_get<T, U>(
&mut self,
name: &str,
callback: impl Fn(&mut T) -> U + SendSync + 'static,
) where
T: Variant + Clone,
U: Variant + Clone,
F: ObjectGetCallback<T, U>,
{
self.register_fn(&make_getter(name), callback);
}
@ -208,11 +210,13 @@ impl Engine {
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
pub fn register_set<T, U, F>(&mut self, name: &str, callback: F)
where
pub fn register_set<T, U>(
&mut self,
name: &str,
callback: impl Fn(&mut T, U) + SendSync + 'static,
) where
T: Variant + Clone,
U: Variant + Clone,
F: ObjectSetCallback<T, U>,
{
self.register_fn(&make_setter(name), callback);
}
@ -256,12 +260,14 @@ impl Engine {
/// # }
/// ```
#[cfg(not(feature = "no_object"))]
pub fn register_get_set<T, U, G, S>(&mut self, name: &str, get_fn: G, set_fn: S)
where
pub fn register_get_set<T, U>(
&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,
U: Variant + Clone,
G: ObjectGetCallback<T, U>,
S: ObjectSetCallback<T, U>,
{
self.register_get(name, get_fn);
self.register_set(name, set_fn);
@ -305,12 +311,13 @@ impl Engine {
/// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
pub fn register_indexer<T, X, U, F>(&mut self, callback: F)
where
pub fn register_indexer<T, X, U>(
&mut self,
callback: impl Fn(&mut T, X) -> U + SendSync + 'static,
) where
T: Variant + Clone,
U: Variant + Clone,
X: Variant + Clone,
F: ObjectIndexerCallback<T, X, U>,
{
self.register_fn(FUNC_INDEXER, callback);
}
@ -334,7 +341,7 @@ impl Engine {
/// # 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)
}
@ -376,7 +383,7 @@ impl Engine {
/// # 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])
}
@ -430,7 +437,7 @@ impl Engine {
&self,
scope: &Scope,
scripts: &[&str],
) -> Result<AST, Box<ParseError>> {
) -> Result<AST, ParseError> {
self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level)
}
@ -440,7 +447,7 @@ impl Engine {
scope: &Scope,
scripts: &[&str],
optimization_level: OptimizationLevel,
) -> Result<AST, Box<ParseError>> {
) -> Result<AST, ParseError> {
let stream = lex(scripts);
parse(
@ -607,7 +614,7 @@ impl Engine {
/// # 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)
}
@ -654,7 +661,7 @@ impl Engine {
&self,
scope: &Scope,
script: &str,
) -> Result<AST, Box<ParseError>> {
) -> Result<AST, ParseError> {
let scripts = [script];
let stream = lex(&scripts);
@ -902,12 +909,12 @@ impl Engine {
scope: &mut Scope,
ast: &AST,
) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
let mut state = State::new(ast.fn_lib());
let mut state = State::new();
ast.statements()
.iter()
.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 {
EvalAltResult::Return(out, _) => Ok(out),
@ -973,12 +980,12 @@ impl Engine {
scope: &mut Scope,
ast: &AST,
) -> Result<(), Box<EvalAltResult>> {
let mut state = State::new(ast.fn_lib());
let mut state = State::new();
ast.statements()
.iter()
.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(
|err| match *err {
@ -1034,17 +1041,18 @@ impl Engine {
) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = args.into_vec();
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let fn_lib = ast.fn_lib();
let lib = ast.lib();
let pos = Position::none();
let fn_def = fn_lib
let fn_def = lib
.get_function_by_signature(name, args.len(), true)
.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 (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());
@ -1074,14 +1082,14 @@ impl Engine {
mut ast: AST,
optimization_level: OptimizationLevel,
) -> AST {
let fn_lib = ast
.fn_lib()
let lib = ast
.lib()
.iter()
.map(|(_, fn_def)| fn_def.as_ref().clone())
.collect();
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.
@ -1118,47 +1126,7 @@ impl Engine {
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "sync")]
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) {
pub fn on_progress(&mut self, callback: impl Fn(u64) -> bool + SendSync + 'static) {
self.progress = Some(Box::new(callback));
}
@ -1186,36 +1154,7 @@ impl Engine {
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "sync")]
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) {
pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) {
self.print = Box::new(callback);
}
@ -1243,36 +1182,7 @@ impl Engine {
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "sync")]
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) {
pub fn on_debug(&mut self, callback: impl Fn(&str) + SendSync + 'static) {
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 {
/// Make a `ParseError` using the current type and position.
pub(crate) fn into_err(self, pos: Position) -> Box<ParseError> {
Box::new(ParseError(self, pos))
pub(crate) fn into_err(self, pos: Position) -> ParseError {
ParseError(Box::new(self), pos)
}
}
/// Error when parsing a script.
#[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 {
/// Get the parse error.
@ -141,7 +141,7 @@ impl ParseError {
}
pub(crate) fn desc(&self) -> &str {
match &self.0 {
match self.0.as_ref() {
ParseErrorType::BadInput(p) => p,
ParseErrorType::UnexpectedEOF => "Script is incomplete",
ParseErrorType::UnknownOperator(_) => "Unknown operator",
@ -173,7 +173,7 @@ impl Error for ParseError {}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
match self.0.as_ref() {
ParseErrorType::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => {
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
}

View File

@ -80,7 +80,7 @@ pub trait Func<ARGS, RET> {
self,
script: &str,
entry_point: &str,
) -> Result<Self::Output, Box<ParseError>>;
) -> Result<Self::Output, ParseError>;
}
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)?;
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};
#[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];
#[cfg(feature = "sync")]
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
#[cfg(not(feature = "sync"))]
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>>;
#[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.
#[derive(Clone)]
pub enum CallableFunction {
/// 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,
/// and the rest passed by value.
Method(SharedNativeFunction),
Method(Shared<FnAny>),
/// An iterator function.
Iterator(IteratorFn),
/// A script-defined function.
Script(SharedFnDef),
Script(Shared<FnDef>),
}
impl CallableFunction {

View File

@ -8,7 +8,7 @@ use crate::fn_native::{CallableFunction, FnAny, FnCallArgs};
use crate::parser::FnAccess;
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`.
///
@ -85,49 +85,22 @@ pub trait RegisterFn<FN, ARGS, RET> {
fn register_fn(&mut self, name: &str, f: FN);
}
/// Trait to register custom functions that return `Dynamic` values with the `Engine`.
pub trait RegisterDynamicFn<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> {
/// Trait to register fallible custom functions returning `Result<Dynamic, Box<EvalAltResult>>` with the `Engine`.
pub trait RegisterResultFn<FN, ARGS> {
/// Register a custom fallible function with the `Engine`.
///
/// # Example
///
/// ```
/// use rhai::{Engine, RegisterResultFn, EvalAltResult};
/// use rhai::{Engine, Dynamic, RegisterResultFn, EvalAltResult};
///
/// // 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 {
/// // '.into()' automatically converts to 'Box<EvalAltResult::ErrorRuntime>'
/// Err("division by zero!".into())
/// } 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.
#[inline(always)]
pub fn map_identity(data: Dynamic) -> Result<Dynamic, Box<EvalAltResult>> {
Ok(data)
}
/// To `Result<Dynamic, Box<EvalAltResult>>` mapping function.
#[inline(always)]
pub fn map_result<T: Variant + Clone>(
data: Result<T, Box<EvalAltResult>>,
pub fn map_result(
data: Result<Dynamic, Box<EvalAltResult>>,
) -> Result<Dynamic, Box<EvalAltResult>> {
data.map(|v| v.into_dynamic())
data
}
macro_rules! def_register {
@ -244,10 +211,10 @@ macro_rules! def_register {
};
(imp $abi:ident : $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => {
// ^ function ABI type
// ^ function parameter generic type name (A, B, C etc.)
// ^ function parameter marker type (T, Ref<T> or Mut<T>)
// ^ function parameter actual type (T, &T or &mut T)
// ^ dereferencing function
// ^ function parameter generic type name (A, B, C etc.)
// ^ function parameter marker type (T, Ref<T> or Mut<T>)
// ^ function parameter actual type (T, &T or &mut T)
// ^ dereferencing function
impl<
$($par: Variant + Clone,)*
@ -261,7 +228,7 @@ macro_rules! def_register {
> RegisterFn<FN, ($($mark,)*), RET> for Engine
{
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>()),*],
CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $clone),*))
);
@ -272,33 +239,13 @@ macro_rules! def_register {
$($par: Variant + Clone,)*
#[cfg(feature = "sync")]
FN: Fn($($param),*) -> Dynamic + Send + Sync + 'static,
FN: Fn($($param),*) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync + 'static,
#[cfg(not(feature = "sync"))]
FN: Fn($($param),*) -> Dynamic + 'static,
> RegisterDynamicFn<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: Fn($($param),*) -> Result<Dynamic, Box<EvalAltResult>> + 'static,
> RegisterResultFn<FN, ($($mark,)*)> for Engine
{
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>()),*],
CallableFunction::$abi(make_func!(f : map_result ; $($par => $clone),*))
);

View File

@ -49,18 +49,18 @@
//!
//! ## Optional features
//!
//! | Feature | Description |
//! | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
//! | `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_index` | Disable arrays and indexing features if not needed. |
//! | `no_object` | Disable support for custom types and objects. |
//! | `no_float` | Disable floating-point numbers and math if not needed. |
//! | `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_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and `AST` are all `Send + Sync`. |
//! | Feature | Description |
//! | ------------- | ----------------------------------------------------------------------------------------------------------------------------------|
//! | `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_index` | Disable arrays and indexing features if not needed. |
//! | `no_object` | Disable support for custom types and objects. |
//! | `no_float` | Disable floating-point numbers and math if not needed. |
//! | `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_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. |
//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. |
//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and `AST` are all `Send + Sync`. |
//!
//! [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};
#[cfg(feature = "plugins")]
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 parser::{AST, INT};
pub use parser::{ImmutableString, AST, INT};
pub use result::EvalAltResult;
pub use scope::Scope;
pub use token::Position;

View File

@ -3,7 +3,7 @@
use crate::any::{Dynamic, Variant};
use crate::calc_fn_hash;
use crate::engine::{Engine, FunctionsLib};
use crate::fn_native::{CallableFunction as CF, FnCallArgs, IteratorFn};
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn};
use crate::parser::{
FnAccess,
FnAccess::{Private, Public},
@ -47,17 +47,17 @@ pub struct Module {
all_variables: HashMap<u64, Dynamic>,
/// External Rust functions.
functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, CF)>,
functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, CallableFunction)>,
/// Script-defined functions.
fn_lib: FunctionsLib,
lib: FunctionsLib,
/// Iterator functions, keyed by the type producing the iterator.
type_iterators: HashMap<TypeId, IteratorFn>,
/// Flattened collection of all external Rust functions, native or scripted,
/// including those in sub-modules.
all_functions: HashMap<u64, CF>,
all_functions: HashMap<u64, CallableFunction>,
}
impl fmt::Debug for Module {
@ -67,7 +67,7 @@ impl fmt::Debug for Module {
"<module {:?}, functions={}, lib={}>",
self.variables,
self.functions.len(),
self.fn_lib.len()
self.lib.len()
)
}
}
@ -164,7 +164,7 @@ impl Module {
/// module.set_var("answer", 42_i64);
/// 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));
}
@ -244,7 +244,7 @@ impl Module {
/// module.set_sub_module("question", sub_module);
/// 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());
}
@ -269,7 +269,15 @@ impl Module {
/// 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.
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 params = params.into_iter().cloned().collect();
@ -293,15 +301,20 @@ impl Module {
/// let hash = module.set_fn_0("calc", || Ok(42_i64));
/// 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,
name: K,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn() -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn() -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f = move |_: &mut FnCallArgs| func().map(Dynamic::from);
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.
@ -317,16 +330,21 @@ impl Module {
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
/// 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,
name: K,
name: impl Into<String>,
#[cfg(not(feature = "sync"))] func: impl Fn(A) -> FuncReturn<T> + 'static,
#[cfg(feature = "sync")] func: impl Fn(A) -> FuncReturn<T> + Send + Sync + 'static,
) -> u64 {
let f =
move |args: &mut FnCallArgs| func(mem::take(args[0]).cast::<A>()).map(Dynamic::from);
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.
@ -342,9 +360,9 @@ impl Module {
/// let hash = module.set_fn_1_mut("calc", |x: &mut i64| { *x += 1; Ok(*x) });
/// 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,
name: K,
name: impl Into<String>,
#[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,
) -> u64 {
@ -352,7 +370,12 @@ impl Module {
func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from)
};
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.
@ -370,9 +393,9 @@ impl Module {
/// });
/// 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,
name: K,
name: impl Into<String>,
#[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,
) -> u64 {
@ -383,7 +406,12 @@ impl Module {
func(a, b).map(Dynamic::from)
};
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,
@ -400,14 +428,9 @@ impl Module {
/// });
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_2_mut<
K: Into<String>,
A: Variant + Clone,
B: Variant + Clone,
T: Variant + Clone,
>(
pub fn set_fn_2_mut<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self,
name: K,
name: impl Into<String>,
#[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,
) -> u64 {
@ -418,7 +441,12 @@ impl Module {
func(a, b).map(Dynamic::from)
};
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.
@ -437,14 +465,13 @@ impl Module {
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_3<
K: Into<String>,
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
T: Variant + Clone,
>(
&mut self,
name: K,
name: impl Into<String>,
#[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,
) -> u64 {
@ -456,7 +483,12 @@ impl Module {
func(a, b, c).map(Dynamic::from)
};
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,
@ -476,14 +508,13 @@ impl Module {
/// assert!(module.get_fn(hash).is_some());
/// ```
pub fn set_fn_3_mut<
K: Into<String>,
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
T: Variant + Clone,
>(
&mut self,
name: K,
name: impl Into<String>,
#[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,
) -> u64 {
@ -495,7 +526,111 @@ impl Module {
func(a, b, c).map(Dynamic::from)
};
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.
@ -512,19 +647,19 @@ impl Module {
/// let hash = module.set_fn_1("calc", |x: i64| Ok(x + 1));
/// 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)
}
/// Get a modules-qualified function.
///
/// The `u64` hash is calculated by the function `crate::calc_fn_hash`.
/// It is also returned by the `set_fn_XXX` calls.
/// The `u64` hash is calculated by the function `crate::calc_fn_hash` and must match
/// the hash calculated by `index_all_sub_modules`.
pub(crate) fn get_qualified_fn(
&mut self,
name: &str,
hash_fn_native: u64,
) -> Result<&CF, Box<EvalAltResult>> {
) -> Result<&CallableFunction, Box<EvalAltResult>> {
self.all_functions.get(&hash_fn_native).ok_or_else(|| {
Box::new(EvalAltResult::ErrorFunctionNotFound(
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)
}
@ -591,7 +726,7 @@ impl Module {
module: &'a Module,
qualifiers: &mut Vec<&'a str>,
variables: &mut Vec<(u64, Dynamic)>,
functions: &mut Vec<(u64, CF)>,
functions: &mut Vec<(u64, CallableFunction)>,
) {
for (name, m) in &module.modules {
// Index all the sub-modules first.
@ -627,7 +762,7 @@ impl Module {
functions.push((hash_fn_native, func.clone()));
}
// Index all script-defined functions
for fn_def in module.fn_lib.values() {
for fn_def in module.lib.values() {
match fn_def.access {
// Private functions are not exported
Private => continue,
@ -640,7 +775,7 @@ impl Module {
fn_def.params.len(),
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::calc_fn_hash;
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::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
@ -54,23 +52,19 @@ struct State<'a> {
/// An `Engine` instance for eager function evaluation.
engine: &'a Engine,
/// Library of script-defined functions.
fn_lib: &'a [(&'a str, usize)],
lib: &'a FunctionsLib,
/// Optimization level.
optimization_level: OptimizationLevel,
}
impl<'a> State<'a> {
/// Create a new State.
pub fn new(
engine: &'a Engine,
fn_lib: &'a [(&'a str, usize)],
level: OptimizationLevel,
) -> Self {
pub fn new(engine: &'a Engine, lib: &'a FunctionsLib, level: OptimizationLevel) -> Self {
Self {
changed: false,
constants: vec![],
engine,
fn_lib,
lib,
optimization_level: level,
}
}
@ -111,27 +105,36 @@ impl<'a> State<'a> {
}
/// Call a registered function
fn call_fn(
packages: &PackagesCollection,
global_module: &Module,
fn call_fn_with_constant_arguments(
state: &State,
fn_name: &str,
args: &mut FnCallArgs,
arg_values: &mut [Dynamic],
pos: Position,
) -> Result<Option<Dynamic>, Box<EvalAltResult>> {
// Search built-in's and external functions
let hash_fn = calc_fn_hash(
empty(),
fn_name,
args.len(),
args.iter().map(|a| a.type_id()),
arg_values.len(),
arg_values.iter().map(|a| a.type_id()),
);
global_module
.get_fn(hash_fn)
.or_else(|| packages.get_fn(hash_fn))
.map(|func| func.get_native_fn()(args))
.transpose()
.map_err(|err| err.new_position(pos))
state
.engine
.call_fn_raw(
None,
&mut Default::default(),
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.
@ -377,26 +380,26 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
stmt => Expr::Stmt(Box::new((stmt, x.1))),
},
// id = expr
Expr::Assignment(x) => match x.1 {
//id = id2 = expr2
Expr::Assignment(x2) => match (x.0, x2.0) {
// var = var = expr2 -> var = expr2
Expr::Assignment(x) => match x.2 {
//id = id2 op= expr2
Expr::Assignment(x2) if x.1 == "=" => match (x.0, x2.0) {
// var = var op= expr2 -> var op= expr2
(Expr::Variable(a), Expr::Variable(b))
if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 =>
{
// Assignment to the same variable - fold
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) => {
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
expr => Expr::Assignment(Box::new((x.0, optimize_expr(expr, state), x.2))),
// id op= expr
expr => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(expr, state), x.3))),
},
// lhs.rhs
@ -435,7 +438,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// All other items can be thrown away.
state.set_dirty();
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))
.unwrap_or_else(|| Expr::Unit(pos))
}
@ -463,7 +466,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// "xxx" in "xxxxx"
(Expr::StringConstant(a), Expr::StringConstant(b)) => {
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"
(Expr::CharConstant(a), Expr::StringConstant(b)) => {
@ -473,7 +476,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// "xxx" in #{...}
(Expr::StringConstant(a), Expr::Map(b)) => {
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)
} else {
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
&& 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)
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
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
return Expr::FnCall(x);
}
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()`
// 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 {
state.engine.map_type_name(call_args[0].type_name())
let arg_for_type_of = if name == KEYWORD_TYPE_OF && arg_values.len() == 1 {
state.engine.map_type_name(arg_values[0].type_name())
} 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|
result.or_else(|| {
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>,
engine: &Engine,
scope: &Scope,
fn_lib: &'a [(&'a str, usize)],
lib: &FunctionsLib,
level: OptimizationLevel,
) -> Vec<Stmt> {
// If optimization level is None then skip optimizing
@ -621,7 +627,7 @@ fn optimize<'a>(
}
// 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
scope
@ -706,41 +712,35 @@ pub fn optimize_into_ast(
const level: OptimizationLevel = OptimizationLevel::None;
#[cfg(not(feature = "no_function"))]
let fn_lib_values: StaticVec<_> = functions
.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| {
let lib = {
if !level.is_none() {
let pos = fn_def.body.position();
let lib = FunctionsLib::from_iter(functions.iter().cloned());
// Optimize the function body
let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), fn_lib, level);
FunctionsLib::from_iter(functions.into_iter().map(|mut fn_def| {
let pos = fn_def.body.position();
// {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
Stmt::Expr(Box::new(x.1.unwrap()))
}
// { return; } -> ()
Stmt::ReturnWithVal(x) if x.1.is_none() && (x.0).0 == ReturnType::Return => {
Stmt::Expr(Box::new(Expr::Unit((x.0).1)))
}
// All others
stmt => stmt,
};
// Optimize the function body
let mut body = optimize(vec![fn_def.body], engine, &Scope::new(), &lib, level);
// {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val
Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => {
Stmt::Expr(Box::new(x.1.unwrap()))
}
// { return; } -> ()
Stmt::ReturnWithVal(x) if x.1.is_none() && (x.0).0 == ReturnType::Return => {
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")]
let lib: FunctionsLib = Default::default();
@ -749,7 +749,7 @@ pub fn optimize_into_ast(
match level {
OptimizationLevel::None => statements,
OptimizationLevel::Simple | OptimizationLevel::Full => {
optimize(statements, engine, &scope, fn_lib, level)
optimize(statements, engine, &scope, &lib, level)
}
},
lib,

View File

@ -20,7 +20,7 @@ use crate::stdlib::{
};
// 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(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Addition overflow: {} + {}", x, y),
@ -29,7 +29,7 @@ fn add<T: Display + CheckedAdd>(x: T, y: T) -> FuncReturn<T> {
})
}
// 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(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Subtraction underflow: {} - {}", x, y),
@ -38,7 +38,7 @@ fn sub<T: Display + CheckedSub>(x: T, y: T) -> FuncReturn<T> {
})
}
// 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(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Multiplication overflow: {} * {}", x, y),
@ -47,7 +47,7 @@ fn mul<T: Display + CheckedMul>(x: T, y: T) -> FuncReturn<T> {
})
}
// Checked divide
fn div<T>(x: T, y: T) -> FuncReturn<T>
pub(crate) fn div<T>(x: T, y: T) -> FuncReturn<T>
where
T: Display + CheckedDiv + PartialEq + Zero,
{
@ -67,7 +67,7 @@ where
})
}
// 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(|| {
Box::new(EvalAltResult::ErrorArithmetic(
format!("Negation overflow: -{}", x),
@ -76,7 +76,7 @@ fn neg<T: Display + CheckedNeg>(x: T) -> FuncReturn<T> {
})
}
// 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
// when the number is ::MIN instead of returning ::MIN itself.
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)
}
// 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
if y < 0 {
return Err(Box::new(EvalAltResult::ErrorArithmetic(
@ -150,7 +150,7 @@ fn shl<T: Display + CheckedShl>(x: T, y: INT) -> FuncReturn<T> {
})
}
// 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
if y < 0 {
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
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))
}
// 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))
}
// 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(|| {
Box::new(EvalAltResult::ErrorArithmetic(
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)
}
// 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"))]
{
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)
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))
}
// Floating-point power - always well-defined
#[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))
}
// Checked power
#[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
if y > (i32::MAX as INT) {
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)
#[cfg(feature = "unchecked")]
#[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))
}
@ -269,119 +269,69 @@ macro_rules! reg_op {
}
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
// Checked basic arithmetic
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_op!(lib, "+", add, INT);
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"))]
#[cfg(not(feature = "unchecked"))]
{
reg_op!(lib, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// Checked basic arithmetic
reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, 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")]
{
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"))]
#[cfg(feature = "unchecked")]
{
reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
// Unchecked basic arithmetic
reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, 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
#[cfg(not(feature = "no_float"))]
{
reg_op!(lib, "+", add_u, f32, f64);
reg_op!(lib, "-", sub_u, f32, f64);
reg_op!(lib, "*", mul_u, f32, f64);
reg_op!(lib, "/", div_u, f32, f64);
reg_op!(lib, "+", add_u, f32);
reg_op!(lib, "-", sub_u, f32);
reg_op!(lib, "*", mul_u, f32);
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_i64"))]
{
reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "^", binary_xor, 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, 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"))]
{
reg_op!(lib, "%", modulo_u, f32, f64);
lib.set_fn_2("~", pow_f_f);
// Checked power
#[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
@ -411,11 +361,4 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
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::engine::Array;
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
fn push<T: Variant + Clone>(list: &mut Array, item: T) -> FuncReturn<()> {
@ -45,14 +45,18 @@ macro_rules! reg_tri {
#[cfg(not(feature = "no_index"))]
def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
reg_op!(lib, "push", push, INT, bool, char, String, Array, ());
reg_tri!(lib, "pad", pad, INT, bool, char, String, Array, ());
reg_tri!(lib, "insert", ins, INT, bool, char, String, Array, ());
reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ());
reg_tri!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ());
reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, Array, ());
lib.set_fn_2_mut("append", |x: &mut Array, y: Array| {
x.extend(y);
Ok(())
});
lib.set_fn_2_mut("+=", |x: &mut Array, y: Array| {
x.extend(y);
Ok(())
});
lib.set_fn_2(
"+",
|mut x: Array, y: Array| {

View File

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

View File

@ -1,8 +1,5 @@
use crate::def_package;
use crate::module::FuncReturn;
use crate::parser::INT;
use crate::stdlib::string::String;
// Comparison operators
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
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> {
Ok(!x)
}
@ -42,48 +33,26 @@ macro_rules! reg_op {
}
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_i64"))]
{
reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "!=", ne, 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, 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, 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, u32, u64, i128, u128);
}
#[cfg(not(feature = "no_float"))]
{
reg_op!(lib, "<", lt, f32, f64);
reg_op!(lib, "<=", lte, f32, f64);
reg_op!(lib, ">", gt, f32, f64);
reg_op!(lib, ">=", gte, f32, f64);
reg_op!(lib, "==", eq, f32, f64);
reg_op!(lib, "!=", ne, f32, f64);
reg_op!(lib, "<", lt, f32);
reg_op!(lib, "<=", lte, f32);
reg_op!(lib, ">", gt, f32);
reg_op!(lib, ">=", gte, f32);
reg_op!(lib, "==", eq, f32);
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);
});

View File

@ -4,12 +4,9 @@ use crate::any::Dynamic;
use crate::def_package;
use crate::engine::Map;
use crate::module::FuncReturn;
use crate::parser::INT;
use crate::parser::{ImmutableString, INT};
use crate::stdlib::{
string::{String, ToString},
vec::Vec,
};
use crate::stdlib::{string::ToString, vec::Vec};
fn map_get_keys(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
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, {
lib.set_fn_2_mut(
"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("clear", |map: &mut Map| {
@ -31,7 +28,7 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
});
lib.set_fn_2_mut(
"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(
"mixin",
@ -42,6 +39,15 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
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(
"+",
|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.
use crate::fn_native::{CallableFunction, IteratorFn};
use crate::fn_native::{CallableFunction, IteratorFn, Shared};
use crate::module::Module;
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 eval;
mod iter_basic;
@ -44,35 +44,27 @@ pub trait Package {
fn get(&self) -> PackageLibrary;
}
/// Type which `Rc`-wraps a `Module` to facilitate sharing library instances.
#[cfg(not(feature = "sync"))]
pub type PackageLibrary = Rc<Module>;
/// Type which `Arc`-wraps a `Module` to facilitate sharing library instances.
#[cfg(feature = "sync")]
pub type PackageLibrary = Arc<Module>;
/// A sharable `Module` to facilitate sharing library instances.
pub type PackageLibrary = Shared<Module>;
/// Type containing a collection of `PackageLibrary` instances.
/// All function and type iterator keys in the loaded packages are indexed for fast access.
#[derive(Clone, Default)]
pub(crate) struct PackagesCollection {
/// Collection of `PackageLibrary` instances.
packages: StaticVec<PackageLibrary>,
}
pub(crate) struct PackagesCollection(StaticVec<PackageLibrary>);
impl PackagesCollection {
/// Add a `PackageLibrary` into the `PackagesCollection`.
pub fn push(&mut self, package: PackageLibrary) {
// 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`?
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.
pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
self.packages
self.0
.iter()
.map(|p| p.get_fn(hash))
.find(|f| f.is_some())
@ -80,11 +72,11 @@ impl PackagesCollection {
}
/// Does the specified TypeId iterator exist in the `PackagesCollection`?
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.
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
self.packages
self.0
.iter()
.map(|p| p.get_iter(id))
.find(|f| f.is_some())

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@ use crate::stdlib::path::PathBuf;
#[derive(Debug)]
pub enum EvalAltResult {
/// Syntax error.
ErrorParsing(Box<ParseError>),
ErrorParsing(ParseError),
/// 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> {
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))
}
}

View File

@ -21,8 +21,8 @@ type LERR = LexError;
/// 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,
/// meaning they go up to a maximum of 65,535 lines/characters per line.
/// 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 and characters per line.
/// 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)]
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
/// 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

View File

@ -4,14 +4,20 @@
//!
//! 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::{
any::TypeId,
borrow::Borrow,
boxed::Box,
fmt,
hash::{Hash, Hasher},
iter::FromIterator,
mem,
mem::MaybeUninit,
ops::{Drop, Index, IndexMut},
ops::{Add, AddAssign, Deref, Drop, Index, IndexMut},
str::FromStr,
string::{String, ToString},
vec::Vec,
};
@ -560,3 +566,273 @@ impl<T> From<Vec<T>> for StaticVec<T> {
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]
#[cfg(not(feature = "no_object"))]
fn struct_with_float() -> Result<(), Box<EvalAltResult>> {
fn test_struct_with_float() -> Result<(), Box<EvalAltResult>> {
#[derive(Clone)]
struct TestStruct {
x: f64,
@ -64,3 +64,16 @@ fn struct_with_float() -> Result<(), Box<EvalAltResult>> {
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"))]
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
use rhai::{Engine, EvalAltResult, ImmutableString, RegisterFn, INT};
#[test]
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);
#[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.add(); a.x")?, 42);

View File

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

File diff suppressed because one or more lines are too long