Merge pull request #116 from schungx/master

Efficiency improvements and code refactor.
This commit is contained in:
Stephen Chung 2020-03-28 11:14:34 +08:00 committed by GitHub
commit 65c4639068
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1306 additions and 1148 deletions

View File

@ -42,21 +42,21 @@ codegen-units = 1
#panic = 'abort' # remove stack backtrace for no-std
[dependencies.libm]
version = "0.2.1"
version = "*"
optional = true
[dependencies.core-error]
version = "0.0.1-rc4"
version = "*"
features = ["alloc"]
optional = true
[dependencies.hashbrown]
version = "0.7.1"
version = "*"
default-features = false
features = ["ahash", "nightly", "inline-more"]
optional = true
[dependencies.ahash]
version = "0.3.2"
version = "*"
default-features = false
optional = true

134
README.md
View File

@ -36,14 +36,20 @@ Install the Rhai crate by adding this line to `dependencies`:
rhai = "0.11.0"
```
or simply:
Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/):
```toml
[dependencies]
rhai = "*"
```
to use the latest version.
Crate versions are released on [`crates.io`](https::/crates.io/crates/rhai/) infrequently, so if you want to track the
latest features, enhancements and bug fixes, pull directly from GitHub:
```toml
[dependencies]
rhai = { git = "https://github.com/jonathandturner/rhai" }
```
Beware that in order to use pre-releases (e.g. alpha and beta), the exact version must be specified in the `Cargo.toml`.
@ -80,7 +86,7 @@ Related
Other cool projects to check out:
* [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being lead by my cousin.
* [ChaiScript](http://chaiscript.com/) - A strong inspiration for Rhai. An embedded scripting language for C++ that I helped created many moons ago, now being led by my cousin.
* Check out the list of [scripting languages for Rust](https://github.com/rust-unofficial/awesome-rust#scripting) on [awesome-rust](https://github.com/rust-unofficial/awesome-rust)
Examples
@ -252,7 +258,7 @@ let result = engine.eval_expression_with_scope::<i64>(&mut scope, "if x { 42 } e
Values and types
----------------
[`type_of`]: #values-and-types
[`type_of()`]: #values-and-types
The following primitive types are supported natively:
@ -547,8 +553,8 @@ let result = engine.eval::<i64>("let x = new_ts(); x.foo()")?;
println!("result: {}", result); // prints 1
```
[`type_of`] works fine with custom types and returns the name of the type. If `register_type_with_name` is used to register the custom type
with a special "pretty-print" name, [`type_of`] will return that name instead.
[`type_of()`] works fine with custom types and returns the name of the type. If `register_type_with_name` is used to register the custom type
with a special "pretty-print" name, [`type_of()`] will return that name instead.
```rust
engine.register_type::<TestStruct>();
@ -645,6 +651,18 @@ fn main() -> Result<(), EvalAltResult>
}
```
Engine configuration options
---------------------------
| Method | Description |
| ------------------------ | ---------------------------------------------------------------------------------------- |
| `set_optimization_level` | Set the amount of script _optimizations_ performed. See [`script optimization`]. |
| `set_max_call_levels` | Set the maximum number of function call levels (default 50) to avoid infinite recursion. |
[`script optimization`]: #script-optimization
-------
Rhai Language Guide
===================
@ -832,9 +850,11 @@ String and char literals follow C-style formatting, with support for Unicode ('`
Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicode code points and '`\U`' maps the full, 32-bit extended Unicode code points.
Although internally Rhai strings are stored as UTF-8 just like in Rust (they _are_ Rust `String`s),
in the Rhai language they can be considered a stream of Unicode characters, and can be directly indexed (unlike Rust).
Individual characters within a Rhai string can be replaced. In Rhai, there is no separate concepts of `String` and `&str` as in Rust.
Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`s!), but there are major differences.
In Rhai a string is the same as an array of Unicode characters and can be directly indexed (unlike Rust).
This is similar to most other languages where strings are internally represented not as UTF-8 but as arrays of multi-byte Unicode characters.
Individual characters within a Rhai string can also be replaced just as if the string is an array of Unicode characters.
In Rhai, there is also no separate concepts of `String` and `&str` as in Rust.
Strings can be built up from other strings and types via the `+` operator (provided by the standard library but excluded if [`no_stdlib`]).
This is particularly useful when printing output.
@ -926,9 +946,11 @@ Arrays
------
Arrays are first-class citizens in Rhai. Like C, arrays are accessed with zero-based, non-negative integer indices.
Array literals are built within square brackets '`[`' ,, '`]`' and separated by commas '`,`'.
Array literals are built within square brackets '`[`' ... '`]`' and separated by commas '`,`'.
The type of a Rhai array is `rhai::Array`. [`type_of()`] an array returns `"array"`.
The Rust type of a Rhai array is `rhai::Array`.
[`type_of()`] an array returns `"array"`.
Arrays are disabled via the [`no_index`] feature.
@ -1075,17 +1097,13 @@ my_str += 12345;
my_str == "abcABC12345"
```
If statements
-------------
All branches of an `if` statement must be enclosed within braces '`{`' .. '`}`', even when there is only one statement.
Like Rust, there is no ambiguity regarding which `if` clause a statement belongs to.
`if` statements
---------------
```rust
if true {
if foo(x) {
print("It's true!");
} else if true {
} else if bar == baz {
print("It's true again!");
} else if ... {
:
@ -1094,13 +1112,28 @@ if true {
} else {
print("It's finally false!");
}
```
All branches of an `if` statement must be enclosed within braces '`{`' .. '`}`', even when there is only one statement.
Like Rust, there is no ambiguity regarding which `if` clause a statement belongs to.
```rust
if (decision) print("I've decided!");
// ^ syntax error, expecting '{' in statement block
```
While loops
-----------
Like Rust, `if` statements can also be used as _expressions_, replacing the `? :` conditional operators in other C-like languages.
```rust
let x = 1 + if true { 42 } else { 123 } / 2;
x == 22;
let x = if false { 42 }; // No else branch defaults to '()'
x == ();
```
`while` loops
-------------
```rust
let x = 10;
@ -1112,8 +1145,8 @@ while x > 0 {
}
```
Infinite loops
--------------
Infinite `loop`
---------------
```rust
let x = 10;
@ -1125,8 +1158,8 @@ loop {
}
```
For loops
---------
`for` loops
-----------
Iterating through a range or an array is provided by the `for` ... `in` loop.
@ -1146,8 +1179,8 @@ for x in range(0, 50) {
}
```
Returning values
----------------
`return`-ing values
-------------------
```rust
return; // equivalent to return ();
@ -1155,8 +1188,8 @@ return; // equivalent to return ();
return 123 + 456; // returns 579
```
Errors and exceptions
---------------------
Errors and `throw`-ing exceptions
--------------------------------
All of [`Engine`]'s evaluation/consuming methods return `Result<T, rhai::EvalAltResult>` with `EvalAltResult` holding error information.
To deliberately return an error during an evaluation, use the `throw` keyword.
@ -1197,8 +1230,10 @@ fn add(x, y) {
print(add(2, 3));
```
### Implicit return
Just like in Rust, an implicit return can be used. In fact, the last statement of a block is _always_ the block's return value
regardless of whether it is terminated with a semicolon `;`. This is different from Rust.
regardless of whether it is terminated with a semicolon `';'`. This is different from Rust.
```rust
fn add(x, y) {
@ -1213,6 +1248,16 @@ print(add(2, 3)); // prints 5
print(add2(42)); // prints 44
```
### No access to external scope
Functions can only access their parameters. They cannot access external variables (even _global_ variables).
```rust
let x = 42;
fn foo() { x } // syntax error - variable 'x' doesn't exist
```
### Passing arguments by value
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type).
@ -1225,7 +1270,7 @@ fn change(s) { // 's' is passed by value
}
let x = 500;
x.change(); // desugars to change(x)
x.change(); // de-sugars to change(x)
x == 500; // 'x' is NOT changed!
```
@ -1251,31 +1296,34 @@ fn do_addition(x) {
### Functions overloading
Functions can be _overloaded_ based on the _number_ of parameters (but not parameter _types_, since all parameters are the same type - [`Dynamic`]).
New definitions of the same name and number of parameters overwrite previous definitions.
Functions can be _overloaded_ and are resolved purely upon the function's _name_ and the _number_ of parameters
(but not parameter _types_, since all parameters are the same type - [`Dynamic`]).
New definitions _overwrite_ previous definitions of the same name and number of parameters.
```rust
fn abc(x,y,z) { print("Three!!! " + x + "," + y + "," + z) }
fn abc(x) { print("One! " + x) }
fn abc(x,y) { print("Two! " + x + "," + y) }
fn abc() { print("None.") }
fn abc(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition
fn foo(x,y,z) { print("Three!!! " + x + "," + y + "," + z) }
fn foo(x) { print("One! " + x) }
fn foo(x,y) { print("Two! " + x + "," + y) }
fn foo() { print("None.") }
fn foo(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition
abc(1,2,3); // prints "Three!!! 1,2,3"
abc(42); // prints "HA! NEW ONE! 42"
abc(1,2); // prints "Two!! 1,2"
abc(); // prints "None."
foo(1,2,3); // prints "Three!!! 1,2,3"
foo(42); // prints "HA! NEW ONE! 42"
foo(1,2); // prints "Two!! 1,2"
foo(); // prints "None."
```
Members and methods
-------------------
Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like in Rust:
Properties and methods in a Rust custom type registered with the [`Engine`] can be called just like in Rust.
```rust
let a = new_ts(); // constructor function
a.field = 500; // property access
a.update(); // method call
update(a); // this works, but 'a' is unchanged because only a COPY of 'a' is passed to 'update' by VALUE
```
`print` and `debug`

View File

@ -1,8 +1,8 @@
use rhai::{Engine, RegisterFn};
use rhai::{Engine, RegisterFn, INT};
#[derive(Clone, Debug)]
struct TestStruct {
x: i64,
x: INT,
}
impl TestStruct {

View File

@ -1,8 +1,8 @@
use rhai::{Engine, EvalAltResult, RegisterFn};
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
#[derive(Clone)]
struct TestStruct {
x: i64,
x: INT,
}
impl TestStruct {

View File

@ -1,9 +1,9 @@
use rhai::{Engine, EvalAltResult};
use rhai::{Engine, EvalAltResult, INT};
fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
let result = engine.eval::<i64>("40 + 2")?;
let result = engine.eval::<INT>("40 + 2")?;
println!("Answer: {}", result); // prints 42

View File

@ -1,12 +1,16 @@
#![cfg_attr(feature = "no_std", no_std)]
use rhai::{Engine, EvalAltResult};
use rhai::{Engine, EvalAltResult, INT};
fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
let result = engine.eval::<i64>("40 + 2")?;
let result = engine.eval::<INT>("40 + 2")?;
#[cfg(not(feature = "no_std"))]
println!("Answer: {}", result);
#[cfg(feature = "no_std")]
assert_eq!(result, 42);
Ok(())

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, Scope, AST};
use rhai::{Engine, EvalAltResult, Position, Scope, AST};
#[cfg(not(feature = "no_optimize"))]
use rhai::OptimizationLevel;
@ -13,7 +13,7 @@ fn print_error(input: &str, err: EvalAltResult) {
iter::repeat(pad).take(len).collect::<String>()
}
let lines: Vec<_> = input.trim().split("\n").collect();
let lines: Vec<_> = input.trim().split('\n').collect();
let line_no = if lines.len() > 1 {
match err.position() {
@ -26,27 +26,18 @@ fn print_error(input: &str, err: EvalAltResult) {
};
// Print error
let pos_text = format!(" ({})", err.position());
let pos = err.position();
let pos_text = format!(" ({})", pos);
match err.position() {
p if p.is_eof() => {
// EOF
let pos = if pos.is_eof() {
let last = lines[lines.len() - 1];
println!("{}{}", line_no, last);
let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
_ => err.to_string(),
Position::new(lines.len(), last.len() + 1)
} else {
pos
};
println!(
"{}^ {}",
padding(" ", line_no.len() + last.len() - 1),
err_text.replace(&pos_text, "")
);
}
match pos {
p if p.is_eof() => panic!("should not be EOF"),
p if p.is_none() => {
// No position
println!("{}", err);
@ -59,7 +50,7 @@ fn print_error(input: &str, err: EvalAltResult) {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
_ => err.to_string(),
err => err.to_string(),
};
println!(
@ -161,7 +152,7 @@ fn main() {
#[cfg(not(feature = "no_optimize"))]
{
engine.set_optimization_level(OptimizationLevel::Full);
ast = Some(engine.optimize_ast(&mut scope, ast_u.as_ref().unwrap()));
ast = Some(engine.optimize_ast(&scope, ast_u.as_ref().unwrap()));
engine.set_optimization_level(OptimizationLevel::None);
}
@ -178,9 +169,9 @@ fn main() {
})
})
{
println!("");
println!();
print_error(&input, err);
println!("");
println!();
}
}
}

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, Scope};
use rhai::{Engine, EvalAltResult, Scope, INT};
fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
@ -6,7 +6,7 @@ fn main() -> Result<(), EvalAltResult> {
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?;
let result = engine.eval_with_scope::<i64>(&mut scope, "x")?;
let result = engine.eval_with_scope::<INT>(&mut scope, "x")?;
println!("result: {}", result);

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult};
use rhai::{Engine, EvalAltResult, Position};
#[cfg(not(feature = "no_optimize"))]
use rhai::OptimizationLevel;
@ -10,7 +10,7 @@ fn padding(pad: &str, len: usize) -> String {
}
fn eprint_error(input: &str, err: EvalAltResult) {
fn eprint_line(lines: &Vec<&str>, line: usize, pos: usize, err: &str) {
fn eprint_line(lines: &[&str], line: usize, pos: usize, err: &str) {
let line_no = format!("{}: ", line);
let pos_text = format!(" (line {}, position {})", line, pos);
@ -23,34 +23,32 @@ fn eprint_error(input: &str, err: EvalAltResult) {
eprintln!("");
}
let lines: Vec<_> = input.split("\n").collect();
let lines: Vec<_> = input.split('\n').collect();
// Print error
match err.position() {
p if p.is_eof() => {
// EOF
let line = lines.len() - 1;
let pos = lines[line - 1].len();
let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
_ => err.to_string(),
let pos = if err.position().is_eof() {
let last = lines[lines.len() - 1];
Position::new(lines.len(), last.len() + 1)
} else {
err.position()
};
eprint_line(&lines, line, pos, &err_text);
}
match pos {
p if p.is_eof() => panic!("should not be EOF"),
p if p.is_none() => {
// No position
eprintln!("{}", err);
}
p => {
// Specific position
eprint_line(
&lines,
p.line().unwrap(),
p.position().unwrap(),
&err.to_string(),
)
let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
err => err.to_string(),
};
eprint_line(&lines, p.line().unwrap(), p.position().unwrap(), &err_text)
}
}
}
@ -72,13 +70,10 @@ fn main() {
let mut contents = String::new();
match f.read_to_string(&mut contents) {
Err(err) => {
if let Err(err) = f.read_to_string(&mut contents) {
eprintln!("Error reading script file: {}\n{}", filename, err);
exit(1);
}
_ => (),
}
if let Err(err) = engine.consume(false, &contents) {
eprintln!("{}", padding("=", filename.len()));

View File

@ -1,15 +1,15 @@
use rhai::{Engine, EvalAltResult, RegisterFn};
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
fn add(x: i64, y: i64) -> i64 {
fn add(x: INT, y: INT) -> INT {
x + y
}
engine.register_fn("add", add);
let result = engine.eval::<i64>("add(40, 2)")?;
let result = engine.eval::<INT>("add(40, 2)")?;
println!("Answer: {}", result); // prints 42

View File

@ -15,6 +15,7 @@ use crate::optimize::optimize_into_ast;
use crate::stdlib::{
any::{type_name, TypeId},
boxed::Box,
format,
string::{String, ToString},
sync::Arc,
vec::Vec,
@ -35,7 +36,7 @@ impl<'e> Engine<'e> {
args,
};
self.ext_functions.insert(spec, f);
self.functions.insert(spec, f);
}
/// Register a custom type for use with the `Engine`.
@ -725,11 +726,9 @@ impl<'e> Engine<'e> {
statements
};
let mut result = ().into_dynamic();
for stmt in statements {
result = engine.eval_stmt(scope, stmt)?;
}
let result = statements.iter().try_fold(().into_dynamic(), |_, stmt| {
engine.eval_stmt(scope, stmt, 0)
})?;
if !retain_functions {
engine.clear_functions();
@ -828,7 +827,7 @@ impl<'e> Engine<'e> {
let result = statements
.iter()
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o))
.try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0))
.map(|_| ());
if !retain_functions {
@ -847,13 +846,7 @@ impl<'e> Engine<'e> {
functions: impl IntoIterator<Item = &'a Arc<FnDef>>,
) {
for f in functions.into_iter() {
match self
.script_functions
.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len()))
{
Ok(n) => self.script_functions[n] = f.clone(),
Err(n) => self.script_functions.insert(n, f.clone()),
}
self.fn_lib.add_or_replace_function(f.clone());
}
}
@ -887,20 +880,11 @@ impl<'e> Engine<'e> {
name: &str,
args: A,
) -> Result<T, EvalAltResult> {
// Split out non-generic portion to avoid exploding code size
fn call_fn_internal(
engine: &mut Engine,
name: &str,
mut values: Vec<Dynamic>,
) -> Result<Dynamic, EvalAltResult> {
let values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
let mut values = args.into_vec();
let mut arg_values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
let result = engine.call_fn_raw(name, values, None, Position::none());
result
}
call_fn_internal(self, name, args.into_vec()).and_then(|b| {
self.call_fn_raw(name, &mut arg_values, None, Position::none(), 0)
.and_then(|b| {
b.downcast().map(|b| *b).map_err(|a| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name((*a).type_name()).into(),

View File

@ -57,7 +57,7 @@ macro_rules! reg_op_result1 {
impl Engine<'_> {
/// Register the core built-in library.
pub(crate) fn register_core_lib(&mut self) {
/// Checked add
// Checked add
#[cfg(not(feature = "unchecked"))]
fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_add(&y).ok_or_else(|| {
@ -67,7 +67,7 @@ impl Engine<'_> {
)
})
}
/// Checked subtract
// Checked subtract
#[cfg(not(feature = "unchecked"))]
fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_sub(&y).ok_or_else(|| {
@ -77,7 +77,7 @@ impl Engine<'_> {
)
})
}
/// Checked multiply
// Checked multiply
#[cfg(not(feature = "unchecked"))]
fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_mul(&y).ok_or_else(|| {
@ -87,7 +87,7 @@ impl Engine<'_> {
)
})
}
/// Checked divide
// Checked divide
#[cfg(not(feature = "unchecked"))]
fn div<T>(x: T, y: T) -> Result<T, EvalAltResult>
where
@ -108,7 +108,7 @@ impl Engine<'_> {
)
})
}
/// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
// Checked negative - e.g. -(i32::MIN) will overflow i32::MAX
#[cfg(not(feature = "unchecked"))]
fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, EvalAltResult> {
x.checked_neg().ok_or_else(|| {
@ -118,7 +118,7 @@ impl Engine<'_> {
)
})
}
/// Checked absolute
// Checked absolute
#[cfg(not(feature = "unchecked"))]
fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, EvalAltResult> {
// FIX - We don't use Signed::abs() here because, contrary to documentation, it panics
@ -134,32 +134,32 @@ impl Engine<'_> {
})
}
}
/// Unchecked add - may panic on overflow
// Unchecked add - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output {
x + y
}
/// Unchecked subtract - may panic on underflow
// Unchecked subtract - may panic on underflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output {
x - y
}
/// Unchecked multiply - may panic on overflow
// Unchecked multiply - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output {
x * y
}
/// Unchecked divide - may panic when dividing by zero
// Unchecked divide - may panic when dividing by zero
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output {
x / y
}
/// Unchecked negative - may panic on overflow
// Unchecked negative - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output {
-x
}
/// Unchecked absolute - may panic on overflow
// Unchecked absolute - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn abs_u<T>(x: T) -> <T as Neg>::Output
where
@ -174,7 +174,6 @@ impl Engine<'_> {
}
// Comparison operators
fn lt<T: PartialOrd>(x: T, y: T) -> bool {
x < y
}
@ -195,7 +194,6 @@ impl Engine<'_> {
}
// Logic operators
fn and(x: bool, y: bool) -> bool {
x && y
}
@ -207,7 +205,6 @@ impl Engine<'_> {
}
// Bit operators
fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output {
x & y
}
@ -218,7 +215,7 @@ impl Engine<'_> {
x ^ y
}
/// Checked left-shift
// Checked left-shift
#[cfg(not(feature = "unchecked"))]
fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, EvalAltResult> {
// Cannot shift by a negative number of bits
@ -236,7 +233,7 @@ impl Engine<'_> {
)
})
}
/// Checked right-shift
// Checked right-shift
#[cfg(not(feature = "unchecked"))]
fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, EvalAltResult> {
// Cannot shift by a negative number of bits
@ -254,17 +251,17 @@ impl Engine<'_> {
)
})
}
/// Unchecked left-shift - may panic if shifting by a negative number of bits
// Unchecked left-shift - may panic if shifting by a negative number of bits
#[cfg(feature = "unchecked")]
fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output {
x.shl(y)
}
/// Unchecked right-shift - may panic if shifting by a negative number of bits
// Unchecked right-shift - may panic if shifting by a negative number of bits
#[cfg(feature = "unchecked")]
fn shr_u<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output {
x.shr(y)
}
/// Checked modulo
// Checked modulo
#[cfg(not(feature = "unchecked"))]
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_rem(&y).ok_or_else(|| {
@ -274,12 +271,12 @@ impl Engine<'_> {
)
})
}
/// Unchecked modulo - may panic if dividing by zero
// Unchecked modulo - may panic if dividing by zero
#[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output {
x % y
}
/// Checked power
// Checked power
#[cfg(not(feature = "unchecked"))]
fn pow_i_i(x: INT, y: INT) -> Result<INT, EvalAltResult> {
#[cfg(not(feature = "only_i32"))]
@ -321,17 +318,17 @@ impl Engine<'_> {
}
}
}
/// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
// Unchecked integer power - may panic on overflow or if the power index is too high (> u32::MAX)
#[cfg(feature = "unchecked")]
fn pow_i_i_u(x: INT, y: INT) -> INT {
x.pow(y as u32)
}
/// Floating-point power - always well-defined
// Floating-point power - always well-defined
#[cfg(not(feature = "no_float"))]
fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT {
x.powf(y)
}
/// Checked power
// Checked power
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_float"))]
fn pow_f_i(x: FLOAT, y: INT) -> Result<FLOAT, EvalAltResult> {
@ -345,7 +342,7 @@ impl Engine<'_> {
Ok(x.powi(y as i32))
}
/// Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
// Unchecked power - may be incorrect if the power index is too high (> i32::MAX)
#[cfg(feature = "unchecked")]
#[cfg(not(feature = "no_float"))]
fn pow_f_i_u(x: FLOAT, y: INT) -> FLOAT {
@ -432,7 +429,8 @@ impl Engine<'_> {
}
}
// `&&` and `||` are treated specially as they short-circuit
// `&&` and `||` are treated specially as they short-circuit.
// They are implemented as special `Expr` instances, not function calls.
//reg_op!(self, "||", or, bool);
//reg_op!(self, "&&", and, bool);
@ -623,10 +621,6 @@ impl Engine<'_> {
});
}
fn range<T>(from: T, to: T) -> Range<T> {
from..to
}
reg_iterator::<INT>(self);
self.register_fn("range", |i1: INT, i2: INT| (i1..i2));
@ -634,15 +628,15 @@ impl Engine<'_> {
#[cfg(not(feature = "only_i64"))]
{
macro_rules! reg_range {
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => (
($self:expr, $x:expr, $( $y:ty ),*) => (
$(
reg_iterator::<$y>(self);
$self.register_fn($x, $op as fn(x: $y, y: $y)->Range<$y>);
$self.register_fn($x, (|x: $y, y: $y| x..y) as fn(x: $y, y: $y)->Range<$y>);
)*
)
}
reg_range!(self, "range", range, i8, u8, i16, u16, i32, i64, u32, u64);
reg_range!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64);
}
}
}
@ -837,10 +831,10 @@ impl Engine<'_> {
}
reg_fn2x!(self, "+", append, String, String, INT, bool, char);
self.register_fn("+", |x: String, _: ()| format!("{}", x));
self.register_fn("+", |x: String, _: ()| x);
reg_fn2y!(self, "+", prepend, String, String, INT, bool, char);
self.register_fn("+", |_: (), y: String| format!("{}", y));
self.register_fn("+", |_: (), y: String| y);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
@ -891,9 +885,7 @@ impl Engine<'_> {
let trimmed = s.trim();
if trimmed.len() < s.len() {
let chars: Vec<_> = trimmed.chars().collect();
s.clear();
chars.iter().for_each(|&ch| s.push(ch));
*s = trimmed.to_string();
}
});
}

View File

@ -11,11 +11,15 @@ use crate::engine::Array;
use crate::stdlib::{string::String, vec, 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
/// as arguments to a function call.
pub trait FuncArgs {
/// Convert to a `Vec` of `Dynamic` arguments.
fn into_vec(self) -> Vec<Dynamic>;
}
/// Macro to implement `FuncArgs` for a single standard type that can be converted
/// into `Dynamic`.
macro_rules! impl_std_args {
($($p:ty),*) => {
$(
@ -43,6 +47,8 @@ impl_std_args!(INT);
#[cfg(not(feature = "no_float"))]
impl_std_args!(f32, f64);
/// Macro to implement `FuncArgs` for tuples of standard types (each can be
/// converted into `Dynamic`).
macro_rules! impl_args {
($($p:ident),*) => {
impl<$($p: Any + Clone),*> FuncArgs for ($($p,)*)
@ -69,5 +75,5 @@ macro_rules! impl_args {
};
}
#[cfg_attr(rustfmt, rustfmt_skip)]
impl_args!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
#[rustfmt::skip]
impl_args!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V);

File diff suppressed because it is too large Load Diff

View File

@ -17,8 +17,6 @@ pub enum LexError {
MalformedNumber(String),
/// An character literal is in an invalid format.
MalformedChar(String),
/// Error in the script text.
InputError(String),
/// An identifier is in an invalid format.
MalformedIdentifier(String),
}
@ -35,7 +33,6 @@ impl fmt::Display for LexError {
Self::MalformedIdentifier(s) => {
write!(f, "Variable name is not in a legal format: '{}'", s)
}
Self::InputError(s) => write!(f, "{}", s),
Self::UnterminatedString => write!(f, "Open string is not terminated"),
}
}
@ -85,6 +82,9 @@ pub enum ParseErrorType {
/// A function definition is missing the parameters list. Wrapped value is the function name.
#[cfg(not(feature = "no_function"))]
FnMissingParams(String),
/// A function definition has duplicated parameters. Wrapped values are the function name and parameter name.
#[cfg(not(feature = "no_function"))]
FnDuplicateParam(String, String),
/// A function definition is missing the body. Wrapped value is the function name.
#[cfg(not(feature = "no_function"))]
FnMissingBody(String),
@ -98,16 +98,23 @@ pub enum ParseErrorType {
LoopBreak,
}
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())
}
}
/// Error when parsing a script.
#[derive(Debug, PartialEq, Clone)]
pub struct ParseError(pub(crate) ParseErrorType, pub(crate) Position);
impl ParseError {
/// Create a new `ParseError`.
pub(crate) fn new(err: ParseErrorType, pos: Position) -> Self {
Self(err, pos)
}
/// Get the parse error.
pub fn error_type(&self) -> &ParseErrorType {
&self.0
@ -142,6 +149,8 @@ impl ParseError {
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnDuplicateParam(_,_) => "Duplicated parameters in function declaration",
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration",
#[cfg(not(feature = "no_function"))]
ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
@ -183,6 +192,11 @@ impl fmt::Display for ParseError {
write!(f, "Expecting body statement block for function '{}'", s)?
}
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnDuplicateParam(ref s, ref arg) => {
write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)?
}
ParseErrorType::MissingRightParen(ref s) | ParseErrorType::MissingRightBrace(ref s) => {
write!(f, "{} for {}", self.desc(), s)?
}

View File

@ -80,7 +80,8 @@ pub trait RegisterResultFn<FN, ARGS, RET> {
/// // Normal function
/// fn div(x: i64, y: i64) -> Result<i64, EvalAltResult> {
/// if y == 0 {
/// Err("division by zero!".into()) // '.into()' automatically converts to 'EvalAltResult::ErrorRuntime'
/// // '.into()' automatically converts to 'EvalAltResult::ErrorRuntime'
/// Err("division by zero!".into())
/// } else {
/// Ok(x / y)
/// }
@ -97,9 +98,30 @@ pub trait RegisterResultFn<FN, ARGS, RET> {
fn register_result_fn(&mut self, name: &str, f: FN);
}
pub struct Ref<A>(A);
pub struct Mut<A>(A);
// These types are used to build a unique _marker_ tuple type for each combination
// of function parameter types in order to make each trait implementation unique.
// That is because stable Rust currently does not allow distinguishing implementations
// based purely on parameter types of traits (Fn, FnOnce and FnMut).
//
// For example:
//
// `RegisterFn<FN, (Mut<A>, B, Ref<C>), R>`
//
// will have the function prototype constraint to:
//
// `FN: (&mut A, B, &C) -> R`
//
// These types are not actually used anywhere.
pub struct Mut<T>(T);
//pub struct Ref<T>(T);
/// Identity dereferencing function.
#[inline]
fn identity<T>(data: T) -> T {
data
}
/// This macro counts the number of arguments via recursion.
macro_rules! count_args {
() => { 0_usize };
( $head:ident $($tail:ident)* ) => { 1_usize + count_args!($($tail)*) };
@ -110,6 +132,10 @@ macro_rules! def_register {
def_register!(imp);
};
(imp $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => {
// ^ function parameter generic type name
// ^ function parameter marker type (T, Ref<T> or Mut<T>)
// ^ function parameter actual type
// ^ dereferencing function
impl<
$($par: Any + Clone,)*
FN: Fn($($param),*) -> RET + 'static,
@ -119,7 +145,7 @@ macro_rules! def_register {
fn register_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string();
let fun = move |mut args: FnCallArgs, pos: Position| {
let fun = move |args: &mut FnCallArgs, pos: Position| {
// Check for length at the beginning to avoid per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*);
@ -128,7 +154,7 @@ macro_rules! def_register {
}
#[allow(unused_variables, unused_mut)]
let mut drain = args.drain(..);
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();
@ -151,7 +177,7 @@ macro_rules! def_register {
fn register_dynamic_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string();
let fun = move |mut args: FnCallArgs, pos: Position| {
let fun = move |args: &mut FnCallArgs, pos: Position| {
// Check for length at the beginning to avoid per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*);
@ -160,7 +186,7 @@ macro_rules! def_register {
}
#[allow(unused_variables, unused_mut)]
let mut drain = args.drain(..);
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();
@ -183,7 +209,7 @@ macro_rules! def_register {
fn register_result_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string();
let fun = move |mut args: FnCallArgs, pos: Position| {
let fun = move |args: &mut FnCallArgs, pos: Position| {
// Check for length at the beginning to avoid per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*);
@ -192,7 +218,7 @@ macro_rules! def_register {
}
#[allow(unused_variables, unused_mut)]
let mut drain = args.drain(..);
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();
@ -200,10 +226,8 @@ macro_rules! def_register {
// 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(|mut err| {
err.set_position(pos);
err
})
f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic)
.map_err(|err| err.set_position(pos))
};
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun));
}
@ -213,8 +237,12 @@ macro_rules! def_register {
};
($p0:ident $(, $p:ident)*) => {
def_register!(imp $p0 => $p0 => $p0 => Clone::clone $(, $p => $p => $p => Clone::clone)*);
def_register!(imp $p0 => Ref<$p0> => &$p0 => |x| { x } $(, $p => $p => $p => Clone::clone)*);
def_register!(imp $p0 => Mut<$p0> => &mut $p0 => |x| { x } $(, $p => $p => $p => Clone::clone)*);
def_register!(imp $p0 => Mut<$p0> => &mut $p0 => identity $(, $p => $p => $p => Clone::clone)*);
// handle the first parameter ^ first parameter passed through
// others passed by value (cloned) ^
// No support for functions where the first argument is a reference
//def_register!(imp $p0 => Ref<$p0> => &$p0 => identity $(, $p => $p => $p => Clone::clone)*);
def_register!($($p),*);
};
@ -224,5 +252,5 @@ macro_rules! def_register {
// };
}
#[cfg_attr(rustfmt, rustfmt_skip)]
def_register!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
#[rustfmt::skip]
def_register!(A, B, C, D, E, F, G, H, J, K, L, M, N, P, Q, R, S, T, U, V);

View File

@ -63,7 +63,7 @@ pub use error::{ParseError, ParseErrorType};
pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
pub use parser::{Position, AST, INT};
pub use result::EvalAltResult;
pub use scope::{Scope, ScopeEntry, VariableType};
pub use scope::Scope;
#[cfg(not(feature = "no_index"))]
pub use engine::Array;

View File

@ -2,11 +2,10 @@
use crate::any::{Any, Dynamic};
use crate::engine::{
Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT,
KEYWORD_TYPE_OF,
Engine, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF,
};
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
use crate::scope::{Scope, ScopeEntry, VariableType};
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::stdlib::{
boxed::Box,
@ -181,16 +180,15 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
// Optimize each statement in the block
let mut result: Vec<_> = block
.into_iter()
.map(|stmt| {
if let Stmt::Const(name, value, pos) = stmt {
.map(|stmt| match stmt {
// Add constant into the state
Stmt::Const(name, value, pos) => {
state.push_constant(&name, *value);
state.set_dirty();
Stmt::Noop(pos) // No need to keep constants
} else {
// Optimize the statement
optimize_stmt(stmt, state, preserve_result)
}
// Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result),
})
.collect();
@ -446,13 +444,13 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
&& args.iter().all(|expr| expr.is_constant()) // all arguments are constants
=> {
// First search in script-defined functions (can override built-in)
if state.engine.script_functions.binary_search_by(|f| f.compare(&id, args.len())).is_ok() {
if state.engine.fn_lib.has_function(&id, args.len()) {
// A script-defined function overrides the built-in function - do not make the call
return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos);
}
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
let call_args: FnCallArgs = arg_values.iter_mut().map(Dynamic::as_mut).collect();
let mut call_args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_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
@ -462,7 +460,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
""
};
state.engine.call_ext_fn_raw(&id, call_args, pos).ok().map(|r|
state.engine.call_ext_fn_raw(&id, &mut call_args, pos).ok().map(|r|
r.or_else(|| {
if !arg_for_type_of.is_empty() {
// Handle `type_of()`
@ -512,9 +510,9 @@ pub(crate) fn optimize<'a>(statements: Vec<Stmt>, engine: &Engine<'a>, scope: &S
// Add constants from the scope into the state
scope
.iter()
.filter(|ScopeEntry { var_type, expr, .. }| {
.filter(|ScopeEntry { typ, expr, .. }| {
// Get all the constants with definite constant expressions
*var_type == VariableType::Constant
*typ == ScopeEntryType::Constant
&& expr.as_ref().map(Expr::is_constant).unwrap_or(false)
})
.for_each(|ScopeEntry { name, expr, .. }| {
@ -566,7 +564,7 @@ pub(crate) fn optimize<'a>(statements: Vec<Stmt>, engine: &Engine<'a>, scope: &S
// Add back the last statement unless it is a lone No-op
if let Some(stmt) = last_stmt {
if result.len() > 0 || !matches!(stmt, Stmt::Noop(_)) {
if !result.is_empty() || !matches!(stmt, Stmt::Noop(_)) {
result.push(stmt);
}
}
@ -591,9 +589,7 @@ pub fn optimize_into_ast(
functions
.into_iter()
.map(|mut fn_def| {
match engine.optimization_level {
OptimizationLevel::None => (),
OptimizationLevel::Simple | OptimizationLevel::Full => {
if engine.optimization_level != OptimizationLevel::None {
let pos = fn_def.body.position();
// Optimize the function body
@ -602,9 +598,7 @@ pub fn optimize_into_ast(
// {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val
Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => {
Stmt::Expr(val)
}
Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => Stmt::Expr(val),
// { return; } -> ()
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
Stmt::Expr(Box::new(Expr::Unit(pos)))
@ -613,7 +607,7 @@ pub fn optimize_into_ast(
stmt => stmt,
};
}
}
Arc::new(fn_def)
})
.collect(),

File diff suppressed because it is too large Load Diff

View File

@ -60,6 +60,8 @@ pub enum EvalAltResult {
ErrorDotExpr(String, Position),
/// Arithmetic error encountered. Wrapped value is the error message.
ErrorArithmetic(String, Position),
/// Call stack over maximum limit.
ErrorStackOverflow(Position),
/// Run-time error encountered. Wrapped value is the error message.
ErrorRuntime(String, Position),
/// Breaking out of loops - not an error if within a loop.
@ -105,6 +107,7 @@ impl EvalAltResult {
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorStackOverflow(_) => "Stack overflow",
Self::ErrorRuntime(_, _) => "Runtime error",
Self::ErrorLoopBreak(_) => "Break statement not inside a loop",
Self::Return(_, _) => "[Not Error] Function returns value",
@ -131,6 +134,7 @@ impl fmt::Display for EvalAltResult {
Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos),
Self::ErrorDotExpr(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", s, pos),
Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorRuntime(s, pos) => {
write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos)
}
@ -230,13 +234,16 @@ impl EvalAltResult {
| Self::ErrorMismatchOutputType(_, pos)
| Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos)
| Self::ErrorStackOverflow(pos)
| Self::ErrorRuntime(_, pos)
| Self::ErrorLoopBreak(pos)
| Self::Return(_, pos) => *pos,
}
}
pub(crate) fn set_position(&mut self, new_position: Position) {
/// Consume the current `EvalAltResult` and return a new one
/// with the specified `Position`.
pub(crate) fn set_position(mut self, new_position: Position) -> Self {
match self {
#[cfg(not(feature = "no_std"))]
Self::ErrorReadingScriptFile(_, _) => (),
@ -258,9 +265,12 @@ impl EvalAltResult {
| Self::ErrorMismatchOutputType(_, ref mut pos)
| Self::ErrorDotExpr(_, ref mut pos)
| Self::ErrorArithmetic(_, ref mut pos)
| Self::ErrorStackOverflow(ref mut pos)
| Self::ErrorRuntime(_, ref mut pos)
| Self::ErrorLoopBreak(ref mut pos)
| Self::Return(_, ref mut pos) => *pos = new_position,
}
self
}
}

View File

@ -10,32 +10,34 @@ use crate::stdlib::{
vec::Vec,
};
/// Type of a variable in the Scope.
/// Type of an entry in the Scope.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
pub enum VariableType {
/// Normal variable.
pub enum EntryType {
/// Normal value.
Normal,
/// Immutable constant value.
Constant,
}
/// An entry in the Scope.
pub struct ScopeEntry<'a> {
/// Name of the variable.
#[derive(Debug, Clone)]
pub struct Entry<'a> {
/// Name of the entry.
pub name: Cow<'a, str>,
/// Type of the variable.
pub var_type: VariableType,
/// Current value of the variable.
/// Type of the entry.
pub typ: EntryType,
/// Current value of the entry.
pub value: Dynamic,
/// A constant expression if the initial value matches one of the recognized types.
pub expr: Option<Expr>,
}
/// Information about a particular entry in the Scope.
pub(crate) struct ScopeSource<'a> {
#[derive(Debug, Hash, Copy, Clone)]
pub(crate) struct EntryRef<'a> {
pub name: &'a str,
pub idx: usize,
pub var_type: VariableType,
pub index: usize,
pub typ: EntryType,
}
/// A type containing information about the current scope.
@ -57,9 +59,9 @@ pub(crate) struct ScopeSource<'a> {
/// # }
/// ```
///
/// When searching for variables, newly-added variables are found before similarly-named but older variables,
/// allowing for automatic _shadowing_ of variables.
pub struct Scope<'a>(Vec<ScopeEntry<'a>>);
/// When searching for entries, newly-added entries are found before similarly-named but older entries,
/// allowing for automatic _shadowing_.
pub struct Scope<'a>(Vec<Entry<'a>>);
impl<'a> Scope<'a> {
/// Create a new Scope.
@ -72,24 +74,24 @@ impl<'a> Scope<'a> {
self.0.clear();
}
/// Get the number of variables inside the Scope.
/// Get the number of entries inside the Scope.
pub fn len(&self) -> usize {
self.0.len()
}
/// Add (push) a new variable to the Scope.
/// Is the Scope empty?
pub fn is_empty(&self) -> bool {
self.0.len() == 0
}
/// Add (push) a new entry to the Scope.
pub fn push<K: Into<Cow<'a, str>>, T: Any + Clone>(&mut self, name: K, value: T) {
let value = value.into_dynamic();
self.push_dynamic_value(name, EntryType::Normal, value.into_dynamic(), false);
}
// Map into constant expressions
//let (expr, value) = map_dynamic_to_expr(value, Position::none());
self.0.push(ScopeEntry {
name: name.into(),
var_type: VariableType::Normal,
value,
expr: None,
});
/// Add (push) a new `Dynamic` entry to the Scope.
pub fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) {
self.push_dynamic_value(name, EntryType::Normal, value, false);
}
/// Add (push) a new constant to the Scope.
@ -99,85 +101,75 @@ impl<'a> Scope<'a> {
/// However, in order to be used for optimization, constants must be in one of the recognized types:
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
pub fn push_constant<K: Into<Cow<'a, str>>, T: Any + Clone>(&mut self, name: K, value: T) {
let value = value.into_dynamic();
// Map into constant expressions
let (expr, value) = map_dynamic_to_expr(value, Position::none());
self.0.push(ScopeEntry {
name: name.into(),
var_type: VariableType::Constant,
value,
expr,
});
self.push_dynamic_value(name, EntryType::Constant, value.into_dynamic(), true);
}
/// Add (push) a new variable with a `Dynamic` value to the Scope.
pub(crate) fn push_dynamic<K: Into<Cow<'a, str>>>(
/// Add (push) a new constant with a `Dynamic` value to the Scope.
///
/// Constants are immutable and cannot be assigned to. Their values never change.
/// Constants propagation is a technique used to optimize an AST.
/// However, in order to be used for optimization, the `Dynamic` value must be in one of the
/// recognized types:
/// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`.
pub fn push_constant_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) {
self.push_dynamic_value(name, EntryType::Constant, value, true);
}
/// Add (push) a new entry with a `Dynamic` value to the Scope.
pub(crate) fn push_dynamic_value<K: Into<Cow<'a, str>>>(
&mut self,
name: K,
var_type: VariableType,
entry_type: EntryType,
value: Dynamic,
map_expr: bool,
) {
let (expr, value) = map_dynamic_to_expr(value, Position::none());
let (expr, value) = if map_expr {
map_dynamic_to_expr(value, Position::none())
} else {
(None, value)
};
self.0.push(ScopeEntry {
self.0.push(Entry {
name: name.into(),
var_type,
typ: entry_type,
value,
expr,
});
}
/// Remove (pop) the last variable from the Scope.
pub fn pop(&mut self) -> Option<(String, VariableType, Dynamic)> {
self.0.pop().map(
|ScopeEntry {
name,
var_type,
value,
..
}| (name.to_string(), var_type, value),
)
}
/// Truncate (rewind) the Scope to a previous size.
pub fn rewind(&mut self, size: usize) {
self.0.truncate(size);
}
/// Does the scope contain the variable?
/// Does the scope contain the entry?
pub fn contains(&self, key: &str) -> bool {
self.0
.iter()
.enumerate()
.rev() // Always search a Scope in reverse order
.find(|(_, ScopeEntry { name, .. })| name == key)
.is_some()
.any(|(_, Entry { name, .. })| name == key)
}
/// Find a variable in the Scope, starting from the last.
pub(crate) fn get(&self, key: &str) -> Option<(ScopeSource, Dynamic)> {
/// Find an entry in the Scope, starting from the last.
pub(crate) fn get(&self, key: &str) -> Option<(EntryRef, Dynamic)> {
self.0
.iter()
.enumerate()
.rev() // Always search a Scope in reverse order
.find(|(_, ScopeEntry { name, .. })| name == key)
.find(|(_, Entry { name, .. })| name == key)
.map(
|(
i,
ScopeEntry {
name,
var_type,
value,
..
Entry {
name, typ, value, ..
},
)| {
(
ScopeSource {
EntryRef {
name: name.as_ref(),
idx: i,
var_type: *var_type,
index: i,
typ: *typ,
},
value.clone(),
)
@ -185,54 +177,60 @@ impl<'a> Scope<'a> {
)
}
/// Get the value of a variable in the Scope, starting from the last.
/// Get the value of an entry in the Scope, starting from the last.
pub fn get_value<T: Any + Clone>(&self, key: &str) -> Option<T> {
self.0
.iter()
.enumerate()
.rev() // Always search a Scope in reverse order
.find(|(_, ScopeEntry { name, .. })| name == key)
.and_then(|(_, ScopeEntry { value, .. })| value.downcast_ref::<T>())
.find(|(_, Entry { name, .. })| name == key)
.and_then(|(_, Entry { value, .. })| value.downcast_ref::<T>())
.map(T::clone)
}
/// Get a mutable reference to a variable in the Scope.
pub(crate) fn get_mut(&mut self, name: &str, index: usize) -> &mut Dynamic {
let entry = self.0.get_mut(index).expect("invalid index in Scope");
/// Get a mutable reference to an entry in the Scope.
pub(crate) fn get_mut(&mut self, key: EntryRef) -> &mut Dynamic {
let entry = self.0.get_mut(key.index).expect("invalid index in Scope");
assert_eq!(entry.typ, key.typ, "entry type not matched");
// assert_ne!(
// entry.var_type,
// VariableType::Constant,
// "get mut of constant variable"
// entry.typ,
// EntryType::Constant,
// "get mut of constant entry"
// );
assert_eq!(entry.name, name, "incorrect key at Scope entry");
assert_eq!(entry.name, key.name, "incorrect key at Scope entry");
&mut entry.value
}
/// Get a mutable reference to a variable in the Scope and downcast it to a specific type
#[cfg(not(feature = "no_index"))]
pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, name: &str, index: usize) -> &mut T {
self.get_mut(name, index)
/// 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 variables in the Scope.
pub fn iter(&self) -> impl Iterator<Item = &ScopeEntry> {
/// 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
}
}
impl<'a, K> iter::Extend<(K, VariableType, Dynamic)> for Scope<'a>
impl Default for Scope<'_> {
fn default() -> Self {
Scope::new()
}
}
impl<'a, K> iter::Extend<(K, EntryType, Dynamic)> for Scope<'a>
where
K: Into<Cow<'a, str>>,
{
fn extend<T: IntoIterator<Item = (K, VariableType, Dynamic)>>(&mut self, iter: T) {
fn extend<T: IntoIterator<Item = (K, EntryType, Dynamic)>>(&mut self, iter: T) {
self.0
.extend(iter.into_iter().map(|(name, var_type, value)| ScopeEntry {
.extend(iter.into_iter().map(|(name, typ, value)| Entry {
name: name.into(),
var_type,
typ,
value,
expr: None,
}));

View File

@ -1,5 +1,22 @@
#![cfg(not(feature = "no_function"))]
use rhai::{Engine, EvalAltResult, INT};
use rhai::{Engine, EvalAltResult, ParseErrorType, INT};
#[test]
fn test_fn() -> Result<(), EvalAltResult> {
let engine = Engine::new();
// Expect duplicated parameters error
match engine
.compile("fn hello(x, x) { x }")
.expect_err("should be error")
.error_type()
{
ParseErrorType::FnDuplicateParam(f, p) if f == "hello" && p == "x" => (),
_ => assert!(false, "wrong error"),
}
Ok(())
}
#[test]
fn test_call_fn() -> Result<(), EvalAltResult> {

View File

@ -12,14 +12,64 @@ fn test_expressions() -> Result<(), EvalAltResult> {
engine.eval_expression_with_scope::<INT>(&mut scope, "2 + (x + 10) * 2")?,
42
);
assert_eq!(
engine.eval_expression_with_scope::<INT>(&mut scope, "if x > 0 { 42 } else { 123 }")?,
42
);
assert!(engine.eval_expression::<()>("40 + 2;").is_err());
assert!(engine.eval_expression::<()>("40 + { 2 }").is_err());
assert!(engine.eval_expression::<()>("x = 42").is_err());
assert!(engine.compile_expression("let x = 42").is_err());
assert!(engine
.eval_expression_with_scope::<INT>(&mut scope, "if x > 0 { 42 } else { 123 }")
.is_err());
Ok(())
}
/// This example taken from https://github.com/jonathandturner/rhai/issues/115
#[test]
fn test_expressions_eval() -> Result<(), EvalAltResult> {
#[derive(Debug, Clone)]
struct AGENT {
pub gender: String,
pub age: INT,
}
impl AGENT {
pub fn get_gender(&mut self) -> String {
self.gender.clone()
}
pub fn get_age(&mut self) -> INT {
self.age
}
}
// This is your agent
let my_agent = AGENT {
gender: "male".into(),
age: 42,
};
// Create the engine
let mut engine = Engine::new();
// Register your AGENT type
engine.register_type_with_name::<AGENT>("AGENT");
engine.register_get("gender", AGENT::get_gender);
engine.register_get("age", AGENT::get_age);
// Create your context, add the agent as a constant
let mut scope = Scope::new();
scope.push_constant("agent", my_agent);
// Evaluate the expression
let result: bool = engine.eval_expression_with_scope(
&mut scope,
r#"
agent.age > 10 && agent.gender == "male"
"#,
)?;
assert_eq!(result, true);
Ok(())
}

View File

@ -1,5 +1,7 @@
#![cfg(not(feature = "no_float"))]
use rhai::{Engine, EvalAltResult, RegisterFn};
use rhai::{Engine, EvalAltResult, RegisterFn, FLOAT};
const EPSILON: FLOAT = 0.000_000_000_1;
#[test]
fn test_float() -> Result<(), EvalAltResult> {
@ -13,7 +15,7 @@ fn test_float() -> Result<(), EvalAltResult> {
engine.eval::<bool>("let x = 0.0; let y = 1.0; x > y")?,
false
);
assert_eq!(engine.eval::<f64>("let x = 9.9999; x")?, 9.9999);
assert!((engine.eval::<FLOAT>("let x = 9.9999; x")? - 9.9999 as FLOAT).abs() < EPSILON);
Ok(())
}
@ -51,13 +53,12 @@ fn struct_with_float() -> Result<(), EvalAltResult> {
engine.register_fn("update", TestStruct::update);
engine.register_fn("new_ts", TestStruct::new);
assert_eq!(
engine.eval::<f64>("let ts = new_ts(); ts.update(); ts.x")?,
6.789
assert!(
(engine.eval::<FLOAT>("let ts = new_ts(); ts.update(); ts.x")? - 6.789).abs() < EPSILON
);
assert_eq!(
engine.eval::<f64>("let ts = new_ts(); ts.x = 10.1001; ts.x")?,
10.1001
assert!(
(engine.eval::<FLOAT>("let ts = new_ts(); ts.x = 10.1001; ts.x")? - 10.1001).abs()
< EPSILON
);
Ok(())

View File

@ -27,3 +27,21 @@ fn test_if() -> Result<(), EvalAltResult> {
Ok(())
}
#[test]
fn test_if_expr() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r"
let x = 42;
let y = 1 + if x > 40 { 100 } else { 0 } / x;
y
"
)?,
3
);
Ok(())
}

View File

@ -13,7 +13,7 @@ fn test_math() -> Result<(), EvalAltResult> {
#[cfg(not(feature = "only_i32"))]
assert_eq!(
engine.eval::<INT>("(-9223372036854775807).abs()")?,
9223372036854775807
9_223_372_036_854_775_807
);
#[cfg(feature = "only_i32")]

View File

@ -25,8 +25,10 @@ fn test_method_call() -> Result<(), EvalAltResult> {
engine.register_fn("new_ts", TestStruct::new);
let ts = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
assert_eq!(ts.x, 1001);
let ts = engine.eval::<TestStruct>("let x = new_ts(); update(x); x")?;
assert_eq!(ts.x, 1);
Ok(())
}

View File

@ -4,7 +4,7 @@ use rhai::{Engine, EvalAltResult, INT};
use rhai::FLOAT;
#[cfg(not(feature = "no_float"))]
const EPSILON: FLOAT = 0.0000000001;
const EPSILON: FLOAT = 0.000_000_000_1;
#[test]
fn test_power_of() -> Result<(), EvalAltResult> {
@ -16,11 +16,11 @@ fn test_power_of() -> Result<(), EvalAltResult> {
#[cfg(not(feature = "no_float"))]
{
assert!(
(engine.eval::<FLOAT>("2.2 ~ 3.3")? - 13.489468760533386 as FLOAT).abs() <= EPSILON
(engine.eval::<FLOAT>("2.2 ~ 3.3")? - 13.489_468_760_533_386 as FLOAT).abs() <= EPSILON
);
assert_eq!(engine.eval::<FLOAT>("2.0~-2.0")?, 0.25 as FLOAT);
assert_eq!(engine.eval::<FLOAT>("(-2.0~-2.0)")?, 0.25 as FLOAT);
assert_eq!(engine.eval::<FLOAT>("(-2.0~-2)")?, 0.25 as FLOAT);
assert!((engine.eval::<FLOAT>("2.0~-2.0")? - 0.25 as FLOAT).abs() < EPSILON);
assert!((engine.eval::<FLOAT>("(-2.0~-2.0)")? - 0.25 as FLOAT).abs() < EPSILON);
assert!((engine.eval::<FLOAT>("(-2.0~-2)")? - 0.25 as FLOAT).abs() < EPSILON);
assert_eq!(engine.eval::<INT>("4~3")?, 64);
}
@ -37,20 +37,18 @@ fn test_power_of_equals() -> Result<(), EvalAltResult> {
#[cfg(not(feature = "no_float"))]
{
assert!(
(engine.eval::<FLOAT>("let x = 2.2; x ~= 3.3; x")? - 13.489468760533386 as FLOAT).abs()
(engine.eval::<FLOAT>("let x = 2.2; x ~= 3.3; x")? - 13.489_468_760_533_386 as FLOAT)
.abs()
<= EPSILON
);
assert_eq!(
engine.eval::<FLOAT>("let x = 2.0; x ~= -2.0; x")?,
0.25 as FLOAT
assert!(
(engine.eval::<FLOAT>("let x = 2.0; x ~= -2.0; x")? - 0.25 as FLOAT).abs() < EPSILON
);
assert_eq!(
engine.eval::<FLOAT>("let x = -2.0; x ~= -2.0; x")?,
0.25 as FLOAT
assert!(
(engine.eval::<FLOAT>("let x = -2.0; x ~= -2.0; x")? - 0.25 as FLOAT).abs() < EPSILON
);
assert_eq!(
engine.eval::<FLOAT>("let x = -2.0; x ~= -2; x")?,
0.25 as FLOAT
assert!(
(engine.eval::<FLOAT>("let x = -2.0; x ~= -2; x")? - 0.25 as FLOAT).abs() < EPSILON
);
assert_eq!(engine.eval::<INT>("let x =4; x ~= 3; x")?, 64);
}

View File

@ -3,7 +3,7 @@ use rhai::{Engine, EvalAltResult};
#[test]
fn test_unit() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
assert_eq!(engine.eval::<()>("let x = (); x")?, ());
engine.eval::<()>("let x = (); x")?;
Ok(())
}
@ -17,6 +17,6 @@ fn test_unit_eq() -> Result<(), EvalAltResult> {
#[test]
fn test_unit_with_spaces() -> Result<(), EvalAltResult> {
let mut engine = Engine::new();
assert_eq!(engine.eval::<()>("let x = ( ); x")?, ());
engine.eval::<()>("let x = ( ); x")?;
Ok(())
}

View File

@ -9,7 +9,7 @@ fn test_var_scope() -> Result<(), EvalAltResult> {
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 9);
engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?;
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 12);
assert_eq!(engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?, ());
engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?;
assert_eq!(engine.eval_with_scope::<INT>(&mut scope, "x")?, 12);
Ok(())