diff --git a/CHANGELOG.md b/CHANGELOG.md index e0c37190..0481e729 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Rhai Release Notes Version 0.19.14 =============== +This version runs faster due to optimizations done on AST node structures. + Bug fixes --------- @@ -35,6 +37,7 @@ Breaking changes Enhancements ------------ +* Layout of AST nodes is optimized to reduce redirections, so speed is improved. * Function calls are more optimized and should now run faster. * `range` function now supports negative step and decreasing streams (i.e. to < from). * More information is provided to the error variable captured by the `catch` statement in an _object map_. diff --git a/src/ast.rs b/src/ast.rs index c773d760..caaecf11 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -757,8 +757,6 @@ pub struct Ident { pub name: ImmutableString, /// Declaration position. pub pos: Position, - /// Is this identifier public? - pub public: bool, } impl fmt::Debug for Ident { @@ -806,6 +804,29 @@ impl<'a> From<&'a Expr> for ASTNode<'a> { } } +/// _(INTERNALS)_ A statements block. +/// Exported under the `internals` feature only. +/// +/// # Volatile Data Structure +/// +/// This type is volatile and may change. +#[derive(Debug, Clone, Hash)] +pub struct StmtBlock { + pub statements: StaticVec, + pub pos: Position, +} + +impl StmtBlock { + /// Is this statements block empty? + pub fn is_empty(&self) -> bool { + self.statements.is_empty() + } + /// Number of statements in this statements block. + pub fn len(&self) -> usize { + self.statements.len() + } +} + /// _(INTERNALS)_ A statement. /// Exported under the `internals` feature only. /// @@ -817,7 +838,7 @@ pub enum Stmt { /// No-op. Noop(Position), /// `if` expr `{` stmt `}` `else` `{` stmt `}` - If(Expr, Box<(Stmt, Stmt)>, Position), + If(Expr, Box<(StmtBlock, StmtBlock)>, Position), /// `switch` expr `{` literal or _ `=>` stmt `,` ... `}` Switch( Expr, @@ -828,21 +849,25 @@ pub enum Stmt { Position, ), /// `while` expr `{` stmt `}` - While(Expr, Box, Position), + While(Expr, Box, Position), /// `do` `{` stmt `}` `while`|`until` expr - Do(Box, Expr, bool, Position), + Do(Box, Expr, bool, Position), /// `for` id `in` expr `{` stmt `}` - For(Expr, Box<(String, Stmt)>, Position), + For(Expr, Box<(String, StmtBlock)>, Position), /// \[`export`\] `let` id `=` expr - Let(Expr, Ident, Position), + Let(Expr, Ident, bool, Position), /// \[`export`\] `const` id `=` expr - Const(Expr, Ident, Position), + Const(Expr, Ident, bool, Position), /// expr op`=` expr Assignment(Box<(Expr, Expr, Option)>, Position), /// `{` stmt`;` ... `}` Block(Vec, Position), /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` - TryCatch(Box<(Stmt, Option, Stmt)>, Position, Position), + TryCatch( + Box<(StmtBlock, Option, StmtBlock)>, + Position, + Position, + ), /// [expression][Expr] Expr(Expr), /// `continue` @@ -853,7 +878,7 @@ pub enum Stmt { Return(ReturnType, Option, Position), /// `import` expr `as` var #[cfg(not(feature = "no_module"))] - Import(Expr, Ident, Position), + Import(Expr, Option, Position), /// `export` var `as` var `,` ... #[cfg(not(feature = "no_module"))] Export(Vec<(Ident, Option)>, Position), @@ -869,6 +894,22 @@ impl Default for Stmt { } } +impl From for StmtBlock { + fn from(stmt: Stmt) -> Self { + match stmt { + Stmt::Block(block, pos) => Self { + statements: block.into(), + pos, + }, + Stmt::Noop(pos) => Self { + statements: Default::default(), + pos, + }, + _ => panic!("cannot convert {:?} into a StmtBlock", stmt), + } + } +} + impl Stmt { /// Is this statement [`Noop`][Stmt::Noop]? #[inline(always)] @@ -892,8 +933,8 @@ impl Stmt { | Self::Do(_, _, _, pos) | Self::For(_, _, pos) | Self::Return(_, _, pos) - | Self::Let(_, _, pos) - | Self::Const(_, _, pos) + | Self::Let(_, _, _, pos) + | Self::Const(_, _, _, pos) | Self::TryCatch(_, pos, _) => *pos, Self::Expr(x) => x.position(), @@ -921,8 +962,8 @@ impl Stmt { | Self::Do(_, _, _, pos) | Self::For(_, _, pos) | Self::Return(_, _, pos) - | Self::Let(_, _, pos) - | Self::Const(_, _, pos) + | Self::Let(_, _, _, pos) + | Self::Const(_, _, _, pos) | Self::TryCatch(_, pos, _) => *pos = new_pos, Self::Expr(x) => { @@ -953,8 +994,8 @@ impl Stmt { // A No-op requires a semicolon in order to know it is an empty statement! Self::Noop(_) => false, - Self::Let(_, _, _) - | Self::Const(_, _, _) + Self::Let(_, _, _, _) + | Self::Const(_, _, _, _) | Self::Assignment(_, _) | Self::Expr(_) | Self::Do(_, _, _, _) @@ -976,20 +1017,28 @@ impl Stmt { match self { Self::Noop(_) => true, Self::Expr(expr) => expr.is_pure(), - Self::If(condition, x, _) => condition.is_pure() && x.0.is_pure() && x.1.is_pure(), + Self::If(condition, x, _) => { + condition.is_pure() + && x.0.statements.iter().all(Stmt::is_pure) + && x.1.statements.iter().all(Stmt::is_pure) + } Self::Switch(expr, x, _) => { expr.is_pure() && x.0.values().all(Stmt::is_pure) && x.1.as_ref().map(Stmt::is_pure).unwrap_or(true) } Self::While(condition, block, _) | Self::Do(block, condition, _, _) => { - condition.is_pure() && block.is_pure() + condition.is_pure() && block.statements.iter().all(Stmt::is_pure) } - Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(), - Self::Let(_, _, _) | Self::Const(_, _, _) | Self::Assignment(_, _) => false, + Self::For(iterable, x, _) => { + iterable.is_pure() && x.1.statements.iter().all(Stmt::is_pure) + } + Self::Let(_, _, _, _) | Self::Const(_, _, _, _) | Self::Assignment(_, _) => false, Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), Self::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false, - Self::TryCatch(x, _, _) => x.0.is_pure() && x.2.is_pure(), + Self::TryCatch(x, _, _) => { + x.0.statements.iter().all(Stmt::is_pure) && x.2.statements.iter().all(Stmt::is_pure) + } #[cfg(not(feature = "no_module"))] Self::Import(_, _, _) => false, @@ -1007,11 +1056,11 @@ impl Stmt { on_node(path); match self { - Self::Let(e, _, _) | Self::Const(e, _, _) => e.walk(path, on_node), + Self::Let(e, _, _, _) | Self::Const(e, _, _, _) => e.walk(path, on_node), Self::If(e, x, _) => { e.walk(path, on_node); - x.0.walk(path, on_node); - x.1.walk(path, on_node); + x.0.statements.iter().for_each(|s| s.walk(path, on_node)); + x.1.statements.iter().for_each(|s| s.walk(path, on_node)); } Self::Switch(e, x, _) => { e.walk(path, on_node); @@ -1022,11 +1071,11 @@ impl Stmt { } Self::While(e, s, _) | Self::Do(s, e, _, _) => { e.walk(path, on_node); - s.walk(path, on_node); + s.statements.iter().for_each(|s| s.walk(path, on_node)); } Self::For(e, x, _) => { e.walk(path, on_node); - x.1.walk(path, on_node); + x.1.statements.iter().for_each(|s| s.walk(path, on_node)); } Self::Assignment(x, _) => { x.0.walk(path, on_node); @@ -1034,8 +1083,8 @@ impl Stmt { } Self::Block(x, _) => x.iter().for_each(|s| s.walk(path, on_node)), Self::TryCatch(x, _, _) => { - x.0.walk(path, on_node); - x.2.walk(path, on_node); + x.0.statements.iter().for_each(|s| s.walk(path, on_node)); + x.2.statements.iter().for_each(|s| s.walk(path, on_node)); } Self::Expr(e) | Self::Return(_, Some(e), _) => e.walk(path, on_node), #[cfg(not(feature = "no_module"))] diff --git a/src/engine.rs b/src/engine.rs index d061c54b..297570ff 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,6 @@ //! Main module defining the script evaluation [`Engine`]. -use crate::ast::{Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, Stmt}; +use crate::ast::{Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, Stmt, StmtBlock}; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::fn_native::{ CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback, @@ -2031,15 +2031,28 @@ impl Engine { // If statement Stmt::If(expr, x, _) => { - let (if_block, else_block) = x.as_ref(); + let ( + StmtBlock { + statements: if_stmt, + .. + }, + StmtBlock { + statements: else_stmt, + .. + }, + ) = x.as_ref(); self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .as_bool() .map_err(|err| self.make_type_mismatch_err::(err, expr.position())) .and_then(|guard_val| { if guard_val { - self.eval_stmt(scope, mods, state, lib, this_ptr, if_block, level) - } else if !else_block.is_noop() { - self.eval_stmt(scope, mods, state, lib, this_ptr, else_block, level) + self.eval_stmt_block( + scope, mods, state, lib, this_ptr, if_stmt, true, level, + ) + } else if !else_stmt.is_empty() { + self.eval_stmt_block( + scope, mods, state, lib, this_ptr, else_stmt, true, level, + ) } else { Ok(Dynamic::UNIT) } @@ -2076,58 +2089,70 @@ impl Engine { } // While loop - Stmt::While(expr, body, _) => loop { - let condition = if !expr.is_unit() { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? - .as_bool() - .map_err(|err| self.make_type_mismatch_err::(err, expr.position()))? - } else { - true - }; + Stmt::While(expr, body, _) => { + let body = &body.statements; + loop { + let condition = if !expr.is_unit() { + self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .as_bool() + .map_err(|err| { + self.make_type_mismatch_err::(err, expr.position()) + })? + } else { + true + }; - if condition { - match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) { + if condition { + match self + .eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level) + { + Ok(_) => (), + Err(err) => match *err { + EvalAltResult::LoopBreak(false, _) => (), + EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT), + _ => return Err(err), + }, + } + } else { + return Ok(Dynamic::UNIT); + } + } + } + + // Do loop + Stmt::Do(body, expr, is_while, _) => { + let body = &body.statements; + + loop { + match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level) + { Ok(_) => (), Err(err) => match *err { - EvalAltResult::LoopBreak(false, _) => (), + EvalAltResult::LoopBreak(false, _) => continue, EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT), _ => return Err(err), }, } - } else { - return Ok(Dynamic::UNIT); - } - }, - // Do loop - Stmt::Do(body, expr, is_while, _) => loop { - match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) { - Ok(_) => (), - Err(err) => match *err { - EvalAltResult::LoopBreak(false, _) => continue, - EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT), - _ => return Err(err), - }, - } - - if self - .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? - .as_bool() - .map_err(|err| self.make_type_mismatch_err::(err, expr.position()))? - { - if !*is_while { - return Ok(Dynamic::UNIT); - } - } else { - if *is_while { - return Ok(Dynamic::UNIT); + if self + .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .as_bool() + .map_err(|err| self.make_type_mismatch_err::(err, expr.position()))? + { + if !*is_while { + return Ok(Dynamic::UNIT); + } + } else { + if *is_while { + return Ok(Dynamic::UNIT); + } } } - }, + } // For loop Stmt::For(expr, x, _) => { - let (name, stmt) = x.as_ref(); + let (name, StmtBlock { statements, pos }) = x.as_ref(); let iter_obj = self .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .flatten(); @@ -2176,9 +2201,11 @@ impl Engine { *loop_var = value; } - self.inc_operations(state, stmt.position())?; + self.inc_operations(state, *pos)?; - match self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) { + match self.eval_stmt_block( + scope, mods, state, lib, this_ptr, statements, true, level, + ) { Ok(_) => (), Err(err) => match *err { EvalAltResult::LoopBreak(false, _) => (), @@ -2204,10 +2231,20 @@ impl Engine { // Try/Catch statement Stmt::TryCatch(x, _, _) => { - let (try_body, err_var, catch_body) = x.as_ref(); + let ( + StmtBlock { + statements: try_body, + .. + }, + err_var, + StmtBlock { + statements: catch_body, + .. + }, + ) = x.as_ref(); let result = self - .eval_stmt(scope, mods, state, lib, this_ptr, try_body, level) + .eval_stmt_block(scope, mods, state, lib, this_ptr, try_body, true, level) .map(|_| Dynamic::UNIT); match result { @@ -2266,8 +2303,9 @@ impl Engine { scope.push(unsafe_cast_var_name_to_lifetime(&name), err_value); } - let result = - self.eval_stmt(scope, mods, state, lib, this_ptr, catch_body, level); + let result = self.eval_stmt_block( + scope, mods, state, lib, this_ptr, catch_body, true, level, + ); state.scope_level -= 1; scope.rewind(orig_scope_len); @@ -2314,11 +2352,11 @@ impl Engine { } // Let/const statement - Stmt::Let(expr, Ident { name, public, .. }, _) - | Stmt::Const(expr, Ident { name, public, .. }, _) => { + Stmt::Let(expr, Ident { name, .. }, export, _) + | Stmt::Const(expr, Ident { name, .. }, export, _) => { let entry_type = match stmt { - Stmt::Let(_, _, _) => AccessMode::ReadWrite, - Stmt::Const(_, _, _) => AccessMode::ReadOnly, + Stmt::Let(_, _, _, _) => AccessMode::ReadWrite, + Stmt::Const(_, _, _, _) => AccessMode::ReadOnly, _ => unreachable!("should be Stmt::Let or Stmt::Const, but gets {:?}", stmt), }; @@ -2329,9 +2367,9 @@ impl Engine { let (var_name, _alias): (Cow<'_, str>, _) = if state.is_global() { ( name.to_string().into(), - if *public { Some(name.clone()) } else { None }, + if *export { Some(name.clone()) } else { None }, ) - } else if *public { + } else if *export { unreachable!("exported variable not on global level"); } else { (unsafe_cast_var_name_to_lifetime(name).into(), None) @@ -2348,7 +2386,7 @@ impl Engine { // Import statement #[cfg(not(feature = "no_module"))] - Stmt::Import(expr, Ident { name, public, .. }, _pos) => { + Stmt::Import(expr, export, _pos) => { // Guard against too many modules #[cfg(not(feature = "unchecked"))] if state.modules >= self.max_modules() { @@ -2375,14 +2413,14 @@ impl Engine { }) .unwrap_or_else(|| self.module_resolver.resolve(self, &path, expr_pos))?; - if *public { + if let Some(name) = export.as_ref().map(|x| x.name.clone()) { if !module.is_indexed() { // Index the module (making a clone copy if necessary) if it is not indexed let mut module = crate::fn_native::shared_take_or_clone(module); module.build_index(); - mods.push(name.clone(), module); + mods.push(name, module); } else { - mods.push(name.clone(), module); + mods.push(name, module); } } diff --git a/src/optimize.rs b/src/optimize.rs index ad3c3f72..b70f72a1 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -171,8 +171,11 @@ fn optimize_stmt_block( pos: Position, state: &mut State, preserve_result: bool, - count_promote_as_dirty: bool, -) -> Stmt { +) -> Vec { + if statements.is_empty() { + return statements; + } + let orig_len = statements.len(); // Original number of statements in the block, for change detection let orig_constants_len = state.variables.len(); // Original number of constants in the state, for restore later let orig_propagate_constants = state.propagate_constants; @@ -181,7 +184,7 @@ fn optimize_stmt_block( statements.iter_mut().for_each(|stmt| { match stmt { // Add constant literals into the state - Stmt::Const(value_expr, Ident { name, .. }, _) => { + Stmt::Const(value_expr, Ident { name, .. }, _, _) => { optimize_expr(value_expr, state); if value_expr.is_constant() { @@ -189,7 +192,7 @@ fn optimize_stmt_block( } } // Add variables into the state - Stmt::Let(value_expr, Ident { name, pos, .. }, _) => { + Stmt::Let(value_expr, Ident { name, pos, .. }, _, _) => { optimize_expr(value_expr, state); state.push_var(name, AccessMode::ReadWrite, Expr::Unit(*pos)); } @@ -218,7 +221,7 @@ fn optimize_stmt_block( while let Some(expr) = statements.pop() { match expr { - Stmt::Let(expr, _, _) | Stmt::Const(expr, _, _) => removed = expr.is_pure(), + Stmt::Let(expr, _, _, _) | Stmt::Const(expr, _, _, _) => removed = expr.is_pure(), #[cfg(not(feature = "no_module"))] Stmt::Import(expr, _, _) => removed = expr.is_pure(), _ => { @@ -238,7 +241,7 @@ fn optimize_stmt_block( statements .iter_mut() .enumerate() - .for_each(|(i, stmt)| optimize_stmt(stmt, state, i == num_statements)); + .for_each(|(i, stmt)| optimize_stmt(stmt, state, i >= num_statements - 1)); } // Remove everything following the the first return/throw @@ -267,28 +270,7 @@ fn optimize_stmt_block( state.propagate_constants = orig_propagate_constants; - 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 const statement - leave it alone - [x] if matches!(x, Stmt::Const(_, _, _)) => 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), - } + statements } /// Optimize a [statement][Stmt]. @@ -303,18 +285,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { } }, - // if false { if_block } -> Noop - Stmt::If(Expr::BoolConstant(false, pos), x, _) if x.1.is_noop() => { - state.set_dirty(); - *stmt = Stmt::Noop(*pos); - } - // if true { if_block } -> if_block - Stmt::If(Expr::BoolConstant(true, _), x, _) if x.1.is_noop() => { - *stmt = mem::take(&mut x.0); - optimize_stmt(stmt, state, true); - } - // if expr { Noop } - Stmt::If(condition, x, _) if x.1.is_noop() && x.0.is_noop() => { + // if expr {} + Stmt::If(condition, x, _) if x.0.is_empty() && x.1.is_empty() => { state.set_dirty(); let pos = condition.position(); @@ -323,32 +295,60 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { *stmt = if preserve_result { // -> { expr, Noop } - Stmt::Block(vec![Stmt::Expr(expr), mem::take(&mut x.0)], pos) + Stmt::Block(vec![Stmt::Expr(expr), Stmt::Noop(pos)], pos) } else { // -> expr Stmt::Expr(expr) }; } - // if expr { if_block } - Stmt::If(condition, x, _) if x.1.is_noop() => { - optimize_expr(condition, state); - optimize_stmt(&mut x.0, state, true); + // if false { if_block } -> Noop + Stmt::If(Expr::BoolConstant(false, pos), x, _) if x.1.is_empty() => { + state.set_dirty(); + *stmt = Stmt::Noop(*pos); } // if false { if_block } else { else_block } -> else_block - Stmt::If(Expr::BoolConstant(false, _), x, _) if !x.1.is_noop() => { - *stmt = mem::take(&mut x.1); - optimize_stmt(stmt, state, true); + Stmt::If(Expr::BoolConstant(false, _), x, _) => { + state.set_dirty(); + *stmt = match optimize_stmt_block( + mem::take(&mut x.1.statements).into_vec(), + x.1.pos, + state, + preserve_result, + ) { + statements if statements.is_empty() => Stmt::Noop(x.1.pos), + statements => Stmt::Block(statements, x.1.pos), + } } // if true { if_block } else { else_block } -> if_block Stmt::If(Expr::BoolConstant(true, _), x, _) => { - *stmt = mem::take(&mut x.0); - optimize_stmt(stmt, state, true); + state.set_dirty(); + *stmt = match optimize_stmt_block( + mem::take(&mut x.0.statements).into_vec(), + x.0.pos, + state, + preserve_result, + ) { + statements if statements.is_empty() => Stmt::Noop(x.0.pos), + statements => Stmt::Block(statements, x.0.pos), + } } // if expr { if_block } else { else_block } Stmt::If(condition, x, _) => { optimize_expr(condition, state); - optimize_stmt(&mut x.0, state, true); - optimize_stmt(&mut x.1, state, true); + x.0.statements = optimize_stmt_block( + mem::take(&mut x.0.statements).into_vec(), + x.0.pos, + state, + preserve_result, + ) + .into(); + x.1.statements = optimize_stmt_block( + mem::take(&mut x.1.statements).into_vec(), + x.1.pos, + state, + preserve_result, + ) + .into(); } // switch const { ... } @@ -376,9 +376,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { Stmt::Switch(expr, x, _) => { optimize_expr(expr, state); x.0.values_mut() - .for_each(|stmt| optimize_stmt(stmt, state, true)); + .for_each(|stmt| optimize_stmt(stmt, state, preserve_result)); if let Some(def_stmt) = x.1.as_mut() { - optimize_stmt(def_stmt, state, true); + optimize_stmt(def_stmt, state, preserve_result); match def_stmt { Stmt::Noop(_) | Stmt::Expr(Expr::Unit(_)) => x.1 = None, @@ -394,70 +394,122 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { } // while expr { block } 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(); - if !condition.is_unit() { - let mut statements = vec![Stmt::Expr(mem::take(condition))]; - if preserve_result { - statements.push(Stmt::Noop(pos)) - } - *stmt = Stmt::Block(statements, pos); - } else { - *stmt = Stmt::Noop(pos); - }; + block.statements = optimize_stmt_block( + mem::take(&mut block.statements).into_vec(), + block.pos, + state, + false, + ) + .into(); + + if block.len() == 1 { + match block.statements[0] { + // while expr { break; } -> { expr; } + Stmt::Break(pos) => { + // Only a single break statement - turn into running the guard expression once + state.set_dirty(); + if !condition.is_unit() { + let mut statements = vec![Stmt::Expr(mem::take(condition))]; + if preserve_result { + statements.push(Stmt::Noop(pos)) + } + *stmt = Stmt::Block(statements, pos); + } else { + *stmt = Stmt::Noop(pos); + }; + } + _ => (), } - _ => (), } } // do { block } while false | do { block } until true -> { block } Stmt::Do(block, Expr::BoolConstant(true, _), false, _) | Stmt::Do(block, Expr::BoolConstant(false, _), true, _) => { state.set_dirty(); - optimize_stmt(block.as_mut(), state, false); - *stmt = mem::take(block.as_mut()); + *stmt = Stmt::Block( + optimize_stmt_block( + mem::take(&mut block.statements).into_vec(), + block.pos, + state, + false, + ), + block.pos, + ); } // do { block } while|until expr Stmt::Do(block, condition, _, _) => { - optimize_stmt(block.as_mut(), state, false); optimize_expr(condition, state); + block.statements = optimize_stmt_block( + mem::take(&mut block.statements).into_vec(), + block.pos, + state, + false, + ) + .into(); } // for id in expr { block } Stmt::For(iterable, x, _) => { optimize_expr(iterable, state); - optimize_stmt(&mut x.1, state, false); + x.1.statements = optimize_stmt_block( + mem::take(&mut x.1.statements).into_vec(), + x.1.pos, + state, + false, + ) + .into(); } // let id = expr; - Stmt::Let(expr, _, _) => optimize_expr(expr, state), + Stmt::Let(expr, _, _, _) => optimize_expr(expr, state), // import expr as var; #[cfg(not(feature = "no_module"))] Stmt::Import(expr, _, _) => optimize_expr(expr, state), // { block } Stmt::Block(statements, pos) => { - *stmt = optimize_stmt_block(mem::take(statements), *pos, state, preserve_result, true); + *stmt = match optimize_stmt_block(mem::take(statements), *pos, state, preserve_result) { + statements if statements.is_empty() => { + state.set_dirty(); + Stmt::Noop(*pos) + } + // Only one statement - promote + mut statements if statements.len() == 1 => { + state.set_dirty(); + statements.pop().unwrap() + } + statements => Stmt::Block(statements, *pos), + }; } - // try { block } catch ( var ) { block } - Stmt::TryCatch(x, _, _) if x.0.is_pure() => { + // try { pure block } catch ( var ) { block } + Stmt::TryCatch(x, _, _) if x.0.statements.iter().all(Stmt::is_pure) => { // If try block is pure, there will never be any exceptions state.set_dirty(); - let pos = x.0.position(); - 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 = Stmt::Block(statements, pos); + *stmt = Stmt::Block( + optimize_stmt_block( + mem::take(&mut x.0.statements).into_vec(), + x.0.pos, + state, + false, + ), + x.0.pos, + ); } // try { block } catch ( var ) { block } Stmt::TryCatch(x, _, _) => { - optimize_stmt(&mut x.0, state, false); - optimize_stmt(&mut x.2, state, false); + x.0.statements = optimize_stmt_block( + mem::take(&mut x.0.statements).into_vec(), + x.0.pos, + state, + false, + ) + .into(); + x.2.statements = optimize_stmt_block( + mem::take(&mut x.2.statements).into_vec(), + x.2.pos, + state, + false, + ) + .into(); } // {} Stmt::Expr(Expr::Stmt(x, pos)) if x.is_empty() => { @@ -492,17 +544,10 @@ fn optimize_expr(expr: &mut Expr, state: &mut 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 => x.push(stmt), + Expr::Stmt(x, pos) => { + let statements = optimize_stmt_block(mem::take(x).into_vec(), *pos, state, true); + *expr = Expr::Stmt(Box::new(statements.into()), *pos); } - // lhs.rhs #[cfg(not(feature = "no_object"))] Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) { @@ -766,7 +811,7 @@ fn optimize_top_level( statements.iter_mut().enumerate().for_each(|(i, stmt)| { match stmt { - Stmt::Const(value_expr, Ident { name, .. }, _) => { + Stmt::Const(value_expr, Ident { name, .. }, _, _) => { // Load constants optimize_expr(value_expr, &mut state); @@ -774,7 +819,7 @@ fn optimize_top_level( state.push_var(name, AccessMode::ReadOnly, value_expr.clone()); } } - Stmt::Let(value_expr, Ident { name, pos, .. }, _) => { + Stmt::Let(value_expr, Ident { name, pos, .. }, _, _) => { optimize_expr(value_expr, &mut state); state.push_var(name, AccessMode::ReadWrite, Expr::Unit(*pos)); } @@ -782,10 +827,10 @@ fn optimize_top_level( // Keep all variable declarations at this level // and always keep the last return value let keep = match stmt { - Stmt::Let(_, _, _) | Stmt::Const(_, _, _) => true, + Stmt::Let(_, _, _, _) | Stmt::Const(_, _, _, _) => true, #[cfg(not(feature = "no_module"))] Stmt::Import(_, _, _) => true, - _ => i == num_statements - 1, + _ => i >= num_statements - 1, }; optimize_stmt(stmt, &mut state, keep); } diff --git a/src/parser.rs b/src/parser.rs index b8088159..56460f8b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,7 +2,7 @@ use crate::ast::{ BinaryExpr, CustomExpr, Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, ScriptFnDef, - Stmt, + Stmt, StmtBlock, }; use crate::dynamic::{AccessMode, Union}; use crate::engine::{KEYWORD_THIS, OP_CONTAINS}; @@ -755,8 +755,7 @@ fn parse_map_literal( let expr = parse_expr(input, state, lib, settings.level_up())?; let name = state.get_interned_string(name); - let public = false; - map.push((Ident { name, pos, public }, expr)); + map.push((Ident { name, pos }, expr)); match input.peek().unwrap() { (Token::Comma, _) => { @@ -1034,7 +1033,6 @@ fn parse_primary( let var_name_def = Ident { name: state.get_interned_string(s), pos: settings.pos, - public: false, }; Expr::Variable(Box::new((None, None, var_name_def))) } @@ -1049,7 +1047,6 @@ fn parse_primary( let var_name_def = Ident { name: state.get_interned_string(s), pos: settings.pos, - public: false, }; Expr::Variable(Box::new((None, None, var_name_def))) } @@ -1059,7 +1056,6 @@ fn parse_primary( let var_name_def = Ident { name: state.get_interned_string(s), pos: settings.pos, - public: false, }; Expr::Variable(Box::new((index, None, var_name_def))) } @@ -1079,7 +1075,6 @@ fn parse_primary( let var_name_def = Ident { name: state.get_interned_string(s), pos: settings.pos, - public: false, }; Expr::Variable(Box::new((None, None, var_name_def))) } @@ -1088,7 +1083,6 @@ fn parse_primary( let var_name_def = Ident { name: state.get_interned_string(s), pos: settings.pos, - public: false, }; Expr::Variable(Box::new((None, None, var_name_def))) } @@ -1182,7 +1176,6 @@ fn parse_primary( let var_name_def = Ident { name: state.get_interned_string(id2), pos: pos2, - public: false, }; Expr::Variable(Box::new((index, namespace, var_name_def))) } @@ -1853,8 +1846,7 @@ fn parse_custom_syntax( let name = state.get_interned_string(s); segments.push(name.clone()); tokens.push(state.get_interned_string(MARKER_IDENT)); - let public = false; - let var_name_def = Ident { name, pos, public }; + let var_name_def = Ident { name, pos }; keywords.push(Expr::Variable(Box::new((None, None, var_name_def)))); } (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { @@ -2014,9 +2006,19 @@ fn parse_if( Stmt::Noop(Position::NONE) }; + let else_body = match else_body { + Stmt::If(_, _, pos) => { + let mut statements: StaticVec<_> = Default::default(); + statements.push(else_body); + StmtBlock { statements, pos } + } + Stmt::Block(_, _) | Stmt::Noop(_) => else_body.into(), + _ => unreachable!("should either be if or a block, not {:?}", else_body), + }; + Ok(Stmt::If( guard, - Box::new((if_body, else_body)), + Box::new((if_body.into(), else_body)), settings.pos, )) } @@ -2045,9 +2047,9 @@ fn parse_while_loop( ensure_not_assignment(input)?; settings.is_breakable = true; - let body = Box::new(parse_block(input, state, lib, settings.level_up())?); + let body = parse_block(input, state, lib, settings.level_up())?; - Ok(Stmt::While(guard, body, settings.pos)) + Ok(Stmt::While(guard, Box::new(body.into()), settings.pos)) } /// Parse a do loop. @@ -2065,7 +2067,7 @@ fn parse_do( // do { body } [while|until] guard settings.is_breakable = true; - let body = Box::new(parse_block(input, state, lib, settings.level_up())?); + let body = parse_block(input, state, lib, settings.level_up())?; let is_while = match input.next().unwrap() { (Token::While, _) => true, @@ -2083,7 +2085,12 @@ fn parse_do( let guard = parse_expr(input, state, lib, settings.level_up())?; ensure_not_assignment(input)?; - Ok(Stmt::Do(body, guard, is_while, settings.pos)) + Ok(Stmt::Do( + Box::new(body.into()), + guard, + is_while, + settings.pos, + )) } /// Parse a for loop. @@ -2138,7 +2145,7 @@ fn parse_for( state.stack.truncate(prev_stack_len); - Ok(Stmt::For(expr, Box::new((name, body)), settings.pos)) + Ok(Stmt::For(expr, Box::new((name, body.into())), settings.pos)) } /// Parse a variable definition statement. @@ -2170,7 +2177,6 @@ fn parse_let( let var_def = Ident { name: name.clone(), pos, - public: export, }; // let name = ... @@ -2185,9 +2191,9 @@ fn parse_let( match var_type { // let name = expr - AccessMode::ReadWrite => Ok(Stmt::Let(expr, var_def, settings.pos)), + AccessMode::ReadWrite => Ok(Stmt::Let(expr, var_def, export, settings.pos)), // const name = { expr:constant } - AccessMode::ReadOnly => Ok(Stmt::Const(expr, var_def, settings.pos)), + AccessMode::ReadOnly => Ok(Stmt::Const(expr, var_def, export, settings.pos)), } } @@ -2210,15 +2216,7 @@ fn parse_import( // import expr as ... if !match_token(input, Token::As).0 { - return Ok(Stmt::Import( - expr, - Ident { - name: "".into(), - pos: Position::NONE, - public: false, - }, - settings.pos, - )); + return Ok(Stmt::Import(expr, None, settings.pos)); } // import expr as name ... @@ -2236,11 +2234,10 @@ fn parse_import( Ok(Stmt::Import( expr, - Ident { + Some(Ident { name, pos: name_pos, - public: true, - }, + }), settings.pos, )) } @@ -2291,7 +2288,6 @@ fn parse_export( (Token::Identifier(s), pos) => Some(Ident { name: state.get_interned_string(s), pos, - public: false, }), (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { return Err(PERR::Reserved(s).into_err(pos)); @@ -2307,7 +2303,6 @@ fn parse_export( Ident { name: state.get_interned_string(id), pos: id_pos, - public: false, }, rename, )); @@ -2646,7 +2641,6 @@ fn parse_try_catch( (Token::Identifier(s), pos) => Ident { name: state.get_interned_string(s), pos, - public: false, }, (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; @@ -2670,7 +2664,7 @@ fn parse_try_catch( let catch_body = parse_block(input, state, lib, settings.level_up())?; Ok(Stmt::TryCatch( - Box::new((body, var_def, catch_body)), + Box::new((body.into(), var_def, catch_body.into())), settings.pos, catch_pos, )) @@ -2879,7 +2873,6 @@ fn parse_anon_fn( .map(|(name, &pos)| Ident { name: name.clone(), pos, - public: false, }) .collect() } diff --git a/tests/optimizer.rs b/tests/optimizer.rs index c5b91b42..29c1c991 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -54,9 +54,8 @@ fn test_optimizer_parse() -> Result<(), Box> { engine.set_optimization_level(OptimizationLevel::Simple); let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?; - println!("{:?}", ast); - assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, statements: [Block([Const(BoolConstant(false, 1:20), Ident("DECISION" @ 1:9), 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)], functions: Module("#)); + assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, statements: [Block([Const(BoolConstant(false, 1:20), Ident("DECISION" @ 1:9), false, 1:3), Expr(IntegerConstant(123, 1:53))], 1:1)], functions: Module("#)); let ast = engine.compile("if 1 == 2 { 42 }")?;