Remove automatic function argument decomposition

Relates to #52
This commit is contained in:
Sebastian Schmidt 2019-05-04 13:43:29 +02:00
parent 2ca7209d22
commit f6c2ef2fb6
7 changed files with 63 additions and 81 deletions

View File

@ -103,7 +103,7 @@ impl Context for HashMapContext {
/// ///
/// let ctx = evalexpr::context_map! { /// let ctx = evalexpr::context_map! {
/// "x" => 8, /// "x" => 8,
/// "f" => Function::new(None, Box::new(|_| Ok(42.into()) )) /// "f" => Function::new(Box::new(|_| Ok(42.into()) ))
/// }.unwrap(); /// }.unwrap();
/// ///
/// assert_eq!(eval_with_context("x + f()", &ctx), Ok(50.into())); /// assert_eq!(eval_with_context("x + f()", &ctx), Ok(50.into()));

View File

@ -353,6 +353,14 @@ pub fn expect_boolean(actual: &Value) -> EvalexprResult<bool> {
} }
} }
/// 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,8 +10,8 @@ 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( "min" => Some(Function::new(
None, Box::new(|argument| {
Box::new(|arguments| { let arguments = expect_tuple(argument)?;
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());
@ -34,8 +34,8 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
}), }),
)), )),
"max" => Some(Function::new( "max" => Some(Function::new(
None, Box::new(|argument| {
Box::new(|arguments| { let arguments = expect_tuple(argument)?;
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());
@ -59,9 +59,8 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
)), )),
"len" => Some(Function::new( "len" => Some(Function::new(
Some(1), Box::new(|argument| {
Box::new(|arguments| { let subject = expect_string(argument)?;
let subject = expect_string(&arguments[0])?;
Ok(Value::from(subject.len() as i64)) Ok(Value::from(subject.len() as i64))
}), }),
)), )),
@ -69,8 +68,9 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
// string functions // string functions
#[cfg(feature = "regex_support")] #[cfg(feature = "regex_support")]
"str::regex_matches" => Some(Function::new( "str::regex_matches" => Some(Function::new(
Some(2), Box::new(|argument| {
Box::new(|arguments| { let arguments = expect_tuple(argument)?;
let subject = expect_string(&arguments[0])?; let subject = expect_string(&arguments[0])?;
let re_str = expect_string(&arguments[1])?; let re_str = expect_string(&arguments[1])?;
match Regex::new(re_str) { match Regex::new(re_str) {
@ -84,8 +84,9 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
)), )),
#[cfg(feature = "regex_support")] #[cfg(feature = "regex_support")]
"str::regex_replace" => Some(Function::new( "str::regex_replace" => Some(Function::new(
Some(3), Box::new(|argument| {
Box::new(|arguments| { let arguments = expect_tuple(argument)?;
let subject = expect_string(&arguments[0])?; let subject = expect_string(&arguments[0])?;
let re_str = expect_string(&arguments[1])?; let re_str = expect_string(&arguments[1])?;
let repl = expect_string(&arguments[2])?; let repl = expect_string(&arguments[2])?;
@ -99,23 +100,20 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
}), }),
)), )),
"str::to_lowercase" => Some(Function::new( "str::to_lowercase" => Some(Function::new(
Some(1), Box::new(|argument| {
Box::new(|arguments| { let subject = expect_string(argument)?;
let subject = expect_string(&arguments[0])?;
Ok(Value::from(subject.to_lowercase())) Ok(Value::from(subject.to_lowercase()))
}), }),
)), )),
"str::to_uppercase" => Some(Function::new( "str::to_uppercase" => Some(Function::new(
Some(1), Box::new(|argument| {
Box::new(|arguments| { let subject = expect_string(argument)?;
let subject = expect_string(&arguments[0])?;
Ok(Value::from(subject.to_uppercase())) Ok(Value::from(subject.to_uppercase()))
}), }),
)), )),
"str::trim" => Some(Function::new( "str::trim" => Some(Function::new(
Some(1), Box::new(|argument| {
Box::new(|arguments| { let subject = expect_string(argument)?;
let subject = expect_string(&arguments[0])?;
Ok(Value::from(subject.trim())) Ok(Value::from(subject.trim()))
}), }),
)), )),

View File

@ -1,6 +1,6 @@
use std::fmt; use std::fmt;
use error::{self, EvalexprResult}; use error::{EvalexprResult};
use value::Value; use value::Value;
pub(crate) mod builtin; pub(crate) mod builtin;
@ -14,39 +14,29 @@ pub(crate) mod builtin;
/// use evalexpr::*; /// use evalexpr::*;
/// ///
/// let mut context = HashMapContext::new(); /// let mut context = HashMapContext::new();
/// context.set_function("id".into(), Function::new(Some(1), Box::new(|arguments| { /// context.set_function("id".into(), Function::new(Box::new(|argument| {
/// Ok(arguments[0].clone()) /// Ok(argument.clone())
/// }))).unwrap(); // Do proper error handling here /// }))).unwrap(); // Do proper error handling here
/// assert_eq!(eval_with_context("id(4)", &context), Ok(Value::from(4))); /// assert_eq!(eval_with_context("id(4)", &context), Ok(Value::from(4)));
/// ``` /// ```
pub struct Function { pub struct Function {
argument_amount: Option<usize>, function: Box<Fn(&Value) -> EvalexprResult<Value>>,
function: Box<Fn(&[Value]) -> EvalexprResult<Value>>,
} }
impl Function { impl Function {
/// Creates a user-defined function. /// Creates a user-defined function.
/// ///
/// The `argument_amount` is the amount of arguments this function takes. /// The `function` is a boxed function that takes a `Value` and returns a `EvalexprResult<Value, Error>`.
/// It is verified before the actual function is executed, assuming it is not `None`.
///
/// The `function` is a boxed function that takes a slice of values and returns a `EvalexprResult<Value, Error>`.
pub fn new( pub fn new(
argument_amount: Option<usize>, function: Box<Fn(&Value) -> EvalexprResult<Value>>,
function: Box<Fn(&[Value]) -> EvalexprResult<Value>>,
) -> Self { ) -> Self {
Self { Self {
argument_amount,
function, function,
} }
} }
pub(crate) fn call(&self, arguments: &[Value]) -> EvalexprResult<Value> { pub(crate) fn call(&self, argument: &Value) -> EvalexprResult<Value> {
if let Some(argument_amount) = self.argument_amount { (self.function)(argument)
error::expect_function_argument_amount(arguments.len(), argument_amount)?;
}
(self.function)(arguments)
} }
} }
@ -54,8 +44,7 @@ impl fmt::Debug for Function {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!( write!(
f, f,
"Function {{ argument_amount: {:?}, function: [...] }}", "Function {{ [...] }}"
self.argument_amount
) )
} }
} }

View File

@ -49,21 +49,22 @@
//! //!
//! ```rust //! ```rust
//! use evalexpr::*; //! use evalexpr::*;
//! use evalexpr::error::expect_number; //! use evalexpr::error::{expect_number, expect_tuple};
//! //!
//! let mut context = HashMapContext::new(); //! let mut context = HashMapContext::new();
//! context.set_value("five".into(), 5.into()).unwrap(); // Do proper error handling here //! context.set_value("five".into(), 5.into()).unwrap(); // Do proper error handling here
//! context.set_value("twelve".into(), 12.into()).unwrap(); // Do proper error handling here //! context.set_value("twelve".into(), 12.into()).unwrap(); // Do proper error handling here
//! context.set_function("f".into(), Function::new(Some(1) /* argument amount */, Box::new(|arguments| { //! context.set_function("f".into(), Function::new(Box::new(|argument| {
//! if let Value::Int(int) = arguments[0] { //! if let Value::Int(int) = argument {
//! Ok(Value::Int(int / 2)) //! Ok(Value::Int(int / 2))
//! } else if let Value::Float(float) = arguments[0] { //! } else if let Value::Float(float) = argument {
//! Ok(Value::Float(float / 2.0)) //! Ok(Value::Float(float / 2.0))
//! } else { //! } else {
//! Err(EvalexprError::expected_number(arguments[0].clone())) //! Err(EvalexprError::expected_number(argument.clone()))
//! } //! }
//! }))).unwrap(); // Do proper error handling here //! }))).unwrap(); // Do proper error handling here
//! context.set_function("avg".into(), Function::new(Some(2) /* argument amount */, Box::new(|arguments| { //! context.set_function("avg".into(), Function::new(Box::new(|argument| {
//! let arguments = expect_tuple(argument)?;
//! expect_number(&arguments[0])?; //! expect_number(&arguments[0])?;
//! expect_number(&arguments[1])?; //! expect_number(&arguments[1])?;
//! //!
@ -339,7 +340,7 @@
//! Ok(free) => assert_eq!(free.eval_with_context(&context), Ok(Value::from(25))), //! Ok(free) => assert_eq!(free.eval_with_context(&context), Ok(Value::from(25))),
//! Err(error) => { //! Err(error) => {
//! () // Handle error //! () // Handle error
//! }, //! }
//! } //! }
//! ``` //! ```
//! //!

View File

@ -469,12 +469,7 @@ impl Operator {
} }
FunctionIdentifier { identifier } => { FunctionIdentifier { identifier } => {
expect_operator_argument_amount(arguments.len(), 1)?; expect_operator_argument_amount(arguments.len(), 1)?;
let arguments = &arguments[0];
let arguments = if let Value::Tuple(arguments) = &arguments[0] {
arguments
} else {
arguments
};
if let Some(function) = context.get_function(&identifier) { if let Some(function) = context.get_function(&identifier) {
function.call(arguments) function.call(arguments)

View File

@ -138,14 +138,13 @@ fn test_functions() {
.set_function( .set_function(
"sub2".to_string(), "sub2".to_string(),
Function::new( Function::new(
Some(1), Box::new(|argument| {
Box::new(|arguments| { if let Value::Int(int) = argument {
if let Value::Int(int) = arguments[0] {
Ok(Value::Int(int - 2)) Ok(Value::Int(int - 2))
} else if let Value::Float(float) = arguments[0] { } else if let Value::Float(float) = argument {
Ok(Value::Float(float - 2.0)) Ok(Value::Float(float - 2.0))
} else { } else {
Err(EvalexprError::expected_number(arguments[0].clone())) Err(EvalexprError::expected_number(argument.clone()))
} }
}), }),
), ),
@ -172,14 +171,13 @@ fn test_n_ary_functions() {
.set_function( .set_function(
"sub2".into(), "sub2".into(),
Function::new( Function::new(
Some(1), Box::new(|argument| {
Box::new(|arguments| { if let Value::Int(int) = argument {
if let Value::Int(int) = arguments[0] {
Ok(Value::Int(int - 2)) Ok(Value::Int(int - 2))
} else if let Value::Float(float) = arguments[0] { } else if let Value::Float(float) = argument {
Ok(Value::Float(float - 2.0)) Ok(Value::Float(float - 2.0))
} else { } else {
Err(EvalexprError::expected_number(arguments[0].clone())) Err(EvalexprError::expected_number(argument.clone()))
} }
}), }),
), ),
@ -189,8 +187,8 @@ fn test_n_ary_functions() {
.set_function( .set_function(
"avg".into(), "avg".into(),
Function::new( Function::new(
Some(2), Box::new(|argument| {
Box::new(|arguments| { let arguments = expect_tuple(argument)?;
expect_number(&arguments[0])?; expect_number(&arguments[0])?;
expect_number(&arguments[1])?; expect_number(&arguments[1])?;
@ -209,8 +207,8 @@ fn test_n_ary_functions() {
.set_function( .set_function(
"muladd".into(), "muladd".into(),
Function::new( Function::new(
Some(3), Box::new(|argument| {
Box::new(|arguments| { let arguments = expect_tuple(argument)?;
expect_number(&arguments[0])?; expect_number(&arguments[0])?;
expect_number(&arguments[1])?; expect_number(&arguments[1])?;
expect_number(&arguments[2])?; expect_number(&arguments[2])?;
@ -233,16 +231,11 @@ fn test_n_ary_functions() {
.set_function( .set_function(
"count".into(), "count".into(),
Function::new( Function::new(
None,
Box::new(|arguments| { Box::new(|arguments| {
if arguments.len() == 1 { match arguments {
if arguments[0] == Value::Empty { Value::Tuple(tuple) => Ok(Value::from(tuple.len() as IntType)),
Ok(Value::Int(0)) Value::Empty => Ok(Value::from(0)),
} else { _ => Ok(Value::from(1)),
Ok(Value::Int(1))
}
} else {
Ok(Value::Int(arguments.len() as IntType))
} }
}), }),
), ),
@ -251,7 +244,7 @@ fn test_n_ary_functions() {
context context
.set_value("five".to_string(), Value::Int(5)) .set_value("five".to_string(), Value::Int(5))
.unwrap(); .unwrap();
context.set_function("function_four".into(), Function::new(Some(0), Box::new(|_| {Ok(Value::Int(4))}))).unwrap(); context.set_function("function_four".into(), Function::new(Box::new(|_| {Ok(Value::Int(4))}))).unwrap();
assert_eq!(eval_with_context("avg(7, 5)", &context), Ok(Value::Int(6))); assert_eq!(eval_with_context("avg(7, 5)", &context), Ok(Value::Int(6)));
assert_eq!( assert_eq!(
@ -266,20 +259,18 @@ fn test_n_ary_functions() {
eval_with_context("sub2 avg(3, 6)", &context), eval_with_context("sub2 avg(3, 6)", &context),
Ok(Value::Int(2)) Ok(Value::Int(2))
); );
dbg!(build_operator_tree("muladd(3, 6, -4)").unwrap());
assert_eq!( assert_eq!(
eval_with_context("muladd(3, 6, -4)", &context), eval_with_context("muladd(3, 6, -4)", &context),
Ok(Value::Int(14)) Ok(Value::Int(14))
); );
assert_eq!(eval_with_context("count()", &context), Ok(Value::Int(0))); assert_eq!(eval_with_context("count()", &context), Ok(Value::Int(0)));
assert_eq!(eval_with_context("count((1, 2, 3))", &context), Ok(Value::Int(1))); assert_eq!(eval_with_context("count((1, 2, 3))", &context), Ok(Value::Int(3)));
assert_eq!( assert_eq!(
eval_with_context("count(3, 5.5, 2)", &context), eval_with_context("count(3, 5.5, 2)", &context),
Ok(Value::Int(3)) Ok(Value::Int(3))
); );
assert_eq!(eval_with_context("count 5", &context), Ok(Value::Int(1))); assert_eq!(eval_with_context("count 5", &context), Ok(Value::Int(1)));
assert_eq!(eval_with_context("function_four()", &context), Ok(Value::Int(4))); assert_eq!(eval_with_context("function_four()", &context), Ok(Value::Int(4)));
assert_eq!(eval_with_context("function_four(())", &context), Err(EvalexprError::WrongFunctionArgumentAmount{expected: 0, actual: 1}));
} }
#[test] #[test]