diff --git a/Cargo.toml b/Cargo.toml index 7e0331b9..03b3fe9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ categories = ["no-std", "embedded", "wasm", "parser-implementations"] smallvec = { version = "1.7", default-features = false, features = ["union", "const_new" ] } ahash = { version = "0.7", default-features = false } num-traits = { version = "0.2", default-features = false } +bitflags = { version = "1", default-features = false } smartstring = { version = "1", default-features = false } rhai_codegen = { version = "1.2", path = "codegen", default-features = false } diff --git a/README.md b/README.md index cf33fd5d..a9c52df4 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Standard features * Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) (including packed [byte arrays](https://rhai.rs/book/language/blobs.html)) and [object maps](https://rhai.rs/book/language/object-maps.html). * Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust. * Relatively little `unsafe` code (yes there are some for performance reasons). -* Few dependencies - currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash) and [`smartstring`](https://crates.io/crates/smartstring). +* Few dependencies - currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash), [`bitflags`](https://crates.io/crates/bitflags) and [`smartstring`](https://crates.io/crates/smartstring). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations. * Scripts are [optimized](https://rhai.rs/book/engine/optimize.html) (useful for template-based machine-generated scripts). diff --git a/src/ast/ast.rs b/src/ast/ast.rs index 40119fe5..7c0115e5 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -1,6 +1,6 @@ //! Module defining the AST (abstract syntax tree). -use super::{Expr, FnAccess, Stmt, StmtBlock, StmtBlockContainer, AST_OPTION_FLAGS::*}; +use super::{ASTFlags, Expr, FnAccess, Stmt, StmtBlock, StmtBlockContainer}; use crate::{Dynamic, FnNamespace, Identifier, Position}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -745,12 +745,12 @@ impl AST { ) -> impl Iterator { self.statements().iter().filter_map(move |stmt| match stmt { Stmt::Var(x, options, ..) - if options.contains(AST_OPTION_CONSTANT) && include_constants - || !options.contains(AST_OPTION_CONSTANT) && include_variables => + if options.contains(ASTFlags::CONSTANT) && include_constants + || !options.contains(ASTFlags::CONSTANT) && include_variables => { let (name, expr, ..) = x.as_ref(); if let Some(value) = expr.get_literal_value() { - Some((name.as_str(), options.contains(AST_OPTION_CONSTANT), value)) + Some((name.as_str(), options.contains(ASTFlags::CONSTANT), value)) } else { None } diff --git a/src/ast/expr.rs b/src/ast/expr.rs index af1fd390..9e90f8b1 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -1,6 +1,6 @@ //! Module defining script expressions. -use super::{ASTNode, Ident, Stmt, StmtBlock}; +use super::{ASTFlags, ASTNode, Ident, Stmt, StmtBlock}; use crate::engine::{KEYWORD_FN_PTR, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE}; use crate::func::hashing::ALT_ZERO_HASH; use crate::tokenizer::Token; @@ -419,10 +419,15 @@ pub enum Expr { Stmt(Box), /// func `(` expr `,` ... `)` FnCall(Box, Position), - /// lhs `.` rhs - boolean variable is a dummy - Dot(Box, bool, Position), - /// lhs `[` rhs `]` - boolean indicates whether the dotting/indexing chain stops - Index(Box, bool, Position), + /// lhs `.` rhs + Dot(Box, ASTFlags, Position), + /// lhs `[` rhs `]` + /// + /// ### Flags + /// + /// [`NONE`][ASTFlags::NONE] = recurse into the indexing chain + /// [`BREAK`][ASTFlags::BREAK] = terminate the indexing chain + Index(Box, ASTFlags, Position), /// lhs `&&` rhs And(Box, Position), /// lhs `||` rhs diff --git a/src/ast/flags.rs b/src/ast/flags.rs index 84190288..264a6374 100644 --- a/src/ast/flags.rs +++ b/src/ast/flags.rs @@ -1,6 +1,6 @@ //! Module defining script options. -use std::ops::{Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, Not, Sub, SubAssign}; +use bitflags::bitflags; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -13,163 +13,19 @@ pub enum FnAccess { Public, } -/// _(internals)_ A type that holds a configuration option with bit-flags. -/// Exported under the `internals` feature only. -/// -/// Functionality-wise, this type is a naive and simplistic implementation of -/// [`bit_flags`](https://crates.io/crates/bitflags). It is re-implemented to avoid pulling in yet -/// one more dependency. -#[derive(PartialEq, Eq, Copy, Clone, Hash, Default)] -pub struct OptionFlags(u8); - -impl OptionFlags { - /// Does this [`OptionFlags`] 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 OptionFlags { - type Output = Self; - - /// Return the negation of the [`OptionFlags`]. - #[inline(always)] - fn not(self) -> Self::Output { - Self(!self.0) & AST_OPTION_FLAGS::AST_OPTION_ALL - } -} - -impl Add for OptionFlags { - type Output = Self; - - /// Return the union of two [`OptionFlags`]. - #[inline(always)] - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 | rhs.0) - } -} - -impl AddAssign for OptionFlags { - /// Add the option flags in one [`OptionFlags`] to another. - #[inline(always)] - fn add_assign(&mut self, rhs: Self) { - self.0 |= rhs.0 - } -} - -impl BitOr for OptionFlags { - type Output = Self; - - /// Return the union of two [`OptionFlags`]. - #[inline(always)] - fn bitor(self, rhs: Self) -> Self::Output { - Self(self.0 | rhs.0) - } -} - -impl BitOrAssign for OptionFlags { - /// Add the option flags in one [`OptionFlags`] to another. - #[inline(always)] - fn bitor_assign(&mut self, rhs: Self) { - self.0 |= rhs.0 - } -} - -impl Sub for OptionFlags { - type Output = Self; - - /// Return the difference of two [`OptionFlags`]. - #[inline(always)] - fn sub(self, rhs: Self) -> Self::Output { - Self(self.0 & !rhs.0) - } -} - -impl SubAssign for OptionFlags { - /// Remove the option flags in one [`OptionFlags`] from another. - #[inline(always)] - fn sub_assign(&mut self, rhs: Self) { - self.0 &= !rhs.0 - } -} - -impl BitAnd for OptionFlags { - type Output = Self; - - /// Return the intersection of two [`OptionFlags`]. - #[inline(always)] - fn bitand(self, rhs: Self) -> Self::Output { - Self(self.0 & !rhs.0) - } -} - -impl BitAndAssign for OptionFlags { - /// Keep only the intersection of one [`OptionFlags`] with another. - #[inline(always)] - fn bitand_assign(&mut self, rhs: Self) { - self.0 &= !rhs.0 - } -} - -/// _(internals)_ Option bit-flags for [`AST`][super::AST] nodes. -/// Exported under the `internals` feature only. -#[allow(non_snake_case)] -pub mod AST_OPTION_FLAGS { - use super::OptionFlags; - - /// _(internals)_ No options for the [`AST`][crate::AST] node. +bitflags! { + /// _(internals)_ A type that holds a configuration option with bit-flags. /// Exported under the `internals` feature only. - 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_OPTION_CONSTANT: OptionFlags = OptionFlags(0b0000_0001); - /// _(internals)_ The [`AST`][crate::AST] node is exported to the outside (i.e. public). - /// Exported under the `internals` feature only. - pub const AST_OPTION_EXPORTED: OptionFlags = OptionFlags(0b0000_0010); - /// _(internals)_ The [`AST`][crate::AST] node is in negated mode - /// (meaning whatever information is the opposite). - /// 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: OptionFlags = OptionFlags(0b0000_1000); - /// _(internals)_ Mask of all options. - /// Exported under the `internals` feature only. - pub(crate) const AST_OPTION_ALL: OptionFlags = OptionFlags( - AST_OPTION_CONSTANT.0 | AST_OPTION_EXPORTED.0 | AST_OPTION_NEGATED.0 | AST_OPTION_BREAK.0, - ); - - impl std::fmt::Debug for OptionFlags { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - 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("(")?; - 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")?; - write_option(self, f, num_flags, AST_OPTION_BREAK, "Break")?; - f.write_str(")")?; - - Ok(()) - } + pub struct ASTFlags: u8 { + /// No options for the [`AST`][crate::AST] node. + const NONE = 0b0000_0000; + /// The [`AST`][crate::AST] node is read-only. + const CONSTANT = 0b0000_0001; + /// The [`AST`][crate::AST] node is exposed to the outside (i.e. public). + const EXPORTED = 0b0000_0010; + /// The [`AST`][crate::AST] node is negated (i.e. whatever information is the opposite). + const NEGATED = 0b0000_0100; + /// The [`AST`][crate::AST] node breaks out of normal control flow. + const BREAK = 0b0000_1000; } } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 3bfe2389..16be3bb9 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -9,7 +9,7 @@ pub mod stmt; pub use ast::{ASTNode, AST}; pub use expr::{BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes}; -pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS}; +pub use flags::{ASTFlags, FnAccess}; pub use ident::Ident; #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 2a4e60bc..ae6d5353 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -1,6 +1,6 @@ //! Module defining script statements. -use super::{ASTNode, BinaryExpr, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS::*}; +use super::{ASTFlags, ASTNode, BinaryExpr, Expr, FnCallExpr, Ident}; use crate::engine::KEYWORD_EVAL; use crate::tokenizer::{Span, Token}; use crate::{calc_fn_hash, Position, StaticVec, INT}; @@ -340,24 +340,20 @@ pub enum Stmt { While(Box<(Expr, StmtBlock)>, Position), /// `do` `{` stmt `}` `while`|`until` expr /// - /// ### Option Flags + /// ### Flags /// - /// * [`AST_OPTION_NONE`] = `while` - /// * [`AST_OPTION_NEGATED`] = `until` - Do(Box<(Expr, StmtBlock)>, OptionFlags, Position), + /// * [`NONE`][ASTFlags::NONE] = `while` + /// * [`NEGATED`][ASTFlags::NEGATED] = `until` + Do(Box<(Expr, StmtBlock)>, ASTFlags, Position), /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` For(Box<(Ident, Option, Expr, StmtBlock)>, Position), /// \[`export`\] `let`|`const` id `=` expr /// - /// ### Option Flags + /// ### Flags /// - /// * [`AST_OPTION_EXPORTED`] = `export` - /// * [`AST_OPTION_CONSTANT`] = `const` - Var( - Box<(Ident, Expr, Option)>, - OptionFlags, - Position, - ), + /// * [`EXPORTED`][ASTFlags::EXPORTED] = `export` + /// * [`CONSTANT`][ASTFlags::CONSTANT] = `const` + Var(Box<(Ident, Expr, Option)>, ASTFlags, Position), /// expr op`=` expr Assignment(Box<(Option>, BinaryExpr)>, Position), /// func `(` expr `,` ... `)` @@ -373,18 +369,18 @@ pub enum Stmt { Expr(Box), /// `continue`/`break` /// - /// ### Option Flags + /// ### Flags /// - /// * [`AST_OPTION_NONE`] = `continue` - /// * [`AST_OPTION_BREAK`] = `break` - BreakLoop(OptionFlags, Position), + /// * [`NONE`][ASTFlags::NONE] = `continue` + /// * [`BREAK`][ASTFlags::BREAK] = `break` + BreakLoop(ASTFlags, Position), /// `return`/`throw` /// - /// ### Option Flags + /// ### Flags /// - /// * [`AST_OPTION_NONE`] = `return` - /// * [`AST_OPTION_BREAK`] = `throw` - Return(Option>, OptionFlags, Position), + /// * [`NONE`][ASTFlags::NONE] = `return` + /// * [`BREAK`][ASTFlags::BREAK] = `throw` + Return(Option>, ASTFlags, Position), /// `import` expr `as` alias /// /// Not available under `no_module`. @@ -590,7 +586,7 @@ impl Stmt { // Loops that exit can be pure because it can never be infinite. Self::While(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => true, Self::Do(x, options, ..) if matches!(x.0, Expr::BoolConstant(..)) => match x.0 { - Expr::BoolConstant(cond, ..) if cond == options.contains(AST_OPTION_NEGATED) => { + Expr::BoolConstant(cond, ..) if cond == options.contains(ASTFlags::NEGATED) => { x.1.iter().all(Stmt::is_pure) } _ => false, diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index dcf50ab1..36ba8436 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -2,7 +2,7 @@ #![cfg(any(not(feature = "no_index"), not(feature = "no_object")))] use super::{EvalState, GlobalRuntimeState, Target}; -use crate::ast::{Expr, OpAssignment}; +use crate::ast::{ASTFlags, Expr, OpAssignment}; use crate::types::dynamic::Union; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR}; use std::hash::Hash; @@ -127,15 +127,15 @@ impl Engine { root: (&str, Position), parent: &Expr, rhs: &Expr, - terminate_chaining: bool, + parent_options: ASTFlags, idx_values: &mut StaticVec, chain_type: ChainType, level: usize, new_val: Option<((Dynamic, Position), (Option, Position))>, ) -> RhaiResultOf<(Dynamic, bool)> { let _parent = parent; + let _parent_options = parent_options; let is_ref_mut = target.is_ref(); - let _terminate_chaining = terminate_chaining; // Pop the last index value let idx_val = idx_values.pop().unwrap(); @@ -151,8 +151,8 @@ impl Engine { match rhs { // xxx[idx].expr... | xxx[idx][expr]... - Expr::Dot(x, term, x_pos) | Expr::Index(x, term, x_pos) - if !_terminate_chaining => + Expr::Dot(x, options, x_pos) | Expr::Index(x, options, x_pos) + if !_parent_options.contains(ASTFlags::BREAK) => { #[cfg(feature = "debugging")] self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?; @@ -169,7 +169,7 @@ impl Engine { let obj_ptr = &mut obj; match self.eval_dot_index_chain_helper( - global, state, lib, this_ptr, obj_ptr, root, rhs, &x.rhs, *term, + global, state, lib, this_ptr, obj_ptr, root, rhs, &x.rhs, *options, idx_values, rhs_chain, level, new_val, ) { Ok((result, true)) if is_obj_temp_val => { @@ -410,7 +410,7 @@ impl Engine { ) } // {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr - Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos) + Expr::Index(x, options, x_pos) | Expr::Dot(x, options, x_pos) if target.is::() => { let _node = &x.lhs; @@ -457,13 +457,13 @@ impl Engine { let rhs_chain = rhs.into(); self.eval_dot_index_chain_helper( - global, state, lib, this_ptr, val_target, root, rhs, &x.rhs, *term, + global, state, lib, this_ptr, val_target, root, rhs, &x.rhs, *options, idx_values, rhs_chain, level, new_val, ) .map_err(|err| err.fill_position(*x_pos)) } // xxx.sub_lhs[expr] | xxx.sub_lhs.expr - Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos) => { + Expr::Index(x, options, x_pos) | Expr::Dot(x, options, x_pos) => { let _node = &x.lhs; match x.lhs { @@ -509,7 +509,7 @@ impl Engine { let (result, may_be_changed) = self .eval_dot_index_chain_helper( global, state, lib, this_ptr, val, root, rhs, &x.rhs, - *term, idx_values, rhs_chain, level, new_val, + *options, idx_values, rhs_chain, level, new_val, ) .map_err(|err| err.fill_position(*x_pos))?; @@ -570,7 +570,7 @@ impl Engine { let val = &mut val.into(); self.eval_dot_index_chain_helper( - global, state, lib, this_ptr, val, root, rhs, &x.rhs, *term, + global, state, lib, this_ptr, val, root, rhs, &x.rhs, *options, idx_values, rhs_chain, level, new_val, ) .map_err(|err| err.fill_position(pos)) @@ -602,18 +602,18 @@ impl Engine { level: usize, new_val: Option<((Dynamic, Position), (Option, Position))>, ) -> RhaiResult { - let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, term, op_pos) = match expr { + let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, options, op_pos) = match expr { #[cfg(not(feature = "no_index"))] - Expr::Index(x, term, pos) => (x.as_ref(), ChainType::Indexing, *term, *pos), + Expr::Index(x, options, pos) => (x.as_ref(), ChainType::Indexing, *options, *pos), #[cfg(not(feature = "no_object"))] - Expr::Dot(x, term, pos) => (x.as_ref(), ChainType::Dotting, *term, *pos), + Expr::Dot(x, options, pos) => (x.as_ref(), ChainType::Dotting, *options, *pos), expr => unreachable!("Expr::Index or Expr::Dot expected but gets {:?}", expr), }; let idx_values = &mut StaticVec::new_const(); self.eval_dot_index_chain_arguments( - scope, global, state, lib, this_ptr, rhs, term, chain_type, idx_values, 0, level, + scope, global, state, lib, this_ptr, rhs, options, chain_type, idx_values, 0, level, )?; let is_assignment = new_val.is_some(); @@ -634,7 +634,7 @@ impl Engine { let root = (x.2.as_str(), *var_pos); self.eval_dot_index_chain_helper( - global, state, lib, &mut None, obj_ptr, root, expr, rhs, term, idx_values, + global, state, lib, &mut None, obj_ptr, root, expr, rhs, options, idx_values, chain_type, level, new_val, ) .map(|(v, ..)| v) @@ -648,7 +648,7 @@ impl Engine { let obj_ptr = &mut value.into(); let root = ("", expr.start_position()); self.eval_dot_index_chain_helper( - global, state, lib, this_ptr, obj_ptr, root, expr, rhs, term, idx_values, + global, state, lib, this_ptr, obj_ptr, root, expr, rhs, options, idx_values, chain_type, level, new_val, ) .map(|(v, ..)| if is_assignment { Dynamic::UNIT } else { v }) @@ -668,7 +668,7 @@ impl Engine { lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, - terminate_chaining: bool, + parent_options: ASTFlags, parent_chain_type: ChainType, idx_values: &mut StaticVec, size: usize, @@ -715,7 +715,9 @@ impl Engine { } Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"), - Expr::Index(x, term, ..) | Expr::Dot(x, term, ..) if !terminate_chaining => { + Expr::Index(x, options, ..) | Expr::Dot(x, options, ..) + if !parent_options.contains(ASTFlags::BREAK) => + { let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref(); // Evaluate in left-to-right order @@ -773,8 +775,8 @@ impl Engine { let chain_type = expr.into(); self.eval_dot_index_chain_arguments( - scope, global, state, lib, this_ptr, rhs, *term, chain_type, idx_values, size, - level, + scope, global, state, lib, this_ptr, rhs, *options, chain_type, idx_values, + size, level, )?; idx_values.push(lhs_arg_val); diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 8079cb58..a0861c88 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -3,7 +3,7 @@ use super::{EvalContext, EvalState, GlobalRuntimeState, Target}; use crate::api::events::VarDefInfo; use crate::ast::{ - BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*, + ASTFlags, BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, }; use crate::func::get_hasher; use crate::types::dynamic::{AccessMode, Union}; @@ -537,7 +537,7 @@ impl Engine { // Do 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(ASTFlags::NEGATED); if !body.is_empty() { match self @@ -700,7 +700,7 @@ impl Engine { // Continue/Break statement Stmt::BreakLoop(options, pos) => { - Err(ERR::LoopBreak(options.contains(AST_OPTION_BREAK), *pos).into()) + Err(ERR::LoopBreak(options.contains(ASTFlags::BREAK), *pos).into()) } // Try/Catch statement @@ -790,12 +790,12 @@ impl Engine { } // Throw value - Stmt::Return(Some(expr), options, pos) if options.contains(AST_OPTION_BREAK) => self + Stmt::Return(Some(expr), options, pos) if options.contains(ASTFlags::BREAK) => self .eval_expr(scope, global, state, lib, this_ptr, expr, level) .and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())), // Empty throw - Stmt::Return(None, options, pos) if options.contains(AST_OPTION_BREAK) => { + Stmt::Return(None, options, pos) if options.contains(ASTFlags::BREAK) => { Err(ERR::ErrorRuntime(Dynamic::UNIT, *pos).into()) } @@ -815,12 +815,12 @@ impl Engine { Stmt::Var(x, options, pos) => { let (Ident { name: var_name, .. }, expr, index) = x.as_ref(); - let access = if options.contains(AST_OPTION_CONSTANT) { + let access = if options.contains(ASTFlags::CONSTANT) { AccessMode::ReadOnly } else { AccessMode::ReadWrite }; - let export = options.contains(AST_OPTION_EXPORTED); + let export = options.contains(ASTFlags::EXPORTED); // Check variable definition filter let result = if let Some(ref filter) = self.def_var_filter { diff --git a/src/lib.rs b/src/lib.rs index 07612641..ad8d640a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -260,9 +260,8 @@ pub use parser::ParseState; #[cfg(feature = "internals")] pub use ast::{ - ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, - OpAssignment, OptionFlags, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock, - AST_OPTION_FLAGS, + ASTFlags, ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, + FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock, }; #[cfg(feature = "internals")] diff --git a/src/module/namespace.rs b/src/module/namespace.rs index c2977030..8ac09985 100644 --- a/src/module/namespace.rs +++ b/src/module/namespace.rs @@ -114,7 +114,7 @@ impl Namespace { pub(crate) fn set_index(&mut self, index: Option) { self.index = index } - /// Get the [position][Position] of this [`NameSpace`]. + /// Get the [position][Position] of this [`Namespace`]. /// /// # Panics /// diff --git a/src/optimizer.rs b/src/optimizer.rs index 412c0f39..d069f7e2 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -1,9 +1,7 @@ //! Module implementing the [`AST`] optimizer. #![cfg(not(feature = "no_optimize"))] -use crate::ast::{ - Expr, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, AST_OPTION_FLAGS::*, -}; +use crate::ast::{ASTFlags, Expr, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases}; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::eval::{EvalState, GlobalRuntimeState}; use crate::func::builtin::get_builtin_binary_op_fn; @@ -255,7 +253,7 @@ fn optimize_stmt_block( for stmt in statements.iter_mut() { match stmt { Stmt::Var(x, options, ..) => { - if options.contains(AST_OPTION_CONSTANT) { + if options.contains(ASTFlags::CONSTANT) { // Add constant literals into the state optimize_expr(&mut x.1, state, false); @@ -324,7 +322,7 @@ fn optimize_stmt_block( match statements[..] { // { return; } -> {} [Stmt::Return(None, options, ..)] - if reduce_return && !options.contains(AST_OPTION_BREAK) => + if reduce_return && !options.contains(ASTFlags::BREAK) => { state.set_dirty(); statements.clear(); @@ -336,7 +334,7 @@ fn optimize_stmt_block( // { ...; return; } -> { ... } [.., ref last_stmt, Stmt::Return(None, options, ..)] if reduce_return - && !options.contains(AST_OPTION_BREAK) + && !options.contains(ASTFlags::BREAK) && !last_stmt.returns_value() => { state.set_dirty(); @@ -344,7 +342,7 @@ fn optimize_stmt_block( } // { ...; return val; } -> { ...; val } [.., Stmt::Return(ref mut expr, options, pos)] - if reduce_return && !options.contains(AST_OPTION_BREAK) => + if reduce_return && !options.contains(ASTFlags::BREAK) => { state.set_dirty(); *statements.last_mut().unwrap() = expr @@ -381,7 +379,7 @@ fn optimize_stmt_block( } // { ...; return; } -> { ... } [.., Stmt::Return(None, options, ..)] - if reduce_return && !options.contains(AST_OPTION_BREAK) => + if reduce_return && !options.contains(ASTFlags::BREAK) => { state.set_dirty(); statements.pop().unwrap(); @@ -389,7 +387,7 @@ fn optimize_stmt_block( // { ...; return pure_val; } -> { ... } [.., Stmt::Return(Some(ref expr), options, ..)] if reduce_return - && !options.contains(AST_OPTION_BREAK) + && !options.contains(ASTFlags::BREAK) && expr.is_pure() => { state.set_dirty(); @@ -745,7 +743,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b if body.len() == 1 { match body[0] { // while expr { break; } -> { expr; } - Stmt::BreakLoop(options, pos) if options.contains(AST_OPTION_BREAK) => { + Stmt::BreakLoop(options, pos) if options.contains(ASTFlags::BREAK) => { // Only a single break statement - turn into running the guard expression once state.set_dirty(); if !condition.is_unit() { @@ -765,7 +763,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // do { block } until true -> { block } Stmt::Do(x, options, ..) if matches!(x.0, Expr::BoolConstant(true, ..)) - && options.contains(AST_OPTION_NEGATED) => + && options.contains(ASTFlags::NEGATED) => { state.set_dirty(); *stmt = ( @@ -777,7 +775,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // do { block } while false -> { block } Stmt::Do(x, options, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) - && !options.contains(AST_OPTION_NEGATED) => + && !options.contains(ASTFlags::NEGATED) => { state.set_dirty(); *stmt = ( @@ -797,7 +795,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b *x.3 = optimize_stmt_block(mem::take(&mut *x.3), state, false, true, false); } // let id = expr; - Stmt::Var(x, options, ..) if !options.contains(AST_OPTION_CONSTANT) => { + Stmt::Var(x, options, ..) if !options.contains(ASTFlags::CONSTANT) => { optimize_expr(&mut x.1, state, false) } // import expr as var; diff --git a/src/parser.rs b/src/parser.rs index b61653e2..bb751e4e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,9 +4,8 @@ use crate::api::custom_syntax::{markers::*, CustomSyntax}; use crate::api::events::VarDefInfo; use crate::api::options::LanguageOptions; use crate::ast::{ - BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, + ASTFlags, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, TryCatchBlock, - AST_OPTION_FLAGS::*, }; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; use crate::eval::{EvalState, GlobalRuntimeState}; @@ -764,14 +763,14 @@ fn parse_index_chain( // Indexing binds to right Ok(Expr::Index( BinaryExpr { lhs, rhs: idx_expr }.into(), - false, + ASTFlags::NONE, prev_pos, )) } // Otherwise terminate the indexing chain _ => Ok(Expr::Index( BinaryExpr { lhs, rhs: idx_expr }.into(), - true, + ASTFlags::BREAK, settings.pos, )), } @@ -1599,7 +1598,7 @@ fn parse_postfix( } let rhs = parse_primary(input, state, lib, settings.level_up())?; - make_dot_expr(state, expr, false, rhs, tail_pos)? + make_dot_expr(state, expr, ASTFlags::NONE, rhs, tail_pos)? } // Unknown postfix operator (expr, token) => unreachable!( @@ -1764,15 +1763,20 @@ fn make_assignment_stmt( #[must_use] fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option { match expr { - Expr::Index(x, term, ..) | Expr::Dot(x, term, ..) if parent_is_dot => match x.lhs { - Expr::Property(..) if !term => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(..))), + Expr::Index(x, options, ..) | Expr::Dot(x, options, ..) if parent_is_dot => match x.lhs + { + Expr::Property(..) if !options.contains(ASTFlags::BREAK) => { + check_lvalue(&x.rhs, matches!(expr, Expr::Dot(..))) + } Expr::Property(..) => None, // Anything other than a property after dotting (e.g. a method call) is not an l-value ref e => Some(e.position()), }, - Expr::Index(x, term, ..) | Expr::Dot(x, term, ..) => match x.lhs { + Expr::Index(x, options, ..) | Expr::Dot(x, options, ..) => match x.lhs { Expr::Property(..) => unreachable!("unexpected Expr::Property in indexing"), - _ if !term => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(..))), + _ if !options.contains(ASTFlags::BREAK) => { + check_lvalue(&x.rhs, matches!(expr, Expr::Dot(..))) + } _ => None, }, Expr::Property(..) if parent_is_dot => None, @@ -1817,8 +1821,8 @@ fn make_assignment_stmt( } } // xxx[???]... = rhs, xxx.prop... = rhs - Expr::Index(ref x, term, ..) | Expr::Dot(ref x, term, ..) => { - let valid_lvalue = if term { + Expr::Index(ref x, options, ..) | Expr::Dot(ref x, options, ..) => { + let valid_lvalue = if options.contains(ASTFlags::BREAK) { None } else { check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(..))) @@ -1890,15 +1894,15 @@ fn parse_op_assignment_stmt( fn make_dot_expr( state: &mut ParseState, lhs: Expr, - terminate_chaining: bool, + parent_options: ASTFlags, rhs: Expr, op_pos: Position, ) -> ParseResult { match (lhs, rhs) { // lhs[idx_expr].rhs - (Expr::Index(mut x, term, pos), rhs) => { - x.rhs = make_dot_expr(state, x.rhs, term || terminate_chaining, rhs, op_pos)?; - Ok(Expr::Index(x, false, pos)) + (Expr::Index(mut x, options, pos), rhs) => { + x.rhs = make_dot_expr(state, x.rhs, options | parent_options, rhs, op_pos)?; + Ok(Expr::Index(x, ASTFlags::NONE, pos)) } // lhs.module::id - syntax error #[cfg(not(feature = "no_module"))] @@ -1908,12 +1912,16 @@ fn make_dot_expr( // lhs.id (lhs, var_expr @ Expr::Variable(..)) => { let rhs = var_expr.into_property(state); - Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos)) + Ok(Expr::Dot( + BinaryExpr { lhs, rhs }.into(), + ASTFlags::NONE, + op_pos, + )) } // lhs.prop (lhs, prop @ Expr::Property(..)) => Ok(Expr::Dot( BinaryExpr { lhs, rhs: prop }.into(), - false, + ASTFlags::NONE, op_pos, )), // lhs.nnn::func(...) - syntax error @@ -1950,7 +1958,11 @@ fn make_dot_expr( ); let rhs = Expr::FnCall(func, func_pos); - Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos)) + Ok(Expr::Dot( + BinaryExpr { lhs, rhs }.into(), + ASTFlags::NONE, + op_pos, + )) } // lhs.dot_lhs.dot_rhs or lhs.dot_lhs[idx_rhs] (lhs, rhs @ Expr::Dot(..)) | (lhs, rhs @ Expr::Index(..)) => { @@ -1984,7 +1996,11 @@ fn make_dot_expr( } else { Expr::Index(new_lhs, term, pos) }; - Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos)) + Ok(Expr::Dot( + BinaryExpr { lhs, rhs }.into(), + ASTFlags::NONE, + op_pos, + )) } // lhs.func().dot_rhs or lhs.func()[idx_rhs] Expr::FnCall(mut func, func_pos) => { @@ -2006,7 +2022,11 @@ fn make_dot_expr( } else { Expr::Index(new_lhs, term, pos) }; - Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos)) + Ok(Expr::Dot( + BinaryExpr { lhs, rhs }.into(), + ASTFlags::NONE, + op_pos, + )) } expr => unreachable!("invalid dot expression: {:?}", expr), } @@ -2482,8 +2502,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_OPTION_NONE, - (Token::Until, ..) => AST_OPTION_NEGATED, + (Token::While, ..) => ASTFlags::NONE, + (Token::Until, ..) => ASTFlags::NEGATED, (.., pos) => { return Err( PERR::MissingToken(Token::While.into(), "for the do statement".into()) @@ -2657,9 +2677,9 @@ fn parse_let( }; let export = if is_export { - AST_OPTION_EXPORTED + ASTFlags::EXPORTED } else { - AST_OPTION_NONE + ASTFlags::NONE }; let existing = state.stack.get_index(&name).and_then(|(n, ..)| { @@ -2685,7 +2705,7 @@ fn parse_let( // let name = expr AccessMode::ReadWrite => Stmt::Var(var_def, export, settings.pos), // const name = { expr:constant } - AccessMode::ReadOnly => Stmt::Var(var_def, AST_OPTION_CONSTANT + export, settings.pos), + AccessMode::ReadOnly => Stmt::Var(var_def, ASTFlags::CONSTANT | export, settings.pos), }) } @@ -3044,11 +3064,11 @@ fn parse_stmt( Token::Continue if settings.default_options.allow_looping && settings.is_breakable => { let pos = eat_token(input, Token::Continue); - Ok(Stmt::BreakLoop(AST_OPTION_NONE, pos)) + Ok(Stmt::BreakLoop(ASTFlags::NONE, pos)) } Token::Break if settings.default_options.allow_looping && settings.is_breakable => { let pos = eat_token(input, Token::Break); - Ok(Stmt::BreakLoop(AST_OPTION_BREAK, pos)) + Ok(Stmt::BreakLoop(ASTFlags::BREAK, pos)) } Token::Continue | Token::Break if settings.default_options.allow_looping => { Err(PERR::LoopBreak.into_err(token_pos)) @@ -3059,8 +3079,8 @@ fn parse_stmt( .next() .map(|(token, pos)| { let flags = match token { - Token::Return => AST_OPTION_NONE, - Token::Throw => AST_OPTION_BREAK, + Token::Return => ASTFlags::NONE, + Token::Throw => ASTFlags::BREAK, token => unreachable!( "Token::Return or Token::Throw expected but gets {:?}", token diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 886b4fff..351d91e5 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -84,7 +84,7 @@ fn test_optimizer_parse() -> Result<(), Box> { assert_eq!( format!("{:?}", ast), - r#"AST { body: [Var(("DECISION" @ 1:7, false @ 1:18, None), (Constant), 1:1), Expr(123 @ 1:51)] }"# + r#"AST { body: [Var(("DECISION" @ 1:7, false @ 1:18, None), CONSTANT, 1:1), Expr(123 @ 1:51)] }"# ); let ast = engine.compile("if 1 == 2 { 42 }")?;