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 #panic = 'abort' # remove stack backtrace for no-std
[dependencies.libm] [dependencies.libm]
version = "0.2.1" version = "*"
optional = true optional = true
[dependencies.core-error] [dependencies.core-error]
version = "0.0.1-rc4" version = "*"
features = ["alloc"] features = ["alloc"]
optional = true optional = true
[dependencies.hashbrown] [dependencies.hashbrown]
version = "0.7.1" version = "*"
default-features = false default-features = false
features = ["ahash", "nightly", "inline-more"] features = ["ahash", "nightly", "inline-more"]
optional = true optional = true
[dependencies.ahash] [dependencies.ahash]
version = "0.3.2" version = "*"
default-features = false default-features = false
optional = true 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" rhai = "0.11.0"
``` ```
or simply: Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/):
```toml ```toml
[dependencies] [dependencies]
rhai = "*" 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`. 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: 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) * 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 Examples
@ -252,7 +258,7 @@ let result = engine.eval_expression_with_scope::<i64>(&mut scope, "if x { 42 } e
Values and types Values and types
---------------- ----------------
[`type_of`]: #values-and-types [`type_of()`]: #values-and-types
The following primitive types are supported natively: 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 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 [`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. with a special "pretty-print" name, [`type_of()`] will return that name instead.
```rust ```rust
engine.register_type::<TestStruct>(); 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 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. 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), Internally Rhai strings are stored as UTF-8 just like Rust (they _are_ Rust `String`s!), but there are major differences.
in the Rhai language they can be considered a stream of Unicode characters, and can be directly indexed (unlike Rust). In Rhai a string is the same as an array 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. 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`]). 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. 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. 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. Arrays are disabled via the [`no_index`] feature.
@ -1075,17 +1097,13 @@ my_str += 12345;
my_str == "abcABC12345" my_str == "abcABC12345"
``` ```
If statements `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.
```rust ```rust
if true { if foo(x) {
print("It's true!"); print("It's true!");
} else if true { } else if bar == baz {
print("It's true again!"); print("It's true again!");
} else if ... { } else if ... {
: :
@ -1094,13 +1112,28 @@ if true {
} else { } else {
print("It's finally false!"); 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!"); if (decision) print("I've decided!");
// ^ syntax error, expecting '{' in statement block // ^ 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 ```rust
let x = 10; let x = 10;
@ -1112,8 +1145,8 @@ while x > 0 {
} }
``` ```
Infinite loops Infinite `loop`
-------------- ---------------
```rust ```rust
let x = 10; 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. 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 ```rust
return; // equivalent to return (); return; // equivalent to return ();
@ -1155,8 +1188,8 @@ return; // equivalent to return ();
return 123 + 456; // returns 579 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. 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. To deliberately return an error during an evaluation, use the `throw` keyword.
@ -1197,8 +1230,10 @@ fn add(x, y) {
print(add(2, 3)); 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 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 ```rust
fn add(x, y) { fn add(x, y) {
@ -1213,6 +1248,16 @@ print(add(2, 3)); // prints 5
print(add2(42)); // prints 44 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 ### Passing arguments by value
Functions defined in script always take [`Dynamic`] parameters (i.e. the parameter can be of any type). 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; let x = 500;
x.change(); // desugars to change(x) x.change(); // de-sugars to change(x)
x == 500; // 'x' is NOT changed! x == 500; // 'x' is NOT changed!
``` ```
@ -1251,31 +1296,34 @@ fn do_addition(x) {
### Functions overloading ### Functions overloading
Functions can be _overloaded_ based on the _number_ of parameters (but not parameter _types_, since all parameters are the same type - [`Dynamic`]). Functions can be _overloaded_ and are resolved purely upon the function's _name_ and the _number_ of parameters
New definitions of the same name and number of parameters overwrite previous definitions. (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 ```rust
fn abc(x,y,z) { print("Three!!! " + x + "," + y + "," + z) } fn foo(x,y,z) { print("Three!!! " + x + "," + y + "," + z) }
fn abc(x) { print("One! " + x) } fn foo(x) { print("One! " + x) }
fn abc(x,y) { print("Two! " + x + "," + y) } fn foo(x,y) { print("Two! " + x + "," + y) }
fn abc() { print("None.") } fn foo() { print("None.") }
fn abc(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition fn foo(x) { print("HA! NEW ONE! " + x) } // overwrites previous definition
abc(1,2,3); // prints "Three!!! 1,2,3" foo(1,2,3); // prints "Three!!! 1,2,3"
abc(42); // prints "HA! NEW ONE! 42" foo(42); // prints "HA! NEW ONE! 42"
abc(1,2); // prints "Two!! 1,2" foo(1,2); // prints "Two!! 1,2"
abc(); // prints "None." foo(); // prints "None."
``` ```
Members and methods 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 ```rust
let a = new_ts(); // constructor function let a = new_ts(); // constructor function
a.field = 500; // property access a.field = 500; // property access
a.update(); // method call 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` `print` and `debug`

View File

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

View File

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

View File

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

View File

@ -1,12 +1,16 @@
#![cfg_attr(feature = "no_std", no_std)] #![cfg_attr(feature = "no_std", no_std)]
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult, INT};
fn main() -> Result<(), EvalAltResult> { fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); 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); assert_eq!(result, 42);
Ok(()) 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"))] #[cfg(not(feature = "no_optimize"))]
use rhai::OptimizationLevel; use rhai::OptimizationLevel;
@ -13,7 +13,7 @@ fn print_error(input: &str, err: EvalAltResult) {
iter::repeat(pad).take(len).collect::<String>() 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 { let line_no = if lines.len() > 1 {
match err.position() { match err.position() {
@ -26,27 +26,18 @@ fn print_error(input: &str, err: EvalAltResult) {
}; };
// Print error // Print error
let pos_text = format!(" ({})", err.position()); let pos = err.position();
let pos_text = format!(" ({})", pos);
match err.position() { let pos = if pos.is_eof() {
p if p.is_eof() => {
// EOF
let last = lines[lines.len() - 1]; let last = lines[lines.len() - 1];
println!("{}{}", line_no, last); Position::new(lines.len(), last.len() + 1)
} else {
let err_text = match err { pos
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
_ => err.to_string(),
}; };
println!( match pos {
"{}^ {}", p if p.is_eof() => panic!("should not be EOF"),
padding(" ", line_no.len() + last.len() - 1),
err_text.replace(&pos_text, "")
);
}
p if p.is_none() => { p if p.is_none() => {
// No position // No position
println!("{}", err); println!("{}", err);
@ -59,7 +50,7 @@ fn print_error(input: &str, err: EvalAltResult) {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err) format!("Runtime error: {}", err)
} }
_ => err.to_string(), err => err.to_string(),
}; };
println!( println!(
@ -161,7 +152,7 @@ fn main() {
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
{ {
engine.set_optimization_level(OptimizationLevel::Full); 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); engine.set_optimization_level(OptimizationLevel::None);
} }
@ -178,9 +169,9 @@ fn main() {
}) })
}) })
{ {
println!(""); println!();
print_error(&input, err); 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> { fn main() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
@ -6,7 +6,7 @@ fn main() -> Result<(), EvalAltResult> {
engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; 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); println!("result: {}", result);

View File

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

View File

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

View File

@ -57,7 +57,7 @@ macro_rules! reg_op_result1 {
impl Engine<'_> { impl Engine<'_> {
/// Register the core built-in library. /// Register the core built-in library.
pub(crate) fn register_core_lib(&mut self) { pub(crate) fn register_core_lib(&mut self) {
/// Checked add // Checked add
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, EvalAltResult> { fn add<T: Display + CheckedAdd>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_add(&y).ok_or_else(|| { x.checked_add(&y).ok_or_else(|| {
@ -67,7 +67,7 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked subtract // Checked subtract
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, EvalAltResult> { fn sub<T: Display + CheckedSub>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_sub(&y).ok_or_else(|| { x.checked_sub(&y).ok_or_else(|| {
@ -77,7 +77,7 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked multiply // Checked multiply
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, EvalAltResult> { fn mul<T: Display + CheckedMul>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_mul(&y).ok_or_else(|| { x.checked_mul(&y).ok_or_else(|| {
@ -87,7 +87,7 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked divide // Checked divide
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn div<T>(x: T, y: T) -> Result<T, EvalAltResult> fn div<T>(x: T, y: T) -> Result<T, EvalAltResult>
where 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"))] #[cfg(not(feature = "unchecked"))]
fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, EvalAltResult> { fn neg<T: Display + CheckedNeg>(x: T) -> Result<T, EvalAltResult> {
x.checked_neg().ok_or_else(|| { x.checked_neg().ok_or_else(|| {
@ -118,7 +118,7 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked absolute // Checked absolute
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn abs<T: Display + CheckedNeg + PartialOrd + Zero>(x: T) -> Result<T, EvalAltResult> { 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 // 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")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output { fn add_u<T: Add>(x: T, y: T) -> <T as Add>::Output {
x + y x + y
} }
/// Unchecked subtract - may panic on underflow // Unchecked subtract - may panic on underflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output { fn sub_u<T: Sub>(x: T, y: T) -> <T as Sub>::Output {
x - y x - y
} }
/// Unchecked multiply - may panic on overflow // Unchecked multiply - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output { fn mul_u<T: Mul>(x: T, y: T) -> <T as Mul>::Output {
x * y 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")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output { fn div_u<T: Div>(x: T, y: T) -> <T as Div>::Output {
x / y x / y
} }
/// Unchecked negative - may panic on overflow // Unchecked negative - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output { fn neg_u<T: Neg>(x: T) -> <T as Neg>::Output {
-x -x
} }
/// Unchecked absolute - may panic on overflow // Unchecked absolute - may panic on overflow
#[cfg(any(feature = "unchecked", not(feature = "no_float")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn abs_u<T>(x: T) -> <T as Neg>::Output fn abs_u<T>(x: T) -> <T as Neg>::Output
where where
@ -174,7 +174,6 @@ impl Engine<'_> {
} }
// Comparison operators // Comparison operators
fn lt<T: PartialOrd>(x: T, y: T) -> bool { fn lt<T: PartialOrd>(x: T, y: T) -> bool {
x < y x < y
} }
@ -195,7 +194,6 @@ impl Engine<'_> {
} }
// Logic operators // Logic operators
fn and(x: bool, y: bool) -> bool { fn and(x: bool, y: bool) -> bool {
x && y x && y
} }
@ -207,7 +205,6 @@ impl Engine<'_> {
} }
// Bit operators // Bit operators
fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output { fn binary_and<T: BitAnd>(x: T, y: T) -> <T as BitAnd>::Output {
x & y x & y
} }
@ -218,7 +215,7 @@ impl Engine<'_> {
x ^ y x ^ y
} }
/// Checked left-shift // Checked left-shift
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, EvalAltResult> { fn shl<T: Display + CheckedShl>(x: T, y: INT) -> Result<T, EvalAltResult> {
// Cannot shift by a negative number of bits // Cannot shift by a negative number of bits
@ -236,7 +233,7 @@ impl Engine<'_> {
) )
}) })
} }
/// Checked right-shift // Checked right-shift
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, EvalAltResult> { fn shr<T: Display + CheckedShr>(x: T, y: INT) -> Result<T, EvalAltResult> {
// Cannot shift by a negative number of bits // 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")] #[cfg(feature = "unchecked")]
fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output { fn shl_u<T: Shl<T>>(x: T, y: T) -> <T as Shl<T>>::Output {
x.shl(y) 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")] #[cfg(feature = "unchecked")]
fn shr_u<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output { fn shr_u<T: Shr<T>>(x: T, y: T) -> <T as Shr<T>>::Output {
x.shr(y) x.shr(y)
} }
/// Checked modulo // Checked modulo
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, EvalAltResult> { fn modulo<T: Display + CheckedRem>(x: T, y: T) -> Result<T, EvalAltResult> {
x.checked_rem(&y).ok_or_else(|| { 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")))] #[cfg(any(feature = "unchecked", not(feature = "no_float")))]
fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output { fn modulo_u<T: Rem>(x: T, y: T) -> <T as Rem>::Output {
x % y x % y
} }
/// Checked power // Checked power
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn pow_i_i(x: INT, y: INT) -> Result<INT, EvalAltResult> { fn pow_i_i(x: INT, y: INT) -> Result<INT, EvalAltResult> {
#[cfg(not(feature = "only_i32"))] #[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")] #[cfg(feature = "unchecked")]
fn pow_i_i_u(x: INT, y: INT) -> INT { fn pow_i_i_u(x: INT, y: INT) -> INT {
x.pow(y as u32) x.pow(y as u32)
} }
/// Floating-point power - always well-defined // Floating-point power - always well-defined
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT { fn pow_f_f(x: FLOAT, y: FLOAT) -> FLOAT {
x.powf(y) x.powf(y)
} }
/// Checked power // Checked power
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_i(x: FLOAT, y: INT) -> Result<FLOAT, EvalAltResult> { fn pow_f_i(x: FLOAT, y: INT) -> Result<FLOAT, EvalAltResult> {
@ -345,7 +342,7 @@ impl Engine<'_> {
Ok(x.powi(y as i32)) 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(feature = "unchecked")]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
fn pow_f_i_u(x: FLOAT, y: INT) -> 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, "||", or, bool);
//reg_op!(self, "&&", and, 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); reg_iterator::<INT>(self);
self.register_fn("range", |i1: INT, i2: INT| (i1..i2)); self.register_fn("range", |i1: INT, i2: INT| (i1..i2));
@ -634,15 +628,15 @@ impl Engine<'_> {
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
macro_rules! reg_range { macro_rules! reg_range {
($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( ($self:expr, $x:expr, $( $y:ty ),*) => (
$( $(
reg_iterator::<$y>(self); 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); 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); 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_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
@ -891,9 +885,7 @@ impl Engine<'_> {
let trimmed = s.trim(); let trimmed = s.trim();
if trimmed.len() < s.len() { if trimmed.len() < s.len() {
let chars: Vec<_> = trimmed.chars().collect(); *s = trimmed.to_string();
s.clear();
chars.iter().for_each(|&ch| s.push(ch));
} }
}); });
} }

View File

@ -11,11 +11,15 @@ use crate::engine::Array;
use crate::stdlib::{string::String, vec, vec::Vec}; use crate::stdlib::{string::String, vec, vec::Vec};
/// Trait that represent arguments to a function call. /// Trait that represent arguments to a function call.
/// Any data type that can be converted into a `Vec` of `Dynamic` values can be used
/// as arguments to a function call.
pub trait FuncArgs { pub trait FuncArgs {
/// Convert to a `Vec` of `Dynamic` arguments. /// Convert to a `Vec` of `Dynamic` arguments.
fn into_vec(self) -> Vec<Dynamic>; 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 { macro_rules! impl_std_args {
($($p:ty),*) => { ($($p:ty),*) => {
$( $(
@ -43,6 +47,8 @@ impl_std_args!(INT);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl_std_args!(f32, f64); impl_std_args!(f32, f64);
/// Macro to implement `FuncArgs` for tuples of standard types (each can be
/// converted into `Dynamic`).
macro_rules! impl_args { macro_rules! impl_args {
($($p:ident),*) => { ($($p:ident),*) => {
impl<$($p: Any + Clone),*> FuncArgs for ($($p,)*) impl<$($p: Any + Clone),*> FuncArgs for ($($p,)*)
@ -69,5 +75,5 @@ macro_rules! impl_args {
}; };
} }
#[cfg_attr(rustfmt, rustfmt_skip)] #[rustfmt::skip]
impl_args!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T); 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), MalformedNumber(String),
/// An character literal is in an invalid format. /// An character literal is in an invalid format.
MalformedChar(String), MalformedChar(String),
/// Error in the script text.
InputError(String),
/// An identifier is in an invalid format. /// An identifier is in an invalid format.
MalformedIdentifier(String), MalformedIdentifier(String),
} }
@ -35,7 +33,6 @@ impl fmt::Display for LexError {
Self::MalformedIdentifier(s) => { Self::MalformedIdentifier(s) => {
write!(f, "Variable name is not in a legal format: '{}'", 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"), 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. /// A function definition is missing the parameters list. Wrapped value is the function name.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
FnMissingParams(String), 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. /// A function definition is missing the body. Wrapped value is the function name.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
FnMissingBody(String), FnMissingBody(String),
@ -98,16 +98,23 @@ pub enum ParseErrorType {
LoopBreak, 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. /// Error when parsing a script.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct ParseError(pub(crate) ParseErrorType, pub(crate) Position); pub struct ParseError(pub(crate) ParseErrorType, pub(crate) Position);
impl ParseError { impl ParseError {
/// Create a new `ParseError`.
pub(crate) fn new(err: ParseErrorType, pos: Position) -> Self {
Self(err, pos)
}
/// Get the parse error. /// Get the parse error.
pub fn error_type(&self) -> &ParseErrorType { pub fn error_type(&self) -> &ParseErrorType {
&self.0 &self.0
@ -142,6 +149,8 @@ impl ParseError {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration", ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
#[cfg(not(feature = "no_function"))] #[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", ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration",
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another 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)? 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) => { ParseErrorType::MissingRightParen(ref s) | ParseErrorType::MissingRightBrace(ref s) => {
write!(f, "{} for {}", self.desc(), s)? write!(f, "{} for {}", self.desc(), s)?
} }

View File

@ -80,7 +80,8 @@ pub trait RegisterResultFn<FN, ARGS, RET> {
/// // Normal function /// // Normal function
/// fn div(x: i64, y: i64) -> Result<i64, EvalAltResult> { /// fn div(x: i64, y: i64) -> Result<i64, EvalAltResult> {
/// if y == 0 { /// 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 { /// } else {
/// Ok(x / y) /// Ok(x / y)
/// } /// }
@ -97,9 +98,30 @@ pub trait RegisterResultFn<FN, ARGS, RET> {
fn register_result_fn(&mut self, name: &str, f: FN); fn register_result_fn(&mut self, name: &str, f: FN);
} }
pub struct Ref<A>(A); // These types are used to build a unique _marker_ tuple type for each combination
pub struct Mut<A>(A); // 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 { macro_rules! count_args {
() => { 0_usize }; () => { 0_usize };
( $head:ident $($tail:ident)* ) => { 1_usize + count_args!($($tail)*) }; ( $head:ident $($tail:ident)* ) => { 1_usize + count_args!($($tail)*) };
@ -110,6 +132,10 @@ macro_rules! def_register {
def_register!(imp); def_register!(imp);
}; };
(imp $($par:ident => $mark:ty => $param:ty => $clone:expr),*) => { (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< impl<
$($par: Any + Clone,)* $($par: Any + Clone,)*
FN: Fn($($param),*) -> RET + 'static, FN: Fn($($param),*) -> RET + 'static,
@ -119,7 +145,7 @@ macro_rules! def_register {
fn register_fn(&mut self, name: &str, f: FN) { fn register_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); let fn_name = name.to_string();
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. // Check for length at the beginning to avoid per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
@ -128,7 +154,7 @@ macro_rules! def_register {
} }
#[allow(unused_variables, unused_mut)] #[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 // Downcast every element, return in case of a type mismatch
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); 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) { fn register_dynamic_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); 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. // Check for length at the beginning to avoid per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
@ -160,7 +186,7 @@ macro_rules! def_register {
} }
#[allow(unused_variables, unused_mut)] #[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 // Downcast every element, return in case of a type mismatch
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); 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) { fn register_result_fn(&mut self, name: &str, f: FN) {
let fn_name = name.to_string(); 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. // Check for length at the beginning to avoid per-element bound checks.
const NUM_ARGS: usize = count_args!($($par)*); const NUM_ARGS: usize = count_args!($($par)*);
@ -192,7 +218,7 @@ macro_rules! def_register {
} }
#[allow(unused_variables, unused_mut)] #[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 // Downcast every element, return in case of a type mismatch
let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); 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 // Call the user-supplied function using ($clone) to
// potentially clone the value, otherwise pass the reference. // potentially clone the value, otherwise pass the reference.
f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic).map_err(|mut err| { f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic)
err.set_position(pos); .map_err(|err| err.set_position(pos))
err
})
}; };
self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); 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)*) => { ($p0:ident $(, $p:ident)*) => {
def_register!(imp $p0 => $p0 => $p0 => Clone::clone $(, $p => $p => $p => Clone::clone)*); 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 => identity $(, $p => $p => $p => Clone::clone)*);
def_register!(imp $p0 => Mut<$p0> => &mut $p0 => |x| { x } $(, $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),*); def_register!($($p),*);
}; };
@ -224,5 +252,5 @@ macro_rules! def_register {
// }; // };
} }
#[cfg_attr(rustfmt, rustfmt_skip)] #[rustfmt::skip]
def_register!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T); 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 fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn};
pub use parser::{Position, AST, INT}; pub use parser::{Position, AST, INT};
pub use result::EvalAltResult; pub use result::EvalAltResult;
pub use scope::{Scope, ScopeEntry, VariableType}; pub use scope::Scope;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub use engine::Array; pub use engine::Array;

View File

@ -2,11 +2,10 @@
use crate::any::{Any, Dynamic}; use crate::any::{Any, Dynamic};
use crate::engine::{ use crate::engine::{
Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, Engine, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF,
KEYWORD_TYPE_OF,
}; };
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; 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::{ use crate::stdlib::{
boxed::Box, 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 // Optimize each statement in the block
let mut result: Vec<_> = block let mut result: Vec<_> = block
.into_iter() .into_iter()
.map(|stmt| { .map(|stmt| match stmt {
if let Stmt::Const(name, value, pos) = stmt {
// Add constant into the state // Add constant into the state
Stmt::Const(name, value, pos) => {
state.push_constant(&name, *value); state.push_constant(&name, *value);
state.set_dirty(); state.set_dirty();
Stmt::Noop(pos) // No need to keep constants 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(); .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 && args.iter().all(|expr| expr.is_constant()) // all arguments are constants
=> { => {
// First search in script-defined functions (can override built-in) // 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 // 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); 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 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()` // Save the typename of the first argument if it is `type_of()`
// This is to avoid `call_args` being passed into the closure // This is to avoid `call_args` being passed into the closure
@ -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(|| { r.or_else(|| {
if !arg_for_type_of.is_empty() { if !arg_for_type_of.is_empty() {
// Handle `type_of()` // 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 // Add constants from the scope into the state
scope scope
.iter() .iter()
.filter(|ScopeEntry { var_type, expr, .. }| { .filter(|ScopeEntry { typ, expr, .. }| {
// Get all the constants with definite constant expressions // 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) && expr.as_ref().map(Expr::is_constant).unwrap_or(false)
}) })
.for_each(|ScopeEntry { name, expr, .. }| { .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 // Add back the last statement unless it is a lone No-op
if let Some(stmt) = last_stmt { 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); result.push(stmt);
} }
} }
@ -591,9 +589,7 @@ pub fn optimize_into_ast(
functions functions
.into_iter() .into_iter()
.map(|mut fn_def| { .map(|mut fn_def| {
match engine.optimization_level { if engine.optimization_level != OptimizationLevel::None {
OptimizationLevel::None => (),
OptimizationLevel::Simple | OptimizationLevel::Full => {
let pos = fn_def.body.position(); let pos = fn_def.body.position();
// Optimize the function body // Optimize the function body
@ -602,9 +598,7 @@ pub fn optimize_into_ast(
// {} -> Noop // {} -> Noop
fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
// { return val; } -> val // { return val; } -> val
Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => { Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => Stmt::Expr(val),
Stmt::Expr(val)
}
// { return; } -> () // { return; } -> ()
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
Stmt::Expr(Box::new(Expr::Unit(pos))) Stmt::Expr(Box::new(Expr::Unit(pos)))
@ -613,7 +607,7 @@ pub fn optimize_into_ast(
stmt => stmt, stmt => stmt,
}; };
} }
}
Arc::new(fn_def) Arc::new(fn_def)
}) })
.collect(), .collect(),

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -10,32 +10,34 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
/// Type of a variable in the Scope. /// Type of an entry in the Scope.
#[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)]
pub enum VariableType { pub enum EntryType {
/// Normal variable. /// Normal value.
Normal, Normal,
/// Immutable constant value. /// Immutable constant value.
Constant, Constant,
} }
/// An entry in the Scope. /// An entry in the Scope.
pub struct ScopeEntry<'a> { #[derive(Debug, Clone)]
/// Name of the variable. pub struct Entry<'a> {
/// Name of the entry.
pub name: Cow<'a, str>, pub name: Cow<'a, str>,
/// Type of the variable. /// Type of the entry.
pub var_type: VariableType, pub typ: EntryType,
/// Current value of the variable. /// Current value of the entry.
pub value: Dynamic, pub value: Dynamic,
/// A constant expression if the initial value matches one of the recognized types. /// A constant expression if the initial value matches one of the recognized types.
pub expr: Option<Expr>, pub expr: Option<Expr>,
} }
/// Information about a particular entry in the Scope. /// 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 name: &'a str,
pub idx: usize, pub index: usize,
pub var_type: VariableType, pub typ: EntryType,
} }
/// A type containing information about the current scope. /// 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, /// When searching for entries, newly-added entries are found before similarly-named but older entries,
/// allowing for automatic _shadowing_ of variables. /// allowing for automatic _shadowing_.
pub struct Scope<'a>(Vec<ScopeEntry<'a>>); pub struct Scope<'a>(Vec<Entry<'a>>);
impl<'a> Scope<'a> { impl<'a> Scope<'a> {
/// Create a new Scope. /// Create a new Scope.
@ -72,24 +74,24 @@ impl<'a> Scope<'a> {
self.0.clear(); self.0.clear();
} }
/// Get the number of variables inside the Scope. /// Get the number of entries inside the Scope.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.0.len() 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) { 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 /// Add (push) a new `Dynamic` entry to the Scope.
//let (expr, value) = map_dynamic_to_expr(value, Position::none()); pub fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, name: K, value: Dynamic) {
self.push_dynamic_value(name, EntryType::Normal, value, false);
self.0.push(ScopeEntry {
name: name.into(),
var_type: VariableType::Normal,
value,
expr: None,
});
} }
/// Add (push) a new constant to the Scope. /// 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: /// 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`. /// `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) { pub fn push_constant<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::Constant, value.into_dynamic(), true);
// 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,
});
} }
/// Add (push) a new variable with a `Dynamic` value to the Scope. /// Add (push) a new constant with a `Dynamic` value to the Scope.
pub(crate) fn push_dynamic<K: Into<Cow<'a, str>>>( ///
/// 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, &mut self,
name: K, name: K,
var_type: VariableType, entry_type: EntryType,
value: Dynamic, 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(), name: name.into(),
var_type, typ: entry_type,
value, value,
expr, 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. /// Truncate (rewind) the Scope to a previous size.
pub fn rewind(&mut self, size: usize) { pub fn rewind(&mut self, size: usize) {
self.0.truncate(size); self.0.truncate(size);
} }
/// Does the scope contain the variable? /// Does the scope contain the entry?
pub fn contains(&self, key: &str) -> bool { pub fn contains(&self, key: &str) -> bool {
self.0 self.0
.iter() .iter()
.enumerate() .enumerate()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.find(|(_, ScopeEntry { name, .. })| name == key) .any(|(_, Entry { name, .. })| name == key)
.is_some()
} }
/// Find a variable in the Scope, starting from the last. /// Find an entry in the Scope, starting from the last.
pub(crate) fn get(&self, key: &str) -> Option<(ScopeSource, Dynamic)> { pub(crate) fn get(&self, key: &str) -> Option<(EntryRef, Dynamic)> {
self.0 self.0
.iter() .iter()
.enumerate() .enumerate()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.find(|(_, ScopeEntry { name, .. })| name == key) .find(|(_, Entry { name, .. })| name == key)
.map( .map(
|( |(
i, i,
ScopeEntry { Entry {
name, name, typ, value, ..
var_type,
value,
..
}, },
)| { )| {
( (
ScopeSource { EntryRef {
name: name.as_ref(), name: name.as_ref(),
idx: i, index: i,
var_type: *var_type, typ: *typ,
}, },
value.clone(), 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> { pub fn get_value<T: Any + Clone>(&self, key: &str) -> Option<T> {
self.0 self.0
.iter() .iter()
.enumerate() .enumerate()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.find(|(_, ScopeEntry { name, .. })| name == key) .find(|(_, Entry { name, .. })| name == key)
.and_then(|(_, ScopeEntry { value, .. })| value.downcast_ref::<T>()) .and_then(|(_, Entry { value, .. })| value.downcast_ref::<T>())
.map(T::clone) .map(T::clone)
} }
/// Get a mutable reference to a variable in the Scope. /// Get a mutable reference to an entry in the Scope.
pub(crate) fn get_mut(&mut self, name: &str, index: usize) -> &mut Dynamic { pub(crate) fn get_mut(&mut self, key: EntryRef) -> &mut Dynamic {
let entry = self.0.get_mut(index).expect("invalid index in Scope"); 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!( // assert_ne!(
// entry.var_type, // entry.typ,
// VariableType::Constant, // EntryType::Constant,
// "get mut of constant variable" // "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 &mut entry.value
} }
/// Get a mutable reference to a variable in the Scope and downcast it to a specific type /// Get a mutable reference to an entry 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, key: EntryRef) -> &mut T {
pub(crate) fn get_mut_by_type<T: Any + Clone>(&mut self, name: &str, index: usize) -> &mut T { self.get_mut(key)
self.get_mut(name, index)
.downcast_mut::<T>() .downcast_mut::<T>()
.expect("wrong type cast") .expect("wrong type cast")
} }
/// Get an iterator to variables in the Scope. /// Get an iterator to entries in the Scope.
pub fn iter(&self) -> impl Iterator<Item = &ScopeEntry> { pub fn iter(&self) -> impl Iterator<Item = &Entry> {
self.0.iter().rev() // Always search a Scope in reverse order self.0.iter().rev() // Always search a Scope in reverse order
} }
} }
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 where
K: Into<Cow<'a, str>>, 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 self.0
.extend(iter.into_iter().map(|(name, var_type, value)| ScopeEntry { .extend(iter.into_iter().map(|(name, typ, value)| Entry {
name: name.into(), name: name.into(),
var_type, typ,
value, value,
expr: None, expr: None,
})); }));

View File

@ -1,5 +1,22 @@
#![cfg(not(feature = "no_function"))] #![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] #[test]
fn test_call_fn() -> Result<(), EvalAltResult> { 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")?, engine.eval_expression_with_scope::<INT>(&mut scope, "2 + (x + 10) * 2")?,
42 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::<()>("40 + { 2 }").is_err()); assert!(engine.eval_expression::<()>("40 + { 2 }").is_err());
assert!(engine.eval_expression::<()>("x = 42").is_err()); assert!(engine.eval_expression::<()>("x = 42").is_err());
assert!(engine.compile_expression("let 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 }") Ok(())
.is_err()); }
/// 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(()) Ok(())
} }

View File

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

View File

@ -27,3 +27,21 @@ fn test_if() -> Result<(), EvalAltResult> {
Ok(()) 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"))] #[cfg(not(feature = "only_i32"))]
assert_eq!( assert_eq!(
engine.eval::<INT>("(-9223372036854775807).abs()")?, engine.eval::<INT>("(-9223372036854775807).abs()")?,
9223372036854775807 9_223_372_036_854_775_807
); );
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]

View File

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

View File

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

View File

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

View File

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