Use bitflags.

This commit is contained in:
Stephen Chung 2022-02-25 11:42:59 +08:00
parent f47b911681
commit 8205547d8a
14 changed files with 143 additions and 266 deletions

View File

@ -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 }

View File

@ -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).

View File

@ -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<Item = (&str, bool, Dynamic)> {
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
}

View File

@ -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<StmtBlock>),
/// func `(` expr `,` ... `)`
FnCall(Box<FnCallExpr>, Position),
/// lhs `.` rhs - boolean variable is a dummy
Dot(Box<BinaryExpr>, bool, Position),
/// lhs `[` rhs `]` - boolean indicates whether the dotting/indexing chain stops
Index(Box<BinaryExpr>, bool, Position),
/// lhs `.` rhs
Dot(Box<BinaryExpr>, ASTFlags, Position),
/// lhs `[` rhs `]`
///
/// ### Flags
///
/// [`NONE`][ASTFlags::NONE] = recurse into the indexing chain
/// [`BREAK`][ASTFlags::BREAK] = terminate the indexing chain
Index(Box<BinaryExpr>, ASTFlags, Position),
/// lhs `&&` rhs
And(Box<BinaryExpr>, Position),
/// lhs `||` rhs

View File

@ -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,
}
bitflags! {
/// _(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.
/// 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;
}
}

View File

@ -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"))]

View File

@ -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<Ident>, Expr, StmtBlock)>, Position),
/// \[`export`\] `let`|`const` id `=` expr
///
/// ### Option Flags
/// ### Flags
///
/// * [`AST_OPTION_EXPORTED`] = `export`
/// * [`AST_OPTION_CONSTANT`] = `const`
Var(
Box<(Ident, Expr, Option<NonZeroUsize>)>,
OptionFlags,
Position,
),
/// * [`EXPORTED`][ASTFlags::EXPORTED] = `export`
/// * [`CONSTANT`][ASTFlags::CONSTANT] = `const`
Var(Box<(Ident, Expr, Option<NonZeroUsize>)>, ASTFlags, Position),
/// expr op`=` expr
Assignment(Box<(Option<OpAssignment<'static>>, BinaryExpr)>, Position),
/// func `(` expr `,` ... `)`
@ -373,18 +369,18 @@ pub enum Stmt {
Expr(Box<Expr>),
/// `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<Box<Expr>>, OptionFlags, Position),
/// * [`NONE`][ASTFlags::NONE] = `return`
/// * [`BREAK`][ASTFlags::BREAK] = `throw`
Return(Option<Box<Expr>>, 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,

View File

@ -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<super::ChainArgument>,
chain_type: ChainType,
level: usize,
new_val: Option<((Dynamic, Position), (Option<OpAssignment>, 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::<crate::Map>() =>
{
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<OpAssignment>, 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<super::ChainArgument>,
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);

View File

@ -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 {

View File

@ -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")]

View File

@ -114,7 +114,7 @@ impl Namespace {
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
self.index = index
}
/// Get the [position][Position] of this [`NameSpace`].
/// Get the [position][Position] of this [`Namespace`].
///
/// # Panics
///

View File

@ -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;

View File

@ -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<Position> {
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<Expr> {
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

View File

@ -84,7 +84,7 @@ fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
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 }")?;