From b8485b190915820190b7acb50df17beb0089e2a1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 24 Jul 2021 12:27:33 +0800 Subject: [PATCH] Fix bug in indexing. --- CHANGELOG.md | 9 ++++ src/ast.rs | 34 ++++++++----- src/engine.rs | 127 +++++++++++++++++++++++++----------------------- src/optimize.rs | 8 +-- src/parse.rs | 72 ++++++++++++++------------- tests/arrays.rs | 12 +++++ 6 files changed, 150 insertions(+), 112 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbdbf134..4f06be45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Rhai Release Notes ================== +Version 1.0.1 +============= + +Bug fixes +--------- + +* Fixed bug in using indexing/dotting inside index bracket. + + Version 1.0.0 ============= diff --git a/src/ast.rs b/src/ast.rs index a7d882e9..31643ed6 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1755,10 +1755,10 @@ pub enum Expr { Stmt(Box), /// func `(` expr `,` ... `)` FnCall(Box, Position), - /// lhs `.` rhs - Dot(Box, Position), - /// expr `[` expr `]` - Index(Box, Position), + /// lhs `.` rhs - bool variable is a dummy + Dot(Box, bool, Position), + /// expr `[` expr `]` - boolean indicates whether the dotting/indexing chain stops + Index(Box, bool, Position), /// lhs `&&` rhs And(Box, Position), /// lhs `||` rhs @@ -1835,10 +1835,18 @@ impl fmt::Debug for Expr { } ff.finish() } - Self::Dot(x, pos) | Self::Index(x, pos) | Self::And(x, pos) | Self::Or(x, pos) => { + Self::Index(x, term, pos) => { + display_pos = *pos; + + f.debug_struct("Index") + .field("lhs", &x.lhs) + .field("rhs", &x.rhs) + .field("terminate", term) + .finish() + } + Self::Dot(x, _, pos) | Self::And(x, pos) | Self::Or(x, pos) => { let op_name = match self { - Self::Dot(_, _) => "Dot", - Self::Index(_, _) => "Index", + Self::Dot(_, _, _) => "Dot", Self::And(_, _) => "And", Self::Or(_, _) => "Or", _ => unreachable!(), @@ -1969,7 +1977,7 @@ impl Expr { Self::Property(x) => (x.2).1, Self::Stmt(x) => x.1, - Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _) | Self::Index(x, _) => { + Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _, _) | Self::Index(x, _, _) => { x.lhs.position() } } @@ -1991,8 +1999,8 @@ impl Expr { | Self::Map(_, pos) | Self::And(_, pos) | Self::Or(_, pos) - | Self::Dot(_, pos) - | Self::Index(_, pos) + | Self::Dot(_, _, pos) + | Self::Index(_, _, pos) | Self::Variable(_, pos, _) | Self::Stack(_, pos) | Self::FnCall(_, pos) @@ -2083,8 +2091,8 @@ impl Expr { | Self::InterpolatedString(_, _) | Self::FnCall(_, _) | Self::Stmt(_) - | Self::Dot(_, _) - | Self::Index(_, _) + | Self::Dot(_, _, _) + | Self::Index(_, _, _) | Self::Array(_, _) | Self::Map(_, _) => match token { #[cfg(not(feature = "no_index"))] @@ -2149,7 +2157,7 @@ impl Expr { } } } - Self::Index(x, _) | Self::Dot(x, _) | Expr::And(x, _) | Expr::Or(x, _) => { + Self::Index(x, _, _) | Self::Dot(x, _, _) | Expr::And(x, _) | Expr::Or(x, _) => { if !x.lhs.walk(path, on_node) { return false; } diff --git a/src/engine.rs b/src/engine.rs index c34d8c2f..34932253 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -293,10 +293,10 @@ pub const TOKEN_OP_CONCAT: Token = Token::PlusAssign; enum ChainType { /// Indexing. #[cfg(not(feature = "no_index"))] - Index, + Indexing, /// Dotting. #[cfg(not(feature = "no_object"))] - Dot, + Dotting, } /// Value of a chaining argument. @@ -324,6 +324,7 @@ impl ChainArgument { pub fn as_index_value(self) -> Option { match self { Self::IndexValue(value, _) => Some(value), + #[cfg(not(feature = "no_object"))] _ => None, } } @@ -355,6 +356,18 @@ impl From<(Dynamic, Position)> for ChainArgument { } } +/// Get the chaining type for an [`Expr`]. +#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] +fn match_chaining_type(expr: &Expr) -> ChainType { + match expr { + #[cfg(not(feature = "no_index"))] + Expr::Index(_, _, _) => ChainType::Indexing, + #[cfg(not(feature = "no_object"))] + Expr::Dot(_, _, _) => ChainType::Dotting, + _ => unreachable!("`expr` should only be `Index` or `Dot`, but got {:?}", expr), + } +} + /// A type that encapsulates a mutation target for an expression with side effects. #[derive(Debug)] pub enum Target<'a> { @@ -1230,21 +1243,12 @@ impl Engine { target: &mut Target, root: (&str, Position), rhs: &Expr, + _terminate_chaining: bool, idx_values: &mut StaticVec, chain_type: ChainType, level: usize, new_val: Option<((Dynamic, Position), (Option, Position))>, ) -> Result<(Dynamic, bool), Box> { - fn match_chain_type(expr: &Expr) -> ChainType { - match expr { - #[cfg(not(feature = "no_index"))] - Expr::Index(_, _) => ChainType::Index, - #[cfg(not(feature = "no_object"))] - Expr::Dot(_, _) => ChainType::Dot, - _ => unreachable!("`expr` should only be `Index` or `Dot`, but got {:?}", expr), - } - } - let is_ref_mut = target.is_ref(); // Pop the last index value @@ -1254,7 +1258,7 @@ impl Engine { match chain_type { #[cfg(not(feature = "no_index"))] - ChainType::Index => { + ChainType::Indexing => { let pos = rhs.position(); let idx_val = idx_val .as_index_value() @@ -1262,15 +1266,17 @@ impl Engine { match rhs { // xxx[idx].expr... | xxx[idx][expr]... - Expr::Dot(x, x_pos) | Expr::Index(x, x_pos) => { + Expr::Dot(x, term, x_pos) | Expr::Index(x, term, x_pos) + if !_terminate_chaining => + { let idx_pos = x.lhs.position(); let obj_ptr = &mut self.get_indexed_mut( mods, state, lib, target, idx_val, idx_pos, false, true, level, )?; - let rhs_chain = match_chain_type(rhs); + let rhs_chain = match_chaining_type(rhs); self.eval_dot_index_chain_helper( - mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, idx_values, + mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, *term, idx_values, rhs_chain, level, new_val, ) .map_err(|err| err.fill_position(*x_pos)) @@ -1327,7 +1333,7 @@ impl Engine { } #[cfg(not(feature = "no_object"))] - ChainType::Dot => { + ChainType::Dotting => { match rhs { // xxx.fn_name(arg_expr_list) Expr::FnCall(x, pos) if !x.is_qualified() && new_val.is_none() => { @@ -1485,7 +1491,9 @@ impl Engine { ) } // {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr - Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target.is::() => { + Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos) + if target.is::() => + { let val_target = &mut match x.lhs { Expr::Property(ref p) => { let (name, pos) = &p.2; @@ -1512,22 +1520,22 @@ impl Engine { // Others - syntax error ref expr => unreachable!("invalid dot expression: {:?}", expr), }; - let rhs_chain = match_chain_type(rhs); + let rhs_chain = match_chaining_type(rhs); self.eval_dot_index_chain_helper( - mods, state, lib, this_ptr, val_target, root, &x.rhs, idx_values, - rhs_chain, level, new_val, + mods, state, lib, this_ptr, val_target, root, &x.rhs, *term, + idx_values, rhs_chain, level, new_val, ) .map_err(|err| err.fill_position(*x_pos)) } // xxx.sub_lhs[expr] | xxx.sub_lhs.expr - Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) => { + Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos) => { match x.lhs { // xxx.prop[expr] | xxx.prop.expr Expr::Property(ref p) => { let ((getter, hash_get), (setter, hash_set), (name, pos)) = p.as_ref(); - let rhs_chain = match_chain_type(rhs); + let rhs_chain = match_chaining_type(rhs); let hash_get = FnCallHashes::from_native(*hash_get); let hash_set = FnCallHashes::from_native(*hash_set); let mut arg_values = [target.as_mut(), &mut Default::default()]; @@ -1567,6 +1575,7 @@ impl Engine { &mut val.into(), root, &x.rhs, + *term, idx_values, rhs_chain, level, @@ -1617,7 +1626,7 @@ impl Engine { // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::FnCall(ref f, pos) if !f.is_qualified() => { let FnCallExpr { name, hashes, .. } = f.as_ref(); - let rhs_chain = match_chain_type(rhs); + let rhs_chain = match_chaining_type(rhs); let args = &mut idx_val .as_fn_call_args() .expect("never fails because `chain_type` is `ChainType::Dot` with `Expr::FnCallExpr`"); @@ -1628,8 +1637,8 @@ impl Engine { let target = &mut val.into(); self.eval_dot_index_chain_helper( - mods, state, lib, this_ptr, target, root, &x.rhs, idx_values, - rhs_chain, level, new_val, + mods, state, lib, this_ptr, target, root, &x.rhs, *term, + idx_values, rhs_chain, level, new_val, ) .map_err(|err| err.fill_position(pos)) } @@ -1663,18 +1672,18 @@ impl Engine { level: usize, new_val: Option<((Dynamic, Position), (Option, Position))>, ) -> RhaiResult { - let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, op_pos) = match expr { + let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, term, op_pos) = match expr { #[cfg(not(feature = "no_index"))] - Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos), + Expr::Index(x, term, pos) => (x.as_ref(), ChainType::Indexing, *term, *pos), #[cfg(not(feature = "no_object"))] - Expr::Dot(x, pos) => (x.as_ref(), ChainType::Dot, *pos), + Expr::Dot(x, term, pos) => (x.as_ref(), ChainType::Dotting, *term, *pos), _ => unreachable!("index or dot chain expected, but gets {:?}", expr), }; let idx_values = &mut Default::default(); - self.eval_indexed_chain( - scope, mods, state, lib, this_ptr, rhs, chain_type, idx_values, 0, level, + self.eval_dot_index_chain_arguments( + scope, mods, state, lib, this_ptr, rhs, term, chain_type, idx_values, 0, level, )?; match lhs { @@ -1687,9 +1696,10 @@ impl Engine { let obj_ptr = &mut target.into(); let root = (x.2.as_str(), *var_pos); + self.eval_dot_index_chain_helper( - mods, state, lib, &mut None, obj_ptr, root, rhs, idx_values, chain_type, level, - new_val, + mods, state, lib, &mut None, obj_ptr, root, rhs, term, idx_values, chain_type, + level, new_val, ) .map(|(v, _)| v) .map_err(|err| err.fill_position(op_pos)) @@ -1702,8 +1712,8 @@ impl Engine { let obj_ptr = &mut value.into(); let root = ("", expr.position()); self.eval_dot_index_chain_helper( - mods, state, lib, this_ptr, obj_ptr, root, rhs, idx_values, chain_type, level, - new_val, + mods, state, lib, this_ptr, obj_ptr, root, rhs, term, idx_values, chain_type, + level, new_val, ) .map(|(v, _)| v) .map_err(|err| err.fill_position(op_pos)) @@ -1716,7 +1726,7 @@ impl Engine { /// just a few levels of indexing. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[must_use] - fn eval_indexed_chain( + fn eval_dot_index_chain_arguments( &self, scope: &mut Scope, mods: &mut Imports, @@ -1724,6 +1734,7 @@ impl Engine { lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, + terminate_chaining: bool, _parent_chain_type: ChainType, idx_values: &mut StaticVec, size: usize, @@ -1734,7 +1745,7 @@ impl Engine { match expr { #[cfg(not(feature = "no_object"))] - Expr::FnCall(x, _) if _parent_chain_type == ChainType::Dot && !x.is_qualified() => { + Expr::FnCall(x, _) if _parent_chain_type == ChainType::Dotting && !x.is_qualified() => { let crate::ast::FnCallExpr { args, constants, .. } = x.as_ref(); @@ -1754,30 +1765,30 @@ impl Engine { idx_values.push((arg_values, first_arg_pos).into()); } #[cfg(not(feature = "no_object"))] - Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dot => { + Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dotting => { unreachable!("function call in dot chain should not be namespace-qualified") } #[cfg(not(feature = "no_object"))] - Expr::Property(x) if _parent_chain_type == ChainType::Dot => { + Expr::Property(x) if _parent_chain_type == ChainType::Dotting => { idx_values.push(ChainArgument::Property((x.2).1)) } Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"), - Expr::Index(x, _) | Expr::Dot(x, _) => { + Expr::Index(x, term, _) | Expr::Dot(x, term, _) if !terminate_chaining => { let crate::ast::BinaryExpr { lhs, rhs, .. } = x.as_ref(); // Evaluate in left-to-right order let lhs_val = match lhs { #[cfg(not(feature = "no_object"))] - Expr::Property(x) if _parent_chain_type == ChainType::Dot => { + Expr::Property(x) if _parent_chain_type == ChainType::Dotting => { ChainArgument::Property((x.2).1) } Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"), #[cfg(not(feature = "no_object"))] Expr::FnCall(x, _) - if _parent_chain_type == ChainType::Dot && !x.is_qualified() => + if _parent_chain_type == ChainType::Dotting && !x.is_qualified() => { let crate::ast::FnCallExpr { args, constants, .. @@ -1798,41 +1809,37 @@ impl Engine { (arg_values, first_arg_pos).into() } #[cfg(not(feature = "no_object"))] - Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dot => { + Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dotting => { unreachable!("function call in dot chain should not be namespace-qualified") } #[cfg(not(feature = "no_object"))] - expr if _parent_chain_type == ChainType::Dot => { + expr if _parent_chain_type == ChainType::Dotting => { unreachable!("invalid dot expression: {:?}", expr); } #[cfg(not(feature = "no_index"))] - _ if _parent_chain_type == ChainType::Index => self + _ if _parent_chain_type == ChainType::Indexing => self .eval_expr(scope, mods, state, lib, this_ptr, lhs, level) .map(|v| (v.flatten(), lhs.position()).into())?, expr => unreachable!("unknown chained expression: {:?}", expr), }; // Push in reverse order - let chain_type = match expr { - #[cfg(not(feature = "no_index"))] - Expr::Index(_, _) => ChainType::Index, - #[cfg(not(feature = "no_object"))] - Expr::Dot(_, _) => ChainType::Dot, - _ => unreachable!("index or dot chain expected, but gets {:?}", expr), - }; - self.eval_indexed_chain( - scope, mods, state, lib, this_ptr, rhs, chain_type, idx_values, size, level, + let chain_type = match_chaining_type(expr); + + self.eval_dot_index_chain_arguments( + scope, mods, state, lib, this_ptr, rhs, *term, chain_type, idx_values, size, + level, )?; idx_values.push(lhs_val); } #[cfg(not(feature = "no_object"))] - _ if _parent_chain_type == ChainType::Dot => { + _ if _parent_chain_type == ChainType::Dotting => { unreachable!("invalid dot expression: {:?}", expr); } #[cfg(not(feature = "no_index"))] - _ if _parent_chain_type == ChainType::Index => idx_values.push( + _ if _parent_chain_type == ChainType::Indexing => idx_values.push( self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) .map(|v| (v.flatten(), expr.position()).into())?, ), @@ -2054,13 +2061,13 @@ impl Engine { // lhs[idx_expr] #[cfg(not(feature = "no_index"))] - Expr::Index(_, _) => { + Expr::Index(_, _, _) => { self.eval_dot_index_chain(scope, mods, state, lib, this_ptr, expr, level, None) } // lhs.dot_rhs #[cfg(not(feature = "no_object"))] - Expr::Dot(_, _) => { + Expr::Dot(_, _, _) => { self.eval_dot_index_chain(scope, mods, state, lib, this_ptr, expr, level, None) } @@ -2449,7 +2456,7 @@ impl Engine { } // idx_lhs[idx_expr] op= rhs #[cfg(not(feature = "no_index"))] - Expr::Index(_, _) => { + Expr::Index(_, _, _) => { self.eval_dot_index_chain( scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val, )?; @@ -2457,7 +2464,7 @@ impl Engine { } // dot_lhs.dot_rhs op= rhs #[cfg(not(feature = "no_object"))] - Expr::Dot(_, _) => { + Expr::Dot(_, _, _) => { self.eval_dot_index_chain( scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val, )?; diff --git a/src/optimize.rs b/src/optimize.rs index 70ad4e6e..76694171 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -706,7 +706,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { } // lhs.rhs #[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 (Expr::Map(m, pos), Expr::Property(p)) if m.0.iter().all(|(_, x)| x.is_pure()) => { let prop = p.2.0.as_str(); @@ -724,11 +724,11 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { } // ....lhs.rhs #[cfg(not(feature = "no_object"))] - Expr::Dot(x, _) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); } + Expr::Dot(x,_, _) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); } // lhs[rhs] #[cfg(not(feature = "no_index"))] - Expr::Index(x, _) if !_chaining => match (&mut x.lhs, &mut x.rhs) { + Expr::Index(x, _, _) if !_chaining => match (&mut x.lhs, &mut x.rhs) { // array[int] (Expr::Array(a, pos), Expr::IntegerConstant(i, _)) if *i >= 0 && (*i as usize) < a.len() && a.iter().all(Expr::is_pure) => @@ -792,7 +792,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { }, // ...[lhs][rhs] #[cfg(not(feature = "no_index"))] - Expr::Index(x, _) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); } + Expr::Index(x, _, _) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); } // `` Expr::InterpolatedString(x, pos) if x.is_empty() => { state.set_dirty(); diff --git a/src/parse.rs b/src/parse.rs index 657ce113..c7d3cf8c 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -700,12 +700,14 @@ fn parse_index_chain( // Indexing binds to right Ok(Expr::Index( BinaryExpr { lhs, rhs: idx_expr }.into(), + false, prev_pos, )) } // Otherwise terminate the indexing chain _ => Ok(Expr::Index( BinaryExpr { lhs, rhs: idx_expr }.into(), + true, settings.pos, )), } @@ -1396,7 +1398,7 @@ fn parse_primary( let rhs = parse_primary(input, state, lib, settings.level_up())?; - make_dot_expr(state, expr, rhs, tail_pos)? + make_dot_expr(state, expr, false, rhs, tail_pos)? } // Unknown postfix operator (expr, token) => unreachable!( @@ -1410,7 +1412,7 @@ fn parse_primary( // Cache the hash key for namespace-qualified variables match root_expr { Expr::Variable(_, _, ref mut x) if x.1.is_some() => Some(x.as_mut()), - Expr::Index(ref mut x, _) | Expr::Dot(ref mut x, _) => match x.lhs { + Expr::Index(ref mut x, _, _) | Expr::Dot(ref mut x, _, _) => match x.lhs { Expr::Variable(_, _, ref mut x) if x.1.is_some() => Some(x.as_mut()), _ => None, }, @@ -1539,13 +1541,13 @@ fn make_assignment_stmt<'a>( #[must_use] fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option { match expr { - Expr::Index(x, _) | Expr::Dot(x, _) if parent_is_dot => match x.lhs { - Expr::Property(_) => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _))), + Expr::Index(x, _, _) | Expr::Dot(x, _, _) if parent_is_dot => match x.lhs { + Expr::Property(_) => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))), ref e => Some(e.position()), }, - Expr::Index(x, _) | Expr::Dot(x, _) => match x.lhs { + Expr::Index(x, _, _) | Expr::Dot(x, _, _) => match x.lhs { Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"), - _ => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _))), + _ => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))), }, Expr::Property(_) if parent_is_dot => None, Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"), @@ -1587,8 +1589,8 @@ fn make_assignment_stmt<'a>( } } // xxx[???]... = rhs, xxx.prop... = rhs - Expr::Index(ref x, _) | Expr::Dot(ref x, _) => { - match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) { + Expr::Index(ref x, _, _) | Expr::Dot(ref x, _, _) => { + match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _, _))) { None => match x.lhs { // var[???] = rhs, var.??? = rhs Expr::Variable(_, _, _) => { @@ -1645,41 +1647,38 @@ fn parse_op_assignment_stmt( fn make_dot_expr( state: &mut ParseState, lhs: Expr, + terminate_chaining: bool, rhs: Expr, op_pos: Position, ) -> Result { Ok(match (lhs, rhs) { - // idx_lhs[idx_expr].rhs - // Attach dot chain to the bottom level of indexing chain - (Expr::Index(mut x, pos), rhs) => { - x.rhs = make_dot_expr(state, x.rhs, rhs, op_pos)?; - Expr::Index(x, pos) + // lhs[???]...[???].rhs + (Expr::Index(mut x, false, pos), rhs) if !terminate_chaining => { + // Attach dot chain to the bottom level of indexing chain + x.rhs = make_dot_expr(state, x.rhs, false, rhs, op_pos)?; + Expr::Index(x, false, pos) + } + // lhs[idx_expr].rhs + (Expr::Index(mut x, _, pos), rhs) => { + x.rhs = make_dot_expr(state, x.rhs, true, rhs, op_pos)?; + Expr::Index(x, false, pos) } // lhs.id - (lhs, Expr::Variable(_, var_pos, x)) if x.1.is_none() => { - let ident = x.2; - let getter = state.get_identifier(crate::engine::make_getter(&ident)); - let hash_get = calc_fn_hash(&getter, 1); - let setter = state.get_identifier(crate::engine::make_setter(&ident)); - let hash_set = calc_fn_hash(&setter, 2); - - let rhs = Expr::Property(Box::new(( - (getter, hash_get), - (setter, hash_set), - (state.get_identifier(ident).into(), var_pos), - ))); - - Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos) + (lhs, var_expr @ Expr::Variable(_, _, _)) if var_expr.is_variable_access(true) => { + let rhs = var_expr.into_property(state); + Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos) } // lhs.module::id - syntax error - (_, Expr::Variable(_, _, x)) if x.1.is_some() => { + (_, Expr::Variable(_, _, x)) => { return Err(PERR::PropertyExpected .into_err(x.1.expect("never fails because the namespace is `Some`").0[0].pos)) } // lhs.prop - (lhs, prop @ Expr::Property(_)) => Expr::Dot(BinaryExpr { lhs, rhs: prop }.into(), op_pos), + (lhs, prop @ Expr::Property(_)) => { + Expr::Dot(BinaryExpr { lhs, rhs: prop }.into(), false, op_pos) + } // lhs.dot_lhs.dot_rhs - (lhs, Expr::Dot(x, pos)) => match x.lhs { + (lhs, Expr::Dot(x, _, pos)) => match x.lhs { Expr::Variable(_, _, _) | Expr::Property(_) => { let rhs = Expr::Dot( BinaryExpr { @@ -1687,9 +1686,10 @@ fn make_dot_expr( rhs: x.rhs, } .into(), + false, pos, ); - Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos) + Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos) } Expr::FnCall(mut func, func_pos) => { // Recalculate hash @@ -1704,23 +1704,25 @@ fn make_dot_expr( rhs: x.rhs, } .into(), + false, pos, ); - Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos) + Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos) } _ => unreachable!("invalid dot expression: {:?}", x.lhs), }, // lhs.idx_lhs[idx_rhs] - (lhs, Expr::Index(x, pos)) => { + (lhs, Expr::Index(x, term, pos)) => { let rhs = Expr::Index( BinaryExpr { lhs: x.lhs.into_property(state), rhs: x.rhs, } .into(), + term, pos, ); - Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos) + Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos) } // lhs.nnn::func(...) (_, Expr::FnCall(x, _)) if x.is_qualified() => { @@ -1756,7 +1758,7 @@ fn make_dot_expr( calc_fn_hash(&func.name, func.args.len() + 1), ); let rhs = Expr::FnCall(func, func_pos); - Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos) + Expr::Dot(BinaryExpr { lhs, rhs }.into(), false, op_pos) } // lhs.rhs (_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())), diff --git a/tests/arrays.rs b/tests/arrays.rs index eb17d0a9..486bfbe8 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -53,6 +53,18 @@ fn test_arrays() -> Result<(), Box> { convert_to_vec::(engine.eval("let y = [1, 2, 3]; y.insert(-999, 4); y")?), [4, 1, 2, 3] ); + assert_eq!( + engine.eval::("let y = [1, 2, 3]; let z = [42]; y[z.len]")?, + 2 + ); + assert_eq!( + engine.eval::("let y = [1, 2, [3, 4, 5, 6]]; let z = [42]; y[2][z.len]")?, + 4 + ); + assert_eq!( + engine.eval::("let y = [1, 2, 3]; let z = [2]; y[z[0]]")?, + 3 + ); assert_eq!( convert_to_vec::(engine.eval(