diff --git a/src/error/display.rs b/src/error/display.rs index effcf95..cc9fe5b 100644 --- a/src/error/display.rs +++ b/src/error/display.rs @@ -50,6 +50,11 @@ impl fmt::Display for EvalexprError write!( + f, + "Expected a the same length for tuples {:?} and {:?}.", + expected, actual + ), ExpectedRangedLengthTuple { expected_length, actual, diff --git a/src/error/mod.rs b/src/error/mod.rs index 3af244e..ff1ac91 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -93,6 +93,14 @@ pub enum EvalexprError actual: Value, }, + /// A tuple value of a certain length was expected. + ExpectedSameLengthTuples { + /// The tuple with expected length. + expected: Value, + /// The actual tuple with unexpected length. + actual: Value, + }, + /// A tuple value of a certain length range was expected. ExpectedRangedLengthTuple { /// The expected length range. @@ -328,6 +336,14 @@ impl EvalexprError { } } + /// Constructs `EvalexprError::ExpectedSameLengthTuples {expected, actual}`. + pub fn expected_same_len_tuples( + expected: Value, + actual: Value, + ) -> Self { + EvalexprError::ExpectedSameLengthTuples { expected, actual } + } + /// Constructs `EvalexprError::ExpectedFixedLenTuple{expected_len, actual}`. pub fn expected_ranged_len_tuple( expected_len: RangeInclusive, diff --git a/src/operator/mod.rs b/src/operator/mod.rs index a815f79..e56ea4d 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -195,6 +195,37 @@ impl Operator { context: &C, ) -> EvalexprResultValue { use crate::operator::Operator::*; + + // Check if we can do piecewise operations on tuples + match self { + Add | Sub | Neg | Mul | Div | And | Or | Not => match arguments { + [Value::Tuple(a_elements), Value::Tuple(b_elements)] => { + if a_elements.len() != b_elements.len() { + return Err(EvalexprError::expected_same_len_tuples( + arguments[0].clone(), + arguments[1].clone(), + )); + } + + let mut computed = Vec::with_capacity(a_elements.len()); + for (a, b) in a_elements.iter().zip(b_elements) { + computed.push(self.eval(&[a.clone(), b.clone()], context)?); + } + + return Ok(Value::Tuple(computed)); + }, + [Value::Tuple(elements)] => { + let mut computed = Vec::with_capacity(elements.len()); + for element in elements { + computed.push(self.eval(std::slice::from_ref(element), context)?); + } + return Ok(Value::Tuple(computed)); + }, + _ => {}, + }, + _ => {}, + } + match self { RootNode => { if let Some(first) = arguments.first() { diff --git a/tests/integration.rs b/tests/integration.rs index f40bc38..428f32b 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1547,6 +1547,46 @@ fn test_tuple_definitions() { ); } +#[test] +fn test_tuple_operations() { + assert_eq!( + eval("!(false, true, true)"), + Ok(Value::Tuple(vec![ + Value::Boolean(true), + Value::Boolean(false), + Value::Boolean(false), + ])) + ); + assert_eq!( + eval("(1, 2, 3) + (4, 5, 6)"), + Ok(Value::Tuple(vec![ + Value::from_int(5), + Value::from_int(7), + Value::from_int(9), + ])) + ); + assert_eq!( + eval("(6, 5, 4) - (1, 2, 3)"), + Ok(Value::Tuple(vec![ + Value::from_int(5), + Value::from_int(3), + Value::from_int(1), + ])) + ); + + assert_eq!( + eval("(1, 2, 3) + (4, 5)"), + Err(EvalexprError::ExpectedSameLengthTuples { + expected: Value::Tuple(vec![ + Value::from_int(1), + Value::from_int(2), + Value::from_int(3), + ]), + actual: Value::Tuple(vec![Value::from_int(4), Value::from_int(5),]) + }) + ); +} + #[test] fn test_implicit_context() { assert_eq!(