Fix bug in indexing.

This commit is contained in:
Stephen Chung 2021-07-24 12:27:33 +08:00
parent 106c8ab5d1
commit b8485b1909
6 changed files with 150 additions and 112 deletions

View File

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

View File

@ -1755,10 +1755,10 @@ pub enum Expr {
Stmt(Box<StmtBlock>),
/// func `(` expr `,` ... `)`
FnCall(Box<FnCallExpr>, Position),
/// lhs `.` rhs
Dot(Box<BinaryExpr>, Position),
/// expr `[` expr `]`
Index(Box<BinaryExpr>, Position),
/// lhs `.` rhs - bool variable is a dummy
Dot(Box<BinaryExpr>, bool, Position),
/// expr `[` expr `]` - boolean indicates whether the dotting/indexing chain stops
Index(Box<BinaryExpr>, bool, Position),
/// lhs `&&` rhs
And(Box<BinaryExpr>, 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;
}

View File

@ -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<Dynamic> {
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<ChainArgument>,
chain_type: ChainType,
level: usize,
new_val: Option<((Dynamic, Position), (Option<OpAssignment>, Position))>,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
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::<Map>() => {
Expr::Index(x, term, x_pos) | Expr::Dot(x, term, x_pos)
if target.is::<Map>() =>
{
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<OpAssignment>, 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<ChainArgument>,
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,
)?;

View File

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

View File

@ -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<Position> {
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<Expr, ParseError> {
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())),

View File

@ -53,6 +53,18 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
convert_to_vec::<INT>(engine.eval("let y = [1, 2, 3]; y.insert(-999, 4); y")?),
[4, 1, 2, 3]
);
assert_eq!(
engine.eval::<INT>("let y = [1, 2, 3]; let z = [42]; y[z.len]")?,
2
);
assert_eq!(
engine.eval::<INT>("let y = [1, 2, [3, 4, 5, 6]]; let z = [42]; y[2][z.len]")?,
4
);
assert_eq!(
engine.eval::<INT>("let y = [1, 2, 3]; let z = [2]; y[z[0]]")?,
3
);
assert_eq!(
convert_to_vec::<INT>(engine.eval(