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
This commit is contained in:
Sebastian Schmidt 2019-08-29 08:56:49 +03:00
parent 502ec0adce
commit e6c19077b6
7 changed files with 77 additions and 86 deletions

View File

@ -35,6 +35,7 @@ impl fmt::Display for EvalexprError {
write!(f, "Expected a Value::Boolean, but got {:?}.", actual) write!(f, "Expected a Value::Boolean, but got {:?}.", actual)
}, },
ExpectedTuple { actual } => write!(f, "Expected a Value::Tuple, 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), ExpectedEmpty { actual } => write!(f, "Expected a Value::Empty, but got {:?}.", actual),
AppendedToLeafNode => write!(f, "Tried to append a node to a leaf node."), AppendedToLeafNode => write!(f, "Tried to append a node to a leaf node."),
PrecedenceViolation => write!( PrecedenceViolation => write!(

View File

@ -75,6 +75,14 @@ pub enum EvalexprError {
actual: Value, 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. /// An empty value was expected.
ExpectedEmpty { ExpectedEmpty {
/// The actual value. /// The actual value.
@ -234,6 +242,11 @@ impl EvalexprError {
EvalexprError::ExpectedTuple { actual } 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}`. /// Constructs `Error::ExpectedEmpty{actual}`.
pub fn expected_empty(actual: Value) -> Self { pub fn expected_empty(actual: Value) -> Self {
EvalexprError::ExpectedEmpty { actual } 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 /// Returns `Ok(())` if the given value is a string or a numeric
pub fn expect_number_or_string(actual: &Value) -> EvalexprResult<()> { pub fn expect_number_or_string(actual: &Value) -> EvalexprResult<()> {
match actual { 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<bool> {
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 {} impl std::error::Error for EvalexprError {}
/// Standard result type used by this crate. /// Standard result type used by this crate.

View File

@ -10,16 +10,16 @@ use Value;
pub fn builtin_function(identifier: &str) -> Option<Function> { pub fn builtin_function(identifier: &str) -> Option<Function> {
match identifier { match identifier {
"min" => Some(Function::new(Box::new(|argument| { "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_int = IntType::max_value();
let mut min_float = 1.0f64 / 0.0f64; let mut min_float = 1.0f64 / 0.0f64;
debug_assert!(min_float.is_infinite()); debug_assert!(min_float.is_infinite());
for argument in arguments { for argument in arguments {
if let Value::Float(float) = argument { 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 { } else if let Value::Int(int) = argument {
min_int = min_int.min(*int); min_int = min_int.min(int);
} else { } else {
return Err(EvalexprError::expected_number(argument.clone())); return Err(EvalexprError::expected_number(argument.clone()));
} }
@ -32,16 +32,16 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
} }
}))), }))),
"max" => Some(Function::new(Box::new(|argument| { "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_int = IntType::min_value();
let mut max_float = -1.0f64 / 0.0f64; let mut max_float = -1.0f64 / 0.0f64;
debug_assert!(max_float.is_infinite()); debug_assert!(max_float.is_infinite());
for argument in arguments { for argument in arguments {
if let Value::Float(float) = argument { 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 { } else if let Value::Int(int) = argument {
max_int = max_int.max(*int); max_int = max_int.max(int);
} else { } else {
return Err(EvalexprError::expected_number(argument.clone())); return Err(EvalexprError::expected_number(argument.clone()));
} }
@ -55,19 +55,19 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
}))), }))),
"len" => Some(Function::new(Box::new(|argument| { "len" => Some(Function::new(Box::new(|argument| {
let subject = expect_string(argument)?; let subject = argument.as_string()?;
Ok(Value::from(subject.len() as i64)) Ok(Value::from(subject.len() as i64))
}))), }))),
// string functions // string functions
#[cfg(feature = "regex_support")] #[cfg(feature = "regex_support")]
"str::regex_matches" => Some(Function::new(Box::new(|argument| { "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 subject = arguments[0].as_string()?;
let re_str = expect_string(&arguments[1])?; let re_str = arguments[1].as_string()?;
match Regex::new(re_str) { match Regex::new(&re_str) {
Ok(re) => Ok(Value::Boolean(re.is_match(subject))), Ok(re) => Ok(Value::Boolean(re.is_match(&subject))),
Err(err) => Err(EvalexprError::invalid_regex( Err(err) => Err(EvalexprError::invalid_regex(
re_str.to_string(), re_str.to_string(),
format!("{}", err), format!("{}", err),
@ -76,13 +76,13 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
}))), }))),
#[cfg(feature = "regex_support")] #[cfg(feature = "regex_support")]
"str::regex_replace" => Some(Function::new(Box::new(|argument| { "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 subject = arguments[0].as_string()?;
let re_str = expect_string(&arguments[1])?; let re_str = arguments[1].as_string()?;
let repl = expect_string(&arguments[2])?; let repl = arguments[2].as_string()?;
match Regex::new(re_str) { match Regex::new(&re_str) {
Ok(re) => Ok(Value::String(re.replace_all(subject, repl).to_string())), Ok(re) => Ok(Value::String(re.replace_all(&subject, repl.as_str()).to_string())),
Err(err) => Err(EvalexprError::invalid_regex( Err(err) => Err(EvalexprError::invalid_regex(
re_str.to_string(), re_str.to_string(),
format!("{}", err), format!("{}", err),
@ -90,15 +90,15 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
} }
}))), }))),
"str::to_lowercase" => Some(Function::new(Box::new(|argument| { "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())) Ok(Value::from(subject.to_lowercase()))
}))), }))),
"str::to_uppercase" => Some(Function::new(Box::new(|argument| { "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())) Ok(Value::from(subject.to_uppercase()))
}))), }))),
"str::trim" => Some(Function::new(Box::new(|argument| { "str::trim" => Some(Function::new(Box::new(|argument| {
let subject = expect_string(argument)?; let subject = argument.as_string()?;
Ok(Value::from(subject.trim())) Ok(Value::from(subject.trim()))
}))), }))),
_ => None, _ => None,

View File

@ -49,24 +49,21 @@
//! //!
//! ```rust //! ```rust
//! use evalexpr::*; //! use evalexpr::*;
//! use evalexpr::error::{expect_number, expect_tuple};
//! //!
//! let context = context_map! { //! let context = context_map! {
//! "five" => 5, //! "five" => 5,
//! "twelve" => 12, //! "twelve" => 12,
//! "f" => Function::new(Box::new(|argument| { //! "f" => Function::new(Box::new(|argument| {
//! if let Value::Int(int) = argument { //! if let Ok(int) = argument.as_int() {
//! Ok(Value::Int(int / 2)) //! 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)) //! Ok(Value::Float(float / 2.0))
//! } else { //! } else {
//! Err(EvalexprError::expected_number(argument.clone())) //! Err(EvalexprError::expected_number(argument.clone()))
//! } //! }
//! })), //! })),
//! "avg" => Function::new(Box::new(|argument| { //! "avg" => Function::new(Box::new(|argument| {
//! let arguments = expect_tuple(argument)?; //! let arguments = argument.as_tuple()?;
//! expect_number(&arguments[0])?;
//! expect_number(&arguments[1])?;
//! //!
//! if let (Value::Int(a), Value::Int(b)) = (&arguments[0], &arguments[1]) { //! if let (Value::Int(a), Value::Int(b)) = (&arguments[0], &arguments[1]) {
//! Ok(Value::Int((a + b) / 2)) //! Ok(Value::Int((a + b) / 2))

View File

@ -161,8 +161,8 @@ impl Operator {
}, },
Sub => { Sub => {
expect_operator_argument_amount(arguments.len(), 2)?; expect_operator_argument_amount(arguments.len(), 2)?;
expect_number(&arguments[0])?; arguments[0].as_number()?;
expect_number(&arguments[1])?; arguments[1].as_number()?;
if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) {
let result = a.checked_sub(b); let result = a.checked_sub(b);
@ -182,7 +182,7 @@ impl Operator {
}, },
Neg => { Neg => {
expect_operator_argument_amount(arguments.len(), 1)?; expect_operator_argument_amount(arguments.len(), 1)?;
expect_number(&arguments[0])?; arguments[0].as_number()?;
if let Ok(a) = arguments[0].as_int() { if let Ok(a) = arguments[0].as_int() {
let result = a.checked_neg(); let result = a.checked_neg();
@ -197,8 +197,8 @@ impl Operator {
}, },
Mul => { Mul => {
expect_operator_argument_amount(arguments.len(), 2)?; expect_operator_argument_amount(arguments.len(), 2)?;
expect_number(&arguments[0])?; arguments[0].as_number()?;
expect_number(&arguments[1])?; arguments[1].as_number()?;
if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) {
let result = a.checked_mul(b); let result = a.checked_mul(b);
@ -218,8 +218,8 @@ impl Operator {
}, },
Div => { Div => {
expect_operator_argument_amount(arguments.len(), 2)?; expect_operator_argument_amount(arguments.len(), 2)?;
expect_number(&arguments[0])?; arguments[0].as_number()?;
expect_number(&arguments[1])?; arguments[1].as_number()?;
if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) {
let result = a.checked_div(b); let result = a.checked_div(b);
@ -239,8 +239,8 @@ impl Operator {
}, },
Mod => { Mod => {
expect_operator_argument_amount(arguments.len(), 2)?; expect_operator_argument_amount(arguments.len(), 2)?;
expect_number(&arguments[0])?; arguments[0].as_number()?;
expect_number(&arguments[1])?; arguments[1].as_number()?;
if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) { if let (Ok(a), Ok(b)) = (arguments[0].as_int(), arguments[1].as_int()) {
let result = a.checked_rem(b); let result = a.checked_rem(b);
@ -260,8 +260,8 @@ impl Operator {
}, },
Exp => { Exp => {
expect_operator_argument_amount(arguments.len(), 2)?; expect_operator_argument_amount(arguments.len(), 2)?;
expect_number(&arguments[0])?; arguments[0].as_number()?;
expect_number(&arguments[1])?; arguments[1].as_number()?;
Ok(Value::Float( Ok(Value::Float(
arguments[0] arguments[0]
@ -390,8 +390,8 @@ impl Operator {
}, },
And => { And => {
expect_operator_argument_amount(arguments.len(), 2)?; expect_operator_argument_amount(arguments.len(), 2)?;
let a = expect_boolean(&arguments[0])?; let a = arguments[0].as_boolean()?;
let b = expect_boolean(&arguments[1])?; let b = arguments[1].as_boolean()?;
if a && b { if a && b {
Ok(Value::Boolean(true)) Ok(Value::Boolean(true))
@ -401,8 +401,8 @@ impl Operator {
}, },
Or => { Or => {
expect_operator_argument_amount(arguments.len(), 2)?; expect_operator_argument_amount(arguments.len(), 2)?;
let a = expect_boolean(&arguments[0])?; let a = arguments[0].as_boolean()?;
let b = expect_boolean(&arguments[1])?; let b = arguments[1].as_boolean()?;
if a || b { if a || b {
Ok(Value::Boolean(true)) Ok(Value::Boolean(true))
@ -412,7 +412,7 @@ impl Operator {
}, },
Not => { Not => {
expect_operator_argument_amount(arguments.len(), 1)?; expect_operator_argument_amount(arguments.len(), 1)?;
let a = expect_boolean(&arguments[0])?; let a = arguments[0].as_boolean()?;
if !a { if !a {
Ok(Value::Boolean(true)) Ok(Value::Boolean(true))
@ -470,7 +470,7 @@ impl Operator {
match self { match self {
Assign => { Assign => {
expect_operator_argument_amount(arguments.len(), 2)?; 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())?; context.set_value(target.into(), arguments[1].clone())?;
Ok(Value::Empty) Ok(Value::Empty)

View File

@ -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<TupleType> {
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`. /// Returns `()`, or returns`Err` if `self` is not a `Value::Tuple`.
pub fn as_empty(&self) -> EvalexprResult<()> { pub fn as_empty(&self) -> EvalexprResult<()> {
match self { match self {

View File

@ -183,9 +183,9 @@ fn test_n_ary_functions() {
.set_function( .set_function(
"avg".into(), "avg".into(),
Function::new(Box::new(|argument| { Function::new(Box::new(|argument| {
let arguments = expect_tuple(argument)?; let arguments = argument.as_tuple()?;
expect_number(&arguments[0])?; arguments[0].as_number()?;
expect_number(&arguments[1])?; arguments[1].as_number()?;
if let (Value::Int(a), Value::Int(b)) = (&arguments[0], &arguments[1]) { if let (Value::Int(a), Value::Int(b)) = (&arguments[0], &arguments[1]) {
Ok(Value::Int((a + b) / 2)) Ok(Value::Int((a + b) / 2))
@ -201,10 +201,10 @@ fn test_n_ary_functions() {
.set_function( .set_function(
"muladd".into(), "muladd".into(),
Function::new(Box::new(|argument| { Function::new(Box::new(|argument| {
let arguments = expect_tuple(argument)?; let arguments = argument.as_tuple()?;
expect_number(&arguments[0])?; arguments[0].as_number()?;
expect_number(&arguments[1])?; arguments[1].as_number()?;
expect_number(&arguments[2])?; arguments[2].as_number()?;
if let (Value::Int(a), Value::Int(b), Value::Int(c)) = if let (Value::Int(a), Value::Int(b), Value::Int(c)) =
(&arguments[0], &arguments[1], &arguments[2]) (&arguments[0], &arguments[1], &arguments[2])