Reduce size of Stmt.

This commit is contained in:
Stephen Chung 2022-02-16 17:51:14 +08:00
parent cf0660e36b
commit 0d2e3d82f3
9 changed files with 457 additions and 342 deletions

View File

@ -99,13 +99,16 @@ impl Engine {
) { ) {
ast.walk(&mut |path| match path.last().unwrap() { ast.walk(&mut |path| match path.last().unwrap() {
// Collect all `import` statements with a string constant path // Collect all `import` statements with a string constant path
ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, ..), ..)) ASTNode::Stmt(Stmt::Import(x, ..)) => match x.0 {
Expr::StringConstant(ref s, ..)
if !resolver.contains_path(s) && !imports.contains(s.as_str()) => if !resolver.contains_path(s) && !imports.contains(s.as_str()) =>
{ {
imports.insert(s.clone().into()); imports.insert(s.clone().into());
true true
} }
_ => true, _ => true,
},
_ => true,
}); });
} }

View File

@ -744,10 +744,11 @@ impl AST {
include_variables: bool, include_variables: bool,
) -> impl Iterator<Item = (&str, bool, Dynamic)> { ) -> impl Iterator<Item = (&str, bool, Dynamic)> {
self.statements().iter().filter_map(move |stmt| match stmt { 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 if options.contains(AST_OPTION_CONSTANT) && include_constants
|| !options.contains(AST_OPTION_CONSTANT) && include_variables => || !options.contains(AST_OPTION_CONSTANT) && include_variables =>
{ {
let (name, expr) = x.as_ref();
if let Some(value) = expr.get_literal_value() { if let Some(value) = expr.get_literal_value() {
Some((name.as_str(), options.contains(AST_OPTION_CONSTANT), value)) Some((name.as_str(), options.contains(AST_OPTION_CONSTANT), value))
} else { } else {

View File

@ -114,9 +114,9 @@ pub struct SwitchCases {
/// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s. /// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s.
pub cases: BTreeMap<u64, Box<ConditionalStmtBlock>>, pub cases: BTreeMap<u64, Box<ConditionalStmtBlock>>,
/// Statements block for the default case (there can be no condition for the default case). /// Statements block for the default case (there can be no condition for the default case).
pub def_case: StmtBlock, pub def_case: Box<StmtBlock>,
/// List of range cases. /// List of range cases.
pub ranges: StaticVec<(INT, INT, bool, ConditionalStmtBlock)>, pub ranges: StaticVec<(INT, INT, bool, Box<ConditionalStmtBlock>)>,
} }
/// _(internals)_ A `try-catch` block. /// _(internals)_ A `try-catch` block.
@ -131,8 +131,20 @@ pub struct TryCatchBlock {
pub catch_block: StmtBlock, 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]>; 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<Stmt>;
/// _(internals)_ A scoped block of statements. /// _(internals)_ A scoped block of statements.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Clone, Hash, Default)] #[derive(Clone, Hash, Default)]
@ -143,21 +155,27 @@ impl StmtBlock {
pub const NONE: Self = Self::empty(Position::NONE); pub const NONE: Self = Self::empty(Position::NONE);
/// Create a new [`StmtBlock`]. /// Create a new [`StmtBlock`].
#[inline(always)]
#[must_use] #[must_use]
pub fn new( pub fn new(
statements: impl IntoIterator<Item = Stmt>, statements: impl IntoIterator<Item = Stmt>,
start_pos: Position, start_pos: Position,
end_pos: Position, end_pos: Position,
) -> Self { ) -> 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<Item = Stmt>, span: Span) -> Self {
let mut statements: smallvec::SmallVec<_> = statements.into_iter().collect(); let mut statements: smallvec::SmallVec<_> = statements.into_iter().collect();
statements.shrink_to_fit(); statements.shrink_to_fit();
Self(statements, Span::new(start_pos, end_pos)) Self(statements, span)
} }
/// Create an empty [`StmtBlock`]. /// Create an empty [`StmtBlock`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn empty(pos: Position) -> Self { 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? /// Is this statements block empty?
#[inline(always)] #[inline(always)]
@ -269,8 +287,8 @@ impl From<Stmt> for StmtBlock {
#[inline] #[inline]
fn from(stmt: Stmt) -> Self { fn from(stmt: Stmt) -> Self {
match stmt { match stmt {
Stmt::Block(mut block, span) => Self(block.iter_mut().map(mem::take).collect(), span), Stmt::Block(block) => *block,
Stmt::Noop(pos) => Self(smallvec::SmallVec::new_const(), Span::new(pos, pos)), Stmt::Noop(pos) => Self(StmtBlockContainer::new_const(), Span::new(pos, pos)),
_ => { _ => {
let pos = stmt.position(); let pos = stmt.position();
Self(vec![stmt].into(), Span::new(pos, Position::NONE)) Self(vec![stmt].into(), Span::new(pos, Position::NONE))
@ -303,7 +321,7 @@ pub enum Stmt {
/// No-op. /// No-op.
Noop(Position), Noop(Position),
/// `if` expr `{` stmt `}` `else` `{` stmt `}` /// `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 `,` ... `}` /// `switch` expr `{` literal or range or _ `if` condition `=>` stmt `,` ... `}`
/// ///
/// ### Data Structure /// ### Data Structure
@ -311,27 +329,27 @@ pub enum Stmt {
/// 0) Hash table for (condition, block) /// 0) Hash table for (condition, block)
/// 1) Default block /// 1) Default block
/// 2) List of ranges: (start, end, inclusive, condition, statement) /// 2) List of ranges: (start, end, inclusive, condition, statement)
Switch(Expr, Box<SwitchCases>, Position), Switch(Box<(Expr, SwitchCases)>, Position),
/// `while` expr `{` stmt `}` | `loop` `{` stmt `}` /// `while` expr `{` stmt `}` | `loop` `{` stmt `}`
/// ///
/// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement. /// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement.
While(Expr, Box<StmtBlock>, Position), While(Box<(Expr, StmtBlock)>, Position),
/// `do` `{` stmt `}` `while`|`until` expr /// `do` `{` stmt `}` `while`|`until` expr
/// ///
/// ### Option Flags /// ### Option Flags
/// ///
/// * [`AST_OPTION_NONE`] = `while` /// * [`AST_OPTION_NONE`] = `while`
/// * [`AST_OPTION_NEGATED`] = `until` /// * [`AST_OPTION_NEGATED`] = `until`
Do(Box<StmtBlock>, Expr, OptionFlags, Position), Do(Box<(Expr, StmtBlock)>, OptionFlags, Position),
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
For(Expr, Box<(Ident, Option<Ident>, StmtBlock)>, Position), For(Box<(Ident, Expr, Option<Ident>, StmtBlock)>, Position),
/// \[`export`\] `let`|`const` id `=` expr /// \[`export`\] `let`|`const` id `=` expr
/// ///
/// ### Option Flags /// ### Option Flags
/// ///
/// * [`AST_OPTION_EXPORTED`] = `export` /// * [`AST_OPTION_EXPORTED`] = `export`
/// * [`AST_OPTION_CONSTANT`] = `const` /// * [`AST_OPTION_CONSTANT`] = `const`
Var(Expr, Box<Ident>, OptionFlags, Position), Var(Box<(Ident, Expr)>, OptionFlags, Position),
/// expr op`=` expr /// expr op`=` expr
Assignment(Box<(Option<OpAssignment<'static>>, BinaryExpr)>, Position), Assignment(Box<(Option<OpAssignment<'static>>, BinaryExpr)>, Position),
/// func `(` expr `,` ... `)` /// func `(` expr `,` ... `)`
@ -340,11 +358,11 @@ pub enum Stmt {
/// function call forming one statement. /// function call forming one statement.
FnCall(Box<FnCallExpr>, Position), FnCall(Box<FnCallExpr>, Position),
/// `{` stmt`;` ... `}` /// `{` stmt`;` ... `}`
Block(Box<[Stmt]>, Span), Block(Box<StmtBlock>),
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
TryCatch(Box<TryCatchBlock>, Position), TryCatch(Box<TryCatchBlock>, Position),
/// [expression][Expr] /// [expression][Expr]
Expr(Expr), Expr(Box<Expr>),
/// `continue`/`break` /// `continue`/`break`
/// ///
/// ### Option Flags /// ### Option Flags
@ -358,12 +376,12 @@ pub enum Stmt {
/// ///
/// * [`AST_OPTION_NONE`] = `return` /// * [`AST_OPTION_NONE`] = `return`
/// * [`AST_OPTION_BREAK`] = `throw` /// * [`AST_OPTION_BREAK`] = `throw`
Return(OptionFlags, Option<Expr>, Position), Return(Option<Box<Expr>>, OptionFlags, Position),
/// `import` expr `as` var /// `import` expr `as` var
/// ///
/// Not available under `no_module`. /// Not available under `no_module`.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Import(Expr, Option<Box<Ident>>, Position), Import(Box<(Expr, Option<Ident>)>, Position),
/// `export` var `as` var /// `export` var `as` var
/// ///
/// Not available under `no_module`. /// 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 /// 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. /// convert a normal variable into a shared variable when the variable is _captured_ by a closure.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Share(crate::Identifier, Position), Share(Box<crate::Identifier>, Position),
} }
impl Default for Stmt { impl Default for Stmt {
@ -391,7 +409,21 @@ impl Default for Stmt {
impl From<StmtBlock> for Stmt { impl From<StmtBlock> for Stmt {
#[inline(always)] #[inline(always)]
fn from(block: StmtBlock) -> Self { fn from(block: StmtBlock) -> Self {
Self::Block(block.0.into_boxed_slice(), block.1) Self::Block(block.into())
}
}
impl<T: IntoIterator<Item = Stmt>> 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<T: IntoIterator<Item = Stmt>> 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::Var(.., pos)
| Self::TryCatch(.., pos) => *pos, | Self::TryCatch(.., pos) => *pos,
Self::Block(.., span) => span.start(), Self::Block(x) => x.position(),
Self::Expr(x) => x.start_position(), Self::Expr(x) => x.start_position(),
@ -448,7 +480,7 @@ impl Stmt {
| Self::Var(.., pos) | Self::Var(.., pos)
| Self::TryCatch(.., pos) => *pos = new_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) => { Self::Expr(x) => {
x.set_position(new_pos); 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! // A No-op requires a semicolon in order to know it is an empty statement!
Self::Noop(..) => false, 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::Var(..)
| Self::Assignment(..) | Self::Assignment(..)
| Self::Expr(..)
| Self::FnCall(..) | Self::FnCall(..)
| Self::Do(..) | Self::Do(..)
| Self::BreakLoop(..) | Self::BreakLoop(..)
@ -529,38 +563,37 @@ impl Stmt {
match self { match self {
Self::Noop(..) => true, Self::Noop(..) => true,
Self::Expr(expr) => expr.is_pure(), Self::Expr(expr) => expr.is_pure(),
Self::If(condition, x, ..) => { Self::If(x, ..) => {
condition.is_pure() x.0.is_pure() && x.1.iter().all(Stmt::is_pure) && x.2.iter().all(Stmt::is_pure)
&& x.0.iter().all(Stmt::is_pure)
&& x.1.iter().all(Stmt::is_pure)
} }
Self::Switch(expr, x, ..) => { Self::Switch(x, ..) => {
expr.is_pure() x.0.is_pure()
&& x.cases.values().all(|block| { && x.1.cases.values().all(|block| {
block.condition.as_ref().map(Expr::is_pure).unwrap_or(true) block.condition.as_ref().map(Expr::is_pure).unwrap_or(true)
&& block.statements.iter().all(Stmt::is_pure) && 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.condition.as_ref().map(Expr::is_pure).unwrap_or(true)
&& block.statements.iter().all(Stmt::is_pure) && 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. // Loops that exit can be pure because it can never be infinite.
Self::While(Expr::BoolConstant(false, ..), ..) => true, Self::While(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => true,
Self::Do(body, Expr::BoolConstant(x, ..), options, ..) Self::Do(x, options, ..) if matches!(x.0, Expr::BoolConstant(..)) => match x.0 {
if *x == options.contains(AST_OPTION_NEGATED) => Expr::BoolConstant(cond, ..) if cond == options.contains(AST_OPTION_NEGATED) => {
{ x.1.iter().all(Stmt::is_pure)
body.iter().all(Stmt::is_pure)
} }
_ => false,
},
// Loops are never pure since they can be infinite - and that's a side effect. // Loops are never pure since they can be infinite - and that's a side effect.
Self::While(..) | Self::Do(..) => false, Self::While(..) | Self::Do(..) => false,
// For loops can be pure because if the iterable is pure, it is finite, // For loops can be pure because if the iterable is pure, it is finite,
// so infinite loops can never occur. // 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::Var(..) | Self::Assignment(..) | Self::FnCall(..) => false,
Self::Block(block, ..) => block.iter().all(|stmt| stmt.is_pure()), Self::Block(block, ..) => block.iter().all(|stmt| stmt.is_pure()),
@ -591,11 +624,13 @@ impl Stmt {
match self { match self {
Self::Var(..) => true, 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, ..)) => { Self::FnCall(x, ..) => !x.is_qualified() && x.name == KEYWORD_EVAL,
!x.is_qualified() && x.name == KEYWORD_EVAL
}
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Self::Import(..) | Self::Export(..) => true, Self::Import(..) | Self::Export(..) => true,
@ -613,12 +648,15 @@ impl Stmt {
#[must_use] #[must_use]
pub fn is_internally_pure(&self) -> bool { pub fn is_internally_pure(&self) -> bool {
match self { 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"))] #[cfg(not(feature = "no_module"))]
Self::Import(expr, ..) => expr.is_pure(), Self::Import(x, ..) => x.0.is_pure(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Self::Export(..) => true, Self::Export(..) => true,
@ -653,86 +691,86 @@ impl Stmt {
} }
match self { match self {
Self::Var(e, _, ..) => { Self::Var(x, ..) => {
if !e.walk(path, on_node) { if !x.1.walk(path, on_node) {
return false; return false;
} }
} }
Self::If(e, x, ..) => { Self::If(x, ..) => {
if !e.walk(path, on_node) { if !x.0.walk(path, on_node) {
return false; return false;
} }
for s in x.0.iter() {
if !s.walk(path, on_node) {
return false;
}
}
for s in x.1.iter() { for s in x.1.iter() {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; 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() { for s in x.2.iter() {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; 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, ..) => { Self::Assignment(x, ..) => {
if !x.1.lhs.walk(path, on_node) { if !x.1.lhs.walk(path, on_node) {
return false; return false;
@ -749,7 +787,7 @@ impl Stmt {
} }
} }
Self::Block(x, ..) => { Self::Block(x, ..) => {
for s in x.iter() { for s in x.statements() {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; 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) { if !e.walk(path, on_node) {
return false; return false;
} }
} }
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Self::Import(e, ..) => { Self::Import(x, ..) => {
if !e.walk(path, on_node) { if !x.0.walk(path, on_node) {
return false; return false;
} }
} }

View File

@ -361,17 +361,23 @@ impl Debugger {
node.position() == *pos && _src == source node.position() == *pos && _src == source
} }
BreakPoint::AtFunctionName { name, .. } => match node { BreakPoint::AtFunctionName { name, .. } => match node {
ASTNode::Expr(Expr::FnCall(x, ..)) ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => {
| ASTNode::Stmt(Stmt::FnCall(x, ..)) x.name == *name
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(x, ..))) => x.name == *name, }
ASTNode::Stmt(Stmt::Expr(e)) => match e.as_ref() {
Expr::FnCall(x, ..) => x.name == *name,
_ => false,
},
_ => false, _ => false,
}, },
BreakPoint::AtFunctionCall { name, args, .. } => match node { BreakPoint::AtFunctionCall { name, args, .. } => match node {
ASTNode::Expr(Expr::FnCall(x, ..)) ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => {
| ASTNode::Stmt(Stmt::FnCall(x, ..))
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(x, ..))) => {
x.args.len() == *args && x.name == *name 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, _ => false,
}, },
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -548,9 +554,12 @@ impl Engine {
DebuggerCommand::FunctionExit => { DebuggerCommand::FunctionExit => {
// Bump a level if it is a function call // Bump a level if it is a function call
let level = match node { let level = match node {
ASTNode::Expr(Expr::FnCall(..)) ASTNode::Expr(Expr::FnCall(..)) | ASTNode::Stmt(Stmt::FnCall(..)) => {
| ASTNode::Stmt(Stmt::FnCall(..)) context.call_level() + 1
| ASTNode::Stmt(Stmt::Expr(Expr::FnCall(..))) => context.call_level() + 1, }
ASTNode::Stmt(Stmt::Expr(e)) if matches!(e.as_ref(), Expr::FnCall(..)) => {
context.call_level() + 1
}
_ => context.call_level(), _ => context.call_level(),
}; };
global.debugger.status = DebuggerStatus::FunctionExit(level); global.debugger.status = DebuggerStatus::FunctionExit(level);

View File

@ -338,7 +338,9 @@ impl Engine {
} }
// If statement // If statement
Stmt::If(expr, x, ..) => { Stmt::If(x, ..) => {
let (expr, if_block, else_block) = x.as_ref();
let guard_val = self let guard_val = self
.eval_expr(scope, global, state, lib, this_ptr, expr, level) .eval_expr(scope, global, state, lib, this_ptr, expr, level)
.and_then(|v| { .and_then(|v| {
@ -349,18 +351,18 @@ impl Engine {
match guard_val { match guard_val {
Ok(true) => { Ok(true) => {
if !x.0.is_empty() { if !if_block.is_empty() {
self.eval_stmt_block( 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 { } else {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }
} }
Ok(false) => { Ok(false) => {
if !x.1.is_empty() { if !else_block.is_empty() {
self.eval_stmt_block( 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 { } else {
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
@ -371,15 +373,17 @@ impl Engine {
} }
// Switch statement // Switch statement
Stmt::Switch(match_expr, x, ..) => { Stmt::Switch(x, ..) => {
let SwitchCases { let (
expr,
SwitchCases {
cases, cases,
def_case, def_case,
ranges, ranges,
} = x.as_ref(); },
) = x.as_ref();
let value_result = let value_result = self.eval_expr(scope, global, state, lib, this_ptr, expr, level);
self.eval_expr(scope, global, state, lib, this_ptr, match_expr, level);
if let Ok(value) = value_result { if let Ok(value) = value_result {
let stmt_block_result = if value.is_hashable() { let stmt_block_result = if value.is_hashable() {
@ -484,7 +488,9 @@ impl Engine {
} }
// Loop // 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() { if !body.is_empty() {
match self match self
.eval_stmt_block(scope, global, state, lib, this_ptr, body, true, level) .eval_stmt_block(scope, global, state, lib, this_ptr, body, true, level)
@ -503,7 +509,9 @@ impl Engine {
}, },
// While loop // While loop
Stmt::While(expr, body, ..) => loop { Stmt::While(x, ..) => loop {
let (expr, body) = x.as_ref();
let condition = self 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| { .and_then(|v| {
@ -532,7 +540,8 @@ impl Engine {
}, },
// Do loop // 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); let is_while = !options.contains(AST_OPTION_NEGATED);
if !body.is_empty() { if !body.is_empty() {
@ -549,7 +558,7 @@ impl Engine {
} }
let condition = self 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| { .and_then(|v| {
v.as_bool().map_err(|typ| { v.as_bool().map_err(|typ| {
self.make_type_mismatch_err::<bool>(typ, expr.position()) self.make_type_mismatch_err::<bool>(typ, expr.position())
@ -564,8 +573,8 @@ impl Engine {
}, },
// For loop // For loop
Stmt::For(expr, x, ..) => { Stmt::For(x, ..) => {
let (Ident { name: var_name, .. }, counter, statements) = x.as_ref(); let (Ident { name: var_name, .. }, expr, counter, statements) = x.as_ref();
let iter_result = self let iter_result = self
.eval_expr(scope, global, state, lib, this_ptr, expr, level) .eval_expr(scope, global, state, lib, this_ptr, expr, level)
@ -786,30 +795,31 @@ impl Engine {
} }
// Throw value // 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) .eval_expr(scope, global, state, lib, this_ptr, expr, level)
.and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())), .and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())),
// Empty throw // 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()) Err(ERR::ErrorRuntime(Dynamic::UNIT, *pos).into())
} }
// Return value // Return value
Stmt::Return(.., Some(expr), pos) => self Stmt::Return(Some(expr), .., pos) => self
.eval_expr(scope, global, state, lib, this_ptr, expr, level) .eval_expr(scope, global, state, lib, this_ptr, expr, level)
.and_then(|v| Err(ERR::Return(v.flatten(), *pos).into())), .and_then(|v| Err(ERR::Return(v.flatten(), *pos).into())),
// Empty return // 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 // Let/const statement - shadowing disallowed
Stmt::Var(.., x, _, pos) if !self.allow_shadowing() && scope.contains(&x.name) => { Stmt::Var(x, .., pos) if !self.allow_shadowing() && scope.contains(&x.0.name) => {
Err(ERR::ErrorVariableExists(x.name.to_string(), *pos).into()) Err(ERR::ErrorVariableExists(x.0.name.to_string(), *pos).into())
} }
// Let/const statement // Let/const statement
Stmt::Var(expr, x, options, pos) => { Stmt::Var(x, options, pos) => {
let var_name = &x.name; let var_name = &x.0.name;
let expr = &x.1;
let entry_type = if options.contains(AST_OPTION_CONSTANT) { let entry_type = if options.contains(AST_OPTION_CONSTANT) {
AccessMode::ReadOnly AccessMode::ReadOnly
@ -902,7 +912,9 @@ impl Engine {
// Import statement // Import statement
#[cfg(not(feature = "no_module"))] #[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 // Guard against too many modules
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
if global.num_modules_loaded >= self.max_modules() { if global.num_modules_loaded >= self.max_modules() {

View File

@ -1,7 +1,9 @@
//! Module implementing the [`AST`] optimizer. //! Module implementing the [`AST`] optimizer.
#![cfg(not(feature = "no_optimize"))] #![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::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF};
use crate::eval::{EvalState, GlobalRuntimeState}; use crate::eval::{EvalState, GlobalRuntimeState};
use crate::func::builtin::get_builtin_binary_op_fn; use crate::func::builtin::get_builtin_binary_op_fn;
@ -252,22 +254,22 @@ fn optimize_stmt_block(
// Optimize each statement in the block // Optimize each statement in the block
for stmt in statements.iter_mut() { for stmt in statements.iter_mut() {
match stmt { match stmt {
Stmt::Var(value_expr, x, options, ..) => { Stmt::Var(x, options, ..) => {
if options.contains(AST_OPTION_CONSTANT) { if options.contains(AST_OPTION_CONSTANT) {
// Add constant literals into the state // 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( state.push_var(
x.name.as_str(), x.0.name.as_str(),
AccessMode::ReadOnly, AccessMode::ReadOnly,
value_expr.get_literal_value(), x.1.get_literal_value(),
); );
} }
} else { } else {
// Add variables into the state // Add variables into the state
optimize_expr(value_expr, state, false); optimize_expr(&mut x.1, state, false);
state.push_var(x.name.as_str(), AccessMode::ReadWrite, None); state.push_var(x.0.name.as_str(), AccessMode::ReadWrite, None);
} }
} }
// Optimize the statement // Optimize the statement
@ -284,10 +286,11 @@ fn optimize_stmt_block(
.find_map(|(i, stmt)| match stmt { .find_map(|(i, stmt)| match stmt {
stmt if !is_pure(stmt) => Some(i), 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"))] #[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, _ => None,
}) })
@ -320,7 +323,7 @@ fn optimize_stmt_block(
loop { loop {
match statements[..] { match statements[..] {
// { return; } -> {} // { return; } -> {}
[Stmt::Return(options, None, ..)] [Stmt::Return(None, options, ..)]
if reduce_return && !options.contains(AST_OPTION_BREAK) => if reduce_return && !options.contains(AST_OPTION_BREAK) =>
{ {
state.set_dirty(); state.set_dirty();
@ -331,7 +334,7 @@ fn optimize_stmt_block(
statements.clear(); statements.clear();
} }
// { ...; return; } -> { ... } // { ...; return; } -> { ... }
[.., ref last_stmt, Stmt::Return(options, None, ..)] [.., ref last_stmt, Stmt::Return(None, options, ..)]
if reduce_return if reduce_return
&& !options.contains(AST_OPTION_BREAK) && !options.contains(AST_OPTION_BREAK)
&& !last_stmt.returns_value() => && !last_stmt.returns_value() =>
@ -340,7 +343,7 @@ fn optimize_stmt_block(
statements.pop().unwrap(); statements.pop().unwrap();
} }
// { ...; return val; } -> { ...; val } // { ...; 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) => if reduce_return && !options.contains(AST_OPTION_BREAK) =>
{ {
state.set_dirty(); state.set_dirty();
@ -377,14 +380,14 @@ fn optimize_stmt_block(
statements.clear(); statements.clear();
} }
// { ...; return; } -> { ... } // { ...; return; } -> { ... }
[.., Stmt::Return(options, None, ..)] [.., Stmt::Return(None, options, ..)]
if reduce_return && !options.contains(AST_OPTION_BREAK) => if reduce_return && !options.contains(AST_OPTION_BREAK) =>
{ {
state.set_dirty(); state.set_dirty();
statements.pop().unwrap(); statements.pop().unwrap();
} }
// { ...; return pure_val; } -> { ... } // { ...; return pure_val; } -> { ... }
[.., Stmt::Return(options, Some(ref expr), ..)] [.., Stmt::Return(Some(ref expr), options, ..)]
if reduce_return if reduce_return
&& !options.contains(AST_OPTION_BREAK) && !options.contains(AST_OPTION_BREAK)
&& expr.is_pure() => && expr.is_pure() =>
@ -460,7 +463,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
} }
// if expr {} // 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(); state.set_dirty();
let pos = condition.start_position(); 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 { *stmt = if preserve_result {
// -> { expr, Noop } // -> { expr, Noop }
Stmt::Block( (
[Stmt::Expr(expr), Stmt::Noop(pos)].into(), [Stmt::Expr(expr.into()), Stmt::Noop(pos)],
Span::new(pos, Position::NONE), pos,
Position::NONE,
) )
.into()
} else { } else {
// -> expr // -> expr
Stmt::Expr(expr) Stmt::Expr(expr.into())
}; };
} }
// if false { if_block } -> Noop // if false { if_block } -> Noop
Stmt::If(Expr::BoolConstant(false, pos), x, ..) if x.1.is_empty() => { 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(); state.set_dirty();
*stmt = Stmt::Noop(*pos); *stmt = Stmt::Noop(pos);
} else {
unreachable!("`Expr::BoolConstant`");
}
} }
// if false { if_block } else { else_block } -> else_block // 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(); state.set_dirty();
*stmt = *stmt =
match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false) 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 if statements.is_empty() => Stmt::Noop(x.1.position()),
statements => Stmt::Block(statements.into_boxed_slice(), x.1.span()), statements => (statements, x.1.span()).into(),
}
}
// 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()),
} }
} }
// if expr { if_block } else { else_block } // 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); optimize_expr(condition, state, false);
*x.0 = optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false); **body =
*x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false); 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 { ... } // 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 value = match_expr.get_literal_value().unwrap();
let hasher = &mut get_hasher(); let hasher = &mut get_hasher();
value.hash(hasher); value.hash(hasher);
let hash = hasher.finish(); let hash = hasher.finish();
let cases = &mut x.cases;
// First check hashes // First check hashes
if let Some(block) = cases.get_mut(&hash) { if let Some(block) = cases.get_mut(&hash) {
if let Some(mut condition) = mem::take(&mut block.condition) { 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); optimize_expr(&mut condition, state, false);
let def_stmt = 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( *stmt = Stmt::If(
(
condition, condition,
Box::new((
mem::take(&mut block.statements), mem::take(&mut block.statements),
Stmt::Block( StmtBlock::new_with_span(
def_stmt.into_boxed_slice(), def_stmt,
x.def_case.span_or_else(*pos, Position::NONE), def_case.span_or_else(*pos, Position::NONE),
),
) )
.into(), .into(),
)),
match_expr.start_position(), match_expr.start_position(),
); );
} else { } else {
@ -549,7 +569,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
true, true,
false, false,
); );
*stmt = Stmt::Block(statements.into_boxed_slice(), block.statements.span()); *stmt = (statements, block.statements.span()).into();
} }
state.set_dirty(); state.set_dirty();
@ -557,8 +577,6 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
} }
// Then check ranges // Then check ranges
let ranges = &mut x.ranges;
if value.is::<INT>() && !ranges.is_empty() { if value.is::<INT>() && !ranges.is_empty() {
let value = value.as_int().expect("`INT`"); 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 } // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(&mut condition, state, false); optimize_expr(&mut condition, state, false);
let def_stmt = optimize_stmt_block( let def_stmt =
mem::take(&mut x.def_case), optimize_stmt_block(mem::take(def_case), state, true, true, false);
state,
true,
true,
false,
);
*stmt = Stmt::If( *stmt = Stmt::If(
(
condition, condition,
Box::new((
mem::take(&mut block.statements), mem::take(&mut block.statements),
Stmt::Block( StmtBlock::new_with_span(
def_stmt.into_boxed_slice(), def_stmt,
x.def_case.span_or_else(*pos, Position::NONE), def_case.span_or_else(*pos, Position::NONE),
),
) )
.into(), .into(),
)),
match_expr.start_position(), match_expr.start_position(),
); );
} else { } 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 = mem::take(&mut *block.statements);
let statements = let statements =
optimize_stmt_block(statements, state, true, true, false); optimize_stmt_block(statements, state, true, true, false);
*stmt = *stmt = (statements, block.statements.span()).into();
Stmt::Block(statements.into_boxed_slice(), block.statements.span());
} }
state.set_dirty(); state.set_dirty();
@ -644,17 +656,25 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// Promote the default case // Promote the default case
state.set_dirty(); state.set_dirty();
let def_stmt = let def_stmt = optimize_stmt_block(mem::take(def_case), state, true, true, false);
optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false); *stmt = (def_stmt, def_case.span_or_else(*pos, Position::NONE)).into();
*stmt = Stmt::Block(
def_stmt.into_boxed_slice(),
x.def_case.span_or_else(*pos, Position::NONE),
);
} }
// switch // 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); 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); let statements = mem::take(&mut *block.statements);
*block.statements = *block.statements =
optimize_stmt_block(statements, state, preserve_result, true, false); 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 // Remove false cases
while let Some((&key, ..)) = x.cases.iter().find(|(.., block)| match block.condition { cases.retain(|_, block| match block.condition {
Some(Expr::BoolConstant(false, ..)) => true, Some(Expr::BoolConstant(false, ..)) => {
_ => false,
}) {
state.set_dirty(); state.set_dirty();
x.cases.remove(&key); 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); // Remove false ranges
*x.def_case = optimize_stmt_block(def_block, state, preserve_result, true, false); 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 // while false { block } -> Noop
Stmt::While(Expr::BoolConstant(false, pos), ..) => { Stmt::While(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => match x.0 {
Expr::BoolConstant(false, pos) => {
state.set_dirty(); state.set_dirty();
*stmt = Stmt::Noop(*pos) *stmt = Stmt::Noop(pos)
} }
_ => unreachable!("`Expr::BoolConstant"),
},
// while expr { block } // while expr { block }
Stmt::While(condition, body, ..) => { Stmt::While(x, ..) => {
let (condition, body) = x.as_mut();
optimize_expr(condition, state, false); optimize_expr(condition, state, false);
if let Expr::BoolConstant(true, pos) = condition { if let Expr::BoolConstant(true, pos) = condition {
*condition = Expr::Unit(*pos); *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 { if body.len() == 1 {
match body[0] { 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 // Only a single break statement - turn into running the guard expression once
state.set_dirty(); state.set_dirty();
if !condition.is_unit() { 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 { if preserve_result {
statements.push(Stmt::Noop(pos)) statements.push(Stmt::Noop(pos))
} }
*stmt = Stmt::Block( *stmt = (statements, Span::new(pos, Position::NONE)).into();
statements.into_boxed_slice(),
Span::new(pos, Position::NONE),
);
} else { } else {
*stmt = Stmt::Noop(pos); *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 } // do { block } until true -> { block }
Stmt::Do(body, Expr::BoolConstant(x, ..), options, ..) Stmt::Do(x, options, ..)
if *x == options.contains(AST_OPTION_NEGATED) => if matches!(x.0, Expr::BoolConstant(true, ..))
&& options.contains(AST_OPTION_NEGATED) =>
{ {
state.set_dirty(); state.set_dirty();
*stmt = Stmt::Block( *stmt = (
optimize_stmt_block(mem::take(&mut **body), state, false, true, false) optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false),
.into_boxed_slice(), x.1.span(),
body.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 // do { block } while|until expr
Stmt::Do(body, condition, ..) => { Stmt::Do(x, ..) => {
optimize_expr(condition, state, false); optimize_expr(&mut x.0, state, false);
***body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false); *x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false);
} }
// for id in expr { block } // for id in expr { block }
Stmt::For(iterable, x, ..) => { Stmt::For(x, ..) => {
optimize_expr(iterable, state, false); optimize_expr(&mut x.1, state, false);
*x.2 = optimize_stmt_block(mem::take(&mut *x.2), state, false, true, false); *x.3 = optimize_stmt_block(mem::take(&mut *x.3), state, false, true, false);
} }
// let id = expr; // let id = expr;
Stmt::Var(expr, _, options, ..) if !options.contains(AST_OPTION_CONSTANT) => { Stmt::Var(x, options, ..) if !options.contains(AST_OPTION_CONSTANT) => {
optimize_expr(expr, state, false) optimize_expr(&mut x.1, state, false)
} }
// import expr as var; // import expr as var;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Import(expr, ..) => optimize_expr(expr, state, false), Stmt::Import(x, ..) => optimize_expr(&mut x.0, state, false),
// { block } // { block }
Stmt::Block(statements, span) => { Stmt::Block(block) => {
let statements = mem::take(statements).into_vec().into(); let span = block.span();
let statements = block.take_statements().into_vec().into();
let mut block = optimize_stmt_block(statements, state, preserve_result, true, false); let mut block = optimize_stmt_block(statements, state, preserve_result, true, false);
match block.as_mut_slice() { match block.as_mut_slice() {
@ -760,18 +819,18 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
state.set_dirty(); state.set_dirty();
*stmt = mem::take(s); *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 // try { pure try_block } catch ( var ) { catch_block } -> try_block
Stmt::TryCatch(x, ..) if x.try_block.iter().all(Stmt::is_pure) => { Stmt::TryCatch(x, ..) if x.try_block.iter().all(Stmt::is_pure) => {
// If try block is pure, there will never be any exceptions // If try block is pure, there will never be any exceptions
state.set_dirty(); state.set_dirty();
*stmt = Stmt::Block( *stmt = (
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false) optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false),
.into_boxed_slice(),
x.try_block.span(), x.try_block.span(),
); )
.into();
} }
// try { try_block } catch ( var ) { catch_block } // try { try_block } catch ( var ) { catch_block }
Stmt::TryCatch(x, ..) => { Stmt::TryCatch(x, ..) => {
@ -780,31 +839,33 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
*x.catch_block = *x.catch_block =
optimize_stmt_block(mem::take(&mut *x.catch_block), state, false, true, false); 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); optimize_expr(expr, state, false);
match expr {
match expr.as_mut() {
// func(...)
Expr::FnCall(x, pos) => { Expr::FnCall(x, pos) => {
state.set_dirty(); state.set_dirty();
*stmt = Stmt::FnCall(mem::take(x), *pos); *stmt = Stmt::FnCall(mem::take(x), *pos);
} }
_ => (), // {...};
} Expr::Stmt(x) => {
} if x.is_empty() {
// {}
Stmt::Expr(Expr::Stmt(x)) if x.is_empty() => {
state.set_dirty(); state.set_dirty();
*stmt = Stmt::Noop(x.position()); *stmt = Stmt::Noop(x.position());
} } else {
// {...};
Stmt::Expr(Expr::Stmt(x)) => {
state.set_dirty(); state.set_dirty();
*stmt = mem::take(&mut **x).into(); *stmt = mem::take(&mut **x).into();
} }
}
// expr; // expr;
Stmt::Expr(expr) => optimize_expr(expr, state, false), _ => (),
}
}
// return expr; // 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 // All other statements - skip
_ => (), _ => (),

View File

@ -5,14 +5,14 @@ use crate::api::events::VarDefInfo;
use crate::api::options::LanguageOptions; use crate::api::options::LanguageOptions;
use crate::ast::{ use crate::ast::{
BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
OpAssignment, ScriptFnDef, Stmt, StmtBlockContainer, SwitchCases, TryCatchBlock, OpAssignment, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, TryCatchBlock,
AST_OPTION_FLAGS::*, AST_OPTION_FLAGS::*,
}; };
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
use crate::eval::{EvalState, GlobalRuntimeState}; use crate::eval::{EvalState, GlobalRuntimeState};
use crate::func::hashing::get_hasher; use crate::func::hashing::get_hasher;
use crate::tokenizer::{ 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, TokenizerControl,
}; };
use crate::types::dynamic::AccessMode; use crate::types::dynamic::AccessMode;
@ -1007,7 +1007,7 @@ fn parse_switch(
} }
let mut cases = BTreeMap::<u64, Box<ConditionalStmtBlock>>::new(); let mut cases = BTreeMap::<u64, Box<ConditionalStmtBlock>>::new();
let mut ranges = StaticVec::<(INT, INT, bool, ConditionalStmtBlock)>::new(); let mut ranges = StaticVec::<(INT, INT, bool, Box<ConditionalStmtBlock>)>::new();
let mut def_pos = Position::NONE; let mut def_pos = Position::NONE;
let mut def_stmt = None; let mut def_stmt = None;
@ -1119,7 +1119,10 @@ fn parse_switch(
}); });
} }
// Other range // 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 None
@ -1129,7 +1132,7 @@ fn parse_switch(
cases.insert(hash, block.into()); cases.insert(hash, block.into());
None None
} }
(None, None) => Some(stmt.into()), (None, None) => Some(Box::new(stmt.into())),
_ => unreachable!("both hash and range in switch statement case"), _ => 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 {
Ok(Stmt::Switch(
item,
SwitchCases {
cases, cases,
def_case, def_case: def_stmt.unwrap_or_else(|| StmtBlock::NONE.into()),
ranges, ranges,
} };
.into(),
settings.pos, Ok(Stmt::Switch((item, cases).into(), settings.pos))
))
} }
/// Parse a primary expression. /// Parse a primary expression.
@ -1877,7 +1875,7 @@ fn parse_op_assignment_stmt(
.map(|(op, pos)| (Some(op), pos)) .map(|(op, pos)| (Some(op), pos))
.expect(NEVER_ENDS), .expect(NEVER_ENDS),
// Not op-assignment // Not op-assignment
_ => return Ok(Stmt::Expr(lhs)), _ => return Ok(Stmt::Expr(lhs.into())),
}; };
let mut settings = settings; let mut settings = settings;
@ -2419,8 +2417,7 @@ fn parse_if(
}; };
Ok(Stmt::If( Ok(Stmt::If(
guard, (guard, if_body.into(), else_body.into()).into(),
(if_body.into(), else_body.into()).into(),
settings.pos, settings.pos,
)) ))
} }
@ -2453,7 +2450,7 @@ fn parse_while_loop(
let body = parse_block(input, state, lib, settings.level_up())?; 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. /// Parse a do loop.
@ -2491,12 +2488,7 @@ fn parse_do(
let guard = parse_expr(input, state, lib, settings.level_up())?.ensure_bool_expr()?; let guard = parse_expr(input, state, lib, settings.level_up())?.ensure_bool_expr()?;
ensure_not_assignment(input)?; ensure_not_assignment(input)?;
Ok(Stmt::Do( Ok(Stmt::Do((guard, body.into()).into(), negated, settings.pos))
Box::new(body.into()),
guard,
negated,
settings.pos,
))
} }
/// Parse a for loop. /// Parse a for loop.
@ -2584,8 +2576,7 @@ fn parse_for(
state.stack.rewind(prev_stack_len); state.stack.rewind(prev_stack_len);
Ok(Stmt::For( Ok(Stmt::For(
expr, Box::new((loop_var, expr, counter_var, body.into())),
Box::new((loop_var, counter_var, body.into())),
settings.pos, settings.pos,
)) ))
} }
@ -2669,14 +2660,13 @@ fn parse_let(
// let name = expr // let name = expr
AccessMode::ReadWrite => { AccessMode::ReadWrite => {
state.stack.push(name, ()); 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 } // const name = { expr:constant }
AccessMode::ReadOnly => { AccessMode::ReadOnly => {
state.stack.push_constant(name, ()); state.stack.push_constant(name, ());
Ok(Stmt::Var( Ok(Stmt::Var(
expr, (var_def, expr).into(),
var_def.into(),
AST_OPTION_CONSTANT + export, AST_OPTION_CONSTANT + export,
settings.pos, settings.pos,
)) ))
@ -2704,7 +2694,7 @@ fn parse_import(
// import expr as ... // import expr as ...
if !match_token(input, Token::As).0 { 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 ... // import expr as name ...
@ -2713,8 +2703,7 @@ fn parse_import(
state.imports.push(name.clone()); state.imports.push(name.clone());
Ok(Stmt::Import( Ok(Stmt::Import(
expr, (expr, Some(Ident { name, pos })).into(),
Some(Ident { name, pos }.into()),
settings.pos, settings.pos,
)) ))
} }
@ -2865,10 +2854,7 @@ fn parse_block(
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
state.imports.truncate(orig_imports_len); state.imports.truncate(orig_imports_len);
Ok(Stmt::Block( Ok((statements, settings.pos, end_pos).into())
statements.into_boxed_slice(),
Span::new(settings.pos, end_pos),
))
} }
/// Parse an expression as a statement. /// Parse an expression as a statement.
@ -3071,17 +3057,17 @@ fn parse_stmt(
match input.peek().expect(NEVER_ENDS) { match input.peek().expect(NEVER_ENDS) {
// `return`/`throw` at <EOF> // `return`/`throw` at <EOF>
(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 // `return`/`throw` at end of block
(Token::RightBrace, ..) if !settings.is_global => { (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;` // `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 // `return` or `throw` with expression
_ => { _ => {
let expr = parse_expr(input, state, lib, settings.level_up())?; 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( statements.extend(
externals externals
.into_iter() .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()) Expr::Stmt(crate::ast::StmtBlock::new(statements, pos, Position::NONE).into())
} }
@ -3482,8 +3468,8 @@ impl Engine {
} }
} }
let mut statements = smallvec::SmallVec::new_const(); let mut statements = StmtBlockContainer::new_const();
statements.push(Stmt::Expr(expr)); statements.push(Stmt::Expr(expr.into()));
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
return Ok(crate::optimizer::optimize_into_ast( return Ok(crate::optimizer::optimize_into_ast(
@ -3509,7 +3495,7 @@ impl Engine {
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
) -> ParseResult<(StmtBlockContainer, StaticVec<Shared<ScriptFnDef>>)> { ) -> ParseResult<(StmtBlockContainer, StaticVec<Shared<ScriptFnDef>>)> {
let mut statements = smallvec::SmallVec::new_const(); let mut statements = StmtBlockContainer::new_const();
let mut functions = BTreeMap::new(); let mut functions = BTreeMap::new();
while !input.peek().expect(NEVER_ENDS).0.is_eof() { while !input.peek().expect(NEVER_ENDS).0.is_eof() {

View File

@ -21,8 +21,8 @@ fn check_struct_sizes() {
); );
assert_eq!(size_of::<ast::Expr>(), if PACKED { 12 } else { 16 }); assert_eq!(size_of::<ast::Expr>(), if PACKED { 12 } else { 16 });
assert_eq!(size_of::<Option<ast::Expr>>(), if PACKED { 12 } else { 16 }); assert_eq!(size_of::<Option<ast::Expr>>(), if PACKED { 12 } else { 16 });
assert_eq!(size_of::<ast::Stmt>(), if PACKED { 24 } else { 32 }); assert_eq!(size_of::<ast::Stmt>(), if PACKED { 12 } else { 16 });
assert_eq!(size_of::<Option<ast::Stmt>>(), if PACKED { 24 } else { 32 }); assert_eq!(size_of::<Option<ast::Stmt>>(), if PACKED { 12 } else { 16 });
#[cfg(target_pointer_width = "64")] #[cfg(target_pointer_width = "64")]
{ {

View File

@ -84,7 +84,7 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
format!("{:?}", ast), 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 }")?; let ast = engine.compile("if 1 == 2 { 42 }")?;