From eed7bef974ab596d6595fdc59846aad6137fe8fd Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 7 Mar 2020 22:50:46 +0800 Subject: [PATCH] Allow chaining of indexing (one level) and dotting. --- src/engine.rs | 197 +++++++++++++++++++++++++++++++++++++++----------- src/parser.rs | 49 +++++-------- src/result.rs | 6 ++ 3 files changed, 178 insertions(+), 74 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index b505950c..a8ec5ff5 100644 --- a/src/engine.rs +++ b/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 { match src_type { // array_id[idx] = val IndexSourceType::Array => { let arr = scope.get_mut_by_type::(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::() - .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 { + if val.is::() { + let arr = val.downcast_mut::().expect("array expected"); + arr[idx as usize] = new_val; + } else if val.is::() { + let s = val.downcast_mut::().expect("string expected"); + // Value must be a character + let ch = *new_val + .downcast::() + .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 { 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 { 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 { 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 diff --git a/src/parser.rs b/src/parser.rs index e14d081e..a395d21e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1275,47 +1275,34 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result Result { - //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( diff --git a/src/result.rs b/src/result.rs index 8fcae55a..d9e009d2 100644 --- a/src/result.rs +++ b/src/result.rs @@ -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) }