Add evaluate expressions.

This commit is contained in:
Stephen Chung 2020-03-22 21:03:58 +08:00
parent b6320c0eef
commit 1b4bcbcfdf
5 changed files with 313 additions and 77 deletions

View File

@ -183,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")?;
@ -200,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,
@ -241,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
---------------- ----------------

View File

@ -26,12 +26,26 @@ fn print_error(input: &str, err: EvalAltResult) {
}; };
// 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() - 1]; let last = lines[lines.len() - 1];
println!("{}{}", line_no, last); println!("{}{}", line_no, last);
println!("{}^ {}", padding(" ", line_no.len() + last.len() - 1), err);
let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
_ => err.to_string(),
};
println!(
"{}^ {}",
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
@ -39,12 +53,6 @@ fn print_error(input: &str, err: EvalAltResult) {
} }
p => { p => {
// Specific position // Specific position
let pos_text = format!(
" (line {}, position {})",
p.line().unwrap(),
p.position().unwrap()
);
println!("{}{}", line_no, lines[p.line().unwrap() - 1]); println!("{}{}", line_no, lines[p.line().unwrap() - 1]);
let err_text = match err { let err_text = match err {
@ -150,9 +158,17 @@ fn main() {
.and_then(|r| { .and_then(|r| {
ast_u = Some(r); ast_u = Some(r);
#[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(&mut scope, ast_u.as_ref().unwrap()));
engine.set_optimization_level(OptimizationLevel::None); 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())

View File

@ -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;
@ -416,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
@ -514,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

View File

@ -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,10 +2033,10 @@ 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
@ -2028,31 +2050,40 @@ fn parse_if<'a>(
} }
/// 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();
@ -2082,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)))
} }
@ -2092,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();
@ -2113,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
@ -2138,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
@ -2152,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();
@ -2201,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,
@ -2223,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();
@ -2250,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
@ -2350,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())),
}; };
@ -2363,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();
@ -2375,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())) {
@ -2388,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
View 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(())
}