Merge pull request #138 from schungx/master

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

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

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

View File

@ -1,6 +1,6 @@
[package]
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
View File

@ -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
View File

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

29
benches/engine.rs Normal file
View File

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

63
benches/eval_array.rs Normal file
View File

@ -0,0 +1,63 @@
#![feature(test)]
///! Test evaluating expressions
extern crate test;
use rhai::{Engine, OptimizationLevel};
use test::Bencher;
#[bench]
fn bench_eval_array_small_get(bench: &mut Bencher) {
let script = "let x = [1, 2, 3, 4, 5]; x[3]";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_array_small_set(bench: &mut Bencher) {
let script = "let x = [1, 2, 3, 4, 5]; x[3] = 42;";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_array_large_get(bench: &mut Bencher) {
let script = r#"let x = [ 1, 2.345, "hello", true,
[ 1, 2, 3, [ "hey", [ "deeply", "nested" ], "jude" ] ]
];
x[4][3][1][1]
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}
#[bench]
fn bench_eval_array_large_set(bench: &mut Bencher) {
let script = r#"let x = [ 1, 2.345, "hello", true,
[ 1, 2, 3, [ "hey", [ "deeply", "nested" ], "jude" ] ]
];
x[4] = 42
"#;
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
let ast = engine.compile(script).unwrap();
bench.iter(|| engine.consume_ast(&ast).unwrap());
}

View File

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

71
benches/eval_map.rs Normal file
View File

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

77
benches/eval_scope.rs Normal file
View File

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

100
benches/eval_type.rs Normal file
View File

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

49
benches/iterations.rs Normal file
View File

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

103
benches/parsing.rs Normal file
View File

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

44
benches/primes.rs Normal file
View File

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

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, Position, Scope, AST};
use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST};
#[cfg(not(feature = "no_optimize"))]
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();
}
}

View File

@ -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
View File

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

73
scripts/mat_mul.rhai Normal file
View File

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

View File

@ -1,70 +1,41 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
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)))
}
}

View File

@ -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));
}
}

View File

@ -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);
}
});
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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)
}
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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));
}

View File

@ -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;

View File

@ -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,

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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

View File

@ -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
View File

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

View File

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

View File

@ -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(())

View File

@ -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::<()>(

View File

@ -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(())
}

View File

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