Factor tuples into structs to aid in understanding.
This commit is contained in:
parent
b86dd3f586
commit
20baae71d4
@ -9,6 +9,11 @@ Bug fixes
|
|||||||
|
|
||||||
* In `Scope::clone_visible`, constants are now properly cloned as constants.
|
* In `Scope::clone_visible`, constants are now properly cloned as constants.
|
||||||
|
|
||||||
|
Script-breaking changes
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
* For consistency, the `export` statement no longer exports multiple variables.
|
||||||
|
|
||||||
New features
|
New features
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -33,6 +33,16 @@ pub struct BinaryExpr {
|
|||||||
pub rhs: Expr,
|
pub rhs: Expr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<(Expr, Expr)> for BinaryExpr {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: (Expr, Expr)) -> Self {
|
||||||
|
Self {
|
||||||
|
lhs: value.0,
|
||||||
|
rhs: value.1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// _(internals)_ A custom syntax expression.
|
/// _(internals)_ A custom syntax expression.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
@ -362,11 +372,8 @@ pub enum Expr {
|
|||||||
),
|
),
|
||||||
/// Property access - ((getter, hash), (setter, hash), prop)
|
/// Property access - ((getter, hash), (setter, hash), prop)
|
||||||
Property(
|
Property(
|
||||||
Box<(
|
Box<((Identifier, u64), (Identifier, u64), ImmutableString)>,
|
||||||
(Identifier, u64),
|
Position,
|
||||||
(Identifier, u64),
|
|
||||||
(ImmutableString, Position),
|
|
||||||
)>,
|
|
||||||
),
|
),
|
||||||
/// Stack slot for function calls. See [`FnCallExpr`] for more details.
|
/// Stack slot for function calls. See [`FnCallExpr`] for more details.
|
||||||
///
|
///
|
||||||
@ -436,7 +443,7 @@ impl fmt::Debug for Expr {
|
|||||||
}
|
}
|
||||||
f.write_str(")")
|
f.write_str(")")
|
||||||
}
|
}
|
||||||
Self::Property(x) => write!(f, "Property({})", (x.2).0),
|
Self::Property(x, _) => write!(f, "Property({})", x.2),
|
||||||
Self::Stack(x, _) => write!(f, "StackSlot({})", x),
|
Self::Stack(x, _) => write!(f, "StackSlot({})", x),
|
||||||
Self::Stmt(x) => {
|
Self::Stmt(x) => {
|
||||||
f.write_str("ExprStmtBlock")?;
|
f.write_str("ExprStmtBlock")?;
|
||||||
@ -648,9 +655,9 @@ impl Expr {
|
|||||||
| Self::FnCall(_, pos)
|
| Self::FnCall(_, pos)
|
||||||
| Self::Index(_, _, pos)
|
| Self::Index(_, _, pos)
|
||||||
| Self::Custom(_, pos)
|
| Self::Custom(_, pos)
|
||||||
| Self::InterpolatedString(_, pos) => *pos,
|
| Self::InterpolatedString(_, pos)
|
||||||
|
| Self::Property(_, pos) => *pos,
|
||||||
|
|
||||||
Self::Property(x) => (x.2).1,
|
|
||||||
Self::Stmt(x) => x.position(),
|
Self::Stmt(x) => x.position(),
|
||||||
|
|
||||||
Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _, _) => x.lhs.position(),
|
Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _, _) => x.lhs.position(),
|
||||||
@ -679,9 +686,9 @@ impl Expr {
|
|||||||
| Self::Stack(_, pos)
|
| Self::Stack(_, pos)
|
||||||
| Self::FnCall(_, pos)
|
| Self::FnCall(_, pos)
|
||||||
| Self::Custom(_, pos)
|
| Self::Custom(_, pos)
|
||||||
| Self::InterpolatedString(_, pos) => *pos = new_pos,
|
| Self::InterpolatedString(_, pos)
|
||||||
|
| Self::Property(_, pos) => *pos = new_pos,
|
||||||
|
|
||||||
Self::Property(x) => (x.2).1 = new_pos,
|
|
||||||
Self::Stmt(x) => x.set_position(new_pos),
|
Self::Stmt(x) => x.set_position(new_pos),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -781,7 +788,7 @@ impl Expr {
|
|||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
|
||||||
Self::Property(_) => match token {
|
Self::Property(_, _) => match token {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Token::LeftBracket => true,
|
Token::LeftBracket => true,
|
||||||
Token::LeftParen => true,
|
Token::LeftParen => true,
|
||||||
|
@ -13,7 +13,7 @@ pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS};
|
|||||||
pub use ident::Ident;
|
pub use ident::Ident;
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
|
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
|
||||||
pub use stmt::{OpAssignment, Stmt, StmtBlock};
|
pub use stmt::{ConditionalStmtBlock, OpAssignment, Stmt, StmtBlock, SwitchCases, TryCatchBlock};
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
pub use expr::FloatWrapper;
|
pub use expr::FloatWrapper;
|
||||||
|
146
src/ast/stmt.rs
146
src/ast/stmt.rs
@ -1,6 +1,6 @@
|
|||||||
//! Module defining script statements.
|
//! Module defining script statements.
|
||||||
|
|
||||||
use super::{ASTNode, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS::*};
|
use super::{ASTNode, BinaryExpr, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS::*};
|
||||||
use crate::engine::KEYWORD_EVAL;
|
use crate::engine::KEYWORD_EVAL;
|
||||||
use crate::tokenizer::Token;
|
use crate::tokenizer::Token;
|
||||||
use crate::{calc_fn_hash, Position, StaticVec, INT};
|
use crate::{calc_fn_hash, Position, StaticVec, INT};
|
||||||
@ -79,6 +79,58 @@ impl OpAssignment<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A statements block with an optional condition.
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct ConditionalStmtBlock {
|
||||||
|
/// Optional condition.
|
||||||
|
pub condition: Option<Expr>,
|
||||||
|
/// Statements block.
|
||||||
|
pub statements: StmtBlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Into<StmtBlock>> From<(Option<Expr>, B)> for ConditionalStmtBlock {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: (Option<Expr>, B)) -> Self {
|
||||||
|
Self {
|
||||||
|
condition: value.0,
|
||||||
|
statements: value.1.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConditionalStmtBlock {
|
||||||
|
/// Does the condition exist?
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn has_condition(&self) -> bool {
|
||||||
|
self.condition.is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// _(internals)_ A type containing all cases for a `switch` statement.
|
||||||
|
/// Exported under the `internals` feature only.
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct SwitchCases {
|
||||||
|
/// Dictionary mapping value hashes to [`ConditionalStmtBlock`]'s.
|
||||||
|
pub cases: BTreeMap<u64, Box<ConditionalStmtBlock>>,
|
||||||
|
/// Statements block for the default case (there can be no condition for the default case).
|
||||||
|
pub def_case: StmtBlock,
|
||||||
|
/// List of range cases.
|
||||||
|
pub ranges: StaticVec<(INT, INT, bool, ConditionalStmtBlock)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// _(internals)_ A `try-catch` block.
|
||||||
|
/// Exported under the `internals` feature only.
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct TryCatchBlock {
|
||||||
|
/// `try` block.
|
||||||
|
pub try_block: StmtBlock,
|
||||||
|
/// `catch` variable, if any.
|
||||||
|
pub catch_var: Option<Ident>,
|
||||||
|
/// `catch` block.
|
||||||
|
pub catch_block: StmtBlock,
|
||||||
|
}
|
||||||
|
|
||||||
/// _(internals)_ A scoped block of statements.
|
/// _(internals)_ A scoped block of statements.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
#[derive(Clone, Hash, Default)]
|
#[derive(Clone, Hash, Default)]
|
||||||
@ -160,6 +212,20 @@ impl DerefMut for StmtBlock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<[Stmt]> for StmtBlock {
|
||||||
|
#[inline(always)]
|
||||||
|
fn as_ref(&self) -> &[Stmt] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsMut<[Stmt]> for StmtBlock {
|
||||||
|
#[inline(always)]
|
||||||
|
fn as_mut(&mut self) -> &mut [Stmt] {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Debug for StmtBlock {
|
impl fmt::Debug for StmtBlock {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str("Block")?;
|
f.write_str("Block")?;
|
||||||
@ -214,15 +280,7 @@ pub enum Stmt {
|
|||||||
/// 0) Hash table for (condition, block)
|
/// 0) Hash table for (condition, block)
|
||||||
/// 1) Default block
|
/// 1) Default block
|
||||||
/// 2) List of ranges: (start, end, inclusive, condition, statement)
|
/// 2) List of ranges: (start, end, inclusive, condition, statement)
|
||||||
Switch(
|
Switch(Expr, Box<SwitchCases>, Position),
|
||||||
Expr,
|
|
||||||
Box<(
|
|
||||||
BTreeMap<u64, Box<(Option<Expr>, StmtBlock)>>,
|
|
||||||
StmtBlock,
|
|
||||||
StaticVec<(INT, INT, bool, Option<Expr>, StmtBlock)>,
|
|
||||||
)>,
|
|
||||||
Position,
|
|
||||||
),
|
|
||||||
/// `while` expr `{` stmt `}` | `loop` `{` stmt `}`
|
/// `while` expr `{` stmt `}` | `loop` `{` stmt `}`
|
||||||
///
|
///
|
||||||
/// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement.
|
/// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement.
|
||||||
@ -244,7 +302,7 @@ pub enum Stmt {
|
|||||||
/// * [`AST_OPTION_CONSTANT`] = `const`
|
/// * [`AST_OPTION_CONSTANT`] = `const`
|
||||||
Var(Expr, Box<Ident>, OptionFlags, Position),
|
Var(Expr, Box<Ident>, OptionFlags, Position),
|
||||||
/// expr op`=` expr
|
/// expr op`=` expr
|
||||||
Assignment(Box<(Expr, Option<OpAssignment<'static>>, Expr)>, Position),
|
Assignment(Box<(Option<OpAssignment<'static>>, BinaryExpr)>, Position),
|
||||||
/// func `(` expr `,` ... `)`
|
/// func `(` expr `,` ... `)`
|
||||||
///
|
///
|
||||||
/// Note - this is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single
|
/// Note - this is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single
|
||||||
@ -253,7 +311,7 @@ pub enum Stmt {
|
|||||||
/// `{` stmt`;` ... `}`
|
/// `{` stmt`;` ... `}`
|
||||||
Block(Box<[Stmt]>, Position),
|
Block(Box<[Stmt]>, Position),
|
||||||
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
||||||
TryCatch(Box<(StmtBlock, Option<Ident>, StmtBlock)>, Position),
|
TryCatch(Box<TryCatchBlock>, Position),
|
||||||
/// [expression][Expr]
|
/// [expression][Expr]
|
||||||
Expr(Expr),
|
Expr(Expr),
|
||||||
/// `continue`/`break`
|
/// `continue`/`break`
|
||||||
@ -275,11 +333,11 @@ pub enum Stmt {
|
|||||||
/// Not available under `no_module`.
|
/// Not available under `no_module`.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Import(Expr, Option<Box<Ident>>, Position),
|
Import(Expr, Option<Box<Ident>>, Position),
|
||||||
/// `export` var `as` var `,` ...
|
/// `export` var `as` var
|
||||||
///
|
///
|
||||||
/// Not available under `no_module`.
|
/// Not available under `no_module`.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Export(Box<[(Ident, Ident)]>, Position),
|
Export(Box<(Ident, Ident)>, Position),
|
||||||
/// Convert a variable to shared.
|
/// Convert a variable to shared.
|
||||||
///
|
///
|
||||||
/// Not available under `no_closure`.
|
/// Not available under `no_closure`.
|
||||||
@ -443,20 +501,20 @@ impl Stmt {
|
|||||||
Self::Expr(expr) => expr.is_pure(),
|
Self::Expr(expr) => expr.is_pure(),
|
||||||
Self::If(condition, x, _) => {
|
Self::If(condition, x, _) => {
|
||||||
condition.is_pure()
|
condition.is_pure()
|
||||||
&& (x.0).0.iter().all(Stmt::is_pure)
|
&& x.0.iter().all(Stmt::is_pure)
|
||||||
&& (x.1).0.iter().all(Stmt::is_pure)
|
&& x.1.iter().all(Stmt::is_pure)
|
||||||
}
|
}
|
||||||
Self::Switch(expr, x, _) => {
|
Self::Switch(expr, x, _) => {
|
||||||
expr.is_pure()
|
expr.is_pure()
|
||||||
&& x.0.values().all(|block| {
|
&& x.cases.values().all(|block| {
|
||||||
block.0.as_ref().map(Expr::is_pure).unwrap_or(true)
|
block.condition.as_ref().map(Expr::is_pure).unwrap_or(true)
|
||||||
&& (block.1).0.iter().all(Stmt::is_pure)
|
&& block.statements.iter().all(Stmt::is_pure)
|
||||||
})
|
})
|
||||||
&& (x.2).iter().all(|(_, _, _, condition, stmt)| {
|
&& x.ranges.iter().all(|(_, _, _, block)| {
|
||||||
condition.as_ref().map(Expr::is_pure).unwrap_or(true)
|
block.condition.as_ref().map(Expr::is_pure).unwrap_or(true)
|
||||||
&& stmt.0.iter().all(Stmt::is_pure)
|
&& block.statements.iter().all(Stmt::is_pure)
|
||||||
})
|
})
|
||||||
&& (x.1).0.iter().all(Stmt::is_pure)
|
&& x.def_case.iter().all(Stmt::is_pure)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loops that exit can be pure because it can never be infinite.
|
// Loops that exit can be pure because it can never be infinite.
|
||||||
@ -472,13 +530,13 @@ impl Stmt {
|
|||||||
|
|
||||||
// For loops can be pure because if the iterable is pure, it is finite,
|
// For loops can be pure because if the iterable is pure, it is finite,
|
||||||
// so infinite loops can never occur.
|
// so infinite loops can never occur.
|
||||||
Self::For(iterable, x, _) => iterable.is_pure() && (x.2).0.iter().all(Stmt::is_pure),
|
Self::For(iterable, x, _) => iterable.is_pure() && x.2.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::BreakLoop(_, _) | Self::Return(_, _, _) => false,
|
Self::BreakLoop(_, _) | Self::Return(_, _, _) => false,
|
||||||
Self::TryCatch(x, _) => {
|
Self::TryCatch(x, _) => {
|
||||||
(x.0).0.iter().all(Stmt::is_pure) && (x.2).0.iter().all(Stmt::is_pure)
|
x.try_block.iter().all(Stmt::is_pure) && x.catch_block.iter().all(Stmt::is_pure)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
@ -574,12 +632,12 @@ impl Stmt {
|
|||||||
if !e.walk(path, on_node) {
|
if !e.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for s in &(x.0).0 {
|
for s in x.0.iter() {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for s in &(x.1).0 {
|
for s in x.1.iter() {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -589,27 +647,37 @@ impl Stmt {
|
|||||||
if !e.walk(path, on_node) {
|
if !e.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for b in x.0.values() {
|
for b in x.cases.values() {
|
||||||
if !b.0.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) {
|
if !b
|
||||||
|
.condition
|
||||||
|
.as_ref()
|
||||||
|
.map(|e| e.walk(path, on_node))
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for s in &(b.1).0 {
|
for s in b.statements.iter() {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (_, _, _, c, stmt) in &x.2 {
|
for (_, _, _, b) in &x.ranges {
|
||||||
if !c.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) {
|
if !b
|
||||||
|
.condition
|
||||||
|
.as_ref()
|
||||||
|
.map(|e| e.walk(path, on_node))
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for s in &stmt.0 {
|
for s in b.statements.iter() {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for s in &(x.1).0 {
|
for s in x.def_case.iter() {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -629,17 +697,17 @@ impl Stmt {
|
|||||||
if !e.walk(path, on_node) {
|
if !e.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for s in &(x.2).0 {
|
for s in x.2.iter() {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Assignment(x, _) => {
|
Self::Assignment(x, _) => {
|
||||||
if !x.0.walk(path, on_node) {
|
if !x.1.lhs.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if !x.2.walk(path, on_node) {
|
if !x.1.rhs.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -658,12 +726,12 @@ impl Stmt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::TryCatch(x, _) => {
|
Self::TryCatch(x, _) => {
|
||||||
for s in &(x.0).0 {
|
for s in x.try_block.iter() {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for s in &(x.2).0 {
|
for s in x.catch_block.iter() {
|
||||||
if !s.walk(path, on_node) {
|
if !s.walk(path, on_node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -290,13 +290,12 @@ impl Engine {
|
|||||||
unreachable!("function call in dot chain should not be namespace-qualified")
|
unreachable!("function call in dot chain should not be namespace-qualified")
|
||||||
}
|
}
|
||||||
// {xxx:map}.id op= ???
|
// {xxx:map}.id op= ???
|
||||||
Expr::Property(x) if target.is::<crate::Map>() && new_val.is_some() => {
|
Expr::Property(x, pos) if target.is::<crate::Map>() && new_val.is_some() => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||||
|
|
||||||
let (name, pos) = &x.2;
|
let index = x.2.clone().into();
|
||||||
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
||||||
let index = name.into();
|
|
||||||
{
|
{
|
||||||
let val_target = &mut self.get_indexed_mut(
|
let val_target = &mut self.get_indexed_mut(
|
||||||
global, state, lib, target, index, *pos, true, false, level,
|
global, state, lib, target, index, *pos, true, false, level,
|
||||||
@ -311,23 +310,22 @@ impl Engine {
|
|||||||
Ok((Dynamic::UNIT, true))
|
Ok((Dynamic::UNIT, true))
|
||||||
}
|
}
|
||||||
// {xxx:map}.id
|
// {xxx:map}.id
|
||||||
Expr::Property(x) if target.is::<crate::Map>() => {
|
Expr::Property(x, pos) if target.is::<crate::Map>() => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||||
|
|
||||||
let (name, pos) = &x.2;
|
let index = x.2.clone().into();
|
||||||
let index = name.into();
|
|
||||||
let val = self.get_indexed_mut(
|
let val = self.get_indexed_mut(
|
||||||
global, state, lib, target, index, *pos, false, false, level,
|
global, state, lib, target, index, *pos, false, false, level,
|
||||||
)?;
|
)?;
|
||||||
Ok((val.take_or_clone(), false))
|
Ok((val.take_or_clone(), false))
|
||||||
}
|
}
|
||||||
// xxx.id op= ???
|
// xxx.id op= ???
|
||||||
Expr::Property(x) if new_val.is_some() => {
|
Expr::Property(x, pos) if new_val.is_some() => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||||
|
|
||||||
let ((getter, hash_get), (setter, hash_set), (name, pos)) = x.as_ref();
|
let ((getter, hash_get), (setter, hash_set), name) = x.as_ref();
|
||||||
let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`");
|
||||||
|
|
||||||
if op_info.is_some() {
|
if op_info.is_some() {
|
||||||
@ -402,11 +400,11 @@ impl Engine {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
// xxx.id
|
// xxx.id
|
||||||
Expr::Property(x) => {
|
Expr::Property(x, pos) => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?;
|
||||||
|
|
||||||
let ((getter, hash_get), _, (name, pos)) = x.as_ref();
|
let ((getter, hash_get), _, name) = x.as_ref();
|
||||||
let hash = crate::ast::FnCallHashes::from_native(*hash_get);
|
let hash = crate::ast::FnCallHashes::from_native(*hash_get);
|
||||||
let args = &mut [target.as_mut()];
|
let args = &mut [target.as_mut()];
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
@ -442,16 +440,15 @@ impl Engine {
|
|||||||
let _node = &x.lhs;
|
let _node = &x.lhs;
|
||||||
|
|
||||||
let val_target = &mut match x.lhs {
|
let val_target = &mut match x.lhs {
|
||||||
Expr::Property(ref p) => {
|
Expr::Property(ref p, pos) => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
self.run_debugger(
|
self.run_debugger(
|
||||||
scope, global, state, lib, this_ptr, _node, level,
|
scope, global, state, lib, this_ptr, _node, level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let (name, pos) = &p.2;
|
let index = p.2.clone().into();
|
||||||
let index = name.into();
|
|
||||||
self.get_indexed_mut(
|
self.get_indexed_mut(
|
||||||
global, state, lib, target, index, *pos, false, true, level,
|
global, state, lib, target, index, pos, false, true, level,
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
|
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
|
||||||
@ -495,14 +492,13 @@ impl Engine {
|
|||||||
|
|
||||||
match x.lhs {
|
match x.lhs {
|
||||||
// xxx.prop[expr] | xxx.prop.expr
|
// xxx.prop[expr] | xxx.prop.expr
|
||||||
Expr::Property(ref p) => {
|
Expr::Property(ref p, pos) => {
|
||||||
#[cfg(feature = "debugging")]
|
#[cfg(feature = "debugging")]
|
||||||
self.run_debugger(
|
self.run_debugger(
|
||||||
scope, global, state, lib, this_ptr, _node, level,
|
scope, global, state, lib, this_ptr, _node, level,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let ((getter, hash_get), (setter, hash_set), (name, pos)) =
|
let ((getter, hash_get), (setter, hash_set), name) = p.as_ref();
|
||||||
p.as_ref();
|
|
||||||
let rhs_chain = rhs.into();
|
let rhs_chain = rhs.into();
|
||||||
let hash_get = crate::ast::FnCallHashes::from_native(*hash_get);
|
let hash_get = crate::ast::FnCallHashes::from_native(*hash_get);
|
||||||
let hash_set = crate::ast::FnCallHashes::from_native(*hash_set);
|
let hash_set = crate::ast::FnCallHashes::from_native(*hash_set);
|
||||||
@ -513,15 +509,15 @@ impl Engine {
|
|||||||
let (mut val, _) = self
|
let (mut val, _) = self
|
||||||
.exec_fn_call(
|
.exec_fn_call(
|
||||||
global, state, lib, getter, hash_get, args, is_ref_mut,
|
global, state, lib, getter, hash_get, args, is_ref_mut,
|
||||||
true, *pos, None, level,
|
true, pos, None, level,
|
||||||
)
|
)
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
// Try an indexer if property does not exist
|
// Try an indexer if property does not exist
|
||||||
ERR::ErrorDotExpr(_, _) => {
|
ERR::ErrorDotExpr(_, _) => {
|
||||||
let prop = name.into();
|
let prop = name.into();
|
||||||
self.get_indexed_mut(
|
self.get_indexed_mut(
|
||||||
global, state, lib, target, prop, *pos, false,
|
global, state, lib, target, prop, pos, false, true,
|
||||||
true, level,
|
level,
|
||||||
)
|
)
|
||||||
.map(|v| (v.take_or_clone(), false))
|
.map(|v| (v.take_or_clone(), false))
|
||||||
.map_err(
|
.map_err(
|
||||||
@ -561,7 +557,7 @@ impl Engine {
|
|||||||
let args = &mut arg_values;
|
let args = &mut arg_values;
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
global, state, lib, setter, hash_set, args, is_ref_mut,
|
global, state, lib, setter, hash_set, args, is_ref_mut,
|
||||||
true, *pos, None, level,
|
true, pos, None, level,
|
||||||
)
|
)
|
||||||
.or_else(
|
.or_else(
|
||||||
|err| match *err {
|
|err| match *err {
|
||||||
@ -576,7 +572,7 @@ impl Engine {
|
|||||||
);
|
);
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
global, state, lib, fn_name, hash_set, args,
|
global, state, lib, fn_name, hash_set, args,
|
||||||
is_ref_mut, true, *pos, None, level,
|
is_ref_mut, true, pos, None, level,
|
||||||
)
|
)
|
||||||
.or_else(|idx_err| match *idx_err {
|
.or_else(|idx_err| match *idx_err {
|
||||||
ERR::ErrorIndexingType(_, _) => {
|
ERR::ErrorIndexingType(_, _) => {
|
||||||
@ -754,10 +750,10 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Property(x) if _parent_chain_type == ChainType::Dotting => {
|
Expr::Property(_, pos) if _parent_chain_type == ChainType::Dotting => {
|
||||||
idx_values.push(super::ChainArgument::Property((x.2).1))
|
idx_values.push(super::ChainArgument::Property(*pos))
|
||||||
}
|
}
|
||||||
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
|
Expr::Property(_, _) => unreachable!("unexpected Expr::Property for indexing"),
|
||||||
|
|
||||||
Expr::Index(x, term, _) | Expr::Dot(x, term, _) if !terminate_chaining => {
|
Expr::Index(x, term, _) | Expr::Dot(x, term, _) if !terminate_chaining => {
|
||||||
let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref();
|
let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref();
|
||||||
@ -765,10 +761,10 @@ impl Engine {
|
|||||||
// Evaluate in left-to-right order
|
// Evaluate in left-to-right order
|
||||||
let lhs_arg_val = match lhs {
|
let lhs_arg_val = match lhs {
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Property(x) if _parent_chain_type == ChainType::Dotting => {
|
Expr::Property(_, pos) if _parent_chain_type == ChainType::Dotting => {
|
||||||
super::ChainArgument::Property((x.2).1)
|
super::ChainArgument::Property(*pos)
|
||||||
}
|
}
|
||||||
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
|
Expr::Property(_, _) => unreachable!("unexpected Expr::Property for indexing"),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::FnCall(x, _)
|
Expr::FnCall(x, _)
|
||||||
|
@ -312,7 +312,7 @@ impl Debugger {
|
|||||||
},
|
},
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
BreakPoint::AtProperty { name, .. } => match node {
|
BreakPoint::AtProperty { name, .. } => match node {
|
||||||
ASTNode::Expr(Expr::Property(x)) => (x.2).0 == *name,
|
ASTNode::Expr(Expr::Property(x, _)) => x.2 == *name,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
//! Module defining functions for evaluating a statement.
|
//! Module defining functions for evaluating a statement.
|
||||||
|
|
||||||
use super::{EvalState, GlobalRuntimeState, Target};
|
use super::{EvalState, GlobalRuntimeState, Target};
|
||||||
use crate::ast::{Expr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
|
use crate::ast::{
|
||||||
|
BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
|
||||||
|
};
|
||||||
use crate::func::get_hasher;
|
use crate::func::get_hasher;
|
||||||
use crate::types::dynamic::{AccessMode, Union};
|
use crate::types::dynamic::{AccessMode, Union};
|
||||||
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT};
|
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT};
|
||||||
@ -225,20 +227,21 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
self.inc_operations(&mut global.num_operations, stmt.position())?;
|
self.inc_operations(&mut global.num_operations, stmt.position())?;
|
||||||
|
|
||||||
let result = if x.0.is_variable_access(false) {
|
let result = if x.1.lhs.is_variable_access(false) {
|
||||||
let (lhs_expr, op_info, rhs_expr) = x.as_ref();
|
let (op_info, BinaryExpr { lhs, rhs }) = x.as_ref();
|
||||||
|
|
||||||
let rhs_result = self
|
let rhs_result = self
|
||||||
.eval_expr(scope, global, state, lib, this_ptr, rhs_expr, level)
|
.eval_expr(scope, global, state, lib, this_ptr, rhs, level)
|
||||||
.map(Dynamic::flatten);
|
.map(Dynamic::flatten);
|
||||||
|
|
||||||
if let Ok(rhs_val) = rhs_result {
|
if let Ok(rhs_val) = rhs_result {
|
||||||
let search_result =
|
let search_result =
|
||||||
self.search_namespace(scope, global, state, lib, this_ptr, lhs_expr);
|
self.search_namespace(scope, global, state, lib, this_ptr, lhs);
|
||||||
|
|
||||||
if let Ok(search_val) = search_result {
|
if let Ok(search_val) = search_result {
|
||||||
let (mut lhs_ptr, pos) = search_val;
|
let (mut lhs_ptr, pos) = search_val;
|
||||||
|
|
||||||
let var_name = lhs_expr.get_variable_name(false).expect("`Expr::Variable`");
|
let var_name = lhs.get_variable_name(false).expect("`Expr::Variable`");
|
||||||
|
|
||||||
if !lhs_ptr.is_ref() {
|
if !lhs_ptr.is_ref() {
|
||||||
return Err(
|
return Err(
|
||||||
@ -259,7 +262,7 @@ impl Engine {
|
|||||||
(var_name, pos),
|
(var_name, pos),
|
||||||
rhs_val,
|
rhs_val,
|
||||||
)
|
)
|
||||||
.map_err(|err| err.fill_position(rhs_expr.position()))
|
.map_err(|err| err.fill_position(rhs.position()))
|
||||||
.map(|_| Dynamic::UNIT)
|
.map(|_| Dynamic::UNIT)
|
||||||
} else {
|
} else {
|
||||||
search_result.map(|_| Dynamic::UNIT)
|
search_result.map(|_| Dynamic::UNIT)
|
||||||
@ -268,16 +271,17 @@ impl Engine {
|
|||||||
rhs_result
|
rhs_result
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let (lhs_expr, op_info, rhs_expr) = x.as_ref();
|
let (op_info, BinaryExpr { lhs, rhs }) = x.as_ref();
|
||||||
|
|
||||||
let rhs_result = self
|
let rhs_result = self
|
||||||
.eval_expr(scope, global, state, lib, this_ptr, rhs_expr, level)
|
.eval_expr(scope, global, state, lib, this_ptr, rhs, level)
|
||||||
.map(Dynamic::flatten);
|
.map(Dynamic::flatten);
|
||||||
|
|
||||||
if let Ok(rhs_val) = rhs_result {
|
if let Ok(rhs_val) = rhs_result {
|
||||||
let _new_val = Some(((rhs_val, rhs_expr.position()), (*op_info, *op_pos)));
|
let _new_val = Some(((rhs_val, rhs.position()), (*op_info, *op_pos)));
|
||||||
|
|
||||||
// Must be either `var[index] op= val` or `var.prop op= val`
|
// Must be either `var[index] op= val` or `var.prop op= val`
|
||||||
match lhs_expr {
|
match lhs {
|
||||||
// name op= rhs (handled above)
|
// name op= rhs (handled above)
|
||||||
Expr::Variable(_, _, _) => {
|
Expr::Variable(_, _, _) => {
|
||||||
unreachable!("Expr::Variable case is already handled")
|
unreachable!("Expr::Variable case is already handled")
|
||||||
@ -286,17 +290,17 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Index(_, _, _) => self
|
Expr::Index(_, _, _) => self
|
||||||
.eval_dot_index_chain(
|
.eval_dot_index_chain(
|
||||||
scope, global, state, lib, this_ptr, lhs_expr, level, _new_val,
|
scope, global, state, lib, this_ptr, lhs, level, _new_val,
|
||||||
)
|
)
|
||||||
.map(|_| Dynamic::UNIT),
|
.map(|_| Dynamic::UNIT),
|
||||||
// dot_lhs.dot_rhs op= rhs
|
// dot_lhs.dot_rhs op= rhs
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(_, _, _) => self
|
Expr::Dot(_, _, _) => self
|
||||||
.eval_dot_index_chain(
|
.eval_dot_index_chain(
|
||||||
scope, global, state, lib, this_ptr, lhs_expr, level, _new_val,
|
scope, global, state, lib, this_ptr, lhs, level, _new_val,
|
||||||
)
|
)
|
||||||
.map(|_| Dynamic::UNIT),
|
.map(|_| Dynamic::UNIT),
|
||||||
_ => unreachable!("cannot assign to expression: {:?}", lhs_expr),
|
_ => unreachable!("cannot assign to expression: {:?}", lhs),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rhs_result
|
rhs_result
|
||||||
@ -362,7 +366,11 @@ impl Engine {
|
|||||||
|
|
||||||
// Switch statement
|
// Switch statement
|
||||||
Stmt::Switch(match_expr, x, _) => {
|
Stmt::Switch(match_expr, x, _) => {
|
||||||
let (table, def_stmt, ranges) = x.as_ref();
|
let SwitchCases {
|
||||||
|
cases,
|
||||||
|
def_case,
|
||||||
|
ranges,
|
||||||
|
} = x.as_ref();
|
||||||
|
|
||||||
let value_result =
|
let value_result =
|
||||||
self.eval_expr(scope, global, state, lib, this_ptr, match_expr, level);
|
self.eval_expr(scope, global, state, lib, this_ptr, match_expr, level);
|
||||||
@ -374,9 +382,9 @@ impl Engine {
|
|||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
// First check hashes
|
// First check hashes
|
||||||
if let Some(t) = table.get(&hash) {
|
if let Some(t) = cases.get(&hash) {
|
||||||
let cond_result = t
|
let cond_result = t
|
||||||
.0
|
.condition
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|cond| {
|
.map(|cond| {
|
||||||
self.eval_expr(scope, global, state, lib, this_ptr, cond, level)
|
self.eval_expr(scope, global, state, lib, this_ptr, cond, level)
|
||||||
@ -392,7 +400,7 @@ impl Engine {
|
|||||||
.unwrap_or(Ok(true));
|
.unwrap_or(Ok(true));
|
||||||
|
|
||||||
match cond_result {
|
match cond_result {
|
||||||
Ok(true) => Ok(Some(&t.1)),
|
Ok(true) => Ok(Some(&t.statements)),
|
||||||
Ok(false) => Ok(None),
|
Ok(false) => Ok(None),
|
||||||
_ => cond_result.map(|_| None),
|
_ => cond_result.map(|_| None),
|
||||||
}
|
}
|
||||||
@ -401,13 +409,14 @@ impl Engine {
|
|||||||
let value = value.as_int().expect("`INT`");
|
let value = value.as_int().expect("`INT`");
|
||||||
let mut result = Ok(None);
|
let mut result = Ok(None);
|
||||||
|
|
||||||
for (_, _, _, condition, stmt_block) in
|
for (_, _, _, block) in
|
||||||
ranges.iter().filter(|&&(start, end, inclusive, _, _)| {
|
ranges.iter().filter(|&&(start, end, inclusive, _)| {
|
||||||
(!inclusive && (start..end).contains(&value))
|
(!inclusive && (start..end).contains(&value))
|
||||||
|| (inclusive && (start..=end).contains(&value))
|
|| (inclusive && (start..=end).contains(&value))
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
let cond_result = condition
|
let cond_result = block
|
||||||
|
.condition
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|cond| {
|
.map(|cond| {
|
||||||
self.eval_expr(
|
self.eval_expr(
|
||||||
@ -425,7 +434,7 @@ impl Engine {
|
|||||||
.unwrap_or(Ok(true));
|
.unwrap_or(Ok(true));
|
||||||
|
|
||||||
match cond_result {
|
match cond_result {
|
||||||
Ok(true) => result = Ok(Some(stmt_block)),
|
Ok(true) => result = Ok(Some(&block.statements)),
|
||||||
Ok(false) => continue,
|
Ok(false) => continue,
|
||||||
_ => result = cond_result.map(|_| None),
|
_ => result = cond_result.map(|_| None),
|
||||||
}
|
}
|
||||||
@ -453,9 +462,9 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
} else if let Ok(None) = stmt_block_result {
|
} else if let Ok(None) = stmt_block_result {
|
||||||
// Default match clause
|
// Default match clause
|
||||||
if !def_stmt.is_empty() {
|
if !def_case.is_empty() {
|
||||||
self.eval_stmt_block(
|
self.eval_stmt_block(
|
||||||
scope, global, state, lib, this_ptr, def_stmt, true, level,
|
scope, global, state, lib, this_ptr, def_case, true, level,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
@ -685,10 +694,14 @@ impl Engine {
|
|||||||
|
|
||||||
// Try/Catch statement
|
// Try/Catch statement
|
||||||
Stmt::TryCatch(x, _) => {
|
Stmt::TryCatch(x, _) => {
|
||||||
let (try_stmt, err_var_name, catch_stmt) = x.as_ref();
|
let TryCatchBlock {
|
||||||
|
try_block,
|
||||||
|
catch_var,
|
||||||
|
catch_block,
|
||||||
|
} = x.as_ref();
|
||||||
|
|
||||||
let result = self
|
let result = self
|
||||||
.eval_stmt_block(scope, global, state, lib, this_ptr, try_stmt, true, level)
|
.eval_stmt_block(scope, global, state, lib, this_ptr, try_block, true, level)
|
||||||
.map(|_| Dynamic::UNIT);
|
.map(|_| Dynamic::UNIT);
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
@ -733,12 +746,19 @@ impl Engine {
|
|||||||
|
|
||||||
let orig_scope_len = scope.len();
|
let orig_scope_len = scope.len();
|
||||||
|
|
||||||
err_var_name
|
catch_var
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|Ident { name, .. }| scope.push(name.clone(), err_value));
|
.map(|Ident { name, .. }| scope.push(name.clone(), err_value));
|
||||||
|
|
||||||
let result = self.eval_stmt_block(
|
let result = self.eval_stmt_block(
|
||||||
scope, global, state, lib, this_ptr, catch_stmt, true, level,
|
scope,
|
||||||
|
global,
|
||||||
|
state,
|
||||||
|
lib,
|
||||||
|
this_ptr,
|
||||||
|
catch_block,
|
||||||
|
true,
|
||||||
|
level,
|
||||||
);
|
);
|
||||||
|
|
||||||
scope.rewind(orig_scope_len);
|
scope.rewind(orig_scope_len);
|
||||||
@ -896,21 +916,18 @@ impl Engine {
|
|||||||
|
|
||||||
// Export statement
|
// Export statement
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Stmt::Export(list, _) => {
|
Stmt::Export(x, _) => {
|
||||||
list.iter()
|
let (Ident { name, pos, .. }, Ident { name: alias, .. }) = x.as_ref();
|
||||||
.try_for_each(|(Ident { name, pos, .. }, Ident { name: rename, .. })| {
|
|
||||||
// Mark scope variables as public
|
// Mark scope variables as public
|
||||||
if let Some((index, _)) = scope.get_index(name) {
|
if let Some((index, _)) = scope.get_index(name) {
|
||||||
scope.add_entry_alias(
|
scope.add_entry_alias(
|
||||||
index,
|
index,
|
||||||
if rename.is_empty() { name } else { rename }.clone(),
|
if alias.is_empty() { name } else { alias }.clone(),
|
||||||
);
|
);
|
||||||
Ok(()) as RhaiResultOf<_>
|
Ok(Dynamic::UNIT)
|
||||||
} else {
|
} else {
|
||||||
Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into())
|
Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into())
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.map(|_| Dynamic::UNIT)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Share statement
|
// Share statement
|
||||||
|
@ -254,8 +254,9 @@ pub use parser::ParseState;
|
|||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
pub use ast::{
|
pub use ast::{
|
||||||
ASTNode, BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment,
|
ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||||
OptionFlags, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*,
|
OpAssignment, OptionFlags, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock,
|
||||||
|
AST_OPTION_FLAGS::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
|
206
src/optimizer.rs
206
src/optimizer.rs
@ -412,26 +412,26 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
match stmt {
|
match stmt {
|
||||||
// var = var op expr => var op= expr
|
// var = var op expr => var op= expr
|
||||||
Stmt::Assignment(x, _)
|
Stmt::Assignment(x, _)
|
||||||
if x.1.is_none()
|
if x.0.is_none()
|
||||||
&& x.0.is_variable_access(true)
|
&& x.1.lhs.is_variable_access(true)
|
||||||
&& matches!(&x.2, Expr::FnCall(x2, _)
|
&& matches!(&x.1.rhs, Expr::FnCall(x2, _)
|
||||||
if Token::lookup_from_syntax(&x2.name).map(|t| t.has_op_assignment()).unwrap_or(false)
|
if Token::lookup_from_syntax(&x2.name).map(|t| t.has_op_assignment()).unwrap_or(false)
|
||||||
&& x2.args.len() == 2
|
&& x2.args.len() == 2
|
||||||
&& x2.args[0].get_variable_name(true) == x.0.get_variable_name(true)
|
&& x2.args[0].get_variable_name(true) == x.1.lhs.get_variable_name(true)
|
||||||
) =>
|
) =>
|
||||||
{
|
{
|
||||||
match x.2 {
|
match x.1.rhs {
|
||||||
Expr::FnCall(ref mut x2, _) => {
|
Expr::FnCall(ref mut x2, _) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
x.1 = Some(OpAssignment::new_from_base(&x2.name));
|
x.0 = Some(OpAssignment::new_from_base(&x2.name));
|
||||||
|
|
||||||
let value = mem::take(&mut x2.args[1]);
|
let value = mem::take(&mut x2.args[1]);
|
||||||
|
|
||||||
if let Expr::Stack(slot, pos) = value {
|
if let Expr::Stack(slot, pos) = value {
|
||||||
x.2 =
|
x.1.rhs =
|
||||||
Expr::from_dynamic(mem::take(x2.constants.get_mut(slot).unwrap()), pos);
|
Expr::from_dynamic(mem::take(x2.constants.get_mut(slot).unwrap()), pos);
|
||||||
} else {
|
} else {
|
||||||
x.2 = value;
|
x.1.rhs = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr),
|
ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr),
|
||||||
@ -439,13 +439,12 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// expr op= expr
|
// expr op= expr
|
||||||
Stmt::Assignment(x, _) => match x.0 {
|
Stmt::Assignment(x, _) => {
|
||||||
Expr::Variable(_, _, _) => optimize_expr(&mut x.2, state, false),
|
if !x.1.lhs.is_variable_access(false) {
|
||||||
_ => {
|
optimize_expr(&mut x.1.lhs, state, false);
|
||||||
optimize_expr(&mut x.0, state, false);
|
}
|
||||||
optimize_expr(&mut x.2, state, false);
|
optimize_expr(&mut x.1.rhs, state, false);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
// if expr {}
|
// if expr {}
|
||||||
Stmt::If(condition, x, _) if x.0.is_empty() && x.1.is_empty() => {
|
Stmt::If(condition, x, _) if x.0.is_empty() && x.1.is_empty() => {
|
||||||
@ -502,66 +501,24 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
value.hash(hasher);
|
value.hash(hasher);
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
let table = &mut x.0;
|
let cases = &mut x.cases;
|
||||||
|
|
||||||
// First check hashes
|
// First check hashes
|
||||||
if let Some(block) = table.get_mut(&hash) {
|
if let Some(block) = cases.get_mut(&hash) {
|
||||||
if let Some(mut condition) = mem::take(&mut block.0) {
|
if let Some(mut condition) = mem::take(&mut block.condition) {
|
||||||
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
|
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
|
||||||
optimize_expr(&mut condition, state, false);
|
optimize_expr(&mut condition, state, false);
|
||||||
|
|
||||||
let def_stmt =
|
let def_stmt =
|
||||||
optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false);
|
optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false);
|
||||||
|
|
||||||
*stmt = Stmt::If(
|
*stmt = Stmt::If(
|
||||||
condition,
|
condition,
|
||||||
Box::new((
|
Box::new((
|
||||||
mem::take(&mut block.1),
|
mem::take(&mut block.statements),
|
||||||
Stmt::Block(def_stmt.into_boxed_slice(), x.1.position().or_else(*pos))
|
|
||||||
.into(),
|
|
||||||
)),
|
|
||||||
match_expr.position(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Promote the matched case
|
|
||||||
let statements =
|
|
||||||
optimize_stmt_block(mem::take(&mut *block.1), state, true, true, false);
|
|
||||||
*stmt = Stmt::Block(statements.into_boxed_slice(), block.1.position());
|
|
||||||
}
|
|
||||||
|
|
||||||
state.set_dirty();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then check ranges
|
|
||||||
let ranges = &mut x.2;
|
|
||||||
|
|
||||||
if value.is::<INT>() && !ranges.is_empty() {
|
|
||||||
let value = value.as_int().expect("`INT`");
|
|
||||||
|
|
||||||
// Only one range or all ranges without conditions
|
|
||||||
if ranges.len() == 1 || ranges.iter().all(|(_, _, _, c, _)| c.is_none()) {
|
|
||||||
for (_, _, _, condition, stmt_block) in
|
|
||||||
ranges
|
|
||||||
.iter_mut()
|
|
||||||
.filter(|&&mut (start, end, inclusive, _, _)| {
|
|
||||||
(!inclusive && (start..end).contains(&value))
|
|
||||||
|| (inclusive && (start..=end).contains(&value))
|
|
||||||
})
|
|
||||||
{
|
|
||||||
if let Some(mut condition) = mem::take(condition) {
|
|
||||||
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
|
|
||||||
optimize_expr(&mut condition, state, false);
|
|
||||||
|
|
||||||
let def_stmt =
|
|
||||||
optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false);
|
|
||||||
*stmt = Stmt::If(
|
|
||||||
condition,
|
|
||||||
Box::new((
|
|
||||||
mem::take(stmt_block),
|
|
||||||
Stmt::Block(
|
Stmt::Block(
|
||||||
def_stmt.into_boxed_slice(),
|
def_stmt.into_boxed_slice(),
|
||||||
x.1.position().or_else(*pos),
|
x.def_case.position().or_else(*pos),
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
)),
|
)),
|
||||||
@ -569,11 +526,68 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Promote the matched case
|
// Promote the matched case
|
||||||
let statements = mem::take(&mut **stmt_block);
|
let statements = optimize_stmt_block(
|
||||||
|
mem::take(&mut block.statements),
|
||||||
|
state,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
*stmt = Stmt::Block(statements.into_boxed_slice(), block.statements.position());
|
||||||
|
}
|
||||||
|
|
||||||
|
state.set_dirty();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then check ranges
|
||||||
|
let ranges = &mut x.ranges;
|
||||||
|
|
||||||
|
if value.is::<INT>() && !ranges.is_empty() {
|
||||||
|
let value = value.as_int().expect("`INT`");
|
||||||
|
|
||||||
|
// Only one range or all ranges without conditions
|
||||||
|
if ranges.len() == 1 || ranges.iter().all(|(_, _, _, c)| !c.has_condition()) {
|
||||||
|
for (_, _, _, block) in
|
||||||
|
ranges
|
||||||
|
.iter_mut()
|
||||||
|
.filter(|&&mut (start, end, inclusive, _)| {
|
||||||
|
(!inclusive && (start..end).contains(&value))
|
||||||
|
|| (inclusive && (start..=end).contains(&value))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if let Some(mut condition) = mem::take(&mut block.condition) {
|
||||||
|
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
|
||||||
|
optimize_expr(&mut condition, state, false);
|
||||||
|
|
||||||
|
let def_stmt = optimize_stmt_block(
|
||||||
|
mem::take(&mut x.def_case),
|
||||||
|
state,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
*stmt = Stmt::If(
|
||||||
|
condition,
|
||||||
|
Box::new((
|
||||||
|
mem::take(&mut block.statements),
|
||||||
|
Stmt::Block(
|
||||||
|
def_stmt.into_boxed_slice(),
|
||||||
|
x.def_case.position().or_else(*pos),
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
)),
|
||||||
|
match_expr.position(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Promote the matched case
|
||||||
|
let statements = mem::take(&mut *block.statements);
|
||||||
let statements =
|
let statements =
|
||||||
optimize_stmt_block(statements, state, true, true, false);
|
optimize_stmt_block(statements, state, true, true, false);
|
||||||
*stmt =
|
*stmt = Stmt::Block(
|
||||||
Stmt::Block(statements.into_boxed_slice(), stmt_block.position());
|
statements.into_boxed_slice(),
|
||||||
|
block.statements.position(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -581,14 +595,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Multiple ranges - clear the table and just keep the right ranges
|
// Multiple ranges - clear the table and just keep the right ranges
|
||||||
if !table.is_empty() {
|
if !cases.is_empty() {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
table.clear();
|
cases.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
let old_ranges_len = ranges.len();
|
let old_ranges_len = ranges.len();
|
||||||
|
|
||||||
ranges.retain(|&mut (start, end, inclusive, _, _)| {
|
ranges.retain(|&mut (start, end, inclusive, _)| {
|
||||||
(!inclusive && (start..end).contains(&value))
|
(!inclusive && (start..end).contains(&value))
|
||||||
|| (inclusive && (start..=end).contains(&value))
|
|| (inclusive && (start..=end).contains(&value))
|
||||||
});
|
});
|
||||||
@ -597,16 +611,16 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (_, _, _, condition, stmt_block) in ranges.iter_mut() {
|
for (_, _, _, block) in ranges.iter_mut() {
|
||||||
let statements = mem::take(&mut **stmt_block);
|
let statements = mem::take(&mut *block.statements);
|
||||||
**stmt_block =
|
*block.statements =
|
||||||
optimize_stmt_block(statements, state, preserve_result, true, false);
|
optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||||
|
|
||||||
if let Some(mut c) = mem::take(condition) {
|
if let Some(mut condition) = mem::take(&mut block.condition) {
|
||||||
optimize_expr(&mut c, state, false);
|
optimize_expr(&mut condition, state, false);
|
||||||
match c {
|
match condition {
|
||||||
Expr::Unit(_) | Expr::BoolConstant(true, _) => state.set_dirty(),
|
Expr::Unit(_) | Expr::BoolConstant(true, _) => state.set_dirty(),
|
||||||
_ => *condition = Some(c),
|
_ => block.condition = Some(condition),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -616,35 +630,41 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
|
|
||||||
// Promote the default case
|
// Promote the default case
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
let def_stmt = optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false);
|
let def_stmt =
|
||||||
*stmt = Stmt::Block(def_stmt.into_boxed_slice(), x.1.position().or_else(*pos));
|
optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false);
|
||||||
|
*stmt = Stmt::Block(
|
||||||
|
def_stmt.into_boxed_slice(),
|
||||||
|
x.def_case.position().or_else(*pos),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// switch
|
// switch
|
||||||
Stmt::Switch(match_expr, x, _) => {
|
Stmt::Switch(match_expr, x, _) => {
|
||||||
optimize_expr(match_expr, state, false);
|
optimize_expr(match_expr, state, false);
|
||||||
x.0.values_mut().for_each(|block| {
|
x.cases.values_mut().for_each(|block| {
|
||||||
let statements = mem::take(&mut *block.1);
|
let statements = mem::take(&mut *block.statements);
|
||||||
*block.1 = optimize_stmt_block(statements, state, preserve_result, true, false);
|
*block.statements =
|
||||||
|
optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||||
|
|
||||||
if let Some(mut condition) = mem::take(&mut block.0) {
|
if let Some(mut condition) = mem::take(&mut block.condition) {
|
||||||
optimize_expr(&mut condition, state, false);
|
optimize_expr(&mut condition, state, false);
|
||||||
match condition {
|
match condition {
|
||||||
Expr::Unit(_) | Expr::BoolConstant(true, _) => state.set_dirty(),
|
Expr::Unit(_) | Expr::BoolConstant(true, _) => state.set_dirty(),
|
||||||
_ => block.0 = Some(condition),
|
_ => block.condition = Some(condition),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove false cases
|
// Remove false cases
|
||||||
while let Some((&key, _)) = x.0.iter().find(|(_, block)| match block.0 {
|
while let Some((&key, _)) = x.cases.iter().find(|(_, block)| match block.condition {
|
||||||
Some(Expr::BoolConstant(false, _)) => true,
|
Some(Expr::BoolConstant(false, _)) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}) {
|
}) {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
x.0.remove(&key);
|
x.cases.remove(&key);
|
||||||
}
|
}
|
||||||
|
|
||||||
*x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false);
|
let def_block = mem::take(&mut *x.def_case);
|
||||||
|
*x.def_case = optimize_stmt_block(def_block, state, preserve_result, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// while false { block } -> Noop
|
// while false { block } -> Noop
|
||||||
@ -727,19 +747,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// try { pure try_block } catch ( var ) { catch_block } -> try_block
|
// try { pure try_block } catch ( var ) { catch_block } -> try_block
|
||||||
Stmt::TryCatch(x, _) if x.0.iter().all(Stmt::is_pure) => {
|
Stmt::TryCatch(x, _) if x.try_block.iter().all(Stmt::is_pure) => {
|
||||||
// If try block is pure, there will never be any exceptions
|
// If try block is pure, there will never be any exceptions
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*stmt = Stmt::Block(
|
*stmt = Stmt::Block(
|
||||||
optimize_stmt_block(mem::take(&mut *x.0), state, false, true, false)
|
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false)
|
||||||
.into_boxed_slice(),
|
.into_boxed_slice(),
|
||||||
x.0.position(),
|
x.try_block.position(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// try { try_block } catch ( var ) { catch_block }
|
// try { try_block } catch ( var ) { catch_block }
|
||||||
Stmt::TryCatch(x, _) => {
|
Stmt::TryCatch(x, _) => {
|
||||||
*x.0 = optimize_stmt_block(mem::take(&mut *x.0), state, false, true, false);
|
*x.try_block =
|
||||||
*x.2 = optimize_stmt_block(mem::take(&mut *x.2), state, false, true, false);
|
optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false);
|
||||||
|
*x.catch_block =
|
||||||
|
optimize_stmt_block(mem::take(&mut *x.catch_block), state, false, true, false);
|
||||||
}
|
}
|
||||||
// func(...)
|
// func(...)
|
||||||
Stmt::Expr(expr @ Expr::FnCall(_, _)) => {
|
Stmt::Expr(expr @ Expr::FnCall(_, _)) => {
|
||||||
@ -800,8 +822,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(x,_, _) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
|
Expr::Dot(x,_, _) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
|
||||||
// map.string
|
// map.string
|
||||||
(Expr::Map(m, pos), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
|
(Expr::Map(m, pos), Expr::Property(p, _)) if m.0.iter().all(|(_, x)| x.is_pure()) => {
|
||||||
let prop = p.2.0.as_str();
|
let prop = p.2.as_str();
|
||||||
// Map literal where everything is pure - promote the indexed item.
|
// Map literal where everything is pure - promote the indexed item.
|
||||||
// All other items can be thrown away.
|
// All other items can be thrown away.
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
|
129
src/parser.rs
129
src/parser.rs
@ -3,8 +3,8 @@
|
|||||||
use crate::api::custom_syntax::{markers::*, CustomSyntax};
|
use crate::api::custom_syntax::{markers::*, CustomSyntax};
|
||||||
use crate::api::options::LanguageOptions;
|
use crate::api::options::LanguageOptions;
|
||||||
use crate::ast::{
|
use crate::ast::{
|
||||||
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt,
|
BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident,
|
||||||
StmtBlock, AST_OPTION_FLAGS::*,
|
OpAssignment, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*,
|
||||||
};
|
};
|
||||||
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
|
||||||
use crate::func::hashing::get_hasher;
|
use crate::func::hashing::get_hasher;
|
||||||
@ -273,11 +273,14 @@ impl Expr {
|
|||||||
let setter = state.get_identifier(crate::engine::FN_SET, &ident);
|
let setter = state.get_identifier(crate::engine::FN_SET, &ident);
|
||||||
let hash_set = calc_fn_hash(&setter, 2);
|
let hash_set = calc_fn_hash(&setter, 2);
|
||||||
|
|
||||||
Self::Property(Box::new((
|
Self::Property(
|
||||||
|
Box::new((
|
||||||
(getter, hash_get),
|
(getter, hash_get),
|
||||||
(setter, hash_set),
|
(setter, hash_set),
|
||||||
(state.get_interned_string("", &ident), pos),
|
state.get_interned_string("", &ident),
|
||||||
)))
|
)),
|
||||||
|
pos,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
_ => self,
|
_ => self,
|
||||||
}
|
}
|
||||||
@ -985,8 +988,8 @@ fn parse_switch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut table = BTreeMap::<u64, Box<(Option<Expr>, StmtBlock)>>::new();
|
let mut cases = BTreeMap::<u64, Box<ConditionalStmtBlock>>::new();
|
||||||
let mut ranges = StaticVec::<(INT, INT, bool, Option<Expr>, StmtBlock)>::new();
|
let mut ranges = StaticVec::<(INT, INT, bool, ConditionalStmtBlock)>::new();
|
||||||
let mut def_pos = Position::NONE;
|
let mut def_pos = Position::NONE;
|
||||||
let mut def_stmt = None;
|
let mut def_stmt = None;
|
||||||
|
|
||||||
@ -1050,7 +1053,7 @@ fn parse_switch(
|
|||||||
value.hash(hasher);
|
value.hash(hasher);
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
if table.contains_key(&hash) {
|
if cases.contains_key(&hash) {
|
||||||
return Err(PERR::DuplicatedSwitchCase.into_err(expr.position()));
|
return Err(PERR::DuplicatedSwitchCase.into_err(expr.position()));
|
||||||
}
|
}
|
||||||
(Some(hash), None)
|
(Some(hash), None)
|
||||||
@ -1092,18 +1095,20 @@ fn parse_switch(
|
|||||||
value.hash(hasher);
|
value.hash(hasher);
|
||||||
let hash = hasher.finish();
|
let hash = hasher.finish();
|
||||||
|
|
||||||
table
|
cases.entry(hash).or_insert_with(|| {
|
||||||
.entry(hash)
|
let block: ConditionalStmtBlock = (condition, stmt).into();
|
||||||
.or_insert_with(|| (condition.clone(), stmt.into()).into());
|
block.into()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// Other range
|
// Other range
|
||||||
_ => ranges.push((range.0, range.1, range.2, condition, stmt.into())),
|
_ => ranges.push((range.0, range.1, range.2, (condition, stmt).into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
(Some(hash), None) => {
|
(Some(hash), None) => {
|
||||||
table.insert(hash, (condition, stmt.into()).into());
|
let block: ConditionalStmtBlock = (condition, stmt).into();
|
||||||
|
cases.insert(hash, block.into());
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
(None, None) => Some(stmt.into()),
|
(None, None) => Some(stmt.into()),
|
||||||
@ -1133,11 +1138,16 @@ fn parse_switch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let def_stmt_block = def_stmt.unwrap_or_else(|| Stmt::Noop(Position::NONE).into());
|
let def_case = def_stmt.unwrap_or_else(|| Stmt::Noop(Position::NONE).into());
|
||||||
|
|
||||||
Ok(Stmt::Switch(
|
Ok(Stmt::Switch(
|
||||||
item,
|
item,
|
||||||
(table, def_stmt_block, ranges).into(),
|
SwitchCases {
|
||||||
|
cases,
|
||||||
|
def_case,
|
||||||
|
ranges,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
settings.pos,
|
settings.pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -1692,20 +1702,20 @@ fn make_assignment_stmt(
|
|||||||
fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option<Position> {
|
fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option<Position> {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Index(x, term, _) | Expr::Dot(x, term, _) if parent_is_dot => match x.lhs {
|
Expr::Index(x, term, _) | Expr::Dot(x, term, _) if parent_is_dot => match x.lhs {
|
||||||
Expr::Property(_) if !term => {
|
Expr::Property(_, _) if !term => {
|
||||||
check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _)))
|
check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _)))
|
||||||
}
|
}
|
||||||
Expr::Property(_) => None,
|
Expr::Property(_, _) => None,
|
||||||
// Anything other than a property after dotting (e.g. a method call) is not an l-value
|
// Anything other than a property after dotting (e.g. a method call) is not an l-value
|
||||||
ref e => Some(e.position()),
|
ref e => Some(e.position()),
|
||||||
},
|
},
|
||||||
Expr::Index(x, term, _) | Expr::Dot(x, term, _) => match x.lhs {
|
Expr::Index(x, term, _) | Expr::Dot(x, term, _) => match x.lhs {
|
||||||
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"),
|
Expr::Property(_, _) => unreachable!("unexpected Expr::Property in indexing"),
|
||||||
_ if !term => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))),
|
_ if !term => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
Expr::Property(_) if parent_is_dot => None,
|
Expr::Property(_, _) if parent_is_dot => None,
|
||||||
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"),
|
Expr::Property(_, _) => unreachable!("unexpected Expr::Property in indexing"),
|
||||||
e if parent_is_dot => Some(e.position()),
|
e if parent_is_dot => Some(e.position()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
@ -1719,9 +1729,10 @@ fn make_assignment_stmt(
|
|||||||
Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position()))
|
Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position()))
|
||||||
}
|
}
|
||||||
// var (non-indexed) = rhs
|
// var (non-indexed) = rhs
|
||||||
Expr::Variable(None, _, ref x) if x.0.is_none() => {
|
Expr::Variable(None, _, ref x) if x.0.is_none() => Ok(Stmt::Assignment(
|
||||||
Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos))
|
(op_info, (lhs, rhs).into()).into(),
|
||||||
}
|
op_pos,
|
||||||
|
)),
|
||||||
// var (indexed) = rhs
|
// var (indexed) = rhs
|
||||||
Expr::Variable(i, var_pos, ref x) => {
|
Expr::Variable(i, var_pos, ref x) => {
|
||||||
let (index, _, name) = x.as_ref();
|
let (index, _, name) = x.as_ref();
|
||||||
@ -1730,7 +1741,10 @@ fn make_assignment_stmt(
|
|||||||
|n| n.get() as usize,
|
|n| n.get() as usize,
|
||||||
);
|
);
|
||||||
match state.stack[state.stack.len() - index].1 {
|
match state.stack[state.stack.len() - index].1 {
|
||||||
AccessMode::ReadWrite => Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos)),
|
AccessMode::ReadWrite => Ok(Stmt::Assignment(
|
||||||
|
(op_info, (lhs, rhs).into()).into(),
|
||||||
|
op_pos,
|
||||||
|
)),
|
||||||
// Constant values cannot be assigned to
|
// Constant values cannot be assigned to
|
||||||
AccessMode::ReadOnly => {
|
AccessMode::ReadOnly => {
|
||||||
Err(PERR::AssignmentToConstant(name.to_string()).into_err(var_pos))
|
Err(PERR::AssignmentToConstant(name.to_string()).into_err(var_pos))
|
||||||
@ -1749,9 +1763,10 @@ fn make_assignment_stmt(
|
|||||||
None => {
|
None => {
|
||||||
match x.lhs {
|
match x.lhs {
|
||||||
// var[???] = rhs, var.??? = rhs
|
// var[???] = rhs, var.??? = rhs
|
||||||
Expr::Variable(_, _, _) => {
|
Expr::Variable(_, _, _) => Ok(Stmt::Assignment(
|
||||||
Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos))
|
(op_info, (lhs, rhs).into()).into(),
|
||||||
}
|
op_pos,
|
||||||
|
)),
|
||||||
// expr[???] = rhs, expr.??? = rhs
|
// expr[???] = rhs, expr.??? = rhs
|
||||||
ref expr => {
|
ref expr => {
|
||||||
Err(PERR::AssignmentToInvalidLHS("".to_string())
|
Err(PERR::AssignmentToInvalidLHS("".to_string())
|
||||||
@ -1830,7 +1845,7 @@ fn make_dot_expr(
|
|||||||
Err(PERR::PropertyExpected.into_err(x.1.expect("`Some`").0[0].pos))
|
Err(PERR::PropertyExpected.into_err(x.1.expect("`Some`").0[0].pos))
|
||||||
}
|
}
|
||||||
// lhs.prop
|
// lhs.prop
|
||||||
(lhs, prop @ Expr::Property(_)) => Ok(Expr::Dot(
|
(lhs, prop @ Expr::Property(_, _)) => Ok(Expr::Dot(
|
||||||
BinaryExpr { lhs, rhs: prop }.into(),
|
BinaryExpr { lhs, rhs: prop }.into(),
|
||||||
false,
|
false,
|
||||||
op_pos,
|
op_pos,
|
||||||
@ -1844,7 +1859,7 @@ fn make_dot_expr(
|
|||||||
};
|
};
|
||||||
|
|
||||||
match x.lhs {
|
match x.lhs {
|
||||||
Expr::Variable(_, _, _) | Expr::Property(_) => {
|
Expr::Variable(_, _, _) | Expr::Property(_, _) => {
|
||||||
let new_lhs = BinaryExpr {
|
let new_lhs = BinaryExpr {
|
||||||
lhs: x.lhs.into_property(state),
|
lhs: x.lhs.into_property(state),
|
||||||
rhs: x.rhs,
|
rhs: x.rhs,
|
||||||
@ -2603,48 +2618,27 @@ fn parse_export(
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut exports = Vec::<(Ident, Ident)>::with_capacity(4);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let (id, id_pos) = parse_var_name(input)?;
|
let (id, id_pos) = parse_var_name(input)?;
|
||||||
|
|
||||||
let (rename, rename_pos) = if match_token(input, Token::As).0 {
|
let (alias, alias_pos) = if match_token(input, Token::As).0 {
|
||||||
let (name, pos) = parse_var_name(input)?;
|
let (name, pos) = parse_var_name(input)?;
|
||||||
if exports.iter().any(|(_, alias)| alias.name == name.as_ref()) {
|
|
||||||
return Err(PERR::DuplicatedVariable(name.to_string()).into_err(pos));
|
|
||||||
}
|
|
||||||
(Some(name), pos)
|
(Some(name), pos)
|
||||||
} else {
|
} else {
|
||||||
(None, Position::NONE)
|
(None, Position::NONE)
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.push((
|
let export = (
|
||||||
Ident {
|
Ident {
|
||||||
name: state.get_identifier("", id),
|
name: state.get_identifier("", id),
|
||||||
pos: id_pos,
|
pos: id_pos,
|
||||||
},
|
},
|
||||||
Ident {
|
Ident {
|
||||||
name: state.get_identifier("", rename.as_ref().map_or("", <_>::as_ref)),
|
name: state.get_identifier("", alias.as_ref().map_or("", <_>::as_ref)),
|
||||||
pos: rename_pos,
|
pos: alias_pos,
|
||||||
},
|
},
|
||||||
));
|
);
|
||||||
|
|
||||||
match input.peek().expect(NEVER_ENDS) {
|
Ok(Stmt::Export(export.into(), settings.pos))
|
||||||
(Token::Comma, _) => {
|
|
||||||
eat_token(input, Token::Comma);
|
|
||||||
}
|
|
||||||
(Token::Identifier(_), pos) => {
|
|
||||||
return Err(PERR::MissingToken(
|
|
||||||
Token::Comma.into(),
|
|
||||||
"to separate the list of exports".into(),
|
|
||||||
)
|
|
||||||
.into_err(*pos))
|
|
||||||
}
|
|
||||||
_ => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Stmt::Export(exports.into_boxed_slice(), settings.pos))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a statement block.
|
/// Parse a statement block.
|
||||||
@ -2996,10 +2990,10 @@ fn parse_try_catch(
|
|||||||
let mut settings = settings;
|
let mut settings = settings;
|
||||||
settings.pos = eat_token(input, Token::Try);
|
settings.pos = eat_token(input, Token::Try);
|
||||||
|
|
||||||
// try { body }
|
// try { try_block }
|
||||||
let body = parse_block(input, state, lib, settings.level_up())?;
|
let try_block = parse_block(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
// try { body } catch
|
// try { try_block } catch
|
||||||
let (matched, catch_pos) = match_token(input, Token::Catch);
|
let (matched, catch_pos) = match_token(input, Token::Catch);
|
||||||
|
|
||||||
if !matched {
|
if !matched {
|
||||||
@ -3009,8 +3003,8 @@ fn parse_try_catch(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// try { body } catch (
|
// try { try_block } catch (
|
||||||
let err_var = if match_token(input, Token::LeftParen).0 {
|
let catch_var = if match_token(input, Token::LeftParen).0 {
|
||||||
let (name, pos) = parse_var_name(input)?;
|
let (name, pos) = parse_var_name(input)?;
|
||||||
let (matched, err_pos) = match_token(input, Token::RightParen);
|
let (matched, err_pos) = match_token(input, Token::RightParen);
|
||||||
|
|
||||||
@ -3029,16 +3023,21 @@ fn parse_try_catch(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// try { body } catch ( var ) { catch_block }
|
// try { try_block } catch ( var ) { catch_block }
|
||||||
let catch_body = parse_block(input, state, lib, settings.level_up())?;
|
let catch_block = parse_block(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
if err_var.is_some() {
|
if catch_var.is_some() {
|
||||||
// Remove the error variable from the stack
|
// Remove the error variable from the stack
|
||||||
state.stack.pop().unwrap();
|
state.stack.pop().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Stmt::TryCatch(
|
Ok(Stmt::TryCatch(
|
||||||
(body.into(), err_var, catch_body.into()).into(),
|
TryCatchBlock {
|
||||||
|
try_block: try_block.into(),
|
||||||
|
catch_var,
|
||||||
|
catch_block: catch_block.into(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
settings.pos,
|
settings.pos,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -317,11 +317,10 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
|
|||||||
foo = calc(foo);
|
foo = calc(foo);
|
||||||
hello = `hello, ${foo} worlds!`;
|
hello = `hello, ${foo} worlds!`;
|
||||||
|
|
||||||
export
|
export x as abc;
|
||||||
x as abc,
|
export x as xxx;
|
||||||
x as xxx,
|
export foo;
|
||||||
foo,
|
export hello;
|
||||||
hello;
|
|
||||||
"#,
|
"#,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user