From 41c815f3555a013612a6a5c996450efa3038966e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 12 Nov 2020 12:37:42 +0800 Subject: [PATCH] Optimize in-place. --- src/optimize.rs | 670 ++++++++++++++++++++++------------------------- tests/bool_op.rs | 20 +- 2 files changed, 322 insertions(+), 368 deletions(-) diff --git a/src/optimize.rs b/src/optimize.rs index 63f856ec..afe189b8 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,6 +1,6 @@ //! Module implementing the AST optimizer. -use crate::ast::{BinaryExpr, CustomExpr, Expr, FnCallExpr, ScriptFnDef, Stmt, AST}; +use crate::ast::{Expr, ScriptFnDef, Stmt, AST}; use crate::dynamic::Dynamic; use crate::engine::{ Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_IS_DEF_FN, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, @@ -10,7 +10,7 @@ use crate::fn_call::run_builtin_binary_op; use crate::module::Module; use crate::parser::map_dynamic_to_expr; use crate::scope::Scope; -use crate::token::{is_valid_identifier, NO_POS}; +use crate::token::{is_valid_identifier, Position, NO_POS}; use crate::{calc_native_fn_hash, StaticVec}; #[cfg(not(feature = "no_function"))] @@ -19,6 +19,7 @@ use crate::ast::ReturnType; use crate::stdlib::{ boxed::Box, iter::empty, + mem, string::{String, ToString}, vec, vec::Vec, @@ -154,285 +155,293 @@ fn call_fn_with_constant_arguments( .map(|(v, _)| v) } +/// Optimize a block of statements. +fn optimize_stmt_block( + mut statements: Vec, + pos: Position, + state: &mut State, + preserve_result: bool, + count_promote_as_dirty: bool, +) -> Stmt { + let orig_len = statements.len(); // Original number of statements in the block, for change detection + let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later + + // Optimize each statement in the block + statements.iter_mut().for_each(|stmt| match stmt { + // Add constant literals into the state + Stmt::Const(var_def, Some(expr), _, pos) if expr.is_literal() => { + state.set_dirty(); + state.push_constant(&var_def.name, mem::take(expr)); + *stmt = Stmt::Noop(*pos); // No need to keep constants + } + Stmt::Const(var_def, None, _, pos) => { + state.set_dirty(); + state.push_constant(&var_def.name, Expr::Unit(var_def.pos)); + *stmt = Stmt::Noop(*pos); // No need to keep constants + } + // Optimize the statement + _ => optimize_stmt(stmt, state, preserve_result), + }); + + // Remove all raw expression statements that are pure except for the very last statement + let last_stmt = if preserve_result { + statements.pop() + } else { + None + }; + + statements.retain(|stmt| !stmt.is_pure()); + + if let Some(stmt) = last_stmt { + statements.push(stmt); + } + + // Remove all let/import statements at the end of a block - the new variables will go away anyway. + // But be careful only remove ones that have no initial values or have values that are pure expressions, + // otherwise there may be side effects. + let mut removed = false; + + while let Some(expr) = statements.pop() { + match expr { + Stmt::Let(_, expr, _, _) => removed = expr.as_ref().map(Expr::is_pure).unwrap_or(true), + #[cfg(not(feature = "no_module"))] + Stmt::Import(expr, _, _) => removed = expr.is_pure(), + _ => { + statements.push(expr); + break; + } + } + } + + if preserve_result { + if removed { + statements.push(Stmt::Noop(pos)) + } + + // Optimize all the statements again + let num_statements = statements.len(); + statements + .iter_mut() + .enumerate() + .for_each(|(i, stmt)| optimize_stmt(stmt, state, i == num_statements)); + } + + // Remove everything following the the first return/throw + let mut dead_code = false; + + statements.retain(|stmt| { + if dead_code { + return false; + } + + match stmt { + Stmt::ReturnWithVal(_, _, _) | Stmt::Break(_) => dead_code = true, + _ => (), + } + + true + }); + + // Change detection + if orig_len != statements.len() { + state.set_dirty(); + } + + // Pop the stack and remove all the local constants + state.restore_constants(orig_constants_len); + + match &statements[..] { + // No statements in block - change to No-op + [] => { + state.set_dirty(); + Stmt::Noop(pos) + } + // Only one let statement - leave it alone + [x] if matches!(x, Stmt::Let(_, _, _, _)) => Stmt::Block(statements, pos), + // Only one import statement - leave it alone + #[cfg(not(feature = "no_module"))] + [x] if matches!(x, Stmt::Import(_, _, _)) => Stmt::Block(statements, pos), + // Only one statement - promote + [_] => { + if count_promote_as_dirty { + state.set_dirty(); + } + statements.remove(0) + } + _ => Stmt::Block(statements, pos), + } +} + /// Optimize a statement. -fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { +fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { match stmt { // expr op= expr - Stmt::Assignment(x, pos) => match x.0 { - Expr::Variable(_) => { - Stmt::Assignment(Box::new((x.0, x.1, optimize_expr(x.2, state))), pos) + Stmt::Assignment(ref mut x, _) => match x.0 { + Expr::Variable(_) => optimize_expr(&mut x.2, state), + _ => { + optimize_expr(&mut x.0, state); + optimize_expr(&mut x.2, state); } - _ => Stmt::Assignment( - Box::new((optimize_expr(x.0, state), x.1, optimize_expr(x.2, state))), - pos, - ), }, // if false { if_block } -> Noop Stmt::IfThenElse(Expr::False(pos), x, _) if x.1.is_none() => { state.set_dirty(); - Stmt::Noop(pos) + *stmt = Stmt::Noop(*pos); } // if true { if_block } -> if_block - Stmt::IfThenElse(Expr::True(_), x, _) if x.1.is_none() => optimize_stmt(x.0, state, true), + Stmt::IfThenElse(Expr::True(_), x, _) if x.1.is_none() => { + *stmt = mem::take(&mut x.0); + optimize_stmt(stmt, state, true); + } // if expr { Noop } - Stmt::IfThenElse(condition, x, _) if x.1.is_none() && matches!(x.0, Stmt::Noop(_)) => { + Stmt::IfThenElse(ref mut condition, x, _) + if x.1.is_none() && matches!(x.0, Stmt::Noop(_)) => + { state.set_dirty(); let pos = condition.position(); - let expr = optimize_expr(condition, state); + let mut expr = mem::take(condition); + optimize_expr(&mut expr, state); - if preserve_result { + *stmt = if preserve_result { // -> { expr, Noop } let mut statements = Vec::new(); statements.push(Stmt::Expr(expr)); - statements.push(x.0); - + statements.push(mem::take(&mut x.0)); Stmt::Block(statements, pos) } else { // -> expr Stmt::Expr(expr) - } + }; } // if expr { if_block } - Stmt::IfThenElse(condition, x, pos) if x.1.is_none() => Stmt::IfThenElse( - optimize_expr(condition, state), - Box::new((optimize_stmt(x.0, state, true), None)), - pos, - ), + Stmt::IfThenElse(ref mut condition, ref mut x, _) if x.1.is_none() => { + optimize_expr(condition, state); + optimize_stmt(&mut x.0, state, true); + } // if false { if_block } else { else_block } -> else_block Stmt::IfThenElse(Expr::False(_), x, _) if x.1.is_some() => { - optimize_stmt(x.1.unwrap(), state, true) + *stmt = mem::take(x.1.as_mut().unwrap()); + optimize_stmt(stmt, state, true); } // if true { if_block } else { else_block } -> if_block - Stmt::IfThenElse(Expr::True(_), x, _) => optimize_stmt(x.0, state, true), + Stmt::IfThenElse(Expr::True(_), x, _) => { + *stmt = mem::take(&mut x.0); + optimize_stmt(stmt, state, true); + } // if expr { if_block } else { else_block } - Stmt::IfThenElse(condition, x, pos) => Stmt::IfThenElse( - optimize_expr(condition, state), - Box::new(( - optimize_stmt(x.0, state, true), - match optimize_stmt(x.1.unwrap(), state, true) { - Stmt::Noop(_) => None, // Noop -> no else block - stmt => Some(stmt), - }, - )), - pos, - ), + Stmt::IfThenElse(ref mut condition, ref mut x, _) => { + optimize_expr(condition, state); + optimize_stmt(&mut x.0, state, true); + if let Some(else_block) = x.1.as_mut() { + optimize_stmt(else_block, state, true); + match else_block { + Stmt::Noop(_) => x.1 = None, // Noop -> no else block + _ => (), + } + } + } // while false { block } -> Noop Stmt::While(Expr::False(pos), _, _) => { state.set_dirty(); - Stmt::Noop(pos) + *stmt = Stmt::Noop(*pos) } // while true { block } -> loop { block } Stmt::While(Expr::True(_), block, pos) => { - Stmt::Loop(Box::new(optimize_stmt(*block, state, false)), pos) + optimize_stmt(block, state, false); + *stmt = Stmt::Loop(Box::new(mem::take(block)), *pos) } // while expr { block } - Stmt::While(condition, block, pos) => { - match optimize_stmt(*block, state, false) { + Stmt::While(condition, block, _) => { + optimize_stmt(block, state, false); + optimize_expr(condition, state); + + match **block { // while expr { break; } -> { expr; } Stmt::Break(pos) => { // Only a single break statement - turn into running the guard expression once state.set_dirty(); let mut statements = Vec::new(); - statements.push(Stmt::Expr(optimize_expr(condition, state))); + statements.push(Stmt::Expr(mem::take(condition))); if preserve_result { statements.push(Stmt::Noop(pos)) } - Stmt::Block(statements, pos) + *stmt = Stmt::Block(statements, pos); } - // while expr { block } - stmt => Stmt::While(optimize_expr(condition, state), Box::new(stmt), pos), + _ => (), } } // loop { block } - Stmt::Loop(block, pos) => match optimize_stmt(*block, state, false) { - // loop { break; } -> Noop - Stmt::Break(pos) => { - // Only a single break statement - state.set_dirty(); - Stmt::Noop(pos) + Stmt::Loop(block, _) => { + optimize_stmt(block, state, false); + + match **block { + // loop { break; } -> Noop + Stmt::Break(pos) => { + // Only a single break statement + state.set_dirty(); + *stmt = Stmt::Noop(pos) + } + _ => (), } - // loop { block } - stmt => Stmt::Loop(Box::new(stmt), pos), - }, + } // for id in expr { block } - Stmt::For(iterable, x, pos) => { - let (var_name, block) = *x; - Stmt::For( - optimize_expr(iterable, state), - Box::new((var_name, optimize_stmt(block, state, false))), - pos, - ) + Stmt::For(ref mut iterable, ref mut x, _) => { + optimize_expr(iterable, state); + optimize_stmt(&mut x.1, state, false); } // let id = expr; - Stmt::Let(name, Some(expr), export, pos) => { - Stmt::Let(name, Some(optimize_expr(expr, state)), export, pos) - } + Stmt::Let(_, Some(ref mut expr), _, _) => optimize_expr(expr, state), // let id; - stmt @ Stmt::Let(_, None, _, _) => stmt, + Stmt::Let(_, None, _, _) => (), // import expr as var; #[cfg(not(feature = "no_module"))] - Stmt::Import(expr, alias, pos) => Stmt::Import(optimize_expr(expr, state), alias, pos), + Stmt::Import(ref mut expr, _, _) => optimize_expr(expr, state), // { block } Stmt::Block(statements, pos) => { - let orig_len = statements.len(); // Original number of statements in the block, for change detection - let orig_constants_len = state.constants.len(); // Original number of constants in the state, for restore later - - // Optimize each statement in the block - let mut result: Vec<_> = statements - .into_iter() - .map(|stmt| match stmt { - // Add constant literals into the state - Stmt::Const(var_def, Some(expr), _, pos) if expr.is_literal() => { - state.set_dirty(); - state.push_constant(&var_def.name, expr); - Stmt::Noop(pos) // No need to keep constants - } - Stmt::Const(var_def, None, _, pos) => { - state.set_dirty(); - state.push_constant(&var_def.name, Expr::Unit(var_def.pos)); - Stmt::Noop(pos) // No need to keep constants - } - // Optimize the statement - stmt => optimize_stmt(stmt, state, preserve_result), - }) - .collect(); - - // Remove all raw expression statements that are pure except for the very last statement - let last_stmt = if preserve_result { result.pop() } else { None }; - - result.retain(|stmt| !stmt.is_pure()); - - if let Some(stmt) = last_stmt { - result.push(stmt); - } - - // Remove all let/import statements at the end of a block - the new variables will go away anyway. - // But be careful only remove ones that have no initial values or have values that are pure expressions, - // otherwise there may be side effects. - let mut removed = false; - - while let Some(expr) = result.pop() { - match expr { - Stmt::Let(_, expr, _, _) => { - removed = expr.as_ref().map(Expr::is_pure).unwrap_or(true) - } - #[cfg(not(feature = "no_module"))] - Stmt::Import(expr, _, _) => removed = expr.is_pure(), - _ => { - result.push(expr); - break; - } - } - } - - if preserve_result { - if removed { - result.push(Stmt::Noop(pos)) - } - - // Optimize all the statements again - result = result - .into_iter() - .rev() - .enumerate() - .map(|(i, stmt)| optimize_stmt(stmt, state, i == 0)) - .rev() - .collect(); - } - - // Remove everything following the the first return/throw - let mut dead_code = false; - - result.retain(|stmt| { - if dead_code { - return false; - } - - match stmt { - Stmt::ReturnWithVal(_, _, _) | Stmt::Break(_) => dead_code = true, - _ => (), - } - - true - }); - - // Change detection - if orig_len != result.len() { - state.set_dirty(); - } - - // Pop the stack and remove all the local constants - state.restore_constants(orig_constants_len); - - match &result[..] { - // No statements in block - change to No-op - [] => { - state.set_dirty(); - Stmt::Noop(pos) - } - // Only one let statement - leave it alone - [x] if matches!(x, Stmt::Let(_, _, _, _)) => Stmt::Block(result, pos), - // Only one import statement - leave it alone - #[cfg(not(feature = "no_module"))] - [x] if matches!(x, Stmt::Import(_, _, _)) => Stmt::Block(result, pos), - // Only one statement - promote - [_] => { - state.set_dirty(); - result.remove(0) - } - _ => Stmt::Block(result, pos), - } + *stmt = optimize_stmt_block(mem::take(statements), *pos, state, preserve_result, true); } // try { block } catch ( var ) { block } Stmt::TryCatch(x, _, _) if x.0.is_pure() => { // If try block is pure, there will never be any exceptions state.set_dirty(); let pos = x.0.position(); - let mut statements = match optimize_stmt(x.0, state, preserve_result) { + optimize_stmt(&mut x.0, state, preserve_result); + let mut statements = match mem::take(&mut x.0) { Stmt::Block(statements, _) => statements, stmt => vec![stmt], }; statements.push(Stmt::Noop(pos)); - Stmt::Block(statements, pos) + *stmt = Stmt::Block(statements, pos); } // try { block } catch ( var ) { block } - Stmt::TryCatch(x, try_pos, catch_pos) => { - let (try_block, var_name, catch_block) = *x; - Stmt::TryCatch( - Box::new(( - optimize_stmt(try_block, state, false), - var_name, - optimize_stmt(catch_block, state, false), - )), - try_pos, - catch_pos, - ) + Stmt::TryCatch(ref mut x, _, _) => { + optimize_stmt(&mut x.0, state, false); + optimize_stmt(&mut x.2, state, false); } // {} Stmt::Expr(Expr::Stmt(x, pos)) if x.is_empty() => { state.set_dirty(); - Stmt::Noop(pos) + *stmt = Stmt::Noop(*pos); } // {...}; Stmt::Expr(Expr::Stmt(x, pos)) => { state.set_dirty(); - Stmt::Block(x.into_vec(), pos) + *stmt = Stmt::Block(mem::take(x).into_vec(), *pos); } // expr; - Stmt::Expr(expr) => Stmt::Expr(optimize_expr(expr, state)), + Stmt::Expr(ref mut expr) => optimize_expr(expr, state), // return expr; - Stmt::ReturnWithVal(ret, Some(expr), pos) => { - Stmt::ReturnWithVal(ret, Some(optimize_expr(expr, state)), pos) - } + Stmt::ReturnWithVal(_, Some(ref mut expr), _) => optimize_expr(expr, state), + // All other statements - skip - stmt => stmt, + _ => (), } } /// Optimize an expression. -fn optimize_expr(expr: Expr, state: &mut State) -> Expr { +fn optimize_expr(expr: &mut Expr, state: &mut State) { // These keywords are handled specially const DONT_EVAL_KEYWORDS: &[&str] = &[ KEYWORD_PRINT, // side effects @@ -444,296 +453,253 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { match expr { // expr - do not promote because there is a reason it is wrapped in an `Expr::Expr` - Expr::Expr(x) => Expr::Expr(Box::new(optimize_expr(*x, state))), + Expr::Expr(x) => optimize_expr(x, state), // {} - Expr::Stmt(x, pos) if x.is_empty() => { - state.set_dirty(); - Expr::Unit(pos) - } - // { stmt } - Expr::Stmt(mut x, pos) if x.len() == 1 => match x.pop().unwrap() { - // {} -> () - Stmt::Noop(_) => { - state.set_dirty(); - Expr::Unit(pos) - } - // { expr } -> expr - Stmt::Expr(expr) => { - state.set_dirty(); - optimize_expr(expr, state) - } + Expr::Stmt(x, pos) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(*pos) } + // { stmt; ... } - do not count promotion as dirty because it gets turned back into an array + Expr::Stmt(x, pos) => match optimize_stmt_block(mem::take(x).into_vec(), *pos, state, true, false) { + // {} + Stmt::Noop(_) => { state.set_dirty(); *expr = Expr::Unit(*pos); } + // { stmt, .. } + Stmt::Block(statements, _) => *x = Box::new(statements.into()), + // { expr } + Stmt::Expr(inner) => { state.set_dirty(); *expr = inner; } // { stmt } - stmt => Expr::Stmt(Box::new(vec![optimize_stmt(stmt, state, true)].into()), pos) + stmt => x.push(stmt), } - // { stmt; ... } - Expr::Stmt(x, pos) => Expr::Stmt(Box::new( - x.into_iter().map(|stmt| optimize_stmt(stmt, state, true)).collect(), - ), pos), // lhs.rhs #[cfg(not(feature = "no_object"))] - Expr::Dot(x, dot_pos) => match (x.lhs, x.rhs) { + Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) { // map.string (Expr::Map(m, pos), Expr::Property(p)) if m.iter().all(|(_, x)| x.is_pure()) => { let prop = &p.1.name; // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); - m.into_iter().find(|(x, _)| &x.name == prop) - .map(|(_, mut expr)| { expr.set_position(pos); expr }) - .unwrap_or_else(|| Expr::Unit(pos)) + *expr = mem::take(m).into_iter().find(|(x, _)| &x.name == prop) + .map(|(_, mut expr)| { expr.set_position(*pos); expr }) + .unwrap_or_else(|| Expr::Unit(*pos)); } // var.rhs - (lhs @ Expr::Variable(_), rhs) => Expr::Dot(Box::new(BinaryExpr { - lhs, - rhs: optimize_expr(rhs, state), - }), dot_pos), + (Expr::Variable(_), rhs) => optimize_expr(rhs, state), // lhs.rhs - (lhs, rhs) => Expr::Dot(Box::new(BinaryExpr { - lhs: optimize_expr(lhs, state), - rhs: optimize_expr(rhs, state), - }), dot_pos) + (lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); } } // lhs[rhs] #[cfg(not(feature = "no_index"))] - Expr::Index(x, idx_pos) => match (x.lhs, x.rhs) { + Expr::Index(x, _) => match (&mut x.lhs, &mut x.rhs) { // array[int] - (Expr::Array(mut a, pos), Expr::IntegerConstant(i, _)) - if i >= 0 && (i as usize) < a.len() && a.iter().all(Expr::is_pure) => + (Expr::Array(a, pos), Expr::IntegerConstant(i, _)) + if *i >= 0 && (*i as usize) < a.len() && a.iter().all(Expr::is_pure) => { // Array literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); - let mut expr = a.remove(i as usize); - expr.set_position(pos); - expr + let mut result = a.remove(*i as usize); + result.set_position(*pos); + *expr = result; } // map[string] (Expr::Map(m, pos), Expr::StringConstant(s)) if m.iter().all(|(_, x)| x.is_pure()) => { // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); - m.into_iter().find(|(x, _)| x.name == s.name) - .map(|(_, mut expr)| { expr.set_position(pos); expr }) - .unwrap_or_else(|| Expr::Unit(pos)) + *expr = mem::take(m).into_iter().find(|(x, _)| x.name == s.name) + .map(|(_, mut expr)| { expr.set_position(*pos); expr }) + .unwrap_or_else(|| Expr::Unit(*pos)); } // string[int] - (Expr::StringConstant(s), Expr::IntegerConstant(i, _)) if i >= 0 && (i as usize) < s.name.chars().count() => { + (Expr::StringConstant(s), Expr::IntegerConstant(i, _)) if *i >= 0 && (*i as usize) < s.name.chars().count() => { // String literal indexing - get the character state.set_dirty(); - Expr::CharConstant(s.name.chars().nth(i as usize).unwrap(), s.pos) + *expr = Expr::CharConstant(s.name.chars().nth(*i as usize).unwrap(), s.pos); } // var[rhs] - (lhs @ Expr::Variable(_), rhs) => Expr::Index(Box::new(BinaryExpr { - lhs, - rhs: optimize_expr(rhs, state), - }), idx_pos), + (Expr::Variable(_), rhs) => optimize_expr(rhs, state), // lhs[rhs] - (lhs, rhs) => Expr::Index(Box::new(BinaryExpr { - lhs: optimize_expr(lhs, state), - rhs: optimize_expr(rhs, state), - }), idx_pos), + (lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); } }, // [ items .. ] #[cfg(not(feature = "no_index"))] - Expr::Array(a, pos) => Expr::Array(Box::new(a - .into_iter().map(|expr| optimize_expr(expr, state)) - .collect()), pos), - // [ items .. ] + Expr::Array(a, _) => a.iter_mut().for_each(|expr| optimize_expr(expr, state)), + // #{ key:value, .. } #[cfg(not(feature = "no_object"))] - Expr::Map(m, pos) => Expr::Map(Box::new(m - .into_iter().map(|(key, expr)| (key, optimize_expr(expr, state))) - .collect()), pos), + Expr::Map(m, _) => m.iter_mut().for_each(|(_, expr)| optimize_expr(expr, state)), // lhs in rhs - Expr::In(x, in_pos) => match (x.lhs, x.rhs) { + Expr::In(x, _) => match (&mut x.lhs, &mut x.rhs) { // "xxx" in "xxxxx" (Expr::StringConstant(a), Expr::StringConstant(b)) => { state.set_dirty(); - if b.name.contains(a.name.as_str()) { Expr::True(a.pos) } else { Expr::False(a.pos) } + *expr = if b.name.contains(a.name.as_str()) { Expr::True(a.pos) } else { Expr::False(a.pos) }; } // 'x' in "xxxxx" (Expr::CharConstant(a, pos), Expr::StringConstant(b)) => { state.set_dirty(); - if b.name.contains(a) { Expr::True(pos) } else { Expr::False(pos) } + *expr = if b.name.contains(*a) { Expr::True(*pos) } else { Expr::False(*pos) }; } // "xxx" in #{...} (Expr::StringConstant(a), Expr::Map(b, _)) => { state.set_dirty(); - if b.iter().find(|(x, _)| x.name == a.name).is_some() { + *expr = if b.iter().find(|(x, _)| x.name == a.name).is_some() { Expr::True(a.pos) } else { Expr::False(a.pos) - } + }; } // 'x' in #{...} (Expr::CharConstant(a, pos), Expr::Map(b, _)) => { state.set_dirty(); let ch = a.to_string(); - if b.iter().find(|(x, _)| x.name == &ch).is_some() { - Expr::True(pos) + *expr = if b.iter().find(|(x, _)| x.name == &ch).is_some() { + Expr::True(*pos) } else { - Expr::False(pos) - } + Expr::False(*pos) + }; } // lhs in rhs - (lhs, rhs) => Expr::In(Box::new(BinaryExpr { - lhs: optimize_expr(lhs, state), - rhs: optimize_expr(rhs, state), - }), in_pos), + (lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); } }, // lhs && rhs - Expr::And(x, and_pos) => match (x.lhs, x.rhs) { + Expr::And(x, _) => match (&mut x.lhs, &mut x.rhs) { // true && rhs -> rhs (Expr::True(_), rhs) => { state.set_dirty(); - rhs + optimize_expr(rhs, state); + *expr = mem::take(rhs); } // false && rhs -> false (Expr::False(pos), _) => { state.set_dirty(); - Expr::False(pos) + *expr = Expr::False(*pos); } // lhs && true -> lhs (lhs, Expr::True(_)) => { state.set_dirty(); - optimize_expr(lhs, state) + optimize_expr(lhs, state); + *expr = mem::take(lhs); } // lhs && rhs - (lhs, rhs) => Expr::And(Box::new(BinaryExpr { - lhs: optimize_expr(lhs, state), - rhs: optimize_expr(rhs, state), - }), and_pos), + (lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); } }, // lhs || rhs - Expr::Or(x, or_pos) => match (x.lhs, x.rhs) { + Expr::Or(ref mut x, _) => match (&mut x.lhs, &mut x.rhs) { // false || rhs -> rhs (Expr::False(_), rhs) => { state.set_dirty(); - rhs + optimize_expr(rhs, state); + *expr = mem::take(rhs); } // true || rhs -> true (Expr::True(pos), _) => { state.set_dirty(); - Expr::True(pos) + *expr = Expr::True(*pos); } // lhs || false (lhs, Expr::False(_)) => { state.set_dirty(); - optimize_expr(lhs, state) + optimize_expr(lhs, state); + *expr = mem::take(lhs); } // lhs || rhs - (lhs, rhs) => Expr::Or(Box::new(BinaryExpr { - lhs: optimize_expr(lhs, state), - rhs: optimize_expr(rhs, state), - }), or_pos), + (lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); } }, // Do not call some special keywords - Expr::FnCall(mut x, pos) if DONT_EVAL_KEYWORDS.contains(&x.name.as_ref()) => { - x.args = x.args.into_iter().map(|a| optimize_expr(a, state)).collect(); - Expr::FnCall(x, pos) + Expr::FnCall(x, _) if DONT_EVAL_KEYWORDS.contains(&x.name.as_ref()) => { + x.args.iter_mut().for_each(|a| optimize_expr(a, state)); } // Call built-in operators - Expr::FnCall(mut x, pos) + Expr::FnCall(x, pos) if x.namespace.is_none() // Non-qualified && state.optimization_level == OptimizationLevel::Simple // simple optimizations && x.args.len() == 2 // binary call && x.args.iter().all(Expr::is_constant) // all arguments are constants && !is_valid_identifier(x.name.chars()) // cannot be scripted => { - let FnCallExpr { name, args, .. } = x.as_mut(); - - let arg_values: StaticVec<_> = args.iter().map(|e| e.get_constant_value().unwrap()).collect(); + let arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()).collect(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); // Search for overloaded operators (can override built-in). - if !state.engine.has_override_by_name_and_arguments(state.lib, name, arg_types.as_ref(), false) { - if let Some(expr) = run_builtin_binary_op(name, &arg_values[0], &arg_values[1]) + if !state.engine.has_override_by_name_and_arguments(state.lib, x.name.as_ref(), arg_types.as_ref(), false) { + if let Some(result) = run_builtin_binary_op(x.name.as_ref(), &arg_values[0], &arg_values[1]) .ok().flatten() - .and_then(|result| map_dynamic_to_expr(result, pos)) + .and_then(|result| map_dynamic_to_expr(result, *pos)) { state.set_dirty(); - return expr; + *expr = result; + return; } } - x.args = x.args.into_iter().map(|a| optimize_expr(a, state)).collect(); - Expr::FnCall(x, pos) + x.args.iter_mut().for_each(|a| optimize_expr(a, state)); } // Eagerly call functions - Expr::FnCall(mut x, pos) + Expr::FnCall(x, pos) if x.namespace.is_none() // Non-qualified && state.optimization_level == OptimizationLevel::Full // full optimizations && x.args.iter().all(Expr::is_constant) // all arguments are constants => { - let FnCallExpr { name, args, def_value, .. } = x.as_mut(); - // First search for script-defined functions (can override built-in) #[cfg(not(feature = "no_function"))] - let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(name, args.len(), false).is_some()); + let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(x.name.as_ref(), x.args.len(), false).is_some()); #[cfg(feature = "no_function")] let has_script_fn = false; if !has_script_fn { - let mut arg_values: StaticVec<_> = args.iter().map(|e| e.get_constant_value().unwrap()).collect(); + let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()).collect(); // Save the typename of the first argument if it is `type_of()` // This is to avoid `call_args` being passed into the closure - let arg_for_type_of = if name == KEYWORD_TYPE_OF && arg_values.len() == 1 { + let arg_for_type_of = if x.name == KEYWORD_TYPE_OF && arg_values.len() == 1 { state.engine.map_type_name(arg_values[0].type_name()) } else { "" }; - if let Some(expr) = call_fn_with_constant_arguments(&state, name, arg_values.as_mut()) + if let Some(result) = call_fn_with_constant_arguments(&state, x.name.as_ref(), arg_values.as_mut()) .or_else(|| { if !arg_for_type_of.is_empty() { // Handle `type_of()` Some(arg_for_type_of.to_string().into()) } else { // Otherwise use the default value, if any - def_value.map(|v| v.into()) + x.def_value.map(|v| v.into()) } }) - .and_then(|result| map_dynamic_to_expr(result, pos)) + .and_then(|result| map_dynamic_to_expr(result, *pos)) { state.set_dirty(); - return expr; + *expr = result; + return; } } - x.args = x.args.into_iter().map(|a| optimize_expr(a, state)).collect(); - Expr::FnCall(x, pos) + x.args.iter_mut().for_each(|a| optimize_expr(a, state)); } // id(args ..) -> optimize function call arguments - Expr::FnCall(mut x, pos) => { - x.args = x.args.into_iter().map(|a| optimize_expr(a, state)).collect(); - Expr::FnCall(x, pos) - } + Expr::FnCall(x, _) => x.args.iter_mut().for_each(|a| optimize_expr(a, state)), // constant-name Expr::Variable(x) if x.1.is_none() && state.contains_constant(&x.3.name) => { state.set_dirty(); // Replace constant with value - let mut expr = state.find_constant(&x.3.name).unwrap().clone(); - expr.set_position(x.3.pos); - expr + let mut result = state.find_constant(&x.3.name).unwrap().clone(); + result.set_position(x.3.pos); + *expr = result; } // Custom syntax - Expr::Custom(x, pos) => Expr::Custom(Box::new(CustomExpr { - keywords: x.keywords.into_iter().map(|expr| optimize_expr(expr, state)).collect(), - ..*x - }), pos), + Expr::Custom(x, _) => x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state)), // All other expressions - skip - expr => expr, + _ => (), } } @@ -773,47 +739,39 @@ fn optimize( let num_statements = result.len(); - result = result - .into_iter() - .enumerate() - .map(|(i, stmt)| { - match stmt { - Stmt::Const(var_def, Some(expr), export, pos) => { - // Load constants - let expr = optimize_expr(expr, &mut state); + result.iter_mut().enumerate().for_each(|(i, stmt)| { + match stmt { + Stmt::Const(var_def, expr, _, _) if expr.is_some() => { + // Load constants + let value_expr = expr.as_mut().unwrap(); + optimize_expr(value_expr, &mut state); - if expr.is_literal() { - state.push_constant(&var_def.name, expr.clone()); - } - - // Keep it in the global scope - if expr.is_unit() { - state.set_dirty(); - Stmt::Const(var_def, None, export, pos) - } else { - Stmt::Const(var_def, Some(expr), export, pos) - } + if value_expr.is_literal() { + state.push_constant(&var_def.name, value_expr.clone()); } - Stmt::Const(ref var_def, None, _, _) => { - state.push_constant(&var_def.name, Expr::Unit(var_def.pos)); - // Keep it in the global scope - stmt - } - _ => { - // Keep all variable declarations at this level - // and always keep the last return value - let keep = match stmt { - Stmt::Let(_, _, _, _) => true, - #[cfg(not(feature = "no_module"))] - Stmt::Import(_, _, _) => true, - _ => i == num_statements - 1, - }; - optimize_stmt(stmt, &mut state, keep) + // Keep it in the global scope + if value_expr.is_unit() { + state.set_dirty(); + *expr = None; } } - }) - .collect(); + Stmt::Const(var_def, None, _, _) => { + state.push_constant(&var_def.name, Expr::Unit(var_def.pos)); + } + _ => { + // Keep all variable declarations at this level + // and always keep the last return value + let keep = match stmt { + Stmt::Let(_, _, _, _) => true, + #[cfg(not(feature = "no_module"))] + Stmt::Import(_, _, _) => true, + _ => i == num_statements - 1, + }; + optimize_stmt(stmt, &mut state, keep); + } + } + }); if !state.is_dirty() { break; diff --git a/tests/bool_op.rs b/tests/bool_op.rs index 84c3d44f..0520736d 100644 --- a/tests/bool_op.rs +++ b/tests/bool_op.rs @@ -40,9 +40,8 @@ fn test_bool_op_short_circuit() -> Result<(), Box> { engine.eval::( r" let x = true; - x || { throw; }; - " + " )?, true ); @@ -51,9 +50,8 @@ fn test_bool_op_short_circuit() -> Result<(), Box> { engine.eval::( r" let x = false; - x && { throw; }; - " + " )?, false ); @@ -68,10 +66,9 @@ fn test_bool_op_no_short_circuit1() { assert!(engine .eval::( r" - let x = true; - - x | { throw; } - " + let x = true; + x | { throw; } + " ) .is_err()); } @@ -83,10 +80,9 @@ fn test_bool_op_no_short_circuit2() { assert!(engine .eval::( r" - let x = false; - - x & { throw; } - " + let x = false; + x & { throw; } + " ) .is_err()); }