diff --git a/README.md b/README.md index c8f5fb73..a2720f62 100644 --- a/README.md +++ b/README.md @@ -1388,19 +1388,19 @@ record == "Bob X. Davis: age 42 ❤\n"; The following standard methods (defined in the [`MoreStringPackage`](#packages) but excluded if using a [raw `Engine`]) operate on strings: -| Function | Parameter(s) | Description | -| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | -| `len` | _none_ | returns the number of characters (not number of bytes) in the string | -| `pad` | character to pad, target length | pads the string with an character to at least a specified length | -| `append` | character/string to append | Adds a character or a string to the end of another string | -| `clear` | _none_ | empties the string | -| `truncate` | target length | cuts off the string at exactly a specified number of characters | -| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string | -| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found | -| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) | -| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) | -| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another | -| `trim` | _none_ | trims the string of whitespace at the beginning and end | +| Function | Parameter(s) | Description | +| ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | +| `len` | _none_ | returns the number of characters (not number of bytes) in the string | +| `pad` | character to pad, target length | pads the string with an character to at least a specified length | +| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string | +| `clear` | _none_ | empties the string | +| `truncate` | target length | cuts off the string at exactly a specified number of characters | +| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string | +| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found | +| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) | +| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) | +| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another | +| `trim` | _none_ | trims the string of whitespace at the beginning and end | ### Examples @@ -1463,19 +1463,19 @@ Arrays are disabled via the [`no_index`] feature. The following methods (defined in the [`BasicArrayPackage`](#packages) but excluded if using a [raw `Engine`]) operate on arrays: -| Function | Parameter(s) | Description | -| ------------ | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -| `push` | element to insert | inserts an element at the end | -| `append` | array to append | concatenates the second array to the end of the first | -| `+` operator | first array, second array | concatenates the first array with the second | -| `insert` | element to insert, position
(beginning if <= 0, end if >= length) | insert an element at a certain index | -| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | -| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | -| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid | -| `len` | _none_ | returns the number of elements | -| `pad` | element to pad, target length | pads the array with an element to at least a specified length | -| `clear` | _none_ | empties the array | -| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | +| Function | Parameter(s) | Description | +| ----------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `push` | element to insert | inserts an element at the end | +| `+=` operator, `append` | array to append | concatenates the second array to the end of the first | +| `+` operator | first array, second array | concatenates the first array with the second | +| `insert` | element to insert, position
(beginning if <= 0, end if >= length) | insert an element at a certain index | +| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) | +| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | +| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid | +| `len` | _none_ | returns the number of elements | +| `pad` | element to pad, target length | pads the array with an element to at least a specified length | +| `clear` | _none_ | empties the array | +| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | ### Examples @@ -1585,16 +1585,16 @@ Object maps are disabled via the [`no_object`] feature. The following methods (defined in the [`BasicMapPackage`](#packages) but excluded if using a [raw `Engine`]) operate on object maps: -| Function | Parameter(s) | Description | -| ------------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `has` | property name | does the object map contain a property of a particular name? | -| `len` | _none_ | returns the number of properties | -| `clear` | _none_ | empties the object map | -| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) | -| `mixin` | second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) | -| `+` operator | first object map, second object map | merges the first object map with the second | -| `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] | -| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] | +| Function | Parameter(s) | Description | +| ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `has` | property name | does the object map contain a property of a particular name? | +| `len` | _none_ | returns the number of properties | +| `clear` | _none_ | empties the object map | +| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) | +| `+=` operator, `mixin` | second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) | +| `+` operator | first object map, second object map | merges the first object map with the second | +| `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] | +| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] | ### Examples diff --git a/benches/iterations.rs b/benches/iterations.rs index 23eb7621..b70e9432 100644 --- a/benches/iterations.rs +++ b/benches/iterations.rs @@ -12,7 +12,7 @@ fn bench_iterations_1000(bench: &mut Bencher) { let x = 1_000; while x > 0 { - x = x - 1; + x -= 1; } "#; diff --git a/scripts/loop.rhai b/scripts/loop.rhai index d173b86f..1b514baf 100644 --- a/scripts/loop.rhai +++ b/scripts/loop.rhai @@ -6,7 +6,7 @@ let x = 10; loop { print(x); - x = x - 1; + x -= 1; if x <= 0 { break; } } diff --git a/scripts/speed_test.rhai b/scripts/speed_test.rhai index 5709d6ac..e07bd7ec 100644 --- a/scripts/speed_test.rhai +++ b/scripts/speed_test.rhai @@ -7,7 +7,7 @@ let x = 1_000_000; print("Ready... Go!"); while x > 0 { - x = x - 1; + x -= 1; } print("Finished. Run time = " + now.elapsed() + " seconds."); diff --git a/scripts/while.rhai b/scripts/while.rhai index 3f58f6b6..e17493b2 100644 --- a/scripts/while.rhai +++ b/scripts/while.rhai @@ -4,5 +4,5 @@ let x = 10; while x > 0 { print(x); - x = x - 1; + x -= 1; } diff --git a/src/engine.rs b/src/engine.rs index 9e134769..40930d6f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1354,10 +1354,11 @@ impl Engine { let def_value = Some(&def_value); let pos = rhs.position(); - // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. - let hash_fn = - calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())); - let hashes = (hash_fn, 0); + let hashes = ( + // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. + calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())), + 0, + ); let (r, _) = self.call_fn_raw( None, state, lib, op, hashes, args, false, def_value, pos, level, @@ -1417,57 +1418,87 @@ impl Engine { // lhs = rhs Expr::Assignment(x) => { - let op_pos = x.2; - let rhs_val = self.eval_expr(scope, state, lib, &x.1, level)?; + let lhs_expr = &x.0; + let op = x.1.as_ref(); + let op_pos = x.3; + let mut rhs_val = self.eval_expr(scope, state, lib, &x.2, level)?; - match &x.0 { - // name = rhs - Expr::Variable(x) => { - let ((name, pos), modules, hash_var, index) = x.as_ref(); - let index = if state.always_search { None } else { *index }; - let mod_and_hash = modules.as_ref().map(|m| (m.as_ref(), *hash_var)); - let (lhs_ptr, typ) = search_scope(scope, name, mod_and_hash, index, *pos)?; - self.inc_operations(state, *pos)?; + // name op= rhs + if let Expr::Variable(x) = &x.0 { + let ((name, pos), modules, hash_var, index) = x.as_ref(); + let index = if state.always_search { None } else { *index }; + let mod_and_hash = modules.as_ref().map(|m| (m.as_ref(), *hash_var)); + let (lhs_ptr, typ) = search_scope(scope, name, mod_and_hash, index, *pos)?; + self.inc_operations(state, *pos)?; - match typ { - ScopeEntryType::Constant => Err(Box::new( - EvalAltResult::ErrorAssignmentToConstant(name.clone(), *pos), - )), - ScopeEntryType::Normal => { - *lhs_ptr = rhs_val; - Ok(Default::default()) + match typ { + ScopeEntryType::Constant => Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant(name.clone(), *pos), + )), + ScopeEntryType::Normal if !op.is_empty() => { + // Complex op-assignment + if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { + // Not built in, do function call + let mut lhs_val = lhs_ptr.clone(); + let args = &mut [&mut lhs_val, &mut rhs_val]; + let hash = calc_fn_hash(empty(), op, 2, empty()); + *lhs_ptr = self + .exec_fn_call( + state, lib, op, true, hash, args, false, None, op_pos, + level, + ) + .map(|(v, _)| v)?; } - // End variable cannot be a module - ScopeEntryType::Module => unreachable!(), + Ok(Default::default()) } + ScopeEntryType::Normal => { + *lhs_ptr = rhs_val; + Ok(Default::default()) + } + // End variable cannot be a module + ScopeEntryType::Module => unreachable!(), } - // idx_lhs[idx_expr] = rhs - #[cfg(not(feature = "no_index"))] - Expr::Index(x) => { - let new_val = Some(rhs_val); - self.eval_dot_index_chain( + } else { + let new_val = Some(if op.is_empty() { + rhs_val + } else { + // Complex op-assignment - always do function call + let args = &mut [ + &mut self.eval_expr(scope, state, lib, lhs_expr, level)?, + &mut rhs_val, + ]; + let hash = calc_fn_hash(empty(), op, 2, empty()); + self.exec_fn_call( + state, lib, op, true, hash, args, false, None, op_pos, level, + ) + .map(|(v, _)| v)? + }); + + match &x.0 { + // name op= rhs + Expr::Variable(_) => unreachable!(), + // idx_lhs[idx_expr] op= rhs + #[cfg(not(feature = "no_index"))] + Expr::Index(x) => self.eval_dot_index_chain( scope, state, lib, &x.0, &x.1, true, x.2, level, new_val, - ) - } - // dot_lhs.dot_rhs = rhs - #[cfg(not(feature = "no_object"))] - Expr::Dot(x) => { - let new_val = Some(rhs_val); - self.eval_dot_index_chain( + ), + // dot_lhs.dot_rhs op= rhs + #[cfg(not(feature = "no_object"))] + Expr::Dot(x) => self.eval_dot_index_chain( scope, state, lib, &x.0, &x.1, false, op_pos, level, new_val, - ) - } - // Error assignment to constant - expr if expr.is_constant() => { - Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( - expr.get_constant_str(), + ), + // Error assignment to constant + expr if expr.is_constant() => { + Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( + expr.get_constant_str(), + expr.position(), + ))) + } + // Syntax error + expr => Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( expr.position(), - ))) + ))), } - // Syntax error - expr => Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( - expr.position(), - ))), } } @@ -1675,7 +1706,7 @@ impl Engine { let result = self.eval_expr(scope, state, lib, expr, level)?; Ok(match expr.as_ref() { - // If it is an assignment, erase the result at the root + // If it is a simple assignment, erase the result at the root Expr::Assignment(_) => Default::default(), _ => result, }) @@ -2079,3 +2110,85 @@ fn run_builtin_binary_op( Ok(None) } + +/// Build in common operator assignment implementations to avoid the cost of calling a registered function. +fn run_builtin_op_assignment( + op: &str, + x: &mut Dynamic, + y: &Dynamic, +) -> Result, Box> { + use crate::packages::arithmetic::*; + + let args_type = x.type_id(); + + if y.type_id() != args_type { + return Ok(None); + } + + if args_type == TypeId::of::() { + let x = x.downcast_mut::().unwrap(); + let y = y.downcast_ref::().unwrap().clone(); + + #[cfg(not(feature = "unchecked"))] + match op { + "+" => return Ok(Some(*x = add(*x, y)?)), + "-" => return Ok(Some(*x = sub(*x, y)?)), + "*" => return Ok(Some(*x = mul(*x, y)?)), + "/" => return Ok(Some(*x = div(*x, y)?)), + "%" => return Ok(Some(*x = modulo(*x, y)?)), + "~" => return Ok(Some(*x = pow_i_i(*x, y)?)), + ">>" => return Ok(Some(*x = shr(*x, y)?)), + "<<" => return Ok(Some(*x = shl(*x, y)?)), + _ => (), + } + + #[cfg(feature = "unchecked")] + match op { + "+" => return Ok(Some(*x += y)), + "-" => return Ok(Some(*x -= y)), + "*" => return Ok(Some(*x *= y)), + "/" => return Ok(Some(*x /= y)), + "%" => return Ok(Some(*x %= y)), + "~" => return Ok(Some(*x = pow_i_i_u(x, y))), + ">>" => return Ok(Some(*x = shr_u(x, y))), + "<<" => return Ok(Some(*x = shl_u(x, y))), + _ => (), + } + + match op { + "&" => return Ok(Some(*x &= y)), + "|" => return Ok(Some(*x |= y)), + "^" => return Ok(Some(*x ^= y)), + _ => (), + } + } else if args_type == TypeId::of::() { + let x = x.downcast_mut::().unwrap(); + let y = y.downcast_ref::().unwrap().clone(); + + match op { + "&" => return Ok(Some(*x = *x && y)), + "|" => return Ok(Some(*x = *x || y)), + _ => (), + } + } + + #[cfg(not(feature = "no_float"))] + { + if args_type == TypeId::of::() { + let x = x.downcast_mut::().unwrap(); + let y = y.downcast_ref::().unwrap().clone(); + + match op { + "+" => return Ok(Some(*x += y)), + "-" => return Ok(Some(*x -= y)), + "*" => return Ok(Some(*x *= y)), + "/" => return Ok(Some(*x /= y)), + "%" => return Ok(Some(*x %= y)), + "~" => return Ok(Some(*x = pow_f_f(*x, y)?)), + _ => (), + } + } + } + + Ok(None) +} diff --git a/src/optimize.rs b/src/optimize.rs index 1d182c19..dedd1135 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -380,26 +380,26 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { stmt => Expr::Stmt(Box::new((stmt, x.1))), }, // id = expr - Expr::Assignment(x) => match x.1 { - //id = id2 = expr2 - Expr::Assignment(x2) => match (x.0, x2.0) { - // var = var = expr2 -> var = expr2 + Expr::Assignment(x) => match x.2 { + //id = id2 op= expr2 + Expr::Assignment(x2) if x.1 == "=" => match (x.0, x2.0) { + // var = var op= expr2 -> var op= expr2 (Expr::Variable(a), Expr::Variable(b)) if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 => { // Assignment to the same variable - fold state.set_dirty(); - Expr::Assignment(Box::new((Expr::Variable(a), optimize_expr(x2.1, state), x.2))) + Expr::Assignment(Box::new((Expr::Variable(a), x2.1, optimize_expr(x2.2, state), x.3))) } - // id1 = id2 = expr2 + // id1 = id2 op= expr2 (id1, id2) => { Expr::Assignment(Box::new(( - id1, Expr::Assignment(Box::new((id2, optimize_expr(x2.1, state), x2.2))), x.2, + id1, x.1, Expr::Assignment(Box::new((id2, x2.1, optimize_expr(x2.2, state), x2.3))), x.3, ))) } }, - // id = expr - expr => Expr::Assignment(Box::new((x.0, optimize_expr(expr, state), x.2))), + // id op= expr + expr => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(expr, state), x.3))), }, // lhs.rhs diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 1abdd3be..5b96ffe2 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -53,6 +53,10 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { x.extend(y); Ok(()) }); + lib.set_fn_2_mut("+=", |x: &mut Array, y: Array| { + x.extend(y); + Ok(()) + }); lib.set_fn_2( "+", |mut x: Array, y: Array| { diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index cc2e4c1c..6f7fb0ef 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -42,6 +42,15 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { Ok(()) }, ); + lib.set_fn_2_mut( + "+=", + |map1: &mut Map, map2: Map| { + map2.into_iter().for_each(|(key, value)| { + map1.insert(key, value); + }); + Ok(()) + }, + ); lib.set_fn_2( "+", |mut map1: Map, map2: Map| { diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index f7ae8930..f4275467 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -105,10 +105,24 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin Ok(s.into()) }, ); + lib.set_fn_2_mut("+=", |s: &mut ImmutableString, ch: char| { + shared_make_mut(s).push(ch); + Ok(()) + }); lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { shared_make_mut(s).push(ch); Ok(()) }); + lib.set_fn_2_mut( + "+=", + |s: &mut ImmutableString, s2: ImmutableString| { + if !s2.is_empty() { + shared_make_mut(s).push_str(s2.as_str()); + } + + Ok(()) + } + ); lib.set_fn_2_mut( "append", |s: &mut ImmutableString, s2: ImmutableString| { diff --git a/src/parser.rs b/src/parser.rs index 4180ffbf..bbb5c808 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -405,8 +405,8 @@ pub enum Expr { Option, )>, ), - /// expr = expr - Assignment(Box<(Expr, Expr, Position)>), + /// expr op= expr + Assignment(Box<(Expr, Cow<'static, str>, Expr, Position)>), /// lhs.rhs Dot(Box<(Expr, Expr, Position)>), /// expr[expr] @@ -508,12 +508,13 @@ impl Expr { Self::Stmt(x) => x.1, Self::Variable(x) => (x.0).1, Self::FnCall(x) => (x.0).2, + Self::Assignment(x) => x.0.position(), Self::And(x) | Self::Or(x) | Self::In(x) => x.2, Self::True(pos) | Self::False(pos) | Self::Unit(pos) => *pos, - Self::Assignment(x) | Self::Dot(x) | Self::Index(x) => x.0.position(), + Self::Dot(x) | Self::Index(x) => x.0.position(), } } @@ -538,7 +539,7 @@ impl Expr { Self::True(pos) => *pos = new_pos, Self::False(pos) => *pos = new_pos, Self::Unit(pos) => *pos = new_pos, - Self::Assignment(x) => x.2 = new_pos, + Self::Assignment(x) => x.3 = new_pos, Self::Dot(x) => x.2 = new_pos, Self::Index(x) => x.2 = new_pos, } @@ -879,30 +880,25 @@ fn parse_index_chain<'a>( } #[cfg(not(feature = "no_float"))] - Expr::FloatConstant(x) => { + Expr::FloatConstant(_) => { return Err(PERR::MalformedIndexExpr( "Only arrays, object maps and strings can be indexed".into(), ) - .into_err(x.1)) + .into_err(lhs.position())) } - Expr::CharConstant(x) => { + Expr::CharConstant(_) + | Expr::Assignment(_) + | Expr::And(_) + | Expr::Or(_) + | Expr::In(_) + | Expr::True(_) + | Expr::False(_) + | Expr::Unit(_) => { return Err(PERR::MalformedIndexExpr( "Only arrays, object maps and strings can be indexed".into(), ) - .into_err(x.1)) - } - Expr::Assignment(x) | Expr::And(x) | Expr::Or(x) | Expr::In(x) => { - return Err(PERR::MalformedIndexExpr( - "Only arrays, object maps and strings can be indexed".into(), - ) - .into_err(x.2)) - } - Expr::True(pos) | Expr::False(pos) | Expr::Unit(pos) => { - return Err(PERR::MalformedIndexExpr( - "Only arrays, object maps and strings can be indexed".into(), - ) - .into_err(pos)) + .into_err(lhs.position())) } _ => (), @@ -920,32 +916,25 @@ fn parse_index_chain<'a>( } #[cfg(not(feature = "no_float"))] - Expr::FloatConstant(x) => { + Expr::FloatConstant(_) => { return Err(PERR::MalformedIndexExpr( "Only arrays, object maps and strings can be indexed".into(), ) - .into_err(x.1)) + .into_err(lhs.position())) } - Expr::CharConstant(x) => { + Expr::CharConstant(_) + | Expr::Assignment(_) + | Expr::And(_) + | Expr::Or(_) + | Expr::In(_) + | Expr::True(_) + | Expr::False(_) + | Expr::Unit(_) => { return Err(PERR::MalformedIndexExpr( "Only arrays, object maps and strings can be indexed".into(), ) - .into_err(x.1)) - } - - Expr::Assignment(x) | Expr::And(x) | Expr::Or(x) | Expr::In(x) => { - return Err(PERR::MalformedIndexExpr( - "Only arrays, object maps and strings can be indexed".into(), - ) - .into_err(x.2)) - } - - Expr::True(pos) | Expr::False(pos) | Expr::Unit(pos) => { - return Err(PERR::MalformedIndexExpr( - "Only arrays, object maps and strings can be indexed".into(), - ) - .into_err(pos)) + .into_err(lhs.position())) } _ => (), @@ -953,46 +942,46 @@ fn parse_index_chain<'a>( // lhs[float] #[cfg(not(feature = "no_float"))] - Expr::FloatConstant(x) => { + x @ Expr::FloatConstant(_) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not a float".into(), ) - .into_err(x.1)) + .into_err(x.position())) } // lhs[char] - Expr::CharConstant(x) => { + x @ Expr::CharConstant(_) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not a character".into(), ) - .into_err(x.1)) + .into_err(x.position())) } // lhs[??? = ??? ] - Expr::Assignment(x) => { + x @ Expr::Assignment(_) => { return Err(PERR::MalformedIndexExpr( - "Array access expects integer index, not ()".into(), + "Array access expects integer index, not an assignment".into(), ) - .into_err(x.2)) + .into_err(x.position())) } // lhs[()] - Expr::Unit(pos) => { + x @ Expr::Unit(_) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not ()".into(), ) - .into_err(*pos)) + .into_err(x.position())) } // lhs[??? && ???], lhs[??? || ???], lhs[??? in ???] - Expr::And(x) | Expr::Or(x) | Expr::In(x) => { + x @ Expr::And(_) | x @ Expr::Or(_) | x @ Expr::In(_) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not a boolean".into(), ) - .into_err(x.2)) + .into_err(x.position())) } // lhs[true], lhs[false] - Expr::True(pos) | Expr::False(pos) => { + x @ Expr::True(_) | x @ Expr::False(_) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not a boolean".into(), ) - .into_err(*pos)) + .into_err(x.position())) } // All other expressions _ => (), @@ -1391,17 +1380,22 @@ fn parse_unary<'a>( } fn make_assignment_stmt<'a>( + fn_name: Cow<'static, str>, state: &mut ParseState, lhs: Expr, rhs: Expr, pos: Position, ) -> Result { match &lhs { - Expr::Variable(x) if x.3.is_none() => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))), + Expr::Variable(x) if x.3.is_none() => { + Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) + } Expr::Variable(x) => { let ((name, name_pos), _, _, index) = x.as_ref(); match state.stack[(state.len() - index.unwrap().get())].1 { - ScopeEntryType::Normal => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))), + ScopeEntryType::Normal => { + Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) + } // Constant values cannot be assigned to ScopeEntryType::Constant => { Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos)) @@ -1410,11 +1404,15 @@ fn make_assignment_stmt<'a>( } } Expr::Index(x) | Expr::Dot(x) => match &x.0 { - Expr::Variable(x) if x.3.is_none() => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))), + Expr::Variable(x) if x.3.is_none() => { + Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) + } Expr::Variable(x) => { let ((name, name_pos), _, _, index) = x.as_ref(); match state.stack[(state.len() - index.unwrap().get())].1 { - ScopeEntryType::Normal => Ok(Expr::Assignment(Box::new((lhs, rhs, pos)))), + ScopeEntryType::Normal => { + Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) + } // Constant values cannot be assigned to ScopeEntryType::Constant => { Err(PERR::AssignmentToConstant(name.clone()).into_err(*name_pos)) @@ -1447,11 +1445,7 @@ fn parse_op_assignment_stmt<'a>( } let op = match token { - Token::Equals => { - let pos = eat_token(input, Token::Equals); - let rhs = parse_expr(input, state, level + 1, allow_stmt_expr)?; - return make_assignment_stmt(state, lhs, rhs, pos); - } + Token::Equals => "".into(), Token::PlusAssign => Token::Plus.syntax(), Token::MinusAssign => Token::Minus.syntax(), Token::MultiplyAssign => Token::Multiply.syntax(), @@ -1467,20 +1461,9 @@ fn parse_op_assignment_stmt<'a>( _ => return Ok(lhs), }; - input.next(); - - let lhs_copy = lhs.clone(); + let (_, pos) = input.next().unwrap(); let rhs = parse_expr(input, state, level + 1, allow_stmt_expr)?; - - // lhs op= rhs -> lhs = op(lhs, rhs) - let mut args = StaticVec::new(); - args.push(lhs_copy); - args.push(rhs); - - let hash = calc_fn_hash(empty(), &op, args.len(), empty()); - let rhs_expr = Expr::FnCall(Box::new(((op, true, pos), None, hash, args, None))); - - make_assignment_stmt(state, lhs, rhs_expr, pos) + make_assignment_stmt(op, state, lhs, rhs, pos) } /// Make a dot expression. @@ -1553,33 +1536,26 @@ fn make_dot_expr( /// Make an 'in' expression. fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { match (&lhs, &rhs) { - (_, Expr::IntegerConstant(x)) => { + (_, x @ Expr::IntegerConstant(_)) + | (_, x @ Expr::And(_)) + | (_, x @ Expr::Or(_)) + | (_, x @ Expr::In(_)) + | (_, x @ Expr::Assignment(_)) + | (_, x @ Expr::True(_)) + | (_, x @ Expr::False(_)) + | (_, x @ Expr::Unit(_)) => { return Err(PERR::MalformedInExpr( "'in' expression expects a string, array or object map".into(), ) - .into_err(x.1)) - } - - (_, Expr::And(x)) | (_, Expr::Or(x)) | (_, Expr::In(x)) | (_, Expr::Assignment(x)) => { - return Err(PERR::MalformedInExpr( - "'in' expression expects a string, array or object map".into(), - ) - .into_err(x.2)) - } - - (_, Expr::True(pos)) | (_, Expr::False(pos)) | (_, Expr::Unit(pos)) => { - return Err(PERR::MalformedInExpr( - "'in' expression expects a string, array or object map".into(), - ) - .into_err(*pos)) + .into_err(x.position())) } #[cfg(not(feature = "no_float"))] - (_, Expr::FloatConstant(x)) => { + (_, x @ Expr::FloatConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression expects a string, array or object map".into(), ) - .into_err(x.1)) + .into_err(x.position())) } // "xxx" in "xxxx", 'x' in "xxxx" - OK! @@ -1588,63 +1564,58 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { + (x @ Expr::FloatConstant(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not a float".into(), ) - .into_err(x.1)) + .into_err(x.position())) } // 123 in "xxxx" - (Expr::IntegerConstant(x), Expr::StringConstant(_)) => { + (x @ Expr::IntegerConstant(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not a number".into(), ) - .into_err(x.1)) + .into_err(x.position())) } // (??? && ???) in "xxxx", (??? || ???) in "xxxx", (??? in ???) in "xxxx", - (Expr::And(x), Expr::StringConstant(_)) - | (Expr::Or(x), Expr::StringConstant(_)) - | (Expr::In(x), Expr::StringConstant(_)) => { - return Err(PERR::MalformedInExpr( - "'in' expression for a string expects a string, not a boolean".into(), - ) - .into_err(x.2)) - } // true in "xxxx", false in "xxxx" - (Expr::True(pos), Expr::StringConstant(_)) - | (Expr::False(pos), Expr::StringConstant(_)) => { + (x @ Expr::And(_), Expr::StringConstant(_)) + | (x @ Expr::Or(_), Expr::StringConstant(_)) + | (x @ Expr::In(_), Expr::StringConstant(_)) + | (x @ Expr::True(_), Expr::StringConstant(_)) + | (x @ Expr::False(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not a boolean".into(), ) - .into_err(*pos)) + .into_err(x.position())) } // [???, ???, ???] in "xxxx" - (Expr::Array(x), Expr::StringConstant(_)) => { + (x @ Expr::Array(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not an array".into(), ) - .into_err(x.1)) + .into_err(x.position())) } // #{...} in "xxxx" - (Expr::Map(x), Expr::StringConstant(_)) => { + (x @ Expr::Map(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not an object map".into(), ) - .into_err(x.1)) + .into_err(x.position())) } // (??? = ???) in "xxxx" - (Expr::Assignment(x), Expr::StringConstant(_)) => { + (x @ Expr::Assignment(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( - "'in' expression for a string expects a string, not ()".into(), + "'in' expression for a string expects a string, not an assignment".into(), ) - .into_err(x.2)) + .into_err(x.position())) } // () in "xxxx" - (Expr::Unit(pos), Expr::StringConstant(_)) => { + (x @ Expr::Unit(_), Expr::StringConstant(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for a string expects a string, not ()".into(), ) - .into_err(*pos)) + .into_err(x.position())) } // "xxx" in #{...}, 'x' in #{...} - OK! @@ -1652,62 +1623,58 @@ fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { + (x @ Expr::FloatConstant(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not a float".into(), ) - .into_err(x.1)) + .into_err(x.position())) } // 123 in #{...} - (Expr::IntegerConstant(x), Expr::Map(_)) => { + (x @ Expr::IntegerConstant(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not a number".into(), ) - .into_err(x.1)) + .into_err(x.position())) } // (??? && ???) in #{...}, (??? || ???) in #{...}, (??? in ???) in #{...}, - (Expr::And(x), Expr::Map(_)) - | (Expr::Or(x), Expr::Map(_)) - | (Expr::In(x), Expr::Map(_)) => { - return Err(PERR::MalformedInExpr( - "'in' expression for an object map expects a string, not a boolean".into(), - ) - .into_err(x.2)) - } // true in #{...}, false in #{...} - (Expr::True(pos), Expr::Map(_)) | (Expr::False(pos), Expr::Map(_)) => { + (x @ Expr::And(_), Expr::Map(_)) + | (x @ Expr::Or(_), Expr::Map(_)) + | (x @ Expr::In(_), Expr::Map(_)) + | (x @ Expr::True(_), Expr::Map(_)) + | (x @ Expr::False(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not a boolean".into(), ) - .into_err(*pos)) + .into_err(x.position())) } // [???, ???, ???] in #{..} - (Expr::Array(x), Expr::Map(_)) => { + (x @ Expr::Array(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not an array".into(), ) - .into_err(x.1)) + .into_err(x.position())) } // #{...} in #{..} - (Expr::Map(x), Expr::Map(_)) => { + (x @ Expr::Map(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not an object map".into(), ) - .into_err(x.1)) + .into_err(x.position())) } // (??? = ???) in #{...} - (Expr::Assignment(x), Expr::Map(_)) => { + (x @ Expr::Assignment(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( - "'in' expression for an object map expects a string, not ()".into(), + "'in' expression for an object map expects a string, not an assignment".into(), ) - .into_err(x.2)) + .into_err(x.position())) } // () in #{...} - (Expr::Unit(pos), Expr::Map(_)) => { + (x @ Expr::Unit(_), Expr::Map(_)) => { return Err(PERR::MalformedInExpr( "'in' expression for an object map expects a string, not ()".into(), ) - .into_err(*pos)) + .into_err(x.position())) } _ => (),