Merge pull request #138 from schungx/master

Turn Dynamic into enum, plus benchmarks
This commit is contained in:
Stephen Chung 2020-04-19 21:42:42 +08:00 committed by GitHub
commit a1ec35f11d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 3763 additions and 2517 deletions

29
.github/workflows/benchmark.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Benchmark
on:
push:
branches:
- master
jobs:
benchmark:
name: Run Rust benchmark
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: rustup toolchain update nightly && rustup default nightly
- name: Run benchmark
run: cargo +nightly bench | tee output.txt
- name: Store benchmark result
uses: rhysd/github-action-benchmark@v1
with:
name: Rust Benchmark
tool: 'cargo'
output-file-path: output.txt
# Use personal access token instead of GITHUB_TOKEN due to https://github.community/t5/GitHub-Actions/Github-action-not-triggering-gh-pages-upon-push/td-p/26869/highlight/false
github-token: ${{ secrets.RHAI }}
auto-push: true
# Show alert with commit comment on detecting possible performance regression
alert-threshold: '200%'
comment-on-alert: true
fail-on-alert: true
alert-comment-cc-users: '@schungx'

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rhai" name = "rhai"
version = "0.12.0" version = "0.13.0"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"

154
README.md
View File

@ -14,20 +14,21 @@ to add scripting to any application.
Rhai's current features set: Rhai's current features set:
* `no-std` support * `no-std` support
* Easy integration with Rust native functions and data types, including getter/setter methods * Easy integration with Rust native functions and types, including getter/setter/methods
* Easily call a script-defined function from Rust * Easily call a script-defined function from Rust
* Freely pass variables/constants into a script via an external [`Scope`] * Freely pass variables/constants into a script via an external [`Scope`]
* Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop) * Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop)
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app) * Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
* Easy-to-use language similar to JS+Rust * Easy-to-use language similar to JS+Rust
* Support for overloaded functions * Support for function overloading
* Support for operator overloading
* Compiled script is optimized for repeat evaluations * Compiled script is optimized for repeat evaluations
* Support for minimal builds by excluding unneeded language features * Support for minimal builds by excluding unneeded language features
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
to do checked arithmetic operations); for [`no_std`] builds, a number of additional dependencies are to do checked arithmetic operations); for [`no_std`] builds, a number of additional dependencies are
pulled in to provide for functionalities that used to be in `std`. pulled in to provide for functionalities that used to be in `std`.
**Note:** Currently, the version is 0.12.0, so the language and API's may change before they stabilize. **Note:** Currently, the version is 0.13.0, so the language and API's may change before they stabilize.
Installation Installation
------------ ------------
@ -36,7 +37,7 @@ Install the Rhai crate by adding this line to `dependencies`:
```toml ```toml
[dependencies] [dependencies]
rhai = "0.12.0" rhai = "0.13.0"
``` ```
Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/): Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/):
@ -87,6 +88,39 @@ Excluding unneeded functionalities can result in smaller, faster builds as well
[`no_std`]: #optional-features [`no_std`]: #optional-features
[`sync`]: #optional-features [`sync`]: #optional-features
### Performance builds
Some features are for performance. For example, using `only_i32` or `only_i64` disables all other integer types (such as `u16`).
If only a single integer type is needed in scripts - most of the time this is the case - it is best to avoid registering
lots of functions related to other integer types that will never be used. As a result, performance will improve.
If only 32-bit integers are needed - again, most of the time this is the case - using `only_i32` disables also `i64`.
On 64-bit targets this may not gain much, but on some 32-bit targets this improves performance due to 64-bit arithmetic
requiring more CPU cycles to complete.
Also, turning on `no_float`, and `only_i32` makes the key [`Dynamic`] data type only 8 bytes small on 32-bit targets
while normally it can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`.
Making [`Dynamic`] small helps performance due to more caching efficiency.
### Minimal builds
In order to compile a _minimal_build - i.e. a build optimized for size - perhaps for embedded targets, it is essential that
the correct linker flags are used in `cargo.toml`:
```toml
[profile.release]
opt-level = "z" # optimize for size
```
Opt out of as many features as possible, if they are not needed, to reduce code size because, remember, by default
all code is compiled in as what a script requires cannot be predicted. If a language feature is not needed,
omitting them via special features is a prudent strategy to optimize the build for size.
Start by using [`Engine::new_raw`](#raw-engine) to create a _raw_ engine which does not register the standard library of utility
functions. Secondly, omitting arrays (`no_index`) yields the most code-size savings, followed by floating-point support
(`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.
Related Related
------- -------
@ -146,6 +180,8 @@ There are also a number of examples scripts that showcase Rhai's features, all i
| -------------------------------------------- | ---------------------------------------------------------------------------------- | | -------------------------------------------- | ---------------------------------------------------------------------------------- |
| [`speed_test.rhai`](scripts/speed_test.rhai) | a simple program to measure the speed of Rhai's interpreter (1 million iterations) | | [`speed_test.rhai`](scripts/speed_test.rhai) | a simple program to measure the speed of Rhai's interpreter (1 million iterations) |
| [`primes.rhai`](scripts/primes.rhai) | use Sieve of Eratosthenes to find all primes smaller than a limit | | [`primes.rhai`](scripts/primes.rhai) | use Sieve of Eratosthenes to find all primes smaller than a limit |
| [`fibonacci.rhai`](scripts/fibonacci.rhai) | calculate the n-th Fibonacci number using a really dumb algorithm |
| [`mat_mul.rhai`](scripts/mat_mul.rhai) | matrix multiplication test to measure the speed of Rhai's interpreter |
To run the scripts, either make a tiny program or use of the `rhai_runner` example: To run the scripts, either make a tiny program or use of the `rhai_runner` example:
@ -180,13 +216,18 @@ fn main() -> Result<(), EvalAltResult>
### Script evaluation ### Script evaluation
The type parameter is used to specify the type of the return value, which _must_ match the actual type or an error is returned. The type parameter is used to specify the type of the return value, which _must_ match the actual type or an error is returned.
Rhai is very strict here. There are two ways to specify the return type - _turbofish_ notation, or type inference. Rhai is very strict here. Use [`Dynamic`] for uncertain return types.
There are two ways to specify the return type - _turbofish_ notation, or type inference.
```rust ```rust
let result = engine.eval::<i64>("40 + 2")?; // return type is i64, specified using 'turbofish' notation let result = engine.eval::<i64>("40 + 2")?; // return type is i64, specified using 'turbofish' notation
let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64 let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64
result.is::<i64>() == true;
let result: Dynamic = engine.eval("boo()")?; // use 'Dynamic' if you're not sure what type it'll be!
let result = engine.eval::<String>("40 + 2")?; // returns an error because the actual return type is i64, not String let result = engine.eval::<String>("40 + 2")?; // returns an error because the actual return type is i64, not String
``` ```
@ -317,8 +358,6 @@ Use `Engine::new_raw` to create a _raw_ `Engine`, in which:
let mut engine = Engine::new_raw(); // create a 'raw' Engine let mut engine = Engine::new_raw(); // create a 'raw' Engine
engine.register_stdlib(); // register the standard library manually engine.register_stdlib(); // register the standard library manually
engine.
``` ```
Evaluate expressions only Evaluate expressions only
@ -375,7 +414,7 @@ The default integer type is `i64`. If other integer types are not needed, it is
smaller build with the [`only_i64`] feature. smaller build with the [`only_i64`] feature.
If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`. If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`.
This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty. This is useful on some 32-bit targets where using 64-bit integers incur a performance penalty.
If no floating-point is needed or supported, use the [`no_float`] feature to remove it. If no floating-point is needed or supported, use the [`no_float`] feature to remove it.
@ -439,12 +478,10 @@ There is no easy way for Rust to decide, at run-time, what type the `Dynamic` va
function and match against the name). function and match against the name).
A `Dynamic` value's actual type can be checked via the `is` method. A `Dynamic` value's actual type can be checked via the `is` method.
The `cast` method (from the `rhai::AnyExt` trait) then converts the value into a specific, known type. The `cast` method then converts the value into a specific, known type.
Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails. Alternatively, use the `try_cast` method which does not panic but returns `None` when the cast fails.
```rust ```rust
use rhai::AnyExt; // pull in the trait.
let list: Array = engine.eval("...")?; // return type is 'Array' let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic' let item = list[0]; // an element in an 'Array' is 'Dynamic'
@ -453,14 +490,12 @@ item.is::<i64>() == true; // 'is' returns whether a 'Dynam
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
let value: i64 = item.cast(); // type can also be inferred let value: i64 = item.cast(); // type can also be inferred
let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns an error 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 you may match against.
```rust ```rust
use rhai::Any; // pull in the trait.
let list: Array = engine.eval("...")?; // return type is 'Array' let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic' let item = list[0]; // an element in an 'Array' is 'Dynamic'
@ -499,8 +534,6 @@ A number of traits, under the `rhai::` module namespace, provide additional func
| Trait | Description | Methods | | Trait | Description | Methods |
| ------------------- | --------------------------------------------------------------------------------- | --------------------------------------- | | ------------------- | --------------------------------------------------------------------------------- | --------------------------------------- |
| `Any` | Generic trait that represents a [`Dynamic`] type | `type_id`, `type_name`, `into_dynamic` |
| `AnyExt` | Extension trait to allows casting of a [`Dynamic`] value to Rust types | `cast`, `try_cast` |
| `RegisterFn` | Trait for registering functions | `register_fn` | | `RegisterFn` | Trait for registering functions | `register_fn` |
| `RegisterDynamicFn` | Trait for registering functions returning [`Dynamic`] | `register_dynamic_fn` | | `RegisterDynamicFn` | Trait for registering functions returning [`Dynamic`] | `register_dynamic_fn` |
| `RegisterResultFn` | Trait for registering fallible functions returning `Result<`_T_`, EvalAltResult>` | `register_result_fn` | | `RegisterResultFn` | Trait for registering fallible functions returning `Result<`_T_`, EvalAltResult>` | `register_result_fn` |
@ -513,9 +546,9 @@ Rhai's scripting engine is very lightweight. It gets most of its abilities from
To call these functions, they need to be registered with the [`Engine`]. To call these functions, they need to be registered with the [`Engine`].
```rust ```rust
use rhai::{Engine, EvalAltResult}; use rhai::{Dynamic, Engine, EvalAltResult};
use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn' use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn'
use rhai::{Any, Dynamic, RegisterDynamicFn}; // use 'RegisterDynamicFn' trait for 'register_dynamic_fn' use rhai::{Dynamic, RegisterDynamicFn}; // use 'RegisterDynamicFn' trait for 'register_dynamic_fn'
// Normal function // Normal function
fn add(x: i64, y: i64) -> i64 { fn add(x: i64, y: i64) -> i64 {
@ -524,7 +557,7 @@ fn add(x: i64, y: i64) -> i64 {
// Function that returns a Dynamic value // Function that returns a Dynamic value
fn get_an_any() -> Dynamic { fn get_an_any() -> Dynamic {
(42_i64).into_dynamic() // 'into_dynamic' is defined by the 'rhai::Any' trait Dynamic::from(42_i64)
} }
fn main() -> Result<(), EvalAltResult> fn main() -> Result<(), EvalAltResult>
@ -548,17 +581,16 @@ fn main() -> Result<(), EvalAltResult>
} }
``` ```
To return a [`Dynamic`] value from a Rust function, use the `into_dynamic()` method To return a [`Dynamic`] value from a Rust function, use the `Dynamic::from` method.
(under the `rhai::Any` trait) to convert it.
```rust ```rust
use rhai::Any; // pull in the trait use rhai::Dynamic;
fn decide(yes_no: bool) -> Dynamic { fn decide(yes_no: bool) -> Dynamic {
if yes_no { if yes_no {
(42_i64).into_dynamic() Dynamic::from(42_i64)
} else { } else {
String::from("hello!").into_dynamic() // remember &str is not supported by Rhai Dynamic::from(String::from("hello!")) // remember &str is not supported by Rhai
} }
} }
``` ```
@ -567,7 +599,7 @@ Generic functions
----------------- -----------------
Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately. Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately.
Essentially this is a form of function overloading as Rhai does not support generics. This is essentially function overloading (Rhai does not natively support generics).
```rust ```rust
use std::fmt::Display; use std::fmt::Display;
@ -641,6 +673,50 @@ fn to_int(num) {
print(to_int(123)); // what happens? print(to_int(123)); // what happens?
``` ```
Operator overloading
--------------------
In Rhai, a lot of functionalities are actually implemented as functions, including basic operations such as arithmetic calculations.
For example, in the expression "`a + b`", the `+` operator is _not_ built-in, but calls a function named "`+`" instead!
```rust
let x = a + b;
let x = +(a, b); // <- the above is equivalent to this function call
```
Similarly, comparison operators including `==`, `!=` etc. are all implemented as functions, with the stark exception of `&&` and `||`.
Because they [_short-circuit_](#boolean-operators), `&&` and `||` are handled specially and _not_ via a function; as a result,
overriding them has no effect at all.
Operator functions cannot be defined as a script function (because operators syntax are not valid function names).
However, operator functions _can_ be registered to the [`Engine`] via `register_fn`, `register_result_fn` etc.
When a custom operator function is registered with the same name as an operator, it _overloads_ (or overrides) the built-in version.
```rust
use rhai::{Engine, EvalAltResult, RegisterFn};
let mut engine = Engine::new();
fn strange_add(a: i64, b: i64) -> i64 { (a + b) * 42 }
engine.register_fn("+", strange_add); // overload '+' operator for two integers!
let result: i64 = engine.eval("1 + 0"); // the overloading version is used
println!("result: {}", result); // prints 42
let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded
println!("result: {}", result); // prints 1.0
```
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 consistent and predictable manner, and will be annoyed if a calculation
for '+' turns into a subtraction, for example.
Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`].
See the [relevant section](#script-optimization) for more details.
Custom types and methods Custom types and methods
----------------------- -----------------------
@ -1152,14 +1228,17 @@ record == "Bob X. Davis: age 42 ❤\n";
The following standard methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on strings: The following standard methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on strings:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ---------- | ------------------------------------- | -------------------------------------------------------------------- | | ------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `len` | _none_ | returns the number of characters (not number of bytes) in the string | | `len` | _none_ | returns the number of characters (not number of bytes) in the string |
| `pad` | character to pad, target length | pads the string with an character to a specified length | | `pad` | character to pad, target length | pads the string with an character to at least a specified length |
| `append` | character/string to append | Adds a character or a string to the end of another string | | `append` | character/string to append | Adds a character or a string to the end of another string |
| `clear` | _none_ | empties the string | | `clear` | _none_ | empties the string |
| `truncate` | target length | cuts off the string at exactly a specified number of characters | | `truncate` | target length | cuts off the string at exactly a specified number of characters |
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string | | `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
| `replace` | target sub-string, replacement string | replaces a substring with another | | `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 sub-string, replacement string | replaces a sub-string with another |
| `trim` | _none_ | trims the string of whitespace at the beginning and end | | `trim` | _none_ | trims the string of whitespace at the beginning and end |
### Examples ### Examples
@ -1176,17 +1255,30 @@ full_name.pad(15, '$');
full_name.len() == 15; full_name.len() == 15;
full_name == "Bob C. Davis$$$"; full_name == "Bob C. Davis$$$";
let n = full_name.index_of('$');
n == 12;
full_name.index_of("$$", n + 1) == 13;
full_name.sub_string(n, 3) == "$$$";
full_name.truncate(6); full_name.truncate(6);
full_name.len() == 6; full_name.len() == 6;
full_name == "Bob C."; full_name == "Bob C.";
full_name.replace("Bob", "John"); full_name.replace("Bob", "John");
full_name.len() == 7; full_name.len() == 7;
full_name = "John C."; full_name == "John C.";
full_name.contains('C') == true; full_name.contains('C') == true;
full_name.contains("John") == true; full_name.contains("John") == true;
full_name.crop(5);
full_name == "C.";
full_name.crop(0, 1);
full_name == "C";
full_name.clear(); full_name.clear();
full_name.len() == 0; full_name.len() == 0;
``` ```
@ -1220,7 +1312,7 @@ The following methods (defined in the standard library but excluded if using a [
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | | `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid | | `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
| `len` | _none_ | returns the number of elements | | `len` | _none_ | returns the number of elements |
| `pad` | element to pad, target length | pads the array with an element until a specified length | | `pad` | element to pad, target length | pads the array with an element to at least a specified length |
| `clear` | _none_ | empties the array | | `clear` | _none_ | empties the array |
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | | `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |

1
_config.yml Normal file
View File

@ -0,0 +1 @@
theme: jekyll-theme-slate

29
benches/engine.rs Normal file
View File

@ -0,0 +1,29 @@
#![feature(test)]
///! Test evaluating expressions
extern crate test;
use rhai::{Array, Engine, Map, RegisterFn, INT};
use test::Bencher;
#[bench]
fn bench_engine_new(bench: &mut Bencher) {
bench.iter(|| Engine::new());
}
#[bench]
fn bench_engine_new_raw(bench: &mut Bencher) {
bench.iter(|| Engine::new_raw());
}
#[bench]
fn bench_engine_register_fn(bench: &mut Bencher) {
fn hello(a: INT, b: Array, c: Map) -> bool {
true
}
bench.iter(|| {
let mut engine = Engine::new();
engine.register_fn("hello", hello);
});
}

63
benches/eval_array.rs Normal file
View File

@ -0,0 +1,63 @@
#![feature(test)]
///! Test evaluating expressions
extern crate test;
use rhai::{Engine, OptimizationLevel};
use test::Bencher;
#[bench]
fn bench_eval_array_small_get(bench: &mut Bencher) {
let script = "let x = [1, 2, 3, 4, 5]; x[3]";
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_array_small_set(bench: &mut Bencher) {
let script = "let x = [1, 2, 3, 4, 5]; x[3] = 42;";
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_array_large_get(bench: &mut Bencher) {
let script = r#"let x = [ 1, 2.345, "hello", true,
[ 1, 2, 3, [ "hey", [ "deeply", "nested" ], "jude" ] ]
];
x[4][3][1][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_array_large_set(bench: &mut Bencher) {
let script = r#"let x = [ 1, 2.345, "hello", true,
[ 1, 2, 3, [ "hey", [ "deeply", "nested" ], "jude" ] ]
];
x[4] = 42
"#;
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

@ -0,0 +1,43 @@
#![feature(test)]
///! Test evaluating expressions
extern crate test;
use rhai::{Engine, OptimizationLevel};
use test::Bencher;
#[bench]
fn bench_eval_expression_single(bench: &mut Bencher) {
let script = "1";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_expression_number_literal(bench: &mut Bencher) {
let script = "2 > 1";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_expression_number_operators(bench: &mut Bencher) {
let script = "2 + 2 == 4";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}

71
benches/eval_map.rs Normal file
View File

@ -0,0 +1,71 @@
#![feature(test)]
///! Test evaluating expressions
extern crate test;
use rhai::{Engine, OptimizationLevel};
use test::Bencher;
#[bench]
fn bench_eval_map_small_get(bench: &mut Bencher) {
let script = "let x = #{a:1}; x.a";
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_map_small_set(bench: &mut Bencher) {
let script = "let x = #{a:1}; x.a = 42;";
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_map_large_get(bench: &mut Bencher) {
let script = r#"let x = #{
a:1,
b:2.345,
c:"hello",
d: true,
e: #{ x: 42, "y$@#%": (), z: [ 1, 2, 3, #{}, #{ "hey": "jude" }]}
};
x["e"].z[4].hey
"#;
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_map_large_set(bench: &mut Bencher) {
let script = r#"let x = #{
a:1,
b:2.345,
c:"hello",
d: true,
e: #{ x: 42, "y$@#%": (), z: [ 1, 2, 3, #{}, #{ "hey": "jude" }]}
};
x["e"].z[4].hey = 42;
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}

77
benches/eval_scope.rs Normal file
View File

@ -0,0 +1,77 @@
#![feature(test)]
///! Test evaluating with scope
extern crate test;
use rhai::{Engine, OptimizationLevel, Scope, INT};
use test::Bencher;
#[bench]
fn bench_eval_scope_single(bench: &mut Bencher) {
let script = "requests_made == requests_made";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let mut scope = Scope::new();
scope.push("requests_made", 99 as INT);
let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap());
}
#[bench]
fn bench_eval_scope_multiple(bench: &mut Bencher) {
let script = "requests_made > requests_succeeded";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let mut scope = Scope::new();
scope.push("requests_made", 99 as INT);
scope.push("requests_succeeded", 90 as INT);
let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap());
}
#[bench]
fn bench_eval_scope_longer(bench: &mut Bencher) {
let script = "(requests_made * requests_succeeded / 100) >= 90";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let mut scope = Scope::new();
scope.push("requests_made", 99 as INT);
scope.push("requests_succeeded", 90 as INT);
let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap());
}
#[bench]
fn bench_eval_scope_complex(bench: &mut Bencher) {
let script = r#"
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
Variable_name_with_spaces <= variableName &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let mut scope = Scope::new();
scope.push("Variable_name_with_spaces", 99 as INT);
scope.push("variableName", 90 as INT);
scope.push("modifierTest", 5 as INT);
let ast = engine.compile_expression(script).unwrap();
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap());
}

100
benches/eval_type.rs Normal file
View File

@ -0,0 +1,100 @@
#![feature(test)]
///! Test evaluating expressions
extern crate test;
use rhai::{Engine, OptimizationLevel, RegisterFn, Scope, INT};
use test::Bencher;
#[derive(Debug, Clone)]
struct Test {
x: INT,
}
impl Test {
pub fn get_x(&mut self) -> INT {
self.x
}
pub fn action(&mut self) {
self.x = 0;
}
pub fn update(&mut self, val: INT) {
self.x = val;
}
pub fn get_nest(&mut self) -> Test {
Test { x: 9 }
}
}
#[bench]
fn bench_type_field(bench: &mut Bencher) {
let script = "foo.field";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
engine.register_type_with_name::<Test>("Test");
engine.register_get("field", Test::get_x);
let ast = engine.compile_expression(script).unwrap();
let mut scope = Scope::new();
scope.push("foo", Test { x: 42 });
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap());
}
#[bench]
fn bench_type_method(bench: &mut Bencher) {
let script = "foo.action()";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
engine.register_type_with_name::<Test>("Test");
engine.register_fn("action", Test::action);
let ast = engine.compile_expression(script).unwrap();
let mut scope = Scope::new();
scope.push("foo", Test { x: 42 });
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap());
}
#[bench]
fn bench_type_method_with_params(bench: &mut Bencher) {
let script = "foo.update(1)";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
engine.register_type_with_name::<Test>("Test");
engine.register_fn("update", Test::update);
let ast = engine.compile_expression(script).unwrap();
let mut scope = Scope::new();
scope.push("foo", Test { x: 42 });
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap());
}
#[bench]
fn bench_type_method_nested(bench: &mut Bencher) {
let script = "foo.nest.field";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
engine.register_type_with_name::<Test>("Test");
engine.register_get("field", Test::get_x);
engine.register_get("nest", Test::get_nest);
let ast = engine.compile_expression(script).unwrap();
let mut scope = Scope::new();
scope.push("foo", Test { x: 42 });
bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap());
}

49
benches/iterations.rs Normal file
View File

@ -0,0 +1,49 @@
#![feature(test)]
///! Test 1,000 iterations
extern crate test;
use rhai::{Engine, OptimizationLevel, Scope, INT};
use test::Bencher;
#[bench]
fn bench_iterations_1000(bench: &mut Bencher) {
let script = r#"
let x = 1_000;
while x > 0 {
x = x - 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_iterations_fibonacci(bench: &mut Bencher) {
let script = r#"
fn fibonacci(n) {
if n < 2 {
n
} else {
fibonacci(n-1) + fibonacci(n-2)
}
}
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| {
engine
.call_fn::<_, INT>(&mut Scope::new(), &ast, "fibonacci", (20 as INT,))
.unwrap()
});
}

103
benches/parsing.rs Normal file
View File

@ -0,0 +1,103 @@
#![feature(test)]
///! Test parsing expressions
extern crate test;
use rhai::{Engine, OptimizationLevel};
use test::Bencher;
#[bench]
fn bench_parse_single(bench: &mut Bencher) {
let script = "1";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
bench.iter(|| engine.compile_expression(script).unwrap());
}
#[bench]
fn bench_parse_simple(bench: &mut Bencher) {
let script = "(requests_made * requests_succeeded / 100) >= 90";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
bench.iter(|| engine.compile_expression(script).unwrap());
}
#[bench]
fn bench_parse_full(bench: &mut Bencher) {
let script = r#"
2 > 1 &&
"something" != "nothing" ||
"2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" &&
[array, with, spaces].len() <= #{prop:name}.len() &&
modifierTest + 1000 / 2 > (80 * 100 % 2)
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
bench.iter(|| engine.compile_expression(script).unwrap());
}
#[bench]
fn bench_parse_array(bench: &mut Bencher) {
let script = r#"[1, 234.789, "hello", false, [ 9, 8, 7] ]"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
bench.iter(|| engine.compile_expression(script).unwrap());
}
#[bench]
fn bench_parse_map(bench: &mut Bencher) {
let script = r#"#{a: 1, b: 42, c: "hi", "dc%$& ": "strange", x: true, y: 123.456 }"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
bench.iter(|| engine.compile_expression(script).unwrap());
}
#[bench]
fn bench_parse_primes(bench: &mut Bencher) {
let script = r#"
// This script uses the Sieve of Eratosthenes to calculate prime numbers.
let now = timestamp();
const MAX_NUMBER_TO_CHECK = 10_000; // 1229 primes <= 10000
let prime_mask = [];
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
prime_mask[0] = prime_mask[1] = false;
let total_primes_found = 0;
for p in range(2, MAX_NUMBER_TO_CHECK) {
if prime_mask[p] {
print(p);
total_primes_found += 1;
let i = 2 * p;
while i < MAX_NUMBER_TO_CHECK {
prime_mask[i] = false;
i += p;
}
}
}
print("Total " + total_primes_found + " primes.");
print("Run time = " + now.elapsed() + " seconds.");
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
bench.iter(|| engine.compile(script).unwrap());
}

44
benches/primes.rs Normal file
View File

@ -0,0 +1,44 @@
#![feature(test)]
///! Test evaluating expressions
extern crate test;
use rhai::{Engine, OptimizationLevel};
use test::Bencher;
// This script uses the Sieve of Eratosthenes to calculate prime numbers.
const SCRIPT: &str = r#"
let now = timestamp();
const MAX_NUMBER_TO_CHECK = 1_000; // 168 primes <= 1000
let prime_mask = [];
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
prime_mask[0] = prime_mask[1] = false;
let total_primes_found = 0;
for p in range(2, MAX_NUMBER_TO_CHECK) {
if prime_mask[p] {
total_primes_found += 1;
let i = 2 * p;
while i < MAX_NUMBER_TO_CHECK {
prime_mask[i] = false;
i += p;
}
}
}
"#;
#[bench]
fn bench_eval_primes(bench: &mut Bencher) {
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

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, Position, Scope, AST}; use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST};
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
use rhai::OptimizationLevel; use rhai::OptimizationLevel;
@ -14,7 +14,6 @@ fn print_error(input: &str, err: EvalAltResult) {
let line_no = if lines.len() > 1 { let line_no = if lines.len() > 1 {
match err.position() { match err.position() {
p if p.is_none() => "".to_string(), p if p.is_none() => "".to_string(),
p if p.is_eof() => format!("{}: ", lines.len()),
p => format!("{}: ", p.line().unwrap()), p => format!("{}: ", p.line().unwrap()),
} }
} else { } else {
@ -25,15 +24,7 @@ fn print_error(input: &str, err: EvalAltResult) {
let pos = err.position(); let pos = err.position();
let pos_text = format!(" ({})", pos); let pos_text = format!(" ({})", pos);
let pos = if pos.is_eof() {
let last = lines[lines.len() - 1];
Position::new(lines.len(), last.len() + 1)
} else {
pos
};
match pos { match pos {
p if p.is_eof() => panic!("should not be EOF"),
p if p.is_none() => { p if p.is_none() => {
// No position // No position
println!("{}", err); println!("{}", err);
@ -137,9 +128,9 @@ fn main() {
_ => (), _ => (),
} }
if let Err(err) = engine match engine
.compile_with_scope(&scope, &script) .compile_with_scope(&scope, &script)
.map_err(EvalAltResult::ErrorParsing) .map_err(|err| err.into())
.and_then(|r| { .and_then(|r| {
ast_u = r.clone(); ast_u = r.clone();
@ -157,22 +148,21 @@ fn main() {
main_ast = main_ast.merge(&ast); main_ast = main_ast.merge(&ast);
// Evaluate // Evaluate
let result = engine engine.eval_ast_with_scope::<Dynamic>(&mut scope, &main_ast)
.consume_ast_with_scope(&mut scope, &main_ast) }) {
.or_else(|err| match err { Ok(result) if !result.is::<()>() => {
EvalAltResult::Return(_, _) => Ok(()), println!("=> {:?}", result);
err => Err(err), println!();
}); }
Ok(_) => (),
// Throw away all the statements, leaving only the functions Err(err) => {
main_ast.retain_functions();
result
})
{
println!(); println!();
print_error(&input, err); print_error(&input, err);
println!(); println!();
} }
} }
// Throw away all the statements, leaving only the functions
main_ast.retain_functions();
}
} }

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, Position}; use rhai::{Engine, EvalAltResult};
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
use rhai::OptimizationLevel; use rhai::OptimizationLevel;
@ -23,15 +23,9 @@ fn eprint_error(input: &str, err: EvalAltResult) {
let lines: Vec<_> = input.split('\n').collect(); let lines: Vec<_> = input.split('\n').collect();
// Print error // Print error
let pos = if err.position().is_eof() { let pos = err.position();
let last = lines[lines.len() - 1];
Position::new(lines.len(), last.len() + 1)
} else {
err.position()
};
match pos { match pos {
p if p.is_eof() => panic!("should not be EOF"),
p if p.is_none() => { p if p.is_none() => {
// No position // No position
eprintln!("{}", err); eprintln!("{}", err);

22
scripts/fibonacci.rhai Normal file
View File

@ -0,0 +1,22 @@
// This script calculates the n-th Fibonacci number using a really dumb algorithm
// to test the speed of the scripting engine.
const target = 30;
let now = timestamp();
fn fib(n) {
if n < 2 {
n
} else {
fib(n-1) + fib(n-2)
}
}
print("Ready... Go!");
let result = fib(target);
print("Fibonacci number #" + target + " = " + result);
print("Finished. Run time = " + now.elapsed() + " seconds.");

73
scripts/mat_mul.rhai Normal file
View File

@ -0,0 +1,73 @@
const SIZE = 50;
fn new_mat(x, y) {
let row = [];
row.pad(y, 0.0);
let matrix = [];
matrix.pad(x, row);
matrix
}
fn mat_gen(n) {
let m = new_mat(n, n);
let tmp = 1.0 / n.to_float() / n.to_float();
for i in range(0, n) {
for j in range(0, n) {
let foo = m[i];
foo[j] = tmp * (i.to_float() - j.to_float()) * (i.to_float() + j.to_float());
m[i] = foo;
}
}
m
}
fn mat_mul(a, b) {
let m = a.len();
let n = a[0].len();
let p = b[0].len();
let b2 = new_mat(n, p);
for i in range(0, n) {
for j in range(0, p) {
let foo = b2[j];
foo[i] = b[i][j];
b2[j] = foo;
}
}
let c = new_mat(m, p);
for i in range(0, c.len()) {
let ci = c[i];
for j in range(0, ci.len()) {
let b2j = b2[j];
ci[j] = 0.0;
for z in range(0, a[i].len()) {
let x = a[i][z];
let y = b2j[z];
ci[j] += x * y;
}
}
c[i] = ci;
}
c
}
let now = timestamp();
let a = mat_gen(SIZE);
let b = mat_gen(SIZE);
let c = mat_mul(a, b);
for i in range(0, SIZE) {
print(c[i]);
}
print("Finished. Run time = " + now.elapsed() + " seconds.");

View File

@ -1,70 +1,41 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling. //! Helper module which defines the `Any` trait to to allow dynamic value handling.
use crate::engine::{Array, Map};
use crate::parser::INT;
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, Any, TypeId},
boxed::Box, boxed::Box,
fmt, fmt,
string::String,
}; };
/// An raw value of any type. #[cfg(not(feature = "no_std"))]
use crate::stdlib::time::Instant;
/// A trait to represent any type.
/// ///
/// Currently, `Variant` is not `Send` nor `Sync`, so it can practically be any type. /// Currently, `Variant` is not `Send` nor `Sync`, so it can practically be any type.
/// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`. /// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`.
pub type Variant = dyn Any;
/// A boxed dynamic type containing any value.
///
/// Currently, `Dynamic` is not `Send` nor `Sync`, so it can practically be any type.
/// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`.
pub type Dynamic = Box<Variant>;
/// A trait covering any type.
#[cfg(feature = "sync")]
pub trait Any: crate::stdlib::any::Any + Send + Sync {
/// Get the `TypeId` of this type.
fn type_id(&self) -> TypeId;
/// Get the name of this type.
fn type_name(&self) -> &'static str;
/// Convert into `Dynamic`.
fn into_dynamic(&self) -> Dynamic;
/// This trait may only be implemented by `rhai`.
#[doc(hidden)]
fn _closed(&self) -> _Private;
}
#[cfg(feature = "sync")]
impl<T: crate::stdlib::any::Any + Clone + Send + Sync + ?Sized> Any for T {
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
}
fn type_name(&self) -> &'static str {
type_name::<T>()
}
fn into_dynamic(&self) -> Dynamic {
Box::new(self.clone())
}
fn _closed(&self) -> _Private {
_Private
}
}
/// A trait covering any type.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub trait Any: crate::stdlib::any::Any { pub trait Variant: Any {
/// Get the `TypeId` of this type. /// Convert this `Variant` trait object to `&dyn Any`.
fn type_id(&self) -> TypeId; fn as_any(&self) -> &dyn Any;
/// Convert this `Variant` trait object to `&mut dyn Any`.
fn as_mut_any(&mut self) -> &mut dyn Any;
/// Get the name of this type. /// Get the name of this type.
fn type_name(&self) -> &'static str; fn type_name(&self) -> &'static str;
/// Convert into `Dynamic`. /// Convert into `Dynamic`.
fn into_dynamic(&self) -> Dynamic; fn into_dynamic(self) -> Dynamic;
/// Clone into `Dynamic`.
fn clone_into_dynamic(&self) -> Dynamic;
/// This trait may only be implemented by `rhai`. /// This trait may only be implemented by `rhai`.
#[doc(hidden)] #[doc(hidden)]
@ -72,104 +43,349 @@ pub trait Any: crate::stdlib::any::Any {
} }
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
impl<T: crate::stdlib::any::Any + Clone + ?Sized> Any for T { impl<T: Any + Clone> Variant for T {
fn type_id(&self) -> TypeId { fn as_any(&self) -> &dyn Any {
TypeId::of::<T>() self as &dyn Any
}
fn as_mut_any(&mut self) -> &mut dyn Any {
self as &mut dyn Any
} }
fn type_name(&self) -> &'static str { fn type_name(&self) -> &'static str {
type_name::<T>() type_name::<T>()
} }
fn into_dynamic(self) -> Dynamic {
fn into_dynamic(&self) -> Dynamic { Dynamic::from(self)
Box::new(self.clone()) }
fn clone_into_dynamic(&self) -> Dynamic {
Dynamic::from(self.clone())
} }
fn _closed(&self) -> _Private { fn _closed(&self) -> _Private {
_Private _Private
} }
} }
impl Variant { /// A trait to represent any type.
#[cfg(feature = "sync")]
pub trait Variant: Any + Send + Sync {
/// Convert this `Variant` trait object to `&dyn Any`.
fn as_any(&self) -> &dyn Any;
/// Convert this `Variant` trait object to `&mut dyn Any`.
fn as_mut_any(&mut self) -> &mut dyn Any;
/// Get the name of this type.
fn type_name(&self) -> &'static str;
/// Convert into `Dynamic`.
fn into_dynamic(self) -> Dynamic;
/// Clone into `Dynamic`.
fn clone_into_dynamic(&self) -> Dynamic;
/// This trait may only be implemented by `rhai`.
#[doc(hidden)]
fn _closed(&self) -> _Private;
}
#[cfg(feature = "sync")]
impl<T: Any + Clone + Send + Sync> Variant for T {
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
fn as_mut_any(&mut self) -> &mut dyn Any {
self as &mut dyn Any
}
fn type_name(&self) -> &'static str {
type_name::<T>()
}
fn into_dynamic(self) -> Dynamic {
Dynamic::from(self)
}
fn clone_into_dynamic(&self) -> Dynamic {
Dynamic::from(self.clone())
}
fn _closed(&self) -> _Private {
_Private
}
}
impl dyn Variant {
/// Is this `Variant` a specific type? /// Is this `Variant` a specific type?
pub fn is<T: Any>(&self) -> bool { pub fn is<T: Any>(&self) -> bool {
TypeId::of::<T>() == <Variant as Any>::type_id(self) TypeId::of::<T>() == self.type_id()
} }
/// Get a reference of a specific type to the `Variant`. /// Get a reference of a specific type to the `Variant`.
/// Returns `None` if the cast fails. /// Returns `None` if the cast fails.
pub fn downcast_ref<T: Any>(&self) -> Option<&T> { pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
if self.is::<T>() { Any::downcast_ref::<T>(self.as_any())
unsafe { Some(&*(self as *const Variant as *const T)) }
} else {
None
}
} }
/// Get a mutable reference of a specific type to the `Variant`. /// Get a mutable reference of a specific type to the `Variant`.
/// Returns `None` if the cast fails. /// Returns `None` if the cast fails.
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> { pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
if self.is::<T>() { Any::downcast_mut::<T>(self.as_mut_any())
unsafe { Some(&mut *(self as *mut Variant as *mut T)) } }
} else { }
None
/// A dynamic type containing any value.
pub struct Dynamic(pub(crate) Union);
/// Internal `Dynamic` representation.
pub enum Union {
Unit(()),
Bool(bool),
Str(Box<String>),
Char(char),
Int(INT),
#[cfg(not(feature = "no_float"))]
Float(FLOAT),
Array(Box<Array>),
Map(Box<Map>),
Variant(Box<Box<dyn Variant>>),
}
impl Dynamic {
/// Does this `Dynamic` hold a variant data type
/// instead of one of the support system primitive types?
pub fn is_variant(&self) -> bool {
match self.0 {
Union::Variant(_) => true,
_ => false,
}
}
/// Is the value held by this `Dynamic` a particular type?
pub fn is<T: Variant + Clone>(&self) -> bool {
self.type_id() == TypeId::of::<T>()
}
/// Get the TypeId of the value held by this `Dynamic`.
pub fn type_id(&self) -> TypeId {
match &self.0 {
Union::Unit(_) => TypeId::of::<()>(),
Union::Bool(_) => TypeId::of::<bool>(),
Union::Str(_) => TypeId::of::<String>(),
Union::Char(_) => TypeId::of::<char>(),
Union::Int(_) => TypeId::of::<INT>(),
#[cfg(not(feature = "no_float"))]
Union::Float(_) => TypeId::of::<FLOAT>(),
Union::Array(_) => TypeId::of::<Array>(),
Union::Map(_) => TypeId::of::<Map>(),
Union::Variant(value) => (***value).type_id(),
}
}
/// Get the name of the type of the value held by this `Dynamic`.
pub fn type_name(&self) -> &'static str {
match &self.0 {
Union::Unit(_) => "()",
Union::Bool(_) => "bool",
Union::Str(_) => "string",
Union::Char(_) => "char",
Union::Int(_) => type_name::<INT>(),
#[cfg(not(feature = "no_float"))]
Union::Float(_) => type_name::<FLOAT>(),
Union::Array(_) => "array",
Union::Map(_) => "map",
#[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => "timestamp",
Union::Variant(value) => (***value).type_name(),
} }
} }
} }
impl fmt::Debug for Variant { impl fmt::Display for Dynamic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("?") match &self.0 {
Union::Unit(_) => write!(f, ""),
Union::Bool(value) => write!(f, "{}", value),
Union::Str(value) => write!(f, "{}", value),
Union::Char(value) => write!(f, "{}", value),
Union::Int(value) => write!(f, "{}", value),
#[cfg(not(feature = "no_float"))]
Union::Float(value) => write!(f, "{}", value),
Union::Array(value) => write!(f, "{:?}", value),
Union::Map(value) => write!(f, "{:?}", value),
Union::Variant(_) => write!(f, "?"),
}
}
}
impl fmt::Debug for Dynamic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
Union::Unit(value) => write!(f, "{:?}", value),
Union::Bool(value) => write!(f, "{:?}", value),
Union::Str(value) => write!(f, "{:?}", value),
Union::Char(value) => write!(f, "{:?}", value),
Union::Int(value) => write!(f, "{:?}", value),
#[cfg(not(feature = "no_float"))]
Union::Float(value) => write!(f, "{:?}", value),
Union::Array(value) => write!(f, "{:?}", value),
Union::Map(value) => write!(f, "{:?}", value),
Union::Variant(_) => write!(f, "<dynamic>"),
}
} }
} }
impl Clone for Dynamic { impl Clone for Dynamic {
fn clone(&self) -> Self { fn clone(&self) -> Self {
self.as_ref().into_dynamic() match &self.0 {
Union::Unit(value) => Self(Union::Unit(value.clone())),
Union::Bool(value) => Self(Union::Bool(value.clone())),
Union::Str(value) => Self(Union::Str(value.clone())),
Union::Char(value) => Self(Union::Char(value.clone())),
Union::Int(value) => Self(Union::Int(value.clone())),
#[cfg(not(feature = "no_float"))]
Union::Float(value) => Self(Union::Float(value.clone())),
Union::Array(value) => Self(Union::Array(value.clone())),
Union::Map(value) => Self(Union::Map(value.clone())),
Union::Variant(value) => (***value).clone_into_dynamic(),
}
} }
} }
/// An extension trait that allows down-casting a `Dynamic` value to a specific type. /// Cast a Boxed type into another type.
pub trait AnyExt: Sized { fn cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<T, Box<X>> {
/// Get a copy of a `Dynamic` value as a specific type. // Only allow casting to the exact same type
fn try_cast<T: Any + Clone>(self) -> Result<T, Self>; if TypeId::of::<X>() == TypeId::of::<T>() {
// SAFETY: just checked whether we are pointing to the correct type
/// Get a copy of a `Dynamic` value as a specific type.
///
/// # Panics
///
/// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type).
fn cast<T: Any + Clone>(self) -> T;
/// This trait may only be implemented by `rhai`.
#[doc(hidden)]
fn _closed(&self) -> _Private;
}
impl AnyExt for Dynamic {
/// Get a copy of the `Dynamic` value as a specific type.
///
/// # Example
///
/// ```
/// use rhai::{Dynamic, Any, AnyExt};
///
/// let x: Dynamic = 42_u32.into_dynamic();
///
/// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
/// ```
fn try_cast<T: Any + Clone>(self) -> Result<T, Self> {
if self.is::<T>() {
unsafe { unsafe {
let raw: *mut Variant = Box::into_raw(self); let raw: *mut dyn Any = Box::into_raw(item as Box<dyn Any>);
Ok(*Box::from_raw(raw as *mut T)) Ok(*Box::from_raw(raw as *mut T))
} }
} else { } else {
Err(self) // Return the consumed item for chaining.
Err(item)
}
}
impl Dynamic {
/// Get a reference to the inner `Union`.
pub(crate) fn get_ref(&self) -> &Union {
&self.0
}
/// Get a mutable reference to the inner `Union`.
pub(crate) fn get_mut(&mut self) -> &mut Union {
&mut self.0
}
/// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is.
///
/// Beware that you need to pass in an `Array` type for it to be recognized as an `Array`.
/// A `Vec<T>` does not get automatically converted to an `Array`, but will be a generic
/// restricted trait object instead, because `Vec<T>` is not a supported standard type.
///
/// Similarly, passing in a `HashMap<String, T>` will not get a `Map` but a trait object.
///
/// # Examples
///
/// ```
/// use rhai::Dynamic;
///
/// let result = Dynamic::from(42_i64);
/// assert_eq!(result.type_name(), "i64");
/// assert_eq!(result.to_string(), "42");
///
/// let result = Dynamic::from("hello".to_string());
/// assert_eq!(result.type_name(), "string");
/// assert_eq!(result.to_string(), "hello");
///
/// let new_result = Dynamic::from(result);
/// assert_eq!(new_result.type_name(), "string");
/// assert_eq!(new_result.to_string(), "hello");
/// ```
pub fn from<T: Variant + Clone>(value: T) -> Self {
let dyn_value = &value as &dyn Variant;
if let Some(result) = dyn_value.downcast_ref::<()>().cloned().map(Union::Unit) {
return Self(result);
} else if let Some(result) = dyn_value.downcast_ref::<bool>().cloned().map(Union::Bool) {
return Self(result);
} else if let Some(result) = dyn_value.downcast_ref::<INT>().cloned().map(Union::Int) {
return Self(result);
} else if let Some(result) = dyn_value.downcast_ref::<char>().cloned().map(Union::Char) {
return Self(result);
}
#[cfg(not(feature = "no_float"))]
{
if let Some(result) = dyn_value.downcast_ref::<FLOAT>().cloned().map(Union::Float) {
return Self(result);
}
}
let var = Box::new(value);
Self(
cast_box::<_, Dynamic>(var)
.map(|x| x.0)
.or_else(|var| {
cast_box::<_, String>(var)
.map(Box::new)
.map(Union::Str)
.or_else(|var| {
cast_box::<_, Array>(var)
.map(Box::new)
.map(Union::Array)
.or_else(|var| {
cast_box::<_, Map>(var)
.map(Box::new)
.map(Union::Map)
.or_else(|var| -> Result<Union, ()> {
Ok(Union::Variant(Box::new(var as Box<dyn Variant>)))
})
})
})
})
.unwrap(),
)
}
/// Get a copy of the `Dynamic` value as a specific type.
/// Casting to a `Dynamic` just returns as is.
///
/// Returns an error with the name of the value's actual type when the cast fails.
///
/// # Example
///
/// ```
/// use rhai::Dynamic;
///
/// let x = Dynamic::from(42_u32);
///
/// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
/// ```
pub fn try_cast<T: Variant + Clone>(self) -> Option<T> {
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return cast_box::<_, T>(Box::new(self)).ok();
}
match &self.0 {
Union::Unit(value) => (value as &dyn Variant).downcast_ref::<T>().cloned(),
Union::Bool(value) => (value as &dyn Variant).downcast_ref::<T>().cloned(),
Union::Str(value) => (value.as_ref() as &dyn Variant)
.downcast_ref::<T>()
.cloned(),
Union::Char(value) => (value as &dyn Variant).downcast_ref::<T>().cloned(),
Union::Int(value) => (value as &dyn Variant).downcast_ref::<T>().cloned(),
#[cfg(not(feature = "no_float"))]
Union::Float(value) => (value as &dyn Variant).downcast_ref::<T>().cloned(),
Union::Array(value) => (value.as_ref() as &dyn Variant)
.downcast_ref::<T>()
.cloned(),
Union::Map(value) => (value.as_ref() as &dyn Variant)
.downcast_ref::<T>()
.cloned(),
Union::Variant(value) => value.as_ref().as_ref().downcast_ref::<T>().cloned(),
} }
} }
/// Get a copy of the `Dynamic` value as a specific type. /// Get a copy of the `Dynamic` value as a specific type.
/// Casting to a `Dynamic` just returns as is.
/// ///
/// # Panics /// # Panics
/// ///
@ -178,18 +394,123 @@ impl AnyExt for Dynamic {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use rhai::{Dynamic, Any, AnyExt}; /// use rhai::Dynamic;
/// ///
/// let x: Dynamic = 42_u32.into_dynamic(); /// let x = Dynamic::from(42_u32);
/// ///
/// assert_eq!(x.cast::<u32>(), 42); /// assert_eq!(x.cast::<u32>(), 42);
/// ``` /// ```
fn cast<T: Any + Clone>(self) -> T { pub fn cast<T: Variant + Clone>(self) -> T {
self.try_cast::<T>().expect("cast failed") self.try_cast::<T>().unwrap()
} }
fn _closed(&self) -> _Private { /// Get a reference of a specific type to the `Dynamic`.
_Private /// Casting to `Dynamic` just returns a reference to it.
/// Returns `None` if the cast fails.
pub fn downcast_ref<T: Variant + Clone>(&self) -> Option<&T> {
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return (self as &dyn Variant).downcast_ref::<T>();
}
match &self.0 {
Union::Unit(value) => (value as &dyn Variant).downcast_ref::<T>(),
Union::Bool(value) => (value as &dyn Variant).downcast_ref::<T>(),
Union::Str(value) => (value.as_ref() as &dyn Variant).downcast_ref::<T>(),
Union::Char(value) => (value as &dyn Variant).downcast_ref::<T>(),
Union::Int(value) => (value as &dyn Variant).downcast_ref::<T>(),
#[cfg(not(feature = "no_float"))]
Union::Float(value) => (value as &dyn Variant).downcast_ref::<T>(),
Union::Array(value) => (value.as_ref() as &dyn Variant).downcast_ref::<T>(),
Union::Map(value) => (value.as_ref() as &dyn Variant).downcast_ref::<T>(),
Union::Variant(value) => value.as_ref().as_ref().downcast_ref::<T>(),
}
}
/// Get a mutable reference of a specific type to the `Dynamic`.
/// Casting to `Dynamic` just returns a mutable reference to it.
/// Returns `None` if the cast fails.
pub fn downcast_mut<T: Variant + Clone>(&mut self) -> Option<&mut T> {
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return (self as &mut dyn Variant).downcast_mut::<T>();
}
match &mut self.0 {
Union::Unit(value) => (value as &mut dyn Variant).downcast_mut::<T>(),
Union::Bool(value) => (value as &mut dyn Variant).downcast_mut::<T>(),
Union::Str(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::<T>(),
Union::Char(value) => (value as &mut dyn Variant).downcast_mut::<T>(),
Union::Int(value) => (value as &mut dyn Variant).downcast_mut::<T>(),
#[cfg(not(feature = "no_float"))]
Union::Float(value) => (value as &mut dyn Variant).downcast_mut::<T>(),
Union::Array(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::<T>(),
Union::Map(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::<T>(),
Union::Variant(value) => value.as_mut().as_mut().downcast_mut::<T>(),
}
}
/// Cast the `Dynamic` as the system integer type `INT` and return it.
/// Returns the name of the actual type if the cast fails.
pub(crate) fn as_int(&self) -> Result<INT, &'static str> {
match self.0 {
Union::Int(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(crate) fn as_bool(&self) -> Result<bool, &'static str> {
match self.0 {
Union::Bool(b) => Ok(b),
_ => Err(self.type_name()),
}
}
/// Cast the `Dynamic` as a `char` and return it.
/// Returns the name of the actual type if the cast fails.
pub(crate) fn as_char(&self) -> Result<char, &'static str> {
match self.0 {
Union::Char(n) => Ok(n),
_ => Err(self.type_name()),
}
}
/// Cast the `Dynamic` as a string and return the string slice.
/// Returns the name of the actual type if the cast fails.
pub(crate) fn as_str(&self) -> Result<&str, &'static str> {
match &self.0 {
Union::Str(s) => Ok(s),
_ => Err(self.type_name()),
}
}
/// Convert the `Dynamic` into `String` and return it.
/// Returns the name of the actual type if the cast fails.
pub(crate) fn take_string(self) -> Result<String, &'static str> {
match self.0 {
Union::Str(s) => Ok(*s),
_ => Err(self.type_name()),
}
}
pub(crate) fn from_unit() -> Self {
Self(Union::Unit(()))
}
pub(crate) fn from_bool(value: bool) -> Self {
Self(Union::Bool(value))
}
pub(crate) fn from_int(value: INT) -> Self {
Self(Union::Int(value))
}
#[cfg(not(feature = "no_float"))]
pub(crate) fn from_float(value: FLOAT) -> Self {
Self(Union::Float(value))
}
pub(crate) fn from_char(value: char) -> Self {
Self(Union::Char(value))
}
pub(crate) fn from_string(value: String) -> Self {
Self(Union::Str(Box::new(value)))
} }
} }

View File

@ -1,14 +1,15 @@
//! Module that defines the extern API of `Engine`. //! Module that defines the extern API of `Engine`.
use crate::any::{Any, AnyExt, Dynamic}; use crate::any::{Dynamic, Variant};
use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec, Map}; use crate::engine::{calc_fn_spec, make_getter, make_setter, Engine, FnAny, Map};
use crate::error::ParseError; use crate::error::ParseError;
use crate::fn_call::FuncArgs; use crate::fn_call::FuncArgs;
use crate::fn_register::RegisterFn; use crate::fn_register::RegisterFn;
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::parser::{lex, parse, parse_global_expr, Position, AST}; use crate::parser::{parse, parse_global_expr, AST};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::Scope; use crate::scope::Scope;
use crate::token::{lex, Position};
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
@ -58,18 +59,11 @@ pub trait IteratorCallback: Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> +
impl<F: Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static> IteratorCallback for F {} impl<F: Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>> + 'static> IteratorCallback for F {}
/// Engine public API /// Engine public API
impl<'e> Engine<'e> { impl Engine {
/// Register a custom function. /// Register a custom function.
pub(crate) fn register_fn_raw(&mut self, fn_name: &str, args: Vec<TypeId>, f: Box<FnAny>) { pub(crate) fn register_fn_raw(&mut self, fn_name: &str, args: Vec<TypeId>, f: Box<FnAny>) {
let spec = FnSpec { self.functions
name: fn_name.to_string().into(), .insert(calc_fn_spec(fn_name, args.into_iter()), f);
args,
};
if self.functions.is_none() {
self.functions = Some(HashMap::new());
}
self.functions.as_mut().unwrap().insert(spec, f);
} }
/// Register a custom type for use with the `Engine`. /// Register a custom type for use with the `Engine`.
@ -109,7 +103,7 @@ impl<'e> Engine<'e> {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn register_type<T: Any + Clone>(&mut self) { pub fn register_type<T: Variant + Clone>(&mut self) {
self.register_type_with_name::<T>(type_name::<T>()); self.register_type_with_name::<T>(type_name::<T>());
} }
@ -157,7 +151,7 @@ impl<'e> Engine<'e> {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn register_type_with_name<T: Any + Clone>(&mut self, name: &str) { pub fn register_type_with_name<T: Variant + Clone>(&mut self, name: &str) {
if self.type_names.is_none() { if self.type_names.is_none() {
self.type_names = Some(HashMap::new()); self.type_names = Some(HashMap::new());
} }
@ -171,15 +165,8 @@ impl<'e> Engine<'e> {
/// Register an iterator adapter for a type with the `Engine`. /// Register an iterator adapter for a type with the `Engine`.
/// This is an advanced feature. /// This is an advanced feature.
pub fn register_iterator<T: Any, F: IteratorCallback>(&mut self, f: F) { pub fn register_iterator<T: Variant + Clone, F: IteratorCallback>(&mut self, f: F) {
if self.type_iterators.is_none() { self.type_iterators.insert(TypeId::of::<T>(), Box::new(f));
self.type_iterators = Some(HashMap::new());
}
self.type_iterators
.as_mut()
.unwrap()
.insert(TypeId::of::<T>(), Box::new(f));
} }
/// Register a getter function for a member of a registered type with the `Engine`. /// Register a getter function for a member of a registered type with the `Engine`.
@ -221,8 +208,8 @@ impl<'e> Engine<'e> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn register_get<T, U, F>(&mut self, name: &str, callback: F) pub fn register_get<T, U, F>(&mut self, name: &str, callback: F)
where where
T: Any + Clone, T: Variant + Clone,
U: Any + Clone, U: Variant + Clone,
F: ObjectGetCallback<T, U>, F: ObjectGetCallback<T, U>,
{ {
self.register_fn(&make_getter(name), callback); self.register_fn(&make_getter(name), callback);
@ -267,8 +254,8 @@ impl<'e> Engine<'e> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn register_set<T, U, F>(&mut self, name: &str, callback: F) pub fn register_set<T, U, F>(&mut self, name: &str, callback: F)
where where
T: Any + Clone, T: Variant + Clone,
U: Any + Clone, U: Variant + Clone,
F: ObjectSetCallback<T, U>, F: ObjectSetCallback<T, U>,
{ {
self.register_fn(&make_setter(name), callback); self.register_fn(&make_setter(name), callback);
@ -315,8 +302,8 @@ impl<'e> Engine<'e> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
pub fn register_get_set<T, U, G, S>(&mut self, name: &str, get_fn: G, set_fn: S) pub fn register_get_set<T, U, G, S>(&mut self, name: &str, get_fn: G, set_fn: S)
where where
T: Any + Clone, T: Variant + Clone,
U: Any + Clone, U: Variant + Clone,
G: ObjectGetCallback<T, U>, G: ObjectGetCallback<T, U>,
S: ObjectSetCallback<T, U>, S: ObjectSetCallback<T, U>,
{ {
@ -397,7 +384,7 @@ impl<'e> Engine<'e> {
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts); let stream = lex(&scripts);
parse(&mut stream.peekable(), self, scope, optimization_level) parse(&mut stream.peekable(), self, scope, optimization_level).map_err(|err| *err)
} }
/// Read the contents of a file into a string. /// Read the contents of a file into a string.
@ -487,7 +474,7 @@ impl<'e> Engine<'e> {
/// ///
/// ``` /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// use rhai::{Engine, AnyExt}; /// use rhai::Engine;
/// ///
/// let engine = Engine::new(); /// let engine = Engine::new();
/// ///
@ -593,7 +580,9 @@ impl<'e> Engine<'e> {
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let scripts = [script]; let scripts = [script];
let stream = lex(&scripts); let stream = lex(&scripts);
parse_global_expr(&mut stream.peekable(), self, scope, self.optimization_level) parse_global_expr(&mut stream.peekable(), self, scope, self.optimization_level)
.map_err(|err| *err)
} }
/// Evaluate a script file. /// Evaluate a script file.
@ -612,7 +601,7 @@ impl<'e> Engine<'e> {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
pub fn eval_file<T: Any + Clone>(&self, path: PathBuf) -> Result<T, EvalAltResult> { pub fn eval_file<T: Variant + Clone>(&self, path: PathBuf) -> Result<T, EvalAltResult> {
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents)) Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
} }
@ -636,7 +625,7 @@ impl<'e> Engine<'e> {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
pub fn eval_file_with_scope<T: Any + Clone>( pub fn eval_file_with_scope<T: Variant + Clone>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
path: PathBuf, path: PathBuf,
@ -658,7 +647,7 @@ impl<'e> Engine<'e> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn eval<T: Any + Clone>(&self, script: &str) -> Result<T, EvalAltResult> { pub fn eval<T: Variant + Clone>(&self, script: &str) -> Result<T, EvalAltResult> {
self.eval_with_scope(&mut Scope::new(), script) self.eval_with_scope(&mut Scope::new(), script)
} }
@ -684,7 +673,7 @@ impl<'e> Engine<'e> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn eval_with_scope<T: Any + Clone>( pub fn eval_with_scope<T: Variant + Clone>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
script: &str, script: &str,
@ -709,7 +698,7 @@ impl<'e> Engine<'e> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn eval_expression<T: Any + Clone>(&self, script: &str) -> Result<T, EvalAltResult> { pub fn eval_expression<T: Variant + Clone>(&self, script: &str) -> Result<T, EvalAltResult> {
self.eval_expression_with_scope(&mut Scope::new(), script) self.eval_expression_with_scope(&mut Scope::new(), script)
} }
@ -731,7 +720,7 @@ impl<'e> Engine<'e> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn eval_expression_with_scope<T: Any + Clone>( pub fn eval_expression_with_scope<T: Variant + Clone>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
script: &str, script: &str,
@ -761,7 +750,7 @@ impl<'e> Engine<'e> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn eval_ast<T: Any + Clone>(&self, ast: &AST) -> Result<T, EvalAltResult> { pub fn eval_ast<T: Variant + Clone>(&self, ast: &AST) -> Result<T, EvalAltResult> {
self.eval_ast_with_scope(&mut Scope::new(), ast) self.eval_ast_with_scope(&mut Scope::new(), ast)
} }
@ -794,32 +783,33 @@ impl<'e> Engine<'e> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn eval_ast_with_scope<T: Any + Clone>( pub fn eval_ast_with_scope<T: Variant + Clone>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<T, EvalAltResult> { ) -> Result<T, EvalAltResult> {
self.eval_ast_with_scope_raw(scope, ast)? let result = self
.try_cast::<T>() .eval_ast_with_scope_raw(scope, ast)
.map_err(|a| { .map_err(|err| *err)?;
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).to_string(), let return_type = self.map_type_name(result.type_name());
Position::none(),
) return result.try_cast::<T>().ok_or_else(|| {
}) EvalAltResult::ErrorMismatchOutputType(return_type.to_string(), Position::none())
});
} }
pub(crate) fn eval_ast_with_scope_raw( pub(crate) fn eval_ast_with_scope_raw(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, Box<EvalAltResult>> {
ast.0 ast.0
.iter() .iter()
.try_fold(().into_dynamic(), |_, stmt| { .try_fold(Dynamic::from_unit(), |_, stmt| {
self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0) self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0)
}) })
.or_else(|err| match err { .or_else(|err| match *err {
EvalAltResult::Return(out, _) => Ok(out), EvalAltResult::Return(out, _) => Ok(out),
_ => Err(err), _ => Err(err),
}) })
@ -875,14 +865,16 @@ impl<'e> Engine<'e> {
) -> Result<(), EvalAltResult> { ) -> Result<(), EvalAltResult> {
ast.0 ast.0
.iter() .iter()
.try_fold(().into_dynamic(), |_, stmt| { .try_fold(Dynamic::from_unit(), |_, stmt| {
self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0) self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0)
}) })
.map(|_| ()) .map_or_else(
.or_else(|err| match err { |err| match *err {
EvalAltResult::Return(_, _) => Ok(()), EvalAltResult::Return(_, _) => Ok(()),
_ => Err(err), err => Err(err),
}) },
|_| Ok(()),
)
} }
/// Call a script function defined in an `AST` with multiple arguments. /// Call a script function defined in an `AST` with multiple arguments.
@ -921,7 +913,7 @@ impl<'e> Engine<'e> {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub fn call_fn<A: FuncArgs, T: Any + Clone>( pub fn call_fn<A: FuncArgs, T: Variant + Clone>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
@ -929,18 +921,19 @@ impl<'e> Engine<'e> {
args: A, args: A,
) -> Result<T, EvalAltResult> { ) -> Result<T, EvalAltResult> {
let mut arg_values = args.into_vec(); let mut arg_values = args.into_vec();
let mut args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); let mut args: Vec<_> = arg_values.iter_mut().collect();
let fn_lib = Some(ast.1.as_ref()); let fn_lib = Some(ast.1.as_ref());
let pos = Position::none(); let pos = Position::none();
self.call_fn_raw(Some(scope), fn_lib, name, &mut args, None, pos, 0)? let result = self
.call_fn_raw(Some(scope), fn_lib, name, &mut args, None, pos, 0)
.map_err(|err| *err)?;
let return_type = self.map_type_name(result.type_name());
return result
.try_cast() .try_cast()
.map_err(|a| { .ok_or_else(|| EvalAltResult::ErrorMismatchOutputType(return_type.into(), pos));
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).into(),
pos,
)
})
} }
/// Optimize the `AST` with constants defined in an external Scope. /// Optimize the `AST` with constants defined in an external Scope.
@ -961,7 +954,11 @@ impl<'e> Engine<'e> {
ast: AST, ast: AST,
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> AST { ) -> AST {
let fn_lib = ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect(); let fn_lib = ast
.1
.iter()
.map(|(_, fn_def)| fn_def.as_ref().clone())
.collect();
optimize_into_ast(self, scope, ast.0, fn_lib, optimization_level) optimize_into_ast(self, scope, ast.0, fn_lib, optimization_level)
} }
@ -972,23 +969,25 @@ impl<'e> Engine<'e> {
/// ``` /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// # use std::sync::RwLock; /// # use std::sync::RwLock;
/// # use std::sync::Arc;
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let result = RwLock::new(String::from("")); /// let result = Arc::new(RwLock::new(String::from("")));
/// { ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Override action of 'print' function /// // Override action of 'print' function
/// engine.on_print(|s| result.write().unwrap().push_str(s)); /// let logger = result.clone();
/// engine.on_print(move |s| logger.write().unwrap().push_str(s));
/// ///
/// engine.consume("print(40 + 2);")?; /// engine.consume("print(40 + 2);")?;
/// } ///
/// assert_eq!(*result.read().unwrap(), "42"); /// assert_eq!(*result.read().unwrap(), "42");
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub fn on_print(&mut self, callback: impl Fn(&str) + Send + Sync + 'e) { pub fn on_print(&mut self, callback: impl Fn(&str) + Send + Sync + 'static) {
self.on_print = Some(Box::new(callback)); self.on_print = Some(Box::new(callback));
} }
/// Override default action of `print` (print to stdout using `println!`) /// Override default action of `print` (print to stdout using `println!`)
@ -998,23 +997,25 @@ impl<'e> Engine<'e> {
/// ``` /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// # use std::sync::RwLock; /// # use std::sync::RwLock;
/// # use std::sync::Arc;
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let result = RwLock::new(String::from("")); /// let result = Arc::new(RwLock::new(String::from("")));
/// { ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Override action of 'print' function /// // Override action of 'print' function
/// engine.on_print(|s| result.write().unwrap().push_str(s)); /// let logger = result.clone();
/// engine.on_print(move |s| logger.write().unwrap().push_str(s));
/// ///
/// engine.consume("print(40 + 2);")?; /// engine.consume("print(40 + 2);")?;
/// } ///
/// assert_eq!(*result.read().unwrap(), "42"); /// assert_eq!(*result.read().unwrap(), "42");
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub fn on_print(&mut self, callback: impl Fn(&str) + 'e) { pub fn on_print(&mut self, callback: impl Fn(&str) + 'static) {
self.on_print = Some(Box::new(callback)); self.on_print = Some(Box::new(callback));
} }
@ -1025,23 +1026,25 @@ impl<'e> Engine<'e> {
/// ``` /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// # use std::sync::RwLock; /// # use std::sync::RwLock;
/// # use std::sync::Arc;
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let result = RwLock::new(String::from("")); /// let result = Arc::new(RwLock::new(String::from("")));
/// { ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Override action of 'print' function /// // Override action of 'print' function
/// engine.on_debug(|s| result.write().unwrap().push_str(s)); /// let logger = result.clone();
/// engine.on_debug(move |s| logger.write().unwrap().push_str(s));
/// ///
/// engine.consume(r#"debug("hello");"#)?; /// engine.consume(r#"debug("hello");"#)?;
/// } ///
/// assert_eq!(*result.read().unwrap(), r#""hello""#); /// assert_eq!(*result.read().unwrap(), r#""hello""#);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub fn on_debug(&mut self, callback: impl Fn(&str) + Send + Sync + 'e) { pub fn on_debug(&mut self, callback: impl Fn(&str) + Send + Sync + 'static) {
self.on_debug = Some(Box::new(callback)); self.on_debug = Some(Box::new(callback));
} }
/// Override default action of `debug` (print to stdout using `println!`) /// Override default action of `debug` (print to stdout using `println!`)
@ -1051,23 +1054,25 @@ impl<'e> Engine<'e> {
/// ``` /// ```
/// # fn main() -> Result<(), rhai::EvalAltResult> { /// # fn main() -> Result<(), rhai::EvalAltResult> {
/// # use std::sync::RwLock; /// # use std::sync::RwLock;
/// # use std::sync::Arc;
/// use rhai::Engine; /// use rhai::Engine;
/// ///
/// let result = RwLock::new(String::from("")); /// let result = Arc::new(RwLock::new(String::from("")));
/// { ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
/// ///
/// // Override action of 'print' function /// // Override action of 'print' function
/// engine.on_debug(|s| result.write().unwrap().push_str(s)); /// let logger = result.clone();
/// engine.on_debug(move |s| logger.write().unwrap().push_str(s));
/// ///
/// engine.consume(r#"debug("hello");"#)?; /// engine.consume(r#"debug("hello");"#)?;
/// } ///
/// assert_eq!(*result.read().unwrap(), r#""hello""#); /// assert_eq!(*result.read().unwrap(), r#""hello""#);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub fn on_debug(&mut self, callback: impl Fn(&str) + 'e) { pub fn on_debug(&mut self, callback: impl Fn(&str) + 'static) {
self.on_debug = Some(Box::new(callback)); self.on_debug = Some(Box::new(callback));
} }
} }

View File

@ -1,11 +1,12 @@
//! Helper module that allows registration of the _core library_ and //! Helper module that allows registration of the _core library_ and
//! _standard library_ of utility functions. //! _standard library_ of utility functions.
use crate::any::{Any, Dynamic}; use crate::any::{Dynamic, Variant};
use crate::engine::{Engine, FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; use crate::engine::{Engine, FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT};
use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
use crate::parser::{Position, INT}; use crate::parser::INT;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::Position;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::engine::Array; use crate::engine::Array;
@ -27,11 +28,18 @@ use crate::stdlib::{
format, format,
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub},
string::{String, ToString}, string::{String, ToString},
time::Instant,
vec::Vec, vec::Vec,
{i32, i64, u32}, {i32, i64, u32},
}; };
#[cfg(not(feature = "no_std"))]
use crate::stdlib::time::Instant;
#[cfg(feature = "only_i32")]
const MAX_INT: INT = i32::MAX;
#[cfg(not(feature = "only_i32"))]
const MAX_INT: INT = i64::MAX;
macro_rules! reg_op { macro_rules! reg_op {
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
$( $(
@ -86,7 +94,7 @@ fn ne<T: PartialEq>(x: T, y: T) -> bool {
x != y x != y
} }
impl Engine<'_> { impl Engine {
/// Register the core built-in library. /// Register the core built-in library.
pub(crate) fn register_core_lib(&mut self) { pub(crate) fn register_core_lib(&mut self) {
// Checked add // Checked add
@ -371,10 +379,10 @@ impl Engine<'_> {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op_result!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64); reg_op_result!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op_result!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64); reg_op_result!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op_result!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64); reg_op_result!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op_result!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64); reg_op_result!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
} }
} }
@ -388,10 +396,10 @@ impl Engine<'_> {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op!(self, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(self, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(self, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(self, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
} }
} }
@ -414,12 +422,12 @@ impl Engine<'_> {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64); reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64); reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64); reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64); reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_cmp!(self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64); reg_cmp!(self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_cmp!(self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64); reg_cmp!(self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -448,9 +456,9 @@ impl Engine<'_> {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op!(self, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -462,9 +470,13 @@ impl Engine<'_> {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op_result1!(self, "<<", shl, i64, i8, u8, i16, u16, i32, i64, u32, u64); reg_op_result1!(
reg_op_result1!(self, ">>", shr, i64, i8, u8, i16, u16, i32, i64, u32, u64); self, "<<", shl, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128
reg_op_result!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64); );
reg_op_result1!(
self, ">>", shr, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128
);
reg_op_result!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
} }
} }
@ -477,9 +489,9 @@ impl Engine<'_> {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_op!(self, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(self, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(self, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64); reg_op!(self, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
} }
} }
@ -595,8 +607,11 @@ impl Engine<'_> {
reg_fn1!(self, FUNC_TO_STRING, to_string, String, i8, u8, i16, u16); reg_fn1!(self, FUNC_TO_STRING, to_string, String, i8, u8, i16, u16);
reg_fn1!(self, KEYWORD_PRINT, to_string, String, i32, i64, u32, u64); reg_fn1!(self, KEYWORD_PRINT, to_string, String, i32, i64, u32, u64);
reg_fn1!(self, FUNC_TO_STRING, to_string, String, i32, i64, u32, u64); reg_fn1!(self, FUNC_TO_STRING, to_string, String, i32, i64, u32, u64);
reg_fn1!(self, KEYWORD_PRINT, to_string, String, i128, u128);
reg_fn1!(self, FUNC_TO_STRING, to_string, String, i128, u128);
reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i8, u8, i16, u16); reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i8, u8, i16, u16);
reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i32, i64, u32, u64); reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i32, i64, u32, u64);
reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i128, u128);
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -628,8 +643,8 @@ impl Engine<'_> {
// Register map access functions // Register map access functions
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
self.register_fn("keys", |map: Map| { self.register_fn("keys", |map: Map| {
map.into_iter() map.iter()
.map(|(k, _)| k.into_dynamic()) .map(|(k, _)| Dynamic::from(k.clone()))
.collect::<Vec<_>>() .collect::<Vec<_>>()
}); });
@ -641,15 +656,16 @@ impl Engine<'_> {
} }
// Register range function // Register range function
fn reg_range<T: Any + Clone>(engine: &mut Engine) fn reg_range<T: Variant + Clone>(engine: &mut Engine)
where where
Range<T>: Iterator<Item = T>, Range<T>: Iterator<Item = T>,
{ {
engine.register_iterator::<Range<T>, _>(|a: &Dynamic| { engine.register_iterator::<Range<T>, _>(|source: &Dynamic| {
Box::new( Box::new(
a.downcast_ref::<Range<T>>() source
.downcast_ref::<Range<T>>()
.cloned()
.unwrap() .unwrap()
.clone()
.map(|x| x.into_dynamic()), .map(|x| x.into_dynamic()),
) as Box<dyn Iterator<Item = Dynamic>> ) as Box<dyn Iterator<Item = Dynamic>>
}); });
@ -670,7 +686,7 @@ impl Engine<'_> {
) )
} }
reg_range!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64); reg_range!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
} }
// Register range function with step // Register range function with step
@ -678,12 +694,12 @@ impl Engine<'_> {
struct StepRange<T>(T, T, T) struct StepRange<T>(T, T, T)
where where
for<'a> &'a T: Add<&'a T, Output = T>, for<'a> &'a T: Add<&'a T, Output = T>,
T: Any + Clone + PartialOrd; T: Variant + Clone + PartialOrd;
impl<T> Iterator for StepRange<T> impl<T> Iterator for StepRange<T>
where where
for<'a> &'a T: Add<&'a T, Output = T>, for<'a> &'a T: Add<&'a T, Output = T>,
T: Any + Clone + PartialOrd, T: Variant + Clone + PartialOrd,
{ {
type Item = T; type Item = T;
@ -701,14 +717,15 @@ impl Engine<'_> {
fn reg_step<T>(engine: &mut Engine) fn reg_step<T>(engine: &mut Engine)
where where
for<'a> &'a T: Add<&'a T, Output = T>, for<'a> &'a T: Add<&'a T, Output = T>,
T: Any + Clone + PartialOrd, T: Variant + Clone + PartialOrd,
StepRange<T>: Iterator<Item = T>, StepRange<T>: Iterator<Item = T>,
{ {
engine.register_iterator::<StepRange<T>, _>(|a: &Dynamic| { engine.register_iterator::<StepRange<T>, _>(|source: &Dynamic| {
Box::new( Box::new(
a.downcast_ref::<StepRange<T>>() source
.downcast_ref::<StepRange<T>>()
.cloned()
.unwrap() .unwrap()
.clone()
.map(|x| x.into_dynamic()), .map(|x| x.into_dynamic()),
) as Box<dyn Iterator<Item = Dynamic>> ) as Box<dyn Iterator<Item = Dynamic>>
}); });
@ -731,7 +748,7 @@ impl Engine<'_> {
) )
} }
reg_step!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64); reg_step!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
} }
} }
} }
@ -753,7 +770,7 @@ macro_rules! reg_fn2y {
} }
/// Register the built-in library. /// Register the built-in library.
impl Engine<'_> { impl Engine {
pub fn register_stdlib(&mut self) { pub fn register_stdlib(&mut self) {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
{ {
@ -799,6 +816,8 @@ impl Engine<'_> {
self.register_fn("to_float", |x: u32| x as FLOAT); self.register_fn("to_float", |x: u32| x as FLOAT);
self.register_fn("to_float", |x: i64| x as FLOAT); self.register_fn("to_float", |x: i64| x as FLOAT);
self.register_fn("to_float", |x: u64| x as FLOAT); self.register_fn("to_float", |x: u64| x as FLOAT);
self.register_fn("to_float", |x: i128| x as FLOAT);
self.register_fn("to_float", |x: u128| x as FLOAT);
} }
} }
@ -827,7 +846,7 @@ impl Engine<'_> {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ {
self.register_result_fn("to_int", |x: f32| { self.register_result_fn("to_int", |x: f32| {
if x > (i64::MAX as f32) { if x > (MAX_INT as f32) {
return Err(EvalAltResult::ErrorArithmetic( return Err(EvalAltResult::ErrorArithmetic(
format!("Integer overflow: to_int({})", x), format!("Integer overflow: to_int({})", x),
Position::none(), Position::none(),
@ -837,7 +856,7 @@ impl Engine<'_> {
Ok(x.trunc() as INT) Ok(x.trunc() as INT)
}); });
self.register_result_fn("to_int", |x: FLOAT| { self.register_result_fn("to_int", |x: FLOAT| {
if x > (i64::MAX as FLOAT) { if x > (MAX_INT as FLOAT) {
return Err(EvalAltResult::ErrorArithmetic( return Err(EvalAltResult::ErrorArithmetic(
format!("Integer overflow: to_int({})", x), format!("Integer overflow: to_int({})", x),
Position::none(), Position::none(),
@ -866,19 +885,19 @@ impl Engine<'_> {
} }
// Register array utility functions // Register array utility functions
fn push<T: Any>(list: &mut Array, item: T) { fn push<T: Variant + Clone>(list: &mut Array, item: T) {
list.push(Box::new(item)); list.push(Dynamic::from(item));
} }
fn ins<T: Any>(list: &mut Array, position: INT, item: T) { fn ins<T: Variant + Clone>(list: &mut Array, position: INT, item: T) {
if position <= 0 { if position <= 0 {
list.insert(0, Box::new(item)); list.insert(0, Dynamic::from(item));
} else if (position as usize) >= list.len() - 1 { } else if (position as usize) >= list.len() - 1 {
push(list, item); push(list, item);
} else { } else {
list.insert(position as usize, Box::new(item)); list.insert(position as usize, Dynamic::from(item));
} }
} }
fn pad<T: Any + Clone>(list: &mut Array, len: INT, item: T) { fn pad<T: Variant + Clone>(list: &mut Array, len: INT, item: T) {
if len >= 0 { if len >= 0 {
while list.len() < len as usize { while list.len() < len as usize {
push(list, item.clone()); push(list, item.clone());
@ -905,10 +924,13 @@ impl Engine<'_> {
{ {
reg_fn2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16); reg_fn2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16);
reg_fn2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); reg_fn2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64);
reg_fn2x!(self, "push", push, &mut Array, (), i128, u128);
reg_fn3!(self, "pad", pad, &mut Array, INT, (), i8, u8, i16, u16); reg_fn3!(self, "pad", pad, &mut Array, INT, (), i8, u8, i16, u16);
reg_fn3!(self, "pad", pad, &mut Array, INT, (), i32, u32, i64, u64); reg_fn3!(self, "pad", pad, &mut Array, INT, (), i32, u32, i64, u64);
reg_fn3!(self, "pad", pad, &mut Array, INT, (), i128, u128);
reg_fn3!(self, "insert", ins, &mut Array, INT, (), i8, u8, i16, u16); reg_fn3!(self, "insert", ins, &mut Array, INT, (), i8, u8, i16, u16);
reg_fn3!(self, "insert", ins, &mut Array, INT, (), i32, i64, u32, u64); reg_fn3!(self, "insert", ins, &mut Array, INT, (), i32, i64, u32, u64);
reg_fn3!(self, "insert", ins, &mut Array, INT, (), i128, u128);
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -919,18 +941,18 @@ impl Engine<'_> {
} }
self.register_dynamic_fn("pop", |list: &mut Array| { self.register_dynamic_fn("pop", |list: &mut Array| {
list.pop().unwrap_or_else(|| ().into_dynamic()) list.pop().unwrap_or_else(|| Dynamic::from_unit())
}); });
self.register_dynamic_fn("shift", |list: &mut Array| { self.register_dynamic_fn("shift", |list: &mut Array| {
if !list.is_empty() { if !list.is_empty() {
().into_dynamic() Dynamic::from_unit()
} else { } else {
list.remove(0) list.remove(0)
} }
}); });
self.register_dynamic_fn("remove", |list: &mut Array, len: INT| { self.register_dynamic_fn("remove", |list: &mut Array, len: INT| {
if len < 0 || (len as usize) >= list.len() { if len < 0 || (len as usize) >= list.len() {
().into_dynamic() Dynamic::from_unit()
} else { } else {
list.remove(len as usize) list.remove(len as usize)
} }
@ -951,7 +973,7 @@ impl Engine<'_> {
self.register_fn("len", |map: &mut Map| map.len() as INT); self.register_fn("len", |map: &mut Map| map.len() as INT);
self.register_fn("clear", |map: &mut Map| map.clear()); self.register_fn("clear", |map: &mut Map| map.clear());
self.register_dynamic_fn("remove", |x: &mut Map, name: String| { self.register_dynamic_fn("remove", |x: &mut Map, name: String| {
x.remove(&name).unwrap_or(().into_dynamic()) x.remove(&name).unwrap_or_else(|| Dynamic::from_unit())
}); });
self.register_fn("mixin", |map1: &mut Map, map2: Map| { self.register_fn("mixin", |map1: &mut Map, map2: Map| {
map2.into_iter().for_each(|(key, value)| { map2.into_iter().for_each(|(key, value)| {
@ -983,8 +1005,13 @@ impl Engine<'_> {
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_fn2x!(self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64); reg_fn2x!(
reg_fn2y!(self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64); self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128
);
reg_fn2y!(
self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64, i128,
u128
);
} }
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -1000,17 +1027,113 @@ impl Engine<'_> {
} }
// Register string utility functions // Register string utility functions
fn sub_string(s: &mut String, start: INT, len: INT) -> String {
let offset = if s.is_empty() || len <= 0 {
return "".to_string();
} else if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
return "".to_string();
} else {
start as usize
};
let chars: Vec<_> = s.chars().collect();
let len = if offset + (len as usize) > chars.len() {
chars.len() - offset
} else {
len as usize
};
chars[offset..][..len].into_iter().collect::<String>()
}
fn crop_string(s: &mut String, start: INT, len: INT) {
let offset = if s.is_empty() || len <= 0 {
s.clear();
return;
} else if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
s.clear();
return;
} else {
start as usize
};
let chars: Vec<_> = s.chars().collect();
let len = if offset + (len as usize) > chars.len() {
chars.len() - offset
} else {
len as usize
};
s.clear();
chars[offset..][..len]
.into_iter()
.for_each(|&ch| s.push(ch));
}
self.register_fn("len", |s: &mut String| s.chars().count() as INT); self.register_fn("len", |s: &mut String| s.chars().count() as INT);
self.register_fn("contains", |s: &mut String, ch: char| s.contains(ch)); self.register_fn("contains", |s: &mut String, ch: char| s.contains(ch));
self.register_fn("contains", |s: &mut String, find: String| s.contains(&find)); self.register_fn("contains", |s: &mut String, find: String| s.contains(&find));
self.register_fn("index_of", |s: &mut String, ch: char, start: INT| {
let start = if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
return -1 as INT;
} else {
s.chars().take(start as usize).collect::<String>().len()
};
s[start..]
.find(ch)
.map(|index| s[0..start + index].chars().count() as INT)
.unwrap_or(-1 as INT)
});
self.register_fn("index_of", |s: &mut String, ch: char| {
s.find(ch)
.map(|index| s[0..index].chars().count() as INT)
.unwrap_or(-1 as INT)
});
self.register_fn("index_of", |s: &mut String, find: String, start: INT| {
let start = if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
return -1 as INT;
} else {
s.chars().take(start as usize).collect::<String>().len()
};
s[start..]
.find(&find)
.map(|index| s[0..start + index].chars().count() as INT)
.unwrap_or(-1 as INT)
});
self.register_fn("index_of", |s: &mut String, find: String| {
s.find(&find)
.map(|index| s[0..index].chars().count() as INT)
.unwrap_or(-1 as INT)
});
self.register_fn("clear", |s: &mut String| s.clear()); self.register_fn("clear", |s: &mut String| s.clear());
self.register_fn("append", |s: &mut String, ch: char| s.push(ch)); self.register_fn("append", |s: &mut String, ch: char| s.push(ch));
self.register_fn("append", |s: &mut String, add: String| s.push_str(&add)); self.register_fn("append", |s: &mut String, add: String| s.push_str(&add));
self.register_fn("sub_string", sub_string);
self.register_fn("sub_string", |s: &mut String, start: INT| {
sub_string(s, start, s.len() as INT)
});
self.register_fn("crop", crop_string);
self.register_fn("crop", |s: &mut String, start: INT| {
crop_string(s, start, s.len() as INT)
});
self.register_fn("truncate", |s: &mut String, len: INT| { self.register_fn("truncate", |s: &mut String, len: INT| {
if len >= 0 { if len >= 0 {
let chars: Vec<_> = s.chars().take(len as usize).collect(); let chars: Vec<_> = s.chars().take(len as usize).collect();
s.clear(); s.clear();
chars.iter().for_each(|&ch| s.push(ch)); chars.into_iter().for_each(|ch| s.push(ch));
} else { } else {
s.clear(); s.clear();
} }
@ -1033,22 +1156,53 @@ impl Engine<'_> {
} }
}); });
#[cfg(not(feature = "no_std"))]
{
// Register date/time functions // Register date/time functions
self.register_fn("timestamp", || Instant::now()); self.register_fn("timestamp", || Instant::now());
self.register_fn("-", |ts1: Instant, ts2: Instant| { self.register_result_fn("-", |ts1: Instant, ts2: Instant| {
if ts2 > ts1 { if ts2 > ts1 {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
return -(ts2 - ts1).as_secs_f64(); return Ok(-(ts2 - ts1).as_secs_f64());
#[cfg(feature = "no_float")] #[cfg(feature = "no_float")]
return -((ts2 - ts1).as_secs() as INT); {
let seconds = (ts2 - ts1).as_secs();
#[cfg(not(feature = "unchecked"))]
{
if seconds > (MAX_INT as u64) {
return Err(EvalAltResult::ErrorArithmetic(
format!(
"Integer overflow for timestamp duration: {}",
-(seconds as i64)
),
Position::none(),
));
}
}
return Ok(-(seconds as INT));
}
} else { } else {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
return (ts1 - ts2).as_secs_f64(); return Ok((ts1 - ts2).as_secs_f64());
#[cfg(feature = "no_float")] #[cfg(feature = "no_float")]
return (ts1 - ts2).as_secs() as INT; {
let seconds = (ts1 - ts2).as_secs();
#[cfg(not(feature = "unchecked"))]
{
if seconds > (MAX_INT as u64) {
return Err(EvalAltResult::ErrorArithmetic(
format!("Integer overflow for timestamp duration: {}", seconds),
Position::none(),
));
}
}
return Ok(seconds as INT);
}
} }
}); });
@ -1059,12 +1213,26 @@ impl Engine<'_> {
reg_cmp!(self, "==", eq, Instant); reg_cmp!(self, "==", eq, Instant);
reg_cmp!(self, "!=", ne, Instant); reg_cmp!(self, "!=", ne, Instant);
self.register_fn("elapsed", |timestamp: Instant| { self.register_result_fn("elapsed", |timestamp: Instant| {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
return timestamp.elapsed().as_secs_f64(); return Ok(timestamp.elapsed().as_secs_f64());
#[cfg(feature = "no_float")] #[cfg(feature = "no_float")]
return timestamp.elapsed().as_secs() as INT; {
let seconds = timestamp.elapsed().as_secs();
#[cfg(not(feature = "unchecked"))]
{
if seconds > (MAX_INT as u64) {
return Err(EvalAltResult::ErrorArithmetic(
format!("Integer overflow for timestamp.elapsed(): {}", seconds),
Position::none(),
));
}
}
return Ok(seconds as INT);
}
}); });
} }
} }
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
//! Module containing error definitions for the parsing process. //! Module containing error definitions for the parsing process.
use crate::parser::Position; use crate::token::Position;
use crate::stdlib::{char, error::Error, fmt, string::String}; use crate::stdlib::{char, error::Error, fmt, string::String};
@ -107,13 +107,8 @@ pub enum ParseErrorType {
impl ParseErrorType { impl ParseErrorType {
/// Make a `ParseError` using the current type and position. /// Make a `ParseError` using the current type and position.
pub(crate) fn into_err(self, pos: Position) -> ParseError { pub(crate) fn into_err(self, pos: Position) -> Box<ParseError> {
ParseError(self, pos) Box::new(ParseError(self, pos))
}
/// Make a `ParseError` using the current type and EOF position.
pub(crate) fn into_err_eof(self) -> ParseError {
ParseError(self, Position::eof())
} }
} }
@ -209,13 +204,11 @@ impl fmt::Display for ParseError {
_ => write!(f, "{}", self.desc())?, _ => write!(f, "{}", self.desc())?,
} }
if !self.1.is_eof() { if !self.1.is_none() {
write!(f, " ({})", self.1)
} else if !self.1.is_none() {
// Do not write any position if None // Do not write any position if None
Ok(()) Ok(())
} else { } else {
write!(f, " at the end of the script but there is no more input") write!(f, " ({})", self.1)
} }
} }
} }

View File

@ -2,9 +2,8 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::any::{Any, Dynamic}; use crate::any::{Dynamic, Variant};
use crate::stdlib::vec::Vec;
use crate::stdlib::{string::String, vec, vec::Vec};
/// Trait that represent arguments to a function call. /// Trait that represent arguments to a function call.
/// Any data type that can be converted into a `Vec` of `Dynamic` values can be used /// Any data type that can be converted into a `Vec` of `Dynamic` values can be used
@ -18,7 +17,7 @@ pub trait FuncArgs {
/// converted into `Dynamic`). /// converted into `Dynamic`).
macro_rules! impl_args { macro_rules! impl_args {
($($p:ident),*) => { ($($p:ident),*) => {
impl<$($p: Any + Clone),*> FuncArgs for ($($p,)*) impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*)
{ {
fn into_vec(self) -> Vec<Dynamic> { fn into_vec(self) -> Vec<Dynamic> {
let ($($p,)*) = self; let ($($p,)*) = self;

View File

@ -2,7 +2,7 @@
#![cfg(not(feature = "no_function"))] #![cfg(not(feature = "no_function"))]
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::any::Any; use crate::any::Variant;
use crate::engine::Engine; use crate::engine::Engine;
use crate::error::ParseError; use crate::error::ParseError;
use crate::parser::AST; use crate::parser::AST;
@ -88,13 +88,13 @@ macro_rules! def_anonymous_fn {
def_anonymous_fn!(imp); def_anonymous_fn!(imp);
}; };
(imp $($par:ident),*) => { (imp $($par:ident),*) => {
impl<'e, $($par: Any + Clone,)* RET: Any + Clone> Func<($($par,)*), RET> for Engine<'e> impl<$($par: Variant + Clone,)* RET: Variant + Clone> Func<($($par,)*), RET> for Engine
{ {
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
type Output = Box<dyn Fn($($par),*) -> Result<RET, EvalAltResult> + Send + Sync + 'e>; type Output = Box<dyn Fn($($par),*) -> Result<RET, EvalAltResult> + Send + Sync>;
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
type Output = Box<dyn Fn($($par),*) -> Result<RET, EvalAltResult> + 'e>; type Output = Box<dyn Fn($($par),*) -> Result<RET, EvalAltResult>>;
fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output { fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output {
let name = entry_point.to_string(); let name = entry_point.to_string();

View File

@ -2,10 +2,10 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::any::{Any, Dynamic}; use crate::any::{Dynamic, Variant};
use crate::engine::{Engine, FnCallArgs}; use crate::engine::{Engine, FnCallArgs};
use crate::parser::Position;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::token::Position;
use crate::stdlib::{any::TypeId, boxed::Box, string::ToString, vec}; use crate::stdlib::{any::TypeId, boxed::Box, string::ToString, vec};
@ -53,7 +53,7 @@ pub trait RegisterDynamicFn<FN, ARGS> {
/// ///
/// // Function that returns a Dynamic value /// // Function that returns a Dynamic value
/// fn return_the_same_as_dynamic(x: i64) -> Dynamic { /// fn return_the_same_as_dynamic(x: i64) -> Dynamic {
/// Box::new(x) /// Dynamic::from(x)
/// } /// }
/// ///
/// let mut engine = Engine::new(); /// let mut engine = Engine::new();
@ -137,7 +137,7 @@ macro_rules! def_register {
// ^ function parameter actual type (T, &T or &mut T) // ^ function parameter actual type (T, &T or &mut T)
// ^ dereferencing function // ^ dereferencing function
impl< impl<
$($par: Any + Clone,)* $($par: Variant + Clone,)*
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
FN: Fn($($param),*) -> RET + Send + Sync + 'static, FN: Fn($($param),*) -> RET + Send + Sync + 'static,
@ -145,8 +145,8 @@ macro_rules! def_register {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
FN: Fn($($param),*) -> RET + 'static, FN: Fn($($param),*) -> RET + 'static,
RET: Any RET: Variant + Clone
> RegisterFn<FN, ($($mark,)*), RET> for Engine<'_> > RegisterFn<FN, ($($mark,)*), RET> for Engine
{ {
fn register_fn(&mut self, name: &str, f: FN) { fn register_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); let fn_name = name.to_string();
@ -156,20 +156,20 @@ macro_rules! def_register {
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS { if args.len() != NUM_ARGS {
return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); return Err(Box::new(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)));
} }
#[allow(unused_variables, unused_mut)] #[allow(unused_variables, unused_mut)]
let mut drain = args.iter_mut(); let mut drain = args.iter_mut();
$( $(
// Downcast every element, return in case of a type mismatch // Downcast every element, return in case of a type mismatch
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); let $par: &mut $par = drain.next().unwrap().downcast_mut().unwrap();
)* )*
// Call the user-supplied function using ($clone) to // Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference. // potentially clone the value, otherwise pass the reference.
let r = f($(($clone)($par)),*); let r = f($(($clone)($par)),*);
Ok(Box::new(r) as Dynamic) Ok(r.into_dynamic())
}; };
self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func)); self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func));
@ -177,14 +177,14 @@ macro_rules! def_register {
} }
impl< impl<
$($par: Any + Clone,)* $($par: Variant + Clone,)*
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
FN: Fn($($param),*) -> Dynamic + Send + Sync + 'static, FN: Fn($($param),*) -> Dynamic + Send + Sync + 'static,
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
FN: Fn($($param),*) -> Dynamic + 'static, FN: Fn($($param),*) -> Dynamic + 'static,
> RegisterDynamicFn<FN, ($($mark,)*)> for Engine<'_> > RegisterDynamicFn<FN, ($($mark,)*)> for Engine
{ {
fn register_dynamic_fn(&mut self, name: &str, f: FN) { fn register_dynamic_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); let fn_name = name.to_string();
@ -194,14 +194,14 @@ macro_rules! def_register {
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS { if args.len() != NUM_ARGS {
return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); return Err(Box::new(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)));
} }
#[allow(unused_variables, unused_mut)] #[allow(unused_variables, unused_mut)]
let mut drain = args.iter_mut(); let mut drain = args.iter_mut();
$( $(
// Downcast every element, return in case of a type mismatch // Downcast every element, return in case of a type mismatch
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); let $par: &mut $par = drain.next().unwrap().downcast_mut().unwrap();
)* )*
// Call the user-supplied function using ($clone) to // Call the user-supplied function using ($clone) to
@ -213,15 +213,15 @@ macro_rules! def_register {
} }
impl< impl<
$($par: Any + Clone,)* $($par: Variant + Clone,)*
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
FN: Fn($($param),*) -> Result<RET, EvalAltResult> + Send + Sync + 'static, FN: Fn($($param),*) -> Result<RET, EvalAltResult> + Send + Sync + 'static,
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
FN: Fn($($param),*) -> Result<RET, EvalAltResult> + 'static, FN: Fn($($param),*) -> Result<RET, EvalAltResult> + 'static,
RET: Any RET: Variant + Clone
> RegisterResultFn<FN, ($($mark,)*), RET> for Engine<'_> > RegisterResultFn<FN, ($($mark,)*), RET> for Engine
{ {
fn register_result_fn(&mut self, name: &str, f: FN) { fn register_result_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); let fn_name = name.to_string();
@ -231,20 +231,20 @@ macro_rules! def_register {
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
if args.len() != NUM_ARGS { if args.len() != NUM_ARGS {
return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); return Err(Box::new(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)));
} }
#[allow(unused_variables, unused_mut)] #[allow(unused_variables, unused_mut)]
let mut drain = args.iter_mut(); let mut drain = args.iter_mut();
$( $(
// Downcast every element, return in case of a type mismatch // Downcast every element, return in case of a type mismatch
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); let $par: &mut $par = drain.next().unwrap().downcast_mut().unwrap();
)* )*
// Call the user-supplied function using ($clone) to // Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference. // potentially clone the value, otherwise pass the reference.
f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic) f($(($clone)($par)),*).map(|r| r.into_dynamic())
.map_err(|err| err.set_position(pos)) .map_err(|err| Box::new(err.set_position(pos)))
}; };
self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func)); self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func));
} }

View File

@ -82,15 +82,17 @@ mod parser;
mod result; mod result;
mod scope; mod scope;
mod stdlib; mod stdlib;
mod token;
pub use any::{Any, AnyExt, Dynamic, Variant}; pub use any::Dynamic;
pub use engine::Engine; pub use engine::Engine;
pub use error::{ParseError, ParseErrorType}; pub use error::{ParseError, ParseErrorType};
pub use fn_call::FuncArgs; pub use fn_call::FuncArgs;
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
pub use parser::{Position, AST, INT}; pub use parser::{AST, INT};
pub use result::EvalAltResult; pub use result::EvalAltResult;
pub use scope::Scope; pub use scope::Scope;
pub use token::Position;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub use fn_func::Func; pub use fn_func::Func;

View File

@ -1,11 +1,12 @@
use crate::any::{Any, Dynamic}; use crate::any::Dynamic;
use crate::engine::{ use crate::engine::{
Engine, FnAny, FnCallArgs, FnSpec, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, calc_fn_spec, Engine, FnAny, FnCallArgs, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL,
KEYWORD_TYPE_OF, KEYWORD_PRINT, KEYWORD_TYPE_OF,
}; };
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, Position, ReturnType, Stmt, AST}; use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::token::Position;
use crate::stdlib::{ use crate::stdlib::{
boxed::Box, boxed::Box,
@ -49,7 +50,7 @@ struct State<'a> {
/// Collection of constants to use for eager function evaluations. /// Collection of constants to use for eager function evaluations.
constants: Vec<(String, Expr)>, constants: Vec<(String, Expr)>,
/// An `Engine` instance for eager function evaluation. /// An `Engine` instance for eager function evaluation.
engine: &'a Engine<'a>, engine: &'a Engine,
/// Library of script-defined functions. /// Library of script-defined functions.
fn_lib: &'a [(&'a str, usize)], fn_lib: &'a [(&'a str, usize)],
/// Optimization level. /// Optimization level.
@ -59,7 +60,7 @@ struct State<'a> {
impl<'a> State<'a> { impl<'a> State<'a> {
/// Create a new State. /// Create a new State.
pub fn new( pub fn new(
engine: &'a Engine<'a>, engine: &'a Engine,
fn_lib: &'a [(&'a str, usize)], fn_lib: &'a [(&'a str, usize)],
level: OptimizationLevel, level: OptimizationLevel,
) -> Self { ) -> Self {
@ -109,19 +110,14 @@ impl<'a> State<'a> {
/// Call a registered function /// Call a registered function
fn call_fn( fn call_fn(
functions: Option<&HashMap<FnSpec, Box<FnAny>>>, functions: &HashMap<u64, Box<FnAny>>,
fn_name: &str, fn_name: &str,
args: &mut FnCallArgs, args: &mut FnCallArgs,
pos: Position, pos: Position,
) -> Result<Option<Dynamic>, EvalAltResult> { ) -> Result<Option<Dynamic>, Box<EvalAltResult>> {
let spec = FnSpec {
name: fn_name.into(),
args: args.iter().map(|a| Any::type_id(*a)).collect(),
};
// Search built-in's and external functions // Search built-in's and external functions
functions functions
.and_then(|f| f.get(&spec)) .get(&calc_fn_spec(fn_name, args.iter().map(|a| a.type_id())))
.map(|func| func(args, pos)) .map(|func| func(args, pos))
.transpose() .transpose()
} }
@ -570,7 +566,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
} }
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
let mut call_args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); let mut call_args: Vec<_> = arg_values.iter_mut().collect();
// Save the typename of the first argument if it is `type_of()` // Save the typename of the first argument if it is `type_of()`
// This is to avoid `call_args` being passed into the closure // This is to avoid `call_args` being passed into the closure
@ -580,12 +576,12 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
"" ""
}; };
call_fn(state.engine.functions.as_ref(), &id, &mut call_args, pos).ok() call_fn(&state.engine.functions, &id, &mut call_args, pos).ok()
.and_then(|result| .and_then(|result|
result.or_else(|| { result.or_else(|| {
if !arg_for_type_of.is_empty() { if !arg_for_type_of.is_empty() {
// Handle `type_of()` // Handle `type_of()`
Some(arg_for_type_of.to_string().into_dynamic()) Some(Dynamic::from_string(arg_for_type_of.to_string()))
} else { } else {
// Otherwise use the default value, if any // Otherwise use the default value, if any
def_value.clone() def_value.clone()
@ -620,7 +616,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
fn optimize<'a>( fn optimize<'a>(
statements: Vec<Stmt>, statements: Vec<Stmt>,
engine: &Engine<'a>, engine: &Engine,
scope: &Scope, scope: &Scope,
fn_lib: &'a [(&'a str, usize)], fn_lib: &'a [(&'a str, usize)],
level: OptimizationLevel, level: OptimizationLevel,

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,8 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::error::ParseError; use crate::error::ParseError;
use crate::parser::{Position, INT}; use crate::parser::INT;
use crate::token::Position;
use crate::stdlib::{ use crate::stdlib::{
error::Error, error::Error,
@ -21,7 +22,7 @@ use crate::stdlib::path::PathBuf;
#[derive(Debug)] #[derive(Debug)]
pub enum EvalAltResult { pub enum EvalAltResult {
/// Syntax error. /// Syntax error.
ErrorParsing(ParseError), ErrorParsing(Box<ParseError>),
/// Error reading from a script file. Wrapped value is the path of the script file. /// Error reading from a script file. Wrapped value is the path of the script file.
/// ///
@ -229,6 +230,11 @@ impl fmt::Display for EvalAltResult {
impl From<ParseError> for EvalAltResult { impl From<ParseError> for EvalAltResult {
fn from(err: ParseError) -> Self { fn from(err: ParseError) -> Self {
Self::ErrorParsing(Box::new(err))
}
}
impl From<Box<ParseError>> for EvalAltResult {
fn from(err: Box<ParseError>) -> Self {
Self::ErrorParsing(err) Self::ErrorParsing(err)
} }
} }
@ -279,8 +285,9 @@ impl EvalAltResult {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(_, _) => (), Self::ErrorReadingScriptFile(_, _) => (),
Self::ErrorParsing(ParseError(_, pos)) Self::ErrorParsing(err) => err.1 = new_position,
| Self::ErrorFunctionNotFound(_, pos)
Self::ErrorFunctionNotFound(_, pos)
| Self::ErrorFunctionArgsMismatch(_, _, _, pos) | Self::ErrorFunctionArgsMismatch(_, _, _, pos)
| Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorBooleanArgMismatch(_, pos)
| Self::ErrorCharMismatch(pos) | Self::ErrorCharMismatch(pos)

View File

@ -1,14 +1,10 @@
//! Module that defines the `Scope` type representing a function call-stack scope. //! Module that defines the `Scope` type representing a function call-stack scope.
use crate::any::{Any, Dynamic}; use crate::any::{Dynamic, Variant};
use crate::parser::{map_dynamic_to_expr, Expr, Position}; use crate::parser::{map_dynamic_to_expr, Expr};
use crate::token::Position;
use crate::stdlib::{ use crate::stdlib::{borrow::Cow, iter, vec::Vec};
borrow::Cow,
iter,
string::{String, ToString},
vec::Vec,
};
/// Type of an entry in the Scope. /// Type of an entry in the Scope.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
@ -20,7 +16,7 @@ pub enum EntryType {
} }
/// An entry in the Scope. /// An entry in the Scope.
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct Entry<'a> { pub struct Entry<'a> {
/// Name of the entry. /// Name of the entry.
pub name: Cow<'a, str>, pub name: Cow<'a, str>,
@ -68,7 +64,7 @@ pub(crate) struct EntryRef<'a> {
/// allowing for automatic _shadowing_. /// allowing for automatic _shadowing_.
/// ///
/// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. /// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct Scope<'a>(Vec<Entry<'a>>); pub struct Scope<'a>(Vec<Entry<'a>>);
impl<'a> Scope<'a> { impl<'a> Scope<'a> {
@ -157,8 +153,8 @@ impl<'a> Scope<'a> {
/// my_scope.push("x", 42_i64); /// my_scope.push("x", 42_i64);
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42); /// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ``` /// ```
pub fn push<K: Into<Cow<'a, str>>, T: Any + Clone>(&mut self, name: K, value: T) { pub fn push<K: Into<Cow<'a, str>>, T: Variant + Clone>(&mut self, name: K, value: T) {
self.push_dynamic_value(name, EntryType::Normal, value.into_dynamic(), false); self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value), false);
} }
/// Add (push) a new `Dynamic` entry to the Scope. /// Add (push) a new `Dynamic` entry to the Scope.
@ -166,11 +162,11 @@ impl<'a> Scope<'a> {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use rhai::{Any, Scope}; /// use rhai::{Dynamic, Scope};
/// ///
/// let mut my_scope = Scope::new(); /// let mut my_scope = Scope::new();
/// ///
/// my_scope.push_dynamic("x", (42_i64).into_dynamic()); /// my_scope.push_dynamic("x", Dynamic::from(42_i64));
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42); /// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ``` /// ```
pub fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) { pub fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) {
@ -195,8 +191,8 @@ impl<'a> Scope<'a> {
/// my_scope.push_constant("x", 42_i64); /// my_scope.push_constant("x", 42_i64);
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42); /// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ``` /// ```
pub fn push_constant<K: Into<Cow<'a, str>>, T: Any + Clone>(&mut self, name: K, value: T) { pub fn push_constant<K: Into<Cow<'a, str>>, T: Variant + Clone>(&mut self, name: K, value: T) {
self.push_dynamic_value(name, EntryType::Constant, value.into_dynamic(), true); self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value), true);
} }
/// Add (push) a new constant with a `Dynamic` value to the Scope. /// Add (push) a new constant with a `Dynamic` value to the Scope.
@ -211,11 +207,11 @@ impl<'a> Scope<'a> {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use rhai::{Any, Scope}; /// use rhai::{Dynamic, Scope};
/// ///
/// let mut my_scope = Scope::new(); /// let mut my_scope = Scope::new();
/// ///
/// my_scope.push_constant_dynamic("x", (42_i64).into_dynamic()); /// my_scope.push_constant_dynamic("x", Dynamic::from(42_i64));
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42); /// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ``` /// ```
pub fn push_constant_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) { pub fn push_constant_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) {
@ -336,13 +332,12 @@ impl<'a> Scope<'a> {
/// my_scope.push("x", 42_i64); /// my_scope.push("x", 42_i64);
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42); /// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
/// ``` /// ```
pub fn get_value<T: Any + Clone>(&self, name: &str) -> Option<T> { pub fn get_value<T: Variant + Clone>(&self, name: &str) -> Option<T> {
self.0 self.0
.iter() .iter()
.rev() .rev()
.find(|Entry { name: key, .. }| name == key) .find(|Entry { name: key, .. }| name == key)
.and_then(|Entry { value, .. }| value.downcast_ref::<T>()) .and_then(|Entry { value, .. }| value.downcast_ref::<T>().cloned())
.map(T::clone)
} }
/// Update the value of the named entry. /// Update the value of the named entry.
@ -366,7 +361,7 @@ impl<'a> Scope<'a> {
/// my_scope.set_value("x", 0_i64); /// my_scope.set_value("x", 0_i64);
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 0); /// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 0);
/// ``` /// ```
pub fn set_value<T: Any + Clone>(&mut self, name: &'a str, value: T) { pub fn set_value<T: Variant + Clone>(&mut self, name: &'a str, value: T) {
match self.get(name) { match self.get(name) {
Some(( Some((
EntryRef { EntryRef {
@ -382,8 +377,8 @@ impl<'a> Scope<'a> {
.. ..
}, },
_, _,
)) => self.0.get_mut(index).unwrap().value = value.into_dynamic(), )) => self.0.get_mut(index).unwrap().value = Dynamic::from(value),
None => self.push(name, value.into_dynamic()), None => self.push(name, value),
} }
} }
@ -402,13 +397,6 @@ impl<'a> Scope<'a> {
&mut entry.value &mut entry.value
} }
/// Get a mutable reference to an entry in the Scope and downcast it to a specific type
pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, key: EntryRef) -> &mut T {
self.get_mut(key)
.downcast_mut::<T>()
.expect("wrong type cast")
}
/// Get an iterator to entries in the Scope. /// Get an iterator to entries in the Scope.
pub fn iter(&self) -> impl Iterator<Item = &Entry> { pub fn iter(&self) -> impl Iterator<Item = &Entry> {
self.0.iter().rev() // Always search a Scope in reverse order self.0.iter().rev() // Always search a Scope in reverse order

View File

@ -8,7 +8,7 @@ mod inner {
panic, pin, prelude, ptr, result, slice, str, task, time, u128, u16, u32, u64, u8, usize, panic, pin, prelude, ptr, result, slice, str, task, time, u128, u16, u32, u64, u8, usize,
}; };
pub use alloc::{borrow, boxed, format, string, sync, vec}; pub use alloc::{borrow, boxed, format, rc, string, sync, vec};
pub use core_error as error; pub use core_error as error;

987
src/token.rs Normal file
View File

@ -0,0 +1,987 @@
//! Main module defining the lexer and parser.
use crate::error::LexError;
use crate::parser::INT;
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
use crate::stdlib::{
borrow::Cow,
boxed::Box,
char, fmt,
iter::Peekable,
str::{Chars, FromStr},
string::{String, ToString},
usize,
vec::Vec,
};
type LERR = LexError;
/// A location (line number + character position) in the input script.
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position {
/// Line number - 0 = none
line: usize,
/// Character position - 0 = BOL
pos: usize,
}
impl Position {
/// Create a new `Position`.
pub fn new(line: usize, position: usize) -> Self {
assert!(line != 0, "line cannot be zero");
assert!(
line != usize::MAX || position != usize::MAX,
"invalid position"
);
Self {
line,
pos: position,
}
}
/// Get the line number (1-based), or `None` if no position.
pub fn line(&self) -> Option<usize> {
if self.is_none() {
None
} else {
Some(self.line)
}
}
/// Get the character position (1-based), or `None` if at beginning of a line.
pub fn position(&self) -> Option<usize> {
if self.is_none() || self.pos == 0 {
None
} else {
Some(self.pos)
}
}
/// Advance by one character position.
pub(crate) fn advance(&mut self) {
self.pos += 1;
}
/// Go backwards by one character position.
///
/// # Panics
///
/// Panics if already at beginning of a line - cannot rewind to a previous line.
///
pub(crate) fn rewind(&mut self) {
assert!(self.pos > 0, "cannot rewind at position 0");
self.pos -= 1;
}
/// Advance to the next line.
pub(crate) fn new_line(&mut self) {
self.line += 1;
self.pos = 0;
}
/// Create a `Position` representing no position.
pub(crate) fn none() -> Self {
Self { line: 0, pos: 0 }
}
/// Is there no `Position`?
pub fn is_none(&self) -> bool {
self.line == 0 && self.pos == 0
}
}
impl Default for Position {
fn default() -> Self {
Self::new(1, 0)
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_none() {
write!(f, "none")
} else {
write!(f, "line {}, position {}", self.line, self.pos)
}
}
}
impl fmt::Debug for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}:{})", self.line, self.pos)
}
}
/// Tokens.
#[derive(Debug, PartialEq, Clone)]
pub enum Token {
IntegerConstant(INT),
#[cfg(not(feature = "no_float"))]
FloatConstant(FLOAT),
Identifier(String),
CharConstant(char),
StringConst(String),
LeftBrace,
RightBrace,
LeftParen,
RightParen,
LeftBracket,
RightBracket,
Plus,
UnaryPlus,
Minus,
UnaryMinus,
Multiply,
Divide,
Modulo,
PowerOf,
LeftShift,
RightShift,
SemiColon,
Colon,
Comma,
Period,
#[cfg(not(feature = "no_object"))]
MapStart,
Equals,
True,
False,
Let,
Const,
If,
Else,
While,
Loop,
For,
In,
LessThan,
GreaterThan,
LessThanEqualsTo,
GreaterThanEqualsTo,
EqualsTo,
NotEqualsTo,
Bang,
Pipe,
Or,
XOr,
Ampersand,
And,
#[cfg(not(feature = "no_function"))]
Fn,
Continue,
Break,
Return,
Throw,
PlusAssign,
MinusAssign,
MultiplyAssign,
DivideAssign,
LeftShiftAssign,
RightShiftAssign,
AndAssign,
OrAssign,
XOrAssign,
ModuloAssign,
PowerOfAssign,
LexError(Box<LexError>),
EOF,
}
impl Token {
/// Get the syntax of the token.
pub fn syntax(&self) -> Cow<str> {
use Token::*;
match self {
IntegerConstant(i) => i.to_string().into(),
#[cfg(not(feature = "no_float"))]
FloatConstant(f) => f.to_string().into(),
Identifier(s) => s.into(),
CharConstant(c) => c.to_string().into(),
LexError(err) => err.to_string().into(),
token => (match token {
StringConst(_) => "string",
LeftBrace => "{",
RightBrace => "}",
LeftParen => "(",
RightParen => ")",
LeftBracket => "[",
RightBracket => "]",
Plus => "+",
UnaryPlus => "+",
Minus => "-",
UnaryMinus => "-",
Multiply => "*",
Divide => "/",
SemiColon => ";",
Colon => ":",
Comma => ",",
Period => ".",
#[cfg(not(feature = "no_object"))]
MapStart => "#{",
Equals => "=",
True => "true",
False => "false",
Let => "let",
Const => "const",
If => "if",
Else => "else",
While => "while",
Loop => "loop",
LessThan => "<",
GreaterThan => ">",
Bang => "!",
LessThanEqualsTo => "<=",
GreaterThanEqualsTo => ">=",
EqualsTo => "==",
NotEqualsTo => "!=",
Pipe => "|",
Or => "||",
Ampersand => "&",
And => "&&",
#[cfg(not(feature = "no_function"))]
Fn => "fn",
Continue => "continue",
Break => "break",
Return => "return",
Throw => "throw",
PlusAssign => "+=",
MinusAssign => "-=",
MultiplyAssign => "*=",
DivideAssign => "/=",
LeftShiftAssign => "<<=",
RightShiftAssign => ">>=",
AndAssign => "&=",
OrAssign => "|=",
XOrAssign => "^=",
LeftShift => "<<",
RightShift => ">>",
XOr => "^",
Modulo => "%",
ModuloAssign => "%=",
PowerOf => "~",
PowerOfAssign => "~=",
For => "for",
In => "in",
EOF => "{EOF}",
_ => panic!("operator should be match in outer scope"),
})
.into(),
}
}
// Is this token EOF?
pub fn is_eof(&self) -> bool {
use Token::*;
match self {
EOF => true,
_ => false,
}
}
// If another operator is after these, it's probably an unary operator
// (not sure about fn name).
pub fn is_next_unary(&self) -> bool {
use Token::*;
match self {
LexError(_) |
LeftBrace | // (+expr) - is unary
// RightBrace | {expr} - expr not unary & is closing
LeftParen | // {-expr} - is unary
// RightParen | (expr) - expr not unary & is closing
LeftBracket | // [-expr] - is unary
// RightBracket | [expr] - expr not unary & is closing
Plus |
UnaryPlus |
Minus |
UnaryMinus |
Multiply |
Divide |
Colon |
Comma |
Period |
Equals |
LessThan |
GreaterThan |
Bang |
LessThanEqualsTo |
GreaterThanEqualsTo |
EqualsTo |
NotEqualsTo |
Pipe |
Or |
Ampersand |
And |
If |
While |
PlusAssign |
MinusAssign |
MultiplyAssign |
DivideAssign |
LeftShiftAssign |
RightShiftAssign |
AndAssign |
OrAssign |
XOrAssign |
LeftShift |
RightShift |
XOr |
Modulo |
ModuloAssign |
Return |
Throw |
PowerOf |
In |
PowerOfAssign => true,
_ => false,
}
}
/// Get the precedence number of the token.
pub fn precedence(&self) -> u8 {
use Token::*;
match self {
Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign
| RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign
| PowerOfAssign => 10,
Or | XOr | Pipe => 40,
And | Ampersand => 50,
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo | EqualsTo
| NotEqualsTo => 60,
In => 70,
Plus | Minus => 80,
Divide | Multiply | PowerOf => 90,
LeftShift | RightShift => 100,
Modulo => 110,
Period => 120,
_ => 0,
}
}
/// Does an expression bind to the right (instead of left)?
pub fn is_bind_right(&self) -> bool {
use Token::*;
match self {
// Assignments bind to the right
Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign
| RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign
| PowerOfAssign => true,
// Property access binds to the right
Period => true,
_ => false,
}
}
}
/// An iterator on a `Token` stream.
pub struct TokenIterator<'a> {
/// Can the next token be a unary operator?
can_be_unary: bool,
/// Current position.
pos: Position,
/// The input character streams.
streams: Vec<Peekable<Chars<'a>>>,
}
impl<'a> TokenIterator<'a> {
/// Consume the next character.
fn eat_next(&mut self) {
self.get_next();
self.advance();
}
/// Get the next character
fn get_next(&mut self) -> Option<char> {
loop {
if self.streams.is_empty() {
// No more streams
return None;
} else if let Some(ch) = self.streams[0].next() {
// Next character in current stream
return Some(ch);
} else {
// Jump to the next stream
let _ = self.streams.remove(0);
}
}
}
/// Peek the next character
fn peek_next(&mut self) -> Option<char> {
loop {
if self.streams.is_empty() {
// No more streams
return None;
} else if let Some(ch) = self.streams[0].peek() {
// Next character in current stream
return Some(*ch);
} else {
// Jump to the next stream
let _ = self.streams.remove(0);
}
}
}
/// Move the current position one character ahead.
fn advance(&mut self) {
self.pos.advance();
}
/// Move the current position back one character.
///
/// # Panics
///
/// Panics if already at the beginning of a line - cannot rewind to the previous line.
fn rewind(&mut self) {
self.pos.rewind();
}
/// Move the current position to the next line.
fn new_line(&mut self) {
self.pos.new_line()
}
/// Parse a string literal wrapped by `enclosing_char`.
pub fn parse_string_literal(
&mut self,
enclosing_char: char,
) -> Result<String, (LexError, Position)> {
let mut result = Vec::new();
let mut escape = String::with_capacity(12);
loop {
let next_char = self
.get_next()
.ok_or((LERR::UnterminatedString, self.pos))?;
self.advance();
match next_char {
// \...
'\\' if escape.is_empty() => {
escape.push('\\');
}
// \\
'\\' if !escape.is_empty() => {
escape.clear();
result.push('\\');
}
// \t
't' if !escape.is_empty() => {
escape.clear();
result.push('\t');
}
// \n
'n' if !escape.is_empty() => {
escape.clear();
result.push('\n');
}
// \r
'r' if !escape.is_empty() => {
escape.clear();
result.push('\r');
}
// \x??, \u????, \U????????
ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => {
let mut seq = escape.clone();
seq.push(ch);
escape.clear();
let mut out_val: u32 = 0;
let len = match ch {
'x' => 2,
'u' => 4,
'U' => 8,
_ => panic!("should be 'x', 'u' or 'U'"),
};
for _ in 0..len {
let c = self.get_next().ok_or_else(|| {
(LERR::MalformedEscapeSequence(seq.to_string()), self.pos)
})?;
seq.push(c);
self.advance();
out_val *= 16;
out_val += c.to_digit(16).ok_or_else(|| {
(LERR::MalformedEscapeSequence(seq.to_string()), self.pos)
})?;
}
result.push(
char::from_u32(out_val)
.ok_or_else(|| (LERR::MalformedEscapeSequence(seq), self.pos))?,
);
}
// \{enclosing_char} - escaped
ch if enclosing_char == ch && !escape.is_empty() => {
escape.clear();
result.push(ch)
}
// Close wrapper
ch if enclosing_char == ch && escape.is_empty() => break,
// Unknown escape sequence
_ if !escape.is_empty() => {
return Err((LERR::MalformedEscapeSequence(escape), self.pos))
}
// Cannot have new-lines inside string literals
'\n' => {
self.rewind();
return Err((LERR::UnterminatedString, self.pos));
}
// All other characters
ch => {
escape.clear();
result.push(ch);
}
}
}
Ok(result.iter().collect())
}
/// Get the next token.
fn inner_next(&mut self) -> Option<(Token, Position)> {
let mut negated = false;
while let Some(c) = self.get_next() {
self.advance();
let pos = self.pos;
match (c, self.peek_next().unwrap_or('\0')) {
// \n
('\n', _) => self.new_line(),
// digit ...
('0'..='9', _) => {
let mut result = Vec::new();
let mut radix_base: Option<u32> = None;
result.push(c);
while let Some(next_char) = self.peek_next() {
match next_char {
'0'..='9' | '_' => {
result.push(next_char);
self.eat_next();
}
#[cfg(not(feature = "no_float"))]
'.' => {
result.push(next_char);
self.eat_next();
while let Some(next_char_in_float) = self.peek_next() {
match next_char_in_float {
'0'..='9' | '_' => {
result.push(next_char_in_float);
self.eat_next();
}
_ => break,
}
}
}
// 0x????, 0o????, 0b????
ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B'
if c == '0' =>
{
result.push(next_char);
self.eat_next();
let valid = match ch {
'x' | 'X' => [
'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_',
],
'o' | 'O' => [
'0', '1', '2', '3', '4', '5', '6', '7', '_', '_', '_', '_',
'_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_',
],
'b' | 'B' => [
'0', '1', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_',
'_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_',
],
_ => panic!("unexpected character {}", ch),
};
radix_base = Some(match ch {
'x' | 'X' => 16,
'o' | 'O' => 8,
'b' | 'B' => 2,
_ => panic!("unexpected character {}", ch),
});
while let Some(next_char_in_hex) = self.peek_next() {
if !valid.contains(&next_char_in_hex) {
break;
}
result.push(next_char_in_hex);
self.eat_next();
}
}
_ => break,
}
}
if negated {
result.insert(0, '-');
}
// Parse number
if let Some(radix) = radix_base {
let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect();
return Some((
INT::from_str_radix(&out, radix)
.map(Token::IntegerConstant)
.unwrap_or_else(|_| {
Token::LexError(Box::new(LERR::MalformedNumber(
result.iter().collect(),
)))
}),
pos,
));
} else {
let out: String = result.iter().filter(|&&c| c != '_').collect();
let num = INT::from_str(&out).map(Token::IntegerConstant);
// If integer parsing is unnecessary, try float instead
#[cfg(not(feature = "no_float"))]
let num = num.or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant));
return Some((
num.unwrap_or_else(|_| {
Token::LexError(Box::new(LERR::MalformedNumber(
result.iter().collect(),
)))
}),
pos,
));
}
}
// letter or underscore ...
('A'..='Z', _) | ('a'..='z', _) | ('_', _) => {
let mut result = Vec::new();
result.push(c);
while let Some(next_char) = self.peek_next() {
match next_char {
x if x.is_ascii_alphanumeric() || x == '_' => {
result.push(x);
self.eat_next();
}
_ => break,
}
}
let is_valid_identifier = result
.iter()
.find(|&ch| char::is_ascii_alphanumeric(ch)) // first alpha-numeric character
.map(char::is_ascii_alphabetic) // is a letter
.unwrap_or(false); // if no alpha-numeric at all - syntax error
let identifier: String = result.iter().collect();
if !is_valid_identifier {
return Some((
Token::LexError(Box::new(LERR::MalformedIdentifier(identifier))),
pos,
));
}
return Some((
match identifier.as_str() {
"true" => Token::True,
"false" => Token::False,
"let" => Token::Let,
"const" => Token::Const,
"if" => Token::If,
"else" => Token::Else,
"while" => Token::While,
"loop" => Token::Loop,
"continue" => Token::Continue,
"break" => Token::Break,
"return" => Token::Return,
"throw" => Token::Throw,
"for" => Token::For,
"in" => Token::In,
#[cfg(not(feature = "no_function"))]
"fn" => Token::Fn,
_ => Token::Identifier(identifier),
},
pos,
));
}
// " - string literal
('"', _) => {
return self.parse_string_literal('"').map_or_else(
|err| Some((Token::LexError(Box::new(err.0)), err.1)),
|out| Some((Token::StringConst(out), pos)),
);
}
// ' - character literal
('\'', '\'') => {
return Some((
Token::LexError(Box::new(LERR::MalformedChar("".to_string()))),
pos,
));
}
('\'', _) => {
return Some(self.parse_string_literal('\'').map_or_else(
|err| (Token::LexError(Box::new(err.0)), err.1),
|result| {
let mut chars = result.chars();
let first = chars.next();
if chars.next().is_some() {
(Token::LexError(Box::new(LERR::MalformedChar(result))), pos)
} else {
(Token::CharConstant(first.expect("should be Some")), pos)
}
},
));
}
// Braces
('{', _) => return Some((Token::LeftBrace, pos)),
('}', _) => return Some((Token::RightBrace, pos)),
// Parentheses
('(', _) => return Some((Token::LeftParen, pos)),
(')', _) => return Some((Token::RightParen, pos)),
// Indexing
('[', _) => return Some((Token::LeftBracket, pos)),
(']', _) => return Some((Token::RightBracket, pos)),
// Map literal
#[cfg(not(feature = "no_object"))]
('#', '{') => {
self.eat_next();
return Some((Token::MapStart, pos));
}
// Operators
('+', '=') => {
self.eat_next();
return Some((Token::PlusAssign, pos));
}
('+', _) if self.can_be_unary => return Some((Token::UnaryPlus, pos)),
('+', _) => return Some((Token::Plus, pos)),
('-', '0'..='9') if self.can_be_unary => negated = true,
('-', '0'..='9') => return Some((Token::Minus, pos)),
('-', '=') => {
self.eat_next();
return Some((Token::MinusAssign, pos));
}
('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)),
('-', _) => return Some((Token::Minus, pos)),
('*', '=') => {
self.eat_next();
return Some((Token::MultiplyAssign, pos));
}
('*', _) => return Some((Token::Multiply, pos)),
// Comments
('/', '/') => {
self.eat_next();
while let Some(c) = self.get_next() {
if c == '\n' {
self.new_line();
break;
}
self.advance();
}
}
('/', '*') => {
let mut level = 1;
self.eat_next();
while let Some(c) = self.get_next() {
self.advance();
match c {
'/' => {
if self.get_next() == Some('*') {
level += 1;
}
self.advance();
}
'*' => {
if self.get_next() == Some('/') {
level -= 1;
}
self.advance();
}
'\n' => self.new_line(),
_ => (),
}
if level == 0 {
break;
}
}
}
('/', '=') => {
self.eat_next();
return Some((Token::DivideAssign, pos));
}
('/', _) => return Some((Token::Divide, pos)),
(';', _) => return Some((Token::SemiColon, pos)),
(':', _) => return Some((Token::Colon, pos)),
(',', _) => return Some((Token::Comma, pos)),
('.', _) => return Some((Token::Period, pos)),
('=', '=') => {
self.eat_next();
return Some((Token::EqualsTo, pos));
}
('=', _) => return Some((Token::Equals, pos)),
('<', '=') => {
self.eat_next();
return Some((Token::LessThanEqualsTo, pos));
}
('<', '<') => {
self.eat_next();
return Some((
if self.peek_next() == Some('=') {
self.eat_next();
Token::LeftShiftAssign
} else {
Token::LeftShift
},
pos,
));
}
('<', _) => return Some((Token::LessThan, pos)),
('>', '=') => {
self.eat_next();
return Some((Token::GreaterThanEqualsTo, pos));
}
('>', '>') => {
self.eat_next();
return Some((
if self.peek_next() == Some('=') {
self.eat_next();
Token::RightShiftAssign
} else {
Token::RightShift
},
pos,
));
}
('>', _) => return Some((Token::GreaterThan, pos)),
('!', '=') => {
self.eat_next();
return Some((Token::NotEqualsTo, pos));
}
('!', _) => return Some((Token::Bang, pos)),
('|', '|') => {
self.eat_next();
return Some((Token::Or, pos));
}
('|', '=') => {
self.eat_next();
return Some((Token::OrAssign, pos));
}
('|', _) => return Some((Token::Pipe, pos)),
('&', '&') => {
self.eat_next();
return Some((Token::And, pos));
}
('&', '=') => {
self.eat_next();
return Some((Token::AndAssign, pos));
}
('&', _) => return Some((Token::Ampersand, pos)),
('^', '=') => {
self.eat_next();
return Some((Token::XOrAssign, pos));
}
('^', _) => return Some((Token::XOr, pos)),
('%', '=') => {
self.eat_next();
return Some((Token::ModuloAssign, pos));
}
('%', _) => return Some((Token::Modulo, pos)),
('~', '=') => {
self.eat_next();
return Some((Token::PowerOfAssign, pos));
}
('~', _) => return Some((Token::PowerOf, pos)),
(ch, _) if ch.is_whitespace() => (),
(ch, _) => return Some((Token::LexError(Box::new(LERR::UnexpectedChar(ch))), pos)),
}
}
self.advance();
Some((Token::EOF, self.pos))
}
}
impl<'a> Iterator for TokenIterator<'a> {
type Item = (Token, Position);
fn next(&mut self) -> Option<Self::Item> {
self.inner_next().map(|x| {
// Save the last token
self.can_be_unary = x.0.is_next_unary();
x
})
}
}
/// Tokenize an input text stream.
pub fn lex<'a>(input: &'a [&'a str]) -> TokenIterator<'a> {
TokenIterator {
can_be_unary: true,
pos: Position::new(1, 0),
streams: input.iter().map(|s| s.chars().peekable()).collect(),
}
}

View File

@ -1,6 +1,6 @@
#![cfg(not(feature = "no_object"))] #![cfg(not(feature = "no_object"))]
use rhai::{AnyExt, Engine, EvalAltResult, Map, Scope, INT}; use rhai::{Engine, EvalAltResult, Map, Scope, INT};
#[test] #[test]
fn test_map_indexing() -> Result<(), EvalAltResult> { fn test_map_indexing() -> Result<(), EvalAltResult> {

View File

@ -81,18 +81,18 @@ fn test_side_effects_command() -> Result<(), EvalAltResult> {
#[test] #[test]
fn test_side_effects_print() -> Result<(), EvalAltResult> { fn test_side_effects_print() -> Result<(), EvalAltResult> {
use std::sync::Arc;
use std::sync::RwLock; use std::sync::RwLock;
let result = RwLock::new(String::from("")); let result = Arc::new(RwLock::new(String::from("")));
{
let mut engine = Engine::new(); let mut engine = Engine::new();
// Override action of 'print' function // Override action of 'print' function
engine.on_print(|s| result.write().unwrap().push_str(s)); let logger = result.clone();
engine.on_print(move |s| logger.write().unwrap().push_str(s));
engine.consume("print(40 + 2);")?; engine.consume("print(40 + 2);")?;
}
assert_eq!(*result.read().unwrap(), "42"); assert_eq!(*result.read().unwrap(), "42");
Ok(()) Ok(())

View File

@ -9,10 +9,10 @@ fn test_stack_overflow() -> Result<(), EvalAltResult> {
engine.eval::<i64>( engine.eval::<i64>(
r" r"
fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } } fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } }
foo(30) foo(25)
", ",
)?, )?,
465 325
); );
match engine.eval::<()>( match engine.eval::<()>(

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_string() -> Result<(), EvalAltResult> { fn test_string() -> Result<(), EvalAltResult> {
@ -33,3 +33,110 @@ fn test_string() -> Result<(), EvalAltResult> {
Ok(()) Ok(())
} }
#[cfg(not(feature = "no_stdlib"))]
#[cfg(not(feature = "no_object"))]
#[test]
fn test_string_substring() -> Result<(), EvalAltResult> {
let engine = Engine::new();
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(-1, 2)"#
)?,
"❤❤"
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1, 5)"#
)?,
"❤❤ he"
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1)"#
)?,
"❤❤ hello! ❤❤❤"
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(99)"#
)?,
""
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1, -1)"#
)?,
""
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1, 999)"#
)?,
"❤❤ hello! ❤❤❤"
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.crop(1, -1); x"#
)?,
""
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.crop(4, 6); x"#
)?,
"hello!"
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.crop(1, 999); x"#
)?,
"❤❤ hello! ❤❤❤"
);
assert_eq!(
engine.eval::<INT>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764')"#
)?,
0
);
assert_eq!(
engine.eval::<INT>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764', 5)"#
)?,
11
);
assert_eq!(
engine.eval::<INT>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764', -1)"#
)?,
0
);
assert_eq!(
engine.eval::<INT>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764', 999)"#
)?,
-1
);
assert_eq!(
engine.eval::<INT>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('x')"#
)?,
-1
);
Ok(())
}

View File

@ -1,4 +1,5 @@
#![cfg(not(feature = "no_stdlib"))] #![cfg(not(feature = "no_stdlib"))]
#![cfg(not(feature = "no_std"))]
use rhai::{Engine, EvalAltResult, INT}; use rhai::{Engine, EvalAltResult, INT};