commit
8cb8b89474
@ -7,13 +7,14 @@ description = "Embedded scripting for Rust"
|
|||||||
homepage = "https://github.com/jonathandturner/rhai"
|
homepage = "https://github.com/jonathandturner/rhai"
|
||||||
repository = "https://github.com/jonathandturner/rhai"
|
repository = "https://github.com/jonathandturner/rhai"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
include = [
|
include = [
|
||||||
"**/*.rs",
|
"**/*.rs",
|
||||||
"scripts/*.rhai",
|
"scripts/*.rhai",
|
||||||
"Cargo.toml"
|
"Cargo.toml"
|
||||||
]
|
]
|
||||||
keywords = [ "scripting" ]
|
keywords = [ "scripting" ]
|
||||||
|
categories = [ "no-std", "embedded", "parser-implementations" ]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
num-traits = "0.2.11"
|
num-traits = "0.2.11"
|
||||||
|
45
README.md
45
README.md
@ -1,6 +1,13 @@
|
|||||||
Rhai - Embedded Scripting for Rust
|
Rhai - Embedded Scripting for Rust
|
||||||
=================================
|
=================================
|
||||||
|
|
||||||
|
![GitHub last commit](https://img.shields.io/github/last-commit/jonathandturner/rhai)
|
||||||
|
[![Travis (.org)](https://img.shields.io/travis/jonathandturner/rhai)](http://travis-ci.org/jonathandturner/rhai)
|
||||||
|
[![license](https://img.shields.io/github/license/jonathandturner/rhai)](https://github.com/license/jonathandturner/rhai)
|
||||||
|
[![crates.io](https://img.shields.io/crates/v/rhai.svg)](https::/crates.io/crates/rhai/)
|
||||||
|
![crates.io](https://img.shields.io/crates/d/rhai)
|
||||||
|
[![API Docs](https://docs.rs/rhai/badge.svg)](https://docs.rs/rhai/)
|
||||||
|
|
||||||
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application.
|
Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application.
|
||||||
|
|
||||||
Rhai's current features set:
|
Rhai's current features set:
|
||||||
@ -176,10 +183,6 @@ let result = engine.eval_file::<i64>("hello_world.rhai".into())?; // 'eval
|
|||||||
To repeatedly evaluate a script, _compile_ it first into an AST (abstract syntax tree) form:
|
To repeatedly evaluate a script, _compile_ it first into an AST (abstract syntax tree) form:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::Engine;
|
|
||||||
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
// Compile to an AST and store it for later evaluations
|
// Compile to an AST and store it for later evaluations
|
||||||
let ast = engine.compile("40 + 2")?;
|
let ast = engine.compile("40 + 2")?;
|
||||||
|
|
||||||
@ -193,20 +196,12 @@ for _ in 0..42 {
|
|||||||
Compiling a script file is also supported:
|
Compiling a script file is also supported:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::Engine;
|
|
||||||
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
let ast = engine.compile_file("hello_world.rhai".into())?;
|
let ast = engine.compile_file("hello_world.rhai".into())?;
|
||||||
```
|
```
|
||||||
|
|
||||||
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - via `call_fn`:
|
Rhai also allows working _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust - via `call_fn`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::Engine;
|
|
||||||
|
|
||||||
let mut engine = Engine::new();
|
|
||||||
|
|
||||||
// Define a function in a script and load it into the Engine.
|
// Define a function in a script and load it into the Engine.
|
||||||
// Pass true to 'retain_functions' otherwise these functions will be cleared at the end of consume()
|
// Pass true to 'retain_functions' otherwise these functions will be cleared at the end of consume()
|
||||||
engine.consume(true,
|
engine.consume(true,
|
||||||
@ -234,6 +229,26 @@ let result: i64 = engine.call_fn("hello", 123_i64)?
|
|||||||
// ^^^^^^^ calls 'hello' with one parameter (no need for tuple)
|
// ^^^^^^^ calls 'hello' with one parameter (no need for tuple)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Evaluate expressions only
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Sometimes a use case does not require a full-blown scripting _language_, but only needs to evaluate _expressions_.
|
||||||
|
In these cases, use the `compile_expression` and `eval_expression` methods or their `_with_scope` variants.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let result = engine.eval_expression::<i64>("2 + (10 + 10) * 2")?;
|
||||||
|
```
|
||||||
|
|
||||||
|
When evaluation _expressions_, no control-flow statement (e.g. `if`, `while`, `for`) is not supported and will be
|
||||||
|
parse errors when encountered - not even variable assignments.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// The following are all syntax errors because the script is not an expression.
|
||||||
|
engine.eval_expression::<()>("x = 42")?;
|
||||||
|
let ast = engine.compile_expression("let x = 42")?;
|
||||||
|
let result = engine.eval_expression_with_scope::<i64>(&mut scope, "if x { 42 } else { 123 }")?;
|
||||||
|
```
|
||||||
|
|
||||||
Values and types
|
Values and types
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
@ -1464,9 +1479,9 @@ Function volatility considerations
|
|||||||
|
|
||||||
Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external
|
Even if a custom function does not mutate state nor cause side effects, it may still be _volatile_, i.e. it _depends_ on the external
|
||||||
environment and not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value!
|
environment and not _pure_. A perfect example is a function that gets the current time - obviously each run will return a different value!
|
||||||
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments.
|
The optimizer, when using [`OptimizationLevel::Full`], _assumes_ that all functions are _pure_, so when it finds constant arguments
|
||||||
This may cause the script to behave differently from the intended semantics because essentially the result of each function call will
|
it will eagerly run execute the function call. This causes the script to behave differently from the intended semantics because
|
||||||
always be the same value.
|
essentially the result of the function call will always be the same value.
|
||||||
|
|
||||||
Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions.
|
Therefore, **avoid using [`OptimizationLevel::Full`]** if you intend to register non-_pure_ custom types and/or functions.
|
||||||
|
|
||||||
|
124
examples/repl.rs
124
examples/repl.rs
@ -13,29 +13,26 @@ 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.split("\n").collect();
|
let lines: Vec<_> = input.trim().split("\n").collect();
|
||||||
|
|
||||||
|
let line_no = if lines.len() > 1 {
|
||||||
|
match err.position() {
|
||||||
|
p if p.is_none() => "".to_string(),
|
||||||
|
p if p.is_eof() => format!("{}: ", lines.len()),
|
||||||
|
p => format!("{}: ", p.line().unwrap()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
// Print error
|
// Print error
|
||||||
|
let pos_text = format!(" ({})", err.position());
|
||||||
|
|
||||||
match err.position() {
|
match err.position() {
|
||||||
p if p.is_eof() => {
|
p if p.is_eof() => {
|
||||||
// EOF
|
// EOF
|
||||||
let last = lines[lines.len() - 2];
|
let last = lines[lines.len() - 1];
|
||||||
println!("{}", last);
|
println!("{}{}", line_no, last);
|
||||||
println!("{}^ {}", padding(" ", last.len() - 1), err);
|
|
||||||
}
|
|
||||||
p if p.is_none() => {
|
|
||||||
// No position
|
|
||||||
println!("{}", err);
|
|
||||||
}
|
|
||||||
p => {
|
|
||||||
// Specific position
|
|
||||||
let pos_text = format!(
|
|
||||||
" (line {}, position {})",
|
|
||||||
p.line().unwrap(),
|
|
||||||
p.position().unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
println!("{}", lines[p.line().unwrap() - 1]);
|
|
||||||
|
|
||||||
let err_text = match err {
|
let err_text = match err {
|
||||||
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
|
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
|
||||||
@ -46,37 +43,103 @@ fn print_error(input: &str, err: EvalAltResult) {
|
|||||||
|
|
||||||
println!(
|
println!(
|
||||||
"{}^ {}",
|
"{}^ {}",
|
||||||
padding(" ", p.position().unwrap() - 1),
|
padding(" ", line_no.len() + last.len() - 1),
|
||||||
|
err_text.replace(&pos_text, "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
p if p.is_none() => {
|
||||||
|
// No position
|
||||||
|
println!("{}", err);
|
||||||
|
}
|
||||||
|
p => {
|
||||||
|
// Specific position
|
||||||
|
println!("{}{}", line_no, lines[p.line().unwrap() - 1]);
|
||||||
|
|
||||||
|
let err_text = match err {
|
||||||
|
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
|
||||||
|
format!("Runtime error: {}", err)
|
||||||
|
}
|
||||||
|
_ => err.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}^ {}",
|
||||||
|
padding(" ", line_no.len() + p.position().unwrap() - 1),
|
||||||
err_text.replace(&pos_text, "")
|
err_text.replace(&pos_text, "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_help() {
|
||||||
|
println!("help => print this help");
|
||||||
|
println!("quit, exit => quit");
|
||||||
|
println!("ast => print the last AST");
|
||||||
|
println!("astu => print the last raw, un-optimized AST");
|
||||||
|
println!(r"end a line with '\' to continue to the next line.");
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
engine.set_optimization_level(OptimizationLevel::Full);
|
engine.set_optimization_level(OptimizationLevel::None);
|
||||||
|
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
let mut ast_u: Option<AST> = None;
|
||||||
let mut ast: Option<AST> = None;
|
let mut ast: Option<AST> = None;
|
||||||
|
|
||||||
|
println!("Rhai REPL tool");
|
||||||
|
println!("==============");
|
||||||
|
print_help();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
print!("rhai> ");
|
print!("rhai> ");
|
||||||
stdout().flush().expect("couldn't flush stdout");
|
stdout().flush().expect("couldn't flush stdout");
|
||||||
|
|
||||||
input.clear();
|
input.clear();
|
||||||
|
|
||||||
|
loop {
|
||||||
if let Err(err) = stdin().read_line(&mut input) {
|
if let Err(err) = stdin().read_line(&mut input) {
|
||||||
println!("input error: {}", err);
|
panic!("input error: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let line = input.as_str().trim_end();
|
||||||
|
|
||||||
|
// Allow line continuation
|
||||||
|
if line.ends_with('\\') {
|
||||||
|
let len = line.len();
|
||||||
|
input.truncate(len - 1);
|
||||||
|
input.push('\n');
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
print!("> ");
|
||||||
|
stdout().flush().expect("couldn't flush stdout");
|
||||||
|
}
|
||||||
|
|
||||||
|
let script = input.trim();
|
||||||
|
|
||||||
// Implement standard commands
|
// Implement standard commands
|
||||||
match input.as_str().trim() {
|
match script {
|
||||||
|
"help" => {
|
||||||
|
print_help();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
"exit" | "quit" => break, // quit
|
"exit" | "quit" => break, // quit
|
||||||
|
"astu" => {
|
||||||
|
if matches!(&ast_u, Some(_)) {
|
||||||
|
// print the last un-optimized AST
|
||||||
|
println!("{:#?}", ast_u.as_ref().unwrap());
|
||||||
|
} else {
|
||||||
|
println!("()");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
"ast" => {
|
"ast" => {
|
||||||
if matches!(&ast, Some(_)) {
|
if matches!(&ast, Some(_)) {
|
||||||
// print the last AST
|
// print the last AST
|
||||||
@ -90,10 +153,23 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = engine
|
if let Err(err) = engine
|
||||||
.compile_with_scope(&scope, &input)
|
.compile_with_scope(&scope, &script)
|
||||||
.map_err(EvalAltResult::ErrorParsing)
|
.map_err(EvalAltResult::ErrorParsing)
|
||||||
.and_then(|r| {
|
.and_then(|r| {
|
||||||
ast = Some(r);
|
ast_u = Some(r);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
{
|
||||||
|
engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
|
ast = Some(engine.optimize_ast(&mut scope, ast_u.as_ref().unwrap()));
|
||||||
|
engine.set_optimization_level(OptimizationLevel::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "no_optimize")]
|
||||||
|
{
|
||||||
|
ast = ast_u.clone();
|
||||||
|
}
|
||||||
|
|
||||||
engine
|
engine
|
||||||
.consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap())
|
.consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap())
|
||||||
.or_else(|err| match err {
|
.or_else(|err| match err {
|
||||||
|
136
src/api.rs
136
src/api.rs
@ -5,7 +5,7 @@ use crate::call::FuncArgs;
|
|||||||
use crate::engine::{Engine, FnAny, FnSpec, FUNC_GETTER, FUNC_SETTER};
|
use crate::engine::{Engine, FnAny, FnSpec, FUNC_GETTER, FUNC_SETTER};
|
||||||
use crate::error::ParseError;
|
use crate::error::ParseError;
|
||||||
use crate::fn_register::RegisterFn;
|
use crate::fn_register::RegisterFn;
|
||||||
use crate::parser::{lex, parse, FnDef, Position, AST};
|
use crate::parser::{lex, parse, parse_global_expr, FnDef, Position, AST};
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::Scope;
|
use crate::scope::Scope;
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ impl<'e> Engine<'e> {
|
|||||||
///
|
///
|
||||||
/// impl TestStruct {
|
/// impl TestStruct {
|
||||||
/// fn new() -> Self { TestStruct { field: 1 } }
|
/// fn new() -> Self { TestStruct { field: 1 } }
|
||||||
/// fn update(&mut self) { self.field += 41; }
|
/// fn update(&mut self, offset: i64) { self.field += offset; }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
@ -64,11 +64,11 @@ impl<'e> Engine<'e> {
|
|||||||
///
|
///
|
||||||
/// engine.register_fn("new_ts", TestStruct::new);
|
/// engine.register_fn("new_ts", TestStruct::new);
|
||||||
///
|
///
|
||||||
/// // Register method on the type.
|
/// // Use `register_fn` to register methods on the type.
|
||||||
/// engine.register_fn("update", TestStruct::update);
|
/// engine.register_fn("update", TestStruct::update);
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(
|
||||||
/// engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?.field,
|
/// engine.eval::<TestStruct>("let x = new_ts(); x.update(41); x")?.field,
|
||||||
/// 42
|
/// 42
|
||||||
/// );
|
/// );
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
@ -138,6 +138,8 @@ impl<'e> Engine<'e> {
|
|||||||
|
|
||||||
/// Register a getter function for a member of a registered type with the `Engine`.
|
/// Register a getter function for a member of a registered type with the `Engine`.
|
||||||
///
|
///
|
||||||
|
/// The function signature must start with `&mut self` and not `&self`.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@ -148,6 +150,8 @@ impl<'e> Engine<'e> {
|
|||||||
///
|
///
|
||||||
/// impl TestStruct {
|
/// impl TestStruct {
|
||||||
/// fn new() -> Self { TestStruct { field: 1 } }
|
/// fn new() -> Self { TestStruct { field: 1 } }
|
||||||
|
///
|
||||||
|
/// // Even a getter must start with `&mut self` and not `&self`.
|
||||||
/// fn get_field(&mut self) -> i64 { self.field }
|
/// fn get_field(&mut self) -> i64 { self.field }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@ -222,6 +226,8 @@ impl<'e> Engine<'e> {
|
|||||||
/// Shorthand for registering both getter and setter functions
|
/// Shorthand for registering both getter and setter functions
|
||||||
/// of a registered type with the `Engine`.
|
/// of a registered type with the `Engine`.
|
||||||
///
|
///
|
||||||
|
/// All function signatures must start with `&mut self` and not `&self`.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@ -233,6 +239,7 @@ impl<'e> Engine<'e> {
|
|||||||
/// impl TestStruct {
|
/// impl TestStruct {
|
||||||
/// fn new() -> Self { TestStruct { field: 1 } }
|
/// fn new() -> Self { TestStruct { field: 1 } }
|
||||||
/// fn get_field(&mut self) -> i64 { self.field }
|
/// fn get_field(&mut self) -> i64 { self.field }
|
||||||
|
/// // Even a getter must start with `&mut self` and not `&self`.
|
||||||
/// fn set_field(&mut self, new_val: i64) { self.field = new_val; }
|
/// fn set_field(&mut self, new_val: i64) { self.field = new_val; }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@ -409,6 +416,78 @@ impl<'e> Engine<'e> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compile a string containing an expression into an `AST`,
|
||||||
|
/// which can be used later for evaluation.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
|
/// use rhai::Engine;
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// // Compile a script to an AST and store it for later evaluation
|
||||||
|
/// let ast = engine.compile_expression("40 + 2")?;
|
||||||
|
///
|
||||||
|
/// for _ in 0..42 {
|
||||||
|
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
|
||||||
|
/// }
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn compile_expression(&self, input: &str) -> Result<AST, ParseError> {
|
||||||
|
self.compile_expression_with_scope(&Scope::new(), input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile a string containing an expression into an `AST` using own scope,
|
||||||
|
/// which can be used later for evaluation.
|
||||||
|
///
|
||||||
|
/// The scope is useful for passing constants into the script for optimization
|
||||||
|
/// when using `OptimizationLevel::Full`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
|
/// # #[cfg(not(feature = "no_optimize"))]
|
||||||
|
/// # {
|
||||||
|
/// use rhai::{Engine, Scope, OptimizationLevel};
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// // Set optimization level to 'Full' so the Engine can fold constants
|
||||||
|
/// // into function calls and operators.
|
||||||
|
/// engine.set_optimization_level(OptimizationLevel::Full);
|
||||||
|
///
|
||||||
|
/// // Create initialized scope
|
||||||
|
/// let mut scope = Scope::new();
|
||||||
|
/// scope.push_constant("x", 10_i64); // 'x' is a constant
|
||||||
|
///
|
||||||
|
/// // Compile a script to an AST and store it for later evaluation.
|
||||||
|
/// // Notice that `Full` optimization is on, so constants are folded
|
||||||
|
/// // into function calls and operators.
|
||||||
|
/// let ast = engine.compile_expression_with_scope(&mut scope,
|
||||||
|
/// "2 + (x + x) * 2" // all 'x' are replaced with 10
|
||||||
|
/// )?;
|
||||||
|
///
|
||||||
|
/// // Normally this would have failed because no scope is passed into the 'eval_ast'
|
||||||
|
/// // call and so the variable 'x' does not exist. Here, it passes because the script
|
||||||
|
/// // has been optimized and all references to 'x' are already gone.
|
||||||
|
/// assert_eq!(engine.eval_ast::<i64>(&ast)?, 42);
|
||||||
|
/// # }
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn compile_expression_with_scope(
|
||||||
|
&self,
|
||||||
|
scope: &Scope,
|
||||||
|
input: &str,
|
||||||
|
) -> Result<AST, ParseError> {
|
||||||
|
let tokens_stream = lex(input);
|
||||||
|
parse_global_expr(&mut tokens_stream.peekable(), self, scope)
|
||||||
|
}
|
||||||
|
|
||||||
/// Evaluate a script file.
|
/// Evaluate a script file.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -507,6 +586,55 @@ impl<'e> Engine<'e> {
|
|||||||
self.eval_ast_with_scope(scope, &ast)
|
self.eval_ast_with_scope(scope, &ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Evaluate a string containing an expression.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
|
/// use rhai::Engine;
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// assert_eq!(engine.eval_expression::<i64>("40 + 2")?, 42);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn eval_expression<T: Any + Clone>(&mut self, input: &str) -> Result<T, EvalAltResult> {
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
self.eval_expression_with_scope(&mut scope, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluate a string containing an expression with own scope.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> Result<(), rhai::EvalAltResult> {
|
||||||
|
/// use rhai::{Engine, Scope};
|
||||||
|
///
|
||||||
|
/// let mut engine = Engine::new();
|
||||||
|
///
|
||||||
|
/// // Create initialized scope
|
||||||
|
/// let mut scope = Scope::new();
|
||||||
|
/// scope.push("x", 40_i64);
|
||||||
|
///
|
||||||
|
/// assert_eq!(engine.eval_expression_with_scope::<i64>(&mut scope, "x + 2")?, 42);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn eval_expression_with_scope<T: Any + Clone>(
|
||||||
|
&mut self,
|
||||||
|
scope: &mut Scope,
|
||||||
|
input: &str,
|
||||||
|
) -> Result<T, EvalAltResult> {
|
||||||
|
let ast = self
|
||||||
|
.compile_expression(input)
|
||||||
|
.map_err(EvalAltResult::ErrorParsing)?;
|
||||||
|
|
||||||
|
self.eval_ast_with_scope(scope, &ast)
|
||||||
|
}
|
||||||
|
|
||||||
/// Evaluate an `AST`.
|
/// Evaluate an `AST`.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use crate::any::{Any, Dynamic};
|
use crate::any::{Any, Dynamic};
|
||||||
|
use crate::parser::INT;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
use crate::engine::Array;
|
use crate::engine::Array;
|
||||||
@ -36,11 +37,8 @@ impl_std_args!(Array);
|
|||||||
#[cfg(not(feature = "only_i64"))]
|
#[cfg(not(feature = "only_i64"))]
|
||||||
impl_std_args!(u8, i8, u16, i16, u32, i32, u64, i64);
|
impl_std_args!(u8, i8, u16, i16, u32, i32, u64, i64);
|
||||||
|
|
||||||
#[cfg(feature = "only_i32")]
|
#[cfg(any(feature = "only_i32", feature = "only_i64"))]
|
||||||
impl_std_args!(i32);
|
impl_std_args!(INT);
|
||||||
|
|
||||||
#[cfg(feature = "only_i64")]
|
|
||||||
impl_std_args!(i64);
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
impl_std_args!(f32, f64);
|
impl_std_args!(f32, f64);
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
//! Main module defining the script evaluation `Engine`.
|
//! Main module defining the script evaluation `Engine`.
|
||||||
|
|
||||||
use crate::any::{Any, AnyExt, Dynamic, Variant};
|
use crate::any::{Any, AnyExt, Dynamic, Variant};
|
||||||
use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt};
|
use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, INT};
|
||||||
use crate::result::EvalAltResult;
|
use crate::result::EvalAltResult;
|
||||||
use crate::scope::{Scope, ScopeSource, VariableType};
|
use crate::scope::{Scope, ScopeSource, VariableType};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_optimize"))]
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
use crate::optimize::OptimizationLevel;
|
use crate::optimize::OptimizationLevel;
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
use crate::INT;
|
|
||||||
|
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
any::{type_name, TypeId},
|
any::{type_name, TypeId},
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
@ -1156,7 +1153,7 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If-else statement
|
// If-else statement
|
||||||
Stmt::IfElse(guard, if_body, else_body) => self
|
Stmt::IfThenElse(guard, if_body, else_body) => self
|
||||||
.eval_expr(scope, guard)?
|
.eval_expr(scope, guard)?
|
||||||
.downcast::<bool>()
|
.downcast::<bool>()
|
||||||
.map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position()))
|
.map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position()))
|
||||||
|
@ -79,7 +79,7 @@ impl State<'_> {
|
|||||||
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
|
fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -> Stmt {
|
||||||
match stmt {
|
match stmt {
|
||||||
// if expr { Noop }
|
// if expr { Noop }
|
||||||
Stmt::IfElse(expr, if_block, None) if matches!(*if_block, Stmt::Noop(_)) => {
|
Stmt::IfThenElse(expr, if_block, None) if matches!(*if_block, Stmt::Noop(_)) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
|
|
||||||
let pos = expr.position();
|
let pos = expr.position();
|
||||||
@ -94,7 +94,7 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if expr { if_block }
|
// if expr { if_block }
|
||||||
Stmt::IfElse(expr, if_block, None) => match *expr {
|
Stmt::IfThenElse(expr, if_block, None) => match *expr {
|
||||||
// if false { if_block } -> Noop
|
// if false { if_block } -> Noop
|
||||||
Expr::False(pos) => {
|
Expr::False(pos) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -103,20 +103,20 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
|
|||||||
// if true { if_block } -> if_block
|
// if true { if_block } -> if_block
|
||||||
Expr::True(_) => optimize_stmt(*if_block, state, true),
|
Expr::True(_) => optimize_stmt(*if_block, state, true),
|
||||||
// if expr { if_block }
|
// if expr { if_block }
|
||||||
expr => Stmt::IfElse(
|
expr => Stmt::IfThenElse(
|
||||||
Box::new(optimize_expr(expr, state)),
|
Box::new(optimize_expr(expr, state)),
|
||||||
Box::new(optimize_stmt(*if_block, state, true)),
|
Box::new(optimize_stmt(*if_block, state, true)),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
// if expr { if_block } else { else_block }
|
// if expr { if_block } else { else_block }
|
||||||
Stmt::IfElse(expr, if_block, Some(else_block)) => match *expr {
|
Stmt::IfThenElse(expr, if_block, Some(else_block)) => match *expr {
|
||||||
// if false { if_block } else { else_block } -> else_block
|
// if false { if_block } else { else_block } -> else_block
|
||||||
Expr::False(_) => optimize_stmt(*else_block, state, true),
|
Expr::False(_) => optimize_stmt(*else_block, state, true),
|
||||||
// if true { if_block } else { else_block } -> if_block
|
// if true { if_block } else { else_block } -> if_block
|
||||||
Expr::True(_) => optimize_stmt(*if_block, state, true),
|
Expr::True(_) => optimize_stmt(*if_block, state, true),
|
||||||
// if expr { if_block } else { else_block }
|
// if expr { if_block } else { else_block }
|
||||||
expr => Stmt::IfElse(
|
expr => Stmt::IfThenElse(
|
||||||
Box::new(optimize_expr(expr, state)),
|
Box::new(optimize_expr(expr, state)),
|
||||||
Box::new(optimize_stmt(*if_block, state, true)),
|
Box::new(optimize_stmt(*if_block, state, true)),
|
||||||
match optimize_stmt(*else_block, state, true) {
|
match optimize_stmt(*else_block, state, true) {
|
||||||
@ -584,8 +584,9 @@ pub fn optimize_into_ast(
|
|||||||
AST(
|
AST(
|
||||||
match engine.optimization_level {
|
match engine.optimization_level {
|
||||||
OptimizationLevel::None => statements,
|
OptimizationLevel::None => statements,
|
||||||
OptimizationLevel::Simple => optimize(statements, engine, &scope),
|
OptimizationLevel::Simple | OptimizationLevel::Full => {
|
||||||
OptimizationLevel::Full => optimize(statements, engine, &scope),
|
optimize(statements, engine, &scope)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
functions
|
functions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
190
src/parser.rs
190
src/parser.rs
@ -204,7 +204,7 @@ pub enum Stmt {
|
|||||||
/// No-op.
|
/// No-op.
|
||||||
Noop(Position),
|
Noop(Position),
|
||||||
/// if expr { stmt } else { stmt }
|
/// if expr { stmt } else { stmt }
|
||||||
IfElse(Box<Expr>, Box<Stmt>, Option<Box<Stmt>>),
|
IfThenElse(Box<Expr>, Box<Stmt>, Option<Box<Stmt>>),
|
||||||
/// while expr { stmt }
|
/// while expr { stmt }
|
||||||
While(Box<Expr>, Box<Stmt>),
|
While(Box<Expr>, Box<Stmt>),
|
||||||
/// loop { stmt }
|
/// loop { stmt }
|
||||||
@ -235,7 +235,7 @@ impl Stmt {
|
|||||||
| Stmt::Block(_, pos)
|
| Stmt::Block(_, pos)
|
||||||
| Stmt::Break(pos)
|
| Stmt::Break(pos)
|
||||||
| Stmt::ReturnWithVal(_, _, pos) => *pos,
|
| Stmt::ReturnWithVal(_, _, pos) => *pos,
|
||||||
Stmt::IfElse(expr, _, _) | Stmt::Expr(expr) => expr.position(),
|
Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) => expr.position(),
|
||||||
Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(),
|
Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,7 +243,7 @@ impl Stmt {
|
|||||||
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
|
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
|
||||||
pub fn is_self_terminated(&self) -> bool {
|
pub fn is_self_terminated(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Stmt::IfElse(_, _, _)
|
Stmt::IfThenElse(_, _, _)
|
||||||
| Stmt::While(_, _)
|
| Stmt::While(_, _)
|
||||||
| Stmt::Loop(_)
|
| Stmt::Loop(_)
|
||||||
| Stmt::For(_, _, _)
|
| Stmt::For(_, _, _)
|
||||||
@ -265,10 +265,10 @@ impl Stmt {
|
|||||||
match self {
|
match self {
|
||||||
Stmt::Noop(_) => true,
|
Stmt::Noop(_) => true,
|
||||||
Stmt::Expr(expr) => expr.is_pure(),
|
Stmt::Expr(expr) => expr.is_pure(),
|
||||||
Stmt::IfElse(guard, if_block, Some(else_block)) => {
|
Stmt::IfThenElse(guard, if_block, Some(else_block)) => {
|
||||||
guard.is_pure() && if_block.is_pure() && else_block.is_pure()
|
guard.is_pure() && if_block.is_pure() && else_block.is_pure()
|
||||||
}
|
}
|
||||||
Stmt::IfElse(guard, block, None) | Stmt::While(guard, block) => {
|
Stmt::IfThenElse(guard, block, None) | Stmt::While(guard, block) => {
|
||||||
guard.is_pure() && block.is_pure()
|
guard.is_pure() && block.is_pure()
|
||||||
}
|
}
|
||||||
Stmt::Loop(block) => block.is_pure(),
|
Stmt::Loop(block) => block.is_pure(),
|
||||||
@ -1346,6 +1346,7 @@ pub fn lex(input: &str) -> TokenIterator<'_> {
|
|||||||
fn parse_paren_expr<'a>(
|
fn parse_paren_expr<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
begin: Position,
|
begin: Position,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
match input.peek() {
|
match input.peek() {
|
||||||
// ()
|
// ()
|
||||||
@ -1356,7 +1357,7 @@ fn parse_paren_expr<'a>(
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let expr = parse_expr(input)?;
|
let expr = parse_expr(input, allow_stmt_expr)?;
|
||||||
|
|
||||||
match input.next() {
|
match input.next() {
|
||||||
// ( xxx )
|
// ( xxx )
|
||||||
@ -1381,6 +1382,7 @@ fn parse_call_expr<'a>(
|
|||||||
id: String,
|
id: String,
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
begin: Position,
|
begin: Position,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
let mut args_expr_list = Vec::new();
|
let mut args_expr_list = Vec::new();
|
||||||
|
|
||||||
@ -1399,7 +1401,7 @@ fn parse_call_expr<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
args_expr_list.push(parse_expr(input)?);
|
args_expr_list.push(parse_expr(input, allow_stmt_expr)?);
|
||||||
|
|
||||||
match input.peek().ok_or_else(|| {
|
match input.peek().ok_or_else(|| {
|
||||||
ParseError::new(
|
ParseError::new(
|
||||||
@ -1436,8 +1438,9 @@ fn parse_index_expr<'a>(
|
|||||||
lhs: Box<Expr>,
|
lhs: Box<Expr>,
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
let idx_expr = parse_expr(input)?;
|
let idx_expr = parse_expr(input, allow_stmt_expr)?;
|
||||||
|
|
||||||
// Check type of indexing - must be integer
|
// Check type of indexing - must be integer
|
||||||
match &idx_expr {
|
match &idx_expr {
|
||||||
@ -1529,24 +1532,30 @@ fn parse_ident_expr<'a>(
|
|||||||
id: String,
|
id: String,
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
begin: Position,
|
begin: Position,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
match input.peek() {
|
match input.peek() {
|
||||||
// id(...) - function call
|
// id(...) - function call
|
||||||
Some((Token::LeftParen, _)) => {
|
Some((Token::LeftParen, _)) => {
|
||||||
input.next();
|
input.next();
|
||||||
parse_call_expr(id, input, begin)
|
parse_call_expr(id, input, begin, allow_stmt_expr)
|
||||||
}
|
}
|
||||||
// id[...] - indexing
|
// id[...] - indexing
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Some((Token::LeftBracket, pos)) => {
|
Some((Token::LeftBracket, pos)) => {
|
||||||
let pos = *pos;
|
let pos = *pos;
|
||||||
input.next();
|
input.next();
|
||||||
parse_index_expr(Box::new(Expr::Variable(id, begin)), input, pos)
|
parse_index_expr(
|
||||||
|
Box::new(Expr::Variable(id, begin)),
|
||||||
|
input,
|
||||||
|
pos,
|
||||||
|
allow_stmt_expr,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// id - variable
|
// id - variable
|
||||||
Some(_) => Ok(Expr::Variable(id, begin)),
|
Some(_) => Ok(Expr::Variable(id, begin)),
|
||||||
// EOF
|
// EOF
|
||||||
None => Ok(Expr::Variable(id, Position::eof())),
|
None => Ok(Expr::Variable(id, begin)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1555,12 +1564,13 @@ fn parse_ident_expr<'a>(
|
|||||||
fn parse_array_literal<'a>(
|
fn parse_array_literal<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
begin: Position,
|
begin: Position,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
let mut arr = Vec::new();
|
let mut arr = Vec::new();
|
||||||
|
|
||||||
if !matches!(input.peek(), Some((Token::RightBracket, _))) {
|
if !matches!(input.peek(), Some((Token::RightBracket, _))) {
|
||||||
while input.peek().is_some() {
|
while input.peek().is_some() {
|
||||||
arr.push(parse_expr(input)?);
|
arr.push(parse_expr(input, allow_stmt_expr)?);
|
||||||
|
|
||||||
match input.peek().ok_or_else(|| {
|
match input.peek().ok_or_else(|| {
|
||||||
ParseError(
|
ParseError(
|
||||||
@ -1600,15 +1610,19 @@ fn parse_array_literal<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a primary expression.
|
/// Parse a primary expression.
|
||||||
fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
fn parse_primary<'a>(
|
||||||
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
|
) -> Result<Expr, ParseError> {
|
||||||
let token = match input
|
let token = match input
|
||||||
.peek()
|
.peek()
|
||||||
.ok_or_else(|| ParseError::new(PERR::UnexpectedEOF, Position::eof()))?
|
.ok_or_else(|| ParseError::new(PERR::UnexpectedEOF, Position::eof()))?
|
||||||
{
|
{
|
||||||
// { - block statement as expression
|
// { - block statement as expression
|
||||||
(Token::LeftBrace, pos) => {
|
(Token::LeftBrace, pos) if allow_stmt_expr => {
|
||||||
let pos = *pos;
|
let pos = *pos;
|
||||||
return parse_block(input, false).map(|block| Expr::Stmt(Box::new(block), pos));
|
return parse_block(input, false, allow_stmt_expr)
|
||||||
|
.map(|block| Expr::Stmt(Box::new(block), pos));
|
||||||
}
|
}
|
||||||
_ => input.next().expect("should be a token"),
|
_ => input.next().expect("should be a token"),
|
||||||
};
|
};
|
||||||
@ -1627,16 +1641,16 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
|
|||||||
}
|
}
|
||||||
(Token::Identifier(s), pos) => {
|
(Token::Identifier(s), pos) => {
|
||||||
can_be_indexed = true;
|
can_be_indexed = true;
|
||||||
parse_ident_expr(s, input, pos)
|
parse_ident_expr(s, input, pos, allow_stmt_expr)
|
||||||
}
|
}
|
||||||
(Token::LeftParen, pos) => {
|
(Token::LeftParen, pos) => {
|
||||||
can_be_indexed = true;
|
can_be_indexed = true;
|
||||||
parse_paren_expr(input, pos)
|
parse_paren_expr(input, pos, allow_stmt_expr)
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
(Token::LeftBracket, pos) => {
|
(Token::LeftBracket, pos) => {
|
||||||
can_be_indexed = true;
|
can_be_indexed = true;
|
||||||
parse_array_literal(input, pos)
|
parse_array_literal(input, pos, allow_stmt_expr)
|
||||||
}
|
}
|
||||||
(Token::True, pos) => Ok(Expr::True(pos)),
|
(Token::True, pos) => Ok(Expr::True(pos)),
|
||||||
(Token::False, pos) => Ok(Expr::False(pos)),
|
(Token::False, pos) => Ok(Expr::False(pos)),
|
||||||
@ -1653,7 +1667,7 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
|
|||||||
while let Some((Token::LeftBracket, pos)) = input.peek() {
|
while let Some((Token::LeftBracket, pos)) = input.peek() {
|
||||||
let pos = *pos;
|
let pos = *pos;
|
||||||
input.next();
|
input.next();
|
||||||
root_expr = parse_index_expr(Box::new(root_expr), input, pos)?;
|
root_expr = parse_index_expr(Box::new(root_expr), input, pos, allow_stmt_expr)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1661,7 +1675,10 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a potential unary operator.
|
/// Parse a potential unary operator.
|
||||||
fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
fn parse_unary<'a>(
|
||||||
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
|
) -> Result<Expr, ParseError> {
|
||||||
match input
|
match input
|
||||||
.peek()
|
.peek()
|
||||||
.ok_or_else(|| ParseError::new(PERR::UnexpectedEOF, Position::eof()))?
|
.ok_or_else(|| ParseError::new(PERR::UnexpectedEOF, Position::eof()))?
|
||||||
@ -1672,7 +1689,7 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
|||||||
|
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
match parse_unary(input)? {
|
match parse_unary(input, allow_stmt_expr)? {
|
||||||
// Negative integer
|
// Negative integer
|
||||||
Expr::IntegerConstant(i, _) => i
|
Expr::IntegerConstant(i, _) => i
|
||||||
.checked_neg()
|
.checked_neg()
|
||||||
@ -1702,7 +1719,7 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
|||||||
// +expr
|
// +expr
|
||||||
(Token::UnaryPlus, _) => {
|
(Token::UnaryPlus, _) => {
|
||||||
input.next();
|
input.next();
|
||||||
parse_unary(input)
|
parse_unary(input, allow_stmt_expr)
|
||||||
}
|
}
|
||||||
// !expr
|
// !expr
|
||||||
(Token::Bang, pos) => {
|
(Token::Bang, pos) => {
|
||||||
@ -1712,13 +1729,13 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
|||||||
|
|
||||||
Ok(Expr::FunctionCall(
|
Ok(Expr::FunctionCall(
|
||||||
"!".into(),
|
"!".into(),
|
||||||
vec![parse_primary(input)?],
|
vec![parse_primary(input, allow_stmt_expr)?],
|
||||||
Some(Box::new(false)), // NOT operator, when operating on invalid operand, defaults to false
|
Some(Box::new(false)), // NOT operator, when operating on invalid operand, defaults to false
|
||||||
pos,
|
pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
// All other tokens
|
// All other tokens
|
||||||
_ => parse_primary(input),
|
_ => parse_primary(input, allow_stmt_expr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1821,6 +1838,7 @@ fn parse_binary_op<'a>(
|
|||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
parent_precedence: u8,
|
parent_precedence: u8,
|
||||||
lhs: Expr,
|
lhs: Expr,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
) -> Result<Expr, ParseError> {
|
) -> Result<Expr, ParseError> {
|
||||||
let mut current_lhs = lhs;
|
let mut current_lhs = lhs;
|
||||||
|
|
||||||
@ -1842,7 +1860,7 @@ fn parse_binary_op<'a>(
|
|||||||
if let Some((op_token, pos)) = input.next() {
|
if let Some((op_token, pos)) = input.next() {
|
||||||
input.peek();
|
input.peek();
|
||||||
|
|
||||||
let rhs = parse_unary(input)?;
|
let rhs = parse_unary(input, allow_stmt_expr)?;
|
||||||
|
|
||||||
let next_precedence = if let Some((next_op, _)) = input.peek() {
|
let next_precedence = if let Some((next_op, _)) = input.peek() {
|
||||||
next_op.precedence()
|
next_op.precedence()
|
||||||
@ -1855,7 +1873,7 @@ fn parse_binary_op<'a>(
|
|||||||
let rhs = if (current_precedence == next_precedence && bind_right)
|
let rhs = if (current_precedence == next_precedence && bind_right)
|
||||||
|| current_precedence < next_precedence
|
|| current_precedence < next_precedence
|
||||||
{
|
{
|
||||||
parse_binary_op(input, current_precedence, rhs)?
|
parse_binary_op(input, current_precedence, rhs, allow_stmt_expr)?
|
||||||
} else {
|
} else {
|
||||||
// Otherwise bind to left (even if next operator has the same precedence)
|
// Otherwise bind to left (even if next operator has the same precedence)
|
||||||
rhs
|
rhs
|
||||||
@ -1971,9 +1989,12 @@ fn parse_binary_op<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an expression.
|
/// Parse an expression.
|
||||||
fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, ParseError> {
|
fn parse_expr<'a>(
|
||||||
let lhs = parse_unary(input)?;
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
parse_binary_op(input, 1, lhs)
|
allow_stmt_expr: bool,
|
||||||
|
) -> Result<Expr, ParseError> {
|
||||||
|
let lhs = parse_unary(input, allow_stmt_expr)?;
|
||||||
|
parse_binary_op(input, 1, lhs, allow_stmt_expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make sure that the expression is not a statement expression (i.e. wrapped in {})
|
/// Make sure that the expression is not a statement expression (i.e. wrapped in {})
|
||||||
@ -1996,14 +2017,15 @@ fn ensure_not_statement_expr<'a>(
|
|||||||
fn parse_if<'a>(
|
fn parse_if<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
breakable: bool,
|
breakable: bool,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
) -> Result<Stmt, ParseError> {
|
) -> Result<Stmt, ParseError> {
|
||||||
// if ...
|
// if ...
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
// if guard { if_body }
|
// if guard { if_body }
|
||||||
ensure_not_statement_expr(input, "a boolean")?;
|
ensure_not_statement_expr(input, "a boolean")?;
|
||||||
let guard = parse_expr(input)?;
|
let guard = parse_expr(input, allow_stmt_expr)?;
|
||||||
let if_body = parse_block(input, breakable)?;
|
let if_body = parse_block(input, breakable, allow_stmt_expr)?;
|
||||||
|
|
||||||
// if guard { if_body } else ...
|
// if guard { if_body } else ...
|
||||||
let else_body = if matches!(input.peek(), Some((Token::Else, _))) {
|
let else_body = if matches!(input.peek(), Some((Token::Else, _))) {
|
||||||
@ -2011,44 +2033,57 @@ fn parse_if<'a>(
|
|||||||
|
|
||||||
Some(Box::new(if matches!(input.peek(), Some((Token::If, _))) {
|
Some(Box::new(if matches!(input.peek(), Some((Token::If, _))) {
|
||||||
// if guard { if_body } else if ...
|
// if guard { if_body } else if ...
|
||||||
parse_if(input, breakable)?
|
parse_if(input, breakable, allow_stmt_expr)?
|
||||||
} else {
|
} else {
|
||||||
// if guard { if_body } else { else-body }
|
// if guard { if_body } else { else-body }
|
||||||
parse_block(input, breakable)?
|
parse_block(input, breakable, allow_stmt_expr)?
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Stmt::IfElse(Box::new(guard), Box::new(if_body), else_body))
|
Ok(Stmt::IfThenElse(
|
||||||
|
Box::new(guard),
|
||||||
|
Box::new(if_body),
|
||||||
|
else_body,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a while loop.
|
/// Parse a while loop.
|
||||||
fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_while<'a>(
|
||||||
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
|
) -> Result<Stmt, ParseError> {
|
||||||
// while ...
|
// while ...
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
// while guard { body }
|
// while guard { body }
|
||||||
ensure_not_statement_expr(input, "a boolean")?;
|
ensure_not_statement_expr(input, "a boolean")?;
|
||||||
let guard = parse_expr(input)?;
|
let guard = parse_expr(input, allow_stmt_expr)?;
|
||||||
let body = parse_block(input, true)?;
|
let body = parse_block(input, true, allow_stmt_expr)?;
|
||||||
|
|
||||||
Ok(Stmt::While(Box::new(guard), Box::new(body)))
|
Ok(Stmt::While(Box::new(guard), Box::new(body)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a loop statement.
|
/// Parse a loop statement.
|
||||||
fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_loop<'a>(
|
||||||
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
|
) -> Result<Stmt, ParseError> {
|
||||||
// loop ...
|
// loop ...
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
// loop { body }
|
// loop { body }
|
||||||
let body = parse_block(input, true)?;
|
let body = parse_block(input, true, allow_stmt_expr)?;
|
||||||
|
|
||||||
Ok(Stmt::Loop(Box::new(body)))
|
Ok(Stmt::Loop(Box::new(body)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a for loop.
|
/// Parse a for loop.
|
||||||
fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_for<'a>(
|
||||||
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
|
) -> Result<Stmt, ParseError> {
|
||||||
// for ...
|
// for ...
|
||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
@ -2078,8 +2113,8 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
|||||||
|
|
||||||
// for name in expr { body }
|
// for name in expr { body }
|
||||||
ensure_not_statement_expr(input, "a boolean")?;
|
ensure_not_statement_expr(input, "a boolean")?;
|
||||||
let expr = parse_expr(input)?;
|
let expr = parse_expr(input, allow_stmt_expr)?;
|
||||||
let body = parse_block(input, true)?;
|
let body = parse_block(input, true, allow_stmt_expr)?;
|
||||||
|
|
||||||
Ok(Stmt::For(name, Box::new(expr), Box::new(body)))
|
Ok(Stmt::For(name, Box::new(expr), Box::new(body)))
|
||||||
}
|
}
|
||||||
@ -2088,6 +2123,7 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
|
|||||||
fn parse_let<'a>(
|
fn parse_let<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
var_type: VariableType,
|
var_type: VariableType,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
) -> Result<Stmt, ParseError> {
|
) -> Result<Stmt, ParseError> {
|
||||||
// let/const... (specified in `var_type`)
|
// let/const... (specified in `var_type`)
|
||||||
input.next();
|
input.next();
|
||||||
@ -2109,7 +2145,7 @@ fn parse_let<'a>(
|
|||||||
input.next();
|
input.next();
|
||||||
|
|
||||||
// let name = expr
|
// let name = expr
|
||||||
let init_value = parse_expr(input)?;
|
let init_value = parse_expr(input, allow_stmt_expr)?;
|
||||||
|
|
||||||
match var_type {
|
match var_type {
|
||||||
// let name = expr
|
// let name = expr
|
||||||
@ -2134,6 +2170,7 @@ fn parse_let<'a>(
|
|||||||
fn parse_block<'a>(
|
fn parse_block<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
breakable: bool,
|
breakable: bool,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
) -> Result<Stmt, ParseError> {
|
) -> Result<Stmt, ParseError> {
|
||||||
// Must start with {
|
// Must start with {
|
||||||
let pos = match input
|
let pos = match input
|
||||||
@ -2148,7 +2185,7 @@ fn parse_block<'a>(
|
|||||||
|
|
||||||
while !matches!(input.peek(), Some((Token::RightBrace, _))) {
|
while !matches!(input.peek(), Some((Token::RightBrace, _))) {
|
||||||
// Parse statements inside the block
|
// Parse statements inside the block
|
||||||
let stmt = parse_stmt(input, breakable)?;
|
let stmt = parse_stmt(input, breakable, allow_stmt_expr)?;
|
||||||
|
|
||||||
// See if it needs a terminating semicolon
|
// See if it needs a terminating semicolon
|
||||||
let need_semicolon = !stmt.is_self_terminated();
|
let need_semicolon = !stmt.is_self_terminated();
|
||||||
@ -2197,14 +2234,18 @@ fn parse_block<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an expression as a statement.
|
/// Parse an expression as a statement.
|
||||||
fn parse_expr_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
|
fn parse_expr_stmt<'a>(
|
||||||
Ok(Stmt::Expr(Box::new(parse_expr(input)?)))
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
|
) -> Result<Stmt, ParseError> {
|
||||||
|
Ok(Stmt::Expr(Box::new(parse_expr(input, allow_stmt_expr)?)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a single statement.
|
/// Parse a single statement.
|
||||||
fn parse_stmt<'a>(
|
fn parse_stmt<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
breakable: bool,
|
breakable: bool,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
) -> Result<Stmt, ParseError> {
|
) -> Result<Stmt, ParseError> {
|
||||||
let token = match input.peek() {
|
let token = match input.peek() {
|
||||||
Some(token) => token,
|
Some(token) => token,
|
||||||
@ -2219,10 +2260,10 @@ fn parse_stmt<'a>(
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
(Token::Fn, pos) => return Err(ParseError::new(PERR::WrongFnDefinition, *pos)),
|
(Token::Fn, pos) => return Err(ParseError::new(PERR::WrongFnDefinition, *pos)),
|
||||||
|
|
||||||
(Token::If, _) => parse_if(input, breakable),
|
(Token::If, _) => parse_if(input, breakable, allow_stmt_expr),
|
||||||
(Token::While, _) => parse_while(input),
|
(Token::While, _) => parse_while(input, allow_stmt_expr),
|
||||||
(Token::Loop, _) => parse_loop(input),
|
(Token::Loop, _) => parse_loop(input, allow_stmt_expr),
|
||||||
(Token::For, _) => parse_for(input),
|
(Token::For, _) => parse_for(input, allow_stmt_expr),
|
||||||
(Token::Break, pos) if breakable => {
|
(Token::Break, pos) if breakable => {
|
||||||
let pos = *pos;
|
let pos = *pos;
|
||||||
input.next();
|
input.next();
|
||||||
@ -2246,23 +2287,26 @@ fn parse_stmt<'a>(
|
|||||||
Some((Token::SemiColon, _)) => Ok(Stmt::ReturnWithVal(None, return_type, pos)),
|
Some((Token::SemiColon, _)) => Ok(Stmt::ReturnWithVal(None, return_type, pos)),
|
||||||
// `return` or `throw` with expression
|
// `return` or `throw` with expression
|
||||||
Some((_, _)) => {
|
Some((_, _)) => {
|
||||||
let expr = parse_expr(input)?;
|
let expr = parse_expr(input, allow_stmt_expr)?;
|
||||||
let pos = expr.position();
|
let pos = expr.position();
|
||||||
|
|
||||||
Ok(Stmt::ReturnWithVal(Some(Box::new(expr)), return_type, pos))
|
Ok(Stmt::ReturnWithVal(Some(Box::new(expr)), return_type, pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Token::LeftBrace, _) => parse_block(input, breakable),
|
(Token::LeftBrace, _) => parse_block(input, breakable, allow_stmt_expr),
|
||||||
(Token::Let, _) => parse_let(input, VariableType::Normal),
|
(Token::Let, _) => parse_let(input, VariableType::Normal, allow_stmt_expr),
|
||||||
(Token::Const, _) => parse_let(input, VariableType::Constant),
|
(Token::Const, _) => parse_let(input, VariableType::Constant, allow_stmt_expr),
|
||||||
_ => parse_expr_stmt(input),
|
_ => parse_expr_stmt(input, allow_stmt_expr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a function definition.
|
/// Parse a function definition.
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseError> {
|
fn parse_fn<'a>(
|
||||||
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
allow_stmt_expr: bool,
|
||||||
|
) -> Result<FnDef, ParseError> {
|
||||||
let pos = input.next().expect("should be fn").1;
|
let pos = input.next().expect("should be fn").1;
|
||||||
|
|
||||||
let name = match input
|
let name = match input
|
||||||
@ -2346,7 +2390,7 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
|
|||||||
}
|
}
|
||||||
|
|
||||||
let body = match input.peek() {
|
let body = match input.peek() {
|
||||||
Some((Token::LeftBrace, _)) => parse_block(input, false)?,
|
Some((Token::LeftBrace, _)) => parse_block(input, false, allow_stmt_expr)?,
|
||||||
Some((_, pos)) => return Err(ParseError::new(PERR::FnMissingBody(name), *pos)),
|
Some((_, pos)) => return Err(ParseError::new(PERR::FnMissingBody(name), *pos)),
|
||||||
None => return Err(ParseError::new(PERR::FnMissingBody(name), Position::eof())),
|
None => return Err(ParseError::new(PERR::FnMissingBody(name), Position::eof())),
|
||||||
};
|
};
|
||||||
@ -2359,8 +2403,34 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_global_expr<'a, 'e>(
|
||||||
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
|
engine: &Engine<'e>,
|
||||||
|
scope: &Scope,
|
||||||
|
) -> Result<AST, ParseError> {
|
||||||
|
let expr = parse_expr(input, false)?;
|
||||||
|
|
||||||
|
if let Some((token, pos)) = input.peek() {
|
||||||
|
// Return error if the expression doesn't end
|
||||||
|
return Err(ParseError::new(
|
||||||
|
PERR::BadInput(format!("Unexpected '{}'", token.syntax())),
|
||||||
|
*pos,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
// Optimize AST
|
||||||
|
#[cfg(not(feature = "no_optimize"))]
|
||||||
|
optimize_into_ast(engine, scope, vec![Stmt::Expr(Box::new(expr))], vec![]),
|
||||||
|
//
|
||||||
|
// Do not optimize AST if `no_optimize`
|
||||||
|
#[cfg(feature = "no_optimize")]
|
||||||
|
AST(vec![Stmt::Expr(Box::new(expr))], vec![]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse the global level statements.
|
/// Parse the global level statements.
|
||||||
fn parse_global_level<'a, 'e>(
|
fn parse_global_level<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
|
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
|
||||||
let mut statements = Vec::<Stmt>::new();
|
let mut statements = Vec::<Stmt>::new();
|
||||||
@ -2371,7 +2441,7 @@ fn parse_global_level<'a, 'e>(
|
|||||||
{
|
{
|
||||||
// Collect all the function definitions
|
// Collect all the function definitions
|
||||||
if matches!(input.peek().expect("should not be None"), (Token::Fn, _)) {
|
if matches!(input.peek().expect("should not be None"), (Token::Fn, _)) {
|
||||||
let f = parse_fn(input)?;
|
let f = parse_fn(input, true)?;
|
||||||
|
|
||||||
// Ensure list is sorted
|
// Ensure list is sorted
|
||||||
match functions.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) {
|
match functions.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) {
|
||||||
@ -2384,7 +2454,7 @@ fn parse_global_level<'a, 'e>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Actual statement
|
// Actual statement
|
||||||
let stmt = parse_stmt(input, false)?;
|
let stmt = parse_stmt(input, false, true)?;
|
||||||
|
|
||||||
let need_semicolon = !stmt.is_self_terminated();
|
let need_semicolon = !stmt.is_self_terminated();
|
||||||
|
|
||||||
|
25
tests/expressions.rs
Normal file
25
tests/expressions.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use rhai::{Engine, EvalAltResult, Scope, INT};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_expressions() -> Result<(), EvalAltResult> {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
|
scope.push("x", 10 as INT);
|
||||||
|
|
||||||
|
assert_eq!(engine.eval_expression::<INT>("2 + (10 + 10) * 2")?, 42);
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval_expression_with_scope::<INT>(&mut scope, "2 + (x + 10) * 2")?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(engine.eval_expression::<()>("40 + 2;").is_err());
|
||||||
|
assert!(engine.eval_expression::<()>("40 + { 2 }").is_err());
|
||||||
|
assert!(engine.eval_expression::<()>("x = 42").is_err());
|
||||||
|
assert!(engine.compile_expression("let x = 42").is_err());
|
||||||
|
assert!(engine
|
||||||
|
.eval_expression_with_scope::<INT>(&mut scope, "if x > 0 { 42 } else { 123 }")
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,16 +1,41 @@
|
|||||||
use rhai::{Engine, EvalAltResult, RegisterFn, Scope};
|
///! This test simulates an external command object that is driven by a script.
|
||||||
use std::cell::Cell;
|
use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT};
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
/// External command.
|
||||||
|
struct Command {
|
||||||
|
state: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
/// Do some action.
|
||||||
|
pub fn action(&mut self, val: i64) {
|
||||||
|
self.state = val;
|
||||||
|
}
|
||||||
|
/// Get current value.
|
||||||
|
pub fn get(&self) -> i64 {
|
||||||
|
self.state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrapper object to wrap a command object.
|
||||||
|
#[derive(Clone)]
|
||||||
struct CommandWrapper {
|
struct CommandWrapper {
|
||||||
value: Rc<Cell<i64>>,
|
command: Rc<RefCell<Command>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandWrapper {
|
impl CommandWrapper {
|
||||||
pub fn set_value(&mut self, x: i64) {
|
/// Delegate command action.
|
||||||
let val = self.value.get();
|
pub fn do_action(&mut self, x: i64) {
|
||||||
self.value.set(val + x);
|
let mut command = self.command.borrow_mut();
|
||||||
|
let val = command.get();
|
||||||
|
command.action(val + x);
|
||||||
|
}
|
||||||
|
/// Delegate get value action.
|
||||||
|
pub fn get_value(&mut self) -> i64 {
|
||||||
|
let command = self.command.borrow();
|
||||||
|
command.get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,21 +44,37 @@ fn test_side_effects() -> Result<(), EvalAltResult> {
|
|||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
let payload = Rc::new(Cell::new(12));
|
// Create the command object with initial state, handled by an `Rc`.
|
||||||
assert_eq!(payload.get(), 12);
|
let command = Rc::new(RefCell::new(Command { state: 12 }));
|
||||||
|
assert_eq!(command.borrow().get(), 12);
|
||||||
|
|
||||||
let command = CommandWrapper {
|
// Create the wrapper.
|
||||||
value: payload.clone(),
|
let wrapper = CommandWrapper {
|
||||||
|
command: command.clone(), // Notice this clones the `Rc` only
|
||||||
};
|
};
|
||||||
|
|
||||||
scope.push_constant("Command", command);
|
// Make the wrapper a singleton in the script environment.
|
||||||
|
scope.push_constant("Command", wrapper);
|
||||||
|
|
||||||
|
// Register type.
|
||||||
engine.register_type_with_name::<CommandWrapper>("CommandType");
|
engine.register_type_with_name::<CommandWrapper>("CommandType");
|
||||||
engine.register_fn("action", CommandWrapper::set_value);
|
engine.register_fn("action", CommandWrapper::do_action);
|
||||||
|
engine.register_get("value", CommandWrapper::get_value);
|
||||||
|
|
||||||
engine.eval_with_scope::<()>(&mut scope, "Command.action(30)")?;
|
assert_eq!(
|
||||||
|
engine.eval_with_scope::<INT>(
|
||||||
|
&mut scope,
|
||||||
|
r"
|
||||||
|
// Drive the command object via the wrapper
|
||||||
|
Command.action(30);
|
||||||
|
Command.value
|
||||||
|
"
|
||||||
|
)?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(payload.get(), 42);
|
// Make sure the actions are properly performed
|
||||||
|
assert_eq!(command.borrow().get(), 42);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user