From 883f08c0260d1ecbadf9428c244e71eaf6de91e7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 5 Mar 2020 20:28:03 +0800 Subject: [PATCH] Enable more indexing expressions. --- README.md | 30 +++++-- src/engine.rs | 243 +++++++++++++++++++++++++++++++------------------- src/parser.rs | 60 +++++++++---- src/result.rs | 20 +++-- 4 files changed, 230 insertions(+), 123 deletions(-) diff --git a/README.md b/README.md index f0ab4f1a..07d19912 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Rhai's current feature set: * Support for overloaded functions * No additional dependencies -**Note:** Currently, the version is 0.10.1, so the language and API may change before they stabilize. +**Note:** Currently, the version is 0.10.1, so the language and API's may change before they stabilize. ## Installation @@ -585,7 +585,7 @@ return 123 + 456; ```rust if error != "" { - throw error; // `throw` takes a string to form the exception text + throw error; // 'throw' takes a string to form the exception text } throw; // no exception text @@ -628,12 +628,19 @@ y[1] = 42; print(y[1]); // prints 42 ts.list = y; // arrays can be assigned completely (by value copy) -let foo = ts.list[1]; // indexing into properties is ok +let foo = ts.list[1]; foo == 42; -let foo = [1, 2, 3][0]; // a syntax error (for now) - cannot index into literals -let foo = abc()[0]; // a syntax error (for now) - cannot index into function call return values -let foo = y[0]; // this works +let foo = [1, 2, 3][0]; +foo == 1; + +fn abc() { [42, 43, 44] } + +let foo = abc()[0]; +foo == 42; + +let foo = y[0]; +foo == 1; y.push(4); // 4 elements y.push(5); // 5 elements @@ -734,14 +741,19 @@ let c = record[4]; c == 'C'; ts.s = record; -let c = ts.s[4]; // indexing into properties is ok + +let c = ts.s[4]; c == 'C'; -let c = "foo"[0]; // a syntax error (for now) - cannot index into literals +let c = "foo"[0]; +c == 'f'; + +let c = ("foo" + "bar")[5]; +c == 'r'; // Escape sequences in strings record += " \u2764\n"; // escape sequence of '❤' in Unicode -record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line +record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line // Unlike Rust, Rhai strings can be modified record[4] = '\x58'; // 0x58 = 'X' diff --git a/src/engine.rs b/src/engine.rs index 813a335b..74654cd5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -18,6 +18,13 @@ const KEYWORD_PRINT: &'static str = "print"; const KEYWORD_DEBUG: &'static str = "debug"; const KEYWORD_TYPE_OF: &'static str = "type_of"; +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +enum VariableType { + Array, + String, + Expression, +} + #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct FnSpec<'a> { pub name: Cow<'a, str>, @@ -193,15 +200,24 @@ impl Engine<'_> { self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) } - Expr::Index(id, idx_expr, pos) => { + Expr::Index(lhs, idx_expr) => { let idx = *self .eval_expr(scope, idx_expr)? .downcast::() .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))?; - let get_fn_name = format!("get${}", id); - let val = self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?; - Self::get_indexed_value(val, idx, *pos).map(|(v, _)| v) + let (lhs_value, pos) = match lhs.as_ref() { + Expr::Identifier(id, pos) => { + let get_fn_name = format!("get${}", id); + ( + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, + *pos, + ) + } + expr => return Err(EvalAltResult::ErrorDotExpr(expr.position())), + }; + + Self::get_indexed_value(lhs_value, idx, pos).map(|(v, _)| v) } Expr::Dot(inner_lhs, inner_rhs) => match inner_lhs.as_ref() { @@ -211,16 +227,25 @@ impl Engine<'_> { self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs)) } - Expr::Index(id, idx_expr, pos) => { + Expr::Index(lhs, idx_expr) => { let idx = *self .eval_expr(scope, idx_expr)? .downcast::() .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))?; - let get_fn_name = format!("get${}", id); - let val = self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?; - Self::get_indexed_value(val, idx, *pos).and_then(|(mut v, _)| { - self.get_dot_val_helper(scope, v.as_mut(), inner_rhs) + let (lhs_value, pos) = match lhs.as_ref() { + Expr::Identifier(id, pos) => { + let get_fn_name = format!("get${}", id); + ( + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, + *pos, + ) + } + expr => return Err(EvalAltResult::ErrorDotExpr(expr.position())), + }; + + Self::get_indexed_value(lhs_value, idx, pos).and_then(|(mut value, _)| { + self.get_dot_val_helper(scope, value.as_mut(), inner_rhs) }) } _ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())), @@ -246,14 +271,14 @@ impl Engine<'_> { val: Dynamic, idx: i64, pos: Position, - ) -> Result<(Dynamic, bool), EvalAltResult> { + ) -> Result<(Dynamic, VariableType), EvalAltResult> { if val.is::() { let arr = val.downcast::().unwrap(); if idx >= 0 { arr.get(idx as usize) .cloned() - .map(|v| (v, true)) + .map(|v| (v, VariableType::Array)) .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, pos)) } else { Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, pos)) @@ -264,7 +289,7 @@ impl Engine<'_> { if idx >= 0 { s.chars() .nth(idx as usize) - .map(|ch| (ch.into_dynamic(), false)) + .map(|ch| (ch.into_dynamic(), VariableType::String)) .ok_or_else(|| EvalAltResult::ErrorStringBounds(s.chars().count(), idx, pos)) } else { Err(EvalAltResult::ErrorStringBounds( @@ -281,22 +306,28 @@ impl Engine<'_> { fn eval_index_expr( &mut self, scope: &mut Scope, - id: &str, - idx: &Expr, - begin: Position, - ) -> Result<(bool, usize, usize, Dynamic), EvalAltResult> { + lhs: &Expr, + idx_expr: &Expr, + ) -> Result<(VariableType, Option<(String, usize)>, usize, Dynamic), EvalAltResult> { let idx = *self - .eval_expr(scope, idx)? + .eval_expr(scope, idx_expr)? .downcast::() - .map_err(|_| EvalAltResult::ErrorIndexExpr(idx.position()))?; + .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))?; - Self::search_scope( - scope, - id, - |val| Self::get_indexed_value(val, idx, begin), - begin, - ) - .map(|(idx_sc, (val, is_array))| (is_array, idx_sc, idx as usize, val)) + match lhs { + Expr::Identifier(id, _) => Self::search_scope( + scope, + &id, + |val| Self::get_indexed_value(val, idx, lhs.position()), + lhs.position(), + ) + .map(|(src_idx, (val, source_type))| { + (source_type, Some((id.clone(), src_idx)), idx as usize, val) + }), + + expr => Self::get_indexed_value(self.eval_expr(scope, expr)?, idx, lhs.position()) + .map(|(val, _)| (VariableType::Expression, None, idx as usize, val)), + } } fn str_replace_char(s: &mut String, idx: usize, new_ch: char) { @@ -333,22 +364,37 @@ impl Engine<'_> { value } - Expr::Index(id, idx_expr, pos) => { - let (is_array, sc_idx, idx, mut target) = - self.eval_index_expr(scope, id, idx_expr, *pos)?; + Expr::Index(lhs, idx_expr) => { + let (source_type, src, idx, mut target) = + self.eval_index_expr(scope, lhs, idx_expr)?; 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`. - if is_array { - scope.get_mut(id, sc_idx).downcast_mut::().unwrap()[idx] = target; - } else { - Self::str_replace_char( - scope.get_mut(id, sc_idx).downcast_mut::().unwrap(), // Root is a string - idx, - *target.downcast::().unwrap(), // Target should be a char - ); + match source_type { + VariableType::Array => { + let src = src.unwrap(); + scope + .get_mut(&src.0, src.1) + .downcast_mut::() + .unwrap()[idx] = target + } + + VariableType::String => { + let src = src.unwrap(); + + Self::str_replace_char( + scope + .get_mut(&src.0, src.1) + .downcast_mut::() + .unwrap(), // Root is a string + idx, + *target.downcast::().unwrap(), // Target should be a char + ) + } + + _ => panic!("source_type must be either Array or String"), } value @@ -417,21 +463,38 @@ impl Engine<'_> { value } - Expr::Index(id, iex_expr, pos) => { - let (is_array, sc_idx, idx, mut target) = - self.eval_index_expr(scope, id, iex_expr, *pos)?; + Expr::Index(lhs, idx_expr) => { + let (source_type, src, idx, mut target) = + self.eval_index_expr(scope, lhs, idx_expr)?; 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`. - if is_array { - scope.get_mut(id, sc_idx).downcast_mut::().unwrap()[idx] = target; - } else { - Self::str_replace_char( - scope.get_mut(id, sc_idx).downcast_mut::().unwrap(), // Root is a string - idx, - *target.downcast::().unwrap(), // Target should be a char - ); + + match source_type { + VariableType::Array => { + let src = src.unwrap(); + let val = scope + .get_mut(&src.0, src.1) + .downcast_mut::() + .unwrap(); + val[idx] = target + } + + VariableType::String => { + let src = src.unwrap(); + + Self::str_replace_char( + scope + .get_mut(&src.0, src.1) + .downcast_mut::() + .unwrap(), // Root is a string + idx, + *target.downcast::().unwrap(), // Target should be a char + ) + } + + _ => panic!("source_type must be either Array or String"), } value @@ -447,20 +510,17 @@ impl Engine<'_> { Expr::FloatConstant(i, _) => Ok((*i).into_dynamic()), Expr::StringConstant(s, _) => Ok(s.into_dynamic()), Expr::CharConstant(c, _) => Ok((*c).into_dynamic()), - - Expr::Identifier(id, pos) => scope - .get(id) - .map(|(_, _, val)| val) - .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.clone(), *pos)), - - Expr::Index(id, idx_expr, pos) => self - .eval_index_expr(scope, id, idx_expr, *pos) + Expr::Identifier(id, pos) => { + Self::search_scope(scope, id, Ok, *pos).map(|(_, val)| val) + } + Expr::Index(lhs, idx_expr) => self + .eval_index_expr(scope, lhs, idx_expr) .map(|(_, _, _, x)| x), - Expr::Assignment(ref id, rhs) => { + Expr::Assignment(lhs, rhs) => { let rhs_val = self.eval_expr(scope, rhs)?; - match id.as_ref() { + match lhs.as_ref() { Expr::Identifier(name, pos) => { if let Some((idx, _, _)) = scope.get(name) { *scope.get_mut(name, idx) = rhs_val; @@ -469,47 +529,44 @@ impl Engine<'_> { Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos)) } } - Expr::Index(id, idx_expr, pos) => { - let idx_pos = idx_expr.position(); - let idx = *match self.eval_expr(scope, &idx_expr)?.downcast::() { - Ok(x) => x, - _ => return Err(EvalAltResult::ErrorIndexExpr(idx_pos)), - }; + Expr::Index(idx_lhs, idx_expr) => { + let (source_type, src, idx, _) = + self.eval_index_expr(scope, idx_lhs, idx_expr)?; - let val = match scope.get(id) { - Some((idx, _, _)) => scope.get_mut(id, idx), - _ => { - return Err(EvalAltResult::ErrorVariableNotFound(id.clone(), *pos)) + match source_type { + VariableType::Array => { + let src = src.unwrap(); + scope + .get_mut(&src.0, src.1) + .downcast_mut::() + .map(|arr| (arr[idx as usize] = rhs_val).into_dynamic()) + .ok_or_else(|| { + EvalAltResult::ErrorIndexExpr(idx_lhs.position()) + }) } - }; - if let Some(arr) = val.downcast_mut() as Option<&mut Array> { - if idx < 0 { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) - } else if idx as usize >= arr.len() { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) - } else { - arr[idx as usize] = rhs_val; - Ok(().into_dynamic()) + VariableType::String => { + let src = src.unwrap(); + scope + .get_mut(&src.0, src.1) + .downcast_mut::() + .map(|s| { + Self::str_replace_char( + s, + idx as usize, + *rhs_val.downcast::().unwrap(), + ) + .into_dynamic() + }) + .ok_or_else(|| { + EvalAltResult::ErrorIndexExpr(idx_lhs.position()) + }) } - } else if let Some(s) = val.downcast_mut() as Option<&mut String> { - let s_len = s.chars().count(); - if idx < 0 { - Err(EvalAltResult::ErrorStringBounds(s_len, idx, idx_pos)) - } else if idx as usize >= s_len { - Err(EvalAltResult::ErrorStringBounds(s_len, idx, idx_pos)) - } else { - Self::str_replace_char( - s, - idx as usize, - *rhs_val.downcast::().unwrap(), - ); - Ok(().into_dynamic()) - } - } else { - Err(EvalAltResult::ErrorIndexExpr(idx_pos)) + _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS( + idx_lhs.position(), + )), } } @@ -517,7 +574,7 @@ impl Engine<'_> { self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val) } - _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(id.position())), + _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(lhs.position())), } } diff --git a/src/parser.rs b/src/parser.rs index 0b2c347c..855c2bca 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -132,7 +132,7 @@ pub enum Expr { FunctionCall(String, Vec, Option, Position), Assignment(Box, Box), Dot(Box, Box), - Index(String, Box, Position), + Index(Box, Box), Array(Vec, Position), And(Box, Box), Or(Box, Box), @@ -150,15 +150,16 @@ impl Expr { | Expr::CharConstant(_, pos) | Expr::StringConstant(_, pos) | Expr::FunctionCall(_, _, _, pos) - | Expr::Index(_, _, pos) | Expr::Array(_, pos) | Expr::True(pos) | Expr::False(pos) | Expr::Unit(pos) => *pos, - Expr::Assignment(e, _) | Expr::Dot(e, _) | Expr::And(e, _) | Expr::Or(e, _) => { - e.position() - } + Expr::Index(e, _) + | Expr::Assignment(e, _) + | Expr::Dot(e, _) + | Expr::And(e, _) + | Expr::Or(e, _) => e.position(), } } } @@ -1076,15 +1077,14 @@ fn parse_call_expr<'a>( } fn parse_index_expr<'a>( - id: String, + lhs: Box, input: &mut Peekable>, - begin: Position, ) -> Result { match parse_expr(input) { - Ok(idx) => match input.peek() { + Ok(idx_expr) => match input.peek() { Some(&(Token::RightBracket, _)) => { input.next(); - return Ok(Expr::Index(id, Box::new(idx), begin)); + return Ok(Expr::Index(lhs, Box::new(idx_expr))); } Some(&(_, pos)) => return Err(ParseError::new(PERR::MalformedIndexExpr, pos)), None => return Err(ParseError::new(PERR::MalformedIndexExpr, Position::eof())), @@ -1105,7 +1105,7 @@ fn parse_ident_expr<'a>( } Some(&(Token::LeftBracket, _)) => { input.next(); - parse_index_expr(id, input, begin) + parse_index_expr(Box::new(Expr::Identifier(id, begin)), input) } Some(_) => Ok(Expr::Identifier(id, begin)), None => Ok(Expr::Identifier(id, Position::eof())), @@ -1147,14 +1147,30 @@ fn parse_array_expr<'a>( } fn parse_primary<'a>(input: &mut Peekable>) -> Result { - match input.next() { + let token = input.next(); + + let mut follow_on = false; + + let r = match token { Some((Token::IntegerConstant(x), pos)) => Ok(Expr::IntegerConstant(x, pos)), Some((Token::FloatConstant(x), pos)) => Ok(Expr::FloatConstant(x, pos)), - Some((Token::StringConst(s), pos)) => Ok(Expr::StringConstant(s, pos)), Some((Token::CharConstant(c), pos)) => Ok(Expr::CharConstant(c, pos)), - Some((Token::Identifier(s), pos)) => parse_ident_expr(s, input, pos), - Some((Token::LeftParen, pos)) => parse_paren_expr(input, pos), - Some((Token::LeftBracket, pos)) => parse_array_expr(input, pos), + Some((Token::StringConst(s), pos)) => { + follow_on = true; + Ok(Expr::StringConstant(s, pos)) + } + Some((Token::Identifier(s), pos)) => { + follow_on = true; + parse_ident_expr(s, input, pos) + } + Some((Token::LeftParen, pos)) => { + follow_on = true; + parse_paren_expr(input, pos) + } + Some((Token::LeftBracket, pos)) => { + follow_on = true; + parse_array_expr(input, pos) + } Some((Token::True, pos)) => Ok(Expr::True(pos)), Some((Token::False, pos)) => Ok(Expr::False(pos)), Some((Token::LexError(le), pos)) => { @@ -1165,6 +1181,20 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), + }?; + + if !follow_on { + return Ok(r); + } + + // Post processing + match input.peek() { + Some(&(Token::LeftBracket, _)) => { + // Possible indexing + input.next(); + parse_index_expr(Box::new(r), input) + } + _ => Ok(r), } } diff --git a/src/result.rs b/src/result.rs index eb69c985..f7d6a8b9 100644 --- a/src/result.rs +++ b/src/result.rs @@ -132,16 +132,24 @@ impl std::fmt::Display for EvalAltResult { write!(f, "{}: {} < 0 ({})", desc, index, pos) } Self::ErrorArrayBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), - Self::ErrorArrayBounds(max, index, pos) => { - write!(f, "{} (max {}): {} ({})", desc, max - 1, index, pos) - } + Self::ErrorArrayBounds(max, index, pos) => write!( + f, + "Array index {} is out of bounds: max {} elements ({})", + index, + max - 1, + pos + ), Self::ErrorStringBounds(_, index, pos) if *index < 0 => { write!(f, "{}: {} < 0 ({})", desc, index, pos) } Self::ErrorStringBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), - Self::ErrorStringBounds(max, index, pos) => { - write!(f, "{} (max {}): {} ({})", desc, max - 1, index, pos) - } + Self::ErrorStringBounds(max, index, pos) => write!( + f, + "String index {} is out of bounds: max {} characters ({})", + index, + max - 1, + pos + ), } } }