From 2168fd536111263d9dd6000e85a6ad1ca98b25b5 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Nov 2020 11:49:02 +0800 Subject: [PATCH] Expr::Stmt takes a statements block. --- src/ast.rs | 4 ++-- src/engine.rs | 53 +++++++++++++++++++++++++++++++---------------- src/engine_api.rs | 4 ++-- src/fn_call.rs | 7 ++++--- src/optimize.rs | 24 ++++++++++++++++----- src/parser.rs | 32 +++++++++++++++------------- 6 files changed, 80 insertions(+), 44 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index c13c2b8b..ebf38250 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -908,7 +908,7 @@ pub enum Expr { /// Property access - (getter, setter), prop Property(Box<((String, String), IdentX)>), /// { stmt } - Stmt(Box, Position), + Stmt(Box>, Position), /// Wrapped expression - should not be optimized away. Expr(Box), /// func(expr, ... ) @@ -1092,7 +1092,7 @@ impl Expr { x.lhs.is_pure() && x.rhs.is_pure() } - Self::Stmt(x, _) => x.is_pure(), + Self::Stmt(x, _) => x.iter().all(Stmt::is_pure), Self::Variable(_) => true, diff --git a/src/engine.rs b/src/engine.rs index 9792c766..bb1f0725 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1579,7 +1579,9 @@ impl Engine { Expr::Property(_) => unreachable!(), // Statement block - Expr::Stmt(x, _) => self.eval_stmt(scope, mods, state, lib, this_ptr, x, level), + Expr::Stmt(x, _) => { + self.eval_statements(scope, mods, state, lib, this_ptr, x.as_ref(), level) + } // lhs[idx_expr] #[cfg(not(feature = "no_index"))] @@ -1706,6 +1708,37 @@ impl Engine { .map_err(|err| err.fill_position(expr.position())) } + pub(crate) fn eval_statements<'a>( + &self, + scope: &mut Scope, + mods: &mut Imports, + state: &mut State, + lib: &[&Module], + this_ptr: &mut Option<&mut Dynamic>, + statements: impl IntoIterator, + level: usize, + ) -> Result> { + let prev_scope_len = scope.len(); + let prev_mods_len = mods.len(); + state.scope_level += 1; + + let result = statements + .into_iter() + .try_fold(Default::default(), |_, stmt| { + self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) + }); + + scope.rewind(prev_scope_len); + mods.truncate(prev_mods_len); + state.scope_level -= 1; + + // The impact of an eval statement goes away at the end of a block + // because any new variables introduced will go out of scope + state.always_search = false; + + result + } + /// Evaluate a statement /// /// @@ -1886,23 +1919,7 @@ impl Engine { // Block scope Stmt::Block(statements, _) => { - let prev_scope_len = scope.len(); - let prev_mods_len = mods.len(); - state.scope_level += 1; - - let result = statements.iter().try_fold(Default::default(), |_, stmt| { - self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) - }); - - scope.rewind(prev_scope_len); - mods.truncate(prev_mods_len); - state.scope_level -= 1; - - // The impact of an eval statement goes away at the end of a block - // because any new variables introduced will go out of scope - state.always_search = false; - - result + self.eval_statements(scope, mods, state, lib, this_ptr, statements, level) } // If-else statement diff --git a/src/engine_api.rs b/src/engine_api.rs index 619e5f54..71325e6e 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -1405,7 +1405,7 @@ impl Engine { mods: &mut Imports, ast: &'a AST, ) -> Result<(Dynamic, u64), Box> { - self.eval_statements(scope, mods, ast.statements(), &[ast.lib()]) + self.eval_statements_raw(scope, mods, ast.statements(), &[ast.lib()]) } /// Evaluate a file, but throw away the result and only return error (if any). @@ -1467,7 +1467,7 @@ impl Engine { ast: &AST, ) -> Result<(), Box> { let mut mods = Default::default(); - self.eval_statements(scope, &mut mods, ast.statements(), &[ast.lib()]) + self.eval_statements_raw(scope, &mut mods, ast.statements(), &[ast.lib()]) .map(|_| ()) } diff --git a/src/fn_call.rs b/src/fn_call.rs index 1a645870..2979c401 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -604,9 +604,10 @@ impl Engine { } } - /// Evaluate a list of statements. + /// Evaluate a list of statements with an empty state and no `this` pointer. + /// This is commonly used to evaluate a list of statements in an `AST` or a script function body. #[inline] - pub(crate) fn eval_statements<'a>( + pub(crate) fn eval_statements_raw<'a>( &self, scope: &mut Scope, mods: &mut Imports, @@ -667,7 +668,7 @@ impl Engine { } // Evaluate the AST - let (result, operations) = self.eval_statements(scope, mods, ast.statements(), lib)?; + let (result, operations) = self.eval_statements_raw(scope, mods, ast.statements(), lib)?; state.operations += operations; self.inc_operations(state)?; diff --git a/src/optimize.rs b/src/optimize.rs index 6d9bc23d..3778a7b1 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -409,9 +409,14 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { ))) } // expr; - Stmt::Expr(Expr::Stmt(x, _)) if matches!(*x, Stmt::Expr(_)) => { + Stmt::Expr(Expr::Stmt(x, pos)) if x.is_empty() => { state.set_dirty(); - optimize_stmt(*x, state, preserve_result) + Stmt::Noop(pos) + } + // expr; + Stmt::Expr(Expr::Stmt(mut x, _)) if x.len() == 1 => { + state.set_dirty(); + optimize_stmt(x.remove(0), state, preserve_result) } // expr; Stmt::Expr(expr) => Stmt::Expr(optimize_expr(expr, state)), @@ -438,8 +443,13 @@ 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::Stmt(x, pos) if x.is_empty() => { + state.set_dirty(); + Expr::Unit(pos) + } // { stmt } - Expr::Stmt(x, pos) => match *x { + Expr::Stmt(mut x, pos) if x.len() == 1 => match x.remove(0) { // {} -> () Stmt::Noop(_) => { state.set_dirty(); @@ -451,8 +461,12 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { optimize_expr(expr, state) } // { stmt } - stmt => Expr::Stmt(Box::new(optimize_stmt(stmt, state, true)), pos), - }, + stmt => Expr::Stmt(Box::new(vec![optimize_stmt(stmt, state, true)].into()), pos) + } + // { 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"))] diff --git a/src/parser.rs b/src/parser.rs index bd6d613c..d808522e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -765,8 +765,10 @@ fn parse_primary( let (token, _) = match token { // { - block statement as expression Token::LeftBrace if settings.allow_stmt_expr => { - return parse_block(input, state, lib, settings.level_up()) - .map(|block| Expr::Stmt(Box::new(block), settings.pos)) + return parse_block(input, state, lib, settings.level_up()).map(|block| match block { + Stmt::Block(statements, pos) => Expr::Stmt(Box::new(statements.into()), pos), + _ => unreachable!(), + }) } Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), _ => input.next().unwrap(), @@ -962,10 +964,11 @@ fn parse_unary( match token { // If statement is allowed to act as expressions - Token::If if settings.allow_if_expr => Ok(Expr::Stmt( - Box::new(parse_if(input, state, lib, settings.level_up())?), - settings.pos, - )), + Token::If if settings.allow_if_expr => { + let mut block: StaticVec<_> = Default::default(); + block.push(parse_if(input, state, lib, settings.level_up())?); + Ok(Expr::Stmt(Box::new(block), settings.pos)) + } // -expr Token::UnaryMinus => { let pos = eat_token(input, Token::UnaryMinus); @@ -1657,12 +1660,13 @@ fn parse_custom_syntax( exprs.push(parse_expr(input, state, lib, settings)?); segments.push(MARKER_EXPR.into()); } - MARKER_BLOCK => { - let stmt = parse_block(input, state, lib, settings)?; - let pos = stmt.position(); - exprs.push(Expr::Stmt(Box::new(stmt), pos)); - segments.push(MARKER_BLOCK.into()); - } + MARKER_BLOCK => match parse_block(input, state, lib, settings)? { + Stmt::Block(statements, pos) => { + exprs.push(Expr::Stmt(Box::new(statements.into()), pos)); + segments.push(MARKER_BLOCK.into()); + } + _ => unreachable!(), + }, s => match input.next().unwrap() { (Token::LexError(err), pos) => return Err(err.into_err(pos)), (t, _) if t.syntax().as_ref() == s => { @@ -2525,12 +2529,12 @@ fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec, pos: Po #[cfg(not(feature = "no_closure"))] { // Statement block - let mut statements: Vec<_> = Default::default(); + let mut statements: StaticVec<_> = Default::default(); // Insert `Share` statements statements.extend(externals.into_iter().map(|x| Stmt::Share(Box::new(x)))); // Final expression statements.push(Stmt::Expr(expr)); - Expr::Stmt(Box::new(Stmt::Block(statements, pos)), pos) + Expr::Stmt(Box::new(statements), pos) } #[cfg(feature = "no_closure")]