Flatten nested block scopes.

This commit is contained in:
Stephen Chung 2021-12-30 12:14:54 +08:00
parent be4ae6e763
commit 1fd242ed2c
2 changed files with 52 additions and 4 deletions

View File

@ -1,6 +1,7 @@
//! Module defining script statements. //! Module defining script statements.
use super::{ASTNode, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS}; use super::{ASTNode, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS};
use crate::engine::KEYWORD_EVAL;
use crate::tokenizer::Token; use crate::tokenizer::Token;
use crate::{calc_fn_hash, Position, StaticVec, INT}; use crate::{calc_fn_hash, Position, StaticVec, INT};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -466,12 +467,33 @@ impl Stmt {
Self::Share(_) => false, 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? /// Is this statement _pure_ within the containing block?
/// ///
/// An internally pure statement only has side effects that disappear outside the 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` /// 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] #[inline]
#[must_use] #[must_use]
pub fn is_internally_pure(&self) -> bool { pub fn is_internally_pure(&self) -> bool {

View File

@ -189,6 +189,32 @@ fn optimize_stmt_block(
Stmt::is_pure 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 { loop {
state.clear_dirty(); state.clear_dirty();
@ -411,7 +437,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
x.2 = value; 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(); state.set_dirty();
*stmt = Stmt::Noop(*pos); *stmt = Stmt::Noop(*pos);
} }
// Only one statement - promote // Only one statement which is not block-dependent - promote
[s] => { [s] if !s.is_block_dependent() => {
state.set_dirty(); state.set_dirty();
*stmt = mem::take(s); *stmt = mem::take(s);
} }