From a05142d4e98630e8dc29912e87f8ad8f416f515c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 1 Aug 2021 22:22:00 +0800 Subject: [PATCH 1/7] Change Rhai dependency to 1. --- codegen/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index df705eb1..b950aff0 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -16,7 +16,7 @@ default = [] metadata = [] [dev-dependencies] -rhai = { path = "..", version = "1.0" } +rhai = { path = "..", version = "1" } trybuild = "1" [dependencies] From 8ea6424d50e200b335fadaa3a3900b825ac80f41 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 2 Aug 2021 10:16:28 +0800 Subject: [PATCH 2/7] Rename syntax to custom_syntax. --- tests/{syntax.rs => custom_syntax.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{syntax.rs => custom_syntax.rs} (100%) diff --git a/tests/syntax.rs b/tests/custom_syntax.rs similarity index 100% rename from tests/syntax.rs rename to tests/custom_syntax.rs From 1d82a11f0b015b12651c8d0d9c4b8acd28f46f4c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 3 Aug 2021 22:19:25 +0800 Subject: [PATCH 3/7] Change AST nodes to use bitflags for options. --- src/ast.rs | 140 +++++++++++++++++++++++++++++++++++++++------ src/engine.rs | 22 ++++--- src/lib.rs | 4 +- src/optimize.rs | 42 ++++++++------ src/parse.rs | 27 +++++---- tests/optimizer.rs | 2 +- 6 files changed, 173 insertions(+), 64 deletions(-) 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 }")?; From 81770f00e072a6f2f10f26270e5b16caaaa95847 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Aug 2021 11:16:11 +0800 Subject: [PATCH 4/7] Rename variables and constants for bit flags. --- src/ast.rs | 81 +++++++++++++++++++++++++------------------------ src/engine.rs | 12 ++++---- src/lib.rs | 4 +-- src/optimize.rs | 12 ++++---- src/parse.rs | 12 ++++---- 5 files changed, 61 insertions(+), 60 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 6b0e4c76..daffd793 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -953,25 +953,27 @@ impl From for Stmt { /// A type that holds a configuration option with bit-flags. /// Exported under the `internals` feature only. #[derive(PartialEq, Eq, Copy, Clone, Hash, Default)] -pub struct BitOptions(u8); +pub struct OptionFlags(u8); -impl BitOptions { +impl OptionFlags { /// Does this [`BitOptions`] contain a particular option flag? #[inline(always)] + #[must_use] pub const fn contains(self, flag: Self) -> bool { self.0 & flag.0 != 0 } } -impl Not for BitOptions { +impl Not for OptionFlags { type Output = Self; + #[inline(always)] fn not(self) -> Self::Output { Self(!self.0) } } -impl Add for BitOptions { +impl Add for OptionFlags { type Output = Self; #[inline(always)] @@ -980,14 +982,14 @@ impl Add for BitOptions { } } -impl AddAssign for BitOptions { +impl AddAssign for OptionFlags { #[inline(always)] fn add_assign(&mut self, rhs: Self) { self.0 |= rhs.0 } } -impl Sub for BitOptions { +impl Sub for OptionFlags { type Output = Self; #[inline(always)] @@ -996,7 +998,7 @@ impl Sub for BitOptions { } } -impl SubAssign for BitOptions { +impl SubAssign for OptionFlags { #[inline(always)] fn sub_assign(&mut self, rhs: Self) { self.0 &= !rhs.0 @@ -1005,48 +1007,47 @@ impl SubAssign for BitOptions { /// Option bit-flags for [`AST`] nodes. #[allow(non_snake_case)] -pub mod AST_FLAGS { - use super::BitOptions; +pub mod AST_OPTION_FLAGS { + use super::OptionFlags; /// _(internals)_ No options for the [`AST`][crate::AST] node. /// Exported under the `internals` feature only. - pub const AST_FLAG_NONE: BitOptions = BitOptions(0b0000_0000); + pub const AST_OPTION_NONE: OptionFlags = OptionFlags(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); + pub const AST_OPTION_CONSTANT: OptionFlags = OptionFlags(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); + pub const AST_OPTION_EXPORTED: OptionFlags = OptionFlags(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); + pub const AST_OPTION_NEGATED: OptionFlags = OptionFlags(0b0000_0100); - impl std::fmt::Debug for BitOptions { + impl std::fmt::Debug for OptionFlags { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut has_flags = false; + fn write_option( + options: &OptionFlags, + f: &mut std::fmt::Formatter<'_>, + num_flags: &mut usize, + flag: OptionFlags, + name: &str, + ) -> std::fmt::Result { + if options.contains(flag) { + if *num_flags > 0 { + f.write_str("+")?; + } + f.write_str(name)?; + *num_flags += 1; + } + Ok(()) + } + + let num_flags = &mut 0; 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; - } + 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_NEGATED, "Negated")?; f.write_str(")")?; Ok(()) @@ -1076,20 +1077,20 @@ pub enum Stmt { While(Expr, Box, Position), /// `do` `{` stmt `}` `while`|`until` expr /// - /// ### Option flags + /// ### 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), + Do(Box, Expr, OptionFlags, Position), /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` For(Expr, Box<(Ident, Option, StmtBlock)>, Position), /// \[`export`\] `let`/`const` id `=` expr /// - /// ### Option flags + /// ### 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), + Var(Expr, Box, OptionFlags, Position), /// expr op`=` expr Assignment(Box<(Expr, Option>, Expr)>, Position), /// func `(` expr `,` ... `)` diff --git a/src/engine.rs b/src/engine.rs index 256c0aa7..cdba7f9f 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_FLAGS::*}; +use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, ReturnType, 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; @@ -2569,8 +2569,8 @@ impl Engine { }, // Do loop - Stmt::Do(body, expr, flags, _) => loop { - let is_while = !flags.contains(AST_FLAG_NEGATED); + Stmt::Do(body, expr, options, _) => loop { + let is_while = !options.contains(AST_OPTION_NEGATED); if !body.is_empty() { match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level) @@ -2853,14 +2853,14 @@ impl Engine { } // Let/const statement - Stmt::Var(expr, x, flags, _) => { + Stmt::Var(expr, x, options, _) => { let name = &x.name; - let entry_type = if flags.contains(AST_FLAG_CONSTANT) { + let entry_type = if options.contains(AST_OPTION_CONSTANT) { AccessMode::ReadOnly } else { AccessMode::ReadWrite }; - let export = flags.contains(AST_FLAG_EXPORTED); + let export = options.contains(AST_OPTION_EXPORTED); let value = self .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? diff --git a/src/lib.rs b/src/lib.rs index 1d103f43..614a8fb8 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, BitOptions, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnCallHashes, - Ident, OpAssignment, ReturnType, ScriptFnDef, Stmt, StmtBlock, AST_FLAGS::*, + ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnCallHashes, Ident, + OpAssignment, OptionFlags, ReturnType, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*, }; #[cfg(feature = "internals")] diff --git a/src/optimize.rs b/src/optimize.rs index 13883603..3376240e 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,6 +1,6 @@ //! Module implementing the [`AST`] optimizer. -use crate::ast::{Expr, OpAssignment, Stmt, AST_FLAGS::*}; +use crate::ast::{Expr, OpAssignment, Stmt, AST_OPTION_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,8 +201,8 @@ fn optimize_stmt_block( // Optimize each statement in the block statements.iter_mut().for_each(|stmt| { match stmt { - Stmt::Var(value_expr, x, flags, _) => { - if flags.contains(AST_FLAG_CONSTANT) { + Stmt::Var(value_expr, x, options, _) => { + if options.contains(AST_OPTION_CONSTANT) { // Add constant literals into the state optimize_expr(value_expr, state, false); @@ -583,8 +583,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } } // do { block } while false | do { block } until true -> { block } - Stmt::Do(body, Expr::BoolConstant(x, _), flags, _) - if *x == flags.contains(AST_FLAG_NEGATED) => + Stmt::Do(body, Expr::BoolConstant(x, _), options, _) + if *x == options.contains(AST_OPTION_NEGATED) => { state.set_dirty(); let block_pos = body.position(); @@ -607,7 +607,7 @@ 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, _, flags, _) if !flags.contains(AST_FLAG_CONSTANT) => { + Stmt::Var(expr, _, options, _) if !options.contains(AST_OPTION_CONSTANT) => { optimize_expr(expr, state, false) } // import expr as var; diff --git a/src/parse.rs b/src/parse.rs index cdfff3ad..777020d8 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, AST_FLAGS::*, + ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*, }; use crate::custom_syntax::{ CustomSyntax, CUSTOM_SYNTAX_MARKER_BLOCK, CUSTOM_SYNTAX_MARKER_BOOL, CUSTOM_SYNTAX_MARKER_EXPR, @@ -2225,8 +2225,8 @@ fn parse_do( let body = parse_block(input, state, lib, settings.level_up())?; let negated = match input.next().expect(NEVER_ENDS) { - (Token::While, _) => AST_FLAG_NONE, - (Token::Until, _) => AST_FLAG_NEGATED, + (Token::While, _) => AST_OPTION_NONE, + (Token::Until, _) => AST_OPTION_NEGATED, (_, pos) => { return Err( PERR::MissingToken(Token::While.into(), "for the do statement".into()) @@ -2382,9 +2382,9 @@ fn parse_let( state.stack.push((name, var_type)); let export = if export { - AST_FLAG_EXPORTED + AST_OPTION_EXPORTED } else { - AST_FLAG_NONE + AST_OPTION_NONE }; match var_type { @@ -2394,7 +2394,7 @@ fn parse_let( AccessMode::ReadOnly => Ok(Stmt::Var( expr, var_def.into(), - AST_FLAG_CONSTANT + export, + AST_OPTION_CONSTANT + export, settings.pos, )), } From 4807fdf1cf2a43d2cccfc29f21725e49e1da0cb0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Aug 2021 17:37:56 +0800 Subject: [PATCH 5/7] Loops cannot be pure. --- CHANGELOG.md | 1 + src/ast.rs | 22 ++++++++++++++++++---- tests/while_loop.rs | 13 +++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f06be45..a7ac4d8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug fixes --------- * Fixed bug in using indexing/dotting inside index bracket. +* `while` and `loop` statements are no longer considered _pure_ (since a loop can go on forever and this is a side effect). Version 1.0.0 diff --git a/src/ast.rs b/src/ast.rs index daffd793..cb366bb7 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1073,7 +1073,9 @@ pub enum Stmt { Box<(BTreeMap, StmtBlock)>>, StmtBlock)>, Position, ), - /// `while` expr `{` stmt `}` + /// `while` expr `{` stmt `}` | `loop` `{` stmt `}` + /// + /// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement. While(Expr, Box, Position), /// `do` `{` stmt `}` `while`|`until` expr /// @@ -1084,7 +1086,7 @@ pub enum Stmt { Do(Box, Expr, OptionFlags, Position), /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` For(Expr, Box<(Ident, Option, StmtBlock)>, Position), - /// \[`export`\] `let`/`const` id `=` expr + /// \[`export`\] `let`|`const` id `=` expr /// /// ### Option Flags /// @@ -1298,10 +1300,22 @@ impl Stmt { }) && (x.1).0.iter().all(Stmt::is_pure) } - Self::While(condition, block, _) | Self::Do(block, condition, _, _) => { - condition.is_pure() && block.0.iter().all(Stmt::is_pure) + + // Loops that exit can be pure because it can never be infinite. + Self::While(Expr::BoolConstant(false, _), _, _) => true, + Self::Do(body, Expr::BoolConstant(x, _), options, _) + if *x == options.contains(AST_OPTION_FLAGS::AST_OPTION_NEGATED) => + { + body.iter().all(Stmt::is_pure) } + + // Loops are never pure since they can be infinite - and that's a side effect. + Self::While(_, _, _) | Self::Do(_, _, _, _) => false, + + // For loops can be pure because if the iterable is pure, it is finite, + // so infinite loops can never occur. Self::For(iterable, x, _) => iterable.is_pure() && (x.2).0.iter().all(Stmt::is_pure), + Self::Var(_, _, _, _) | Self::Assignment(_, _) | Self::FnCall(_, _) => false, Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), Self::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false, diff --git a/tests/while_loop.rs b/tests/while_loop.rs index 222ce7b1..2d412c4b 100644 --- a/tests/while_loop.rs +++ b/tests/while_loop.rs @@ -49,3 +49,16 @@ fn test_do() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_infinite_loops() -> Result<(), Box> { + let mut engine = Engine::new(); + + engine.set_max_operations(1024); + + assert!(engine.consume("loop {}").is_err()); + assert!(engine.consume("while true {}").is_err()); + assert!(engine.consume("do {} while true").is_err()); + + Ok(()) +} From 9b56c1ba7809716641f6ba172aa82c267de3a326 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Aug 2021 17:40:26 +0800 Subject: [PATCH 6/7] Optimize loops better. --- src/engine.rs | 29 ++++++++++++++++++++++------- src/optimize.rs | 3 +++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index cdba7f9f..d72b2dac 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2542,15 +2542,30 @@ impl Engine { }) } + // Loop + Stmt::While(Expr::Unit(_), body, _) => loop { + if !body.is_empty() { + match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level) + { + Ok(_) => (), + Err(err) => match *err { + EvalAltResult::LoopBreak(false, _) => (), + EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT), + _ => return Err(err), + }, + } + } else { + #[cfg(not(feature = "unchecked"))] + self.inc_operations(state, body.position())?; + } + }, + // While loop Stmt::While(expr, body, _) => loop { - let condition = if !expr.is_unit() { - self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? - .as_bool() - .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))? - } else { - true - }; + let condition = self + .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? + .as_bool() + .map_err(|typ| self.make_type_mismatch_err::(typ, expr.position()))?; if !condition { return Ok(Dynamic::UNIT); diff --git a/src/optimize.rs b/src/optimize.rs index 3376240e..08f3fadd 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -559,6 +559,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // while expr { block } Stmt::While(condition, body, _) => { optimize_expr(condition, state, false); + 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(); From 278b840e138ed57f30a823b9ef1909b186770135 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Aug 2021 18:57:52 +0800 Subject: [PATCH 7/7] Fix unchecked build. --- tests/while_loop.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/while_loop.rs b/tests/while_loop.rs index 2d412c4b..7507242b 100644 --- a/tests/while_loop.rs +++ b/tests/while_loop.rs @@ -50,6 +50,7 @@ fn test_do() -> Result<(), Box> { Ok(()) } +#[cfg(not(feature = "unchecked"))] #[test] fn test_infinite_loops() -> Result<(), Box> { let mut engine = Engine::new();