diff --git a/src/ast.rs b/src/ast.rs index ba459184..c25014a7 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -10,7 +10,7 @@ use crate::stdlib::{ hash::Hash, iter::empty, num::{NonZeroU8, NonZeroUsize}, - ops::{Add, AddAssign}, + ops::{Add, AddAssign, Deref, DerefMut}, vec, vec::Vec, }; @@ -247,7 +247,7 @@ impl AST { #[deprecated = "this method is volatile and may change"] #[inline(always)] pub fn statements(&self) -> &[Stmt] { - &self.body.statements + &self.body.0 } /// Get a mutable reference to the statements. #[cfg(not(feature = "no_optimize"))] @@ -718,7 +718,7 @@ impl AST { } } #[cfg(not(feature = "no_function"))] - for stmt in self.iter_fn_def().flat_map(|f| f.body.statements.iter()) { + for stmt in self.iter_fn_def().flat_map(|f| f.body.0.iter()) { if !stmt.walk(path, on_node) { return false; } @@ -824,9 +824,13 @@ impl<'a> From<&'a Expr> for ASTNode<'a> { /// /// This type is volatile and may change. #[derive(Clone, Hash, Default)] -pub struct StmtBlock(pub StaticVec, pub Position); +pub struct StmtBlock(StaticVec, Position); impl StmtBlock { + /// Create a new [`StmtBlock`]. + pub fn new(statements: impl Into>, pos: Position) -> Self { + Self(statements.into(), pos) + } /// Is this statements block empty? #[inline(always)] pub fn is_empty(&self) -> bool { @@ -837,6 +841,28 @@ impl StmtBlock { pub fn len(&self) -> usize { self.0.len() } + /// Get the position of this statements block. + pub fn position(&self) -> Position { + self.1 + } + /// Get the statements of this statements block. + pub fn statements(&mut self) -> &mut StaticVec { + &mut self.0 + } +} + +impl Deref for StmtBlock { + type Target = StaticVec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for StmtBlock { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } } impl fmt::Debug for StmtBlock { @@ -850,6 +876,13 @@ impl fmt::Debug for StmtBlock { } } +impl From for Stmt { + fn from(block: StmtBlock) -> Self { + let block_pos = block.position(); + Self::Block(block.0.into_vec(), block_pos) + } +} + /// _(INTERNALS)_ A statement. /// Exported under the `internals` feature only. /// diff --git a/src/engine.rs b/src/engine.rs index 45001b93..17274c4e 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, Ident, OpAssignment, ReturnType, Stmt, StmtBlock}; +use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, ReturnType, Stmt}; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::fn_native::{ CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback, @@ -1733,7 +1733,7 @@ impl Engine { // Statement block Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT), Expr::Stmt(x) => { - self.eval_stmt_block(scope, mods, state, lib, this_ptr, &x.0, true, level) + self.eval_stmt_block(scope, mods, state, lib, this_ptr, x, true, level) } // lhs[idx_expr] @@ -2133,31 +2133,29 @@ impl Engine { } // If statement - Stmt::If(expr, x, _) => { - let (StmtBlock(if_stmt, _), StmtBlock(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 { - if !if_stmt.is_empty() { - self.eval_stmt_block( - scope, mods, state, lib, this_ptr, if_stmt, true, level, - ) - } else { - Ok(Dynamic::UNIT) - } + Stmt::If(expr, x, _) => 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 { + if !x.0.is_empty() { + self.eval_stmt_block( + scope, mods, state, lib, this_ptr, &x.0, 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) - } + Ok(Dynamic::UNIT) } - }) - } + } else { + if !x.1.is_empty() { + self.eval_stmt_block( + scope, mods, state, lib, this_ptr, &x.1, true, level, + ) + } else { + Ok(Dynamic::UNIT) + } + } + }), // Switch statement Stmt::Switch(match_expr, x, _) => { @@ -2188,7 +2186,7 @@ impl Engine { } } - let statements = &(t.1).0; + let statements = &t.1; Some(if !statements.is_empty() { self.eval_stmt_block( @@ -2204,7 +2202,6 @@ impl Engine { } .unwrap_or_else(|| { // Default match clause - let def_stmt = &def_stmt.0; if !def_stmt.is_empty() { self.eval_stmt_block( scope, mods, state, lib, this_ptr, def_stmt, true, level, @@ -2216,75 +2213,65 @@ impl Engine { } // While loop - Stmt::While(expr, body, _) => { - let body = &body.0; - 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, _) => 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 { - return Ok(Dynamic::UNIT); - } - if body.is_empty() { - continue; - } + if !condition { + return Ok(Dynamic::UNIT); + } + if body.is_empty() { + continue; + } + 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), + }, + } + }, + + // Do loop + Stmt::Do(body, expr, is_while, _) => loop { + if !body.is_empty() { 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), }, } } - } - // Do loop - Stmt::Do(body, expr, is_while, _) => { - let body = &body.0; - - loop { - if !body.is_empty() { - match self - .eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, 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); } - - 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); - } + } else { + if *is_while { + return Ok(Dynamic::UNIT); } } - } + }, // For loop Stmt::For(expr, x, _) => { - let (Ident { name, .. }, StmtBlock(statements, pos)) = x.as_ref(); + let (Ident { name, .. }, statements) = x.as_ref(); + let pos = statements.position(); let iter_obj = self .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .flatten(); @@ -2333,7 +2320,7 @@ impl Engine { *loop_var = value; } - self.inc_operations(state, *pos)?; + self.inc_operations(state, pos)?; if statements.is_empty() { continue; @@ -2367,10 +2354,10 @@ impl Engine { // Try/Catch statement Stmt::TryCatch(x, _, _) => { - let (StmtBlock(try_body, _), err_var, StmtBlock(catch_body, _)) = x.as_ref(); + let (try_stmt, err_var, catch_stmt) = x.as_ref(); let result = self - .eval_stmt_block(scope, mods, state, lib, this_ptr, try_body, true, level) + .eval_stmt_block(scope, mods, state, lib, this_ptr, try_stmt, true, level) .map(|_| Dynamic::UNIT); match result { @@ -2430,7 +2417,7 @@ impl Engine { } let result = self.eval_stmt_block( - scope, mods, state, lib, this_ptr, catch_body, true, level, + scope, mods, state, lib, this_ptr, catch_stmt, true, level, ); state.scope_level -= 1; diff --git a/src/fn_call.rs b/src/fn_call.rs index c8c139a0..9f9984e7 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -540,7 +540,7 @@ impl Engine { } // Evaluate the function - let body = &fn_def.body.0; + let body = &fn_def.body; let result = self .eval_stmt_block(scope, mods, state, unified_lib, this_ptr, body, true, level) diff --git a/src/optimize.rs b/src/optimize.rs index 38074208..2f3111bd 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,6 +1,6 @@ //! Module implementing the [`AST`] optimizer. -use crate::ast::{Expr, Stmt, StmtBlock}; +use crate::ast::{Expr, Stmt}; use crate::dynamic::AccessMode; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::fn_builtin::get_builtin_binary_op_fn; @@ -416,28 +416,30 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { // if false { if_block } else { else_block } -> else_block Stmt::If(Expr::BoolConstant(false, _), x, _) => { state.set_dirty(); - let else_block = mem::take(&mut (x.1).0).into_vec(); + let else_block = mem::take(&mut *x.1).into_vec(); *stmt = match optimize_stmt_block(else_block, state, preserve_result, true, false) { - statements if statements.is_empty() => Stmt::Noop((x.1).1), - statements => Stmt::Block(statements, (x.1).1), + statements if statements.is_empty() => Stmt::Noop(x.1.position()), + statements => Stmt::Block(statements, x.1.position()), } } // if true { if_block } else { else_block } -> if_block Stmt::If(Expr::BoolConstant(true, _), x, _) => { state.set_dirty(); - let if_block = mem::take(&mut (x.0).0).into_vec(); + let if_block = mem::take(&mut *x.0).into_vec(); *stmt = match optimize_stmt_block(if_block, state, preserve_result, true, false) { - statements if statements.is_empty() => Stmt::Noop((x.0).1), - statements => Stmt::Block(statements, (x.0).1), + statements if statements.is_empty() => Stmt::Noop(x.0.position()), + statements => Stmt::Block(statements, x.0.position()), } } // if expr { if_block } else { else_block } Stmt::If(condition, x, _) => { optimize_expr(condition, state); - let if_block = mem::take(&mut (x.0).0).into_vec(); - (x.0).0 = optimize_stmt_block(if_block, state, preserve_result, true, false).into(); - let else_block = mem::take(&mut (x.1).0).into_vec(); - (x.1).0 = optimize_stmt_block(else_block, state, preserve_result, true, false).into(); + let if_block = mem::take(x.0.statements()).into_vec(); + *x.0.statements() = + optimize_stmt_block(if_block, state, preserve_result, true, false).into(); + let else_block = mem::take(x.1.statements()).into_vec(); + *x.1.statements() = + optimize_stmt_block(else_block, state, preserve_result, true, false).into(); } // switch const { ... } @@ -455,9 +457,13 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def } optimize_expr(&mut condition, state); - let def_block = mem::take(&mut (x.1).0).into_vec(); + let def_block = mem::take(&mut *x.1).into_vec(); let def_stmt = optimize_stmt_block(def_block, state, true, true, false); - let def_pos = if (x.1).1.is_none() { *pos } else { (x.1).1 }; + let def_pos = if x.1.position().is_none() { + *pos + } else { + x.1.position() + }; *stmt = Stmt::If( condition, @@ -469,16 +475,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { ); } else { // Promote the matched case - let StmtBlock(statements, new_pos) = mem::take(&mut block.1); + let new_pos = block.1.position(); + let statements = mem::take(&mut *block.1); let statements = optimize_stmt_block(statements.into_vec(), state, true, true, false); *stmt = Stmt::Block(statements, new_pos); } } else { // Promote the default case - let def_block = mem::take(&mut (x.1).0).into_vec(); + let def_block = mem::take(&mut *x.1).into_vec(); let def_stmt = optimize_stmt_block(def_block, state, true, true, false); - let def_pos = if (x.1).1.is_none() { *pos } else { (x.1).1 }; + let def_pos = if x.1.position().is_none() { + *pos + } else { + x.1.position() + }; *stmt = Stmt::Block(def_stmt, def_pos); } } @@ -498,10 +509,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { _ => { block.0 = Some(condition); - let match_block = mem::take(&mut (block.1).0).into_vec(); - (block.1).0 = - optimize_stmt_block(match_block, state, preserve_result, true, false) - .into(); + *block.1.statements() = optimize_stmt_block( + mem::take(block.1.statements()).into_vec(), + state, + preserve_result, + true, + false, + ) + .into(); } } }); @@ -515,8 +530,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { x.0.remove(&key); } - let def_block = mem::take(&mut (x.1).0).into_vec(); - (x.1).0 = optimize_stmt_block(def_block, state, preserve_result, true, false).into() + let def_block = mem::take(x.1.statements()).into_vec(); + *x.1.statements() = + optimize_stmt_block(def_block, state, preserve_result, true, false).into(); } // while false { block } -> Noop @@ -527,12 +543,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { // while expr { block } Stmt::While(condition, body, _) => { optimize_expr(condition, state); - - let block = mem::take(&mut body.0).into_vec(); - body.0 = optimize_stmt_block(block, state, false, true, false).into(); + let block = mem::take(body.statements()).into_vec(); + *body.statements() = optimize_stmt_block(block, state, false, true, false).into(); if body.len() == 1 { - match body.0[0] { + match body[0] { // while expr { break; } -> { expr; } Stmt::Break(pos) => { // Only a single break statement - turn into running the guard expression once @@ -555,23 +570,24 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { Stmt::Do(body, Expr::BoolConstant(true, _), false, _) | Stmt::Do(body, Expr::BoolConstant(false, _), true, _) => { state.set_dirty(); - let block = mem::take(&mut body.0).into_vec(); + let block_pos = body.position(); + let block = mem::take(body.statements()).into_vec(); *stmt = Stmt::Block( optimize_stmt_block(block, state, false, true, false), - body.1, + block_pos, ); } // do { block } while|until expr Stmt::Do(body, condition, _, _) => { optimize_expr(condition, state); - let block = mem::take(&mut body.0).into_vec(); - body.0 = optimize_stmt_block(block, state, false, true, false).into(); + let block = mem::take(body.statements()).into_vec(); + *body.statements() = optimize_stmt_block(block, state, false, true, false).into(); } // for id in expr { block } Stmt::For(iterable, x, _) => { optimize_expr(iterable, state); - let body = mem::take(&mut (x.1).0).into_vec(); - (x.1).0 = optimize_stmt_block(body, state, false, true, false).into(); + let body = mem::take(x.1.statements()).into_vec(); + *x.1.statements() = optimize_stmt_block(body, state, false, true, false).into(); } // let id = expr; Stmt::Let(expr, _, _, _) => optimize_expr(expr, state), @@ -597,31 +613,32 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { } } // try { pure try_block } catch ( var ) { catch_block } -> try_block - Stmt::TryCatch(x, _, _) if (x.0).0.iter().all(Stmt::is_pure) => { + Stmt::TryCatch(x, _, _) if x.0.iter().all(Stmt::is_pure) => { // If try block is pure, there will never be any exceptions state.set_dirty(); - let try_block = mem::take(&mut (x.0).0).into_vec(); + let try_pos = x.0.position(); + let try_block = mem::take(&mut *x.0).into_vec(); *stmt = Stmt::Block( optimize_stmt_block(try_block, state, false, true, false), - (x.0).1, + try_pos, ); } // try { try_block } catch ( var ) { catch_block } Stmt::TryCatch(x, _, _) => { - let try_block = mem::take(&mut (x.0).0).into_vec(); - (x.0).0 = optimize_stmt_block(try_block, state, false, true, false).into(); - let catch_block = mem::take(&mut (x.2).0).into_vec(); - (x.2).0 = optimize_stmt_block(catch_block, state, false, true, false).into(); + let try_block = mem::take(x.0.statements()).into_vec(); + *x.0.statements() = optimize_stmt_block(try_block, state, false, true, false).into(); + let catch_block = mem::take(x.2.statements()).into_vec(); + *x.2.statements() = optimize_stmt_block(catch_block, state, false, true, false).into(); } // {} - Stmt::Expr(Expr::Stmt(x)) if x.0.is_empty() => { + Stmt::Expr(Expr::Stmt(x)) if x.is_empty() => { state.set_dirty(); - *stmt = Stmt::Noop(x.1); + *stmt = Stmt::Noop(x.position()); } // {...}; Stmt::Expr(Expr::Stmt(x)) => { state.set_dirty(); - *stmt = Stmt::Block(mem::take(&mut x.0).into_vec(), x.1); + *stmt = mem::take(x.as_mut()).into(); } // expr; Stmt::Expr(expr) => optimize_expr(expr, state), @@ -644,13 +661,13 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { match expr { // {} - Expr::Stmt(x) if x.0.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.1) } + Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) } // { stmt; ... } - do not count promotion as dirty because it gets turned back into an array Expr::Stmt(x) => { - x.0 = optimize_stmt_block(mem::take(&mut x.0).into_vec(), state, true, true, false).into(); + *x.statements() = optimize_stmt_block(mem::take(x.statements()).into_vec(), state, true, true, false).into(); // { Stmt(Expr) } - promote - match x.0.as_mut() { + match x.as_mut().as_mut() { [ Stmt::Expr(e) ] => { state.set_dirty(); *expr = mem::take(e); } _ => () } @@ -1068,14 +1085,13 @@ pub fn optimize_into_ast( .map(|fn_def| { let mut fn_def = crate::fn_native::shared_take_or_clone(fn_def); - let mut body = fn_def.body.0.into_vec(); - // Optimize the function body let state = &mut State::new(engine, lib2, level); - body = optimize_stmt_block(body, state, true, true, true); + let body = mem::take(fn_def.body.statements()).into_vec(); - fn_def.body.0 = body.into(); + *fn_def.body.statements() = + optimize_stmt_block(body, state, true, true, true).into(); fn_def }) diff --git a/src/parser.rs b/src/parser.rs index 0f0270fb..b94683c3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2888,7 +2888,7 @@ fn make_curry_from_externals( let mut statements: StaticVec<_> = Default::default(); statements.extend(externals.into_iter().map(Stmt::Share)); statements.push(Stmt::Expr(expr)); - Expr::Stmt(Box::new(StmtBlock(statements, pos))) + Expr::Stmt(Box::new(StmtBlock::new(statements, pos))) } /// Parse an anonymous function definition.