Allow chaining of indexing (one level) and dotting.
This commit is contained in:
parent
df6950f8f7
commit
eed7bef974
197
src/engine.rs
197
src/engine.rs
@ -220,8 +220,6 @@ impl Engine<'_> {
|
||||
|
||||
// xxx.lhs[idx_expr]
|
||||
Expr::Index(lhs, idx_expr, idx_pos) => {
|
||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||
|
||||
let (lhs_value, _) = match lhs.as_ref() {
|
||||
Expr::Identifier(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
@ -235,6 +233,7 @@ impl Engine<'_> {
|
||||
}
|
||||
};
|
||||
|
||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||
self.get_indexed_value(lhs_value, idx, idx_expr.position(), *idx_pos)
|
||||
.map(|(v, _)| v)
|
||||
}
|
||||
@ -250,8 +249,6 @@ impl Engine<'_> {
|
||||
}
|
||||
// xxx.lhs[idx_expr].rhs
|
||||
Expr::Index(lhs, idx_expr, idx_pos) => {
|
||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||
|
||||
let (lhs_value, _) = match lhs.as_ref() {
|
||||
Expr::Identifier(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
@ -268,6 +265,7 @@ impl Engine<'_> {
|
||||
}
|
||||
};
|
||||
|
||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||
self.get_indexed_value(lhs_value, idx, idx_expr.position(), *idx_pos)
|
||||
.and_then(|(mut value, _)| {
|
||||
self.get_dot_val_helper(scope, value.as_mut(), rhs)
|
||||
@ -403,20 +401,20 @@ impl Engine<'_> {
|
||||
}
|
||||
|
||||
/// Update the value at an index position in a variable inside the scope
|
||||
fn update_indexed_variable_in_scope(
|
||||
fn update_indexed_var_in_scope(
|
||||
src_type: IndexSourceType,
|
||||
scope: &mut Scope,
|
||||
id: &str,
|
||||
src_idx: usize,
|
||||
idx: usize,
|
||||
val: Dynamic,
|
||||
) -> Dynamic {
|
||||
val_pos: Position,
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
match src_type {
|
||||
// array_id[idx] = val
|
||||
IndexSourceType::Array => {
|
||||
let arr = scope.get_mut_by_type::<Array>(id, src_idx);
|
||||
arr[idx as usize] = val;
|
||||
().into_dynamic()
|
||||
Ok((arr[idx as usize] = val).into_dynamic())
|
||||
}
|
||||
|
||||
// string_id[idx] = val
|
||||
@ -425,9 +423,8 @@ impl Engine<'_> {
|
||||
// Value must be a character
|
||||
let ch = *val
|
||||
.downcast::<char>()
|
||||
.expect("char value expected to update an index position in a string");
|
||||
Self::str_replace_char(s, idx as usize, ch);
|
||||
().into_dynamic()
|
||||
.map_err(|_| EvalAltResult::ErrorCharMismatch(val_pos))?;
|
||||
Ok(Self::str_replace_char(s, idx as usize, ch).into_dynamic())
|
||||
}
|
||||
|
||||
// All other variable types should be an error
|
||||
@ -435,6 +432,31 @@ impl Engine<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the value at an index position
|
||||
fn update_indexed_value(
|
||||
mut val: Dynamic,
|
||||
idx: usize,
|
||||
new_val: Dynamic,
|
||||
pos: Position,
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
if val.is::<Array>() {
|
||||
let arr = val.downcast_mut::<Array>().expect("array expected");
|
||||
arr[idx as usize] = new_val;
|
||||
} else if val.is::<String>() {
|
||||
let s = val.downcast_mut::<String>().expect("string expected");
|
||||
// Value must be a character
|
||||
let ch = *new_val
|
||||
.downcast::<char>()
|
||||
.map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?;
|
||||
Self::str_replace_char(s, idx as usize, ch);
|
||||
} else {
|
||||
// All other variable types should be an error
|
||||
panic!("array or string source type expected for indexing")
|
||||
}
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
/// Evaluate a dot chain getter
|
||||
fn get_dot_val(
|
||||
&mut self,
|
||||
@ -443,13 +465,12 @@ impl Engine<'_> {
|
||||
dot_rhs: &Expr,
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
match dot_lhs {
|
||||
// xxx.???
|
||||
// id.???
|
||||
Expr::Identifier(id, pos) => {
|
||||
let (src_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
||||
let value = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs);
|
||||
|
||||
// In case the expression mutated `target`, we need to reassign it because
|
||||
// of the above `clone`.
|
||||
// In case the expression mutated `target`, we need to reassign it because of the above `clone`.
|
||||
*scope.get_mut(id, src_idx) = target;
|
||||
|
||||
value
|
||||
@ -461,12 +482,17 @@ impl Engine<'_> {
|
||||
self.eval_index_expr(scope, lhs, idx_expr, *idx_pos)?;
|
||||
let value = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs);
|
||||
|
||||
// In case the expression mutated `target`, we need to reassign it because
|
||||
// of the above `clone`.
|
||||
// In case the expression mutated `target`, we need to reassign it because of the above `clone`.
|
||||
if let Some((id, src_idx)) = src {
|
||||
Self::update_indexed_variable_in_scope(
|
||||
src_type, scope, id, src_idx, idx, target,
|
||||
);
|
||||
Self::update_indexed_var_in_scope(
|
||||
src_type,
|
||||
scope,
|
||||
id,
|
||||
src_idx,
|
||||
idx,
|
||||
target,
|
||||
lhs.position(),
|
||||
)?;
|
||||
}
|
||||
|
||||
value
|
||||
@ -483,31 +509,54 @@ impl Engine<'_> {
|
||||
/// Chain-evaluate a dot setter
|
||||
fn set_dot_val_helper(
|
||||
&mut self,
|
||||
scope: &mut Scope,
|
||||
this_ptr: &mut Variant,
|
||||
dot_rhs: &Expr,
|
||||
mut source_val: Dynamic,
|
||||
mut new_val: Dynamic,
|
||||
val_pos: Position,
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
match dot_rhs {
|
||||
// xxx.id
|
||||
Expr::Identifier(id, pos) => {
|
||||
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
||||
|
||||
self.call_fn_raw(
|
||||
&set_fn_name,
|
||||
vec![this_ptr, source_val.as_mut()],
|
||||
None,
|
||||
*pos,
|
||||
)
|
||||
self.call_fn_raw(&set_fn_name, vec![this_ptr, new_val.as_mut()], None, *pos)
|
||||
}
|
||||
|
||||
// xxx.lhs.rhs
|
||||
// xxx.lhs[idx_expr]
|
||||
// TODO - Allow chaining of indexing!
|
||||
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
|
||||
// xxx.id[idx_expr]
|
||||
Expr::Identifier(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||
.and_then(|v| {
|
||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||
Self::update_indexed_value(v, idx as usize, new_val, val_pos)
|
||||
})
|
||||
.and_then(|mut v| {
|
||||
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
||||
self.call_fn_raw(&set_fn_name, vec![this_ptr, v.as_mut()], None, *pos)
|
||||
})
|
||||
}
|
||||
|
||||
// All others - syntax error for setters chain
|
||||
_ => Err(EvalAltResult::ErrorDotExpr(
|
||||
"for assignment".to_string(),
|
||||
*idx_pos,
|
||||
)),
|
||||
},
|
||||
|
||||
// xxx.lhs.{...}
|
||||
Expr::Dot(lhs, rhs, _) => match lhs.as_ref() {
|
||||
// xxx.id.rhs
|
||||
Expr::Identifier(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||
.and_then(|mut v| {
|
||||
self.set_dot_val_helper(v.as_mut(), rhs, source_val)
|
||||
self.set_dot_val_helper(scope, v.as_mut(), rhs, new_val, val_pos)
|
||||
.map(|_| v) // Discard Ok return value
|
||||
})
|
||||
.and_then(|mut v| {
|
||||
@ -516,6 +565,55 @@ impl Engine<'_> {
|
||||
self.call_fn_raw(&set_fn_name, vec![this_ptr, v.as_mut()], None, *pos)
|
||||
})
|
||||
}
|
||||
|
||||
// xxx.lhs[idx_expr].rhs
|
||||
// TODO - Allow chaining of indexing!
|
||||
Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() {
|
||||
// xxx.id[idx_expr].rhs
|
||||
Expr::Identifier(id, pos) => {
|
||||
let get_fn_name = format!("{}{}", FUNC_GETTER, id);
|
||||
|
||||
self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)
|
||||
.and_then(|v| {
|
||||
let idx = self.eval_index_value(scope, idx_expr)?;
|
||||
let (mut target, _) = self.get_indexed_value(
|
||||
v.clone(), // TODO - Avoid cloning this
|
||||
idx,
|
||||
idx_expr.position(),
|
||||
*idx_pos,
|
||||
)?;
|
||||
|
||||
self.set_dot_val_helper(
|
||||
scope,
|
||||
target.as_mut(),
|
||||
rhs,
|
||||
new_val,
|
||||
val_pos,
|
||||
)?;
|
||||
|
||||
// In case the expression mutated `target`, we need to reassign it because of the above `clone`.
|
||||
Self::update_indexed_value(v, idx as usize, target, val_pos)
|
||||
})
|
||||
.and_then(|mut v| {
|
||||
let set_fn_name = format!("{}{}", FUNC_SETTER, id);
|
||||
|
||||
self.call_fn_raw(
|
||||
&set_fn_name,
|
||||
vec![this_ptr, v.as_mut()],
|
||||
None,
|
||||
*pos,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// All others - syntax error for setters chain
|
||||
_ => Err(EvalAltResult::ErrorDotExpr(
|
||||
"for assignment".to_string(),
|
||||
*idx_pos,
|
||||
)),
|
||||
},
|
||||
|
||||
// All others - syntax error for setters chain
|
||||
_ => Err(EvalAltResult::ErrorDotExpr(
|
||||
"for assignment".to_string(),
|
||||
lhs.position(),
|
||||
@ -536,34 +634,41 @@ impl Engine<'_> {
|
||||
scope: &mut Scope,
|
||||
dot_lhs: &Expr,
|
||||
dot_rhs: &Expr,
|
||||
source_val: Dynamic,
|
||||
new_val: Dynamic,
|
||||
val_pos: Position,
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
match dot_lhs {
|
||||
// id.???
|
||||
Expr::Identifier(id, pos) => {
|
||||
let (src_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?;
|
||||
let value = self.set_dot_val_helper(target.as_mut(), dot_rhs, source_val);
|
||||
let value =
|
||||
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos);
|
||||
|
||||
// In case the expression mutated `target`, we need to reassign it because
|
||||
// of the above `clone`.
|
||||
// In case the expression mutated `target`, we need to reassign it because of the above `clone`.
|
||||
*scope.get_mut(id, src_idx) = target;
|
||||
|
||||
value
|
||||
}
|
||||
|
||||
// lhs[idx_expr].???
|
||||
// TODO - Allow chaining of indexing!
|
||||
Expr::Index(lhs, idx_expr, idx_pos) => {
|
||||
let (src_type, src, idx, mut target) =
|
||||
self.eval_index_expr(scope, lhs, idx_expr, *idx_pos)?;
|
||||
let value = self.set_dot_val_helper(target.as_mut(), dot_rhs, source_val);
|
||||
|
||||
// In case the expression mutated `target`, we need to reassign it because
|
||||
// of the above `clone`.
|
||||
let value =
|
||||
self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos);
|
||||
|
||||
// In case the expression mutated `target`, we need to reassign it because of the above `clone`.
|
||||
if let Some((id, src_idx)) = src {
|
||||
Self::update_indexed_variable_in_scope(
|
||||
src_type, scope, id, src_idx, idx, target,
|
||||
);
|
||||
Self::update_indexed_var_in_scope(
|
||||
src_type,
|
||||
scope,
|
||||
id,
|
||||
src_idx,
|
||||
idx,
|
||||
target,
|
||||
lhs.position(),
|
||||
)?;
|
||||
}
|
||||
|
||||
value
|
||||
@ -617,9 +722,15 @@ impl Engine<'_> {
|
||||
self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?;
|
||||
|
||||
if let Some((id, src_idx)) = src {
|
||||
Ok(Self::update_indexed_variable_in_scope(
|
||||
src_type, scope, &id, src_idx, idx, rhs_val,
|
||||
))
|
||||
Ok(Self::update_indexed_var_in_scope(
|
||||
src_type,
|
||||
scope,
|
||||
&id,
|
||||
src_idx,
|
||||
idx,
|
||||
rhs_val,
|
||||
rhs.position(),
|
||||
)?)
|
||||
} else {
|
||||
Err(EvalAltResult::ErrorAssignmentToUnknownLHS(
|
||||
idx_lhs.position(),
|
||||
@ -629,7 +740,7 @@ impl Engine<'_> {
|
||||
|
||||
// dot_lhs.dot_rhs = rhs
|
||||
Expr::Dot(dot_lhs, dot_rhs, _) => {
|
||||
self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val)
|
||||
self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val, rhs.position())
|
||||
}
|
||||
|
||||
// Syntax error
|
||||
|
@ -1275,47 +1275,34 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
|
||||
}
|
||||
|
||||
fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result<Expr, ParseError> {
|
||||
//println!("{:?} = {:?}", lhs, rhs);
|
||||
fn all_dots(expr: &Expr) -> (bool, Position) {
|
||||
fn valid_assignment_chain(expr: &Expr) -> (bool, Position) {
|
||||
match expr {
|
||||
Expr::Identifier(_, pos) => (true, *pos),
|
||||
|
||||
Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() {
|
||||
Expr::Identifier(_, _) => (true, idx_lhs.position()),
|
||||
_ => (false, idx_lhs.position()),
|
||||
},
|
||||
|
||||
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
|
||||
Expr::Identifier(_, _) => all_dots(dot_rhs),
|
||||
Expr::Identifier(_, _) => valid_assignment_chain(dot_rhs),
|
||||
Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() {
|
||||
Expr::Identifier(_, _) => valid_assignment_chain(dot_rhs),
|
||||
_ => (false, idx_lhs.position()),
|
||||
},
|
||||
_ => (false, dot_lhs.position()),
|
||||
},
|
||||
|
||||
_ => (false, expr.position()),
|
||||
}
|
||||
}
|
||||
|
||||
match &lhs {
|
||||
Expr::Identifier(_, _) => return Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
|
||||
Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() {
|
||||
Expr::Identifier(_, _) => {
|
||||
return Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos))
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() {
|
||||
Expr::Identifier(_, _) => match all_dots(&lhs) {
|
||||
(true, _) => return Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
|
||||
(false, pos) => return Err(ParseError::new(PERR::AssignmentToInvalidLHS, pos)),
|
||||
},
|
||||
Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() {
|
||||
Expr::Identifier(_, _) => match all_dots(&dot_rhs) {
|
||||
(true, _) => return Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
|
||||
(false, pos) => return Err(ParseError::new(PERR::AssignmentToInvalidLHS, pos)),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
//println!("{:?} = {:?}", lhs, rhs);
|
||||
|
||||
return Err(ParseError::new(
|
||||
PERR::AssignmentToInvalidLHS,
|
||||
lhs.position(),
|
||||
));
|
||||
match valid_assignment_chain(&lhs) {
|
||||
(true, _) => Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)),
|
||||
(false, pos) => Err(ParseError::new(PERR::AssignmentToInvalidLHS, pos)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_op_assignment(
|
||||
|
@ -19,6 +19,8 @@ pub enum EvalAltResult {
|
||||
ErrorFunctionArgsMismatch(String, usize, usize, Position),
|
||||
/// Non-boolean operand encountered for boolean operator. Wrapped value is the operator.
|
||||
ErrorBooleanArgMismatch(String, Position),
|
||||
/// Non-character value encountered where a character is required.
|
||||
ErrorCharMismatch(Position),
|
||||
/// Array access out-of-bounds.
|
||||
/// Wrapped values are the current number of elements in the array and the index number.
|
||||
ErrorArrayBounds(usize, i64, Position),
|
||||
@ -64,6 +66,7 @@ impl Error for EvalAltResult {
|
||||
"Function call with wrong number of arguments"
|
||||
}
|
||||
Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands",
|
||||
Self::ErrorCharMismatch(_) => "Character expected",
|
||||
Self::ErrorIndexExpr(_) => "Indexing into an array or string expects an integer index",
|
||||
Self::ErrorIndexingType(_, _) => {
|
||||
"Indexing can only be performed on an array or a string"
|
||||
@ -131,6 +134,9 @@ impl std::fmt::Display for EvalAltResult {
|
||||
Self::ErrorBooleanArgMismatch(op, pos) => {
|
||||
write!(f, "{} operator expects boolean operands ({})", op, pos)
|
||||
}
|
||||
Self::ErrorCharMismatch(pos) => {
|
||||
write!(f, "string indexing expects a character value ({})", pos)
|
||||
}
|
||||
Self::ErrorArrayBounds(_, index, pos) if *index < 0 => {
|
||||
write!(f, "{}: {} < 0 ({})", desc, index, pos)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user