Support addition and comparison of strings.

This commit is contained in:
Quest 2019-04-05 23:07:54 +02:00
parent 725472ef96
commit a9c45307dd
4 changed files with 70 additions and 15 deletions

View File

@ -24,6 +24,9 @@ impl fmt::Display for EvalexprError {
ExpectedNumber { actual } => { ExpectedNumber { actual } => {
write!(f, "Expected a Value::Number, but got {:?}.", 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 } => { ExpectedBoolean { actual } => {
write!(f, "Expected a Value::Boolean, but got {:?}.", actual) write!(f, "Expected a Value::Boolean, but got {:?}.", actual)
}, },

View File

@ -56,6 +56,13 @@ pub enum EvalexprError {
actual: Value, 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. /// A boolean value was expected.
ExpectedBoolean { ExpectedBoolean {
/// The actual value. /// The actual value.
@ -204,6 +211,11 @@ impl EvalexprError {
EvalexprError::ExpectedNumber { actual } EvalexprError::ExpectedNumber { actual }
} }
/// Constructs `Error::ExpectedNumberOrString{actual}`.
pub fn expected_number_or_string(actual: Value) -> Self {
EvalexprError::ExpectedNumberOrString { actual }
}
/// Constructs `Error::ExpectedBoolean{actual}`. /// Constructs `Error::ExpectedBoolean{actual}`.
pub fn expected_boolean(actual: Value) -> Self { pub fn expected_boolean(actual: Value) -> Self {
EvalexprError::ExpectedBoolean { actual } 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. /// Returns `Ok(bool)` if the given value is a `Value::Boolean`, or `Err(Error::ExpectedBoolean)` otherwise.
pub fn expect_boolean(actual: &Value) -> EvalexprResult<bool> { pub fn expect_boolean(actual: &Value) -> EvalexprResult<bool> {
match actual { match actual {

View File

@ -146,10 +146,15 @@ impl Operator for Add {
fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult<Value> { fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult<Value> {
expect_operator_argument_amount(arguments.len(), 2)?; expect_operator_argument_amount(arguments.len(), 2)?;
expect_number(&arguments[0])?; expect_number_or_string(&arguments[0])?;
expect_number(&arguments[1])?; 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); let result = a.checked_add(b);
if let Some(result) = result { if let Some(result) = result {
Ok(Value::Int(result)) Ok(Value::Int(result))
@ -395,10 +400,16 @@ impl Operator for Gt {
fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult<Value> { fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult<Value> {
expect_operator_argument_amount(arguments.len(), 2)?; expect_operator_argument_amount(arguments.len(), 2)?;
expect_number(&arguments[0])?; expect_number_or_string(&arguments[0])?;
expect_number(&arguments[1])?; 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 { if a > b {
Ok(Value::Boolean(true)) Ok(Value::Boolean(true))
} else { } else {
@ -425,10 +436,16 @@ impl Operator for Lt {
fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult<Value> { fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult<Value> {
expect_operator_argument_amount(arguments.len(), 2)?; expect_operator_argument_amount(arguments.len(), 2)?;
expect_number(&arguments[0])?; expect_number_or_string(&arguments[0])?;
expect_number(&arguments[1])?; 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 { if a < b {
Ok(Value::Boolean(true)) Ok(Value::Boolean(true))
} else { } else {
@ -455,10 +472,16 @@ impl Operator for Geq {
fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult<Value> { fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult<Value> {
expect_operator_argument_amount(arguments.len(), 2)?; expect_operator_argument_amount(arguments.len(), 2)?;
expect_number(&arguments[0])?; expect_number_or_string(&arguments[0])?;
expect_number(&arguments[1])?; 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 { if a >= b {
Ok(Value::Boolean(true)) Ok(Value::Boolean(true))
} else { } else {
@ -485,10 +508,16 @@ impl Operator for Leq {
fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult<Value> { fn eval(&self, arguments: &[Value], _context: &Context) -> EvalexprResult<Value> {
expect_operator_argument_amount(arguments.len(), 2)?; expect_operator_argument_amount(arguments.len(), 2)?;
expect_number(&arguments[0])?; expect_number_or_string(&arguments[0])?;
expect_number(&arguments[1])?; 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 { if a <= b {
Ok(Value::Boolean(true)) Ok(Value::Boolean(true))
} else { } else {

View File

@ -551,6 +551,9 @@ fn test_strings() {
eval_boolean_with_context("a == \"a string\"", &context), eval_boolean_with_context("a == \"a string\"", &context),
Ok(true) 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")] #[cfg(feature = "serde")]