diff --git a/src/ast.rs b/src/ast.rs index 16288c17..6b0e4c76 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -16,7 +16,7 @@ use std::{ hash::Hash, mem, num::{NonZeroU8, NonZeroUsize}, - ops::{Add, AddAssign, Deref, DerefMut}, + ops::{Add, AddAssign, Deref, DerefMut, Not, Sub, SubAssign}, }; #[cfg(not(feature = "no_float"))] @@ -950,16 +950,108 @@ impl From for Stmt { } } -/// _(internals)_ Type of variable declaration. +/// A type that holds a configuration option with bit-flags. /// Exported under the `internals` feature only. -/// -/// # Volatile Data Structure -/// -/// This type is volatile and may change. -#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] -pub enum VarDeclaration { - Let, - Const, +#[derive(PartialEq, Eq, Copy, Clone, Hash, Default)] +pub struct BitOptions(u8); + +impl BitOptions { + /// Does this [`BitOptions`] contain a particular option flag? + #[inline(always)] + pub const fn contains(self, flag: Self) -> bool { + self.0 & flag.0 != 0 + } +} + +impl Not for BitOptions { + type Output = Self; + + fn not(self) -> Self::Output { + Self(!self.0) + } +} + +impl Add for BitOptions { + type Output = Self; + + #[inline(always)] + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +impl AddAssign for BitOptions { + #[inline(always)] + fn add_assign(&mut self, rhs: Self) { + self.0 |= rhs.0 + } +} + +impl Sub for BitOptions { + type Output = Self; + + #[inline(always)] + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 & !rhs.0) + } +} + +impl SubAssign for BitOptions { + #[inline(always)] + fn sub_assign(&mut self, rhs: Self) { + self.0 &= !rhs.0 + } +} + +/// Option bit-flags for [`AST`] nodes. +#[allow(non_snake_case)] +pub mod AST_FLAGS { + use super::BitOptions; + + /// _(internals)_ No options for the [`AST`][crate::AST] node. + /// Exported under the `internals` feature only. + pub const AST_FLAG_NONE: BitOptions = BitOptions(0b0000_0000); + /// _(internals)_ The [`AST`][crate::AST] node is constant. + /// Exported under the `internals` feature only. + pub const AST_FLAG_CONSTANT: BitOptions = BitOptions(0b0000_0001); + /// _(internals)_ The [`AST`][crate::AST] node is exported. + /// Exported under the `internals` feature only. + pub const AST_FLAG_EXPORTED: BitOptions = BitOptions(0b0000_0010); + /// _(internals)_ The [`AST`][crate::AST] node is in negated mode. + /// Exported under the `internals` feature only. + pub const AST_FLAG_NEGATED: BitOptions = BitOptions(0b0000_0100); + + impl std::fmt::Debug for BitOptions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut has_flags = false; + + f.write_str("(")?; + if self.contains(AST_FLAG_CONSTANT) { + if has_flags { + f.write_str("+")?; + } + f.write_str("Constant")?; + has_flags = true; + } + if self.contains(AST_FLAG_EXPORTED) { + if has_flags { + f.write_str("+")?; + } + f.write_str("Exported")?; + has_flags = true; + } + if self.contains(AST_FLAG_NEGATED) { + if has_flags { + f.write_str("+")?; + } + f.write_str("Negated")?; + //has_flags = true; + } + f.write_str(")")?; + + Ok(()) + } + } } /// _(internals)_ A statement. @@ -983,11 +1075,21 @@ pub enum Stmt { /// `while` expr `{` stmt `}` While(Expr, Box, Position), /// `do` `{` stmt `}` `while`|`until` expr - Do(Box, Expr, bool, Position), + /// + /// ### Option flags + /// + /// * [`AST_FLAG_NONE`][AST_FLAGS::AST_FLAG_NONE] = `while` + /// * [`AST_FLAG_NEGATED`][AST_FLAGS::AST_FLAG_NEGATED] = `until` + Do(Box, Expr, BitOptions, Position), /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` For(Expr, Box<(Ident, Option, StmtBlock)>, Position), /// \[`export`\] `let`/`const` id `=` expr - Var(Expr, Box, VarDeclaration, bool, Position), + /// + /// ### Option flags + /// + /// * [`AST_FLAG_EXPORTED`][AST_FLAGS::AST_FLAG_EXPORTED] = `export` + /// * [`AST_FLAG_CONSTANT`][AST_FLAGS::AST_FLAG_CONSTANT] = `const` + Var(Expr, Box, BitOptions, Position), /// expr op`=` expr Assignment(Box<(Expr, Option>, Expr)>, Position), /// func `(` expr `,` ... `)` @@ -1068,7 +1170,7 @@ impl Stmt { | Self::Do(_, _, _, pos) | Self::For(_, _, pos) | Self::Return(_, _, pos) - | Self::Var(_, _, _, _, pos) + | Self::Var(_, _, _, pos) | Self::TryCatch(_, pos) => *pos, Self::Expr(x) => x.position(), @@ -1097,7 +1199,7 @@ impl Stmt { | Self::Do(_, _, _, pos) | Self::For(_, _, pos) | Self::Return(_, _, pos) - | Self::Var(_, _, _, _, pos) + | Self::Var(_, _, _, pos) | Self::TryCatch(_, pos) => *pos = new_pos, Self::Expr(x) => { @@ -1131,7 +1233,7 @@ impl Stmt { | Self::For(_, _, _) | Self::TryCatch(_, _) => false, - Self::Var(_, _, _, _, _) + Self::Var(_, _, _, _) | Self::Assignment(_, _) | Self::Continue(_) | Self::Break(_) @@ -1158,7 +1260,7 @@ impl Stmt { // A No-op requires a semicolon in order to know it is an empty statement! Self::Noop(_) => false, - Self::Var(_, _, _, _, _) + Self::Var(_, _, _, _) | Self::Assignment(_, _) | Self::FnCall(_, _) | Self::Expr(_) @@ -1199,7 +1301,7 @@ impl Stmt { condition.is_pure() && block.0.iter().all(Stmt::is_pure) } Self::For(iterable, x, _) => iterable.is_pure() && (x.2).0.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::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false, Self::TryCatch(x, _) => { @@ -1225,7 +1327,7 @@ impl Stmt { #[must_use] pub fn is_internally_pure(&self) -> bool { match self { - Self::Var(expr, _, _, _, _) => expr.is_pure(), + Self::Var(expr, _, _, _) => expr.is_pure(), #[cfg(not(feature = "no_module"))] Self::Import(expr, _, _) => expr.is_pure(), @@ -1263,7 +1365,7 @@ impl Stmt { } match self { - Self::Var(e, _, _, _, _) => { + Self::Var(e, _, _, _) => { if !e.walk(path, on_node) { return false; } diff --git a/src/engine.rs b/src/engine.rs index 8bd29996..256c0aa7 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, VarDeclaration}; +use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, ReturnType, Stmt, AST_FLAGS::*}; use crate::custom_syntax::CustomSyntax; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::fn_hash::get_hasher; @@ -2569,7 +2569,9 @@ impl Engine { }, // Do loop - Stmt::Do(body, expr, is_while, _) => loop { + Stmt::Do(body, expr, flags, _) => loop { + let is_while = !flags.contains(AST_FLAG_NEGATED); + if !body.is_empty() { match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level) { @@ -2587,7 +2589,7 @@ impl Engine { .as_bool() .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; - if condition ^ *is_while { + if condition ^ is_while { return Ok(Dynamic::UNIT); } }, @@ -2851,12 +2853,14 @@ impl Engine { } // Let/const statement - Stmt::Var(expr, x, var_type, export, _) => { + Stmt::Var(expr, x, flags, _) => { let name = &x.name; - let entry_type = match var_type { - VarDeclaration::Let => AccessMode::ReadWrite, - VarDeclaration::Const => AccessMode::ReadOnly, + let entry_type = if flags.contains(AST_FLAG_CONSTANT) { + AccessMode::ReadOnly + } else { + AccessMode::ReadWrite }; + let export = flags.contains(AST_FLAG_EXPORTED); let value = self .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? @@ -2893,9 +2897,9 @@ impl Engine { ( name.to_string().into(), - if *export { Some(name.clone()) } else { None }, + if export { Some(name.clone()) } else { None }, ) - } else if *export { + } else if export { unreachable!("exported variable not on global level"); } else { (unsafe_cast_var_name_to_lifetime(name).into(), None) diff --git a/src/lib.rs b/src/lib.rs index 1d72720f..1d103f43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -227,8 +227,8 @@ pub use token::{InputStream, Token, TokenizeState, TokenizerControl, TokenizerCo #[cfg(feature = "internals")] #[deprecated = "this type is volatile and may change"] pub use ast::{ - ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnCallHashes, Ident, - OpAssignment, ReturnType, ScriptFnDef, Stmt, StmtBlock, VarDeclaration, + ASTNode, BinaryExpr, BitOptions, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnCallHashes, + Ident, OpAssignment, ReturnType, ScriptFnDef, Stmt, StmtBlock, AST_FLAGS::*, }; #[cfg(feature = "internals")] diff --git a/src/optimize.rs b/src/optimize.rs index 75fe9a83..13883603 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,6 +1,6 @@ //! Module implementing the [`AST`] optimizer. -use crate::ast::{Expr, OpAssignment, Stmt, VarDeclaration}; +use crate::ast::{Expr, OpAssignment, Stmt, AST_FLAGS::*}; use crate::dynamic::AccessMode; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::fn_builtin::get_builtin_binary_op_fn; @@ -201,23 +201,24 @@ fn optimize_stmt_block( // Optimize each statement in the block statements.iter_mut().for_each(|stmt| { match stmt { - // Add constant literals into the state - Stmt::Var(value_expr, x, VarDeclaration::Const, _, _) => { - optimize_expr(value_expr, state, false); + Stmt::Var(value_expr, x, flags, _) => { + if flags.contains(AST_FLAG_CONSTANT) { + // Add constant literals into the state + optimize_expr(value_expr, state, false); - if value_expr.is_constant() { - state.push_var( - &x.name, - AccessMode::ReadOnly, - value_expr.get_literal_value(), - ); + if value_expr.is_constant() { + state.push_var( + &x.name, + AccessMode::ReadOnly, + value_expr.get_literal_value(), + ); + } + } else { + // Add variables into the state + optimize_expr(value_expr, state, false); + state.push_var(&x.name, AccessMode::ReadWrite, None); } } - // Add variables into the state - Stmt::Var(value_expr, x, VarDeclaration::Let, _, _) => { - optimize_expr(value_expr, state, false); - state.push_var(&x.name, AccessMode::ReadWrite, None); - } // Optimize the statement _ => optimize_stmt(stmt, state, preserve_result), } @@ -232,7 +233,7 @@ 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(e, _, _, _) | Stmt::Expr(e) if !e.is_constant() => Some(i), #[cfg(not(feature = "no_module"))] Stmt::Import(e, _, _) if !e.is_constant() => Some(i), @@ -582,8 +583,9 @@ 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(true, _), false, _) - | Stmt::Do(body, Expr::BoolConstant(false, _), true, _) => { + Stmt::Do(body, Expr::BoolConstant(x, _), flags, _) + if *x == flags.contains(AST_FLAG_NEGATED) => + { state.set_dirty(); let block_pos = body.position(); let block = mem::take(body.statements_mut()).into_vec(); @@ -605,7 +607,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b *x.2.statements_mut() = optimize_stmt_block(body, state, false, true, false).into(); } // let id = expr; - Stmt::Var(expr, _, VarDeclaration::Let, _, _) => optimize_expr(expr, state, false), + Stmt::Var(expr, _, flags, _) if !flags.contains(AST_FLAG_CONSTANT) => { + optimize_expr(expr, state, false) + } // import expr as var; #[cfg(not(feature = "no_module"))] Stmt::Import(expr, _, _) => optimize_expr(expr, state, false), diff --git a/src/parse.rs b/src/parse.rs index a87ed5c8..cdfff3ad 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -2,7 +2,7 @@ use crate::ast::{ BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ReturnType, - ScriptFnDef, Stmt, StmtBlock, VarDeclaration, + ScriptFnDef, Stmt, StmtBlock, AST_FLAGS::*, }; use crate::custom_syntax::{ CustomSyntax, CUSTOM_SYNTAX_MARKER_BLOCK, CUSTOM_SYNTAX_MARKER_BOOL, CUSTOM_SYNTAX_MARKER_EXPR, @@ -2224,9 +2224,9 @@ fn parse_do( settings.is_breakable = true; let body = parse_block(input, state, lib, settings.level_up())?; - let is_while = match input.next().expect(NEVER_ENDS) { - (Token::While, _) => true, - (Token::Until, _) => false, + let negated = match input.next().expect(NEVER_ENDS) { + (Token::While, _) => AST_FLAG_NONE, + (Token::Until, _) => AST_FLAG_NEGATED, (_, pos) => { return Err( PERR::MissingToken(Token::While.into(), "for the do statement".into()) @@ -2244,7 +2244,7 @@ fn parse_do( Ok(Stmt::Do( Box::new(body.into()), guard, - is_while, + negated, settings.pos, )) } @@ -2381,21 +2381,20 @@ fn parse_let( state.stack.push((name, var_type)); + let export = if export { + AST_FLAG_EXPORTED + } else { + AST_FLAG_NONE + }; + match var_type { // let name = expr - AccessMode::ReadWrite => Ok(Stmt::Var( - expr, - var_def.into(), - VarDeclaration::Let, - export, - settings.pos, - )), + AccessMode::ReadWrite => Ok(Stmt::Var(expr, var_def.into(), export, settings.pos)), // const name = { expr:constant } AccessMode::ReadOnly => Ok(Stmt::Var( expr, var_def.into(), - VarDeclaration::Const, - export, + AST_FLAG_CONSTANT + export, settings.pos, )), } diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 8f64d7a2..06cbb8d5 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -86,7 +86,7 @@ fn test_optimizer_parse() -> Result<(), Box> { assert_eq!( format!("{:?}", ast), - r#"AST { source: None, body: Block[Var(false @ 1:18, "DECISION" @ 1:7, Const, false, 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"# + r#"AST { source: None, body: Block[Var(false @ 1:18, "DECISION" @ 1:7, (Constant), 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"# ); let ast = engine.compile("if 1 == 2 { 42 }")?;