From e6c19077b6811d308711969be9372c88657c9d84 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Thu, 29 Aug 2019 08:56:49 +0300 Subject: [PATCH] Implement value decomposition API * Removed expect_... methods and replaced them with .as_...() methods. This removes the need to import the free-standing methods every time and makes the code cleaner. * Changed all the examples appropriately. Implements #53 --- src/error/display.rs | 1 + src/error/mod.rs | 47 ++++++++++++----------------------------- src/function/builtin.rs | 42 ++++++++++++++++++------------------ src/lib.rs | 9 +++----- src/operator/mod.rs | 34 ++++++++++++++--------------- src/value/mod.rs | 16 +++++++++++++- tests/integration.rs | 14 ++++++------ 7 files changed, 77 insertions(+), 86 deletions(-) diff --git a/src/error/display.rs b/src/error/display.rs index 8b15a49..7db03fc 100644 --- a/src/error/display.rs +++ b/src/error/display.rs @@ -35,6 +35,7 @@ impl fmt::Display for EvalexprError { write!(f, "Expected a Value::Boolean, but got {:?}.", actual) }, ExpectedTuple { actual } => write!(f, "Expected a Value::Tuple, but got {:?}.", actual), + ExpectedFixedLenTuple { expected_len, actual } => write!(f, "Expected a Value::Tuple of len {}, but got {:?}.", expected_len, actual), ExpectedEmpty { actual } => write!(f, "Expected a Value::Empty, but got {:?}.", actual), AppendedToLeafNode => write!(f, "Tried to append a node to a leaf node."), PrecedenceViolation => write!( diff --git a/src/error/mod.rs b/src/error/mod.rs index a053371..96b364d 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -75,6 +75,14 @@ pub enum EvalexprError { actual: Value, }, + /// A tuple value of a certain length was expected. + ExpectedFixedLenTuple { + /// The expected len + expected_len: usize, + /// The actual value. + actual: Value, + }, + /// An empty value was expected. ExpectedEmpty { /// The actual value. @@ -234,6 +242,11 @@ impl EvalexprError { EvalexprError::ExpectedTuple { actual } } + /// Constructs `Error::ExpectedFixedLenTuple{expected_len, actual}`. + pub fn expected_fixed_len_tuple(expected_len: usize, actual: Value) -> Self { + EvalexprError::ExpectedFixedLenTuple { expected_len, actual } + } + /// Constructs `Error::ExpectedEmpty{actual}`. pub fn expected_empty(actual: Value) -> Self { EvalexprError::ExpectedEmpty { actual } @@ -319,24 +332,6 @@ pub fn expect_function_argument_amount(actual: usize, expected: usize) -> Evalex } } -/// Returns `Ok(&str)` if the given value is a `Value::String`, or `Err(Error::ExpectedString)` otherwise. -pub fn expect_string(actual: &Value) -> EvalexprResult<&str> { - match actual { - Value::String(string) => Ok(string), - _ => Err(EvalexprError::expected_string(actual.clone())), - } -} - -/// Returns `Ok(())` if the given value is numeric. -/// Numeric types are `Value::Int` and `Value::Float`. -/// Otherwise, `Err(Error::ExpectedNumber)` is returned. -pub fn expect_number(actual: &Value) -> EvalexprResult<()> { - match actual { - Value::Float(_) | Value::Int(_) => Ok(()), - _ => Err(EvalexprError::expected_number(actual.clone())), - } -} - /// Returns `Ok(())` if the given value is a string or a numeric pub fn expect_number_or_string(actual: &Value) -> EvalexprResult<()> { match actual { @@ -345,22 +340,6 @@ pub fn expect_number_or_string(actual: &Value) -> EvalexprResult<()> { } } -/// 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 { - Value::Boolean(boolean) => Ok(*boolean), - _ => Err(EvalexprError::expected_boolean(actual.clone())), - } -} - -/// Returns `Ok(&[Value])` if the given value is a `Value::Tuple`, or `Err(Error::ExpectedTuple)` otherwise. -pub fn expect_tuple(actual: &Value) -> EvalexprResult<&TupleType> { - match actual { - Value::Tuple(tuple) => Ok(tuple), - _ => Err(EvalexprError::expected_tuple(actual.clone())), - } -} - impl std::error::Error for EvalexprError {} /// Standard result type used by this crate. diff --git a/src/function/builtin.rs b/src/function/builtin.rs index 0d46512..a9e8086 100644 --- a/src/function/builtin.rs +++ b/src/function/builtin.rs @@ -10,16 +10,16 @@ use Value; pub fn builtin_function(identifier: &str) -> Option { match identifier { "min" => Some(Function::new(Box::new(|argument| { - let arguments = expect_tuple(argument)?; + let arguments = argument.as_tuple()?; let mut min_int = IntType::max_value(); let mut min_float = 1.0f64 / 0.0f64; debug_assert!(min_float.is_infinite()); for argument in arguments { if let Value::Float(float) = argument { - min_float = min_float.min(*float); + min_float = min_float.min(float); } else if let Value::Int(int) = argument { - min_int = min_int.min(*int); + min_int = min_int.min(int); } else { return Err(EvalexprError::expected_number(argument.clone())); } @@ -32,16 +32,16 @@ pub fn builtin_function(identifier: &str) -> Option { } }))), "max" => Some(Function::new(Box::new(|argument| { - let arguments = expect_tuple(argument)?; + let arguments = argument.as_tuple()?; let mut max_int = IntType::min_value(); let mut max_float = -1.0f64 / 0.0f64; debug_assert!(max_float.is_infinite()); for argument in arguments { if let Value::Float(float) = argument { - max_float = max_float.max(*float); + max_float = max_float.max(float); } else if let Value::Int(int) = argument { - max_int = max_int.max(*int); + max_int = max_int.max(int); } else { return Err(EvalexprError::expected_number(argument.clone())); } @@ -55,19 +55,19 @@ pub fn builtin_function(identifier: &str) -> Option { }))), "len" => Some(Function::new(Box::new(|argument| { - let subject = expect_string(argument)?; + let subject = argument.as_string()?; Ok(Value::from(subject.len() as i64)) }))), // string functions #[cfg(feature = "regex_support")] "str::regex_matches" => Some(Function::new(Box::new(|argument| { - let arguments = expect_tuple(argument)?; + let arguments = argument.as_tuple()?; - let subject = expect_string(&arguments[0])?; - let re_str = expect_string(&arguments[1])?; - match Regex::new(re_str) { - Ok(re) => Ok(Value::Boolean(re.is_match(subject))), + let subject = arguments[0].as_string()?; + let re_str = arguments[1].as_string()?; + match Regex::new(&re_str) { + Ok(re) => Ok(Value::Boolean(re.is_match(&subject))), Err(err) => Err(EvalexprError::invalid_regex( re_str.to_string(), format!("{}", err), @@ -76,13 +76,13 @@ pub fn builtin_function(identifier: &str) -> Option { }))), #[cfg(feature = "regex_support")] "str::regex_replace" => Some(Function::new(Box::new(|argument| { - let arguments = expect_tuple(argument)?; + let arguments = argument.as_tuple()?; - let subject = expect_string(&arguments[0])?; - let re_str = expect_string(&arguments[1])?; - let repl = expect_string(&arguments[2])?; - match Regex::new(re_str) { - Ok(re) => Ok(Value::String(re.replace_all(subject, repl).to_string())), + let subject = arguments[0].as_string()?; + let re_str = arguments[1].as_string()?; + let repl = arguments[2].as_string()?; + match Regex::new(&re_str) { + Ok(re) => Ok(Value::String(re.replace_all(&subject, repl.as_str()).to_string())), Err(err) => Err(EvalexprError::invalid_regex( re_str.to_string(), format!("{}", err), @@ -90,15 +90,15 @@ pub fn builtin_function(identifier: &str) -> Option { } }))), "str::to_lowercase" => Some(Function::new(Box::new(|argument| { - let subject = expect_string(argument)?; + let subject = argument.as_string()?; Ok(Value::from(subject.to_lowercase())) }))), "str::to_uppercase" => Some(Function::new(Box::new(|argument| { - let subject = expect_string(argument)?; + let subject = argument.as_string()?; Ok(Value::from(subject.to_uppercase())) }))), "str::trim" => Some(Function::new(Box::new(|argument| { - let subject = expect_string(argument)?; + let subject = argument.as_string()?; Ok(Value::from(subject.trim())) }))), _ => None, diff --git a/src/lib.rs b/src/lib.rs index 88a0155..3fbe0fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,24 +49,21 @@ //! //! ```rust //! use evalexpr::*; -//! use evalexpr::error::{expect_number, expect_tuple}; //! //! let context = context_map! { //! "five" => 5, //! "twelve" => 12, //! "f" => Function::new(Box::new(|argument| { -//! if let Value::Int(int) = argument { +//! if let Ok(int) = argument.as_int() { //! Ok(Value::Int(int / 2)) -//! } else if let Value::Float(float) = argument { +//! } else if let Ok(float) = argument.as_float() { //! Ok(Value::Float(float / 2.0)) //! } else { //! Err(EvalexprError::expected_number(argument.clone())) //! } //! })), //! "avg" => Function::new(Box::new(|argument| { -//! let arguments = expect_tuple(argument)?; -//! expect_number(&arguments[0])?; -//! expect_number(&arguments[1])?; +//! let arguments = argument.as_tuple()?; //! //! if let (Value::Int(a), Value::Int(b)) = (&arguments[0], &arguments[1]) { //! Ok(Value::Int((a + b) / 2)) diff --git a/src/operator/mod.rs b/src/operator/mod.rs index a8ddda6..0bc7a6f 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -161,8 +161,8 @@ impl Operator { }, Sub => { expect_operator_argument_amount(arguments.len(), 2)?; - expect_number(&arguments[0])?; - expect_number(&arguments[1])?; + arguments[0].as_number()?; + arguments[1].as_number()?; if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { let result = a.checked_sub(b); @@ -182,7 +182,7 @@ impl Operator { }, Neg => { expect_operator_argument_amount(arguments.len(), 1)?; - expect_number(&arguments[0])?; + arguments[0].as_number()?; if let Ok(a) = arguments[0].as_int() { let result = a.checked_neg(); @@ -197,8 +197,8 @@ impl Operator { }, Mul => { expect_operator_argument_amount(arguments.len(), 2)?; - expect_number(&arguments[0])?; - expect_number(&arguments[1])?; + arguments[0].as_number()?; + arguments[1].as_number()?; if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { let result = a.checked_mul(b); @@ -218,8 +218,8 @@ impl Operator { }, Div => { expect_operator_argument_amount(arguments.len(), 2)?; - expect_number(&arguments[0])?; - expect_number(&arguments[1])?; + arguments[0].as_number()?; + arguments[1].as_number()?; if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { let result = a.checked_div(b); @@ -239,8 +239,8 @@ impl Operator { }, Mod => { expect_operator_argument_amount(arguments.len(), 2)?; - expect_number(&arguments[0])?; - expect_number(&arguments[1])?; + arguments[0].as_number()?; + arguments[1].as_number()?; if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { let result = a.checked_rem(b); @@ -260,8 +260,8 @@ impl Operator { }, Exp => { expect_operator_argument_amount(arguments.len(), 2)?; - expect_number(&arguments[0])?; - expect_number(&arguments[1])?; + arguments[0].as_number()?; + arguments[1].as_number()?; Ok(Value::Float( arguments[0] @@ -390,8 +390,8 @@ impl Operator { }, And => { expect_operator_argument_amount(arguments.len(), 2)?; - let a = expect_boolean(&arguments[0])?; - let b = expect_boolean(&arguments[1])?; + let a = arguments[0].as_boolean()?; + let b = arguments[1].as_boolean()?; if a && b { Ok(Value::Boolean(true)) @@ -401,8 +401,8 @@ impl Operator { }, Or => { expect_operator_argument_amount(arguments.len(), 2)?; - let a = expect_boolean(&arguments[0])?; - let b = expect_boolean(&arguments[1])?; + let a = arguments[0].as_boolean()?; + let b = arguments[1].as_boolean()?; if a || b { Ok(Value::Boolean(true)) @@ -412,7 +412,7 @@ impl Operator { }, Not => { expect_operator_argument_amount(arguments.len(), 1)?; - let a = expect_boolean(&arguments[0])?; + let a = arguments[0].as_boolean()?; if !a { Ok(Value::Boolean(true)) @@ -470,7 +470,7 @@ impl Operator { match self { Assign => { expect_operator_argument_amount(arguments.len(), 2)?; - let target = expect_string(&arguments[0])?; + let target = arguments[0].as_string()?; context.set_value(target.into(), arguments[1].clone())?; Ok(Value::Empty) diff --git a/src/value/mod.rs b/src/value/mod.rs index e12bef9..4549e08 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -135,7 +135,7 @@ impl Value { } } - /// Clones the value stored in `self` as `TupleType`, or returns`Err` if `self` is not a `Value::Tuple`. + /// Clones the value stored in `self` as `TupleType`, or returns `Err` if `self` is not a `Value::Tuple`. pub fn as_tuple(&self) -> EvalexprResult { match self { Value::Tuple(tuple) => Ok(tuple.clone()), @@ -143,6 +143,20 @@ impl Value { } } + /// Clones the value stored in `self` as `TupleType` or returns `Err` if `self` is not a `Value::Tuple` of the required length. + pub fn as_fixed_len_tuple(&self, len: usize) -> EvalexprResult { + match self { + Value::Tuple(tuple) => { + if tuple.len() == len { + Ok(tuple.clone()) + } else { + Err(EvalexprError::expected_fixed_len_tuple(len, self.clone())) + } + }, + value => Err(EvalexprError::expected_tuple(value.clone())), + } + } + /// Returns `()`, or returns`Err` if `self` is not a `Value::Tuple`. pub fn as_empty(&self) -> EvalexprResult<()> { match self { diff --git a/tests/integration.rs b/tests/integration.rs index 86dce15..561e66f 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -183,9 +183,9 @@ fn test_n_ary_functions() { .set_function( "avg".into(), Function::new(Box::new(|argument| { - let arguments = expect_tuple(argument)?; - expect_number(&arguments[0])?; - expect_number(&arguments[1])?; + let arguments = argument.as_tuple()?; + arguments[0].as_number()?; + arguments[1].as_number()?; if let (Value::Int(a), Value::Int(b)) = (&arguments[0], &arguments[1]) { Ok(Value::Int((a + b) / 2)) @@ -201,10 +201,10 @@ fn test_n_ary_functions() { .set_function( "muladd".into(), Function::new(Box::new(|argument| { - let arguments = expect_tuple(argument)?; - expect_number(&arguments[0])?; - expect_number(&arguments[1])?; - expect_number(&arguments[2])?; + let arguments = argument.as_tuple()?; + arguments[0].as_number()?; + arguments[1].as_number()?; + arguments[2].as_number()?; if let (Value::Int(a), Value::Int(b), Value::Int(c)) = (&arguments[0], &arguments[1], &arguments[2])