From 8491bc61fcb203da56b88a240995ca305d1cfd06 Mon Sep 17 00:00:00 2001 From: Edwin Svensson Date: Sun, 30 May 2021 08:52:28 +0200 Subject: [PATCH 1/3] add common math functions --- src/function/builtin.rs | 48 ++++++++++++++++++++++++++++++++++++++--- tests/integration.rs | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/function/builtin.rs b/src/function/builtin.rs index fa776d4..5ea0aac 100644 --- a/src/function/builtin.rs +++ b/src/function/builtin.rs @@ -6,8 +6,49 @@ use crate::{ EvalexprError, Function, Value, ValueType, }; +macro_rules! simple_math { + ($func:ident) => { + Some(Function::new(|argument| { + let num = argument.as_number()?; + Ok(Value::Float(num.$func())) + })) + }; +} + pub fn builtin_function(identifier: &str) -> Option { match identifier { + // Log + "ln" => simple_math!(ln), + "log" => Some(Function::new(|argument| { + let tuple = argument.as_fixed_len_tuple(2)?; + let (a, b) = (tuple[0].as_number()?, tuple[1].as_number()?); + Ok(Value::Float(a.log(b))) + })), + "log2" => simple_math!(log2), + "log10" => simple_math!(log10), + // Cos + "cos" => simple_math!(cos), + "acos" => simple_math!(acos), + "cosh" => simple_math!(cosh), + "acosh" => simple_math!(acosh), + // Sin + "sin" => simple_math!(sin), + "asin" => simple_math!(asin), + "sinh" => simple_math!(sinh), + "asinh" => simple_math!(asinh), + // Tan + "tan" => simple_math!(tan), + "atan" => simple_math!(atan), + "tanh" => simple_math!(tanh), + "atanh" => simple_math!(atanh), + // Root + "sqrt" => simple_math!(sqrt), + "cbrt" => simple_math!(cbrt), + // Rounding + "floor" => simple_math!(floor), + "round" => simple_math!(round), + "ceil" => simple_math!(ceil), + // Other "min" => Some(Function::new(|argument| { let arguments = argument.as_tuple()?; let mut min_int = IntType::max_value(); @@ -52,7 +93,6 @@ pub fn builtin_function(identifier: &str) -> Option { Ok(Value::Float(max_float)) } })), - "len" => Some(Function::new(|argument| { if let Ok(subject) = argument.as_string() { Ok(Value::from(subject.len() as i64)) @@ -65,8 +105,7 @@ pub fn builtin_function(identifier: &str) -> Option { )) } })), - - // string functions + // String functions #[cfg(feature = "regex_support")] "str::regex_matches" => Some(Function::new(|argument| { let arguments = argument.as_tuple()?; @@ -110,6 +149,9 @@ pub fn builtin_function(identifier: &str) -> Option { let subject = argument.as_string()?; Ok(Value::from(subject.trim())) })), + "str::from" => Some(Function::new(|argument| { + Ok(Value::String(argument.to_string())) + })), _ => None, } } diff --git a/tests/integration.rs b/tests/integration.rs index 1bdede7..972c0ac 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -271,10 +271,44 @@ fn test_n_ary_functions() { #[test] fn test_builtin_functions() { + // Log + assert_eq!(eval("ln(2.718281828459045)"), Ok(Value::Float(1.0))); + assert_eq!(eval("log(9, 9)"), Ok(Value::Float(1.0))); + assert_eq!(eval("log2(2)"), Ok(Value::Float(1.0))); + assert_eq!(eval("log10(10)"), Ok(Value::Float(1.0))); + // Cos + assert_eq!(eval("cos(0)"), Ok(Value::Float(1.0))); + assert_eq!(eval("acos(1)"), Ok(Value::Float(0.0))); + assert_eq!(eval("cosh(0)"), Ok(Value::Float(1.0))); + assert_eq!(eval("acosh(1)"), Ok(Value::Float(0.0))); + // Sin + assert_eq!(eval("sin(0)"), Ok(Value::Float(0.0))); + assert_eq!(eval("asin(0)"), Ok(Value::Float(0.0))); + assert_eq!(eval("sinh(0)"), Ok(Value::Float(0.0))); + assert_eq!(eval("asinh(0)"), Ok(Value::Float(0.0))); + // Tan + assert_eq!(eval("tan(0)"), Ok(Value::Float(0.0))); + assert_eq!(eval("atan(0)"), Ok(Value::Float(0.0))); + assert_eq!(eval("tanh(0)"), Ok(Value::Float(0.0))); + assert_eq!(eval("atanh(0)"), Ok(Value::Float(0.0))); + // Root + assert_eq!(eval("sqrt(25)"), Ok(Value::Float(5.0))); + assert_eq!(eval("cbrt(8)"), Ok(Value::Float(2.0))); + // Rounding + assert_eq!(eval("floor(1.1)"), Ok(Value::Float(1.0))); + assert_eq!(eval("floor(1.9)"), Ok(Value::Float(1.0))); + assert_eq!(eval("round(1.1)"), Ok(Value::Float(1.0))); + assert_eq!(eval("round(1.5)"), Ok(Value::Float(2.0))); + assert_eq!(eval("round(2.5)"), Ok(Value::Float(3.0))); + assert_eq!(eval("round(1.9)"), Ok(Value::Float(2.0))); + assert_eq!(eval("ceil(1.1)"), Ok(Value::Float(2.0))); + assert_eq!(eval("ceil(1.9)"), Ok(Value::Float(2.0))); + // Other assert_eq!(eval("min(4.0, 3)"), Ok(Value::Int(3))); assert_eq!(eval("max(4.0, 3)"), Ok(Value::Float(4.0))); assert_eq!(eval("len(\"foobar\")"), Ok(Value::Int(6))); assert_eq!(eval("len(\"a\", \"b\")"), Ok(Value::Int(2))); + // String assert_eq!( eval("str::to_lowercase(\"FOOBAR\")"), Ok(Value::from("foobar")) @@ -287,6 +321,12 @@ fn test_builtin_functions() { eval("str::trim(\" foo bar \")"), Ok(Value::from("foo bar")) ); + assert_eq!(eval("str::from(\"a\")"), Ok(Value::String(String::from("\"a\"")))); + assert_eq!(eval("str::from(1.0)"), Ok(Value::String(String::from("1")))); + assert_eq!(eval("str::from(1)"), Ok(Value::String(String::from("1")))); + assert_eq!(eval("str::from(true)"), Ok(Value::String(String::from("true")))); + assert_eq!(eval("str::from(1, 2, 3)"), Ok(Value::String(String::from("(1, 2, 3)")))); + assert_eq!(eval("str::from()"), Ok(Value::String(String::from("()")))); } #[test] From 10a388aaf31df73d5a60cbf6a9a8f903ad0d3331 Mon Sep 17 00:00:00 2001 From: Edwin Svensson Date: Mon, 31 May 2021 01:04:33 +0200 Subject: [PATCH 2/3] 'namespace' & document math functions --- README.md | 42 +++++++++++++++++++++++++++++++---------- src/function/builtin.rs | 36 +++++++++++++++++------------------ src/lib.rs | 42 +++++++++++++++++++++++++++++++---------- 3 files changed, 82 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index eabe3c3..1d27007 100644 --- a/README.md +++ b/README.md @@ -320,16 +320,38 @@ For more information about user-defined functions, refer to the respective [sect This crate offers a set of builtin functions. -| Identifier | Argument Amount | Argument Types | Description | -|------------|-----------------|----------------|-------------| -| `min` | >= 1 | Numeric | Returns the minimum of the arguments | -| `max` | >= 1 | Numeric | Returns the maximum of the arguments | -| `len` | 1 | String/Tuple | Returns the character length of a string, or the amount of elements in a tuple (not recursively) | -| `str::regex_matches` | 2 | String, String | Returns true if the first argument matches the regex in the second argument (Requires `regex_support` feature flag) | -| `str::regex_replace` | 3 | String, String, String | Returns the first argument with all matches of the regex in the second argument replaced by the third argument (Requires `regex_support` feature flag) | -| `str::to_lowercase` | 1 | String | Returns the lower-case version of the string | -| `str::to_uppercase` | 1 | String | Returns the upper-case version of the string | -| `str::trim` | 1 | String | Strips whitespace from the start and the end of the string | +| Identifier | Argument Amount | Argument Types | Description | +|----------------------|-----------------|------------------------|-------------| +| `min` | >= 1 | Numeric | Returns the minimum of the arguments | +| `max` | >= 1 | Numeric | Returns the maximum of the arguments | +| `len` | 1 | String/Tuple | Returns the character length of a string, or the amount of elements in a tuple (not recursively) | +| `floor` | 1 | Numeric | Returns the largest integer less than or equal to a number | +| `round` | 1 | Numeric | Returns the nearest integer to a number. Round half-way cases away from 0.0 | +| `ceil` | 1 | Numeric | Returns the smallest integer greater than or equal to a number | +| `math::ln` | 1 | Numeric | Returns the natural logarithm of the number | +| `math::log` | 2 | Numeric, Numeric | Returns the logarithm of the number with respect to an arbitrary base | +| `math::log2` | 1 | Numeric | Returns the base 2 logarithm of the number | +| `math::log10` | 1 | Numeric | Returns the base 10 logarithm of the number | +| `math::cos` | 1 | Numeric | Computes the cosine of a number (in radians) | +| `math::acos` | 1 | Numeric | Computes the arccosine of a number. Return value is in radians in the range [0, pi] or NaN if the number is outside the range [-1, 1] | +| `math::cosh` | 1 | Numeric | Hyperbolic cosine function | +| `math::acosh` | 1 | Numeric | Inverse hyperbolic cosine function | +| `math::sin` | 1 | Numeric | Computes the sine of a number (in radians) | +| `math::asin` | 1 | Numeric | Computes the arcsine of a number. Return value is in radians in the range [-pi/2, pi/2] or NaN if the number is outside the range [-1, 1] | +| `math::sinh` | 1 | Numeric | Hyperbolic sine function | +| `math::asinh` | 1 | Numeric | Inverse hyperbolic sine function | +| `math::tan` | 1 | Numeric | Computes the tangent of a number (in radians) | +| `math::atan` | 1 | Numeric | Computes the arctangent of a number. Return value is in radians in the range [-pi/2, pi/2] | +| `math::tanh` | 1 | Numeric | Hyperbolic tangent function | +| `math::atanh` | 1 | Numeric | Inverse hyperbolic tangent function. | +| `math::sqrt` | 1 | Numeric | Returns the square root of a number. Returns NaN if a negative number | +| `math::cbrt` | 1 | Numeric | Returns the cube root of a number | +| `str::regex_matches` | 2 | String, String | Returns true if the first argument matches the regex in the second argument (Requires `regex_support` feature flag) | +| `str::regex_replace` | 3 | String, String, String | Returns the first argument with all matches of the regex in the second argument replaced by the third argument (Requires `regex_support` feature flag) | +| `str::to_lowercase` | 1 | String | Returns the lower-case version of the string | +| `str::to_uppercase` | 1 | String | Returns the upper-case version of the string | +| `str::trim` | 1 | String | Strips whitespace from the start and the end of the string | +| `str::from` | >= 0 | Any | Returns passed value as string | The `min` and `max` functions can deal with a mixture of integer and floating point arguments. If the maximum or minimum is an integer, then an integer is returned. diff --git a/src/function/builtin.rs b/src/function/builtin.rs index 5ea0aac..68f6239 100644 --- a/src/function/builtin.rs +++ b/src/function/builtin.rs @@ -18,32 +18,32 @@ macro_rules! simple_math { pub fn builtin_function(identifier: &str) -> Option { match identifier { // Log - "ln" => simple_math!(ln), - "log" => Some(Function::new(|argument| { + "math::ln" => simple_math!(ln), + "math::log" => Some(Function::new(|argument| { let tuple = argument.as_fixed_len_tuple(2)?; let (a, b) = (tuple[0].as_number()?, tuple[1].as_number()?); Ok(Value::Float(a.log(b))) })), - "log2" => simple_math!(log2), - "log10" => simple_math!(log10), + "math::log2" => simple_math!(log2), + "math::log10" => simple_math!(log10), // Cos - "cos" => simple_math!(cos), - "acos" => simple_math!(acos), - "cosh" => simple_math!(cosh), - "acosh" => simple_math!(acosh), + "math::cos" => simple_math!(cos), + "math::acos" => simple_math!(acos), + "math::cosh" => simple_math!(cosh), + "math::acosh" => simple_math!(acosh), // Sin - "sin" => simple_math!(sin), - "asin" => simple_math!(asin), - "sinh" => simple_math!(sinh), - "asinh" => simple_math!(asinh), + "math::sin" => simple_math!(sin), + "math::asin" => simple_math!(asin), + "math::sinh" => simple_math!(sinh), + "math::asinh" => simple_math!(asinh), // Tan - "tan" => simple_math!(tan), - "atan" => simple_math!(atan), - "tanh" => simple_math!(tanh), - "atanh" => simple_math!(atanh), + "math::tan" => simple_math!(tan), + "math::atan" => simple_math!(atan), + "math::tanh" => simple_math!(tanh), + "math::atanh" => simple_math!(atanh), // Root - "sqrt" => simple_math!(sqrt), - "cbrt" => simple_math!(cbrt), + "math::sqrt" => simple_math!(sqrt), + "math::cbrt" => simple_math!(cbrt), // Rounding "floor" => simple_math!(floor), "round" => simple_math!(round), diff --git a/src/lib.rs b/src/lib.rs index d20d49b..02c26bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -307,16 +307,38 @@ //! //! This crate offers a set of builtin functions. //! -//! | Identifier | Argument Amount | Argument Types | Description | -//! |------------|-----------------|----------------|-------------| -//! | `min` | >= 1 | Numeric | Returns the minimum of the arguments | -//! | `max` | >= 1 | Numeric | Returns the maximum of the arguments | -//! | `len` | 1 | String/Tuple | Returns the character length of a string, or the amount of elements in a tuple (not recursively) | -//! | `str::regex_matches` | 2 | String, String | Returns true if the first argument matches the regex in the second argument (Requires `regex_support` feature flag) | -//! | `str::regex_replace` | 3 | String, String, String | Returns the first argument with all matches of the regex in the second argument replaced by the third argument (Requires `regex_support` feature flag) | -//! | `str::to_lowercase` | 1 | String | Returns the lower-case version of the string | -//! | `str::to_uppercase` | 1 | String | Returns the upper-case version of the string | -//! | `str::trim` | 1 | String | Strips whitespace from the start and the end of the string | +//! | Identifier | Argument Amount | Argument Types | Description | +//! |----------------------|-----------------|------------------------|-------------| +//! | `min` | >= 1 | Numeric | Returns the minimum of the arguments | +//! | `max` | >= 1 | Numeric | Returns the maximum of the arguments | +//! | `len` | 1 | String/Tuple | Returns the character length of a string, or the amount of elements in a tuple (not recursively) | +//! | `floor` | 1 | Numeric | Returns the largest integer less than or equal to a number | +//! | `round` | 1 | Numeric | Returns the nearest integer to a number. Round half-way cases away from 0.0 | +//! | `ceil` | 1 | Numeric | Returns the smallest integer greater than or equal to a number | +//! | `math::ln` | 1 | Numeric | Returns the natural logarithm of the number | +//! | `math::log` | 2 | Numeric, Numeric | Returns the logarithm of the number with respect to an arbitrary base | +//! | `math::log2` | 1 | Numeric | Returns the base 2 logarithm of the number | +//! | `math::log10` | 1 | Numeric | Returns the base 10 logarithm of the number | +//! | `math::cos` | 1 | Numeric | Computes the cosine of a number (in radians) | +//! | `math::acos` | 1 | Numeric | Computes the arccosine of a number. Return value is in radians in the range [0, pi] or NaN if the number is outside the range [-1, 1] | +//! | `math::cosh` | 1 | Numeric | Hyperbolic cosine function | +//! | `math::acosh` | 1 | Numeric | Inverse hyperbolic cosine function | +//! | `math::sin` | 1 | Numeric | Computes the sine of a number (in radians) | +//! | `math::asin` | 1 | Numeric | Computes the arcsine of a number. Return value is in radians in the range [-pi/2, pi/2] or NaN if the number is outside the range [-1, 1] | +//! | `math::sinh` | 1 | Numeric | Hyperbolic sine function | +//! | `math::asinh` | 1 | Numeric | Inverse hyperbolic sine function | +//! | `math::tan` | 1 | Numeric | Computes the tangent of a number (in radians) | +//! | `math::atan` | 1 | Numeric | Computes the arctangent of a number. Return value is in radians in the range [-pi/2, pi/2] | +//! | `math::tanh` | 1 | Numeric | Hyperbolic tangent function | +//! | `math::atanh` | 1 | Numeric | Inverse hyperbolic tangent function. | +//! | `math::sqrt` | 1 | Numeric | Returns the square root of a number. Returns NaN if a negative number | +//! | `math::cbrt` | 1 | Numeric | Returns the cube root of a number | +//! | `str::regex_matches` | 2 | String, String | Returns true if the first argument matches the regex in the second argument (Requires `regex_support` feature flag) | +//! | `str::regex_replace` | 3 | String, String, String | Returns the first argument with all matches of the regex in the second argument replaced by the third argument (Requires `regex_support` feature flag) | +//! | `str::to_lowercase` | 1 | String | Returns the lower-case version of the string | +//! | `str::to_uppercase` | 1 | String | Returns the upper-case version of the string | +//! | `str::trim` | 1 | String | Strips whitespace from the start and the end of the string | +//! | `str::from` | >= 0 | Any | Returns passed value as string | //! //! The `min` and `max` functions can deal with a mixture of integer and floating point arguments. //! If the maximum or minimum is an integer, then an integer is returned. From a25b347c17927a8e0d800552e301acb1d1a673b0 Mon Sep 17 00:00:00 2001 From: Edwin Svensson Date: Mon, 31 May 2021 01:15:01 +0200 Subject: [PATCH 3/3] fix tests --- tests/integration.rs | 51 ++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 972c0ac..c93934c 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -272,28 +272,28 @@ fn test_n_ary_functions() { #[test] fn test_builtin_functions() { // Log - assert_eq!(eval("ln(2.718281828459045)"), Ok(Value::Float(1.0))); - assert_eq!(eval("log(9, 9)"), Ok(Value::Float(1.0))); - assert_eq!(eval("log2(2)"), Ok(Value::Float(1.0))); - assert_eq!(eval("log10(10)"), Ok(Value::Float(1.0))); + assert_eq!(eval("math::ln(2.718281828459045)"), Ok(Value::Float(1.0))); + assert_eq!(eval("math::log(9, 9)"), Ok(Value::Float(1.0))); + assert_eq!(eval("math::log2(2)"), Ok(Value::Float(1.0))); + assert_eq!(eval("math::log10(10)"), Ok(Value::Float(1.0))); // Cos - assert_eq!(eval("cos(0)"), Ok(Value::Float(1.0))); - assert_eq!(eval("acos(1)"), Ok(Value::Float(0.0))); - assert_eq!(eval("cosh(0)"), Ok(Value::Float(1.0))); - assert_eq!(eval("acosh(1)"), Ok(Value::Float(0.0))); + assert_eq!(eval("math::cos(0)"), Ok(Value::Float(1.0))); + assert_eq!(eval("math::acos(1)"), Ok(Value::Float(0.0))); + assert_eq!(eval("math::cosh(0)"), Ok(Value::Float(1.0))); + assert_eq!(eval("math::acosh(1)"), Ok(Value::Float(0.0))); // Sin - assert_eq!(eval("sin(0)"), Ok(Value::Float(0.0))); - assert_eq!(eval("asin(0)"), Ok(Value::Float(0.0))); - assert_eq!(eval("sinh(0)"), Ok(Value::Float(0.0))); - assert_eq!(eval("asinh(0)"), Ok(Value::Float(0.0))); + assert_eq!(eval("math::sin(0)"), Ok(Value::Float(0.0))); + assert_eq!(eval("math::asin(0)"), Ok(Value::Float(0.0))); + assert_eq!(eval("math::sinh(0)"), Ok(Value::Float(0.0))); + assert_eq!(eval("math::asinh(0)"), Ok(Value::Float(0.0))); // Tan - assert_eq!(eval("tan(0)"), Ok(Value::Float(0.0))); - assert_eq!(eval("atan(0)"), Ok(Value::Float(0.0))); - assert_eq!(eval("tanh(0)"), Ok(Value::Float(0.0))); - assert_eq!(eval("atanh(0)"), Ok(Value::Float(0.0))); + assert_eq!(eval("math::tan(0)"), Ok(Value::Float(0.0))); + assert_eq!(eval("math::atan(0)"), Ok(Value::Float(0.0))); + assert_eq!(eval("math::tanh(0)"), Ok(Value::Float(0.0))); + assert_eq!(eval("math::atanh(0)"), Ok(Value::Float(0.0))); // Root - assert_eq!(eval("sqrt(25)"), Ok(Value::Float(5.0))); - assert_eq!(eval("cbrt(8)"), Ok(Value::Float(2.0))); + assert_eq!(eval("math::sqrt(25)"), Ok(Value::Float(5.0))); + assert_eq!(eval("math::cbrt(8)"), Ok(Value::Float(2.0))); // Rounding assert_eq!(eval("floor(1.1)"), Ok(Value::Float(1.0))); assert_eq!(eval("floor(1.9)"), Ok(Value::Float(1.0))); @@ -321,11 +321,20 @@ fn test_builtin_functions() { eval("str::trim(\" foo bar \")"), Ok(Value::from("foo bar")) ); - assert_eq!(eval("str::from(\"a\")"), Ok(Value::String(String::from("\"a\"")))); + assert_eq!( + eval("str::from(\"a\")"), + Ok(Value::String(String::from("\"a\""))) + ); assert_eq!(eval("str::from(1.0)"), Ok(Value::String(String::from("1")))); assert_eq!(eval("str::from(1)"), Ok(Value::String(String::from("1")))); - assert_eq!(eval("str::from(true)"), Ok(Value::String(String::from("true")))); - assert_eq!(eval("str::from(1, 2, 3)"), Ok(Value::String(String::from("(1, 2, 3)")))); + assert_eq!( + eval("str::from(true)"), + Ok(Value::String(String::from("true"))) + ); + assert_eq!( + eval("str::from(1, 2, 3)"), + Ok(Value::String(String::from("(1, 2, 3)"))) + ); assert_eq!(eval("str::from()"), Ok(Value::String(String::from("()")))); }