diff --git a/CHANGELOG.md b/CHANGELOG.md index 2172dc0d..dd254426 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,8 @@ Enhancements ### `AST` API * Added `ASTNode::position`. +* `ReturnType` is removed in favor of option flags for `Stmt::Return`. +* `Stmt::Break` and `Stmt::Continue` are merged into `Stmt::BreakLoop` via an option flag. Version 1.0.4 diff --git a/src/ast.rs b/src/ast.rs index 897bc956..871c3efc 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -837,20 +837,6 @@ impl fmt::Debug for Ident { } } -/// _(internals)_ A type encapsulating the mode of a `return`/`throw` statement. -/// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. -#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] -pub enum ReturnType { - /// `return` statement. - Return, - /// `throw` statement. - Exception, -} - /// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`]. /// Exported under the `internals` feature only. /// @@ -859,7 +845,9 @@ pub enum ReturnType { /// This type is volatile and may change. #[derive(Debug, Clone, Hash)] pub enum ASTNode<'a> { + /// A statement ([`Stmt`]). Stmt(&'a Stmt), + /// An expression ([`Expr`]). Expr(&'a Expr), } @@ -885,7 +873,7 @@ impl ASTNode<'_> { } } -/// _(internals)_ A statements block. +/// _(internals)_ A scoped block of statements. /// Exported under the `internals` feature only. /// /// # Volatile Data Structure @@ -914,18 +902,12 @@ impl StmtBlock { pub fn len(&self) -> usize { self.0.len() } - /// Get the position of this statements block. + /// Get the position (location of the beginning `{`) of this statements block. #[inline(always)] #[must_use] pub const fn position(&self) -> Position { self.1 } - /// Get the statements of this statements block. - #[inline(always)] - #[must_use] - pub fn statements_mut(&mut self) -> &mut StaticVec { - &mut self.0 - } } impl Deref for StmtBlock { @@ -1026,12 +1008,15 @@ pub mod AST_OPTION_FLAGS { /// _(internals)_ The [`AST`][crate::AST] node is constant. /// Exported under the `internals` feature only. pub const AST_OPTION_CONSTANT: OptionFlags = OptionFlags(0b0000_0001); - /// _(internals)_ The [`AST`][crate::AST] node is exported. + /// _(internals)_ The [`AST`][crate::AST] node is public. /// Exported under the `internals` feature only. - pub const AST_OPTION_EXPORTED: OptionFlags = OptionFlags(0b0000_0010); + pub const AST_OPTION_PUBLIC: OptionFlags = OptionFlags(0b0000_0010); /// _(internals)_ The [`AST`][crate::AST] node is in negated mode. /// Exported under the `internals` feature only. pub const AST_OPTION_NEGATED: OptionFlags = OptionFlags(0b0000_0100); + /// _(internals)_ The [`AST`][crate::AST] node breaks out of normal control flow. + /// Exported under the `internals` feature only. + pub const AST_OPTION_BREAK_OUT: OptionFlags = OptionFlags(0b0000_1000); impl std::fmt::Debug for OptionFlags { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -1056,8 +1041,9 @@ pub mod AST_OPTION_FLAGS { f.write_str("(")?; write_option(self, f, num_flags, AST_OPTION_CONSTANT, "Constant")?; - write_option(self, f, num_flags, AST_OPTION_EXPORTED, "Exported")?; + write_option(self, f, num_flags, AST_OPTION_PUBLIC, "Public")?; write_option(self, f, num_flags, AST_OPTION_NEGATED, "Negated")?; + write_option(self, f, num_flags, AST_OPTION_BREAK_OUT, "Break")?; f.write_str(")")?; Ok(()) @@ -1100,7 +1086,7 @@ pub enum Stmt { /// /// ### Option Flags /// - /// * [`AST_FLAG_EXPORTED`][AST_FLAGS::AST_FLAG_EXPORTED] = `export` + /// * [`AST_FLAG_PUBLIC`][AST_FLAGS::AST_FLAG_PUBLIC] = `export` /// * [`AST_FLAG_CONSTANT`][AST_FLAGS::AST_FLAG_CONSTANT] = `const` Var(Expr, Box, OptionFlags, Position), /// expr op`=` expr @@ -1116,12 +1102,20 @@ pub enum Stmt { TryCatch(Box<(StmtBlock, Option, StmtBlock)>, Position), /// [expression][Expr] Expr(Expr), - /// `continue` - Continue(Position), - /// `break` - Break(Position), + /// `continue`/`break` + /// + /// ### Option Flags + /// + /// * [`AST_FLAG_NONE`][AST_FLAGS::AST_FLAG_NONE] = `continue` + /// * [`AST_FLAG_BREAK_OUT`][AST_FLAGS::AST_FLAG_BREAK_OUT] = `break` + BreakLoop(OptionFlags, Position), /// `return`/`throw` - Return(ReturnType, Option, Position), + /// + /// ### Option Flags + /// + /// * [`AST_FLAG_NONE`][AST_FLAGS::AST_FLAG_NONE] = `return` + /// * [`AST_FLAG_BREAK_OUT`][AST_FLAGS::AST_FLAG_BREAK_OUT] = `throw` + Return(OptionFlags, Option, Position), /// `import` expr `as` var /// /// Not available under `no_module`. @@ -1135,6 +1129,11 @@ pub enum Stmt { /// Convert a variable to shared. /// /// Not available under `no_closure`. + /// + /// # Notes + /// + /// 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(Identifier), } @@ -1172,8 +1171,7 @@ impl Stmt { pub const fn position(&self) -> Position { match self { Self::Noop(pos) - | Self::Continue(pos) - | Self::Break(pos) + | Self::BreakLoop(_, pos) | Self::Block(_, pos) | Self::Assignment(_, pos) | Self::FnCall(_, pos) @@ -1201,8 +1199,7 @@ impl Stmt { pub fn set_position(&mut self, new_pos: Position) -> &mut Self { match self { Self::Noop(pos) - | Self::Continue(pos) - | Self::Break(pos) + | Self::BreakLoop(_, pos) | Self::Block(_, pos) | Self::Assignment(_, pos) | Self::FnCall(_, pos) @@ -1248,8 +1245,7 @@ impl Stmt { Self::Var(_, _, _, _) | Self::Assignment(_, _) - | Self::Continue(_) - | Self::Break(_) + | Self::BreakLoop(_, _) | Self::Return(_, _, _) => false, #[cfg(not(feature = "no_module"))] @@ -1280,8 +1276,7 @@ impl Stmt { | Self::Expr(_) | Self::FnCall(_, _) | Self::Do(_, _, _, _) - | Self::Continue(_) - | Self::Break(_) + | Self::BreakLoop(_, _) | Self::Return(_, _, _) => false, #[cfg(not(feature = "no_module"))] @@ -1330,7 +1325,7 @@ impl Stmt { Self::Var(_, _, _, _) | Self::Assignment(_, _) | Self::FnCall(_, _) => false, Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), - Self::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false, + Self::BreakLoop(_, _) | Self::Return(_, _, _) => false, Self::TryCatch(x, _) => { (x.0).0.iter().all(Stmt::is_pure) && (x.2).0.iter().all(Stmt::is_pure) } @@ -1348,8 +1343,8 @@ impl Stmt { /// /// An internally pure statement only has side effects that disappear outside the block. /// - /// Only variable declarations (i.e. `let` and `const`) and `import`/`export` statements - /// are internally pure. + /// Currently only variable declarations (i.e. `let` and `const`) and `import`/`export` + /// statements are internally pure. #[inline] #[must_use] pub fn is_internally_pure(&self) -> bool { @@ -1373,7 +1368,7 @@ impl Stmt { #[must_use] pub const fn is_control_flow_break(&self) -> bool { match self { - Self::Return(_, _, _) | Self::Break(_) | Self::Continue(_) => true, + Self::Return(_, _, _) | Self::BreakLoop(_, _) => true, _ => false, } } @@ -1516,7 +1511,8 @@ impl Stmt { pub struct CustomExpr { /// List of keywords. pub keywords: StaticVec, - /// Is the current [`Scope`][crate::Scope] modified? + /// Is the current [`Scope`][crate::Scope] possibly modified by this custom statement + /// (e.g. introducing a new variable)? pub scope_may_be_changed: bool, /// List of tokens actually parsed. pub tokens: StaticVec, @@ -1689,6 +1685,9 @@ pub struct FnCallExpr { /// List of function call argument expressions. pub args: StaticVec, /// List of function call arguments that are constants. + /// + /// Any arguments in `args` that is [`Expr::Stack`][Expr::Stack] indexes into this + /// array to find the constant for use as its argument value. pub constants: smallvec::SmallVec<[Dynamic; 2]>, /// Function name. pub name: Identifier, @@ -1889,6 +1888,12 @@ pub enum Expr { )>, ), /// Stack slot + /// + /// # Notes + /// + /// This variant does not map to any language structure. It is currently only used in function + /// calls with constant arguments where the `usize` number indexes into an array containing a + /// list of constant arguments for the function call. See [`FnCallExpr`] for more details. Stack(usize, Position), /// { [statement][Stmt] ... } Stmt(Box), diff --git a/src/engine.rs b/src/engine.rs index abe08f76..0631ec66 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,6 @@ //! Main module defining the script evaluation [`Engine`]. -use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, ReturnType, Stmt, AST_OPTION_FLAGS::*}; +use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*}; use crate::custom_syntax::CustomSyntax; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::fn_hash::get_hasher; @@ -2704,11 +2704,10 @@ impl Engine { } } - // Continue statement - Stmt::Continue(pos) => EvalAltResult::LoopBreak(false, *pos).into(), - - // Break statement - Stmt::Break(pos) => EvalAltResult::LoopBreak(true, *pos).into(), + // Continue/Break statement + Stmt::BreakLoop(options, pos) => { + EvalAltResult::LoopBreak(options.contains(AST_OPTION_BREAK_OUT), *pos).into() + } // Namespace-qualified function call Stmt::FnCall(x, pos) if x.is_qualified() => { @@ -2828,8 +2827,23 @@ impl Engine { } } + // Throw value + Stmt::Return(options, Some(expr), pos) if options.contains(AST_OPTION_BREAK_OUT) => { + EvalAltResult::ErrorRuntime( + self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .flatten(), + *pos, + ) + .into() + } + + // Empty throw + Stmt::Return(options, None, pos) if options.contains(AST_OPTION_BREAK_OUT) => { + EvalAltResult::ErrorRuntime(Dynamic::UNIT, *pos).into() + } + // Return value - Stmt::Return(ReturnType::Return, Some(expr), pos) => EvalAltResult::Return( + Stmt::Return(_, Some(expr), pos) => EvalAltResult::Return( self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .flatten(), *pos, @@ -2837,22 +2851,7 @@ impl Engine { .into(), // Empty return - Stmt::Return(ReturnType::Return, None, pos) => { - EvalAltResult::Return(Dynamic::UNIT, *pos).into() - } - - // Throw value - Stmt::Return(ReturnType::Exception, Some(expr), pos) => EvalAltResult::ErrorRuntime( - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? - .flatten(), - *pos, - ) - .into(), - - // Empty throw - Stmt::Return(ReturnType::Exception, None, pos) => { - EvalAltResult::ErrorRuntime(Dynamic::UNIT, *pos).into() - } + Stmt::Return(_, None, pos) => EvalAltResult::Return(Dynamic::UNIT, *pos).into(), // Let/const statement Stmt::Var(expr, x, options, _) => { @@ -2862,7 +2861,7 @@ impl Engine { } else { AccessMode::ReadWrite }; - let export = options.contains(AST_OPTION_EXPORTED); + let export = options.contains(AST_OPTION_PUBLIC); let value = self .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? diff --git a/src/lib.rs b/src/lib.rs index e7853cda..97423577 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -230,7 +230,7 @@ pub use token::{InputStream, Token, TokenizeState, TokenizerControl, TokenizerCo #[deprecated = "this type is volatile and may change"] pub use ast::{ ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnCallHashes, Ident, - OpAssignment, OptionFlags, ReturnType, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*, + OpAssignment, OptionFlags, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*, }; #[cfg(feature = "internals")] diff --git a/src/optimize.rs b/src/optimize.rs index 32c90183..6a8f2ff9 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -16,6 +16,7 @@ use std::{ any::TypeId, hash::{Hash, Hasher}, mem, + ops::DerefMut, }; #[cfg(not(feature = "no_closure"))] @@ -267,7 +268,9 @@ fn optimize_stmt_block( loop { match statements[..] { // { return; } -> {} - [Stmt::Return(crate::ast::ReturnType::Return, None, _)] if reduce_return => { + [Stmt::Return(options, None, _)] + if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) => + { state.set_dirty(); statements.clear(); } @@ -276,8 +279,10 @@ fn optimize_stmt_block( statements.clear(); } // { ...; return; } -> { ... } - [.., ref last_stmt, Stmt::Return(crate::ast::ReturnType::Return, None, _)] - if reduce_return && !last_stmt.returns_value() => + [.., ref last_stmt, Stmt::Return(options, None, _)] + if reduce_return + && !options.contains(AST_OPTION_BREAK_OUT) + && !last_stmt.returns_value() => { state.set_dirty(); statements @@ -285,8 +290,8 @@ fn optimize_stmt_block( .expect("`statements` contains at least two elements"); } // { ...; return val; } -> { ...; val } - [.., Stmt::Return(crate::ast::ReturnType::Return, ref mut expr, pos)] - if reduce_return => + [.., Stmt::Return(options, ref mut expr, pos)] + if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) => { state.set_dirty(); *statements @@ -332,8 +337,8 @@ fn optimize_stmt_block( statements.clear(); } // { ...; return; } -> { ... } - [.., Stmt::Return(crate::ast::ReturnType::Return, None, _)] - if reduce_return => + [.., Stmt::Return(options, None, _)] + if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) => { state.set_dirty(); statements @@ -341,8 +346,10 @@ fn optimize_stmt_block( .expect("`statements` contains at least two elements"); } // { ...; return pure_val; } -> { ... } - [.., Stmt::Return(crate::ast::ReturnType::Return, Some(ref expr), _)] - if reduce_return && expr.is_pure() => + [.., Stmt::Return(options, Some(ref expr), _)] + if reduce_return + && !options.contains(AST_OPTION_BREAK_OUT) + && expr.is_pure() => { state.set_dirty(); statements @@ -467,12 +474,10 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // if expr { if_block } else { else_block } Stmt::If(condition, x, _) => { optimize_expr(condition, state, false); - let if_block = mem::take(x.0.statements_mut()).into_vec(); - *x.0.statements_mut() = - optimize_stmt_block(if_block, state, preserve_result, true, false).into(); - let else_block = mem::take(x.1.statements_mut()).into_vec(); - *x.1.statements_mut() = - optimize_stmt_block(else_block, state, preserve_result, true, false).into(); + let if_block = mem::take(x.0.deref_mut()).into_vec(); + *x.0 = optimize_stmt_block(if_block, state, preserve_result, true, false).into(); + let else_block = mem::take(x.1.deref_mut()).into_vec(); + *x.1 = optimize_stmt_block(else_block, state, preserve_result, true, false).into(); } // switch const { ... } @@ -545,8 +550,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b _ => { block.0 = Some(condition); - *block.1.statements_mut() = optimize_stmt_block( - mem::take(block.1.statements_mut()).into_vec(), + *block.1 = optimize_stmt_block( + mem::take(block.1.deref_mut()).into_vec(), state, preserve_result, true, @@ -566,9 +571,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b x.0.remove(&key); } - let def_block = mem::take(x.1.statements_mut()).into_vec(); - *x.1.statements_mut() = - optimize_stmt_block(def_block, state, preserve_result, true, false).into(); + let def_block = mem::take(x.1.deref_mut()).into_vec(); + *x.1 = optimize_stmt_block(def_block, state, preserve_result, true, false).into(); } // while false { block } -> Noop @@ -582,13 +586,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b if let Expr::BoolConstant(true, pos) = condition { *condition = Expr::Unit(*pos); } - let block = mem::take(body.statements_mut()).into_vec(); - *body.statements_mut() = optimize_stmt_block(block, state, false, true, false).into(); + let block = mem::take(body.as_mut().deref_mut()).into_vec(); + *body.as_mut().deref_mut() = + optimize_stmt_block(block, state, false, true, false).into(); if body.len() == 1 { match body[0] { // while expr { break; } -> { expr; } - Stmt::Break(pos) => { + Stmt::BreakLoop(options, pos) if options.contains(AST_OPTION_BREAK_OUT) => { // Only a single break statement - turn into running the guard expression once state.set_dirty(); if !condition.is_unit() { @@ -611,7 +616,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b { state.set_dirty(); let block_pos = body.position(); - let block = mem::take(body.statements_mut()).into_vec(); + let block = mem::take(body.as_mut().deref_mut()).into_vec(); *stmt = Stmt::Block( optimize_stmt_block(block, state, false, true, false).into_boxed_slice(), block_pos, @@ -620,14 +625,15 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // do { block } while|until expr Stmt::Do(body, condition, _, _) => { optimize_expr(condition, state, false); - let block = mem::take(body.statements_mut()).into_vec(); - *body.statements_mut() = optimize_stmt_block(block, state, false, true, false).into(); + let block = mem::take(body.as_mut().deref_mut()).into_vec(); + *body.as_mut().deref_mut() = + optimize_stmt_block(block, state, false, true, false).into(); } // for id in expr { block } Stmt::For(iterable, x, _) => { optimize_expr(iterable, state, false); - let body = mem::take(x.2.statements_mut()).into_vec(); - *x.2.statements_mut() = optimize_stmt_block(body, state, false, true, false).into(); + let body = mem::take(x.2.deref_mut()).into_vec(); + *x.2 = optimize_stmt_block(body, state, false, true, false).into(); } // let id = expr; Stmt::Var(expr, _, options, _) if !options.contains(AST_OPTION_CONSTANT) => { @@ -667,12 +673,10 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } // try { try_block } catch ( var ) { catch_block } Stmt::TryCatch(x, _) => { - let try_block = mem::take(x.0.statements_mut()).into_vec(); - *x.0.statements_mut() = - optimize_stmt_block(try_block, state, false, true, false).into(); - let catch_block = mem::take(x.2.statements_mut()).into_vec(); - *x.2.statements_mut() = - optimize_stmt_block(catch_block, state, false, true, false).into(); + let try_block = mem::take(x.0.deref_mut()).into_vec(); + *x.0 = optimize_stmt_block(try_block, state, false, true, false).into(); + let catch_block = mem::take(x.2.deref_mut()).into_vec(); + *x.2 = optimize_stmt_block(catch_block, state, false, true, false).into(); } // func(...) Stmt::Expr(expr @ Expr::FnCall(_, _)) => { @@ -721,7 +725,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) { Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) } // { stmt; ... } - do not count promotion as dirty because it gets turned back into an array Expr::Stmt(x) => { - *x.statements_mut() = optimize_stmt_block(mem::take(x.statements_mut()).into_vec(), state, true, true, false).into(); + *x.as_mut().deref_mut() = + optimize_stmt_block(mem::take(x.as_mut().deref_mut()).into_vec(), state, true, true, false).into(); // { Stmt(Expr) } - promote match x.as_mut().as_mut() { @@ -1171,10 +1176,9 @@ pub fn optimize_into_ast( // Optimize the function body let state = &mut OptimizerState::new(engine, lib2, level); - let body = mem::take(fn_def.body.statements_mut()).into_vec(); + let body = mem::take(fn_def.body.deref_mut()).into_vec(); - *fn_def.body.statements_mut() = - optimize_stmt_block(body, state, true, true, true).into(); + *fn_def.body = optimize_stmt_block(body, state, true, true, true).into(); fn_def }) diff --git a/src/parse.rs b/src/parse.rs index 94b0de56..5ecca6a5 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,8 +1,8 @@ //! Main module defining the lexer and parser. use crate::ast::{ - BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ReturnType, - ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*, + BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt, + StmtBlock, AST_OPTION_FLAGS::*, }; use crate::custom_syntax::{markers::*, CustomSyntax}; use crate::dynamic::AccessMode; @@ -2428,7 +2428,7 @@ fn parse_let( state.stack.push((name, var_type)); let export = if is_export { - AST_OPTION_EXPORTED + AST_OPTION_PUBLIC } else { AST_OPTION_NONE }; @@ -2829,11 +2829,11 @@ fn parse_stmt( Token::Continue if settings.is_breakable => { let pos = eat_token(input, Token::Continue); - Ok(Stmt::Continue(pos)) + Ok(Stmt::BreakLoop(AST_OPTION_NONE, pos)) } Token::Break if settings.is_breakable => { let pos = eat_token(input, Token::Break); - Ok(Stmt::Break(pos)) + Ok(Stmt::BreakLoop(AST_OPTION_BREAK_OUT, pos)) } Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(settings.pos)), @@ -2843,8 +2843,8 @@ fn parse_stmt( .map(|(token, pos)| { ( match token { - Token::Return => ReturnType::Return, - Token::Throw => ReturnType::Exception, + Token::Return => AST_OPTION_NONE, + Token::Throw => AST_OPTION_BREAK_OUT, _ => unreachable!(), }, pos,