Change AST nodes to use bitflags for options.

This commit is contained in:
Stephen Chung 2021-08-03 22:19:25 +08:00
parent 8ea6424d50
commit 1d82a11f0b
6 changed files with 173 additions and 64 deletions

View File

@ -16,7 +16,7 @@ use std::{
hash::Hash, hash::Hash,
mem, mem,
num::{NonZeroU8, NonZeroUsize}, num::{NonZeroU8, NonZeroUsize},
ops::{Add, AddAssign, Deref, DerefMut}, ops::{Add, AddAssign, Deref, DerefMut, Not, Sub, SubAssign},
}; };
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -950,16 +950,108 @@ impl From<StmtBlock> for Stmt {
} }
} }
/// _(internals)_ Type of variable declaration. /// A type that holds a configuration option with bit-flags.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// #[derive(PartialEq, Eq, Copy, Clone, Hash, Default)]
/// # Volatile Data Structure pub struct BitOptions(u8);
///
/// This type is volatile and may change. impl BitOptions {
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] /// Does this [`BitOptions`] contain a particular option flag?
pub enum VarDeclaration { #[inline(always)]
Let, pub const fn contains(self, flag: Self) -> bool {
Const, 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. /// _(internals)_ A statement.
@ -983,11 +1075,21 @@ pub enum Stmt {
/// `while` expr `{` stmt `}` /// `while` expr `{` stmt `}`
While(Expr, Box<StmtBlock>, Position), While(Expr, Box<StmtBlock>, Position),
/// `do` `{` stmt `}` `while`|`until` expr /// `do` `{` stmt `}` `while`|`until` expr
Do(Box<StmtBlock>, 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<StmtBlock>, Expr, BitOptions, Position),
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
For(Expr, Box<(Ident, Option<Ident>, StmtBlock)>, Position), For(Expr, Box<(Ident, Option<Ident>, StmtBlock)>, Position),
/// \[`export`\] `let`/`const` id `=` expr /// \[`export`\] `let`/`const` id `=` expr
Var(Expr, Box<Ident>, 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<Ident>, BitOptions, Position),
/// expr op`=` expr /// expr op`=` expr
Assignment(Box<(Expr, Option<OpAssignment<'static>>, Expr)>, Position), Assignment(Box<(Expr, Option<OpAssignment<'static>>, Expr)>, Position),
/// func `(` expr `,` ... `)` /// func `(` expr `,` ... `)`
@ -1068,7 +1170,7 @@ impl Stmt {
| Self::Do(_, _, _, pos) | Self::Do(_, _, _, pos)
| Self::For(_, _, pos) | Self::For(_, _, pos)
| Self::Return(_, _, pos) | Self::Return(_, _, pos)
| Self::Var(_, _, _, _, pos) | Self::Var(_, _, _, pos)
| Self::TryCatch(_, pos) => *pos, | Self::TryCatch(_, pos) => *pos,
Self::Expr(x) => x.position(), Self::Expr(x) => x.position(),
@ -1097,7 +1199,7 @@ impl Stmt {
| Self::Do(_, _, _, pos) | Self::Do(_, _, _, pos)
| Self::For(_, _, pos) | Self::For(_, _, pos)
| Self::Return(_, _, pos) | Self::Return(_, _, pos)
| Self::Var(_, _, _, _, pos) | Self::Var(_, _, _, pos)
| Self::TryCatch(_, pos) => *pos = new_pos, | Self::TryCatch(_, pos) => *pos = new_pos,
Self::Expr(x) => { Self::Expr(x) => {
@ -1131,7 +1233,7 @@ impl Stmt {
| Self::For(_, _, _) | Self::For(_, _, _)
| Self::TryCatch(_, _) => false, | Self::TryCatch(_, _) => false,
Self::Var(_, _, _, _, _) Self::Var(_, _, _, _)
| Self::Assignment(_, _) | Self::Assignment(_, _)
| Self::Continue(_) | Self::Continue(_)
| Self::Break(_) | Self::Break(_)
@ -1158,7 +1260,7 @@ 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::Var(_, _, _, _, _) Self::Var(_, _, _, _)
| Self::Assignment(_, _) | Self::Assignment(_, _)
| Self::FnCall(_, _) | Self::FnCall(_, _)
| Self::Expr(_) | Self::Expr(_)
@ -1199,7 +1301,7 @@ impl Stmt {
condition.is_pure() && block.0.iter().all(Stmt::is_pure) 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::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::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
Self::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false, Self::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false,
Self::TryCatch(x, _) => { Self::TryCatch(x, _) => {
@ -1225,7 +1327,7 @@ 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(expr, _, _, _) => expr.is_pure(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Self::Import(expr, _, _) => expr.is_pure(), Self::Import(expr, _, _) => expr.is_pure(),
@ -1263,7 +1365,7 @@ impl Stmt {
} }
match self { match self {
Self::Var(e, _, _, _, _) => { Self::Var(e, _, _, _) => {
if !e.walk(path, on_node) { if !e.walk(path, on_node) {
return false; return false;
} }

View File

@ -1,6 +1,6 @@
//! Main module defining the script evaluation [`Engine`]. //! 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::custom_syntax::CustomSyntax;
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant};
use crate::fn_hash::get_hasher; use crate::fn_hash::get_hasher;
@ -2569,7 +2569,9 @@ impl Engine {
}, },
// Do loop // 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() { if !body.is_empty() {
match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level) match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level)
{ {
@ -2587,7 +2589,7 @@ impl Engine {
.as_bool() .as_bool()
.map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?; .map_err(|typ| self.make_type_mismatch_err::<bool>(typ, expr.position()))?;
if condition ^ *is_while { if condition ^ is_while {
return Ok(Dynamic::UNIT); return Ok(Dynamic::UNIT);
} }
}, },
@ -2851,12 +2853,14 @@ impl Engine {
} }
// Let/const statement // Let/const statement
Stmt::Var(expr, x, var_type, export, _) => { Stmt::Var(expr, x, flags, _) => {
let name = &x.name; let name = &x.name;
let entry_type = match var_type { let entry_type = if flags.contains(AST_FLAG_CONSTANT) {
VarDeclaration::Let => AccessMode::ReadWrite, AccessMode::ReadOnly
VarDeclaration::Const => AccessMode::ReadOnly, } else {
AccessMode::ReadWrite
}; };
let export = flags.contains(AST_FLAG_EXPORTED);
let value = self let value = self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
@ -2893,9 +2897,9 @@ impl Engine {
( (
name.to_string().into(), 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"); unreachable!("exported variable not on global level");
} else { } else {
(unsafe_cast_var_name_to_lifetime(name).into(), None) (unsafe_cast_var_name_to_lifetime(name).into(), None)

View File

@ -227,8 +227,8 @@ pub use token::{InputStream, Token, TokenizeState, TokenizerControl, TokenizerCo
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[deprecated = "this type is volatile and may change"]
pub use ast::{ pub use ast::{
ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnCallHashes, Ident, ASTNode, BinaryExpr, BitOptions, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnCallHashes,
OpAssignment, ReturnType, ScriptFnDef, Stmt, StmtBlock, VarDeclaration, Ident, OpAssignment, ReturnType, ScriptFnDef, Stmt, StmtBlock, AST_FLAGS::*,
}; };
#[cfg(feature = "internals")] #[cfg(feature = "internals")]

View File

@ -1,6 +1,6 @@
//! Module implementing the [`AST`] optimizer. //! 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::dynamic::AccessMode;
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::fn_builtin::get_builtin_binary_op_fn; use crate::fn_builtin::get_builtin_binary_op_fn;
@ -201,8 +201,9 @@ fn optimize_stmt_block(
// Optimize each statement in the block // Optimize each statement in the block
statements.iter_mut().for_each(|stmt| { statements.iter_mut().for_each(|stmt| {
match stmt { match stmt {
Stmt::Var(value_expr, x, flags, _) => {
if flags.contains(AST_FLAG_CONSTANT) {
// Add constant literals into the state // Add constant literals into the state
Stmt::Var(value_expr, x, VarDeclaration::Const, _, _) => {
optimize_expr(value_expr, state, false); optimize_expr(value_expr, state, false);
if value_expr.is_constant() { if value_expr.is_constant() {
@ -212,12 +213,12 @@ fn optimize_stmt_block(
value_expr.get_literal_value(), value_expr.get_literal_value(),
); );
} }
} } else {
// Add variables into the state // Add variables into the state
Stmt::Var(value_expr, x, VarDeclaration::Let, _, _) => {
optimize_expr(value_expr, state, false); optimize_expr(value_expr, state, false);
state.push_var(&x.name, AccessMode::ReadWrite, None); state.push_var(&x.name, AccessMode::ReadWrite, None);
} }
}
// Optimize the statement // Optimize the statement
_ => optimize_stmt(stmt, state, preserve_result), _ => optimize_stmt(stmt, state, preserve_result),
} }
@ -232,7 +233,7 @@ 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(e, _, _, _) | 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(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 } // do { block } while false | do { block } until true -> { block }
Stmt::Do(body, Expr::BoolConstant(true, _), false, _) Stmt::Do(body, Expr::BoolConstant(x, _), flags, _)
| Stmt::Do(body, Expr::BoolConstant(false, _), true, _) => { if *x == flags.contains(AST_FLAG_NEGATED) =>
{
state.set_dirty(); state.set_dirty();
let block_pos = body.position(); let block_pos = body.position();
let block = mem::take(body.statements_mut()).into_vec(); 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(); *x.2.statements_mut() = optimize_stmt_block(body, state, false, true, false).into();
} }
// let id = expr; // 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; // import expr as var;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Import(expr, _, _) => optimize_expr(expr, state, false), Stmt::Import(expr, _, _) => optimize_expr(expr, state, false),

View File

@ -2,7 +2,7 @@
use crate::ast::{ use crate::ast::{
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ReturnType, BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ReturnType,
ScriptFnDef, Stmt, StmtBlock, VarDeclaration, ScriptFnDef, Stmt, StmtBlock, AST_FLAGS::*,
}; };
use crate::custom_syntax::{ use crate::custom_syntax::{
CustomSyntax, CUSTOM_SYNTAX_MARKER_BLOCK, CUSTOM_SYNTAX_MARKER_BOOL, CUSTOM_SYNTAX_MARKER_EXPR, CustomSyntax, CUSTOM_SYNTAX_MARKER_BLOCK, CUSTOM_SYNTAX_MARKER_BOOL, CUSTOM_SYNTAX_MARKER_EXPR,
@ -2224,9 +2224,9 @@ fn parse_do(
settings.is_breakable = true; settings.is_breakable = true;
let body = parse_block(input, state, lib, settings.level_up())?; let body = parse_block(input, state, lib, settings.level_up())?;
let is_while = match input.next().expect(NEVER_ENDS) { let negated = match input.next().expect(NEVER_ENDS) {
(Token::While, _) => true, (Token::While, _) => AST_FLAG_NONE,
(Token::Until, _) => false, (Token::Until, _) => AST_FLAG_NEGATED,
(_, pos) => { (_, pos) => {
return Err( return Err(
PERR::MissingToken(Token::While.into(), "for the do statement".into()) PERR::MissingToken(Token::While.into(), "for the do statement".into())
@ -2244,7 +2244,7 @@ fn parse_do(
Ok(Stmt::Do( Ok(Stmt::Do(
Box::new(body.into()), Box::new(body.into()),
guard, guard,
is_while, negated,
settings.pos, settings.pos,
)) ))
} }
@ -2381,21 +2381,20 @@ fn parse_let(
state.stack.push((name, var_type)); state.stack.push((name, var_type));
let export = if export {
AST_FLAG_EXPORTED
} else {
AST_FLAG_NONE
};
match var_type { match var_type {
// let name = expr // let name = expr
AccessMode::ReadWrite => Ok(Stmt::Var( AccessMode::ReadWrite => Ok(Stmt::Var(expr, var_def.into(), export, settings.pos)),
expr,
var_def.into(),
VarDeclaration::Let,
export,
settings.pos,
)),
// const name = { expr:constant } // const name = { expr:constant }
AccessMode::ReadOnly => Ok(Stmt::Var( AccessMode::ReadOnly => Ok(Stmt::Var(
expr, expr,
var_def.into(), var_def.into(),
VarDeclaration::Const, AST_FLAG_CONSTANT + export,
export,
settings.pos, settings.pos,
)), )),
} }

View File

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