Merge pull request #116 from schungx/master
Efficiency improvements and code refactor.
This commit is contained in:
commit
65c4639068
@ -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
134
README.md
@ -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`
|
||||
|
@ -1,8 +1,8 @@
|
||||
use rhai::{Engine, RegisterFn};
|
||||
use rhai::{Engine, RegisterFn, INT};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TestStruct {
|
||||
x: i64,
|
||||
x: INT,
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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(())
|
||||
|
@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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()));
|
||||
|
@ -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
|
||||
|
||||
|
38
src/api.rs
38
src/api.rs
@ -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(),
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
10
src/call.rs
10
src/call.rs
@ -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);
|
||||
|
527
src/engine.rs
527
src/engine.rs
File diff suppressed because it is too large
Load Diff
30
src/error.rs
30
src/error.rs
@ -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)?
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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(),
|
||||
|
877
src/parser.rs
877
src/parser.rs
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
}
|
||||
|
176
src/scope.rs
176
src/scope.rs
@ -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,
|
||||
}));
|
||||
|
@ -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> {
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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(())
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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")]
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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(())
|
||||
|
Loading…
Reference in New Issue
Block a user