Merge pull request #114 from schungx/master

Add evaluate expressions.
This commit is contained in:
Stephen Chung 2020-03-23 09:22:36 +08:00 committed by GitHub
commit 8cb8b89474
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 491 additions and 139 deletions

View File

@ -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"

View File

@ -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.

View File

@ -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 {

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;
@ -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

View File

@ -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);

View File

@ -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()))

View File

@ -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()

View File

@ -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
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(())
}

View File

@ -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(())
} }