From 1fd242ed2cf5ecd6acb85eac0d23dbd42f2ce141 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 30 Dec 2021 12:14:54 +0800 Subject: [PATCH] Flatten nested block scopes. --- src/ast/stmt.rs | 24 +++++++++++++++++++++++- src/optimizer.rs | 32 +++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 1754ee56..c90d405e 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -1,6 +1,7 @@ //! Module defining script statements. use super::{ASTNode, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS}; +use crate::engine::KEYWORD_EVAL; use crate::tokenizer::Token; use crate::{calc_fn_hash, Position, StaticVec, INT}; #[cfg(feature = "no_std")] @@ -466,12 +467,33 @@ impl Stmt { Self::Share(_) => false, } } + /// Does this statement's behavior depend on its containing block? + /// + /// A statement that depends on its containing block behaves differently when promoted + /// to an upper block. + /// + /// Currently only variable definitions (i.e. `let` and `const`), `import`/`export` statements, + /// and `eval` calls (which may in turn call define variables) fall under this category. + #[inline] + #[must_use] + pub fn is_block_dependent(&self) -> bool { + match self { + Self::Var(_, _, _, _) => true, + + Self::FnCall(x, _) if x.name == KEYWORD_EVAL => true, + + #[cfg(not(feature = "no_module"))] + Self::Import(_, _, _) | Self::Export(_, _) => true, + + _ => false, + } + } /// Is this statement _pure_ within the containing block? /// /// An internally pure statement only has side effects that disappear outside the block. /// /// Currently only variable definitions (i.e. `let` and `const`) and `import`/`export` - /// statements are internally pure. + /// statements are internally pure, other than pure expressions. #[inline] #[must_use] pub fn is_internally_pure(&self) -> bool { diff --git a/src/optimizer.rs b/src/optimizer.rs index aca3df1c..fb3c7cd4 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -189,6 +189,32 @@ fn optimize_stmt_block( Stmt::is_pure }; + // Flatten blocks + loop { + if let Some(n) = statements.iter().enumerate().find_map(|(i, s)| match s { + Stmt::Block(block, _) if !block.iter().any(Stmt::is_block_dependent) => Some(i), + _ => None, + }) { + let (first, second) = statements.split_at_mut(n); + let stmt = mem::take(&mut second[0]); + let mut stmts = match stmt { + Stmt::Block(block, _) => block, + _ => unreachable!("Stmt::Block expected but gets {:?}", stmt), + }; + statements = first + .iter_mut() + .map(mem::take) + .chain(stmts.iter_mut().map(mem::take)) + .chain(second.iter_mut().skip(1).map(mem::take)) + .collect(); + } else { + break; + } + + is_dirty = true; + } + + // Optimize loop { state.clear_dirty(); @@ -411,7 +437,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b x.2 = value; } } - _ => unreachable!(), + ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr), } } @@ -695,8 +721,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b state.set_dirty(); *stmt = Stmt::Noop(*pos); } - // Only one statement - promote - [s] => { + // Only one statement which is not block-dependent - promote + [s] if !s.is_block_dependent() => { state.set_dirty(); *stmt = mem::take(s); }