From a9c45307ddc179b49f1021731cd4ee66e9ea4a18 Mon Sep 17 00:00:00 2001 From: Quest Date: Fri, 5 Apr 2019 23:07:54 +0200 Subject: [PATCH] Support addition and comparison of strings. --- src/error/display.rs | 3 +++ src/error/mod.rs | 20 +++++++++++++++ src/operator/mod.rs | 59 +++++++++++++++++++++++++++++++++----------- tests/integration.rs | 3 +++ 4 files changed, 70 insertions(+), 15 deletions(-) diff --git a/src/error/display.rs b/src/error/display.rs index 3bfd640..6ad82f0 100644 --- a/src/error/display.rs +++ b/src/error/display.rs @@ -24,6 +24,9 @@ impl fmt::Display for EvalexprError { ExpectedNumber { actual } => { write!(f, "Expected a Value::Number, but got {:?}.", actual) }, + ExpectedNumberOrString { actual } => { + write!(f, "Expected a Value::Number or a Value::String, but got {:?}.", actual) + }, ExpectedBoolean { actual } => { write!(f, "Expected a Value::Boolean, but got {:?}.", actual) }, diff --git a/src/error/mod.rs b/src/error/mod.rs index b4d9ef9..3956d92 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -56,6 +56,13 @@ pub enum EvalexprError { actual: Value, }, + /// A numeric or string value was expected. + /// Numeric values are the variants `Value::Int` and `Value::Float`. + ExpectedNumberOrString { + /// The actual value. + actual: Value, + }, + /// A boolean value was expected. ExpectedBoolean { /// The actual value. @@ -204,6 +211,11 @@ impl EvalexprError { EvalexprError::ExpectedNumber { actual } } + /// Constructs `Error::ExpectedNumberOrString{actual}`. + pub fn expected_number_or_string(actual: Value) -> Self { + EvalexprError::ExpectedNumberOrString { actual } + } + /// Constructs `Error::ExpectedBoolean{actual}`. pub fn expected_boolean(actual: Value) -> Self { EvalexprError::ExpectedBoolean { actual } @@ -315,6 +327,14 @@ pub fn expect_number(actual: &Value) -> EvalexprResult<()> { } } +/// Returns Ok(()) if the given value is a string or a numeric +pub fn expect_number_or_string(actual: &Value) -> EvalexprResult<()> { + match actual { + Value::String(_) | Value::Float(_) | Value::Int(_) => Ok(()), + _ => Err(EvalexprError::expected_number_or_string(actual.clone())), + } +} + /// Returns `Ok(bool)` if the given value is a `Value::Boolean`, or `Err(Error::ExpectedBoolean)` otherwise. pub fn expect_boolean(actual: &Value) -> EvalexprResult { match actual { diff --git a/src/operator/mod.rs b/src/operator/mod.rs index 4252854..f8b772e 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -146,10 +146,15 @@ impl Operator for Add { fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult { expect_operator_argument_amount(arguments.len(), 2)?; - expect_number(&arguments[0])?; - expect_number(&arguments[1])?; + expect_number_or_string(&arguments[0])?; + expect_number_or_string(&arguments[1])?; - if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { + if let (Ok(a), Ok(b)) = (arguments[0].as_string(), arguments[1].as_string()) { + let mut result = String::with_capacity(a.len() + b.len()); + result.push_str(&a); + result.push_str(&b); + Ok(Value::String(result)) + } else if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { let result = a.checked_add(b); if let Some(result) = result { Ok(Value::Int(result)) @@ -395,10 +400,16 @@ impl Operator for Gt { fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult { expect_operator_argument_amount(arguments.len(), 2)?; - expect_number(&arguments[0])?; - expect_number(&arguments[1])?; + expect_number_or_string(&arguments[0])?; + expect_number_or_string(&arguments[1])?; - if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { + if let (Ok(a), Ok(b)) = (arguments[0].as_string(), arguments[1].as_string()) { + if a > b { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } else if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { if a > b { Ok(Value::Boolean(true)) } else { @@ -425,10 +436,16 @@ impl Operator for Lt { fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult { expect_operator_argument_amount(arguments.len(), 2)?; - expect_number(&arguments[0])?; - expect_number(&arguments[1])?; + expect_number_or_string(&arguments[0])?; + expect_number_or_string(&arguments[1])?; - if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { + if let (Ok(a), Ok(b)) = (arguments[0].as_string(), arguments[1].as_string()) { + if a < b { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } else if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { if a < b { Ok(Value::Boolean(true)) } else { @@ -455,10 +472,16 @@ impl Operator for Geq { fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult { expect_operator_argument_amount(arguments.len(), 2)?; - expect_number(&arguments[0])?; - expect_number(&arguments[1])?; + expect_number_or_string(&arguments[0])?; + expect_number_or_string(&arguments[1])?; - if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { + if let (Ok(a), Ok(b)) = (arguments[0].as_string(), arguments[1].as_string()) { + if a >= b { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } else if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { if a >= b { Ok(Value::Boolean(true)) } else { @@ -485,10 +508,16 @@ impl Operator for Leq { fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult { expect_operator_argument_amount(arguments.len(), 2)?; - expect_number(&arguments[0])?; - expect_number(&arguments[1])?; + expect_number_or_string(&arguments[0])?; + expect_number_or_string(&arguments[1])?; - if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { + if let (Ok(a), Ok(b)) = (arguments[0].as_string(), arguments[1].as_string()) { + if a <= b { + Ok(Value::Boolean(true)) + } else { + Ok(Value::Boolean(false)) + } + } else if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { if a <= b { Ok(Value::Boolean(true)) } else { diff --git a/tests/integration.rs b/tests/integration.rs index 42fa35c..e0e225f 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -551,6 +551,9 @@ fn test_strings() { eval_boolean_with_context("a == \"a string\"", &context), Ok(true) ); + assert_eq!(eval("\"a\" + \"b\""), Ok(Value::from("ab"))); + assert_eq!(eval("\"a\" > \"b\""), Ok(Value::from(false))); + assert_eq!(eval("\"a\" < \"b\""), Ok(Value::from(true))); } #[cfg(feature = "serde")]