From f6c2ef2fb645e336a9483e5eeee3aa3698251e7f Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Sat, 4 May 2019 13:43:29 +0200 Subject: [PATCH] Remove automatic function argument decomposition Relates to #52 --- src/context/mod.rs | 2 +- src/error/mod.rs | 8 ++++++++ src/function/builtin.rs | 38 +++++++++++++++++----------------- src/function/mod.rs | 29 +++++++++----------------- src/lib.rs | 15 +++++++------- src/operator/mod.rs | 7 +------ tests/integration.rs | 45 +++++++++++++++++------------------------ 7 files changed, 63 insertions(+), 81 deletions(-) diff --git a/src/context/mod.rs b/src/context/mod.rs index e07f9c7..3161027 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -103,7 +103,7 @@ impl Context for HashMapContext { /// /// let ctx = evalexpr::context_map! { /// "x" => 8, -/// "f" => Function::new(None, Box::new(|_| Ok(42.into()) )) +/// "f" => Function::new(Box::new(|_| Ok(42.into()) )) /// }.unwrap(); /// /// assert_eq!(eval_with_context("x + f()", &ctx), Ok(50.into())); diff --git a/src/error/mod.rs b/src/error/mod.rs index 9b73b47..a053371 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -353,6 +353,14 @@ pub fn expect_boolean(actual: &Value) -> EvalexprResult { } } +/// 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 1ff1256..f1c92c1 100644 --- a/src/function/builtin.rs +++ b/src/function/builtin.rs @@ -10,8 +10,8 @@ use Value; pub fn builtin_function(identifier: &str) -> Option { match identifier { "min" => Some(Function::new( - None, - Box::new(|arguments| { + Box::new(|argument| { + let arguments = expect_tuple(argument)?; let mut min_int = IntType::max_value(); let mut min_float = 1.0f64 / 0.0f64; debug_assert!(min_float.is_infinite()); @@ -34,8 +34,8 @@ pub fn builtin_function(identifier: &str) -> Option { }), )), "max" => Some(Function::new( - None, - Box::new(|arguments| { + Box::new(|argument| { + let arguments = expect_tuple(argument)?; let mut max_int = IntType::min_value(); let mut max_float = -1.0f64 / 0.0f64; debug_assert!(max_float.is_infinite()); @@ -59,9 +59,8 @@ pub fn builtin_function(identifier: &str) -> Option { )), "len" => Some(Function::new( - Some(1), - Box::new(|arguments| { - let subject = expect_string(&arguments[0])?; + Box::new(|argument| { + let subject = expect_string(argument)?; Ok(Value::from(subject.len() as i64)) }), )), @@ -69,8 +68,9 @@ pub fn builtin_function(identifier: &str) -> Option { // string functions #[cfg(feature = "regex_support")] "str::regex_matches" => Some(Function::new( - Some(2), - Box::new(|arguments| { + Box::new(|argument| { + let arguments = expect_tuple(argument)?; + let subject = expect_string(&arguments[0])?; let re_str = expect_string(&arguments[1])?; match Regex::new(re_str) { @@ -84,8 +84,9 @@ pub fn builtin_function(identifier: &str) -> Option { )), #[cfg(feature = "regex_support")] "str::regex_replace" => Some(Function::new( - Some(3), - Box::new(|arguments| { + Box::new(|argument| { + let arguments = expect_tuple(argument)?; + let subject = expect_string(&arguments[0])?; let re_str = expect_string(&arguments[1])?; let repl = expect_string(&arguments[2])?; @@ -99,23 +100,20 @@ pub fn builtin_function(identifier: &str) -> Option { }), )), "str::to_lowercase" => Some(Function::new( - Some(1), - Box::new(|arguments| { - let subject = expect_string(&arguments[0])?; + Box::new(|argument| { + let subject = expect_string(argument)?; Ok(Value::from(subject.to_lowercase())) }), )), "str::to_uppercase" => Some(Function::new( - Some(1), - Box::new(|arguments| { - let subject = expect_string(&arguments[0])?; + Box::new(|argument| { + let subject = expect_string(argument)?; Ok(Value::from(subject.to_uppercase())) }), )), "str::trim" => Some(Function::new( - Some(1), - Box::new(|arguments| { - let subject = expect_string(&arguments[0])?; + Box::new(|argument| { + let subject = expect_string(argument)?; Ok(Value::from(subject.trim())) }), )), diff --git a/src/function/mod.rs b/src/function/mod.rs index b861adb..ccab8f0 100644 --- a/src/function/mod.rs +++ b/src/function/mod.rs @@ -1,6 +1,6 @@ use std::fmt; -use error::{self, EvalexprResult}; +use error::{EvalexprResult}; use value::Value; pub(crate) mod builtin; @@ -14,39 +14,29 @@ pub(crate) mod builtin; /// use evalexpr::*; /// /// let mut context = HashMapContext::new(); -/// context.set_function("id".into(), Function::new(Some(1), Box::new(|arguments| { -/// Ok(arguments[0].clone()) +/// context.set_function("id".into(), Function::new(Box::new(|argument| { +/// Ok(argument.clone()) /// }))).unwrap(); // Do proper error handling here /// assert_eq!(eval_with_context("id(4)", &context), Ok(Value::from(4))); /// ``` pub struct Function { - argument_amount: Option, - function: Box EvalexprResult>, + function: Box EvalexprResult>, } impl Function { /// Creates a user-defined function. /// - /// The `argument_amount` is the amount of arguments this function takes. - /// 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`. + /// The `function` is a boxed function that takes a `Value` and returns a `EvalexprResult`. pub fn new( - argument_amount: Option, - function: Box EvalexprResult>, + function: Box EvalexprResult>, ) -> Self { Self { - argument_amount, function, } } - pub(crate) fn call(&self, arguments: &[Value]) -> EvalexprResult { - if let Some(argument_amount) = self.argument_amount { - error::expect_function_argument_amount(arguments.len(), argument_amount)?; - } - - (self.function)(arguments) + pub(crate) fn call(&self, argument: &Value) -> EvalexprResult { + (self.function)(argument) } } @@ -54,8 +44,7 @@ impl fmt::Debug for Function { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!( f, - "Function {{ argument_amount: {:?}, function: [...] }}", - self.argument_amount + "Function {{ [...] }}" ) } } diff --git a/src/lib.rs b/src/lib.rs index 957e42a..bdbb1ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,21 +49,22 @@ //! //! ```rust //! use evalexpr::*; -//! use evalexpr::error::expect_number; +//! use evalexpr::error::{expect_number, expect_tuple}; //! //! let mut context = HashMapContext::new(); //! 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_function("f".into(), Function::new(Some(1) /* argument amount */, Box::new(|arguments| { -//! if let Value::Int(int) = arguments[0] { +//! context.set_function("f".into(), Function::new(Box::new(|argument| { +//! if let Value::Int(int) = argument { //! 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)) //! } else { -//! Err(EvalexprError::expected_number(arguments[0].clone())) +//! Err(EvalexprError::expected_number(argument.clone())) //! } //! }))).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[1])?; //! @@ -339,7 +340,7 @@ //! Ok(free) => assert_eq!(free.eval_with_context(&context), Ok(Value::from(25))), //! Err(error) => { //! () // Handle error -//! }, +//! } //! } //! ``` //! diff --git a/src/operator/mod.rs b/src/operator/mod.rs index 8725b5d..007fdad 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -469,12 +469,7 @@ impl Operator { } FunctionIdentifier { identifier } => { expect_operator_argument_amount(arguments.len(), 1)?; - - let arguments = if let Value::Tuple(arguments) = &arguments[0] { - arguments - } else { - arguments - }; + let arguments = &arguments[0]; if let Some(function) = context.get_function(&identifier) { function.call(arguments) diff --git a/tests/integration.rs b/tests/integration.rs index 02bc439..93bae42 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -138,14 +138,13 @@ fn test_functions() { .set_function( "sub2".to_string(), Function::new( - Some(1), - Box::new(|arguments| { - if let Value::Int(int) = arguments[0] { + Box::new(|argument| { + if let Value::Int(int) = argument { 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)) } 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( "sub2".into(), Function::new( - Some(1), - Box::new(|arguments| { - if let Value::Int(int) = arguments[0] { + Box::new(|argument| { + if let Value::Int(int) = argument { 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)) } 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( "avg".into(), Function::new( - Some(2), - Box::new(|arguments| { + Box::new(|argument| { + let arguments = expect_tuple(argument)?; expect_number(&arguments[0])?; expect_number(&arguments[1])?; @@ -209,8 +207,8 @@ fn test_n_ary_functions() { .set_function( "muladd".into(), Function::new( - Some(3), - Box::new(|arguments| { + Box::new(|argument| { + let arguments = expect_tuple(argument)?; expect_number(&arguments[0])?; expect_number(&arguments[1])?; expect_number(&arguments[2])?; @@ -233,16 +231,11 @@ fn test_n_ary_functions() { .set_function( "count".into(), Function::new( - None, Box::new(|arguments| { - if arguments.len() == 1 { - if arguments[0] == Value::Empty { - Ok(Value::Int(0)) - } else { - Ok(Value::Int(1)) - } - } else { - Ok(Value::Int(arguments.len() as IntType)) + match arguments { + Value::Tuple(tuple) => Ok(Value::from(tuple.len() as IntType)), + Value::Empty => Ok(Value::from(0)), + _ => Ok(Value::from(1)), } }), ), @@ -251,7 +244,7 @@ fn test_n_ary_functions() { context .set_value("five".to_string(), Value::Int(5)) .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!( @@ -266,20 +259,18 @@ fn test_n_ary_functions() { eval_with_context("sub2 avg(3, 6)", &context), Ok(Value::Int(2)) ); - dbg!(build_operator_tree("muladd(3, 6, -4)").unwrap()); assert_eq!( eval_with_context("muladd(3, 6, -4)", &context), Ok(Value::Int(14)) ); 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!( eval_with_context("count(3, 5.5, 2)", &context), Ok(Value::Int(3)) ); 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), Err(EvalexprError::WrongFunctionArgumentAmount{expected: 0, actual: 1})); } #[test]