Merge pull request #138 from schungx/master
Turn Dynamic into enum, plus benchmarks
This commit is contained in:
commit
a1ec35f11d
29
.github/workflows/benchmark.yml
vendored
Normal file
29
.github/workflows/benchmark.yml
vendored
Normal 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'
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rhai"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
|
||||
description = "Embedded scripting for Rust"
|
||||
|
170
README.md
170
README.md
@ -14,20 +14,21 @@ to add scripting to any application.
|
||||
Rhai's current features set:
|
||||
|
||||
* `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
|
||||
* 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)
|
||||
* Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app)
|
||||
* 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
|
||||
* Support for minimal builds by excluding unneeded language features
|
||||
* Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/)
|
||||
to do checked arithmetic operations); for [`no_std`] builds, a number of additional dependencies are
|
||||
pulled in to provide for functionalities that used to be in `std`.
|
||||
|
||||
**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
|
||||
------------
|
||||
@ -36,7 +37,7 @@ Install the Rhai crate by adding this line to `dependencies`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rhai = "0.12.0"
|
||||
rhai = "0.13.0"
|
||||
```
|
||||
|
||||
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
|
||||
[`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
|
||||
-------
|
||||
|
||||
@ -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) |
|
||||
| [`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:
|
||||
|
||||
@ -180,13 +216,18 @@ fn main() -> Result<(), EvalAltResult>
|
||||
### 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.
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
@ -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
|
||||
|
||||
engine.register_stdlib(); // register the standard library manually
|
||||
|
||||
engine.
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@ -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).
|
||||
|
||||
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.
|
||||
Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails.
|
||||
The `cast` method then converts the value into a specific, known type.
|
||||
Alternatively, use the `try_cast` method which does not panic but returns `None` when the cast fails.
|
||||
|
||||
```rust
|
||||
use rhai::AnyExt; // pull in the trait.
|
||||
|
||||
let list: Array = engine.eval("...")?; // return type is 'Array'
|
||||
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: 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.
|
||||
|
||||
```rust
|
||||
use rhai::Any; // pull in the trait.
|
||||
|
||||
let list: Array = engine.eval("...")?; // return type is 'Array'
|
||||
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 |
|
||||
| ------------------- | --------------------------------------------------------------------------------- | --------------------------------------- |
|
||||
| `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` |
|
||||
| `RegisterDynamicFn` | Trait for registering functions returning [`Dynamic`] | `register_dynamic_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`].
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Dynamic, Engine, EvalAltResult};
|
||||
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
|
||||
fn add(x: i64, y: i64) -> i64 {
|
||||
@ -524,7 +557,7 @@ fn add(x: i64, y: i64) -> i64 {
|
||||
|
||||
// Function that returns a Dynamic value
|
||||
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>
|
||||
@ -548,17 +581,16 @@ fn main() -> Result<(), EvalAltResult>
|
||||
}
|
||||
```
|
||||
|
||||
To return a [`Dynamic`] value from a Rust function, use the `into_dynamic()` method
|
||||
(under the `rhai::Any` trait) to convert it.
|
||||
To return a [`Dynamic`] value from a Rust function, use the `Dynamic::from` method.
|
||||
|
||||
```rust
|
||||
use rhai::Any; // pull in the trait
|
||||
use rhai::Dynamic;
|
||||
|
||||
fn decide(yes_no: bool) -> Dynamic {
|
||||
if yes_no {
|
||||
(42_i64).into_dynamic()
|
||||
Dynamic::from(42_i64)
|
||||
} 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.
|
||||
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
|
||||
use std::fmt::Display;
|
||||
@ -641,6 +673,50 @@ fn to_int(num) {
|
||||
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
|
||||
-----------------------
|
||||
|
||||
@ -676,7 +752,7 @@ fn main() -> Result<(), EvalAltResult>
|
||||
|
||||
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
|
||||
|
||||
println!("result: {}", result.field); // prints 42
|
||||
println!("result: {}", result.field); // prints 42
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1151,16 +1227,19 @@ 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:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ---------- | ------------------------------------- | -------------------------------------------------------------------- |
|
||||
| `len` | _none_ | returns the number of characters (not number of bytes) in the string |
|
||||
| `pad` | character to pad, target length | pads the string with an character to a specified length |
|
||||
| `append` | character/string to append | Adds a character or a string to the end of another string |
|
||||
| `clear` | _none_ | empties the string |
|
||||
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
|
||||
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
|
||||
| `replace` | target sub-string, replacement string | replaces a substring with another |
|
||||
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
|
||||
| `len` | _none_ | returns the number of characters (not number of bytes) in the string |
|
||||
| `pad` | character to pad, target length | pads the string with an character to at least a specified length |
|
||||
| `append` | character/string to append | Adds a character or a string to the end of another string |
|
||||
| `clear` | _none_ | empties the string |
|
||||
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
|
||||
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
|
||||
| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
|
||||
| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
|
||||
| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
|
||||
| `replace` | target sub-string, replacement string | replaces a sub-string with another |
|
||||
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
|
||||
|
||||
### Examples
|
||||
|
||||
@ -1176,17 +1255,30 @@ full_name.pad(15, '$');
|
||||
full_name.len() == 15;
|
||||
full_name == "Bob C. Davis$$$";
|
||||
|
||||
let n = full_name.index_of('$');
|
||||
n == 12;
|
||||
|
||||
full_name.index_of("$$", n + 1) == 13;
|
||||
|
||||
full_name.sub_string(n, 3) == "$$$";
|
||||
|
||||
full_name.truncate(6);
|
||||
full_name.len() == 6;
|
||||
full_name == "Bob C.";
|
||||
|
||||
full_name.replace("Bob", "John");
|
||||
full_name.len() == 7;
|
||||
full_name = "John C.";
|
||||
full_name == "John C.";
|
||||
|
||||
full_name.contains('C') == true;
|
||||
full_name.contains("John") == true;
|
||||
|
||||
full_name.crop(5);
|
||||
full_name == "C.";
|
||||
|
||||
full_name.crop(0, 1);
|
||||
full_name == "C";
|
||||
|
||||
full_name.clear();
|
||||
full_name.len() == 0;
|
||||
```
|
||||
@ -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) |
|
||||
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
|
||||
| `len` | _none_ | returns the number of elements |
|
||||
| `pad` | element to pad, target length | pads the array with an element 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 |
|
||||
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
|
||||
|
||||
|
1
_config.yml
Normal file
1
_config.yml
Normal file
@ -0,0 +1 @@
|
||||
theme: jekyll-theme-slate
|
29
benches/engine.rs
Normal file
29
benches/engine.rs
Normal 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
63
benches/eval_array.rs
Normal 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());
|
||||
}
|
43
benches/eval_expression.rs
Normal file
43
benches/eval_expression.rs
Normal 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
71
benches/eval_map.rs
Normal 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
77
benches/eval_scope.rs
Normal 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
100
benches/eval_type.rs
Normal 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
49
benches/iterations.rs
Normal 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
103
benches/parsing.rs
Normal 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
44
benches/primes.rs
Normal 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());
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use rhai::{Engine, EvalAltResult, Position, Scope, AST};
|
||||
use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST};
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
use rhai::OptimizationLevel;
|
||||
@ -14,7 +14,6 @@ fn print_error(input: &str, err: EvalAltResult) {
|
||||
let line_no = if lines.len() > 1 {
|
||||
match err.position() {
|
||||
p if p.is_none() => "".to_string(),
|
||||
p if p.is_eof() => format!("{}: ", lines.len()),
|
||||
p => format!("{}: ", p.line().unwrap()),
|
||||
}
|
||||
} else {
|
||||
@ -25,15 +24,7 @@ fn print_error(input: &str, err: EvalAltResult) {
|
||||
let pos = err.position();
|
||||
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 {
|
||||
p if p.is_eof() => panic!("should not be EOF"),
|
||||
p if p.is_none() => {
|
||||
// No position
|
||||
println!("{}", err);
|
||||
@ -137,9 +128,9 @@ fn main() {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if let Err(err) = engine
|
||||
match engine
|
||||
.compile_with_scope(&scope, &script)
|
||||
.map_err(EvalAltResult::ErrorParsing)
|
||||
.map_err(|err| err.into())
|
||||
.and_then(|r| {
|
||||
ast_u = r.clone();
|
||||
|
||||
@ -157,22 +148,21 @@ fn main() {
|
||||
main_ast = main_ast.merge(&ast);
|
||||
|
||||
// Evaluate
|
||||
let result = engine
|
||||
.consume_ast_with_scope(&mut scope, &main_ast)
|
||||
.or_else(|err| match err {
|
||||
EvalAltResult::Return(_, _) => Ok(()),
|
||||
err => Err(err),
|
||||
});
|
||||
|
||||
// Throw away all the statements, leaving only the functions
|
||||
main_ast.retain_functions();
|
||||
|
||||
result
|
||||
})
|
||||
{
|
||||
println!();
|
||||
print_error(&input, err);
|
||||
println!();
|
||||
engine.eval_ast_with_scope::<Dynamic>(&mut scope, &main_ast)
|
||||
}) {
|
||||
Ok(result) if !result.is::<()>() => {
|
||||
println!("=> {:?}", result);
|
||||
println!();
|
||||
}
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
println!();
|
||||
print_error(&input, err);
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
// Throw away all the statements, leaving only the functions
|
||||
main_ast.retain_functions();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use rhai::{Engine, EvalAltResult, Position};
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
use rhai::OptimizationLevel;
|
||||
@ -23,15 +23,9 @@ fn eprint_error(input: &str, err: EvalAltResult) {
|
||||
let lines: Vec<_> = input.split('\n').collect();
|
||||
|
||||
// Print error
|
||||
let pos = if err.position().is_eof() {
|
||||
let last = lines[lines.len() - 1];
|
||||
Position::new(lines.len(), last.len() + 1)
|
||||
} else {
|
||||
err.position()
|
||||
};
|
||||
let pos = err.position();
|
||||
|
||||
match pos {
|
||||
p if p.is_eof() => panic!("should not be EOF"),
|
||||
p if p.is_none() => {
|
||||
// No position
|
||||
eprintln!("{}", err);
|
||||
|
22
scripts/fibonacci.rhai
Normal file
22
scripts/fibonacci.rhai
Normal 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
73
scripts/mat_mul.rhai
Normal 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.");
|
531
src/any.rs
531
src/any.rs
@ -1,70 +1,41 @@
|
||||
//! 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::{
|
||||
any::{type_name, TypeId},
|
||||
any::{type_name, Any, TypeId},
|
||||
boxed::Box,
|
||||
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.
|
||||
/// 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"))]
|
||||
pub trait Any: crate::stdlib::any::Any {
|
||||
/// Get the `TypeId` of this type.
|
||||
fn type_id(&self) -> TypeId;
|
||||
pub trait Variant: Any {
|
||||
/// 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;
|
||||
fn into_dynamic(self) -> Dynamic;
|
||||
|
||||
/// Clone into `Dynamic`.
|
||||
fn clone_into_dynamic(&self) -> Dynamic;
|
||||
|
||||
/// This trait may only be implemented by `rhai`.
|
||||
#[doc(hidden)]
|
||||
@ -72,104 +43,349 @@ pub trait Any: crate::stdlib::any::Any {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
impl<T: crate::stdlib::any::Any + Clone + ?Sized> Any for T {
|
||||
fn type_id(&self) -> TypeId {
|
||||
TypeId::of::<T>()
|
||||
impl<T: Any + Clone> 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 {
|
||||
Box::new(self.clone())
|
||||
fn into_dynamic(self) -> Dynamic {
|
||||
Dynamic::from(self)
|
||||
}
|
||||
fn clone_into_dynamic(&self) -> Dynamic {
|
||||
Dynamic::from(self.clone())
|
||||
}
|
||||
|
||||
fn _closed(&self) -> _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?
|
||||
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`.
|
||||
/// Returns `None` if the cast fails.
|
||||
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
|
||||
if self.is::<T>() {
|
||||
unsafe { Some(&*(self as *const Variant as *const T)) }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Any::downcast_ref::<T>(self.as_any())
|
||||
}
|
||||
|
||||
/// Get a mutable reference of a specific type to the `Variant`.
|
||||
/// Returns `None` if the cast fails.
|
||||
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
|
||||
if self.is::<T>() {
|
||||
unsafe { Some(&mut *(self as *mut Variant as *mut T)) }
|
||||
} else {
|
||||
None
|
||||
Any::downcast_mut::<T>(self.as_mut_any())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
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 {
|
||||
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.
|
||||
pub trait AnyExt: Sized {
|
||||
/// Get a copy of a `Dynamic` value as a specific type.
|
||||
fn try_cast<T: Any + Clone>(self) -> Result<T, Self>;
|
||||
|
||||
/// 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;
|
||||
/// Cast a Boxed type into another type.
|
||||
fn cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<T, Box<X>> {
|
||||
// Only allow casting to the exact same type
|
||||
if TypeId::of::<X>() == TypeId::of::<T>() {
|
||||
// SAFETY: just checked whether we are pointing to the correct type
|
||||
unsafe {
|
||||
let raw: *mut dyn Any = Box::into_raw(item as Box<dyn Any>);
|
||||
Ok(*Box::from_raw(raw as *mut T))
|
||||
}
|
||||
} else {
|
||||
// Return the consumed item for chaining.
|
||||
Err(item)
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyExt for Dynamic {
|
||||
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, Any, AnyExt};
|
||||
/// use rhai::Dynamic;
|
||||
///
|
||||
/// let x: Dynamic = 42_u32.into_dynamic();
|
||||
/// let x = Dynamic::from(42_u32);
|
||||
///
|
||||
/// assert_eq!(x.try_cast::<u32>().unwrap(), 42);
|
||||
/// ```
|
||||
fn try_cast<T: Any + Clone>(self) -> Result<T, Self> {
|
||||
if self.is::<T>() {
|
||||
unsafe {
|
||||
let raw: *mut Variant = Box::into_raw(self);
|
||||
Ok(*Box::from_raw(raw as *mut T))
|
||||
}
|
||||
} else {
|
||||
Err(self)
|
||||
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.
|
||||
/// Casting to a `Dynamic` just returns as is.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
@ -178,18 +394,123 @@ impl AnyExt for Dynamic {
|
||||
/// # 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);
|
||||
/// ```
|
||||
fn cast<T: Any + Clone>(self) -> T {
|
||||
self.try_cast::<T>().expect("cast failed")
|
||||
pub fn cast<T: Variant + Clone>(self) -> T {
|
||||
self.try_cast::<T>().unwrap()
|
||||
}
|
||||
|
||||
fn _closed(&self) -> _Private {
|
||||
_Private
|
||||
/// Get a reference of a specific type to the `Dynamic`.
|
||||
/// 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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
203
src/api.rs
203
src/api.rs
@ -1,14 +1,15 @@
|
||||
//! Module that defines the extern API of `Engine`.
|
||||
|
||||
use crate::any::{Any, AnyExt, Dynamic};
|
||||
use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec, Map};
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::engine::{calc_fn_spec, make_getter, make_setter, Engine, FnAny, Map};
|
||||
use crate::error::ParseError;
|
||||
use crate::fn_call::FuncArgs;
|
||||
use crate::fn_register::RegisterFn;
|
||||
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::scope::Scope;
|
||||
use crate::token::{lex, Position};
|
||||
|
||||
use crate::stdlib::{
|
||||
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 {}
|
||||
|
||||
/// Engine public API
|
||||
impl<'e> Engine<'e> {
|
||||
impl Engine {
|
||||
/// Register a custom function.
|
||||
pub(crate) fn register_fn_raw(&mut self, fn_name: &str, args: Vec<TypeId>, f: Box<FnAny>) {
|
||||
let spec = FnSpec {
|
||||
name: fn_name.to_string().into(),
|
||||
args,
|
||||
};
|
||||
|
||||
if self.functions.is_none() {
|
||||
self.functions = Some(HashMap::new());
|
||||
}
|
||||
self.functions.as_mut().unwrap().insert(spec, f);
|
||||
self.functions
|
||||
.insert(calc_fn_spec(fn_name, args.into_iter()), f);
|
||||
}
|
||||
|
||||
/// Register a custom type for use with the `Engine`.
|
||||
@ -109,7 +103,7 @@ impl<'e> Engine<'e> {
|
||||
/// # }
|
||||
/// ```
|
||||
#[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>());
|
||||
}
|
||||
|
||||
@ -157,7 +151,7 @@ impl<'e> Engine<'e> {
|
||||
/// # }
|
||||
/// ```
|
||||
#[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() {
|
||||
self.type_names = Some(HashMap::new());
|
||||
}
|
||||
@ -171,15 +165,8 @@ impl<'e> Engine<'e> {
|
||||
|
||||
/// Register an iterator adapter for a type with the `Engine`.
|
||||
/// This is an advanced feature.
|
||||
pub fn register_iterator<T: Any, F: IteratorCallback>(&mut self, f: F) {
|
||||
if self.type_iterators.is_none() {
|
||||
self.type_iterators = Some(HashMap::new());
|
||||
}
|
||||
|
||||
self.type_iterators
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.insert(TypeId::of::<T>(), Box::new(f));
|
||||
pub fn register_iterator<T: Variant + Clone, F: IteratorCallback>(&mut self, f: F) {
|
||||
self.type_iterators.insert(TypeId::of::<T>(), Box::new(f));
|
||||
}
|
||||
|
||||
/// 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"))]
|
||||
pub fn register_get<T, U, F>(&mut self, name: &str, callback: F)
|
||||
where
|
||||
T: Any + Clone,
|
||||
U: Any + Clone,
|
||||
T: Variant + Clone,
|
||||
U: Variant + Clone,
|
||||
F: ObjectGetCallback<T, U>,
|
||||
{
|
||||
self.register_fn(&make_getter(name), callback);
|
||||
@ -267,8 +254,8 @@ impl<'e> Engine<'e> {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
pub fn register_set<T, U, F>(&mut self, name: &str, callback: F)
|
||||
where
|
||||
T: Any + Clone,
|
||||
U: Any + Clone,
|
||||
T: Variant + Clone,
|
||||
U: Variant + Clone,
|
||||
F: ObjectSetCallback<T, U>,
|
||||
{
|
||||
self.register_fn(&make_setter(name), callback);
|
||||
@ -315,8 +302,8 @@ impl<'e> Engine<'e> {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
pub fn register_get_set<T, U, G, S>(&mut self, name: &str, get_fn: G, set_fn: S)
|
||||
where
|
||||
T: Any + Clone,
|
||||
U: Any + Clone,
|
||||
T: Variant + Clone,
|
||||
U: Variant + Clone,
|
||||
G: ObjectGetCallback<T, U>,
|
||||
S: ObjectSetCallback<T, U>,
|
||||
{
|
||||
@ -397,7 +384,7 @@ impl<'e> Engine<'e> {
|
||||
) -> Result<AST, ParseError> {
|
||||
let scripts = [script];
|
||||
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.
|
||||
@ -487,7 +474,7 @@ impl<'e> Engine<'e> {
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// use rhai::{Engine, AnyExt};
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let engine = Engine::new();
|
||||
///
|
||||
@ -593,7 +580,9 @@ impl<'e> Engine<'e> {
|
||||
) -> Result<AST, ParseError> {
|
||||
let scripts = [script];
|
||||
let stream = lex(&scripts);
|
||||
|
||||
parse_global_expr(&mut stream.peekable(), self, scope, self.optimization_level)
|
||||
.map_err(|err| *err)
|
||||
}
|
||||
|
||||
/// Evaluate a script file.
|
||||
@ -612,7 +601,7 @@ impl<'e> Engine<'e> {
|
||||
/// # }
|
||||
/// ```
|
||||
#[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))
|
||||
}
|
||||
|
||||
@ -636,7 +625,7 @@ impl<'e> Engine<'e> {
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
pub fn eval_file_with_scope<T: Any + Clone>(
|
||||
pub fn eval_file_with_scope<T: Variant + Clone>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
path: PathBuf,
|
||||
@ -658,7 +647,7 @@ impl<'e> Engine<'e> {
|
||||
/// # 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)
|
||||
}
|
||||
|
||||
@ -684,7 +673,7 @@ impl<'e> Engine<'e> {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn eval_with_scope<T: Any + Clone>(
|
||||
pub fn eval_with_scope<T: Variant + Clone>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
script: &str,
|
||||
@ -709,7 +698,7 @@ impl<'e> Engine<'e> {
|
||||
/// # 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)
|
||||
}
|
||||
|
||||
@ -731,7 +720,7 @@ impl<'e> Engine<'e> {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn eval_expression_with_scope<T: Any + Clone>(
|
||||
pub fn eval_expression_with_scope<T: Variant + Clone>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
script: &str,
|
||||
@ -761,7 +750,7 @@ impl<'e> Engine<'e> {
|
||||
/// # 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)
|
||||
}
|
||||
|
||||
@ -794,32 +783,33 @@ impl<'e> Engine<'e> {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn eval_ast_with_scope<T: Any + Clone>(
|
||||
pub fn eval_ast_with_scope<T: Variant + Clone>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
) -> Result<T, EvalAltResult> {
|
||||
self.eval_ast_with_scope_raw(scope, ast)?
|
||||
.try_cast::<T>()
|
||||
.map_err(|a| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name((*a).type_name()).to_string(),
|
||||
Position::none(),
|
||||
)
|
||||
})
|
||||
let result = self
|
||||
.eval_ast_with_scope_raw(scope, ast)
|
||||
.map_err(|err| *err)?;
|
||||
|
||||
let return_type = self.map_type_name(result.type_name());
|
||||
|
||||
return result.try_cast::<T>().ok_or_else(|| {
|
||||
EvalAltResult::ErrorMismatchOutputType(return_type.to_string(), Position::none())
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn eval_ast_with_scope_raw(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
ast.0
|
||||
.iter()
|
||||
.try_fold(().into_dynamic(), |_, stmt| {
|
||||
.try_fold(Dynamic::from_unit(), |_, stmt| {
|
||||
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),
|
||||
_ => Err(err),
|
||||
})
|
||||
@ -875,14 +865,16 @@ impl<'e> Engine<'e> {
|
||||
) -> Result<(), EvalAltResult> {
|
||||
ast.0
|
||||
.iter()
|
||||
.try_fold(().into_dynamic(), |_, stmt| {
|
||||
.try_fold(Dynamic::from_unit(), |_, stmt| {
|
||||
self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0)
|
||||
})
|
||||
.map(|_| ())
|
||||
.or_else(|err| match err {
|
||||
EvalAltResult::Return(_, _) => Ok(()),
|
||||
_ => Err(err),
|
||||
})
|
||||
.map_or_else(
|
||||
|err| match *err {
|
||||
EvalAltResult::Return(_, _) => Ok(()),
|
||||
err => Err(err),
|
||||
},
|
||||
|_| Ok(()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Call a script function defined in an `AST` with multiple arguments.
|
||||
@ -921,7 +913,7 @@ impl<'e> Engine<'e> {
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn call_fn<A: FuncArgs, T: Any + Clone>(
|
||||
pub fn call_fn<A: FuncArgs, T: Variant + Clone>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
@ -929,18 +921,19 @@ impl<'e> Engine<'e> {
|
||||
args: A,
|
||||
) -> Result<T, EvalAltResult> {
|
||||
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 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()
|
||||
.map_err(|a| {
|
||||
EvalAltResult::ErrorMismatchOutputType(
|
||||
self.map_type_name((*a).type_name()).into(),
|
||||
pos,
|
||||
)
|
||||
})
|
||||
.ok_or_else(|| EvalAltResult::ErrorMismatchOutputType(return_type.into(), pos));
|
||||
}
|
||||
|
||||
/// Optimize the `AST` with constants defined in an external Scope.
|
||||
@ -961,7 +954,11 @@ impl<'e> Engine<'e> {
|
||||
ast: AST,
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> 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)
|
||||
}
|
||||
|
||||
@ -972,23 +969,25 @@ impl<'e> Engine<'e> {
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// # use std::sync::RwLock;
|
||||
/// # use std::sync::Arc;
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let result = RwLock::new(String::from(""));
|
||||
/// {
|
||||
/// let mut engine = Engine::new();
|
||||
/// let result = Arc::new(RwLock::new(String::from("")));
|
||||
///
|
||||
/// // Override action of 'print' function
|
||||
/// engine.on_print(|s| result.write().unwrap().push_str(s));
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Override action of 'print' function
|
||||
/// 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");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[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));
|
||||
}
|
||||
/// Override default action of `print` (print to stdout using `println!`)
|
||||
@ -998,23 +997,25 @@ impl<'e> Engine<'e> {
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// # use std::sync::RwLock;
|
||||
/// # use std::sync::Arc;
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let result = RwLock::new(String::from(""));
|
||||
/// {
|
||||
/// let mut engine = Engine::new();
|
||||
/// let result = Arc::new(RwLock::new(String::from("")));
|
||||
///
|
||||
/// // Override action of 'print' function
|
||||
/// engine.on_print(|s| result.write().unwrap().push_str(s));
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Override action of 'print' function
|
||||
/// 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");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[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));
|
||||
}
|
||||
|
||||
@ -1025,23 +1026,25 @@ impl<'e> Engine<'e> {
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// # use std::sync::RwLock;
|
||||
/// # use std::sync::Arc;
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let result = RwLock::new(String::from(""));
|
||||
/// {
|
||||
/// let mut engine = Engine::new();
|
||||
/// let result = Arc::new(RwLock::new(String::from("")));
|
||||
///
|
||||
/// // Override action of 'print' function
|
||||
/// engine.on_debug(|s| result.write().unwrap().push_str(s));
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Override action of 'print' function
|
||||
/// 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""#);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[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));
|
||||
}
|
||||
/// Override default action of `debug` (print to stdout using `println!`)
|
||||
@ -1051,23 +1054,25 @@ impl<'e> Engine<'e> {
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||
/// # use std::sync::RwLock;
|
||||
/// # use std::sync::Arc;
|
||||
/// use rhai::Engine;
|
||||
///
|
||||
/// let result = RwLock::new(String::from(""));
|
||||
/// {
|
||||
/// let mut engine = Engine::new();
|
||||
/// let result = Arc::new(RwLock::new(String::from("")));
|
||||
///
|
||||
/// // Override action of 'print' function
|
||||
/// engine.on_debug(|s| result.write().unwrap().push_str(s));
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Override action of 'print' function
|
||||
/// 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""#);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[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));
|
||||
}
|
||||
}
|
||||
|
338
src/builtin.rs
338
src/builtin.rs
@ -1,11 +1,12 @@
|
||||
//! Helper module that allows registration of the _core library_ and
|
||||
//! _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::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
||||
use crate::parser::{Position, INT};
|
||||
use crate::parser::INT;
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::token::Position;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::engine::Array;
|
||||
@ -27,11 +28,18 @@ use crate::stdlib::{
|
||||
format,
|
||||
ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub},
|
||||
string::{String, ToString},
|
||||
time::Instant,
|
||||
vec::Vec,
|
||||
{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 {
|
||||
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
|
||||
$(
|
||||
@ -86,7 +94,7 @@ fn ne<T: PartialEq>(x: T, y: T) -> bool {
|
||||
x != y
|
||||
}
|
||||
|
||||
impl Engine<'_> {
|
||||
impl Engine {
|
||||
/// Register the core built-in library.
|
||||
pub(crate) fn register_core_lib(&mut self) {
|
||||
// Checked add
|
||||
@ -371,10 +379,10 @@ impl Engine<'_> {
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
{
|
||||
reg_op_result!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_op_result!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_op_result!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_op_result!(self, "/", div, 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, i128, u128);
|
||||
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, i128, u128);
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,10 +396,10 @@ impl Engine<'_> {
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
{
|
||||
reg_op!(self, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_op!(self, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_op!(self, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_op!(self, "/", div_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, i128, u128);
|
||||
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, i128, u128);
|
||||
}
|
||||
}
|
||||
|
||||
@ -414,12 +422,12 @@ impl Engine<'_> {
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
{
|
||||
reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_cmp!(self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_cmp!(self, "!=", ne, 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, i128, u128);
|
||||
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, i128, u128);
|
||||
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, i128, u128);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
@ -448,9 +456,9 @@ impl Engine<'_> {
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
{
|
||||
reg_op!(self, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_op!(self, "^", binary_xor, 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, i128, u128);
|
||||
reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
@ -462,9 +470,13 @@ impl Engine<'_> {
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
{
|
||||
reg_op_result1!(self, "<<", shl, i64, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_op_result1!(self, ">>", shr, i64, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_op_result!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_op_result1!(
|
||||
self, "<<", shl, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128
|
||||
);
|
||||
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_i64"))]
|
||||
{
|
||||
reg_op!(self, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_op!(self, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_op!(self, "%", modulo_u, 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, i128, u128);
|
||||
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, 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, 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, i32, i64, u32, u64);
|
||||
reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i128, u128);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
@ -628,8 +643,8 @@ impl Engine<'_> {
|
||||
// Register map access functions
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
self.register_fn("keys", |map: Map| {
|
||||
map.into_iter()
|
||||
.map(|(k, _)| k.into_dynamic())
|
||||
map.iter()
|
||||
.map(|(k, _)| Dynamic::from(k.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
@ -641,15 +656,16 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
// Register range function
|
||||
fn reg_range<T: Any + Clone>(engine: &mut Engine)
|
||||
fn reg_range<T: Variant + Clone>(engine: &mut Engine)
|
||||
where
|
||||
Range<T>: Iterator<Item = T>,
|
||||
{
|
||||
engine.register_iterator::<Range<T>, _>(|a: &Dynamic| {
|
||||
engine.register_iterator::<Range<T>, _>(|source: &Dynamic| {
|
||||
Box::new(
|
||||
a.downcast_ref::<Range<T>>()
|
||||
source
|
||||
.downcast_ref::<Range<T>>()
|
||||
.cloned()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.map(|x| x.into_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
|
||||
@ -678,12 +694,12 @@ impl Engine<'_> {
|
||||
struct StepRange<T>(T, T, T)
|
||||
where
|
||||
for<'a> &'a T: Add<&'a T, Output = T>,
|
||||
T: Any + Clone + PartialOrd;
|
||||
T: Variant + Clone + PartialOrd;
|
||||
|
||||
impl<T> Iterator for StepRange<T>
|
||||
where
|
||||
for<'a> &'a T: Add<&'a T, Output = T>,
|
||||
T: Any + Clone + PartialOrd,
|
||||
T: Variant + Clone + PartialOrd,
|
||||
{
|
||||
type Item = T;
|
||||
|
||||
@ -701,14 +717,15 @@ impl Engine<'_> {
|
||||
fn reg_step<T>(engine: &mut Engine)
|
||||
where
|
||||
for<'a> &'a T: Add<&'a T, Output = T>,
|
||||
T: Any + Clone + PartialOrd,
|
||||
T: Variant + Clone + PartialOrd,
|
||||
StepRange<T>: Iterator<Item = T>,
|
||||
{
|
||||
engine.register_iterator::<StepRange<T>, _>(|a: &Dynamic| {
|
||||
engine.register_iterator::<StepRange<T>, _>(|source: &Dynamic| {
|
||||
Box::new(
|
||||
a.downcast_ref::<StepRange<T>>()
|
||||
source
|
||||
.downcast_ref::<StepRange<T>>()
|
||||
.cloned()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.map(|x| x.into_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.
|
||||
impl Engine<'_> {
|
||||
impl Engine {
|
||||
pub fn register_stdlib(&mut self) {
|
||||
#[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: i64| 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"))]
|
||||
{
|
||||
self.register_result_fn("to_int", |x: f32| {
|
||||
if x > (i64::MAX as f32) {
|
||||
if x > (MAX_INT as f32) {
|
||||
return Err(EvalAltResult::ErrorArithmetic(
|
||||
format!("Integer overflow: to_int({})", x),
|
||||
Position::none(),
|
||||
@ -837,7 +856,7 @@ impl Engine<'_> {
|
||||
Ok(x.trunc() as INT)
|
||||
});
|
||||
self.register_result_fn("to_int", |x: FLOAT| {
|
||||
if x > (i64::MAX as FLOAT) {
|
||||
if x > (MAX_INT as FLOAT) {
|
||||
return Err(EvalAltResult::ErrorArithmetic(
|
||||
format!("Integer overflow: to_int({})", x),
|
||||
Position::none(),
|
||||
@ -866,19 +885,19 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
// Register array utility functions
|
||||
fn push<T: Any>(list: &mut Array, item: T) {
|
||||
list.push(Box::new(item));
|
||||
fn push<T: Variant + Clone>(list: &mut Array, item: T) {
|
||||
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 {
|
||||
list.insert(0, Box::new(item));
|
||||
list.insert(0, Dynamic::from(item));
|
||||
} else if (position as usize) >= list.len() - 1 {
|
||||
push(list, item);
|
||||
} 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 {
|
||||
while list.len() < len as usize {
|
||||
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, (), 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, (), 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, (), i32, i64, u32, u64);
|
||||
reg_fn3!(self, "insert", ins, &mut Array, INT, (), i128, u128);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
@ -919,18 +941,18 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
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| {
|
||||
if !list.is_empty() {
|
||||
().into_dynamic()
|
||||
Dynamic::from_unit()
|
||||
} else {
|
||||
list.remove(0)
|
||||
}
|
||||
});
|
||||
self.register_dynamic_fn("remove", |list: &mut Array, len: INT| {
|
||||
if len < 0 || (len as usize) >= list.len() {
|
||||
().into_dynamic()
|
||||
Dynamic::from_unit()
|
||||
} else {
|
||||
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("clear", |map: &mut Map| map.clear());
|
||||
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| {
|
||||
map2.into_iter().for_each(|(key, value)| {
|
||||
@ -983,8 +1005,13 @@ impl Engine<'_> {
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
{
|
||||
reg_fn2x!(self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_fn2y!(self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64);
|
||||
reg_fn2x!(
|
||||
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"))]
|
||||
@ -1000,17 +1027,113 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
// 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("contains", |s: &mut String, ch: char| s.contains(ch));
|
||||
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("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("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| {
|
||||
if len >= 0 {
|
||||
let chars: Vec<_> = s.chars().take(len as usize).collect();
|
||||
s.clear();
|
||||
chars.iter().for_each(|&ch| s.push(ch));
|
||||
chars.into_iter().for_each(|ch| s.push(ch));
|
||||
} else {
|
||||
s.clear();
|
||||
}
|
||||
@ -1033,38 +1156,83 @@ impl Engine<'_> {
|
||||
}
|
||||
});
|
||||
|
||||
// Register date/time functions
|
||||
self.register_fn("timestamp", || Instant::now());
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
{
|
||||
// Register date/time functions
|
||||
self.register_fn("timestamp", || Instant::now());
|
||||
|
||||
self.register_fn("-", |ts1: Instant, ts2: Instant| {
|
||||
if ts2 > ts1 {
|
||||
self.register_result_fn("-", |ts1: Instant, ts2: Instant| {
|
||||
if ts2 > ts1 {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
return Ok(-(ts2 - ts1).as_secs_f64());
|
||||
|
||||
#[cfg(feature = "no_float")]
|
||||
{
|
||||
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 {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
return Ok((ts1 - ts2).as_secs_f64());
|
||||
|
||||
#[cfg(feature = "no_float")]
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
reg_cmp!(self, "<", lt, Instant);
|
||||
reg_cmp!(self, "<=", lte, Instant);
|
||||
reg_cmp!(self, ">", gt, Instant);
|
||||
reg_cmp!(self, ">=", gte, Instant);
|
||||
reg_cmp!(self, "==", eq, Instant);
|
||||
reg_cmp!(self, "!=", ne, Instant);
|
||||
|
||||
self.register_result_fn("elapsed", |timestamp: Instant| {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
return -(ts2 - ts1).as_secs_f64();
|
||||
return Ok(timestamp.elapsed().as_secs_f64());
|
||||
|
||||
#[cfg(feature = "no_float")]
|
||||
return -((ts2 - ts1).as_secs() as INT);
|
||||
} else {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
return (ts1 - ts2).as_secs_f64();
|
||||
{
|
||||
let seconds = timestamp.elapsed().as_secs();
|
||||
|
||||
#[cfg(feature = "no_float")]
|
||||
return (ts1 - ts2).as_secs() as INT;
|
||||
}
|
||||
});
|
||||
|
||||
reg_cmp!(self, "<", lt, Instant);
|
||||
reg_cmp!(self, "<=", lte, Instant);
|
||||
reg_cmp!(self, ">", gt, Instant);
|
||||
reg_cmp!(self, ">=", gte, Instant);
|
||||
reg_cmp!(self, "==", eq, Instant);
|
||||
reg_cmp!(self, "!=", ne, Instant);
|
||||
|
||||
self.register_fn("elapsed", |timestamp: Instant| {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
return timestamp.elapsed().as_secs_f64();
|
||||
|
||||
#[cfg(feature = "no_float")]
|
||||
return timestamp.elapsed().as_secs() as INT;
|
||||
});
|
||||
#[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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1218
src/engine.rs
1218
src/engine.rs
File diff suppressed because it is too large
Load Diff
17
src/error.rs
17
src/error.rs
@ -1,6 +1,6 @@
|
||||
//! 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};
|
||||
|
||||
@ -107,13 +107,8 @@ pub enum ParseErrorType {
|
||||
|
||||
impl ParseErrorType {
|
||||
/// Make a `ParseError` using the current type and position.
|
||||
pub(crate) fn into_err(self, pos: Position) -> ParseError {
|
||||
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())
|
||||
pub(crate) fn into_err(self, pos: Position) -> Box<ParseError> {
|
||||
Box::new(ParseError(self, pos))
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,13 +204,11 @@ impl fmt::Display for ParseError {
|
||||
_ => write!(f, "{}", self.desc())?,
|
||||
}
|
||||
|
||||
if !self.1.is_eof() {
|
||||
write!(f, " ({})", self.1)
|
||||
} else if !self.1.is_none() {
|
||||
if !self.1.is_none() {
|
||||
// Do not write any position if None
|
||||
Ok(())
|
||||
} else {
|
||||
write!(f, " at the end of the script but there is no more input")
|
||||
write!(f, " ({})", self.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,8 @@
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::any::{Any, Dynamic};
|
||||
|
||||
use crate::stdlib::{string::String, vec, vec::Vec};
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::stdlib::vec::Vec;
|
||||
|
||||
/// Trait that represent arguments to a function call.
|
||||
/// 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`).
|
||||
macro_rules! impl_args {
|
||||
($($p:ident),*) => {
|
||||
impl<$($p: Any + Clone),*> FuncArgs for ($($p,)*)
|
||||
impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*)
|
||||
{
|
||||
fn into_vec(self) -> Vec<Dynamic> {
|
||||
let ($($p,)*) = self;
|
||||
|
@ -2,7 +2,7 @@
|
||||
#![cfg(not(feature = "no_function"))]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::any::Any;
|
||||
use crate::any::Variant;
|
||||
use crate::engine::Engine;
|
||||
use crate::error::ParseError;
|
||||
use crate::parser::AST;
|
||||
@ -88,13 +88,13 @@ macro_rules! def_anonymous_fn {
|
||||
def_anonymous_fn!(imp);
|
||||
};
|
||||
(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")]
|
||||
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"))]
|
||||
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 {
|
||||
let name = entry_point.to_string();
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::any::{Any, Dynamic};
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::engine::{Engine, FnCallArgs};
|
||||
use crate::parser::Position;
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::token::Position;
|
||||
|
||||
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
|
||||
/// fn return_the_same_as_dynamic(x: i64) -> Dynamic {
|
||||
/// Box::new(x)
|
||||
/// Dynamic::from(x)
|
||||
/// }
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
@ -137,7 +137,7 @@ macro_rules! def_register {
|
||||
// ^ function parameter actual type (T, &T or &mut T)
|
||||
// ^ dereferencing function
|
||||
impl<
|
||||
$($par: Any + Clone,)*
|
||||
$($par: Variant + Clone,)*
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
FN: Fn($($param),*) -> RET + Send + Sync + 'static,
|
||||
@ -145,8 +145,8 @@ macro_rules! def_register {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
FN: Fn($($param),*) -> RET + 'static,
|
||||
|
||||
RET: Any
|
||||
> RegisterFn<FN, ($($mark,)*), RET> for Engine<'_>
|
||||
RET: Variant + Clone
|
||||
> RegisterFn<FN, ($($mark,)*), RET> for Engine
|
||||
{
|
||||
fn register_fn(&mut self, name: &str, f: FN) {
|
||||
let fn_name = name.to_string();
|
||||
@ -156,20 +156,20 @@ macro_rules! def_register {
|
||||
const NUM_ARGS: usize = count_args!($($par)*);
|
||||
|
||||
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)]
|
||||
let mut drain = args.iter_mut();
|
||||
$(
|
||||
// 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
|
||||
// potentially clone the value, otherwise pass the reference.
|
||||
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));
|
||||
@ -177,14 +177,14 @@ macro_rules! def_register {
|
||||
}
|
||||
|
||||
impl<
|
||||
$($par: Any + Clone,)*
|
||||
$($par: Variant + Clone,)*
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
FN: Fn($($param),*) -> Dynamic + Send + Sync + 'static,
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
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) {
|
||||
let fn_name = name.to_string();
|
||||
@ -194,14 +194,14 @@ macro_rules! def_register {
|
||||
const NUM_ARGS: usize = count_args!($($par)*);
|
||||
|
||||
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)]
|
||||
let mut drain = args.iter_mut();
|
||||
$(
|
||||
// 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
|
||||
@ -213,15 +213,15 @@ macro_rules! def_register {
|
||||
}
|
||||
|
||||
impl<
|
||||
$($par: Any + Clone,)*
|
||||
$($par: Variant + Clone,)*
|
||||
|
||||
#[cfg(feature = "sync")]
|
||||
FN: Fn($($param),*) -> Result<RET, EvalAltResult> + Send + Sync + 'static,
|
||||
#[cfg(not(feature = "sync"))]
|
||||
FN: Fn($($param),*) -> Result<RET, EvalAltResult> + 'static,
|
||||
|
||||
RET: Any
|
||||
> RegisterResultFn<FN, ($($mark,)*), RET> for Engine<'_>
|
||||
RET: Variant + Clone
|
||||
> RegisterResultFn<FN, ($($mark,)*), RET> for Engine
|
||||
{
|
||||
fn register_result_fn(&mut self, name: &str, f: FN) {
|
||||
let fn_name = name.to_string();
|
||||
@ -231,20 +231,20 @@ macro_rules! def_register {
|
||||
const NUM_ARGS: usize = count_args!($($par)*);
|
||||
|
||||
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)]
|
||||
let mut drain = args.iter_mut();
|
||||
$(
|
||||
// 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
|
||||
// potentially clone the value, otherwise pass the reference.
|
||||
f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic)
|
||||
.map_err(|err| err.set_position(pos))
|
||||
f($(($clone)($par)),*).map(|r| r.into_dynamic())
|
||||
.map_err(|err| Box::new(err.set_position(pos)))
|
||||
};
|
||||
self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func));
|
||||
}
|
||||
|
@ -82,15 +82,17 @@ mod parser;
|
||||
mod result;
|
||||
mod scope;
|
||||
mod stdlib;
|
||||
mod token;
|
||||
|
||||
pub use any::{Any, AnyExt, Dynamic, Variant};
|
||||
pub use any::Dynamic;
|
||||
pub use engine::Engine;
|
||||
pub use error::{ParseError, ParseErrorType};
|
||||
pub use fn_call::FuncArgs;
|
||||
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
|
||||
pub use parser::{Position, AST, INT};
|
||||
pub use parser::{AST, INT};
|
||||
pub use result::EvalAltResult;
|
||||
pub use scope::Scope;
|
||||
pub use token::Position;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use fn_func::Func;
|
||||
|
@ -1,11 +1,12 @@
|
||||
use crate::any::{Any, Dynamic};
|
||||
use crate::any::Dynamic;
|
||||
use crate::engine::{
|
||||
Engine, FnAny, FnCallArgs, FnSpec, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT,
|
||||
KEYWORD_TYPE_OF,
|
||||
calc_fn_spec, Engine, FnAny, FnCallArgs, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL,
|
||||
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::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
||||
use crate::token::Position;
|
||||
|
||||
use crate::stdlib::{
|
||||
boxed::Box,
|
||||
@ -49,7 +50,7 @@ struct State<'a> {
|
||||
/// Collection of constants to use for eager function evaluations.
|
||||
constants: Vec<(String, Expr)>,
|
||||
/// An `Engine` instance for eager function evaluation.
|
||||
engine: &'a Engine<'a>,
|
||||
engine: &'a Engine,
|
||||
/// Library of script-defined functions.
|
||||
fn_lib: &'a [(&'a str, usize)],
|
||||
/// Optimization level.
|
||||
@ -59,7 +60,7 @@ struct State<'a> {
|
||||
impl<'a> State<'a> {
|
||||
/// Create a new State.
|
||||
pub fn new(
|
||||
engine: &'a Engine<'a>,
|
||||
engine: &'a Engine,
|
||||
fn_lib: &'a [(&'a str, usize)],
|
||||
level: OptimizationLevel,
|
||||
) -> Self {
|
||||
@ -109,19 +110,14 @@ impl<'a> State<'a> {
|
||||
|
||||
/// Call a registered function
|
||||
fn call_fn(
|
||||
functions: Option<&HashMap<FnSpec, Box<FnAny>>>,
|
||||
functions: &HashMap<u64, Box<FnAny>>,
|
||||
fn_name: &str,
|
||||
args: &mut FnCallArgs,
|
||||
pos: Position,
|
||||
) -> Result<Option<Dynamic>, EvalAltResult> {
|
||||
let spec = FnSpec {
|
||||
name: fn_name.into(),
|
||||
args: args.iter().map(|a| Any::type_id(*a)).collect(),
|
||||
};
|
||||
|
||||
) -> Result<Option<Dynamic>, Box<EvalAltResult>> {
|
||||
// Search built-in's and external 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))
|
||||
.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 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()`
|
||||
// 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|
|
||||
result.or_else(|| {
|
||||
if !arg_for_type_of.is_empty() {
|
||||
// Handle `type_of()`
|
||||
Some(arg_for_type_of.to_string().into_dynamic())
|
||||
Some(Dynamic::from_string(arg_for_type_of.to_string()))
|
||||
} else {
|
||||
// Otherwise use the default value, if any
|
||||
def_value.clone()
|
||||
@ -620,7 +616,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
|
||||
fn optimize<'a>(
|
||||
statements: Vec<Stmt>,
|
||||
engine: &Engine<'a>,
|
||||
engine: &Engine,
|
||||
scope: &Scope,
|
||||
fn_lib: &'a [(&'a str, usize)],
|
||||
level: OptimizationLevel,
|
||||
|
1764
src/parser.rs
1764
src/parser.rs
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,8 @@
|
||||
|
||||
use crate::any::Dynamic;
|
||||
use crate::error::ParseError;
|
||||
use crate::parser::{Position, INT};
|
||||
use crate::parser::INT;
|
||||
use crate::token::Position;
|
||||
|
||||
use crate::stdlib::{
|
||||
error::Error,
|
||||
@ -21,7 +22,7 @@ use crate::stdlib::path::PathBuf;
|
||||
#[derive(Debug)]
|
||||
pub enum EvalAltResult {
|
||||
/// Syntax error.
|
||||
ErrorParsing(ParseError),
|
||||
ErrorParsing(Box<ParseError>),
|
||||
|
||||
/// 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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -279,8 +285,9 @@ impl EvalAltResult {
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Self::ErrorReadingScriptFile(_, _) => (),
|
||||
|
||||
Self::ErrorParsing(ParseError(_, pos))
|
||||
| Self::ErrorFunctionNotFound(_, pos)
|
||||
Self::ErrorParsing(err) => err.1 = new_position,
|
||||
|
||||
Self::ErrorFunctionNotFound(_, pos)
|
||||
| Self::ErrorFunctionArgsMismatch(_, _, _, pos)
|
||||
| Self::ErrorBooleanArgMismatch(_, pos)
|
||||
| Self::ErrorCharMismatch(pos)
|
||||
|
50
src/scope.rs
50
src/scope.rs
@ -1,14 +1,10 @@
|
||||
//! Module that defines the `Scope` type representing a function call-stack scope.
|
||||
|
||||
use crate::any::{Any, Dynamic};
|
||||
use crate::parser::{map_dynamic_to_expr, Expr, Position};
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::parser::{map_dynamic_to_expr, Expr};
|
||||
use crate::token::Position;
|
||||
|
||||
use crate::stdlib::{
|
||||
borrow::Cow,
|
||||
iter,
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use crate::stdlib::{borrow::Cow, iter, vec::Vec};
|
||||
|
||||
/// Type of an entry in the Scope.
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
|
||||
@ -20,7 +16,7 @@ pub enum EntryType {
|
||||
}
|
||||
|
||||
/// An entry in the Scope.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct Entry<'a> {
|
||||
/// Name of the entry.
|
||||
pub name: Cow<'a, str>,
|
||||
@ -68,7 +64,7 @@ pub(crate) struct EntryRef<'a> {
|
||||
/// allowing for automatic _shadowing_.
|
||||
///
|
||||
/// 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>>);
|
||||
|
||||
impl<'a> Scope<'a> {
|
||||
@ -157,8 +153,8 @@ impl<'a> Scope<'a> {
|
||||
/// my_scope.push("x", 42_i64);
|
||||
/// 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) {
|
||||
self.push_dynamic_value(name, EntryType::Normal, value.into_dynamic(), false);
|
||||
pub fn push<K: Into<Cow<'a, str>>, T: Variant + Clone>(&mut self, name: K, value: T) {
|
||||
self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value), false);
|
||||
}
|
||||
|
||||
/// Add (push) a new `Dynamic` entry to the Scope.
|
||||
@ -166,11 +162,11 @@ impl<'a> Scope<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::{Any, Scope};
|
||||
/// use rhai::{Dynamic, Scope};
|
||||
///
|
||||
/// 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);
|
||||
/// ```
|
||||
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);
|
||||
/// 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) {
|
||||
self.push_dynamic_value(name, EntryType::Constant, value.into_dynamic(), true);
|
||||
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, Dynamic::from(value), true);
|
||||
}
|
||||
|
||||
/// Add (push) a new constant with a `Dynamic` value to the Scope.
|
||||
@ -211,11 +207,11 @@ impl<'a> Scope<'a> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::{Any, Scope};
|
||||
/// use rhai::{Dynamic, Scope};
|
||||
///
|
||||
/// 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);
|
||||
/// ```
|
||||
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);
|
||||
/// 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
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|Entry { name: key, .. }| name == key)
|
||||
.and_then(|Entry { value, .. }| value.downcast_ref::<T>())
|
||||
.map(T::clone)
|
||||
.and_then(|Entry { value, .. }| value.downcast_ref::<T>().cloned())
|
||||
}
|
||||
|
||||
/// Update the value of the named entry.
|
||||
@ -366,7 +361,7 @@ impl<'a> Scope<'a> {
|
||||
/// my_scope.set_value("x", 0_i64);
|
||||
/// 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) {
|
||||
Some((
|
||||
EntryRef {
|
||||
@ -382,8 +377,8 @@ impl<'a> Scope<'a> {
|
||||
..
|
||||
},
|
||||
_,
|
||||
)) => self.0.get_mut(index).unwrap().value = value.into_dynamic(),
|
||||
None => self.push(name, value.into_dynamic()),
|
||||
)) => self.0.get_mut(index).unwrap().value = Dynamic::from(value),
|
||||
None => self.push(name, value),
|
||||
}
|
||||
}
|
||||
|
||||
@ -402,13 +397,6 @@ impl<'a> Scope<'a> {
|
||||
&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.
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Entry> {
|
||||
self.0.iter().rev() // Always search a Scope in reverse order
|
||||
|
@ -8,7 +8,7 @@ mod inner {
|
||||
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;
|
||||
|
||||
|
987
src/token.rs
Normal file
987
src/token.rs
Normal 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(),
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
#![cfg(not(feature = "no_object"))]
|
||||
|
||||
use rhai::{AnyExt, Engine, EvalAltResult, Map, Scope, INT};
|
||||
use rhai::{Engine, EvalAltResult, Map, Scope, INT};
|
||||
|
||||
#[test]
|
||||
fn test_map_indexing() -> Result<(), EvalAltResult> {
|
||||
|
@ -81,18 +81,18 @@ fn test_side_effects_command() -> Result<(), EvalAltResult> {
|
||||
|
||||
#[test]
|
||||
fn test_side_effects_print() -> Result<(), EvalAltResult> {
|
||||
use std::sync::Arc;
|
||||
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
|
||||
engine.on_print(|s| result.write().unwrap().push_str(s));
|
||||
// Override action of 'print' function
|
||||
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");
|
||||
Ok(())
|
||||
|
@ -9,10 +9,10 @@ fn test_stack_overflow() -> Result<(), EvalAltResult> {
|
||||
engine.eval::<i64>(
|
||||
r"
|
||||
fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } }
|
||||
foo(30)
|
||||
foo(25)
|
||||
",
|
||||
)?,
|
||||
465
|
||||
325
|
||||
);
|
||||
|
||||
match engine.eval::<()>(
|
||||
|
109
tests/string.rs
109
tests/string.rs
@ -1,4 +1,4 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
#[test]
|
||||
fn test_string() -> Result<(), EvalAltResult> {
|
||||
@ -33,3 +33,110 @@ fn test_string() -> Result<(), EvalAltResult> {
|
||||
|
||||
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(())
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
#![cfg(not(feature = "no_stdlib"))]
|
||||
#![cfg(not(feature = "no_std"))]
|
||||
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user