diff --git a/CHANGELOG.md b/CHANGELOG.md index 068a83de..bf9379c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ Bug fixes * 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 ------------ diff --git a/src/ast/expr.rs b/src/ast/expr.rs index bda45d83..8e60551e 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -33,6 +33,16 @@ pub struct BinaryExpr { 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. /// Exported under the `internals` feature only. #[derive(Debug, Clone, Hash)] @@ -362,11 +372,8 @@ pub enum Expr { ), /// Property access - ((getter, hash), (setter, hash), prop) Property( - Box<( - (Identifier, u64), - (Identifier, u64), - (ImmutableString, Position), - )>, + Box<((Identifier, u64), (Identifier, u64), ImmutableString)>, + Position, ), /// Stack slot for function calls. See [`FnCallExpr`] for more details. /// @@ -436,7 +443,7 @@ impl fmt::Debug for Expr { } 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::Stmt(x) => { f.write_str("ExprStmtBlock")?; @@ -648,9 +655,9 @@ impl Expr { | Self::FnCall(_, pos) | Self::Index(_, _, 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::And(x, _) | Self::Or(x, _) | Self::Dot(x, _, _) => x.lhs.position(), @@ -679,9 +686,9 @@ impl Expr { | Self::Stack(_, pos) | Self::FnCall(_, 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), } @@ -781,7 +788,7 @@ impl Expr { _ => false, }, - Self::Property(_) => match token { + Self::Property(_, _) => match token { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, Token::LeftParen => true, diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 3b269a71..aebf79fc 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -13,7 +13,7 @@ pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS}; pub use ident::Ident; #[cfg(not(feature = "no_function"))] 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"))] pub use expr::FloatWrapper; diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 10f21fb0..f9f6717c 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -1,6 +1,6 @@ //! 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::tokenizer::Token; 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, + /// Statements block. + pub statements: StmtBlock, +} + +impl> From<(Option, B)> for ConditionalStmtBlock { + #[inline(always)] + fn from(value: (Option, 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>, + /// 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, + /// `catch` block. + pub catch_block: StmtBlock, +} + /// _(internals)_ A scoped block of statements. /// Exported under the `internals` feature only. #[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 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Block")?; @@ -214,15 +280,7 @@ pub enum Stmt { /// 0) Hash table for (condition, block) /// 1) Default block /// 2) List of ranges: (start, end, inclusive, condition, statement) - Switch( - Expr, - Box<( - BTreeMap, StmtBlock)>>, - StmtBlock, - StaticVec<(INT, INT, bool, Option, StmtBlock)>, - )>, - Position, - ), + Switch(Expr, Box, Position), /// `while` expr `{` stmt `}` | `loop` `{` stmt `}` /// /// 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` Var(Expr, Box, OptionFlags, Position), /// expr op`=` expr - Assignment(Box<(Expr, Option>, Expr)>, Position), + Assignment(Box<(Option>, BinaryExpr)>, Position), /// func `(` expr `,` ... `)` /// /// 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`;` ... `}` Block(Box<[Stmt]>, Position), /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` - TryCatch(Box<(StmtBlock, Option, StmtBlock)>, Position), + TryCatch(Box, Position), /// [expression][Expr] Expr(Expr), /// `continue`/`break` @@ -275,11 +333,11 @@ pub enum Stmt { /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] Import(Expr, Option>, Position), - /// `export` var `as` var `,` ... + /// `export` var `as` var /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] - Export(Box<[(Ident, Ident)]>, Position), + Export(Box<(Ident, Ident)>, Position), /// Convert a variable to shared. /// /// Not available under `no_closure`. @@ -443,20 +501,20 @@ impl Stmt { Self::Expr(expr) => expr.is_pure(), Self::If(condition, x, _) => { condition.is_pure() - && (x.0).0.iter().all(Stmt::is_pure) - && (x.1).0.iter().all(Stmt::is_pure) + && x.0.iter().all(Stmt::is_pure) + && x.1.iter().all(Stmt::is_pure) } Self::Switch(expr, x, _) => { expr.is_pure() - && x.0.values().all(|block| { - block.0.as_ref().map(Expr::is_pure).unwrap_or(true) - && (block.1).0.iter().all(Stmt::is_pure) + && x.cases.values().all(|block| { + block.condition.as_ref().map(Expr::is_pure).unwrap_or(true) + && block.statements.iter().all(Stmt::is_pure) }) - && (x.2).iter().all(|(_, _, _, condition, stmt)| { - condition.as_ref().map(Expr::is_pure).unwrap_or(true) - && stmt.0.iter().all(Stmt::is_pure) + && x.ranges.iter().all(|(_, _, _, block)| { + block.condition.as_ref().map(Expr::is_pure).unwrap_or(true) + && 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. @@ -472,13 +530,13 @@ impl Stmt { // For loops can be pure because if the iterable is pure, it is finite, // so infinite loops can never occur. - Self::For(iterable, x, _) => iterable.is_pure() && (x.2).0.iter().all(Stmt::is_pure), + Self::For(iterable, x, _) => iterable.is_pure() && x.2.iter().all(Stmt::is_pure), Self::Var(_, _, _, _) | Self::Assignment(_, _) | Self::FnCall(_, _) => false, Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), Self::BreakLoop(_, _) | Self::Return(_, _, _) => false, 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"))] @@ -574,12 +632,12 @@ impl Stmt { if !e.walk(path, on_node) { return false; } - for s in &(x.0).0 { + for s in x.0.iter() { if !s.walk(path, on_node) { return false; } } - for s in &(x.1).0 { + for s in x.1.iter() { if !s.walk(path, on_node) { return false; } @@ -589,27 +647,37 @@ impl Stmt { if !e.walk(path, on_node) { return false; } - for b in x.0.values() { - if !b.0.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) { + for b in x.cases.values() { + if !b + .condition + .as_ref() + .map(|e| e.walk(path, on_node)) + .unwrap_or(true) + { return false; } - for s in &(b.1).0 { + for s in b.statements.iter() { if !s.walk(path, on_node) { return false; } } } - for (_, _, _, c, stmt) in &x.2 { - if !c.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) { + for (_, _, _, b) in &x.ranges { + if !b + .condition + .as_ref() + .map(|e| e.walk(path, on_node)) + .unwrap_or(true) + { return false; } - for s in &stmt.0 { + for s in b.statements.iter() { if !s.walk(path, on_node) { return false; } } } - for s in &(x.1).0 { + for s in x.def_case.iter() { if !s.walk(path, on_node) { return false; } @@ -629,17 +697,17 @@ impl Stmt { if !e.walk(path, on_node) { return false; } - for s in &(x.2).0 { + for s in x.2.iter() { if !s.walk(path, on_node) { return false; } } } Self::Assignment(x, _) => { - if !x.0.walk(path, on_node) { + if !x.1.lhs.walk(path, on_node) { return false; } - if !x.2.walk(path, on_node) { + if !x.1.rhs.walk(path, on_node) { return false; } } @@ -658,12 +726,12 @@ impl Stmt { } } Self::TryCatch(x, _) => { - for s in &(x.0).0 { + for s in x.try_block.iter() { if !s.walk(path, on_node) { return false; } } - for s in &(x.2).0 { + for s in x.catch_block.iter() { if !s.walk(path, on_node) { return false; } diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 1a7c0407..53cd1767 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -290,13 +290,12 @@ impl Engine { unreachable!("function call in dot chain should not be namespace-qualified") } // {xxx:map}.id op= ??? - Expr::Property(x) if target.is::() && new_val.is_some() => { + Expr::Property(x, pos) if target.is::() && new_val.is_some() => { #[cfg(feature = "debugging")] 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 index = name.into(); { let val_target = &mut self.get_indexed_mut( global, state, lib, target, index, *pos, true, false, level, @@ -311,23 +310,22 @@ impl Engine { Ok((Dynamic::UNIT, true)) } // {xxx:map}.id - Expr::Property(x) if target.is::() => { + Expr::Property(x, pos) if target.is::() => { #[cfg(feature = "debugging")] self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?; - let (name, pos) = &x.2; - let index = name.into(); + let index = x.2.clone().into(); let val = self.get_indexed_mut( global, state, lib, target, index, *pos, false, false, level, )?; Ok((val.take_or_clone(), false)) } // xxx.id op= ??? - Expr::Property(x) if new_val.is_some() => { + Expr::Property(x, pos) if new_val.is_some() => { #[cfg(feature = "debugging")] 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`"); if op_info.is_some() { @@ -402,11 +400,11 @@ impl Engine { }) } // xxx.id - Expr::Property(x) => { + Expr::Property(x, pos) => { #[cfg(feature = "debugging")] 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 args = &mut [target.as_mut()]; self.exec_fn_call( @@ -442,16 +440,15 @@ impl Engine { let _node = &x.lhs; let val_target = &mut match x.lhs { - Expr::Property(ref p) => { + Expr::Property(ref p, pos) => { #[cfg(feature = "debugging")] self.run_debugger( scope, global, state, lib, this_ptr, _node, level, )?; - let (name, pos) = &p.2; - let index = name.into(); + let index = p.2.clone().into(); 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 @@ -495,14 +492,13 @@ impl Engine { match x.lhs { // xxx.prop[expr] | xxx.prop.expr - Expr::Property(ref p) => { + Expr::Property(ref p, pos) => { #[cfg(feature = "debugging")] self.run_debugger( scope, global, state, lib, this_ptr, _node, level, )?; - let ((getter, hash_get), (setter, hash_set), (name, pos)) = - p.as_ref(); + let ((getter, hash_get), (setter, hash_set), name) = p.as_ref(); let rhs_chain = rhs.into(); let hash_get = crate::ast::FnCallHashes::from_native(*hash_get); let hash_set = crate::ast::FnCallHashes::from_native(*hash_set); @@ -513,15 +509,15 @@ impl Engine { let (mut val, _) = self .exec_fn_call( global, state, lib, getter, hash_get, args, is_ref_mut, - true, *pos, None, level, + true, pos, None, level, ) .or_else(|err| match *err { // Try an indexer if property does not exist ERR::ErrorDotExpr(_, _) => { let prop = name.into(); self.get_indexed_mut( - global, state, lib, target, prop, *pos, false, - true, level, + global, state, lib, target, prop, pos, false, true, + level, ) .map(|v| (v.take_or_clone(), false)) .map_err( @@ -561,7 +557,7 @@ impl Engine { let args = &mut arg_values; self.exec_fn_call( global, state, lib, setter, hash_set, args, is_ref_mut, - true, *pos, None, level, + true, pos, None, level, ) .or_else( |err| match *err { @@ -576,7 +572,7 @@ impl Engine { ); self.exec_fn_call( 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 { ERR::ErrorIndexingType(_, _) => { @@ -754,10 +750,10 @@ impl Engine { } #[cfg(not(feature = "no_object"))] - Expr::Property(x) if _parent_chain_type == ChainType::Dotting => { - idx_values.push(super::ChainArgument::Property((x.2).1)) + Expr::Property(_, pos) if _parent_chain_type == ChainType::Dotting => { + 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 => { let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref(); @@ -765,10 +761,10 @@ impl Engine { // Evaluate in left-to-right order let lhs_arg_val = match lhs { #[cfg(not(feature = "no_object"))] - Expr::Property(x) if _parent_chain_type == ChainType::Dotting => { - super::ChainArgument::Property((x.2).1) + Expr::Property(_, pos) if _parent_chain_type == ChainType::Dotting => { + 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"))] Expr::FnCall(x, _) diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index 75215ecb..8c5c31e9 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -312,7 +312,7 @@ impl Debugger { }, #[cfg(not(feature = "no_object"))] BreakPoint::AtProperty { name, .. } => match node { - ASTNode::Expr(Expr::Property(x)) => (x.2).0 == *name, + ASTNode::Expr(Expr::Property(x, _)) => x.2 == *name, _ => false, }, }) diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 81d47694..70dac04f 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -1,7 +1,9 @@ //! Module defining functions for evaluating a statement. 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::types::dynamic::{AccessMode, Union}; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT}; @@ -225,20 +227,21 @@ impl Engine { #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, stmt.position())?; - let result = if x.0.is_variable_access(false) { - let (lhs_expr, op_info, rhs_expr) = x.as_ref(); + let result = if x.1.lhs.is_variable_access(false) { + let (op_info, BinaryExpr { lhs, rhs }) = x.as_ref(); + 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); if let Ok(rhs_val) = rhs_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 { 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() { return Err( @@ -259,7 +262,7 @@ impl Engine { (var_name, pos), rhs_val, ) - .map_err(|err| err.fill_position(rhs_expr.position())) + .map_err(|err| err.fill_position(rhs.position())) .map(|_| Dynamic::UNIT) } else { search_result.map(|_| Dynamic::UNIT) @@ -268,16 +271,17 @@ impl Engine { rhs_result } } else { - let (lhs_expr, op_info, rhs_expr) = x.as_ref(); + let (op_info, BinaryExpr { lhs, rhs }) = x.as_ref(); + 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); 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` - match lhs_expr { + match lhs { // name op= rhs (handled above) Expr::Variable(_, _, _) => { unreachable!("Expr::Variable case is already handled") @@ -286,17 +290,17 @@ impl Engine { #[cfg(not(feature = "no_index"))] Expr::Index(_, _, _) => self .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), // dot_lhs.dot_rhs op= rhs #[cfg(not(feature = "no_object"))] Expr::Dot(_, _, _) => self .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), - _ => unreachable!("cannot assign to expression: {:?}", lhs_expr), + _ => unreachable!("cannot assign to expression: {:?}", lhs), } } else { rhs_result @@ -362,7 +366,11 @@ impl Engine { // Switch statement 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 = self.eval_expr(scope, global, state, lib, this_ptr, match_expr, level); @@ -374,9 +382,9 @@ impl Engine { let hash = hasher.finish(); // First check hashes - if let Some(t) = table.get(&hash) { + if let Some(t) = cases.get(&hash) { let cond_result = t - .0 + .condition .as_ref() .map(|cond| { self.eval_expr(scope, global, state, lib, this_ptr, cond, level) @@ -392,7 +400,7 @@ impl Engine { .unwrap_or(Ok(true)); match cond_result { - Ok(true) => Ok(Some(&t.1)), + Ok(true) => Ok(Some(&t.statements)), Ok(false) => Ok(None), _ => cond_result.map(|_| None), } @@ -401,13 +409,14 @@ impl Engine { let value = value.as_int().expect("`INT`"); let mut result = Ok(None); - for (_, _, _, condition, stmt_block) in - ranges.iter().filter(|&&(start, end, inclusive, _, _)| { + for (_, _, _, block) in + ranges.iter().filter(|&&(start, end, inclusive, _)| { (!inclusive && (start..end).contains(&value)) || (inclusive && (start..=end).contains(&value)) }) { - let cond_result = condition + let cond_result = block + .condition .as_ref() .map(|cond| { self.eval_expr( @@ -425,7 +434,7 @@ impl Engine { .unwrap_or(Ok(true)); match cond_result { - Ok(true) => result = Ok(Some(stmt_block)), + Ok(true) => result = Ok(Some(&block.statements)), Ok(false) => continue, _ => result = cond_result.map(|_| None), } @@ -453,9 +462,9 @@ impl Engine { } } else if let Ok(None) = stmt_block_result { // Default match clause - if !def_stmt.is_empty() { + if !def_case.is_empty() { 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 { Ok(Dynamic::UNIT) @@ -685,10 +694,14 @@ impl Engine { // Try/Catch statement 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 - .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); match result { @@ -733,12 +746,19 @@ impl Engine { let orig_scope_len = scope.len(); - err_var_name + catch_var .as_ref() .map(|Ident { name, .. }| scope.push(name.clone(), err_value)); 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); @@ -896,21 +916,18 @@ impl Engine { // Export statement #[cfg(not(feature = "no_module"))] - Stmt::Export(list, _) => { - list.iter() - .try_for_each(|(Ident { name, pos, .. }, Ident { name: rename, .. })| { - // Mark scope variables as public - if let Some((index, _)) = scope.get_index(name) { - scope.add_entry_alias( - index, - if rename.is_empty() { name } else { rename }.clone(), - ); - Ok(()) as RhaiResultOf<_> - } else { - Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into()) - } - }) - .map(|_| Dynamic::UNIT) + Stmt::Export(x, _) => { + let (Ident { name, pos, .. }, Ident { name: alias, .. }) = x.as_ref(); + // Mark scope variables as public + if let Some((index, _)) = scope.get_index(name) { + scope.add_entry_alias( + index, + if alias.is_empty() { name } else { alias }.clone(), + ); + Ok(Dynamic::UNIT) + } else { + Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into()) + } } // Share statement diff --git a/src/lib.rs b/src/lib.rs index eaf687f7..58c79df0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -254,8 +254,9 @@ pub use parser::ParseState; #[cfg(feature = "internals")] pub use ast::{ - ASTNode, BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, - OptionFlags, ScriptFnDef, Stmt, StmtBlock, AST_OPTION_FLAGS::*, + ASTNode, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, + OpAssignment, OptionFlags, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock, + AST_OPTION_FLAGS::*, }; #[cfg(feature = "internals")] diff --git a/src/optimizer.rs b/src/optimizer.rs index 1b438502..2bf5da2c 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -412,26 +412,26 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b match stmt { // var = var op expr => var op= expr Stmt::Assignment(x, _) - if x.1.is_none() - && x.0.is_variable_access(true) - && matches!(&x.2, Expr::FnCall(x2, _) + if x.0.is_none() + && x.1.lhs.is_variable_access(true) + && matches!(&x.1.rhs, Expr::FnCall(x2, _) if Token::lookup_from_syntax(&x2.name).map(|t| t.has_op_assignment()).unwrap_or(false) && 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, _) => { 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]); if let Expr::Stack(slot, pos) = value { - x.2 = + x.1.rhs = Expr::from_dynamic(mem::take(x2.constants.get_mut(slot).unwrap()), pos); } else { - x.2 = value; + x.1.rhs = value; } } 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 - Stmt::Assignment(x, _) => match x.0 { - Expr::Variable(_, _, _) => optimize_expr(&mut x.2, state, false), - _ => { - optimize_expr(&mut x.0, state, false); - optimize_expr(&mut x.2, state, false); + Stmt::Assignment(x, _) => { + if !x.1.lhs.is_variable_access(false) { + optimize_expr(&mut x.1.lhs, state, false); } - }, + optimize_expr(&mut x.1.rhs, state, false); + } // if expr {} Stmt::If(condition, x, _) if x.0.is_empty() && x.1.is_empty() => { @@ -502,31 +501,39 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b value.hash(hasher); let hash = hasher.finish(); - let table = &mut x.0; + let cases = &mut x.cases; // First check hashes - if let Some(block) = table.get_mut(&hash) { - if let Some(mut condition) = mem::take(&mut block.0) { + if let Some(block) = cases.get_mut(&hash) { + if let Some(mut condition) = mem::take(&mut block.condition) { // switch const { case 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); + optimize_stmt_block(mem::take(&mut x.def_case), state, true, true, false); *stmt = Stmt::If( condition, Box::new(( - mem::take(&mut block.1), - Stmt::Block(def_stmt.into_boxed_slice(), x.1.position().or_else(*pos)) - .into(), + 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 = - optimize_stmt_block(mem::take(&mut *block.1), state, true, true, false); - *stmt = Stmt::Block(statements.into_boxed_slice(), block.1.position()); + 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(); @@ -534,34 +541,39 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } // Then check ranges - let ranges = &mut x.2; + let ranges = &mut x.ranges; if value.is::() && !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 + if ranges.len() == 1 || ranges.iter().all(|(_, _, _, c)| !c.has_condition()) { + for (_, _, _, block) in ranges .iter_mut() - .filter(|&&mut (start, end, inclusive, _, _)| { + .filter(|&&mut (start, end, inclusive, _)| { (!inclusive && (start..end).contains(&value)) || (inclusive && (start..=end).contains(&value)) }) { - if let Some(mut condition) = mem::take(condition) { + 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.1), state, true, true, 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(stmt_block), + mem::take(&mut block.statements), Stmt::Block( def_stmt.into_boxed_slice(), - x.1.position().or_else(*pos), + x.def_case.position().or_else(*pos), ) .into(), )), @@ -569,11 +581,13 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b ); } else { // Promote the matched case - let statements = mem::take(&mut **stmt_block); + let statements = mem::take(&mut *block.statements); let statements = optimize_stmt_block(statements, state, true, true, false); - *stmt = - Stmt::Block(statements.into_boxed_slice(), stmt_block.position()); + *stmt = Stmt::Block( + statements.into_boxed_slice(), + block.statements.position(), + ); } state.set_dirty(); @@ -581,14 +595,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } } else { // Multiple ranges - clear the table and just keep the right ranges - if !table.is_empty() { + if !cases.is_empty() { state.set_dirty(); - table.clear(); + cases.clear(); } 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)) }); @@ -597,16 +611,16 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b state.set_dirty(); } - for (_, _, _, condition, stmt_block) in ranges.iter_mut() { - let statements = mem::take(&mut **stmt_block); - **stmt_block = + for (_, _, _, block) in ranges.iter_mut() { + let statements = mem::take(&mut *block.statements); + *block.statements = optimize_stmt_block(statements, state, preserve_result, true, false); - if let Some(mut c) = mem::take(condition) { - optimize_expr(&mut c, state, false); - match c { + if let Some(mut condition) = mem::take(&mut block.condition) { + optimize_expr(&mut condition, state, false); + match condition { 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 state.set_dirty(); - let def_stmt = optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false); - *stmt = Stmt::Block(def_stmt.into_boxed_slice(), x.1.position().or_else(*pos)); + let def_stmt = + 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 Stmt::Switch(match_expr, x, _) => { optimize_expr(match_expr, state, false); - x.0.values_mut().for_each(|block| { - let statements = mem::take(&mut *block.1); - *block.1 = optimize_stmt_block(statements, state, preserve_result, true, false); + x.cases.values_mut().for_each(|block| { + let statements = mem::take(&mut *block.statements); + *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); match condition { Expr::Unit(_) | Expr::BoolConstant(true, _) => state.set_dirty(), - _ => block.0 = Some(condition), + _ => block.condition = Some(condition), } } }); // 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, _ => false, }) { 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 @@ -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 - 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 state.set_dirty(); *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(), - x.0.position(), + x.try_block.position(), ); } // try { try_block } catch ( var ) { catch_block } Stmt::TryCatch(x, _) => { - *x.0 = optimize_stmt_block(mem::take(&mut *x.0), state, false, true, false); - *x.2 = optimize_stmt_block(mem::take(&mut *x.2), state, false, true, false); + *x.try_block = + 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(...) 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"))] Expr::Dot(x,_, _) if !_chaining => match (&mut x.lhs, &mut x.rhs) { // map.string - (Expr::Map(m, pos), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => { - let prop = p.2.0.as_str(); + (Expr::Map(m, pos), Expr::Property(p, _)) if m.0.iter().all(|(_, x)| x.is_pure()) => { + let prop = p.2.as_str(); // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); diff --git a/src/parser.rs b/src/parser.rs index 0c814199..c3b06e1a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,8 +3,8 @@ use crate::api::custom_syntax::{markers::*, CustomSyntax}; use crate::api::options::LanguageOptions; use crate::ast::{ - BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt, - StmtBlock, AST_OPTION_FLAGS::*, + BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, + OpAssignment, ScriptFnDef, Stmt, StmtBlock, SwitchCases, TryCatchBlock, AST_OPTION_FLAGS::*, }; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; use crate::func::hashing::get_hasher; @@ -273,11 +273,14 @@ impl Expr { let setter = state.get_identifier(crate::engine::FN_SET, &ident); let hash_set = calc_fn_hash(&setter, 2); - Self::Property(Box::new(( - (getter, hash_get), - (setter, hash_set), - (state.get_interned_string("", &ident), pos), - ))) + Self::Property( + Box::new(( + (getter, hash_get), + (setter, hash_set), + state.get_interned_string("", &ident), + )), + pos, + ) } _ => self, } @@ -985,8 +988,8 @@ fn parse_switch( } } - let mut table = BTreeMap::, StmtBlock)>>::new(); - let mut ranges = StaticVec::<(INT, INT, bool, Option, StmtBlock)>::new(); + let mut cases = BTreeMap::>::new(); + let mut ranges = StaticVec::<(INT, INT, bool, ConditionalStmtBlock)>::new(); let mut def_pos = Position::NONE; let mut def_stmt = None; @@ -1050,7 +1053,7 @@ fn parse_switch( value.hash(hasher); let hash = hasher.finish(); - if table.contains_key(&hash) { + if cases.contains_key(&hash) { return Err(PERR::DuplicatedSwitchCase.into_err(expr.position())); } (Some(hash), None) @@ -1092,18 +1095,20 @@ fn parse_switch( value.hash(hasher); let hash = hasher.finish(); - table - .entry(hash) - .or_insert_with(|| (condition.clone(), stmt.into()).into()); + cases.entry(hash).or_insert_with(|| { + let block: ConditionalStmtBlock = (condition, stmt).into(); + block.into() + }); } // 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 } (Some(hash), None) => { - table.insert(hash, (condition, stmt.into()).into()); + let block: ConditionalStmtBlock = (condition, stmt).into(); + cases.insert(hash, block.into()); None } (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( item, - (table, def_stmt_block, ranges).into(), + SwitchCases { + cases, + def_case, + ranges, + } + .into(), settings.pos, )) } @@ -1692,20 +1702,20 @@ fn make_assignment_stmt( fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option { match expr { Expr::Index(x, term, _) | Expr::Dot(x, term, _) if parent_is_dot => match x.lhs { - Expr::Property(_) if !term => { + Expr::Property(_, _) if !term => { 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 ref e => Some(e.position()), }, 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(_, _, _))), _ => None, }, - Expr::Property(_) if parent_is_dot => None, - Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"), + Expr::Property(_, _) if parent_is_dot => None, + Expr::Property(_, _) => unreachable!("unexpected Expr::Property in indexing"), e if parent_is_dot => Some(e.position()), _ => None, } @@ -1719,9 +1729,10 @@ fn make_assignment_stmt( Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position())) } // var (non-indexed) = rhs - Expr::Variable(None, _, ref x) if x.0.is_none() => { - Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos)) - } + Expr::Variable(None, _, ref x) if x.0.is_none() => Ok(Stmt::Assignment( + (op_info, (lhs, rhs).into()).into(), + op_pos, + )), // var (indexed) = rhs Expr::Variable(i, var_pos, ref x) => { let (index, _, name) = x.as_ref(); @@ -1730,7 +1741,10 @@ fn make_assignment_stmt( |n| n.get() as usize, ); 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 AccessMode::ReadOnly => { Err(PERR::AssignmentToConstant(name.to_string()).into_err(var_pos)) @@ -1749,9 +1763,10 @@ fn make_assignment_stmt( None => { match x.lhs { // var[???] = rhs, var.??? = rhs - Expr::Variable(_, _, _) => { - Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos)) - } + Expr::Variable(_, _, _) => Ok(Stmt::Assignment( + (op_info, (lhs, rhs).into()).into(), + op_pos, + )), // expr[???] = rhs, expr.??? = rhs ref expr => { 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)) } // lhs.prop - (lhs, prop @ Expr::Property(_)) => Ok(Expr::Dot( + (lhs, prop @ Expr::Property(_, _)) => Ok(Expr::Dot( BinaryExpr { lhs, rhs: prop }.into(), false, op_pos, @@ -1844,7 +1859,7 @@ fn make_dot_expr( }; match x.lhs { - Expr::Variable(_, _, _) | Expr::Property(_) => { + Expr::Variable(_, _, _) | Expr::Property(_, _) => { let new_lhs = BinaryExpr { lhs: x.lhs.into_property(state), rhs: x.rhs, @@ -2603,48 +2618,27 @@ fn parse_export( _ => (), } - let mut exports = Vec::<(Ident, Ident)>::with_capacity(4); + let (id, id_pos) = parse_var_name(input)?; - loop { - let (id, id_pos) = parse_var_name(input)?; + let (alias, alias_pos) = if match_token(input, Token::As).0 { + let (name, pos) = parse_var_name(input)?; + (Some(name), pos) + } else { + (None, Position::NONE) + }; - let (rename, rename_pos) = if match_token(input, Token::As).0 { - 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) - } else { - (None, Position::NONE) - }; + let export = ( + Ident { + name: state.get_identifier("", id), + pos: id_pos, + }, + Ident { + name: state.get_identifier("", alias.as_ref().map_or("", <_>::as_ref)), + pos: alias_pos, + }, + ); - exports.push(( - Ident { - name: state.get_identifier("", id), - pos: id_pos, - }, - Ident { - name: state.get_identifier("", rename.as_ref().map_or("", <_>::as_ref)), - pos: rename_pos, - }, - )); - - match input.peek().expect(NEVER_ENDS) { - (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)) + Ok(Stmt::Export(export.into(), settings.pos)) } /// Parse a statement block. @@ -2996,10 +2990,10 @@ fn parse_try_catch( let mut settings = settings; settings.pos = eat_token(input, Token::Try); - // try { body } - let body = parse_block(input, state, lib, settings.level_up())?; + // try { try_block } + 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); if !matched { @@ -3009,8 +3003,8 @@ fn parse_try_catch( ); } - // try { body } catch ( - let err_var = if match_token(input, Token::LeftParen).0 { + // try { try_block } catch ( + let catch_var = if match_token(input, Token::LeftParen).0 { let (name, pos) = parse_var_name(input)?; let (matched, err_pos) = match_token(input, Token::RightParen); @@ -3029,16 +3023,21 @@ fn parse_try_catch( None }; - // try { body } catch ( var ) { catch_block } - let catch_body = parse_block(input, state, lib, settings.level_up())?; + // try { try_block } catch ( var ) { catch_block } + 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 state.stack.pop().unwrap(); } 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, )) } diff --git a/tests/modules.rs b/tests/modules.rs index 6f8e56b7..9b3e62d4 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -317,11 +317,10 @@ fn test_module_from_ast() -> Result<(), Box> { foo = calc(foo); hello = `hello, ${foo} worlds!`; - export - x as abc, - x as xxx, - foo, - hello; + export x as abc; + export x as xxx; + export foo; + export hello; "#, )?;