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)
},
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!(

View File

@ -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<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 {}
/// Standard result type used by this crate.

View File

@ -10,16 +10,16 @@ use Value;
pub fn builtin_function(identifier: &str) -> Option<Function> {
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<Function> {
}
}))),
"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<Function> {
}))),
"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<Function> {
}))),
#[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<Function> {
}
}))),
"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,

View File

@ -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))

View File

@ -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)

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`.
pub fn as_empty(&self) -> EvalexprResult<()> {
match self {

View File

@ -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])