From 0d2e3d82f37baf0d539dd2620593cc85dde94c25 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 16 Feb 2022 17:51:14 +0800 Subject: [PATCH] Reduce size of Stmt. --- src/api/compile.rs | 15 +- src/ast/ast.rs | 3 +- src/ast/stmt.rs | 269 ++++++++++++++++++++--------------- src/eval/debugger.rs | 27 ++-- src/eval/stmt.rs | 68 +++++---- src/optimizer.rs | 331 +++++++++++++++++++++++++------------------ src/parser.rs | 80 +++++------ src/tests.rs | 4 +- tests/optimizer.rs | 2 +- 9 files changed, 457 insertions(+), 342 deletions(-) diff --git a/src/api/compile.rs b/src/api/compile.rs index 39595a8d..6d491da6 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -99,12 +99,15 @@ impl Engine { ) { ast.walk(&mut |path| match path.last().unwrap() { // Collect all `import` statements with a string constant path - ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, ..), ..)) - if !resolver.contains_path(s) && !imports.contains(s.as_str()) => - { - imports.insert(s.clone().into()); - true - } + ASTNode::Stmt(Stmt::Import(x, ..)) => match x.0 { + Expr::StringConstant(ref s, ..) + if !resolver.contains_path(s) && !imports.contains(s.as_str()) => + { + imports.insert(s.clone().into()); + true + } + _ => true, + }, _ => true, }); } diff --git a/src/ast/ast.rs b/src/ast/ast.rs index dcf43977..b7603111 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -744,10 +744,11 @@ impl AST { include_variables: bool, ) -> impl Iterator { self.statements().iter().filter_map(move |stmt| match stmt { - Stmt::Var(expr, name, options, ..) + Stmt::Var(x, options, ..) if options.contains(AST_OPTION_CONSTANT) && include_constants || !options.contains(AST_OPTION_CONSTANT) && include_variables => { + let (name, expr) = x.as_ref(); if let Some(value) = expr.get_literal_value() { Some((name.as_str(), options.contains(AST_OPTION_CONSTANT), value)) } else { diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 08ab0f21..26c880fe 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -114,9 +114,9 @@ pub struct SwitchCases { /// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s. pub cases: BTreeMap>, /// Statements block for the default case (there can be no condition for the default case). - pub def_case: StmtBlock, + pub def_case: Box, /// List of range cases. - pub ranges: StaticVec<(INT, INT, bool, ConditionalStmtBlock)>, + pub ranges: StaticVec<(INT, INT, bool, Box)>, } /// _(internals)_ A `try-catch` block. @@ -131,8 +131,20 @@ pub struct TryCatchBlock { pub catch_block: StmtBlock, } +/// _(internals)_ The underlying container type for [`StmtBlock`]. +/// Exported under the `internals` feature only. +/// +/// A [`SmallVec`](https://crates.io/crates/smallvec) containing up to 8 items inline is used to +/// hold a statements block, with the assumption that most program blocks would container fewer than +/// 8 statements, and those that do have a lot more statements. +#[cfg(not(feature = "no_std"))] pub type StmtBlockContainer = smallvec::SmallVec<[Stmt; 8]>; +/// _(internals)_ The underlying container type for [`StmtBlock`]. +/// Exported under the `internals` feature only. +#[cfg(feature = "no_std")] +pub type StmtBlockContainer = StaticVec; + /// _(internals)_ A scoped block of statements. /// Exported under the `internals` feature only. #[derive(Clone, Hash, Default)] @@ -143,21 +155,27 @@ impl StmtBlock { pub const NONE: Self = Self::empty(Position::NONE); /// Create a new [`StmtBlock`]. + #[inline(always)] #[must_use] pub fn new( statements: impl IntoIterator, start_pos: Position, end_pos: Position, ) -> Self { + Self::new_with_span(statements, Span::new(start_pos, end_pos)) + } + /// Create a new [`StmtBlock`]. + #[must_use] + pub fn new_with_span(statements: impl IntoIterator, span: Span) -> Self { let mut statements: smallvec::SmallVec<_> = statements.into_iter().collect(); statements.shrink_to_fit(); - Self(statements, Span::new(start_pos, end_pos)) + Self(statements, span) } /// Create an empty [`StmtBlock`]. #[inline(always)] #[must_use] pub const fn empty(pos: Position) -> Self { - Self(smallvec::SmallVec::new_const(), Span::new(pos, pos)) + Self(StmtBlockContainer::new_const(), Span::new(pos, pos)) } /// Is this statements block empty? #[inline(always)] @@ -269,8 +287,8 @@ impl From for StmtBlock { #[inline] fn from(stmt: Stmt) -> Self { match stmt { - Stmt::Block(mut block, span) => Self(block.iter_mut().map(mem::take).collect(), span), - Stmt::Noop(pos) => Self(smallvec::SmallVec::new_const(), Span::new(pos, pos)), + Stmt::Block(block) => *block, + Stmt::Noop(pos) => Self(StmtBlockContainer::new_const(), Span::new(pos, pos)), _ => { let pos = stmt.position(); Self(vec![stmt].into(), Span::new(pos, Position::NONE)) @@ -303,7 +321,7 @@ pub enum Stmt { /// No-op. Noop(Position), /// `if` expr `{` stmt `}` `else` `{` stmt `}` - If(Expr, Box<(StmtBlock, StmtBlock)>, Position), + If(Box<(Expr, StmtBlock, StmtBlock)>, Position), /// `switch` expr `{` literal or range or _ `if` condition `=>` stmt `,` ... `}` /// /// ### Data Structure @@ -311,27 +329,27 @@ pub enum Stmt { /// 0) Hash table for (condition, block) /// 1) Default block /// 2) List of ranges: (start, end, inclusive, condition, statement) - Switch(Expr, Box, Position), + Switch(Box<(Expr, SwitchCases)>, Position), /// `while` expr `{` stmt `}` | `loop` `{` stmt `}` /// /// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement. - While(Expr, Box, Position), + While(Box<(Expr, StmtBlock)>, Position), /// `do` `{` stmt `}` `while`|`until` expr /// /// ### Option Flags /// /// * [`AST_OPTION_NONE`] = `while` /// * [`AST_OPTION_NEGATED`] = `until` - Do(Box, Expr, OptionFlags, Position), + Do(Box<(Expr, StmtBlock)>, OptionFlags, Position), /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` - For(Expr, Box<(Ident, Option, StmtBlock)>, Position), + For(Box<(Ident, Expr, Option, StmtBlock)>, Position), /// \[`export`\] `let`|`const` id `=` expr /// /// ### Option Flags /// /// * [`AST_OPTION_EXPORTED`] = `export` /// * [`AST_OPTION_CONSTANT`] = `const` - Var(Expr, Box, OptionFlags, Position), + Var(Box<(Ident, Expr)>, OptionFlags, Position), /// expr op`=` expr Assignment(Box<(Option>, BinaryExpr)>, Position), /// func `(` expr `,` ... `)` @@ -340,11 +358,11 @@ pub enum Stmt { /// function call forming one statement. FnCall(Box, Position), /// `{` stmt`;` ... `}` - Block(Box<[Stmt]>, Span), + Block(Box), /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` TryCatch(Box, Position), /// [expression][Expr] - Expr(Expr), + Expr(Box), /// `continue`/`break` /// /// ### Option Flags @@ -358,12 +376,12 @@ pub enum Stmt { /// /// * [`AST_OPTION_NONE`] = `return` /// * [`AST_OPTION_BREAK`] = `throw` - Return(OptionFlags, Option, Position), + Return(Option>, OptionFlags, Position), /// `import` expr `as` var /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] - Import(Expr, Option>, Position), + Import(Box<(Expr, Option)>, Position), /// `export` var `as` var /// /// Not available under `no_module`. @@ -378,7 +396,7 @@ pub enum Stmt { /// This variant does not map to any language structure. It is currently only used only to /// convert a normal variable into a shared variable when the variable is _captured_ by a closure. #[cfg(not(feature = "no_closure"))] - Share(crate::Identifier, Position), + Share(Box, Position), } impl Default for Stmt { @@ -391,7 +409,21 @@ impl Default for Stmt { impl From for Stmt { #[inline(always)] fn from(block: StmtBlock) -> Self { - Self::Block(block.0.into_boxed_slice(), block.1) + Self::Block(block.into()) + } +} + +impl> From<(T, Position, Position)> for Stmt { + #[inline(always)] + fn from(value: (T, Position, Position)) -> Self { + StmtBlock::new(value.0, value.1, value.2).into() + } +} + +impl> From<(T, Span)> for Stmt { + #[inline(always)] + fn from(value: (T, Span)) -> Self { + StmtBlock::new_with_span(value.0, value.1).into() } } @@ -419,7 +451,7 @@ impl Stmt { | Self::Var(.., pos) | Self::TryCatch(.., pos) => *pos, - Self::Block(.., span) => span.start(), + Self::Block(x) => x.position(), Self::Expr(x) => x.start_position(), @@ -448,7 +480,7 @@ impl Stmt { | Self::Var(.., pos) | Self::TryCatch(.., pos) => *pos = new_pos, - Self::Block(.., span) => *span = Span::new(new_pos, span.end()), + Self::Block(x) => x.set_position(new_pos, x.end_position()), Self::Expr(x) => { x.set_position(new_pos); @@ -504,11 +536,13 @@ impl Stmt { // A No-op requires a semicolon in order to know it is an empty statement! Self::Noop(..) => false, - Self::Expr(Expr::Custom(x, ..)) if x.is_self_terminated() => true, + Self::Expr(e) => match &**e { + Expr::Custom(x, ..) if x.is_self_terminated() => true, + _ => false, + }, Self::Var(..) | Self::Assignment(..) - | Self::Expr(..) | Self::FnCall(..) | Self::Do(..) | Self::BreakLoop(..) @@ -529,38 +563,37 @@ impl Stmt { match self { Self::Noop(..) => true, Self::Expr(expr) => expr.is_pure(), - Self::If(condition, x, ..) => { - condition.is_pure() - && x.0.iter().all(Stmt::is_pure) - && x.1.iter().all(Stmt::is_pure) + Self::If(x, ..) => { + x.0.is_pure() && x.1.iter().all(Stmt::is_pure) && x.2.iter().all(Stmt::is_pure) } - Self::Switch(expr, x, ..) => { - expr.is_pure() - && x.cases.values().all(|block| { + Self::Switch(x, ..) => { + x.0.is_pure() + && x.1.cases.values().all(|block| { block.condition.as_ref().map(Expr::is_pure).unwrap_or(true) && block.statements.iter().all(Stmt::is_pure) }) - && x.ranges.iter().all(|(.., block)| { + && x.1.ranges.iter().all(|(.., block)| { block.condition.as_ref().map(Expr::is_pure).unwrap_or(true) && block.statements.iter().all(Stmt::is_pure) }) - && x.def_case.iter().all(Stmt::is_pure) + && x.1.def_case.iter().all(Stmt::is_pure) } // Loops that exit can be pure because it can never be infinite. - Self::While(Expr::BoolConstant(false, ..), ..) => true, - Self::Do(body, Expr::BoolConstant(x, ..), options, ..) - if *x == options.contains(AST_OPTION_NEGATED) => - { - body.iter().all(Stmt::is_pure) - } + Self::While(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => true, + Self::Do(x, options, ..) if matches!(x.0, Expr::BoolConstant(..)) => match x.0 { + Expr::BoolConstant(cond, ..) if cond == options.contains(AST_OPTION_NEGATED) => { + x.1.iter().all(Stmt::is_pure) + } + _ => false, + }, // Loops are never pure since they can be infinite - and that's a side effect. Self::While(..) | Self::Do(..) => false, // For loops can be pure because if the iterable is pure, it is finite, // so infinite loops can never occur. - Self::For(iterable, x, ..) => iterable.is_pure() && x.2.iter().all(Stmt::is_pure), + Self::For(x, ..) => x.1.is_pure() && x.3.iter().all(Stmt::is_pure), Self::Var(..) | Self::Assignment(..) | Self::FnCall(..) => false, Self::Block(block, ..) => block.iter().all(|stmt| stmt.is_pure()), @@ -591,11 +624,13 @@ impl Stmt { match self { Self::Var(..) => true, - Self::Expr(Expr::Stmt(s)) => s.iter().all(Stmt::is_block_dependent), + Self::Expr(e) => match &**e { + Expr::Stmt(s) => s.iter().all(Stmt::is_block_dependent), + Expr::FnCall(x, ..) => !x.is_qualified() && x.name == KEYWORD_EVAL, + _ => false, + }, - Self::FnCall(x, ..) | Self::Expr(Expr::FnCall(x, ..)) => { - !x.is_qualified() && x.name == KEYWORD_EVAL - } + Self::FnCall(x, ..) => !x.is_qualified() && x.name == KEYWORD_EVAL, #[cfg(not(feature = "no_module"))] Self::Import(..) | Self::Export(..) => true, @@ -613,12 +648,15 @@ impl Stmt { #[must_use] pub fn is_internally_pure(&self) -> bool { match self { - Self::Var(expr, _, ..) => expr.is_pure(), + Self::Var(x, ..) => x.1.is_pure(), - Self::Expr(Expr::Stmt(s)) => s.iter().all(Stmt::is_internally_pure), + Self::Expr(e) => match e.as_ref() { + Expr::Stmt(s) => s.iter().all(Stmt::is_internally_pure), + _ => self.is_pure(), + }, #[cfg(not(feature = "no_module"))] - Self::Import(expr, ..) => expr.is_pure(), + Self::Import(x, ..) => x.0.is_pure(), #[cfg(not(feature = "no_module"))] Self::Export(..) => true, @@ -653,86 +691,86 @@ impl Stmt { } match self { - Self::Var(e, _, ..) => { - if !e.walk(path, on_node) { + Self::Var(x, ..) => { + if !x.1.walk(path, on_node) { return false; } } - Self::If(e, x, ..) => { - if !e.walk(path, on_node) { + Self::If(x, ..) => { + if !x.0.walk(path, on_node) { return false; } - for s in x.0.iter() { - if !s.walk(path, on_node) { - return false; - } - } for s in x.1.iter() { if !s.walk(path, on_node) { return false; } } - } - Self::Switch(e, x, ..) => { - if !e.walk(path, on_node) { - return false; - } - for b in x.cases.values() { - if !b - .condition - .as_ref() - .map(|e| e.walk(path, on_node)) - .unwrap_or(true) - { - return false; - } - for s in b.statements.iter() { - if !s.walk(path, on_node) { - return false; - } - } - } - for (.., b) in &x.ranges { - if !b - .condition - .as_ref() - .map(|e| e.walk(path, on_node)) - .unwrap_or(true) - { - return false; - } - for s in b.statements.iter() { - if !s.walk(path, on_node) { - return false; - } - } - } - for s in x.def_case.iter() { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::While(e, s, ..) | Self::Do(s, e, ..) => { - if !e.walk(path, on_node) { - return false; - } - for s in &s.0 { - if !s.walk(path, on_node) { - return false; - } - } - } - Self::For(e, x, ..) => { - if !e.walk(path, on_node) { - return false; - } for s in x.2.iter() { if !s.walk(path, on_node) { return false; } } } + Self::Switch(x, ..) => { + if !x.0.walk(path, on_node) { + return false; + } + for b in x.1.cases.values() { + if !b + .condition + .as_ref() + .map(|e| e.walk(path, on_node)) + .unwrap_or(true) + { + return false; + } + for s in b.statements.iter() { + if !s.walk(path, on_node) { + return false; + } + } + } + for (.., b) in &x.1.ranges { + if !b + .condition + .as_ref() + .map(|e| e.walk(path, on_node)) + .unwrap_or(true) + { + return false; + } + for s in b.statements.iter() { + if !s.walk(path, on_node) { + return false; + } + } + } + for s in x.1.def_case.iter() { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::While(x, ..) | Self::Do(x, ..) => { + if !x.0.walk(path, on_node) { + return false; + } + for s in x.1.statements() { + if !s.walk(path, on_node) { + return false; + } + } + } + Self::For(x, ..) => { + if !x.1.walk(path, on_node) { + return false; + } + for s in x.3.iter() { + if !s.walk(path, on_node) { + return false; + } + } + } Self::Assignment(x, ..) => { if !x.1.lhs.walk(path, on_node) { return false; @@ -749,7 +787,7 @@ impl Stmt { } } Self::Block(x, ..) => { - for s in x.iter() { + for s in x.statements() { if !s.walk(path, on_node) { return false; } @@ -767,14 +805,19 @@ impl Stmt { } } } - Self::Expr(e) | Self::Return(_, Some(e), _) => { + Self::Expr(e) => { + if !e.walk(path, on_node) { + return false; + } + } + Self::Return(Some(e), ..) => { if !e.walk(path, on_node) { return false; } } #[cfg(not(feature = "no_module"))] - Self::Import(e, ..) => { - if !e.walk(path, on_node) { + Self::Import(x, ..) => { + if !x.0.walk(path, on_node) { return false; } } diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index d6f10ca0..5fc04e47 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -361,17 +361,23 @@ impl Debugger { node.position() == *pos && _src == source } BreakPoint::AtFunctionName { name, .. } => match node { - ASTNode::Expr(Expr::FnCall(x, ..)) - | ASTNode::Stmt(Stmt::FnCall(x, ..)) - | ASTNode::Stmt(Stmt::Expr(Expr::FnCall(x, ..))) => x.name == *name, + ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => { + x.name == *name + } + ASTNode::Stmt(Stmt::Expr(e)) => match e.as_ref() { + Expr::FnCall(x, ..) => x.name == *name, + _ => false, + }, _ => false, }, BreakPoint::AtFunctionCall { name, args, .. } => match node { - ASTNode::Expr(Expr::FnCall(x, ..)) - | ASTNode::Stmt(Stmt::FnCall(x, ..)) - | ASTNode::Stmt(Stmt::Expr(Expr::FnCall(x, ..))) => { + ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => { x.args.len() == *args && x.name == *name } + ASTNode::Stmt(Stmt::Expr(e)) => match e.as_ref() { + Expr::FnCall(x, ..) => x.args.len() == *args && x.name == *name, + _ => false, + }, _ => false, }, #[cfg(not(feature = "no_object"))] @@ -548,9 +554,12 @@ impl Engine { DebuggerCommand::FunctionExit => { // Bump a level if it is a function call let level = match node { - ASTNode::Expr(Expr::FnCall(..)) - | ASTNode::Stmt(Stmt::FnCall(..)) - | ASTNode::Stmt(Stmt::Expr(Expr::FnCall(..))) => context.call_level() + 1, + ASTNode::Expr(Expr::FnCall(..)) | ASTNode::Stmt(Stmt::FnCall(..)) => { + context.call_level() + 1 + } + ASTNode::Stmt(Stmt::Expr(e)) if matches!(e.as_ref(), Expr::FnCall(..)) => { + context.call_level() + 1 + } _ => context.call_level(), }; global.debugger.status = DebuggerStatus::FunctionExit(level); diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 294705ac..8d7d7358 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -338,7 +338,9 @@ impl Engine { } // If statement - Stmt::If(expr, x, ..) => { + Stmt::If(x, ..) => { + let (expr, if_block, else_block) = x.as_ref(); + let guard_val = self .eval_expr(scope, global, state, lib, this_ptr, expr, level) .and_then(|v| { @@ -349,18 +351,18 @@ impl Engine { match guard_val { Ok(true) => { - if !x.0.is_empty() { + if !if_block.is_empty() { self.eval_stmt_block( - scope, global, state, lib, this_ptr, &x.0, true, level, + scope, global, state, lib, this_ptr, if_block, true, level, ) } else { Ok(Dynamic::UNIT) } } Ok(false) => { - if !x.1.is_empty() { + if !else_block.is_empty() { self.eval_stmt_block( - scope, global, state, lib, this_ptr, &x.1, true, level, + scope, global, state, lib, this_ptr, else_block, true, level, ) } else { Ok(Dynamic::UNIT) @@ -371,15 +373,17 @@ impl Engine { } // Switch statement - Stmt::Switch(match_expr, x, ..) => { - let SwitchCases { - cases, - def_case, - ranges, - } = x.as_ref(); + Stmt::Switch(x, ..) => { + let ( + expr, + SwitchCases { + cases, + def_case, + ranges, + }, + ) = x.as_ref(); - let value_result = - self.eval_expr(scope, global, state, lib, this_ptr, match_expr, level); + let value_result = self.eval_expr(scope, global, state, lib, this_ptr, expr, level); if let Ok(value) = value_result { let stmt_block_result = if value.is_hashable() { @@ -484,7 +488,9 @@ impl Engine { } // Loop - Stmt::While(Expr::Unit(..), body, ..) => loop { + Stmt::While(x, ..) if matches!(x.0, Expr::Unit(..)) => loop { + let (.., body) = x.as_ref(); + if !body.is_empty() { match self .eval_stmt_block(scope, global, state, lib, this_ptr, body, true, level) @@ -503,7 +509,9 @@ impl Engine { }, // While loop - Stmt::While(expr, body, ..) => loop { + Stmt::While(x, ..) => loop { + let (expr, body) = x.as_ref(); + let condition = self .eval_expr(scope, global, state, lib, this_ptr, expr, level) .and_then(|v| { @@ -532,7 +540,8 @@ impl Engine { }, // Do loop - Stmt::Do(body, expr, options, ..) => loop { + Stmt::Do(x, options, ..) => loop { + let (expr, body) = x.as_ref(); let is_while = !options.contains(AST_OPTION_NEGATED); if !body.is_empty() { @@ -549,7 +558,7 @@ impl Engine { } let condition = self - .eval_expr(scope, global, state, lib, this_ptr, expr, level) + .eval_expr(scope, global, state, lib, this_ptr, &expr, level) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::(typ, expr.position()) @@ -564,8 +573,8 @@ impl Engine { }, // For loop - Stmt::For(expr, x, ..) => { - let (Ident { name: var_name, .. }, counter, statements) = x.as_ref(); + Stmt::For(x, ..) => { + let (Ident { name: var_name, .. }, expr, counter, statements) = x.as_ref(); let iter_result = self .eval_expr(scope, global, state, lib, this_ptr, expr, level) @@ -786,30 +795,31 @@ impl Engine { } // Throw value - Stmt::Return(options, Some(expr), pos) if options.contains(AST_OPTION_BREAK) => self + Stmt::Return(Some(expr), options, pos) if options.contains(AST_OPTION_BREAK) => self .eval_expr(scope, global, state, lib, this_ptr, expr, level) .and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())), // Empty throw - Stmt::Return(options, None, pos) if options.contains(AST_OPTION_BREAK) => { + Stmt::Return(None, options, pos) if options.contains(AST_OPTION_BREAK) => { Err(ERR::ErrorRuntime(Dynamic::UNIT, *pos).into()) } // Return value - Stmt::Return(.., Some(expr), pos) => self + Stmt::Return(Some(expr), .., pos) => self .eval_expr(scope, global, state, lib, this_ptr, expr, level) .and_then(|v| Err(ERR::Return(v.flatten(), *pos).into())), // Empty return - Stmt::Return(.., None, pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()), + Stmt::Return(None, .., pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()), // Let/const statement - shadowing disallowed - Stmt::Var(.., x, _, pos) if !self.allow_shadowing() && scope.contains(&x.name) => { - Err(ERR::ErrorVariableExists(x.name.to_string(), *pos).into()) + Stmt::Var(x, .., pos) if !self.allow_shadowing() && scope.contains(&x.0.name) => { + Err(ERR::ErrorVariableExists(x.0.name.to_string(), *pos).into()) } // Let/const statement - Stmt::Var(expr, x, options, pos) => { - let var_name = &x.name; + Stmt::Var(x, options, pos) => { + let var_name = &x.0.name; + let expr = &x.1; let entry_type = if options.contains(AST_OPTION_CONSTANT) { AccessMode::ReadOnly @@ -902,7 +912,9 @@ impl Engine { // Import statement #[cfg(not(feature = "no_module"))] - Stmt::Import(expr, export, _pos) => { + Stmt::Import(x, _pos) => { + let (expr, export) = x.as_ref(); + // Guard against too many modules #[cfg(not(feature = "unchecked"))] if global.num_modules_loaded >= self.max_modules() { diff --git a/src/optimizer.rs b/src/optimizer.rs index e60a9922..daad6b3d 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -1,7 +1,9 @@ //! Module implementing the [`AST`] optimizer. #![cfg(not(feature = "no_optimize"))] -use crate::ast::{Expr, OpAssignment, Stmt, StmtBlockContainer, AST_OPTION_FLAGS::*}; +use crate::ast::{ + Expr, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, AST_OPTION_FLAGS::*, +}; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::eval::{EvalState, GlobalRuntimeState}; use crate::func::builtin::get_builtin_binary_op_fn; @@ -252,22 +254,22 @@ fn optimize_stmt_block( // Optimize each statement in the block for stmt in statements.iter_mut() { match stmt { - Stmt::Var(value_expr, x, options, ..) => { + Stmt::Var(x, options, ..) => { if options.contains(AST_OPTION_CONSTANT) { // Add constant literals into the state - optimize_expr(value_expr, state, false); + optimize_expr(&mut x.1, state, false); - if value_expr.is_constant() { + if x.1.is_constant() { state.push_var( - x.name.as_str(), + x.0.name.as_str(), AccessMode::ReadOnly, - value_expr.get_literal_value(), + x.1.get_literal_value(), ); } } else { // Add variables into the state - optimize_expr(value_expr, state, false); - state.push_var(x.name.as_str(), AccessMode::ReadWrite, None); + optimize_expr(&mut x.1, state, false); + state.push_var(x.0.name.as_str(), AccessMode::ReadWrite, None); } } // Optimize the statement @@ -284,10 +286,11 @@ fn optimize_stmt_block( .find_map(|(i, stmt)| match stmt { stmt if !is_pure(stmt) => Some(i), - Stmt::Var(e, _, ..) | Stmt::Expr(e) if !e.is_constant() => Some(i), + Stmt::Var(x, ..) if x.1.is_constant() => Some(i), + Stmt::Expr(e) if !e.is_constant() => Some(i), #[cfg(not(feature = "no_module"))] - Stmt::Import(e, ..) if !e.is_constant() => Some(i), + Stmt::Import(x, ..) if !x.0.is_constant() => Some(i), _ => None, }) @@ -320,7 +323,7 @@ fn optimize_stmt_block( loop { match statements[..] { // { return; } -> {} - [Stmt::Return(options, None, ..)] + [Stmt::Return(None, options, ..)] if reduce_return && !options.contains(AST_OPTION_BREAK) => { state.set_dirty(); @@ -331,7 +334,7 @@ fn optimize_stmt_block( statements.clear(); } // { ...; return; } -> { ... } - [.., ref last_stmt, Stmt::Return(options, None, ..)] + [.., ref last_stmt, Stmt::Return(None, options, ..)] if reduce_return && !options.contains(AST_OPTION_BREAK) && !last_stmt.returns_value() => @@ -340,7 +343,7 @@ fn optimize_stmt_block( statements.pop().unwrap(); } // { ...; return val; } -> { ...; val } - [.., Stmt::Return(options, ref mut expr, pos)] + [.., Stmt::Return(ref mut expr, options, pos)] if reduce_return && !options.contains(AST_OPTION_BREAK) => { state.set_dirty(); @@ -377,14 +380,14 @@ fn optimize_stmt_block( statements.clear(); } // { ...; return; } -> { ... } - [.., Stmt::Return(options, None, ..)] + [.., Stmt::Return(None, options, ..)] if reduce_return && !options.contains(AST_OPTION_BREAK) => { state.set_dirty(); statements.pop().unwrap(); } // { ...; return pure_val; } -> { ... } - [.., Stmt::Return(options, Some(ref expr), ..)] + [.., Stmt::Return(Some(ref expr), options, ..)] if reduce_return && !options.contains(AST_OPTION_BREAK) && expr.is_pure() => @@ -460,7 +463,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } // if expr {} - Stmt::If(condition, x, ..) if x.0.is_empty() && x.1.is_empty() => { + Stmt::If(x, ..) if x.1.is_empty() && x.2.is_empty() => { + let condition = &mut x.0; state.set_dirty(); let pos = condition.start_position(); @@ -469,56 +473,72 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b *stmt = if preserve_result { // -> { expr, Noop } - Stmt::Block( - [Stmt::Expr(expr), Stmt::Noop(pos)].into(), - Span::new(pos, Position::NONE), + ( + [Stmt::Expr(expr.into()), Stmt::Noop(pos)], + pos, + Position::NONE, ) + .into() } else { // -> expr - Stmt::Expr(expr) + Stmt::Expr(expr.into()) }; } // if false { if_block } -> Noop - Stmt::If(Expr::BoolConstant(false, pos), x, ..) if x.1.is_empty() => { - state.set_dirty(); - *stmt = Stmt::Noop(*pos); + Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) && x.2.is_empty() => { + if let Expr::BoolConstant(false, pos) = x.0 { + state.set_dirty(); + *stmt = Stmt::Noop(pos); + } else { + unreachable!("`Expr::BoolConstant`"); + } } // if false { if_block } else { else_block } -> else_block - Stmt::If(Expr::BoolConstant(false, ..), x, ..) => { + Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => { + state.set_dirty(); + *stmt = + match optimize_stmt_block(mem::take(&mut *x.2), state, preserve_result, true, false) + { + statements if statements.is_empty() => Stmt::Noop(x.2.position()), + statements => (statements, x.2.span()).into(), + } + } + // if true { if_block } else { else_block } -> if_block + Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(true, ..)) => { state.set_dirty(); *stmt = match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false) { statements if statements.is_empty() => Stmt::Noop(x.1.position()), - statements => Stmt::Block(statements.into_boxed_slice(), x.1.span()), - } - } - // if true { if_block } else { else_block } -> if_block - Stmt::If(Expr::BoolConstant(true, ..), x, ..) => { - state.set_dirty(); - *stmt = - match optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false) - { - statements if statements.is_empty() => Stmt::Noop(x.0.position()), - statements => Stmt::Block(statements.into_boxed_slice(), x.0.span()), + statements => (statements, x.1.span()).into(), } } // if expr { if_block } else { else_block } - Stmt::If(condition, x, ..) => { + Stmt::If(x, ..) => { + let (condition, body, other) = x.as_mut(); optimize_expr(condition, state, false); - *x.0 = optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false); - *x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false); + **body = + optimize_stmt_block(mem::take(&mut **body), state, preserve_result, true, false); + **other = + optimize_stmt_block(mem::take(&mut **other), state, preserve_result, true, false); } // switch const { ... } - Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => { + Stmt::Switch(x, pos) if x.0.is_constant() => { + let ( + match_expr, + SwitchCases { + cases, + ranges, + def_case, + }, + ) = x.as_mut(); + let value = match_expr.get_literal_value().unwrap(); let hasher = &mut get_hasher(); value.hash(hasher); let hash = hasher.finish(); - let cases = &mut x.cases; - // First check hashes if let Some(block) = cases.get_mut(&hash) { if let Some(mut condition) = mem::take(&mut block.condition) { @@ -526,18 +546,18 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b optimize_expr(&mut condition, state, false); let def_stmt = - optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false); + optimize_stmt_block(mem::take(def_case), state, true, true, false); *stmt = Stmt::If( - condition, - Box::new(( + ( + condition, mem::take(&mut block.statements), - Stmt::Block( - def_stmt.into_boxed_slice(), - x.def_case.span_or_else(*pos, Position::NONE), - ) + StmtBlock::new_with_span( + def_stmt, + def_case.span_or_else(*pos, Position::NONE), + ), + ) .into(), - )), match_expr.start_position(), ); } else { @@ -549,7 +569,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b true, false, ); - *stmt = Stmt::Block(statements.into_boxed_slice(), block.statements.span()); + *stmt = (statements, block.statements.span()).into(); } state.set_dirty(); @@ -557,8 +577,6 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } // Then check ranges - let ranges = &mut x.ranges; - if value.is::() && !ranges.is_empty() { let value = value.as_int().expect("`INT`"); @@ -576,23 +594,18 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def } optimize_expr(&mut condition, state, false); - let def_stmt = optimize_stmt_block( - mem::take(&mut x.def_case), - state, - true, - true, - false, - ); + let def_stmt = + optimize_stmt_block(mem::take(def_case), state, true, true, false); *stmt = Stmt::If( - condition, - Box::new(( + ( + condition, mem::take(&mut block.statements), - Stmt::Block( - def_stmt.into_boxed_slice(), - x.def_case.span_or_else(*pos, Position::NONE), - ) + StmtBlock::new_with_span( + def_stmt, + def_case.span_or_else(*pos, Position::NONE), + ), + ) .into(), - )), match_expr.start_position(), ); } else { @@ -600,8 +613,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b let statements = mem::take(&mut *block.statements); let statements = optimize_stmt_block(statements, state, true, true, false); - *stmt = - Stmt::Block(statements.into_boxed_slice(), block.statements.span()); + *stmt = (statements, block.statements.span()).into(); } state.set_dirty(); @@ -644,17 +656,25 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // Promote the default case state.set_dirty(); - let def_stmt = - optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false); - *stmt = Stmt::Block( - def_stmt.into_boxed_slice(), - x.def_case.span_or_else(*pos, Position::NONE), - ); + let def_stmt = optimize_stmt_block(mem::take(def_case), state, true, true, false); + *stmt = (def_stmt, def_case.span_or_else(*pos, Position::NONE)).into(); } // switch - Stmt::Switch(match_expr, x, ..) => { + Stmt::Switch(x, ..) => { + let ( + match_expr, + SwitchCases { + cases, + ranges, + def_case, + .. + }, + ) = x.as_mut(); + optimize_expr(match_expr, state, false); - for block in x.cases.values_mut() { + + // Optimize cases + for block in cases.values_mut() { let statements = mem::take(&mut *block.statements); *block.statements = optimize_stmt_block(statements, state, preserve_result, true, false); @@ -669,30 +689,58 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } // Remove false cases - while let Some((&key, ..)) = x.cases.iter().find(|(.., block)| match block.condition { - Some(Expr::BoolConstant(false, ..)) => true, - _ => false, - }) { - state.set_dirty(); - x.cases.remove(&key); + cases.retain(|_, block| match block.condition { + Some(Expr::BoolConstant(false, ..)) => { + state.set_dirty(); + false + } + _ => true, + }); + + // Optimize ranges + for (.., block) in ranges.iter_mut() { + let statements = mem::take(&mut *block.statements); + *block.statements = + optimize_stmt_block(statements, state, preserve_result, true, false); + + if let Some(mut condition) = mem::take(&mut block.condition) { + optimize_expr(&mut condition, state, false); + match condition { + Expr::Unit(..) | Expr::BoolConstant(true, ..) => state.set_dirty(), + _ => block.condition = Some(condition), + } + } } - let def_block = mem::take(&mut *x.def_case); - *x.def_case = optimize_stmt_block(def_block, state, preserve_result, true, false); + // Remove false ranges + ranges.retain(|(.., block)| match block.condition { + Some(Expr::BoolConstant(false, ..)) => { + state.set_dirty(); + false + } + _ => true, + }); + + let def_block = mem::take(&mut ***def_case); + ***def_case = optimize_stmt_block(def_block, state, preserve_result, true, false); } // while false { block } -> Noop - Stmt::While(Expr::BoolConstant(false, pos), ..) => { - state.set_dirty(); - *stmt = Stmt::Noop(*pos) - } + Stmt::While(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => match x.0 { + Expr::BoolConstant(false, pos) => { + state.set_dirty(); + *stmt = Stmt::Noop(pos) + } + _ => unreachable!("`Expr::BoolConstant"), + }, // while expr { block } - Stmt::While(condition, body, ..) => { + Stmt::While(x, ..) => { + let (condition, body) = x.as_mut(); optimize_expr(condition, state, false); if let Expr::BoolConstant(true, pos) = condition { *condition = Expr::Unit(*pos); } - ***body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false); + **body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false); if body.len() == 1 { match body[0] { @@ -701,14 +749,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // 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))]; + let mut statements = vec![Stmt::Expr(mem::take(condition).into())]; if preserve_result { statements.push(Stmt::Noop(pos)) } - *stmt = Stmt::Block( - statements.into_boxed_slice(), - Span::new(pos, Position::NONE), - ); + *stmt = (statements, Span::new(pos, Position::NONE)).into(); } else { *stmt = Stmt::Noop(pos); }; @@ -717,37 +762,51 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } } } - // do { block } while false | do { block } until true -> { block } - Stmt::Do(body, Expr::BoolConstant(x, ..), options, ..) - if *x == options.contains(AST_OPTION_NEGATED) => + // do { block } until true -> { block } + Stmt::Do(x, options, ..) + if matches!(x.0, Expr::BoolConstant(true, ..)) + && options.contains(AST_OPTION_NEGATED) => { state.set_dirty(); - *stmt = Stmt::Block( - optimize_stmt_block(mem::take(&mut **body), state, false, true, false) - .into_boxed_slice(), - body.span(), - ); + *stmt = ( + optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false), + x.1.span(), + ) + .into(); + } + // do { block } while false -> { block } + Stmt::Do(x, options, ..) + if matches!(x.0, Expr::BoolConstant(false, ..)) + && !options.contains(AST_OPTION_NEGATED) => + { + state.set_dirty(); + *stmt = ( + optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false), + x.1.span(), + ) + .into(); } // do { block } while|until expr - Stmt::Do(body, condition, ..) => { - optimize_expr(condition, state, false); - ***body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false); + Stmt::Do(x, ..) => { + optimize_expr(&mut x.0, state, false); + *x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false); } // for id in expr { block } - Stmt::For(iterable, x, ..) => { - optimize_expr(iterable, state, false); - *x.2 = optimize_stmt_block(mem::take(&mut *x.2), state, false, true, false); + Stmt::For(x, ..) => { + optimize_expr(&mut x.1, state, false); + *x.3 = optimize_stmt_block(mem::take(&mut *x.3), state, false, true, false); } // let id = expr; - Stmt::Var(expr, _, options, ..) if !options.contains(AST_OPTION_CONSTANT) => { - optimize_expr(expr, state, false) + Stmt::Var(x, options, ..) if !options.contains(AST_OPTION_CONSTANT) => { + optimize_expr(&mut x.1, state, false) } // import expr as var; #[cfg(not(feature = "no_module"))] - Stmt::Import(expr, ..) => optimize_expr(expr, state, false), + Stmt::Import(x, ..) => optimize_expr(&mut x.0, state, false), // { block } - Stmt::Block(statements, span) => { - let statements = mem::take(statements).into_vec().into(); + Stmt::Block(block) => { + let span = block.span(); + let statements = block.take_statements().into_vec().into(); let mut block = optimize_stmt_block(statements, state, preserve_result, true, false); match block.as_mut_slice() { @@ -760,18 +819,18 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b state.set_dirty(); *stmt = mem::take(s); } - _ => *stmt = Stmt::Block(block.into_boxed_slice(), *span), + _ => *stmt = (block, span).into(), } } // try { pure try_block } catch ( var ) { catch_block } -> try_block Stmt::TryCatch(x, ..) if x.try_block.iter().all(Stmt::is_pure) => { // If try block is pure, there will never be any exceptions state.set_dirty(); - *stmt = Stmt::Block( - optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false) - .into_boxed_slice(), + *stmt = ( + optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false), x.try_block.span(), - ); + ) + .into(); } // try { try_block } catch ( var ) { catch_block } Stmt::TryCatch(x, ..) => { @@ -780,31 +839,33 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b *x.catch_block = optimize_stmt_block(mem::take(&mut *x.catch_block), state, false, true, false); } - // func(...) - Stmt::Expr(expr @ Expr::FnCall(..)) => { + + Stmt::Expr(expr) => { optimize_expr(expr, state, false); - match expr { + + match expr.as_mut() { + // func(...) Expr::FnCall(x, pos) => { state.set_dirty(); *stmt = Stmt::FnCall(mem::take(x), *pos); } + // {...}; + Expr::Stmt(x) => { + if x.is_empty() { + state.set_dirty(); + *stmt = Stmt::Noop(x.position()); + } else { + state.set_dirty(); + *stmt = mem::take(&mut **x).into(); + } + } + // expr; _ => (), } } - // {} - Stmt::Expr(Expr::Stmt(x)) if x.is_empty() => { - state.set_dirty(); - *stmt = Stmt::Noop(x.position()); - } - // {...}; - Stmt::Expr(Expr::Stmt(x)) => { - state.set_dirty(); - *stmt = mem::take(&mut **x).into(); - } - // expr; - Stmt::Expr(expr) => optimize_expr(expr, state, false), + // return expr; - Stmt::Return(_, Some(ref mut expr), _) => optimize_expr(expr, state, false), + Stmt::Return(Some(ref mut expr), ..) => optimize_expr(expr, state, false), // All other statements - skip _ => (), diff --git a/src/parser.rs b/src/parser.rs index e43c0fd1..3d1acfce 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5,14 +5,14 @@ use crate::api::events::VarDefInfo; use crate::api::options::LanguageOptions; use crate::ast::{ BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, - OpAssignment, ScriptFnDef, Stmt, StmtBlockContainer, SwitchCases, TryCatchBlock, + OpAssignment, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*, }; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; use crate::eval::{EvalState, GlobalRuntimeState}; use crate::func::hashing::get_hasher; use crate::tokenizer::{ - is_keyword_function, is_valid_function_name, is_valid_identifier, Span, Token, TokenStream, + is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream, TokenizerControl, }; use crate::types::dynamic::AccessMode; @@ -1007,7 +1007,7 @@ fn parse_switch( } let mut cases = BTreeMap::>::new(); - let mut ranges = StaticVec::<(INT, INT, bool, ConditionalStmtBlock)>::new(); + let mut ranges = StaticVec::<(INT, INT, bool, Box)>::new(); let mut def_pos = Position::NONE; let mut def_stmt = None; @@ -1119,7 +1119,10 @@ fn parse_switch( }); } // Other range - _ => ranges.push((range.0, range.1, range.2, (condition, stmt).into())), + _ => { + let block: ConditionalStmtBlock = (condition, stmt).into(); + ranges.push((range.0, range.1, range.2, block.into())) + } } } None @@ -1129,7 +1132,7 @@ fn parse_switch( cases.insert(hash, block.into()); None } - (None, None) => Some(stmt.into()), + (None, None) => Some(Box::new(stmt.into())), _ => unreachable!("both hash and range in switch statement case"), }; @@ -1156,18 +1159,13 @@ fn parse_switch( } } - let def_case = def_stmt.unwrap_or_else(|| Stmt::Noop(Position::NONE).into()); + let cases = SwitchCases { + cases, + def_case: def_stmt.unwrap_or_else(|| StmtBlock::NONE.into()), + ranges, + }; - Ok(Stmt::Switch( - item, - SwitchCases { - cases, - def_case, - ranges, - } - .into(), - settings.pos, - )) + Ok(Stmt::Switch((item, cases).into(), settings.pos)) } /// Parse a primary expression. @@ -1877,7 +1875,7 @@ fn parse_op_assignment_stmt( .map(|(op, pos)| (Some(op), pos)) .expect(NEVER_ENDS), // Not op-assignment - _ => return Ok(Stmt::Expr(lhs)), + _ => return Ok(Stmt::Expr(lhs.into())), }; let mut settings = settings; @@ -2419,8 +2417,7 @@ fn parse_if( }; Ok(Stmt::If( - guard, - (if_body.into(), else_body.into()).into(), + (guard, if_body.into(), else_body.into()).into(), settings.pos, )) } @@ -2453,7 +2450,7 @@ fn parse_while_loop( let body = parse_block(input, state, lib, settings.level_up())?; - Ok(Stmt::While(guard, Box::new(body.into()), settings.pos)) + Ok(Stmt::While((guard, body.into()).into(), settings.pos)) } /// Parse a do loop. @@ -2491,12 +2488,7 @@ fn parse_do( let guard = parse_expr(input, state, lib, settings.level_up())?.ensure_bool_expr()?; ensure_not_assignment(input)?; - Ok(Stmt::Do( - Box::new(body.into()), - guard, - negated, - settings.pos, - )) + Ok(Stmt::Do((guard, body.into()).into(), negated, settings.pos)) } /// Parse a for loop. @@ -2584,8 +2576,7 @@ fn parse_for( state.stack.rewind(prev_stack_len); Ok(Stmt::For( - expr, - Box::new((loop_var, counter_var, body.into())), + Box::new((loop_var, expr, counter_var, body.into())), settings.pos, )) } @@ -2669,14 +2660,13 @@ fn parse_let( // let name = expr AccessMode::ReadWrite => { state.stack.push(name, ()); - Ok(Stmt::Var(expr, var_def.into(), export, settings.pos)) + Ok(Stmt::Var((var_def, expr).into(), export, settings.pos)) } // const name = { expr:constant } AccessMode::ReadOnly => { state.stack.push_constant(name, ()); Ok(Stmt::Var( - expr, - var_def.into(), + (var_def, expr).into(), AST_OPTION_CONSTANT + export, settings.pos, )) @@ -2704,7 +2694,7 @@ fn parse_import( // import expr as ... if !match_token(input, Token::As).0 { - return Ok(Stmt::Import(expr, None, settings.pos)); + return Ok(Stmt::Import((expr, None).into(), settings.pos)); } // import expr as name ... @@ -2713,8 +2703,7 @@ fn parse_import( state.imports.push(name.clone()); Ok(Stmt::Import( - expr, - Some(Ident { name, pos }.into()), + (expr, Some(Ident { name, pos })).into(), settings.pos, )) } @@ -2865,10 +2854,7 @@ fn parse_block( #[cfg(not(feature = "no_module"))] state.imports.truncate(orig_imports_len); - Ok(Stmt::Block( - statements.into_boxed_slice(), - Span::new(settings.pos, end_pos), - )) + Ok((statements, settings.pos, end_pos).into()) } /// Parse an expression as a statement. @@ -3071,17 +3057,17 @@ fn parse_stmt( match input.peek().expect(NEVER_ENDS) { // `return`/`throw` at - (Token::EOF, ..) => Ok(Stmt::Return(return_type, None, token_pos)), + (Token::EOF, ..) => Ok(Stmt::Return(None, return_type, token_pos)), // `return`/`throw` at end of block (Token::RightBrace, ..) if !settings.is_global => { - Ok(Stmt::Return(return_type, None, token_pos)) + Ok(Stmt::Return(None, return_type, token_pos)) } // `return;` or `throw;` - (Token::SemiColon, ..) => Ok(Stmt::Return(return_type, None, token_pos)), + (Token::SemiColon, ..) => Ok(Stmt::Return(None, return_type, token_pos)), // `return` or `throw` with expression _ => { let expr = parse_expr(input, state, lib, settings.level_up())?; - Ok(Stmt::Return(return_type, Some(expr), token_pos)) + Ok(Stmt::Return(Some(expr.into()), return_type, token_pos)) } } } @@ -3327,9 +3313,9 @@ fn make_curry_from_externals( statements.extend( externals .into_iter() - .map(|crate::ast::Ident { name, pos }| Stmt::Share(name, pos)), + .map(|crate::ast::Ident { name, pos }| Stmt::Share(name.into(), pos)), ); - statements.push(Stmt::Expr(expr)); + statements.push(Stmt::Expr(expr.into())); Expr::Stmt(crate::ast::StmtBlock::new(statements, pos, Position::NONE).into()) } @@ -3482,8 +3468,8 @@ impl Engine { } } - let mut statements = smallvec::SmallVec::new_const(); - statements.push(Stmt::Expr(expr)); + let mut statements = StmtBlockContainer::new_const(); + statements.push(Stmt::Expr(expr.into())); #[cfg(not(feature = "no_optimize"))] return Ok(crate::optimizer::optimize_into_ast( @@ -3509,7 +3495,7 @@ impl Engine { input: &mut TokenStream, state: &mut ParseState, ) -> ParseResult<(StmtBlockContainer, StaticVec>)> { - let mut statements = smallvec::SmallVec::new_const(); + let mut statements = StmtBlockContainer::new_const(); let mut functions = BTreeMap::new(); while !input.peek().expect(NEVER_ENDS).0.is_eof() { diff --git a/src/tests.rs b/src/tests.rs index e96d8dd5..f9e099e2 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -21,8 +21,8 @@ fn check_struct_sizes() { ); assert_eq!(size_of::(), if PACKED { 12 } else { 16 }); assert_eq!(size_of::>(), if PACKED { 12 } else { 16 }); - assert_eq!(size_of::(), if PACKED { 24 } else { 32 }); - assert_eq!(size_of::>(), if PACKED { 24 } else { 32 }); + assert_eq!(size_of::(), if PACKED { 12 } else { 16 }); + assert_eq!(size_of::>(), if PACKED { 12 } else { 16 }); #[cfg(target_pointer_width = "64")] { diff --git a/tests/optimizer.rs b/tests/optimizer.rs index af9754f4..cac15cf2 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -84,7 +84,7 @@ fn test_optimizer_parse() -> Result<(), Box> { assert_eq!( format!("{:?}", ast), - r#"AST { body: [Var(false @ 1:18, "DECISION" @ 1:7, (Constant), 1:1), Expr(123 @ 1:51)] }"# + r#"AST { body: [Var(("DECISION" @ 1:7, false @ 1:18), (Constant), 1:1), Expr(123 @ 1:51)] }"# ); let ast = engine.compile("if 1 == 2 { 42 }")?;