Use scope offset for variable access.

This commit is contained in:
Stephen Chung 2020-04-28 23:05:03 +08:00
parent 9ad9dd52ab
commit 304c658f89
6 changed files with 328 additions and 158 deletions

View File

@ -1,7 +1,7 @@
//! Module that defines the extern API of `Engine`. //! Module that defines the extern API of `Engine`.
use crate::any::{Dynamic, Variant}; use crate::any::{Dynamic, Variant};
use crate::engine::{make_getter, make_setter, Engine, Map}; use crate::engine::{make_getter, make_setter, Engine, Map, State};
use crate::error::ParseError; use crate::error::ParseError;
use crate::fn_call::FuncArgs; use crate::fn_call::FuncArgs;
use crate::fn_register::RegisterFn; use crate::fn_register::RegisterFn;
@ -795,10 +795,12 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let mut state = State::new();
ast.0 ast.0
.iter() .iter()
.try_fold(().into(), |_, stmt| { .try_fold(().into(), |_, stmt| {
self.eval_stmt(scope, ast.1.as_ref(), stmt, 0) self.eval_stmt(scope, &mut state, ast.1.as_ref(), stmt, 0)
}) })
.or_else(|err| match *err { .or_else(|err| match *err {
EvalAltResult::Return(out, _) => Ok(out), EvalAltResult::Return(out, _) => Ok(out),
@ -858,10 +860,12 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
ast: &AST, ast: &AST,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
let mut state = State::new();
ast.0 ast.0
.iter() .iter()
.try_fold(().into(), |_, stmt| { .try_fold(().into(), |_, stmt| {
self.eval_stmt(scope, ast.1.as_ref(), stmt, 0) self.eval_stmt(scope, &mut state, ast.1.as_ref(), stmt, 0)
}) })
.map_or_else( .map_or_else(
|err| match *err { |err| match *err {

View File

@ -188,6 +188,23 @@ impl StaticVec {
} }
} }
/// A type that holds all the current states of the Engine.
#[derive(Debug, Clone, Hash, Copy)]
pub struct State {
/// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup.
/// In some situation, e.g. after running an `eval` statement, subsequent offsets may become mis-aligned.
/// When that happens, this flag is turned on to force a scope lookup by name.
pub always_search: bool,
}
impl State {
pub fn new() -> Self {
Self {
always_search: false,
}
}
}
/// A type that holds a library (`HashMap`) of script-defined functions. /// A type that holds a library (`HashMap`) of script-defined functions.
/// ///
/// Since script-defined functions have `Dynamic` parameters, functions with the same name /// Since script-defined functions have `Dynamic` parameters, functions with the same name
@ -439,11 +456,11 @@ fn search_scope<'a>(
name: &str, name: &str,
begin: Position, begin: Position,
) -> Result<(&'a mut Dynamic, ScopeEntryType), Box<EvalAltResult>> { ) -> Result<(&'a mut Dynamic, ScopeEntryType), Box<EvalAltResult>> {
let (index, typ) = scope let (index, _) = scope
.get(name) .get(name)
.ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), begin)))?; .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(name.into(), begin)))?;
Ok((scope.get_mut(index), typ)) Ok(scope.get_mut(index))
} }
impl Engine { impl Engine {
@ -601,6 +618,7 @@ impl Engine {
// Extern scope passed in which is not empty // Extern scope passed in which is not empty
Some(scope) if scope.len() > 0 => { Some(scope) if scope.len() > 0 => {
let scope_len = scope.len(); let scope_len = scope.len();
let mut state = State::new();
scope.extend( scope.extend(
// Put arguments into scope as variables - variable name is copied // Put arguments into scope as variables - variable name is copied
@ -614,7 +632,7 @@ impl Engine {
// Evaluate the function at one higher level of call depth // Evaluate the function at one higher level of call depth
let result = self let result = self
.eval_stmt(scope, fn_lib, &fn_def.body, level + 1) .eval_stmt(scope, &mut state, fn_lib, &fn_def.body, level + 1)
.or_else(|err| match *err { .or_else(|err| match *err {
// Convert return statement to return value // Convert return statement to return value
EvalAltResult::Return(x, _) => Ok(x), EvalAltResult::Return(x, _) => Ok(x),
@ -628,6 +646,7 @@ impl Engine {
// No new scope - create internal scope // No new scope - create internal scope
_ => { _ => {
let mut scope = Scope::new(); let mut scope = Scope::new();
let mut state = State::new();
scope.extend( scope.extend(
// Put arguments into scope as variables // Put arguments into scope as variables
@ -640,7 +659,7 @@ impl Engine {
// Evaluate the function at one higher level of call depth // Evaluate the function at one higher level of call depth
return self return self
.eval_stmt(&mut scope, fn_lib, &fn_def.body, level + 1) .eval_stmt(&mut scope, &mut state, fn_lib, &fn_def.body, level + 1)
.or_else(|err| match *err { .or_else(|err| match *err {
// Convert return statement to return value // Convert return statement to return value
EvalAltResult::Return(x, _) => Ok(x), EvalAltResult::Return(x, _) => Ok(x),
@ -784,7 +803,7 @@ impl Engine {
let mut args: Vec<_> = once(obj) let mut args: Vec<_> = once(obj)
.chain(idx_val.downcast_mut::<Array>().unwrap().iter_mut()) .chain(idx_val.downcast_mut::<Array>().unwrap().iter_mut())
.collect(); .collect();
let def_val = def_val.as_ref(); let def_val = def_val.as_deref();
// A function call is assumed to have side effects, so the value is changed // A function call is assumed to have side effects, so the value is changed
// TODO - Remove assumption of side effects by checking whether the first parameter is &mut // TODO - Remove assumption of side effects by checking whether the first parameter is &mut
self.exec_fn_call(fn_lib, fn_name, &mut args, def_val, *pos, 0).map(|v| (v, true)) self.exec_fn_call(fn_lib, fn_name, &mut args, def_val, *pos, 0).map(|v| (v, true))
@ -879,6 +898,7 @@ impl Engine {
fn eval_dot_index_chain( fn eval_dot_index_chain(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
state: &mut State,
fn_lib: &FunctionsLib, fn_lib: &FunctionsLib,
dot_lhs: &Expr, dot_lhs: &Expr,
dot_rhs: &Expr, dot_rhs: &Expr,
@ -889,12 +909,16 @@ impl Engine {
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let idx_values = &mut StaticVec::new(); let idx_values = &mut StaticVec::new();
self.eval_indexed_chain(scope, fn_lib, dot_rhs, idx_values, 0, level)?; self.eval_indexed_chain(scope, state, fn_lib, dot_rhs, idx_values, 0, level)?;
match dot_lhs { match dot_lhs {
// id.??? or id[???] // id.??? or id[???]
Expr::Variable(id, _, pos) => { Expr::Variable(id, index, pos) => {
let (target, typ) = search_scope(scope, id, *pos)?; let (target, typ) = if !state.always_search && *index > 0 {
scope.get_mut(scope.len() - *index)
} else {
search_scope(scope, id, *pos)?
};
// Constants cannot be modified // Constants cannot be modified
match typ { match typ {
@ -927,7 +951,7 @@ impl Engine {
} }
// {expr}.??? or {expr}[???] // {expr}.??? or {expr}[???]
expr => { expr => {
let val = self.eval_expr(scope, fn_lib, expr, level)?; let val = self.eval_expr(scope, state, fn_lib, expr, level)?;
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
fn_lib, fn_lib,
@ -952,6 +976,7 @@ impl Engine {
fn eval_indexed_chain( fn eval_indexed_chain(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
state: &mut State,
fn_lib: &FunctionsLib, fn_lib: &FunctionsLib,
expr: &Expr, expr: &Expr,
idx_values: &mut StaticVec, idx_values: &mut StaticVec,
@ -962,7 +987,7 @@ impl Engine {
Expr::FunctionCall(_, arg_exprs, _, _) => { Expr::FunctionCall(_, arg_exprs, _, _) => {
let arg_values = arg_exprs let arg_values = arg_exprs
.iter() .iter()
.map(|arg_expr| self.eval_expr(scope, fn_lib, arg_expr, level)) .map(|arg_expr| self.eval_expr(scope, state, fn_lib, arg_expr, level))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
idx_values.push(arg_values) idx_values.push(arg_values)
@ -973,15 +998,15 @@ impl Engine {
// Evaluate in left-to-right order // Evaluate in left-to-right order
let lhs_val = match lhs.as_ref() { let lhs_val = match lhs.as_ref() {
Expr::Property(_, _) => ().into(), // Store a placeholder in case of a property Expr::Property(_, _) => ().into(), // Store a placeholder in case of a property
_ => self.eval_expr(scope, fn_lib, lhs, level)?, _ => self.eval_expr(scope, state, fn_lib, lhs, level)?,
}; };
// Push in reverse order // Push in reverse order
self.eval_indexed_chain(scope, fn_lib, rhs, idx_values, size, level)?; self.eval_indexed_chain(scope, state, fn_lib, rhs, idx_values, size, level)?;
idx_values.push(lhs_val); idx_values.push(lhs_val);
} }
_ => idx_values.push(self.eval_expr(scope, fn_lib, expr, level)?), _ => idx_values.push(self.eval_expr(scope, state, fn_lib, expr, level)?),
} }
Ok(()) Ok(())
@ -1066,13 +1091,14 @@ impl Engine {
fn eval_in_expr( fn eval_in_expr(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
state: &mut State,
fn_lib: &FunctionsLib, fn_lib: &FunctionsLib,
lhs: &Expr, lhs: &Expr,
rhs: &Expr, rhs: &Expr,
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let mut lhs_value = self.eval_expr(scope, fn_lib, lhs, level)?; let mut lhs_value = self.eval_expr(scope, state, fn_lib, lhs, level)?;
let rhs_value = self.eval_expr(scope, fn_lib, rhs, level)?; let rhs_value = self.eval_expr(scope, state, fn_lib, rhs, level)?;
match rhs_value { match rhs_value {
Dynamic(Union::Array(mut rhs_value)) => { Dynamic(Union::Array(mut rhs_value)) => {
@ -1119,6 +1145,7 @@ impl Engine {
fn eval_expr( fn eval_expr(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
state: &mut State,
fn_lib: &FunctionsLib, fn_lib: &FunctionsLib,
expr: &Expr, expr: &Expr,
level: usize, level: usize,
@ -1129,15 +1156,18 @@ impl Engine {
Expr::FloatConstant(f, _) => Ok((*f).into()), Expr::FloatConstant(f, _) => Ok((*f).into()),
Expr::StringConstant(s, _) => Ok(s.to_string().into()), Expr::StringConstant(s, _) => Ok(s.to_string().into()),
Expr::CharConstant(c, _) => Ok((*c).into()), Expr::CharConstant(c, _) => Ok((*c).into()),
Expr::Variable(_, index, _) if !state.always_search && *index > 0 => {
Ok(scope.get_mut(scope.len() - *index).0.clone())
}
Expr::Variable(id, _, pos) => search_scope(scope, id, *pos).map(|(v, _)| v.clone()), Expr::Variable(id, _, pos) => search_scope(scope, id, *pos).map(|(v, _)| v.clone()),
Expr::Property(_, _) => panic!("unexpected property."), Expr::Property(_, _) => panic!("unexpected property."),
// Statement block // Statement block
Expr::Stmt(stmt, _) => self.eval_stmt(scope, fn_lib, stmt, level), Expr::Stmt(stmt, _) => self.eval_stmt(scope, state, fn_lib, stmt, level),
// lhs = rhs // lhs = rhs
Expr::Assignment(lhs, rhs, op_pos) => { Expr::Assignment(lhs, rhs, op_pos) => {
let rhs_val = self.eval_expr(scope, fn_lib, rhs, level)?; let rhs_val = self.eval_expr(scope, state, fn_lib, rhs, level)?;
match lhs.as_ref() { match lhs.as_ref() {
// name = rhs // name = rhs
@ -1151,7 +1181,7 @@ impl Engine {
Some((index, ScopeEntryType::Normal)) => { Some((index, ScopeEntryType::Normal)) => {
// Avoid referencing scope which is used below as mut // Avoid referencing scope which is used below as mut
*scope.get_mut(index) = rhs_val.clone(); *scope.get_mut(index).0 = rhs_val.clone();
Ok(rhs_val) Ok(rhs_val)
} }
@ -1165,7 +1195,7 @@ impl Engine {
Expr::Index(idx_lhs, idx_expr, op_pos) => { Expr::Index(idx_lhs, idx_expr, op_pos) => {
let new_val = Some(rhs_val); let new_val = Some(rhs_val);
self.eval_dot_index_chain( self.eval_dot_index_chain(
scope, fn_lib, idx_lhs, idx_expr, true, *op_pos, level, new_val, scope, state, fn_lib, idx_lhs, idx_expr, true, *op_pos, level, new_val,
) )
} }
// dot_lhs.dot_rhs = rhs // dot_lhs.dot_rhs = rhs
@ -1173,7 +1203,7 @@ impl Engine {
Expr::Dot(dot_lhs, dot_rhs, _) => { Expr::Dot(dot_lhs, dot_rhs, _) => {
let new_val = Some(rhs_val); let new_val = Some(rhs_val);
self.eval_dot_index_chain( self.eval_dot_index_chain(
scope, fn_lib, dot_lhs, dot_rhs, false, *op_pos, level, new_val, scope, state, fn_lib, dot_lhs, dot_rhs, false, *op_pos, level, new_val,
) )
} }
// Error assignment to constant // Error assignment to constant
@ -1193,22 +1223,22 @@ impl Engine {
// lhs[idx_expr] // lhs[idx_expr]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, idx_expr, op_pos) => { Expr::Index(lhs, idx_expr, op_pos) => self.eval_dot_index_chain(
self.eval_dot_index_chain(scope, fn_lib, lhs, idx_expr, true, *op_pos, level, None) scope, state, fn_lib, lhs, idx_expr, true, *op_pos, level, None,
} ),
// lhs.dot_rhs // lhs.dot_rhs
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Dot(lhs, dot_rhs, op_pos) => { Expr::Dot(lhs, dot_rhs, op_pos) => self.eval_dot_index_chain(
self.eval_dot_index_chain(scope, fn_lib, lhs, dot_rhs, false, *op_pos, level, None) scope, state, fn_lib, lhs, dot_rhs, false, *op_pos, level, None,
} ),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(contents, _) => { Expr::Array(contents, _) => {
let mut arr = Array::new(); let mut arr = Array::new();
contents.into_iter().try_for_each(|item| { contents.into_iter().try_for_each(|item| {
self.eval_expr(scope, fn_lib, item, level) self.eval_expr(scope, state, fn_lib, item, level)
.map(|val| arr.push(val)) .map(|val| arr.push(val))
})?; })?;
@ -1220,9 +1250,10 @@ impl Engine {
let mut map = Map::new(); let mut map = Map::new();
contents.into_iter().try_for_each(|(key, expr, _)| { contents.into_iter().try_for_each(|(key, expr, _)| {
self.eval_expr(scope, fn_lib, &expr, level).map(|val| { self.eval_expr(scope, state, fn_lib, &expr, level)
map.insert(key.clone(), val); .map(|val| {
}) map.insert(key.clone(), val);
})
})?; })?;
Ok(Dynamic(Union::Map(Box::new(map)))) Ok(Dynamic(Union::Map(Box::new(map))))
@ -1231,7 +1262,7 @@ impl Engine {
Expr::FunctionCall(fn_name, arg_exprs, def_val, pos) => { Expr::FunctionCall(fn_name, arg_exprs, def_val, pos) => {
let mut arg_values = arg_exprs let mut arg_values = arg_exprs
.iter() .iter()
.map(|expr| self.eval_expr(scope, fn_lib, expr, level)) .map(|expr| self.eval_expr(scope, state, fn_lib, expr, level))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let mut args: Vec<_> = arg_values.iter_mut().collect(); let mut args: Vec<_> = arg_values.iter_mut().collect();
@ -1242,27 +1273,34 @@ impl Engine {
&& !self.has_override(fn_lib, KEYWORD_EVAL) && !self.has_override(fn_lib, KEYWORD_EVAL)
{ {
// Evaluate the text string as a script // Evaluate the text string as a script
return self.eval_script_expr(scope, fn_lib, args[0], arg_exprs[0].position()); let result =
self.eval_script_expr(scope, fn_lib, args[0], arg_exprs[0].position());
// IMPORTANT! The eval may define new variables in the current scope!
// We can no longer trust the variable offsets.
state.always_search = true;
return result;
} }
// Normal function call // Normal function call
let def_val = def_val.as_ref(); let def_val = def_val.as_deref();
self.exec_fn_call(fn_lib, fn_name, &mut args, def_val, *pos, level) self.exec_fn_call(fn_lib, fn_name, &mut args, def_val, *pos, level)
} }
Expr::In(lhs, rhs, _) => { Expr::In(lhs, rhs, _) => {
self.eval_in_expr(scope, fn_lib, lhs.as_ref(), rhs.as_ref(), level) self.eval_in_expr(scope, state, fn_lib, lhs.as_ref(), rhs.as_ref(), level)
} }
Expr::And(lhs, rhs, _) => Ok((self Expr::And(lhs, rhs, _) => Ok((self
.eval_expr(scope, fn_lib,lhs.as_ref(), level)? .eval_expr(scope, state, fn_lib, lhs.as_ref(), level)?
.as_bool() .as_bool()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position())
})? })?
&& // Short-circuit using && && // Short-circuit using &&
self self
.eval_expr(scope, fn_lib,rhs.as_ref(), level)? .eval_expr(scope, state, fn_lib, rhs.as_ref(), level)?
.as_bool() .as_bool()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position())
@ -1270,14 +1308,14 @@ impl Engine {
.into()), .into()),
Expr::Or(lhs, rhs, _) => Ok((self Expr::Or(lhs, rhs, _) => Ok((self
.eval_expr(scope,fn_lib, lhs.as_ref(), level)? .eval_expr(scope, state, fn_lib, lhs.as_ref(), level)?
.as_bool() .as_bool()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position())
})? })?
|| // Short-circuit using || || // Short-circuit using ||
self self
.eval_expr(scope,fn_lib, rhs.as_ref(), level)? .eval_expr(scope, state, fn_lib, rhs.as_ref(), level)?
.as_bool() .as_bool()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position())
@ -1296,6 +1334,7 @@ impl Engine {
pub(crate) fn eval_stmt( pub(crate) fn eval_stmt(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
state: &mut State,
fn_lib: &FunctionsLib, fn_lib: &FunctionsLib,
stmt: &Stmt, stmt: &Stmt,
level: usize, level: usize,
@ -1306,7 +1345,7 @@ impl Engine {
// Expression as statement // Expression as statement
Stmt::Expr(expr) => { Stmt::Expr(expr) => {
let result = self.eval_expr(scope, fn_lib, expr, level)?; let result = self.eval_expr(scope, state, fn_lib, expr, level)?;
Ok(if let Expr::Assignment(_, _, _) = *expr.as_ref() { Ok(if let Expr::Assignment(_, _, _) = *expr.as_ref() {
// If it is an assignment, erase the result at the root // If it is an assignment, erase the result at the root
@ -1321,24 +1360,28 @@ impl Engine {
let prev_len = scope.len(); let prev_len = scope.len();
let result = block.iter().try_fold(().into(), |_, stmt| { let result = block.iter().try_fold(().into(), |_, stmt| {
self.eval_stmt(scope, fn_lib, stmt, level) self.eval_stmt(scope, state, fn_lib, stmt, level)
}); });
scope.rewind(prev_len); scope.rewind(prev_len);
// The impact of an eval statement goes away at the end of a block
// because any new variables introduced will go out of scope
state.always_search = false;
result result
} }
// If-else statement // If-else statement
Stmt::IfThenElse(guard, if_body, else_body) => self Stmt::IfThenElse(guard, if_body, else_body) => self
.eval_expr(scope, fn_lib, guard, level)? .eval_expr(scope, state, fn_lib, guard, level)?
.as_bool() .as_bool()
.map_err(|_| Box::new(EvalAltResult::ErrorLogicGuard(guard.position()))) .map_err(|_| Box::new(EvalAltResult::ErrorLogicGuard(guard.position())))
.and_then(|guard_val| { .and_then(|guard_val| {
if guard_val { if guard_val {
self.eval_stmt(scope, fn_lib, if_body, level) self.eval_stmt(scope, state, fn_lib, if_body, level)
} else if let Some(stmt) = else_body { } else if let Some(stmt) = else_body {
self.eval_stmt(scope, fn_lib, stmt.as_ref(), level) self.eval_stmt(scope, state, fn_lib, stmt.as_ref(), level)
} else { } else {
Ok(().into()) Ok(().into())
} }
@ -1346,8 +1389,11 @@ impl Engine {
// While loop // While loop
Stmt::While(guard, body) => loop { Stmt::While(guard, body) => loop {
match self.eval_expr(scope, fn_lib, guard, level)?.as_bool() { match self
Ok(true) => match self.eval_stmt(scope, fn_lib, body, level) { .eval_expr(scope, state, fn_lib, guard, level)?
.as_bool()
{
Ok(true) => match self.eval_stmt(scope, state, fn_lib, body, level) {
Ok(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
EvalAltResult::ErrorLoopBreak(false, _) => (), EvalAltResult::ErrorLoopBreak(false, _) => (),
@ -1364,7 +1410,7 @@ impl Engine {
// Loop statement // Loop statement
Stmt::Loop(body) => loop { Stmt::Loop(body) => loop {
match self.eval_stmt(scope, fn_lib, body, level) { match self.eval_stmt(scope, state, fn_lib, body, level) {
Ok(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
EvalAltResult::ErrorLoopBreak(false, _) => (), EvalAltResult::ErrorLoopBreak(false, _) => (),
@ -1376,7 +1422,7 @@ impl Engine {
// For loop // For loop
Stmt::For(name, expr, body) => { Stmt::For(name, expr, body) => {
let arr = self.eval_expr(scope, fn_lib, expr, level)?; let arr = self.eval_expr(scope, state, fn_lib, expr, level)?;
let tid = arr.type_id(); let tid = arr.type_id();
if let Some(iter_fn) = self.type_iterators.get(&tid).or_else(|| { if let Some(iter_fn) = self.type_iterators.get(&tid).or_else(|| {
@ -1390,9 +1436,9 @@ impl Engine {
let index = scope.len() - 1; let index = scope.len() - 1;
for a in iter_fn(arr) { for a in iter_fn(arr) {
*scope.get_mut(index) = a; *scope.get_mut(index).0 = a;
match self.eval_stmt(scope, fn_lib, body, level) { match self.eval_stmt(scope, state, fn_lib, body, level) {
Ok(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
EvalAltResult::ErrorLoopBreak(false, _) => (), EvalAltResult::ErrorLoopBreak(false, _) => (),
@ -1422,7 +1468,7 @@ impl Engine {
// Return value // Return value
Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => Err(Box::new( Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => Err(Box::new(
EvalAltResult::Return(self.eval_expr(scope, fn_lib, a, level)?, *pos), EvalAltResult::Return(self.eval_expr(scope, state, fn_lib, a, level)?, *pos),
)), )),
// Empty throw // Empty throw
@ -1432,7 +1478,7 @@ impl Engine {
// Throw value // Throw value
Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => { Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => {
let val = self.eval_expr(scope, fn_lib, a, level)?; let val = self.eval_expr(scope, state, fn_lib, a, level)?;
Err(Box::new(EvalAltResult::ErrorRuntime( Err(Box::new(EvalAltResult::ErrorRuntime(
val.take_string().unwrap_or_else(|_| "".to_string()), val.take_string().unwrap_or_else(|_| "".to_string()),
*pos, *pos,
@ -1441,7 +1487,7 @@ impl Engine {
// Let statement // Let statement
Stmt::Let(name, Some(expr), _) => { Stmt::Let(name, Some(expr), _) => {
let val = self.eval_expr(scope, fn_lib, expr, level)?; let val = self.eval_expr(scope, state, fn_lib, expr, level)?;
// TODO - avoid copying variable name in inner block? // TODO - avoid copying variable name in inner block?
scope.push_dynamic_value(name.clone(), ScopeEntryType::Normal, val, false); scope.push_dynamic_value(name.clone(), ScopeEntryType::Normal, val, false);
Ok(().into()) Ok(().into())
@ -1455,7 +1501,7 @@ impl Engine {
// Const statement // Const statement
Stmt::Const(name, expr, _) if expr.is_constant() => { Stmt::Const(name, expr, _) if expr.is_constant() => {
let val = self.eval_expr(scope, fn_lib, expr, level)?; let val = self.eval_expr(scope, state, fn_lib, expr, level)?;
// TODO - avoid copying variable name in inner block? // TODO - avoid copying variable name in inner block?
scope.push_dynamic_value(name.clone(), ScopeEntryType::Constant, val, true); scope.push_dynamic_value(name.clone(), ScopeEntryType::Constant, val, true);
Ok(().into()) Ok(().into())

View File

@ -559,7 +559,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// Do not call some special keywords // Do not call some special keywords
Expr::FunctionCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref())=> Expr::FunctionCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_ref())=>
Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos), Expr::FunctionCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos),
// Eagerly call functions // Eagerly call functions
Expr::FunctionCall(id, args, def_value, pos) Expr::FunctionCall(id, args, def_value, pos)
@ -569,7 +569,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
// First search in script-defined functions (can override built-in) // First search in script-defined functions (can override built-in)
if state.fn_lib.iter().find(|(name, len)| name == &id && *len == args.len()).is_some() { if state.fn_lib.iter().find(|(name, len)| name == &id && *len == args.len()).is_some() {
// A script-defined function overrides the built-in function - do not make the call // A script-defined function overrides the built-in function - do not make the call
return Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos); return Expr::FunctionCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos);
} }
let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect();
@ -591,7 +591,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
Some(arg_for_type_of.to_string().into()) Some(arg_for_type_of.to_string().into())
} else { } else {
// Otherwise use the default value, if any // Otherwise use the default value, if any
def_value.clone() def_value.clone().map(|v| *v)
} }
}).and_then(|result| map_dynamic_to_expr(result, pos)) }).and_then(|result| map_dynamic_to_expr(result, pos))
.map(|expr| { .map(|expr| {
@ -600,13 +600,13 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
}) })
).unwrap_or_else(|| ).unwrap_or_else(||
// Optimize function call arguments // Optimize function call arguments
Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos) Expr::FunctionCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos)
) )
} }
// id(args ..) -> optimize function call arguments // id(args ..) -> optimize function call arguments
Expr::FunctionCall(id, args, def_value, pos) => Expr::FunctionCall(id, args, def_value, pos) =>
Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos), Expr::FunctionCall(id, Box::new(args.into_iter().map(|a| optimize_expr(a, state)).collect()), def_value, pos),
// constant-name // constant-name
Expr::Variable(name, _, pos) if state.contains_constant(&name) => { Expr::Variable(name, _, pos) if state.contains_constant(&name) => {

View File

@ -183,6 +183,33 @@ pub enum ReturnType {
Exception, Exception,
} }
/// A type that encapsulates a local stack with variable names to simulate an actual runtime scope.
struct Stack(Vec<String>);
impl Stack {
pub fn new() -> Self {
Self(Vec::new())
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn push(&mut self, name: String) {
self.0.push(name);
}
pub fn rewind(&mut self, len: usize) {
self.0.truncate(len);
}
pub fn find(&self, name: &str) -> usize {
self.0
.iter()
.rev()
.enumerate()
.find(|(_, n)| *n == name)
.map(|(i, _)| i + 1)
.unwrap_or(0)
}
}
/// A statement. /// A statement.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Stmt { pub enum Stmt {
@ -289,7 +316,12 @@ pub enum Expr {
/// func(expr, ... ) /// func(expr, ... )
/// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls /// Use `Cow<'static, str>` because a lot of operators (e.g. `==`, `>=`) are implemented as function calls
/// and the function names are predictable, so no need to allocate a new `String`. /// and the function names are predictable, so no need to allocate a new `String`.
FunctionCall(Cow<'static, str>, Vec<Expr>, Option<Dynamic>, Position), FunctionCall(
Cow<'static, str>,
Box<Vec<Expr>>,
Option<Box<Dynamic>>,
Position,
),
/// expr = expr /// expr = expr
Assignment(Box<Expr>, Box<Expr>, Position), Assignment(Box<Expr>, Box<Expr>, Position),
/// lhs.rhs /// lhs.rhs
@ -544,6 +576,7 @@ fn match_token(input: &mut Peekable<TokenIterator>, token: Token) -> Result<bool
/// Parse ( expr ) /// Parse ( expr )
fn parse_paren_expr<'a>( fn parse_paren_expr<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
begin: Position, begin: Position,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, Box<ParseError>> {
@ -551,7 +584,7 @@ fn parse_paren_expr<'a>(
return Ok(Expr::Unit(begin)); return Ok(Expr::Unit(begin));
} }
let expr = parse_expr(input, allow_stmt_expr)?; let expr = parse_expr(input, stack, allow_stmt_expr)?;
match input.next().unwrap() { match input.next().unwrap() {
// ( xxx ) // ( xxx )
@ -569,12 +602,13 @@ fn parse_paren_expr<'a>(
/// Parse a function call. /// Parse a function call.
fn parse_call_expr<'a>( fn parse_call_expr<'a>(
id: String,
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
id: String,
begin: Position, begin: Position,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, Box<ParseError>> {
let mut args_expr_list = Vec::new(); let mut args = Vec::new();
match input.peek().unwrap() { match input.peek().unwrap() {
// id <EOF> // id <EOF>
@ -590,19 +624,19 @@ fn parse_call_expr<'a>(
// id() // id()
(Token::RightParen, _) => { (Token::RightParen, _) => {
eat_token(input, Token::RightParen); eat_token(input, Token::RightParen);
return Ok(Expr::FunctionCall(id.into(), args_expr_list, None, begin)); return Ok(Expr::FunctionCall(id.into(), Box::new(args), None, begin));
} }
// id... // id...
_ => (), _ => (),
} }
loop { loop {
args_expr_list.push(parse_expr(input, allow_stmt_expr)?); args.push(parse_expr(input, stack, allow_stmt_expr)?);
match input.peek().unwrap() { match input.peek().unwrap() {
(Token::RightParen, _) => { (Token::RightParen, _) => {
eat_token(input, Token::RightParen); eat_token(input, Token::RightParen);
return Ok(Expr::FunctionCall(id.into(), args_expr_list, None, begin)); return Ok(Expr::FunctionCall(id.into(), Box::new(args), None, begin));
} }
(Token::Comma, _) => { (Token::Comma, _) => {
eat_token(input, Token::Comma); eat_token(input, Token::Comma);
@ -631,12 +665,13 @@ fn parse_call_expr<'a>(
/// Parse an indexing chain. /// Parse an indexing chain.
/// Indexing binds to the right, so this call parses all possible levels of indexing following in the input. /// Indexing binds to the right, so this call parses all possible levels of indexing following in the input.
fn parse_index_chain<'a>( fn parse_index_chain<'a>(
lhs: Expr,
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
lhs: Expr,
pos: Position, pos: Position,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, Box<ParseError>> {
let idx_expr = parse_expr(input, allow_stmt_expr)?; let idx_expr = parse_expr(input, stack, allow_stmt_expr)?;
// Check type of indexing - must be integer or string // Check type of indexing - must be integer or string
match &idx_expr { match &idx_expr {
@ -751,7 +786,8 @@ fn parse_index_chain<'a>(
(Token::LeftBracket, _) => { (Token::LeftBracket, _) => {
let follow_pos = eat_token(input, Token::LeftBracket); let follow_pos = eat_token(input, Token::LeftBracket);
// Recursively parse the indexing chain, right-binding each // Recursively parse the indexing chain, right-binding each
let follow = parse_index_chain(idx_expr, input, follow_pos, allow_stmt_expr)?; let follow =
parse_index_chain(input, stack, idx_expr, follow_pos, allow_stmt_expr)?;
// Indexing binds to right // Indexing binds to right
Ok(Expr::Index(Box::new(lhs), Box::new(follow), pos)) Ok(Expr::Index(Box::new(lhs), Box::new(follow), pos))
} }
@ -771,6 +807,7 @@ fn parse_index_chain<'a>(
/// Parse an array literal. /// Parse an array literal.
fn parse_array_literal<'a>( fn parse_array_literal<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
begin: Position, begin: Position,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, Box<ParseError>> {
@ -778,7 +815,7 @@ fn parse_array_literal<'a>(
if !match_token(input, Token::RightBracket)? { if !match_token(input, Token::RightBracket)? {
while !input.peek().unwrap().0.is_eof() { while !input.peek().unwrap().0.is_eof() {
arr.push(parse_expr(input, allow_stmt_expr)?); arr.push(parse_expr(input, stack, allow_stmt_expr)?);
match input.peek().unwrap() { match input.peek().unwrap() {
(Token::Comma, _) => eat_token(input, Token::Comma), (Token::Comma, _) => eat_token(input, Token::Comma),
@ -812,6 +849,7 @@ fn parse_array_literal<'a>(
/// Parse a map literal. /// Parse a map literal.
fn parse_map_literal<'a>( fn parse_map_literal<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
begin: Position, begin: Position,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, Box<ParseError>> {
@ -853,7 +891,7 @@ fn parse_map_literal<'a>(
} }
}; };
let expr = parse_expr(input, allow_stmt_expr)?; let expr = parse_expr(input, stack, allow_stmt_expr)?;
map.push((name, expr, pos)); map.push((name, expr, pos));
@ -899,13 +937,14 @@ fn parse_map_literal<'a>(
/// Parse a primary expression. /// Parse a primary expression.
fn parse_primary<'a>( fn parse_primary<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, Box<ParseError>> {
let (token, pos) = match input.peek().unwrap() { let (token, pos) = match input.peek().unwrap() {
// { - block statement as expression // { - block statement as expression
(Token::LeftBrace, pos) if allow_stmt_expr => { (Token::LeftBrace, pos) if allow_stmt_expr => {
let pos = *pos; let pos = *pos;
return parse_block(input, false, allow_stmt_expr) return parse_block(input, stack, false, allow_stmt_expr)
.map(|block| Expr::Stmt(Box::new(block), pos)); .map(|block| Expr::Stmt(Box::new(block), pos));
} }
(Token::EOF, pos) => return Err(PERR::UnexpectedEOF.into_err(*pos)), (Token::EOF, pos) => return Err(PERR::UnexpectedEOF.into_err(*pos)),
@ -918,12 +957,15 @@ fn parse_primary<'a>(
Token::FloatConstant(x) => Expr::FloatConstant(x, pos), Token::FloatConstant(x) => Expr::FloatConstant(x, pos),
Token::CharConstant(c) => Expr::CharConstant(c, pos), Token::CharConstant(c) => Expr::CharConstant(c, pos),
Token::StringConst(s) => Expr::StringConstant(s, pos), Token::StringConst(s) => Expr::StringConstant(s, pos),
Token::Identifier(s) => Expr::Variable(s, 0, pos), Token::Identifier(s) => {
Token::LeftParen => parse_paren_expr(input, pos, allow_stmt_expr)?, let index = stack.find(&s);
Expr::Variable(s, index, pos)
}
Token::LeftParen => parse_paren_expr(input, stack, pos, allow_stmt_expr)?,
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Token::LeftBracket => parse_array_literal(input, pos, allow_stmt_expr)?, Token::LeftBracket => parse_array_literal(input, stack, pos, allow_stmt_expr)?,
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Token::MapStart => parse_map_literal(input, pos, allow_stmt_expr)?, Token::MapStart => parse_map_literal(input, stack, pos, allow_stmt_expr)?,
Token::True => Expr::True(pos), Token::True => Expr::True(pos),
Token::False => Expr::False(pos), Token::False => Expr::False(pos),
Token::LexError(err) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), Token::LexError(err) => return Err(PERR::BadInput(err.to_string()).into_err(pos)),
@ -946,10 +988,12 @@ fn parse_primary<'a>(
// Function call // Function call
(Expr::Variable(id, _, pos), Token::LeftParen) (Expr::Variable(id, _, pos), Token::LeftParen)
| (Expr::Property(id, pos), Token::LeftParen) => { | (Expr::Property(id, pos), Token::LeftParen) => {
parse_call_expr(id, input, pos, allow_stmt_expr)? parse_call_expr(input, stack, id, pos, allow_stmt_expr)?
} }
// Indexing // Indexing
(expr, Token::LeftBracket) => parse_index_chain(expr, input, pos, allow_stmt_expr)?, (expr, Token::LeftBracket) => {
parse_index_chain(input, stack, expr, pos, allow_stmt_expr)?
}
// Unknown postfix operator // Unknown postfix operator
(expr, token) => panic!("unknown postfix operator {:?} for {:?}", token, expr), (expr, token) => panic!("unknown postfix operator {:?} for {:?}", token, expr),
} }
@ -961,6 +1005,7 @@ fn parse_primary<'a>(
/// Parse a potential unary operator. /// Parse a potential unary operator.
fn parse_unary<'a>( fn parse_unary<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, Box<ParseError>> {
match input.peek().unwrap() { match input.peek().unwrap() {
@ -968,7 +1013,7 @@ fn parse_unary<'a>(
(Token::If, pos) => { (Token::If, pos) => {
let pos = *pos; let pos = *pos;
Ok(Expr::Stmt( Ok(Expr::Stmt(
Box::new(parse_if(input, false, allow_stmt_expr)?), Box::new(parse_if(input, stack, false, allow_stmt_expr)?),
pos, pos,
)) ))
} }
@ -976,7 +1021,7 @@ fn parse_unary<'a>(
(Token::UnaryMinus, _) => { (Token::UnaryMinus, _) => {
let pos = eat_token(input, Token::UnaryMinus); let pos = eat_token(input, Token::UnaryMinus);
match parse_unary(input, allow_stmt_expr)? { match parse_unary(input, stack, allow_stmt_expr)? {
// Negative integer // Negative integer
Expr::IntegerConstant(i, _) => i Expr::IntegerConstant(i, _) => i
.checked_neg() .checked_neg()
@ -1001,49 +1046,51 @@ fn parse_unary<'a>(
Expr::FloatConstant(f, pos) => Ok(Expr::FloatConstant(-f, pos)), Expr::FloatConstant(f, pos) => Ok(Expr::FloatConstant(-f, pos)),
// Call negative function // Call negative function
expr => Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)), e => Ok(Expr::FunctionCall("-".into(), Box::new(vec![e]), None, pos)),
} }
} }
// +expr // +expr
(Token::UnaryPlus, _) => { (Token::UnaryPlus, _) => {
eat_token(input, Token::UnaryPlus); eat_token(input, Token::UnaryPlus);
parse_unary(input, allow_stmt_expr) parse_unary(input, stack, allow_stmt_expr)
} }
// !expr // !expr
(Token::Bang, _) => { (Token::Bang, _) => {
let pos = eat_token(input, Token::Bang); let pos = eat_token(input, Token::Bang);
Ok(Expr::FunctionCall( Ok(Expr::FunctionCall(
"!".into(), "!".into(),
vec![parse_primary(input, allow_stmt_expr)?], Box::new(vec![parse_primary(input, stack, allow_stmt_expr)?]),
Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false Some(Box::new(false.into())), // NOT operator, when operating on invalid operand, defaults to false
pos, pos,
)) ))
} }
// <EOF> // <EOF>
(Token::EOF, pos) => Err(PERR::UnexpectedEOF.into_err(*pos)), (Token::EOF, pos) => Err(PERR::UnexpectedEOF.into_err(*pos)),
// All other tokens // All other tokens
_ => parse_primary(input, allow_stmt_expr), _ => parse_primary(input, stack, allow_stmt_expr),
} }
} }
fn parse_assignment_stmt<'a>( fn parse_assignment_stmt<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
lhs: Expr, lhs: Expr,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, Box<ParseError>> {
let pos = eat_token(input, Token::Equals); let pos = eat_token(input, Token::Equals);
let rhs = parse_expr(input, allow_stmt_expr)?; let rhs = parse_expr(input, stack, allow_stmt_expr)?;
Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)) Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos))
} }
/// Parse an operator-assignment expression. /// Parse an operator-assignment expression.
fn parse_op_assignment_stmt<'a>( fn parse_op_assignment_stmt<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
lhs: Expr, lhs: Expr,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, Box<ParseError>> {
let (op, pos) = match *input.peek().unwrap() { let (op, pos) = match *input.peek().unwrap() {
(Token::Equals, _) => return parse_assignment_stmt(input, lhs, allow_stmt_expr), (Token::Equals, _) => return parse_assignment_stmt(input, stack, lhs, allow_stmt_expr),
(Token::PlusAssign, pos) => ("+", pos), (Token::PlusAssign, pos) => ("+", pos),
(Token::MinusAssign, pos) => ("-", pos), (Token::MinusAssign, pos) => ("-", pos),
(Token::MultiplyAssign, pos) => ("*", pos), (Token::MultiplyAssign, pos) => ("*", pos),
@ -1061,10 +1108,10 @@ fn parse_op_assignment_stmt<'a>(
input.next(); input.next();
let lhs_copy = lhs.clone(); let lhs_copy = lhs.clone();
let rhs = parse_expr(input, allow_stmt_expr)?; let rhs = parse_expr(input, stack, allow_stmt_expr)?;
// lhs op= rhs -> lhs = op(lhs, rhs) // lhs op= rhs -> lhs = op(lhs, rhs)
let rhs_expr = Expr::FunctionCall(op.into(), vec![lhs_copy, rhs], None, pos); let rhs_expr = Expr::FunctionCall(op.into(), Box::new(vec![lhs_copy, rhs]), None, pos);
Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs_expr), pos)) Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs_expr), pos))
} }
@ -1240,6 +1287,7 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, Box<Pars
/// Parse a binary expression. /// Parse a binary expression.
fn parse_binary_op<'a>( fn parse_binary_op<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
parent_precedence: u8, parent_precedence: u8,
lhs: Expr, lhs: Expr,
allow_stmt_expr: bool, allow_stmt_expr: bool,
@ -1262,7 +1310,7 @@ fn parse_binary_op<'a>(
let (op_token, pos) = input.next().unwrap(); let (op_token, pos) = input.next().unwrap();
let rhs = parse_unary(input, allow_stmt_expr)?; let rhs = parse_unary(input, stack, allow_stmt_expr)?;
let next_precedence = input.peek().unwrap().0.precedence(); let next_precedence = input.peek().unwrap().0.precedence();
@ -1271,48 +1319,90 @@ 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, allow_stmt_expr)? parse_binary_op(input, stack, 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
}; };
current_lhs = match op_token { let cmp_default = Some(Box::new(false.into()));
Token::Plus => Expr::FunctionCall("+".into(), vec![current_lhs, rhs], None, pos),
Token::Minus => Expr::FunctionCall("-".into(), vec![current_lhs, rhs], None, pos),
Token::Multiply => Expr::FunctionCall("*".into(), vec![current_lhs, rhs], None, pos),
Token::Divide => Expr::FunctionCall("/".into(), vec![current_lhs, rhs], None, pos),
Token::LeftShift => Expr::FunctionCall("<<".into(), vec![current_lhs, rhs], None, pos), current_lhs = match op_token {
Token::RightShift => Expr::FunctionCall(">>".into(), vec![current_lhs, rhs], None, pos), Token::Plus => {
Token::Modulo => Expr::FunctionCall("%".into(), vec![current_lhs, rhs], None, pos), Expr::FunctionCall("+".into(), Box::new(vec![current_lhs, rhs]), None, pos)
Token::PowerOf => Expr::FunctionCall("~".into(), vec![current_lhs, rhs], None, pos), }
Token::Minus => {
Expr::FunctionCall("-".into(), Box::new(vec![current_lhs, rhs]), None, pos)
}
Token::Multiply => {
Expr::FunctionCall("*".into(), Box::new(vec![current_lhs, rhs]), None, pos)
}
Token::Divide => {
Expr::FunctionCall("/".into(), Box::new(vec![current_lhs, rhs]), None, pos)
}
Token::LeftShift => {
Expr::FunctionCall("<<".into(), Box::new(vec![current_lhs, rhs]), None, pos)
}
Token::RightShift => {
Expr::FunctionCall(">>".into(), Box::new(vec![current_lhs, rhs]), None, pos)
}
Token::Modulo => {
Expr::FunctionCall("%".into(), Box::new(vec![current_lhs, rhs]), None, pos)
}
Token::PowerOf => {
Expr::FunctionCall("~".into(), Box::new(vec![current_lhs, rhs]), None, pos)
}
// Comparison operators default to false when passed invalid operands // Comparison operators default to false when passed invalid operands
Token::EqualsTo => { Token::EqualsTo => Expr::FunctionCall(
Expr::FunctionCall("==".into(), vec![current_lhs, rhs], Some(false.into()), pos) "==".into(),
} Box::new(vec![current_lhs, rhs]),
Token::NotEqualsTo => { cmp_default,
Expr::FunctionCall("!=".into(), vec![current_lhs, rhs], Some(false.into()), pos) pos,
} ),
Token::LessThan => { Token::NotEqualsTo => Expr::FunctionCall(
Expr::FunctionCall("<".into(), vec![current_lhs, rhs], Some(false.into()), pos) "!=".into(),
} Box::new(vec![current_lhs, rhs]),
Token::LessThanEqualsTo => { cmp_default,
Expr::FunctionCall("<=".into(), vec![current_lhs, rhs], Some(false.into()), pos) pos,
} ),
Token::GreaterThan => { Token::LessThan => Expr::FunctionCall(
Expr::FunctionCall(">".into(), vec![current_lhs, rhs], Some(false.into()), pos) "<".into(),
} Box::new(vec![current_lhs, rhs]),
Token::GreaterThanEqualsTo => { cmp_default,
Expr::FunctionCall(">=".into(), vec![current_lhs, rhs], Some(false.into()), pos) pos,
} ),
Token::LessThanEqualsTo => Expr::FunctionCall(
"<=".into(),
Box::new(vec![current_lhs, rhs]),
cmp_default,
pos,
),
Token::GreaterThan => Expr::FunctionCall(
">".into(),
Box::new(vec![current_lhs, rhs]),
cmp_default,
pos,
),
Token::GreaterThanEqualsTo => Expr::FunctionCall(
">=".into(),
Box::new(vec![current_lhs, rhs]),
cmp_default,
pos,
),
Token::Or => Expr::Or(Box::new(current_lhs), Box::new(rhs), pos), Token::Or => Expr::Or(Box::new(current_lhs), Box::new(rhs), pos),
Token::And => Expr::And(Box::new(current_lhs), Box::new(rhs), pos), Token::And => Expr::And(Box::new(current_lhs), Box::new(rhs), pos),
Token::Ampersand => Expr::FunctionCall("&".into(), vec![current_lhs, rhs], None, pos), Token::Ampersand => {
Token::Pipe => Expr::FunctionCall("|".into(), vec![current_lhs, rhs], None, pos), Expr::FunctionCall("&".into(), Box::new(vec![current_lhs, rhs]), None, pos)
Token::XOr => Expr::FunctionCall("^".into(), vec![current_lhs, rhs], None, pos), }
Token::Pipe => {
Expr::FunctionCall("|".into(), Box::new(vec![current_lhs, rhs]), None, pos)
}
Token::XOr => {
Expr::FunctionCall("^".into(), Box::new(vec![current_lhs, rhs]), None, pos)
}
Token::In => make_in_expr(current_lhs, rhs, pos)?, Token::In => make_in_expr(current_lhs, rhs, pos)?,
@ -1327,10 +1417,11 @@ fn parse_binary_op<'a>(
/// Parse an expression. /// Parse an expression.
fn parse_expr<'a>( fn parse_expr<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Expr, Box<ParseError>> { ) -> Result<Expr, Box<ParseError>> {
let lhs = parse_unary(input, allow_stmt_expr)?; let lhs = parse_unary(input, stack, allow_stmt_expr)?;
parse_binary_op(input, 1, lhs, allow_stmt_expr) parse_binary_op(input, stack, 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 `{}`).
@ -1380,6 +1471,7 @@ fn ensure_not_assignment<'a>(
/// Parse an if statement. /// Parse an if statement.
fn parse_if<'a>( fn parse_if<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
breakable: bool, breakable: bool,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, Box<ParseError>> {
@ -1388,18 +1480,18 @@ fn parse_if<'a>(
// 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, allow_stmt_expr)?; let guard = parse_expr(input, stack, allow_stmt_expr)?;
ensure_not_assignment(input)?; ensure_not_assignment(input)?;
let if_body = parse_block(input, breakable, allow_stmt_expr)?; let if_body = parse_block(input, stack, breakable, allow_stmt_expr)?;
// if guard { if_body } else ... // if guard { if_body } else ...
let else_body = if match_token(input, Token::Else).unwrap_or(false) { let else_body = if match_token(input, Token::Else).unwrap_or(false) {
Some(Box::new(if let (Token::If, _) = input.peek().unwrap() { Some(Box::new(if let (Token::If, _) = input.peek().unwrap() {
// if guard { if_body } else if ... // if guard { if_body } else if ...
parse_if(input, breakable, allow_stmt_expr)? parse_if(input, stack, breakable, allow_stmt_expr)?
} else { } else {
// if guard { if_body } else { else-body } // if guard { if_body } else { else-body }
parse_block(input, breakable, allow_stmt_expr)? parse_block(input, stack, breakable, allow_stmt_expr)?
})) }))
} else { } else {
None None
@ -1415,6 +1507,7 @@ fn parse_if<'a>(
/// Parse a while loop. /// Parse a while loop.
fn parse_while<'a>( fn parse_while<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, Box<ParseError>> {
// while ... // while ...
@ -1422,9 +1515,9 @@ fn parse_while<'a>(
// 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, allow_stmt_expr)?; let guard = parse_expr(input, stack, allow_stmt_expr)?;
ensure_not_assignment(input)?; ensure_not_assignment(input)?;
let body = parse_block(input, true, allow_stmt_expr)?; let body = parse_block(input, stack, true, allow_stmt_expr)?;
Ok(Stmt::While(Box::new(guard), Box::new(body))) Ok(Stmt::While(Box::new(guard), Box::new(body)))
} }
@ -1432,13 +1525,14 @@ fn parse_while<'a>(
/// Parse a loop statement. /// Parse a loop statement.
fn parse_loop<'a>( fn parse_loop<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, Box<ParseError>> {
// loop ... // loop ...
eat_token(input, Token::Loop); eat_token(input, Token::Loop);
// loop { body } // loop { body }
let body = parse_block(input, true, allow_stmt_expr)?; let body = parse_block(input, stack, true, allow_stmt_expr)?;
Ok(Stmt::Loop(Box::new(body))) Ok(Stmt::Loop(Box::new(body)))
} }
@ -1446,6 +1540,7 @@ fn parse_loop<'a>(
/// Parse a for loop. /// Parse a for loop.
fn parse_for<'a>( fn parse_for<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, Box<ParseError>> {
// for ... // for ...
@ -1477,8 +1572,14 @@ fn parse_for<'a>(
// 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, allow_stmt_expr)?; let expr = parse_expr(input, stack, allow_stmt_expr)?;
let body = parse_block(input, true, allow_stmt_expr)?;
let prev_len = stack.len();
stack.push(name.clone());
let body = parse_block(input, stack, true, allow_stmt_expr)?;
stack.rewind(prev_len);
Ok(Stmt::For(name, Box::new(expr), Box::new(body))) Ok(Stmt::For(name, Box::new(expr), Box::new(body)))
} }
@ -1486,6 +1587,7 @@ fn parse_for<'a>(
/// Parse a variable definition statement. /// Parse a variable definition statement.
fn parse_let<'a>( fn parse_let<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
var_type: ScopeEntryType, var_type: ScopeEntryType,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, Box<ParseError>> {
@ -1502,13 +1604,17 @@ fn parse_let<'a>(
// let name = ... // let name = ...
if match_token(input, Token::Equals)? { if match_token(input, Token::Equals)? {
// let name = expr // let name = expr
let init_value = parse_expr(input, allow_stmt_expr)?; let init_value = parse_expr(input, stack, allow_stmt_expr)?;
match var_type { match var_type {
// let name = expr // let name = expr
ScopeEntryType::Normal => Ok(Stmt::Let(name, Some(Box::new(init_value)), pos)), ScopeEntryType::Normal => {
stack.push(name.clone());
Ok(Stmt::Let(name, Some(Box::new(init_value)), pos))
}
// const name = { expr:constant } // const name = { expr:constant }
ScopeEntryType::Constant if init_value.is_constant() => { ScopeEntryType::Constant if init_value.is_constant() => {
stack.push(name.clone());
Ok(Stmt::Const(name, Box::new(init_value), pos)) Ok(Stmt::Const(name, Box::new(init_value), pos))
} }
// const name = expr - error // const name = expr - error
@ -1525,6 +1631,7 @@ fn parse_let<'a>(
/// Parse a statement block. /// Parse a statement block.
fn parse_block<'a>( fn parse_block<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
breakable: bool, breakable: bool,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, Box<ParseError>> {
@ -1540,10 +1647,11 @@ fn parse_block<'a>(
}; };
let mut statements = Vec::new(); let mut statements = Vec::new();
let prev_len = stack.len();
while !match_token(input, Token::RightBrace)? { while !match_token(input, Token::RightBrace)? {
// Parse statements inside the block // Parse statements inside the block
let stmt = parse_stmt(input, breakable, allow_stmt_expr)?; let stmt = parse_stmt(input, stack, 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();
@ -1579,22 +1687,26 @@ fn parse_block<'a>(
} }
} }
stack.rewind(prev_len);
Ok(Stmt::Block(statements, pos)) Ok(Stmt::Block(statements, pos))
} }
/// Parse an expression as a statement. /// Parse an expression as a statement.
fn parse_expr_stmt<'a>( fn parse_expr_stmt<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, Box<ParseError>> {
let expr = parse_expr(input, allow_stmt_expr)?; let expr = parse_expr(input, stack, allow_stmt_expr)?;
let expr = parse_op_assignment_stmt(input, expr, allow_stmt_expr)?; let expr = parse_op_assignment_stmt(input, stack, expr, allow_stmt_expr)?;
Ok(Stmt::Expr(Box::new(expr))) Ok(Stmt::Expr(Box::new(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>>,
stack: &mut Stack,
breakable: bool, breakable: bool,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<Stmt, Box<ParseError>> { ) -> Result<Stmt, Box<ParseError>> {
@ -1607,16 +1719,16 @@ fn parse_stmt<'a>(
// Semicolon - empty statement // Semicolon - empty statement
Token::SemiColon => Ok(Stmt::Noop(*pos)), Token::SemiColon => Ok(Stmt::Noop(*pos)),
Token::LeftBrace => parse_block(input, breakable, allow_stmt_expr), Token::LeftBrace => parse_block(input, stack, breakable, allow_stmt_expr),
// fn ... // fn ...
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Token::Fn => Err(PERR::WrongFnDefinition.into_err(*pos)), Token::Fn => Err(PERR::WrongFnDefinition.into_err(*pos)),
Token::If => parse_if(input, breakable, allow_stmt_expr), Token::If => parse_if(input, stack, breakable, allow_stmt_expr),
Token::While => parse_while(input, allow_stmt_expr), Token::While => parse_while(input, stack, allow_stmt_expr),
Token::Loop => parse_loop(input, allow_stmt_expr), Token::Loop => parse_loop(input, stack, allow_stmt_expr),
Token::For => parse_for(input, allow_stmt_expr), Token::For => parse_for(input, stack, allow_stmt_expr),
Token::Continue if breakable => { Token::Continue if breakable => {
let pos = eat_token(input, Token::Continue); let pos = eat_token(input, Token::Continue);
@ -1644,23 +1756,24 @@ fn parse_stmt<'a>(
(Token::SemiColon, _) => Ok(Stmt::ReturnWithVal(None, return_type, pos)), (Token::SemiColon, _) => Ok(Stmt::ReturnWithVal(None, return_type, pos)),
// `return` or `throw` with expression // `return` or `throw` with expression
(_, _) => { (_, _) => {
let expr = parse_expr(input, allow_stmt_expr)?; let expr = parse_expr(input, stack, 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::Let => parse_let(input, ScopeEntryType::Normal, allow_stmt_expr), Token::Let => parse_let(input, stack, ScopeEntryType::Normal, allow_stmt_expr),
Token::Const => parse_let(input, ScopeEntryType::Constant, allow_stmt_expr), Token::Const => parse_let(input, stack, ScopeEntryType::Constant, allow_stmt_expr),
_ => parse_expr_stmt(input, allow_stmt_expr), _ => parse_expr_stmt(input, stack, allow_stmt_expr),
} }
} }
/// Parse a function definition. /// Parse a function definition.
fn parse_fn<'a>( fn parse_fn<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
stack: &mut Stack,
allow_stmt_expr: bool, allow_stmt_expr: bool,
) -> Result<FnDef, Box<ParseError>> { ) -> Result<FnDef, Box<ParseError>> {
let pos = input.next().expect("should be fn").1; let pos = input.next().expect("should be fn").1;
@ -1683,7 +1796,10 @@ fn parse_fn<'a>(
loop { loop {
match input.next().unwrap() { match input.next().unwrap() {
(Token::Identifier(s), pos) => params.push((s, pos)), (Token::Identifier(s), pos) => {
stack.push(s.clone());
params.push((s, pos))
}
(Token::LexError(err), pos) => { (Token::LexError(err), pos) => {
return Err(PERR::BadInput(err.to_string()).into_err(pos)) return Err(PERR::BadInput(err.to_string()).into_err(pos))
} }
@ -1721,7 +1837,7 @@ fn parse_fn<'a>(
// Parse function body // Parse function body
let body = match input.peek().unwrap() { let body = match input.peek().unwrap() {
(Token::LeftBrace, _) => parse_block(input, false, allow_stmt_expr)?, (Token::LeftBrace, _) => parse_block(input, stack, false, allow_stmt_expr)?,
(_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), (_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)),
}; };
@ -1741,7 +1857,8 @@ pub fn parse_global_expr<'a>(
scope: &Scope, scope: &Scope,
optimization_level: OptimizationLevel, optimization_level: OptimizationLevel,
) -> Result<AST, Box<ParseError>> { ) -> Result<AST, Box<ParseError>> {
let expr = parse_expr(input, false)?; let mut stack = Stack::new();
let expr = parse_expr(input, &mut stack, false)?;
match input.peek().unwrap() { match input.peek().unwrap() {
(Token::EOF, _) => (), (Token::EOF, _) => (),
@ -1769,20 +1886,22 @@ fn parse_global_level<'a>(
) -> Result<(Vec<Stmt>, HashMap<u64, FnDef>), Box<ParseError>> { ) -> Result<(Vec<Stmt>, HashMap<u64, FnDef>), Box<ParseError>> {
let mut statements = Vec::<Stmt>::new(); let mut statements = Vec::<Stmt>::new();
let mut functions = HashMap::<u64, FnDef>::new(); let mut functions = HashMap::<u64, FnDef>::new();
let mut stack = Stack::new();
while !input.peek().unwrap().0.is_eof() { while !input.peek().unwrap().0.is_eof() {
// Collect all the function definitions // Collect all the function definitions
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
{ {
if let (Token::Fn, _) = input.peek().unwrap() { if let (Token::Fn, _) = input.peek().unwrap() {
let f = parse_fn(input, true)?; let mut stack = Stack::new();
let f = parse_fn(input, &mut stack, true)?;
functions.insert(calc_fn_def(&f.name, f.params.len()), f); functions.insert(calc_fn_def(&f.name, f.params.len()), f);
continue; continue;
} }
} }
// Actual statement // Actual statement
let stmt = parse_stmt(input, false, true)?; let stmt = parse_stmt(input, &mut stack, false, true)?;
let need_semicolon = !stmt.is_self_terminated(); let need_semicolon = !stmt.is_self_terminated();

View File

@ -349,7 +349,7 @@ impl<'a> Scope<'a> {
} }
/// Get a mutable reference to an entry in the Scope. /// Get a mutable reference to an entry in the Scope.
pub(crate) fn get_mut(&mut self, index: usize) -> &mut Dynamic { pub(crate) fn get_mut(&mut self, index: usize) -> (&mut Dynamic, EntryType) {
let entry = self.0.get_mut(index).expect("invalid index in Scope"); let entry = self.0.get_mut(index).expect("invalid index in Scope");
// assert_ne!( // assert_ne!(
@ -358,7 +358,7 @@ impl<'a> Scope<'a> {
// "get mut of constant entry" // "get mut of constant entry"
// ); // );
&mut entry.value (&mut entry.value, entry.typ)
} }
/// Get an iterator to entries in the Scope. /// Get an iterator to entries in the Scope.

View File

@ -34,10 +34,10 @@ fn test_eval_function() -> Result<(), Box<EvalAltResult>> {
script += "y += foo(y);"; script += "y += foo(y);";
script += "x + y"; script += "x + y";
eval(script) eval(script) + x + y
"# "#
)?, )?,
42 84
); );
assert_eq!( assert_eq!(
@ -54,7 +54,8 @@ fn test_eval_function() -> Result<(), Box<EvalAltResult>> {
32 32
); );
assert!(!scope.contains("z")); assert!(scope.contains("script"));
assert_eq!(scope.len(), 3);
Ok(()) Ok(())
} }