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:
parent
502ec0adce
commit
e6c19077b6
@ -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!(
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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<TupleType> {
|
||||
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<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 {
|
||||
|
@ -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])
|
||||
|
Loading…
Reference in New Issue
Block a user