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
|
#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
134
README.md
@ -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`
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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(())
|
||||||
|
@ -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!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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()));
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
38
src/api.rs
38
src/api.rs
@ -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(),
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
10
src/call.rs
10
src/call.rs
@ -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);
|
||||||
|
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),
|
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)?
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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(),
|
||||||
|
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),
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
176
src/scope.rs
176
src/scope.rs
@ -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,
|
||||||
}));
|
}));
|
||||||
|
@ -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> {
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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(())
|
||||||
|
@ -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(())
|
||||||
|
}
|
||||||
|
@ -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")]
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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(())
|
||||||
|
Loading…
Reference in New Issue
Block a user